new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

AI SDK · Core · all subjects

agents

279 notes in this subject, read out of this brain and free to use. This is page 3 of 5.

WorkflowChatTransport initialization example

Example: const transport = useMemo(() => new WorkflowChatTransport({ api: '/api/chat', maxConsecutiveErrors: 5, initialStartIndex: -50 }), []); const { messages, sendMessage } = useChat({ transport }); This sets up resumable streaming with reconnection logic and fetches the last 50 chunks on page refresh.

WorkflowAgent stream call example

Example usage: const result = await agent.stream({ messages: modelMessages, writable: getWritable<ModelCallStreamPart>() }); return { messages: result.messages }; This calls the agent's stream method with converted model messages and a writable stream, then returns the resulting messages.

WorkflowAgent example with flight booking

Example: const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-4-6', instructions: 'You are a flight booking assistant.', tools: { searchFlights: tool({ description: 'Search for available flights', inputSchema: z.object({ origin: z.string(), destination: z.string(), date: z.string() }), execute: searchFlightsStep }), bookFlight: tool({ description: 'Book a specific flight', inputSchema: z.object({ flightId: z.string(), passengerName: z.string() }), execute: bookFlightStep }) } }); This defines an agent with two tools for flight operations.

Migrating from DurableAgent to WorkflowAgent

WorkflowAgent replaces the Workflow DevKit's DurableAgent. Key migration steps: (1) Import from @ai-sdk/workflow instead of workflow/ai, (2) Write ModelCallStreamPart to writable, not UIMessageChunk directly, (3) Use stopWhen with isStepCount() instead of maxSteps, (4) Replace experimental_output with output, (5) Use needsApproval on tool definition instead of approval Hook in execute, (6) Return result.messages instead of result.uiMessages, (7) No generate() method—use stream() only, (8) Replace experimental_context with runtimeContext and toolsContext.

InferWorkflowAgentUIMessage type inference

Infer the UI message type for type-safe client components using InferWorkflowAgentUIMessage<typeof myAgent>, where myAgent is a WorkflowAgent instance. This provides type safety for components that render messages from that specific agent.

WorkflowAgent lifecycle callbacks

WorkflowAgent provides lifecycle callbacks: experimental_onStart({ modelId, messages }), experimental_onStepStart({ stepNumber }), onToolExecutionStart({ toolCall }), onToolExecutionEnd({ toolCall, toolOutput }), onStepEnd({ usage, finishReason }), onEnd({ steps, totalUsage }). All can be defined in the constructor (agent-wide) or in stream() (per-call). When both are provided, both fire with constructor first.

WorkflowAgent prepareStep lifecycle callback

prepareStep is called before each step (LLM call). It receives stepNumber, runtimeContext, experimental_sandbox. Use it to modify settings, manage context, or inject messages dynamically. Can return partial configuration to override for that step. Can be defined in the constructor or per-call in stream().

WorkflowAgent prepareCall lifecycle callback

prepareCall is called once before the agent loop starts. It receives model, tools, and messages. Use it to transform model, instructions, or other settings based on runtime context. Can be defined in the constructor or per-call in stream().

WorkflowAgent toolsContext for per-tool state

toolsContext is a per-tool map keyed by tool name. Each tool's execute function receives only its own validated entry as context. Tools can declare a contextSchema to validate their entry against the schema before execution. Must be serializable and passed in the constructor or per-call to stream().

WorkflowAgent experimental_sandbox parameter

Pass a sandbox session when tools need an execution environment. The sandbox is available to tool descriptions and execute functions as experimental_sandbox, and to prepareStep where you can override it for the current step. experimental_sandbox is a live runtime handle, not durable context, so it cannot be stored in runtimeContext or toolsContext. If a tool runs as a separate workflow step, pass serializable sandbox identifiers and reattach inside that step.

WorkflowAgent runtimeContext for shared agent state

runtimeContext is shared agent state that flows through prepareStep, lifecycle callbacks, and onEnd. It is available to the agent but not individual tools. Treat it as immutable; return a new value from prepareStep to update it for the current and subsequent steps. Can be passed in the constructor or per-call to stream(). Must be serializable (strings, numbers, booleans, arrays, plain objects, dates, URLs, maps, sets) and cannot contain functions, class instances, symbols, WeakMap, WeakSet, or SDK clients.

WorkflowAgent structured output with Output

Parse agent responses into typed objects using Output.object(). Example: const result = await agent.stream({ messages, output: Output.object({ schema: z.object({ sentiment: z.enum(['positive', 'neutral', 'negative']), summary: z.string() }) }) }); result.output contains the typed output.

HarnessAgent installation packages

Install three packages: @ai-sdk/harness (core harness package), a harness adapter (such as @ai-sdk/harness-claude-code), and a sandbox provider (such as @ai-sdk/sandbox-vercel). Bridge-backed harnesses like Claude Code and Codex require real network sandbox like @ai-sdk/sandbox-vercel. Host-runtime harnesses like Pi can also run with @ai-sdk/sandbox-just-bash.

isStepCount predicate for stopWhen

Import isStepCount from 'ai' and use it with stopWhen to stop after a specific number of harness steps: stopWhen: isStepCount(1) stops after 1 step.

HarnessAgent class definition and purpose

HarnessAgent is an AI SDK Agent implementation backed by a harness adapter. It provides generate() and stream() methods that return AI SDK-compatible results while a preconfigured harness powers these results.

HarnessAgent constructor example

const agent = new HarnessAgent({ harness: claudeCode, sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000], }), instructions: 'You are a careful coding assistant. Prefer small changes and explain tradeoffs.', });

HarnessAgent construction scope and session creation

Construct HarnessAgent at module scope to hold configuration, not a live session. Live state belongs to HarnessAgentSession. Create a session by calling agent.createSession().

HarnessAgent.generate() method

The generate() method drains the turn and returns a GenerateTextResult. Call it with parameters: session (HarnessAgentSession) and prompt (string or message array).

HarnessAgent.stream() method

The stream() method provides incremental output. Call it with parameters: session (HarnessAgentSession) and prompt (string or message array). It returns a result object with a stream property that yields parts. Iterate with for await and check part.type for 'text-delta' to get incremental text.

HarnessAgent.stream() example

const session = await agent.createSession(); let exitCode = 0; try { const result = await agent.stream({ session, prompt: 'Create a short TODO.md for this repository.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); process.exit(exitCode); }

HarnessAgent message history handling

A harness session owns its native conversation history. When you pass messages or a message-array prompt, HarnessAgent takes the latest user message as the fresh input for the turn; it does not replay the full prior conversation into the harness. A trailing tool message is handled differently: tool approval responses and client-provided tool results continue the unfinished harness turn that produced the corresponding request or tool call. In chat routes, persist and resume the harness session instead of relying on message replay.

HarnessAgentSession.destroy() method

The destroy() method stops the runtime and discards resumability. Use it for one-off scripts and tests.

HarnessAgentSession.detach() method

The detach() method parks the runtime and sandbox, returns resume state, and keeps the sandbox warm for a later attach. If the turn is unfinished, the resume state includes the continuation state. Use it for HTTP routes that need multi-turn continuity.

HarnessAgentSession.stop() method

The stop() method saves resume state, then stops the runtime and sandbox. If the turn is unfinished, the resume state includes the continuation state. Use it for HTTP routes that need multi-turn continuity.

HarnessAgentSession.suspendTurn() method

The suspendTurn() method is for advanced active-turn continuation across a process boundary. It suspends the turn and returns continuation state that can be persisted.

HarnessAgentSession.hasUnfinishedTurn() method

The hasUnfinishedTurn() method reports whether the current turn must be continued or suspended before the session accepts a new prompt.

HarnessAgent session detach example

const session = await agent.createSession({ sessionId: chatId }); try { const result = await agent.stream({ session, messages }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } const resumeState = await session.detach(); await persistResumeState({ chatId, resumeState }); } catch (error) { await session.destroy(); throw error; }

HarnessAgent.createSession() with resume state

Pass resume state and sessionId to createSession() to resume a session: agent.createSession(resumeState ? { sessionId: chatId, resumeFrom: resumeState } : { sessionId: chatId }). HarnessAgent validates that the resume state was produced by the same harness adapter before handing it to the runtime. If the resume state includes an unfinished turn, call continueStream() or continueGenerate() before sending a new prompt.

HarnessAgent session resumption example

const resumeState = await loadResumeState({ chatId }); const session = await agent.createSession( resumeState ? { sessionId: chatId, resumeFrom: resumeState } : { sessionId: chatId }, );

HarnessAgent.continueStream() and continueGenerate() methods

Use continueStream() for incremental output when resuming a turn with continueFrom, or continueGenerate() to drain the continued turn and return a GenerateTextResult. Call these after creating a session with continueFrom and without sending a new prompt.

HarnessAgent suspended turn continuation example

if (session.hasUnfinishedTurn()) { const continuationState = await session.suspendTurn(); await persistContinuationState({ chatId, continuationState }); } // Later: const session = await agent.createSession({ sessionId: chatId, continueFrom: continuationState, }); const result = await agent.continueStream({ session });

HarnessAgent stopWhen option

Use stopWhen to opt into semantic step boundaries. It accepts one predicate or an array of predicates. Predicates run after real harness tool steps that can continue into another model step, and receive the completed steps from the current invocation. When a predicate matches, the returned result finishes while the underlying turn remains unfinished. A terminal text-only step consumes the turn's finish event and finishes naturally. stopWhen has no default; when omitted, HarnessAgent continues running until the turn naturally finishes or pauses for host input. Resume a stopped turn with createSession({ continueFrom }) and continueStream() or continueGenerate().

HarnessAgent stopWhen example with step control

import { isStepCount } from 'ai'; const steppedAgent = new HarnessAgent({ harness: claudeCode, sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000], }), stopWhen: isStepCount(1), }); const session = await steppedAgent.createSession(); const result = await steppedAgent.generate({ session, prompt: 'Create a short TODO.md for this repository.', }); if (session.hasUnfinishedTurn()) { const continueFrom = await session.suspendTurn(); await persistContinuationState({ chatId, continuationState: continueFrom }); }

HarnessAgent sandboxConfig.onBootstrap lifecycle hook

sandboxConfig.onBootstrap runs during sandbox template creation, after the harness adapter's own bootstrap and before snapshot-capable providers publish a snapshot. Use it for expensive setup that should be reused by future sessions. When you provide onBootstrap, also provide bootstrapHash; change the hash whenever the bootstrap output should invalidate the reusable snapshot. It receives { session, abortSignal }.

HarnessAgent sandboxConfig.onSession lifecycle hook

sandboxConfig.onSession runs after each sandbox session is acquired and its working directory exists, including resumed sessions. Use it for per-session files or lightweight configuration. It receives { session, sessionWorkDir, abortSignal }.

HarnessAgent sandboxConfig.workDir option

sandboxConfig.workDir is optional. When provided, it must be relative to the sandbox's default working directory and is used as the session working directory. When omitted, regular sessions use the default <harnessId>-<sessionId> directory, while onBootstrap receives the sandbox's default working directory.

HarnessAgent sandboxConfig example

const agent = new HarnessAgent({ harness: claudeCode, sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000], }), sandboxConfig: { workDir: 'repo', bootstrapHash: 'ripgrep-v1', onBootstrap: async ({ session, abortSignal }) => { const result = await session.run({ command: 'command -v rg >/dev/null || (apt-get update && apt-get install -y ripgrep)', abortSignal, }); if (result.exitCode !== 0) { throw new Error(`Failed to install ripgrep: ${result.stderr}`); } }, onSession: async ({ session, sessionWorkDir, abortSignal }) => { await session.writeTextFile({ path: `${sessionWorkDir}/README.md`, content: 'Session notes for the harness.', abortSignal, }); }, }, });

prepareHarnessSandboxTemplate() function

Use prepareHarnessSandboxTemplate() when you want the sandbox provider to create or refresh its reusable template for one harness ahead of time. Import from '@ai-sdk/harness/agent'. It accepts harness, sandboxProvider, and optional sandboxConfig parameters.

prepareHarnessSandboxTemplate() example

import { prepareHarnessSandboxTemplate } from '@ai-sdk/harness/agent'; await prepareHarnessSandboxTemplate({ harness: claudeCode, sandboxProvider: createVercelSandbox({ runtime: 'node24', ports: [4000], }), sandboxConfig: { bootstrapHash: 'ripgrep-v1', onBootstrap: async ({ session, abortSignal }) => { await session.run({ command: 'command -v rg >/dev/null || (apt-get update && apt-get install -y ripgrep)', abortSignal, }); }, }, });

prepareSandboxForHarness() function

Use prepareSandboxForHarness() when you own the native sandbox lifecycle and want to snapshot the prepared sandbox yourself. Import from '@ai-sdk/harness/agent'. It applies the selected harness bootstrap recipes and sandboxConfig.onBootstrap, then returns preparation metadata. It does not stop or snapshot the sandbox. It accepts session, harnesses (array), and sandboxConfig.

prepareSandboxForHarness() example

import { HarnessAgent, prepareSandboxForHarness } from '@ai-sdk/harness/agent'; import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'; import { Sandbox } from '@vercel/sandbox'; const nativeSandbox = await Sandbox.create({ runtime: 'node24', ports: [4000], }); const sandboxProvider = createVercelSandbox({ sandbox: nativeSandbox }); const session = await sandboxProvider.createSession(); const preparation = await prepareSandboxForHarness({ session: session.restricted(), harnesses: [claudeCode, codex], sandboxConfig, }); const { snapshot } = await nativeSandbox.stop(); if (snapshot == null) { throw new Error('Prepared sandbox did not create a snapshot.'); } const sandboxFromSnapshot = await Sandbox.create({ source: { type: 'snapshot', snapshotId: snapshot.id, }, ports: [4000], }); const agent = new HarnessAgent({ harness: claudeCode, sandbox: createVercelSandbox({ sandbox: sandboxFromSnapshot, bridgePorts: [4000], }), sandboxConfig, }); console.log(preparation.identity);

HarnessAgent constructor settings table

HarnessAgent accepts these main settings: harness (adapter instance, required), sandbox (HarnessV1SandboxProvider, required), id (optional stable agent identifier), instructions (instructions applied once to a fresh session), stopWhen (condition(s) for finishing a result slice after a completed harness tool step), tools (AI SDK tools executed by the host when the harness calls them), activeTools (allowlist of built-in and host-executed tools the harness can call), inactiveTools (denylist of built-in and host-executed tools the harness cannot call), skills (instruction bundles surfaced by the adapter), permissionMode (built-in tool permission mode), toolApproval (approval status map for host-executed tools), sandboxConfig (sandbox working-directory and lifecycle hook configuration), telemetry (observability setting), debug (diagnostics setting), onLog (observability setting). Adapter-specific settings belong on the adapter factory.

Agent interface version

The Agent interface has a required readonly property `version` with the value 'agent-v1'. This specifies the interface specification version to enable evolution of the agent interface while retaining backwards compatibility.

Agent id property

The Agent interface has a readonly property `id` of type string or undefined. This is an optional agent identifier.

Agent tools property

The Agent interface has a readonly property `tools` of type ToolSet. This represents the set of tools available to the agent.

AgentCallParameters onStepStart callback

AgentCallParameters has an optional `onStepStart` callback of type GenerateTextOnStepStartCallback<TOOLS>. This callback is invoked when a step (LLM call) begins, before the provider is called.

AgentCallParameters onToolExecutionStart callback

AgentCallParameters has an optional `onToolExecutionStart` callback of type OnToolExecutionStartCallback<TOOLS>. This callback is invoked right before a tool's execute function runs.

AgentCallParameters onStepEnd callback

AgentCallParameters has an optional `onStepEnd` callback of type GenerateTextOnStepEndCallback<TOOLS>. This callback is invoked after each agent step (LLM/tool call) completes. It is useful for tracking token usage, per-step performance, or logging.

AgentCallParameters onEnd callback

AgentCallParameters has an optional `onEnd` callback of type GenerateTextOnEndCallback<TOOLS>. This callback is invoked when all steps are finished and the response is complete.

AgentCallParameters onFinish callback deprecated

AgentCallParameters has a deprecated optional `onFinish` callback of type GenerateTextOnEndCallback<TOOLS>. Use `onEnd` instead.

Custom Agent implementation example

import { Agent, GenerateTextResult, StreamTextResult } from 'ai'; import type { ModelMessage } from '@ai-sdk/provider-utils'; class MyEchoAgent implements Agent { version = 'agent-v1' as const; id = 'echo'; tools = {}; async generate({ prompt, messages, abortSignal }) { const text = prompt ?? JSON.stringify(messages); return { text, steps: [] }; } async stream({ prompt, messages, abortSignal }) { const text = prompt ?? JSON.stringify(messages); return { textStream: (async function* () { yield text; })(), }; } } This example demonstrates how to implement a custom Agent by creating a class that implements the Agent interface with the required properties and methods.

Agent usage with SDK utilities

All SDK utilities that accept an agent—including createAgentUIStream, createAgentUIStreamResponse, and pipeAgentUIStreamToResponse—expect an object adhering to the Agent interface. You can use the official ToolLoopAgent or supply your own implementation.

Agent generate method

The Agent interface has a `generate()` method that generates full, non-streaming output. It accepts an AgentCallParameters object and returns a PromiseLike<GenerateTextResult<TOOLS, OUTPUT>>.

Agent stream method

The Agent interface has a `stream()` method that streams output (chunks or steps). It accepts an AgentStreamParameters object and returns a PromiseLike<StreamTextResult<TOOLS, OUTPUT>>.

Agent generic parameters

The Agent interface accepts three generic parameters: CALL_OPTIONS (default: never) for optional call options, TOOLS (default: {}) for the tool set type, and OUTPUT (default: never) for additional output data the agent can produce.

AgentCallParameters prompt or messages

AgentCallParameters accepts either a `prompt` (string or array of ModelMessage objects) or `messages` (array of ModelMessage objects), but not both. These are mutually exclusive.

AgentCallParameters options field

AgentCallParameters has an optional `options` field of type CALL_OPTIONS. When CALL_OPTIONS is never, the options field must not be provided.

AgentCallParameters abortSignal

AgentCallParameters has an optional `abortSignal` field of type AbortSignal. This enables cancellation of agent operations.

AgentCallParameters timeout

AgentCallParameters has an optional `timeout` field that can be specified as a number (milliseconds) or as an object with a `totalMs` property. The call will be aborted if it takes longer than the specified timeout. Can be used alongside abortSignal.

Agent input mutually exclusive constraint

The Agent interface accepts both plain prompts and message arrays as input, but only one at a time. Prompt and messages are mutually exclusive.

Give your agent this brain