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.state.memory and agentkit.agents.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], and the widest type is
deliberate: a plain dict, the FrozenDict the runtime normally
hands down (ToolRequest.arguments — a dict SUBCLASS that
refuses mutation, see kernel._frozen), and any other mapping a
caller supplies all satisfy it. An impl READING args needs to
know only that it is a mapping. An impl WRITING to it is the case
the freeze exists to stop: measured, a Tool.run reached through
the Invoker receives a FrozenDict and args["x"] = 1
inside the tool raises TypeError. Copy first if you need to
edit — dict(args) — and note that copy is SHALLOW, so nested
values stay frozen.
ToolArgumentError
¶
ToolArgumentError(
tool_name: str,
*,
unexpected: tuple[str, ...] = (),
missing: tuple[str, ...] = (),
accepted: tuple[str, ...] = (),
invalid: tuple[tuple[str, str], ...] = (),
)
Bases: ValueError
A tool CALL named arguments the tool does not accept, or omitted required ones.
The mirror of :class:ToolShapeError: that one is a bad value coming OUT of a
tool, this one is a bad call going IN. Both are raised at call time and both
are meant to be reflected back to the model, which authored the call and is
the only party that can fix it.
Why this is an error rather than a shrug: unknown keys used to be dropped
silently, and a parameter with a DEFAULT then ran with that default. A model
calling notify(message="page the on-call, prod is down") against
def notify(msg: str = "default message") got back "sent: default
message" — a side-effecting tool reporting success for something it never
did. Nothing downstream could tell.
The message names the tool, the offending arguments and the accepted set, so
the retry middleware can hand the model something it can act on. A tool that
genuinely wants arbitrary keys declares **kwargs and receives them.
Subclasses ValueError: the failure mode is a bad value, and existing
except ValueError isolation around tool calls keeps working.
invalid is the third flavour of bad call, and it is the same kind of
mistake as the other two: the argument NAME was right, the VALUE was not one
the advertised schema permits. unit="kelvin" against
{"enum": ["celsius", "fahrenheit"]} used to be neither reported nor
converted — the string went straight into a body annotated Unit. It
belongs on this type rather than a new one precisely because the recovery is
identical: reflect the message, let the model reissue the call. Each entry is
(argument name, why it was rejected), and the reason carries the legal
values so the repair turn does not have to guess.
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
¶
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).
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
¶
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
¶
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.
FileTool
¶
The memory(command=…) tool. Declares description and
output_schema so isinstance(FileTool(), Tool) holds under the
@runtime_checkable :class:Tool Protocol.
The class is FileTool and the tool is memory on purpose: the class
name is for the reader (it is a file-tree tool, and it sits next to
agentkit.state.memory, which is a different thing), while the tool name is for
the MODEL and is fixed by Anthropic's memory-tool contract. The module
docstring has the full argument for why that asymmetry is the right one and
why renaming the tool is not a tidy-up.
InMemoryFiles
¶
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 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.
StoreFiles
¶
StoreFiles(
store: StorePort,
*,
namespace: str,
root: str = "/memories",
max_bytes: int = 8388608,
max_files: int = 4096,
)
A FileTool/FileMemory backend with atomic tree mutations.
The host supplies a fixed tenant namespace; model paths never select
another namespace or become operating-system paths. Each tree is one JSON
document updated with the store's compare-and-set primitive, including
rename and recursive delete. Durability and cross-process atomicity follow
the supplied store: FileStore is process-local; Redis/Postgres provide
their respective distributed guarantees. Concurrent FileStore users must
share the same FileStore instance.
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
¶
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 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.