Tools¶
Decorator¶
The primary entry point — wrap a Python function and you get a Tool
the agent can call. Parameters are introspected from the type hints
and the JSON Schema is generated automatically.
tool ¶
tool(fn: Callable[P, R] | None = None, *, name: str | None = None, description: str | None = None, idempotent: bool = False) -> Tool | Callable[[Callable[P, R]], Tool]
Decorator to create a tool from a function.
Usage
@tool def search(query: str) -> str: '''Search the knowledge base.''' return "results..."
@tool(name="custom_name", description="Custom description") def my_tool(x: int) -> int: return x * 2
@tool(idempotent=True) def book_flight(flight_id: str, customer_id: str) -> dict: '''Book a flight — safe to mark idempotent because repeated calls with the same flight/customer would create duplicate bookings, which we never want.''' ...
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[P, R] | None
|
The function to wrap |
None
|
name
|
str | None
|
Override tool name (defaults to function name) |
None
|
description
|
str | None
|
Override description (defaults to docstring) |
None
|
idempotent
|
bool
|
If True, the ReAct loop deduplicates calls with matching (name, arguments) within a single agent run. Prevents duplicate side-effects when a model re-issues a tool call it has already made this turn. |
False
|
Returns:
| Type | Description |
|---|---|
Tool | Callable[[Callable[P, R]], Tool]
|
Tool instance |
Source code in src/locus/tools/decorator.py
Tool ¶
Bases: BaseModel
A tool that can be called by agents.
Created via the @tool decorator.
idempotent
class-attribute
instance-attribute
¶
When True, the ReAct loop deduplicates calls: if the model emits the same (tool_name, arguments) combination that has already been executed earlier in the current agent run, the prior result is reused and the tool function is not invoked again. Use for tools that either have side-effects you don't want duplicated (bookings, transfers, writes) or whose output is stable across the run (config/date lookups).
execute
async
¶
Execute the tool with given arguments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ctx
|
ToolContext | None
|
Optional tool context (injected if function accepts it) |
None
|
**kwargs
|
Any
|
Tool arguments |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
Tool result |
Source code in src/locus/tools/decorator.py
to_openai_schema ¶
Get OpenAI-compatible tool schema.
Source code in src/locus/tools/decorator.py
Tool context¶
Inject per-call context (the agent's state, custom metadata, the
hook orchestrator) into a tool by declaring a ToolContext parameter.
ToolContext ¶
Bases: BaseModel
Context passed to tools during execution.
Provides access to agent state, metadata, and utilities.
get_metadata ¶
Registry¶
The agent's compiled tool collection. Built once during
Agent.__init__; mutating config.tools directly afterwards has no
effect (use Agent.add_tool / add_tools instead).
ToolRegistry ¶
Bases: BaseModel
Registry for managing available tools.
Handles tool registration, lookup, and schema generation.
Executors¶
The strategy the agent uses to run a batch of tool calls. The default
is ConcurrentExecutor (parallel up to max_concurrency);
SequentialExecutor runs them one at a time for tools that share
non-thread-safe state.
ToolExecutor ¶
Bases: BaseModel, ABC
Base class for tool execution strategies.
Subclasses implement different execution patterns (sequential, concurrent, rate-limited, etc.)
execute
abstractmethod
async
¶
execute(tool_calls: list[ToolCall], registry: ToolRegistry, ctx_factory: ToolContextFactory | None = None) -> list[ToolResult]
Execute a batch of tool calls.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tool_calls
|
list[ToolCall]
|
Tool calls to execute |
required |
registry
|
ToolRegistry
|
Tool registry to look up tools |
required |
ctx_factory
|
ToolContextFactory | None
|
Optional factory for creating tool contexts |
None
|
Returns:
| Type | Description |
|---|---|
list[ToolResult]
|
List of tool results |
Source code in src/locus/tools/executor.py
execute_streaming
async
¶
execute_streaming(tool_calls: list[ToolCall], registry: ToolRegistry, ctx_factory: ToolContextFactory | None = None) -> AsyncIterator[tuple[int, ToolResult]]
Yield (input_index, result) as each tool call completes.
The default implementation falls back to :meth:execute and yields
in input order — fine for executors with no useful streaming
semantics (sequential, single-shot rate-limited). Concurrent
executors override this to stream results in completion order.
Breaking out of the consumer async for (or raising inside its
body) must cancel any tasks the executor has in flight. Concrete
implementations are responsible for that guarantee — the runtime
loop relies on it for interrupt-driven sibling cancellation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tool_calls
|
list[ToolCall]
|
Tool calls to execute. |
required |
registry
|
ToolRegistry
|
Tool registry to look up tools. |
required |
ctx_factory
|
ToolContextFactory | None
|
Optional factory for creating tool contexts. |
None
|
Yields:
| Type | Description |
|---|---|
AsyncIterator[tuple[int, ToolResult]]
|
Tuples of |
AsyncIterator[tuple[int, ToolResult]]
|
for concurrent executors, input order for sequential ones. |
Source code in src/locus/tools/executor.py
ConcurrentExecutor ¶
Bases: ToolExecutor
Execute tools concurrently with optional concurrency limit.
execute
async
¶
execute(tool_calls: list[ToolCall], registry: ToolRegistry, ctx_factory: ToolContextFactory | None = None) -> list[ToolResult]
Execute tools concurrently.
Source code in src/locus/tools/executor.py
execute_streaming
async
¶
execute_streaming(tool_calls: list[ToolCall], registry: ToolRegistry, ctx_factory: ToolContextFactory | None = None) -> AsyncIterator[tuple[int, ToolResult]]
Yield (input_index, result) in completion order.
Fan out one asyncio.Task per tool call (bounded by
max_concurrency via a shared semaphore). Results land on a
queue as they finish; the consumer iterates the queue and sees
events in completion order. If the consumer breaks early or
raises inside the async for body, the finally cancels
every still-in-flight task — that's how the runtime loop
propagates an interrupt to siblings.
We deliberately use create_task + a stop-marker pattern
instead of asyncio.TaskGroup: TaskGroup blocks __aexit__
on every task finishing, which deadlocks the streaming consumer
when a producer is cancelled (the cancelled task never puts on
the queue, so the consumer's queue.get() blocks forever).
The per-task finally here unconditionally posts a
stop-marker so the consumer always learns when each task is
done, even on cancellation.
Source code in src/locus/tools/executor.py
SequentialExecutor ¶
Bases: ToolExecutor
Execute tools one at a time.
execute
async
¶
execute(tool_calls: list[ToolCall], registry: ToolRegistry, ctx_factory: ToolContextFactory | None = None) -> list[ToolResult]
Execute tools sequentially.
Source code in src/locus/tools/executor.py
execute_streaming
async
¶
execute_streaming(tool_calls: list[ToolCall], registry: ToolRegistry, ctx_factory: ToolContextFactory | None = None) -> AsyncIterator[tuple[int, ToolResult]]
Yield (input_index, result) per call as it finishes.
Sequential execution means input order == completion order; this impl exists so the runtime loop can treat both executors through the same streaming interface. Breaking from the consumer simply stops further calls — there are no siblings to cancel.
Source code in src/locus/tools/executor.py
Schema generation¶
JSON Schema generation from Python type hints / Pydantic models —
used by the @tool decorator but also callable directly when you
need a schema for an external system (e.g. an MCP server).
generate_schema ¶
Generate OpenAI-compatible tool schema from a function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[..., Any]
|
The function to generate schema for |
required |
description
|
str | None
|
Override description (uses docstring if not provided) |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Tool schema in OpenAI function format |
Source code in src/locus/tools/schema.py
pydantic_to_json_schema ¶
Built-in tools¶
get_today_date ¶
Return today's date plus common reference points for date arithmetic.
Call this whenever the user mentions a relative or partial date ("tomorrow", "next Monday", "in ten days", "April 20") so you can convert to an explicit YYYY-MM-DD before calling a date-sensitive tool.
Returns:
| Type | Description |
|---|---|
dict
|
A dict with: |
dict
|
|
dict
|
|
dict
|
|
dict
|
|
dict
|
|
dict
|
|