Skip to content

Agents

Build an agent, run a task, and control a run in flight.

createAgent() binds a provider, a system prompt, tools and defaults. agent.run() runs one task. It streams the reply and runs tool calls until the model stops, a limit trips or you abort the run.

Create an agent#

agent.ts
import { createAgent } from 'zidane'
import { basic } from 'zidane/presets'
import { anthropic } from 'zidane/providers'

const agent = createAgent({
  ...basic,
  provider: anthropic({ apiKey: 'sk-ant-...' }),
})

const stats = await agent.run({ prompt: 'build a REST API' })
console.log(`Done in ${stats.turns} turns`)

All options:

TypeScript
createAgent({
  provider,                          // required, an array enables fallback
  name: 'basic',                     // display name (traces/logs)
  system: 'You are a helpful...',
  tools: { shell, readFile },        // default: {}
  toolAliases: { shell: 'Bash' },    // canonical to LLM-facing names
  toolDescriptions: {                // override any tool's description (native or MCP)
    shell: cur => `${cur}\nNever invoke sudo.`,
    mcp_github_create_pull_request: 'Open a PR. Always target `develop`.',
  },
  session,
  behavior: {
    maxConcurrentTools: 10,          // default 10
    maxTurns: 50,                    // default: unlimited
    maxTokens: 16384,                // default: the model's max output
    thinkingBudget: 10240,
    cache: true,                     // default true
    schema: undefined,               // JSON Schema for structured output
    maxCostUsd: undefined,
    maxTotalTokens: undefined,
  },
  execution: createProcessContext(),
  mcpServers: [],
  eager: true,                       // pre-connect MCP in the background (default false)
  skills: {},
})
OptionDetails
providerProviders
hooksAgent-lifetime handlers, keyed by event name (Hooks). An unknown event name throws at construction.
tools, toolAliases, toolDescriptionsTools
sessionSessions
durable, sessionIdCrash recovery. sessionId needs durable and loads or creates that session in the runtime's store.
behaviorHooks, Errors
executionExecution contexts
mcpServers, eagerMCP servers
skillsSkills

Run a task#

TypeScript
await agent.run({
  prompt: 'your task',       // string or PromptPart[] (text, image, audio, video, document)
  model: 'claude-opus-4-7',  // or an ordered fallback list
  system: 'be concise',
  thinking: 'medium',
  behavior: { maxTurns: 10, maxTokens: 4096, thinkingBudget: 8192 },
  tools: {},                 // {} = no tools for this run
  signal: abortController.signal,
})

run.behavior overrides agent.behavior, which overrides the defaults. Omit prompt to resume a session that already has turns. run() rejects when another run of the same agent is in progress. For a model list, see model fallback.

systemNotes adds notes to the run's prompt turn, to tell the model about a change (mode, environment, effort) without editing the system prompt. Anthropic models that accept mid-conversation system messages get them as role: "system" messages. Other models get <system-reminder> text. On Anthropic, a note with effort changes the effort level, per message where the model supports it, so the top-level effort and the cache stay. Other providers ignore effort. systemFreeze sends a saved system prompt in place of the fresh one, so the prompt cache keeps hitting. Both are in keeping the prefix stable.

Presets#

A preset is a Partial<AgentOptions> without provider, execution, session or mcpConnector. basic and its tools are listed in built-in tools.

TypeScript
import { basic, basicTools, composePresets, definePreset } from 'zidane/presets'

createAgent({ ...basic, provider, system: 'be concise' })
createAgent({ ...definePreset({ name: 'researcher', tools: basicTools }), provider })
createAgent({ ...composePresets(basic, mine), provider })  // merges hooks and tools per field
createAgent({ provider })                                   // no tools

A plain spread is shallow. The hooks or tools of a later preset replace the earlier ones. composePresets merges them. It keeps only the preset fields that it knows how to merge, so pass extensions, decisions, artifacts and clock to createAgent directly.

Thinking#

LevelEffect
offNo reasoning.
minimal, low, medium, high, xhigh, maxEffort tier, mapped to the provider's reasoning field (Anthropic output_config.effort, OpenRouter reasoning.effort, and so on). Providers clamp to the tiers they support.
adaptiveThe model decides per turn. Anthropic only. Other providers send no reasoning.

behavior.thinkingBudget pins an exact token budget on top of a level:

TypeScript
await agent.run({ prompt: '…', thinking: 'high' })
await agent.run({ prompt: '…', thinking: 'high', behavior: { thinkingBudget: 50000 } })  // exact
KeyDoes
modelOptions (run) or behavior.modelOptionsExtra model options, such as Anthropic's fast mode: { fast: true }
behavior.effortHint: falseStops sending the effort hint to Anthropic
behavior.taskBudgetCaps thinking across a whole task (Anthropic adaptive thinking only)
behavior.thinkingDecayLowers the budget on later turns (tool dispatch)

Thinking is saved as { type: 'thinking', text } session blocks and streams through stream:thinking.

Structured output#

behavior.schema forces the final response to a JSON Schema. On the final turn only, zidane forces a synthetic output tool with toolChoice. The result lands on stats.output and fires the output hook.

TypeScript
const stats = await agent.run({
  prompt: 'Extract the entities',
  behavior: {
    schema: {
      type: 'object',
      properties: { name: { type: 'string' }, age: { type: 'number' } },
      required: ['name', 'age'],
    },
  },
})
console.log(stats.output) // { name: 'Alice', age: 30 }

With Zod v4, pass zodToJsonSchema(z.toJSONSchema(schema)). It strips $schema, which some providers reject.

Run control#

TypeScript
agent.isRunning             // boolean
agent.turns                 // SessionTurn[]
agent.steer('new focus')    // inject mid-run, delivered between tool calls; also takes PromptPart[]
agent.followUp('next task') // queue for after the run finishes
agent.abort()               // cancel the whole run
await agent.suspend({ graceMs: 5000 }) // stop so a later resume() continues it
await agent.resume()        // continue the run a dead process left, or null
agent.cancelTool(callId)    // cancel one in-flight tool call
await agent.reset()         // clear turns and queues
await agent.warmup()        // pre-connect MCP and resolve skills (idempotent)
await agent.waitForIdle()   // wait for the current run to finish
await agent.compact()       // summarize older turns now (sessions › compaction)
agent.cacheExpiresAt()      // epoch ms when the prompt cache likely expires, or null
agent.cacheImpact({ model: 'claude-haiku-4-5' }) // { tokens, reasons, expired }
await agent.destroy()       // tear down the execution context and MCP connections

cancelTool(callId) aborts the ctx.signal of that call, fires tool:cancelled and returns a cancelled result with isError: true. Other calls in the batch keep running. It returns true when it cancelled a live call. A repeat call returns false. A cancel sent before the call starts waits and applies when the call registers.

steer works only during a run. The model gets the message at the next tool boundary. followUp waits until the model returns a final answer, then continues the same run with the message. Both return false and queue nothing when no run is in flight or the run is aborted. The apps keep one queue per session instead (queue and steer).

suspend({ graceMs }) stops the run so resume() can continue it later, for a host that drains before it exits. The model stream stops at once. Running tool calls get graceMs ms to finish (unset: they all finish). A call still running after that stays pending. run() resolves with suspended: true. On a replay-mode runtime (restateRuntime, which replays a journal), suspend() aborts the run instead.

resume() continues the session's run that a dead process left unfinished, with the options its run record kept. It resolves null when there is nothing to continue. It rejects without a checkpoint-mode runtime in durable (SQLite or memory, which store finished steps as rows). Details: crash recovery.

cacheExpiresAt() adds the cache lifetime of the last request (behavior.cacheTtl, else 5 minutes) to the time it was sent. The provider can evict earlier. cacheImpact(change) estimates the prompt tokens that a change (model, thinking, system: true, tools: true) would make the next request read again instead of from the cache. It compares the change with the last request as is, so do not ask about a change that you send without touching the request, such as effort in a note. Compaction by hand covers compact().

agent.killBackgroundTask(taskId) stops a background task from host code. The model sees the exit as a <task-notification> on its next turn.

await using agent = createAgent(...) calls destroy at scope exit (Symbol.asyncDispose, Node 20+ or Bun).

Usage and cost#

TypeScript
import { flattenTurns, statsByModel } from 'zidane'

stats.totalIn / stats.totalOut / stats.cost  // parent run plus every spawned child
stats.turns / stats.turnUsage                // parent loop only
stats.children                               // ChildRunStats[] in completion order
stats.timeTillFirstTokenMs                   // ms to first stream or tool event

flattenTurns(stats)   // every TurnUsage in the tree (DFS)
statsByModel(stats)   // Map<modelId, { input, output, cost, cacheRead, cacheCreation, turns }>

Behavior options#

behavior holds every loop setting. Set it on the agent or per run. The value of the run wins. Types and defaults are in AgentBehavior.

PurposeKeysDetails
Turns and budgetsmaxTurns, maxTurnsWarning, maxTurnsWarningMessage, maxCostUsd, maxTotalTokens, maxWallMsRun-level budgets
Retries and timeoutsretry, providerStreamStartTimeoutMs, providerStreamTimeoutMs, providerStreamIdleTimeoutMs, mcpConnectTimeoutMs, hookTimeoutMs, spawnTimeoutMs, destroyTimeoutMs, toolBatchTimeoutMsRetry with backoff. The watchdogs fail a stuck stream, connector, hook, spawn or teardown after that many ms.
Output and thinkingmaxTokens, thinkingBudget, thinkingDecay, taskBudget, effortHint, thinkingOverrunPolicy, modelOptions, schema, resumeOnMaxTokens, maxConsecutiveLengthResumes, maxConsecutivePauseTurns, maxConsecutiveRefusalResumesThinking, structured output. The resume keys continue a reply cut by the output cap, an Anthropic pause_turn or a refusal, up to that many times.
Model metadatamodelCatalog, contextWindowOverride a model's output ceiling, pricing, context window or reasoning support
Prompt cachecache, cacheTtlPrompt caching
Tool schedulingmaxConcurrentTools, toolBatchExecutorTool dispatch. toolBatchExecutor replaces the scheduler.
Tool outputtoolOutputBudget, toolOutputBudgetExcludeTools, readFileDefaults, readLineNumbers, elideStaleReadsOutput and context budgets
Guards and nudgesdedupReads, dedupTools, repeatGuard, toolBudgets, requireReadBeforeEdit, trackShellReads, writeFileExistingPolicy, textToolCallRescue, unknownToolRecovery, progressNudges, shellToolNudgesRead dedup and guards. The nudges add one-line hints when the model stalls, writes a tool call as text, calls an unknown tool, or uses shell for work a dedicated tool does.
CompactionautoCompact, compactStrategy, compactThreshold, compactThresholdWindowFraction, compactLowWaterFraction, compactSkip, compactKeepTurnsCompaction
MediamediaKeepTurns, mediaBudget, maxImageEdgeReplace old images and documents in the request with markers after N messages (mediaKeepTurns) or near the provider's limits (mediaBudget). Shrink large PNG images once (maxImageEdge).
Saving results to diskpersistTurns, persistThreshold, persistExcludeTools, persistDir, persistMaxBytesSave each turn as it ends, and replace large tool results with a short preview plus a file path
Background taskstasksDir, shellLongTimeoutMs, disableBackgroundTasks, backgroundOutputCap, backgroundStallWatchdogMsBackground tasks
MCP and tool discoverytoolDisclosure, toolSearch, mcpToolNameSeparator, surfaceMcpInstructions, codemodeMCP, codemode
Message repairstrictToolPairing, maxPairingRepairsPerTurnzidane repairs a tool call that has no result before it sends the request. strictToolPairing fails instead, with AgentToolPairingError (errors).
Web toolsfetchUrlAllowHosts, fetchUrlCacheTtlMsLimit fetch_url and web_search to some hosts, and cache fetched pages
Host dataextensionsA bag for host settings that zidane itself never reads

Other agent options#

OptionDoes
runTransforms, behaviorTransforms, toolCatalogTransforms, mcpConfigTransformsRewrite run options, behavior, the tool list or MCP configs before each run. Extensions fill these.
skillPoliciesTurn a skill on from the run's context
toolResultMetadata, contextBreakdownSectionsRead metadata on tool results, and add rows to a context breakdown
readState, toolStateDurable storage for read tracking and ToolContext.state
mcpToolWrapWrap every MCP tool after discovery
toolKillTune cancelTool. toolKill.defaultMessage sets the reason when cancelTool gets none.
handleAn execution handle that the host owns. The agent never spawns or destroys it.
mcpConnectorReplace the MCP connector, for a custom transport or auth provider (OAuth 2.1)
decisionsPin the provider, model or timeout for agent.decide, or false to turn decisions off
clockThe time and UUID source. A replay-mode durable runtime (restateRuntime) uses its own journaled clock instead.
extensionsA merged extension registry. Extensions fill it.
artifactsfalse turns off session artifacts. Default on.

run() also takes modelOptions, resumeRunId (continue the turns of a past run), runLabel, contextSections, tracingContext, clock, leaseRenewMs (how often a top-level checkpoint run renews its session's lease, default 60 s), and depth, parentRunId and parentCallId for hosts that run sub-agents themselves. See AgentRunOptions.

Reference#