Tool calls
A tool is a function the model can call. You declare it in the agent config.
When the model calls one, the engine sends your worker a tool.execute
trigger; the worker runs it and answers with a tool.result or tool.error.
The engine settles the call, fires a tool.finished trigger, and re-prompts
the model with the outcome.
Example
A tool with an input schema. The engine validates the model's arguments; the
worker returns a tool.error when the tool itself fails.
const forecasts = { "San Francisco": "foggy", Tokyo: "clear" };
const tools = [
{
name: "get_weather",
description: "Get the weather for a city.",
input: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"]
},
exec: ({ city }) => {
const sky = forecasts[city];
if (!sky) throw new Error(`No forecast for ${city}.`);
return `It's ${sky} in ${city}.`;
}
}
];
function decide({ trigger, proposed }) {
if (trigger.type === "session.start") {
return {
agent: {
model: "claude-haiku-4-5-20251001",
stream: true,
tools: tools.map(({ name, description, input }) =>
({ name, description, input }))
}
};
}
// The model called a tool. Run it, or report why it failed.
if (trigger.type === "tool.execute") {
const tool = tools.find((t) => t.name === trigger.name);
try {
const result = tool.exec(trigger.input.value);
return { actions: [{ type: "tool.result", result }] };
} catch (err) {
return { actions: [{ type: "tool.error", error: err.message }] };
}
}
return proposed;
}Declaration
Tools live in the agent config. The model sees name, description, and
input; the rest is for the engine.
type AgentTool = {
name: string
description?: string
input?: unknown // JSON Schema for arguments; omitted: none
output?: unknown // JSON Schema the result must satisfy
handler?: "worker" | "client" // where it runs; default worker
}input and output are optional JSON Schemas; the engine enforces them (see
Schemas).
Schemas
The engine enforces a tool's declared schemas in both directions. It only validates and never mutates: a passing value is exactly what came in, with no coercion or default-filling. Tools with no schema pass through unchecked.
input
Before the tool.execute trigger reaches your worker, the engine classifies
the raw arguments against input and reports the outcome in trigger.input:
status | Meaning |
|---|---|
valid | Parsed to an object and conforms. value holds the arguments. |
invalid | Parsed to an object but violates the schema. |
malformed | Not a JSON object at all. |
Validation never blocks the call. Every case reaches your worker, so you
decide whether to run, coerce, or reject it. When the arguments are invalid
or malformed, the engine sets proposed to a ready-made tool.error you can
return as-is.
output
When a call settles with a result, the engine checks it against output. A
result that violates the schema never reaches the model. It settles as a
terminal tool.error instead. The result is read as JSON when it parses, else
as a plain string.
Triggers
Two triggers concern a tool call. Your worker answers tool.execute; it
usually accepts the proposal on tool.finished.
type ToolExecute = {
type: "tool.execute"
id: string
name: string
arguments: string // raw argument string
input: ToolInput // the engine's validation of arguments
attempt: number
deadline?: string
}
type ToolInput =
| { status: "valid"; value: unknown }
| { status: "invalid"; value: unknown; error: string }
| { status: "malformed"; error: string }
type ToolFinished = {
type: "tool.finished"
id: string
ok: boolean
name: string
result?: string
error?: string
}tool.execute
The model called a tool; run it. input carries the engine's validation of
the raw arguments (see Schemas). proposed is empty on a clean
call, because only your worker can run the tool, so you answer with a
tool.result or tool.error. When validation fails or the model named a tool
you never declared, proposed is instead a ready-made tool.error to return
as-is.
tool.finished
A tool call settled, after its result or error and any retries. proposed
records the outcome as a tool message, then re-prompts the model, or holds
when sibling calls are still in flight. Return it to continue the loop.
Actions
type ToolCall = {
type: "tool.call"
id?: string // omitted: the engine mints one
name: string
arguments: unknown
handler?: "worker" | "client" // default worker
retry?: RetryPolicy // default: no retry
}
type ToolResult = {
type: "tool.result"
id?: string // id and attempt default to the answered
attempt?: number // tool.execute trigger
result: unknown
}
type ToolError = {
type: "tool.error"
id?: string
attempt?: number
error: string
retryable?: boolean // default false: terminal
code?: ErrorCode
detail?: unknown
}tool.call
Dispatch a tool call. The engine proposes these for the model's calls, and
your worker can issue one directly. handler routes it to your worker or
the client; retry bounds the attempts.
tool.result
Settle a call with a result. A result checked against a declared output
schema may settle as a terminal error instead (see Schemas).
tool.error
Settle a call with a failure. It's terminal by default; set retryable: true
to retry under the call's retry policy. code and detail carry structured
info. The error text reaches the model as the tool's result, so write it to be
read.
Full types in Protocol.
Next
- Client-side tools: tools that run in the browser.
- Sub-agents: delegation as a tool call.
- Retries: timeouts and backoff.