Skip to content

agentkit.tools

The Tool Protocol, FunctionTool, the @tool decorator, the ToolRegistry, and the as_tool adapter that turns an Agent into a tool callable by another agent.

Tool Protocol stack — peer of agentkit.memory and agentkit.skills.

The Tool Protocol is the Command-pattern surface every action an agent can request must satisfy. FunctionTool is the canonical impl wrapping a Python callable, with the @tool decorator as the ergonomic front door. ToolRegistry is the Composite holding many tools behind a single lookup-by-name surface.

Tool

Bases: Protocol

An action an agent can request — Command pattern done as a Protocol.

Any object satisfying this shape IS a Tool: it carries a stable name + description, a JSON schema describing its input args, an optional output_schema for return-value validation, flags for side_effecting and requires_approval so the runtime can make dispatch decisions, and an async run(args, ctx) that actually executes.

FunctionTool is the canonical impl wrapping a Python callable. Others may implement directly — remote-procedure tools, MCP tools, skill-as-tool adapters. The framework dispatches tools through the middleware chain (audit / approval-gate / output-coerce / retry) regardless of which concrete impl backs them.

schema may be None (loop-invisible tool advertising no schema) or a ToolSchema; output_schema accepts the same shapes FunctionTool.output_schema does — a JSON-Schema dict, a Pydantic / dataclass / attrs class, or None (no check). run.args is Mapping[str, Any] — a plain dict OR a MappingProxyType handed down from a ToolCall both satisfy it.

ToolDefinitionError

Bases: ValueError

Raised at decoration/registration time when a tool fails the framework's wiring contract: missing/thin docstring, missing side_effecting= declaration, or other static defects the framework can catch before the agent ever runs. Subclasses ValueError so existing except ValueError paths still trip — the failure mode is genuinely a bad value.

ToolShapeError

ToolShapeError(
    tool_name: str,
    expected: str,
    raw: Any,
    errors: list[str] | None = None,
)

Bases: Exception

A tool's result didn't match its declared output_schema.

Raised by :meth:FunctionTool.run AFTER the function executed successfully but the result failed schema validation. The retry middleware catches this and reflects the error back to the model as a tool-call failure — the model sees a structured "tool returned a value that doesn't match its declared output" message and can re-issue the call (often with different args) or pivot.

Distinct from :class:OutputCoercionError (which is about MODEL response coercion); tool shape mismatches have a different fire site (after the tool function ran) and a different recovery shape on the model side (the model didn't author the bad value — the tool did).

FileTool

FileTool(backend: Any = None, *, root: str = '/memories')

The memory(command=…) tool. Declares description and output_schema so isinstance(FileTool(), Tool) holds under the @runtime_checkable :class:Tool Protocol.

InMemoryFiles

InMemoryFiles(root: str = '/memories')

A minimal in-memory file tree (path -> text). The backend protocol is async so a real durable/filesystem backend does its blocking I/O off the loop (via to_thread) without stalling it — in-memory ops are instant but stay async def to keep the seam uniform (async-first).

create async

create(
    path: str, file_text: str, *, overwrite: bool = False
) -> str

Create a file. Refuses to silently clobber an existing path — pass overwrite=True to replace deliberately. An unconditional write would actively lie about what happened when the path already held a different note from a prior run. Pairs with rename's no-clobber semantics so all destructive writes are explicit.

FunctionTool dataclass

FunctionTool(
    name: str,
    fn: Callable[[Any, Any], Any],
    description: str,
    side_effecting: bool,
    schema: Any = None,
    idempotent: bool = False,
    requires_approval: bool = False,
    caps: tuple[str, ...] = (),
    url_arg: str | None = None,
    output_schema: Any = None,
)

output_schema class-attribute instance-attribute

output_schema: Any = None

Optional schema the tool's RESULT must match. Pydantic BaseModel / dataclass / attrs class / raw JSON Schema dict — same accepted shapes as Agent.output=. When set, the tool's return value is validated through adapt(output_schema).validate(result) before the framework hands it back to the model.

A mismatch drops a tool.shape_mismatch span event on the open execute_tool span and raises :class:ToolShapeError — catchable by the retry middleware so the model can see the structured error and recover (re-issue the call with different args or pivot).

None (default) means NO check — fast path. Tools that already return well-typed Python objects get zero overhead. from_callable auto-sets this from the function's return-type annotation when the annotation is a Pydantic / dataclass / attrs class; the caller can override (or opt out via output_schema=None) on the @tool decorator.

run async

run(args: Any, ctx: Ctx) -> Any

Always-async run — bridges a sync fn off the event loop so a blocking tool never stalls it.

When an output_schema is wired on this tool, the result is run through adapter.validate(result) AFTER the function returns. A schema mismatch drops a tool.shape_mismatch event on the currently-open span (the execute_tool span the tracing middleware just opened) and raises :class:ToolShapeError so the retry middleware can reflect the error back to the model.

Fast path: no adapter wired → one if branch, zero overhead.

from_callable classmethod

from_callable(
    func: Callable[..., Any],
    *,
    name: str | None = None,
    description: str | None = None,
    side_effecting: bool = False,
    idempotent: bool = False,
    requires_approval: bool = False,
    caps: tuple[str, ...] = (),
    url_arg: str | None = None,
    output_schema: Any = _OUTPUT_AUTO,
) -> FunctionTool

Turn a plain Python function into a tool: inspect its signature + type hints → a JSON-schema ToolSchema, and wrap execution (sync → off the loop via to_thread, async → awaited). The model calls it by name with JSON args; a parameter named ctx/context is injected with the RunContext (and not advertised). The function's return value is the tool result; exceptions propagate to the framework's per-tool isolation + typed Failure — no result is silently swallowed.

The function MUST carry a docstring (or be passed an explicit description=) of at least _MIN_DESCRIPTION_LEN chars — otherwise raises ToolDefinitionError at registration time. side_effecting defaults to False here for compatibility with the @tool decorator which enforces explicit declaration; direct callers should always pass it knowingly.

output_schema controls tool-result validation: - default (_OUTPUT_AUTO sentinel): auto-infer from the function's return-type annotation. A Pydantic BaseModel / dataclass / attrs class return type triggers validation on every call; everything else (primitives, Any, generics) gets no check. - explicit class / dict: use this as the output schema (overrides auto-inference). - explicit None: OPT OUT of validation even if the return type looks enforceable.

ToolRegistry

ToolRegistry()

register

register(tool: Any, *, replace: bool = False) -> Tool

Register a FunctionTool — or a plain callable, auto-converted via from_callable. A plain callable goes through the same tool-writing contract as @tool (docstring floor, explicit side_effecting if you want to deviate from the read-only default); a callable whose docstring is too thin surfaces a ToolDefinitionError here, not at runtime.

Raises :class:ValueError on name collision unless replace=True. The model advertises tools by name; a silent overwrite would change the implementation under the agent without any signal.

from_tools classmethod

from_tools(items: Iterable[Any]) -> ToolRegistry

Build a registry from a list mixing FunctionTools and plain functions ([get_weather, …]). Each plain callable is validated by FunctionTool.from_callable; a thin docstring or other contract violation raises ToolDefinitionError here instead of silently wrapping a bad tool.

Name collisions across the list propagate the same :class:ValueError as register.

schemas

schemas() -> list[Any]

Advertised tools, stable order (so a cacheable system+tools prefix stays byte-identical).

as_tool

as_tool(
    runnable: Any,
    *,
    name: str,
    description: str = "",
    side_effecting: bool = False,
    requires_approval: bool = False,
    render: Callable[[Any], str] | None = None,
) -> FunctionTool

Wrap any runnable (leaf Agent / coordinator Agent / Workflow — anything with async run(task, ctx) -> result) as a FunctionTool an Agent can call. The sub-run executes on a ctx.child() (so depth/budget/cancellation/observation all flow), and its result is rendered to text for the loop. This is the explicit/coordinator-inside-emergent composition.

render_result

render_result(res: Any) -> str

Render any run result to text for re-entry into a loop: AgentResult.output or a WorkflowResult's terminal outputs.

tool

tool(
    func: Callable[..., Any] | None = None,
    *,
    side_effecting: Any = _REQUIRED,
    idempotent: bool = False,
    name: str | None = None,
    description: str | None = None,
    requires_approval: bool = False,
    caps: tuple[str, ...] = (),
    url_arg: str | None = None,
    output_schema: Any = _OUTPUT_AUTO,
) -> Any

Decorator / converter: @tool(side_effecting=False), @tool(side_effecting=True, idempotent=False), or tool(fn, side_effecting=...) → a FunctionTool. side_effecting= is REQUIRED — the framework's gating + idempotency primitives rely on knowing whether a tool mutates the world. The decorated callable MUST carry a docstring of >=30 chars (the model needs enough to understand the tool); both failure modes raise ToolDefinitionError at decoration time (not at call time).

output_schema= is opt-in tool-result schema validation. By default (the _OUTPUT_AUTO sentinel) it auto-infers from the function's return-type annotation — a Pydantic / dataclass / attrs class triggers validation on every tool result; primitives and generics are skipped. Pass an explicit class to override; pass output_schema=None to opt out entirely even when the return type looks enforceable.