Skip to main content

agent

agent(ctx, id, options) takes the workflow context, a stable step id, and:
string
required
The model each turn calls.
string
required
The task. Every turn after the first sees it again together with the tool results so far.
number
required
Hard cap on turns. Required, not defaulted: how long an agent may keep calling tools is your decision.
string
The system prompt - who the agent is and how it should behave.
ToolDef[]
The tools the model may call, each from tool().
McpServerDef[]
External MCP servers whose tools join the ones above. Each server is discovered once as a durable step.
ApprovalRule
The gate for every tool that has not answered for itself. See Which rule applies to a tool below.
number
Ceiling on the human decisions this agent may ask for. Omitted, there is none. See Capping the decisions an agent asks for below.
Record<string, unknown>
A JSON Schema the final answer must satisfy. Set, the answer comes back parsed as the object rather than as text. See Answering in a shape below.
"function-calling"
How a turn is composed. Defaults to function-calling.
ProviderName | AIProvider
A provider name resolves through the built-in registry; pass an AIProvider to run against your own adapter.
string
Passed per call and never stored. Omit it to use the provider SDK’s conventional env var.
number
Per-turn output cap, passed to the provider.
number
Passed to the provider when set.
boolean
Stream each turn’s tokens onto the run timeline as they arrive. Opt-in; the turn’s durable result is unchanged either way. See Streaming agent turns.
PromptCacheScope
Ask the provider to bill this agent’s repeated prefix at its own cached rate. “conversation” is the scope an agent wants: every turn re-sends the instructions, the tool declarations and every exchange so far. Opt-in, and refused rather than silently downgraded when the provider cannot place a cache breakpoint. See Prompt caching.
StopCondition[]
Named ceilings checked after each turn, in order; the first met ends the run and names itself. See Stopping an agent below.
(ctx) => boolean
Optional early stop once a turn’s tools have run; must be pure for replay. Prefer stopWhen.
It returns the loop’s result: final (the model’s answer), iterations, stopReason ("final", "max-iterations", "stopped", "approval-budget", "bail" or "guardrail"), stoppedBy - which named condition ended it, when one did - and haltedBy, which names the guardrail on "guardrail". See Guardrails.

Stopping an agent

maxIterations bounds turns. It does not bound time, and it does not bound the work one turn generates: a single turn calling twenty tools in parallel is one iteration and twenty-one steps. stopWhen covers the rest.
Conditions are asked in the order you list them and the first met wins, so the list reads as a priority order. Each pass is its own durable step, which is what lets wallClock read a real clock and still replay to the same decision - a replayed run re-walks its committed turns in milliseconds, and a re-read clock would end it somewhere else. Two other things follow from being data rather than a closure: a condition registers into the workflow’s agent manifest, so the console can show what bounds an agent before it has ever run; and stoppedBy names the one that fired, on the result and on the run’s trace. The stop closure still works and is still the right tool for a predicate nobody needs to read back - it reports stopReason: "stopped" with no stoppedBy, because a closure has no name.

Ending early on success

A tool that has already produced the answer says so with bail(), and the loop returns that value instead of handing the result back for a turn it does not need.
The run ends with stopReason: "bail" and the value as final. Every other tool the same turn started still runs to completion first - a side effect that already happened is not dropped from the record because a sibling finished sooner.
bail() is returned, never thrown. A tool handler runs inside a durable step, so a throw is committed as that step’s failure - an early success would be indistinguishable from a broken tool, and would replay as a failure forever.

tool

A tool is one definition. Give it a name the model calls back with, a description it reads to decide, and a JSON Schema for the input it must produce:
string
required
The tool’s identity: what the model calls back with, and the tail of the durable step name its call writes. Must be unique within an agent.
string
What the tool does. This is what the model reads to decide whether to call it.
Record<string, unknown>
JSON Schema for the input the model must produce. A tool with none is declared as an object schema with no properties.
Record<string, unknown>
JSON Schema for what the tool returns. Nothing validates against it - it is published, so a surface can render a result before it has seen one.
(output: unknown) => unknown
Project the result for the model only. The durable step still records what the tool returned. See Showing the model less than you record below.
ToolAnnotations
Behaviour hints - title, readOnlyHint, destructiveHint, idempotentHint, openWorldHint. Hints, never gates. See Behaviour hints below.
(input: unknown) => unknown | Promise<unknown>
Your own code. Runs as a durable step: the model’s input in, the return value back to the model.
string
A workflow to run instead of a handler. The tool call becomes a linked child run.
string
The workflow tool’s app; addressed like step.runWorkflow.
string
Pin the workflow tool to a specific runner.
boolean
Park the run on a human before this tool runs. See Tools a human has to approve below.
ApprovalRule
Annotates the gate (risk, summary, policy, context, escalatesTo, allow, timeout, onTimeout) and decides whether it is raised at all (if, minRisk). Declaring one gates the tool; requiresApproval: false opts out of it.
string
The integration this tool reaches, e.g. “google-calendar” - a free-text label, not a Credential id. Feeds the tools manifest below.
Exactly one of handler or workflow - a tool is backed by your code or by a workflow:
That call is followable in both directions (call->run lineage). The tool’s own durable step carries childRunId - the id of the run it started - and the run it started carries parentRunId, parentStep (the step it was spawned on) and parentAttempt (which attempt of that step spawned it). So GET /runs?parentRunId=<id> returns every child one agent run started, and each child links back to the exact call that made it. A handler-backed tool has neither end of the edge: it runs inside the agent’s own run. See the runs API.
A tool’s input is raw model output. The schema tells the model what to produce; it does not validate what arrives, so parse it in the handler before you trust it.

Behaviour hints

annotations describes how a tool behaves, in the same field names MCP uses, so one declaration means the same thing to Duraton and to any MCP client reading your tools:
These are hints, and a hint is not a gate. destructiveHint: true on its own stops nothing - requiresApproval and its rule are what park the run on a person, and a hint is not one of the four names a rule can read. The hints exist so a surface can suggest the right default without every dangerous tool having to be marked by hand, and MCP defines them the same way: a client is told never to make tool-use decisions on annotations it received from a server it does not trust - on an attached server the hint is written by that server’s operator, not by you. Set both on a tool that is genuinely destructive.

What the model sees

Every turn sees the task, the exchanges so far, and your tools declared as function-calling schemas. How the exchanges reach the model depends on the adapter: The fallback is not a lesser answer, just a lesser shape: a model reading its own tool calls as prose is working in a form it was not trained on. Either way the turns are read from the loop’s recorded history and nothing else, so a replayed turn is identical to the original one.

Bounding context

Nothing trims history by default, so an agent that runs long enough eventually hits the model’s context window and fails. Pass context and every turn - both a native transcript and the rendered-text fallback above - sees a bounded view instead of the full, ever-growing history:
count and token-budget are free: recomputed fresh every turn, they never change what the run itself remembers. summarize is the one that costs something, so it is the one that persists - a later turn starts from the already-reduced history instead of paying to re-summarize the same prefix every time. summarize’s model call rides its own durable step, so a replay reads the recorded summary rather than generating a new one - full detail on that step and its replay guarantee lives on the Context management page, the SDK-level page for the loop-level context option this wraps.
Without context, an agent behaves exactly as it did before the option existed - the whole history reaches every turn. The option changes nothing for an agent that does not use it.