Skip to main content
A tool is a typed function the model can call. Define one with defineTool(): the argument and result types are inferred from its zod input, the arguments are validated before execute runs, and the result drops in anywhere tools are accepted (createAgent({ tools: [...] }), ToolRegistry.register(tool), AgentBuilder.addTool(tool)).

defineTool() options

defineTool() checks the name, the description and the zod input when it is called, and throws an error that says how to fix a bad value. Registering two tools with the same name throws an error naming the conflict. A defined tool is a regular ToolDescriptor: it carries its schema as inputSchema (the same schema as input) and its execute function directly. The .tool field (an ai v4 { description, parameters, execute } object) is legacy: it is still built for compatibility, and only used for hand-written descriptors that set neither inputSchema nor execute.

What happens when the model calls a tool

  • Validation first. The model’s arguments are parsed with inputSchema (defaults, coercions and transforms applied) before hooks, needsApproval and execute see them. Arguments that do not match never reach execute: the model gets a structured ToolArgumentsValidationError result and can retry.
  • Errors are results. A tool that throws gives the model { error, toolName, message, kind } (no stack trace) and the run continues. See Errors.
  • Parallel calls. Several calls in one model turn run concurrently; cap them with toolConcurrency (1 for strictly sequential). Results reach the transcript in the model’s call order. See Parallel tool calls.
  • Cancellation. A run’s AbortSignal reaches each call as ctx.abortSignal, so long-running work can stop early.
  • Retries after a crash. With durable execution a tool can run more than once across a crash; use ctx.toolCallId as an idempotency key. See Durable execution.

Execute context

execute(args, ctx) always gets a real second argument, typed ToolExecutionContext (exported from the package root; it replaces the ai SDK’s ToolExecutionOptions), on every path that runs a tool: the main loop, the call that runs after an approval, tools that run in a sandbox (sandboxExecute(args, sandbox, ctx)) and a flow’s tool-call node. A sandboxExecute(args, sandbox) that ignores the third argument keeps working.

Errors

Every way a tool call can fail reaches the model as the same result, so one check works everywhere (including the call that runs after an approval):
  • error: the error’s name (TypeError, ToolArgumentsValidationError, …), or the kind’s default name for a failure that is not a thrown error.
  • toolName and message: the message only, never a stack, capped at 2,000 characters (... (truncated) marks a cut).
  • kind: why the call failed.
  • Some kinds add fields: issues for validation, note for rejected, reason for denied.
The transcript message carries isError: true; tool-result events, onToolResult, postToolCall hooks and tool.error events see the call as failed. toolErrorResult({ toolName, error, kind?, toolCallId?, details? }) builds this result; use it in your own tool wrappers so they match. A thrown error can pick its kind by carrying a toolErrorKind property. An error extending PropagatingToolError is not a result: it aborts the run.

Built-in tools

Built-in descriptors are passed keyed by the name the agent uses: createAgent({ tools: { current_date: currentDateTool } }). Spec files refer to http, current-date and day-name by name (see Configuration).

ToolRegistry

createAgent() builds a registry for you. With AgentBuilder + AgentExecutor.execute(), or to share tools between agents, fill one yourself: register(tool) for a defineTool() result, register(name, descriptor) for a raw ToolDescriptor (a built-in tool, an MCP tool, or an existing tool() from the ai SDK), and registerMany() for a record of descriptors or an array of defined tools.
Pass it as toolRegistry to AgentExecutor.execute(); the agent config’s tools map names the tools it may use.