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

ai sdk harnesses

99 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

HarnessAgent.generate() and HarnessAgent.stream() return types

HarnessAgent.generate() returns an AI SDK GenerateTextResult. HarnessAgent.stream() returns an AI SDK StreamTextResult.

Compatible fields in HarnessAgent results

HarnessAgent results consume familiar fields: result.text, result.stream, result.steps, result.usage, and result.responseMessages.

Harness-specific events translation to AI SDK stream parts

Harness-specific events are translated into compatible stream parts. Text, reasoning, tool calls, tool results, usage, and finish reasons use the same AI SDK shapes where possible. Events without a first-class AI SDK part, such as workspace file changes and compaction, are surfaced as dynamic provider-executed tool parts.

Harness packages are experimental

Harness packages are experimental and users should expect breaking changes between releases as this early API gets further refined.

Creating and managing a harness session

Create a session before running turns using agent.createSession(). For server routes, use a stable sessionId and persist the resume state returned by session.detach() or session.stop().

Session state ownership in harness runtime

Unlike a language model call, a harness session owns state. The session carries the harness runtime, sandbox, working directory, native conversation history, and pending approvals.

HarnessAgent session example code

Example showing how to create a session, call agent.generate() with the session and prompt, log the result text, and destroy the session: const session = await agent.createSession(); try { const result = await agent.generate({ session, prompt: 'Inspect the repository and summarize the test setup.', }); console.log(result.text); } finally { await session.destroy(); }

AI SDK Harness definition and purpose

A harness is a complete agent runtime such as Claude Code, Codex, or Pi that owns capabilities larger than a model call, including workspace access, built-in coding tools, native session state, compaction, permission flows, and runtime-specific configuration. All AI SDK agent harnesses operate in a sandbox to keep the host environment safe.

Harness abstraction separate from provider/model abstraction

The AI SDK harness abstraction is separate from the provider/model abstraction. Providers expose models to AI SDK Core functions such as generateText and streamText. Harnesses expose agent runtimes to HarnessAgent.

HarnessAgent customization options

HarnessAgent lets you provide your own instructions, skills, AI SDK tools, permission settings, sandbox setup hooks, and adapter-specific configuration while preserving the runtime behavior that makes each harness powerful.

Harness output compatibility with AI SDK streams

Harness output is projected into AI SDK stream and response types, so surfaces that consume AI SDK model streams can also consume harness streams. For example, you can pass a HarnessAgent stream to toUIMessageStream and render it with useChat.

When to use a harness versus providers and models

Use a harness when you want an existing agent runtime to drive the task, such as coding agents that can inspect and modify a sandboxed workspace, agent runtimes with built-in tools and permission models, multi-turn sessions where the runtime owns conversation history, or workflows that should preserve native harness behavior. Use providers and models when you want direct control over the model call, the tool loop, model settings, structured output, or a custom agent architecture.

HarnessAgent four primary pieces

Harnesses have four primary pieces: HarnessAgent (the AI SDK agent implementation used in application code), Harness adapter (the package that connects to a runtime, such as @ai-sdk/harness-claude-code), Sandbox provider (the isolated filesystem and process environment where the harness runs), and Session (the live conversation and workspace state for a harness run).

Built-in tools merged into agent.tools

HarnessAgent automatically merges built-in tools from the harness adapter with host-defined tools passed via the tools setting, exposing the combined tool set through agent.tools.

HarnessAgent has two tool surfaces

HarnessAgent exposes two categories of tools: built-in tools from the underlying harness runtime (such as file reads, edits, shell commands, and web search), and AI SDK tools that you pass via the tools setting.

Built-in tool execution is provider-executed

Built-in tool calls are executed by the harness runtime, not by the application process. Stream parts for built-in tools use providerExecuted: true.

Client-side tool example without execute

Example of defining a client-side tool: const weather = tool({ description: 'Get the current temperature for a city.', inputSchema: z.object({ city: z.string() }) }); Omit the execute function to let external processes provide the result.

experimental_sandbox tool example

Example of using experimental_sandbox in a host-executed tool: const inspectFile = tool({ description: 'Read a file from the harness workspace.', inputSchema: z.object({ path: z.string() }), execute: async ({ path }, { experimental_sandbox }) => { return { content: await experimental_sandbox?.readTextFile({ path }) }; } });

HarnessAgent activeTools example

Example of using activeTools allowlist: const agent = new HarnessAgent({ harness: claudeCode, sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000] }), tools: { weather }, activeTools: ['weather'] });

HarnessAgent inactiveTools example

Example of using inactiveTools denylist: const agent = new HarnessAgent({ harness: claudeCode, sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000] }), tools: { weather }, inactiveTools: ['bash', 'write'] });

permissionMode example

Example of setting permissionMode: const agent = new HarnessAgent({ harness: pi, sandbox: createVercelSandbox({ runtime: 'node24' }), permissionMode: 'allow-edits' });

toolApproval example

Example of configuring tool approval: const agent = new HarnessAgent({ harness: claudeCode, sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000] }), tools: { weather }, toolApproval: { weather: 'user-approval' } });

Built-in tools access example

Example of accessing built-in tools: const agent = new HarnessAgent({ harness: claudeCode, sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000] }) }); agent.tools.bash; agent.tools.read; agent.tools.write;

Common built-in tool names across adapters

Harness adapters use common names where possible for built-in tools: read, write, edit, bash, grep, glob, and webSearch. Some runtimes expose native tools without cross-harness names, which appear under their native names.

Pass AI SDK tools to HarnessAgent same as ToolLoopAgent

You pass AI SDK tools to HarnessAgent using the tools setting with the same structure as ToolLoopAgent. When the harness calls a host-executed tool, HarnessAgent executes it in the host process and submits the result back to the harness runtime.

Client-side tools omit execute function

For tools that should be executed by a browser, user interaction, or external process, omit the execute function from the tool definition. When the harness calls such a tool, the result slice ends after the tool-call step and the turn waits for a result.

session.hasUnfinishedTurn() for pending tool calls

When a client-side tool is called without execute, session.hasUnfinishedTurn() remains true, and session.suspendTurn() includes the pending tool call in its serializable continuation state.

continueStream with toolResultContinuations

To resume a paused turn with client-side tool results, provide the raw result to continueStream() or continueGenerate() using the toolResultContinuations parameter, which accepts an array of objects with toolCallId and output properties. Set isError: true when the external tool failed.

activeTools allowlist and inactiveTools denylist

Use activeTools to specify an allowlist of tool names the harness can call, or inactiveTools to specify a denylist. Pass either activeTools or inactiveTools, not both. Both settings accept tool names from the combined tool set of built-in and AI SDK tools.

Inactive tools blocked at different levels

For host-executed tools, inactive tools are not passed to the underlying harness runtime. For built-in tools, support for filtering depends on the adapter: some filter natively, others enforce filtering through built-in tool approval by denying calls before they execute, and some throw when you attempt to filter built-in tools.

experimental_sandbox in tool execution context

Host-executed tools receive the session sandbox through the experimental_sandbox execution option. This provides a restricted sandbox session that allows tools to read, write, and run commands without being able to stop the network sandbox or change its network policy.

permissionMode for built-in tool approvals

Use the permissionMode setting to control adapter-native built-in tool permissions. Valid values are: allow-all (default, allows reads, edits, and shell commands), allow-edits (allows reads and edits, requests approval for shell commands), and allow-reads (allows reads, requests approval for edits and shell commands).

toolApproval for host-executed tool approvals

Use the toolApproval setting to configure approval requirements for host-executed tools. It accepts the same status values as AI SDK tool approval status objects: not-applicable, approved, user-approval, and denied.

Tool approval stream pause and resume

When tool approval is required, the stream pauses after a tool-approval-request. Continue by sending a tool approval response message. In UI flows, useChat sends these messages automatically when you add the approval result. In direct agent code, pass the approval response as messages on the next stream() or generate() call.

fileChange and compaction as dynamic tool parts

Some harness events like fileChange (for workspace file mutations) and compaction (when runtime compacts context) are projected as dynamic, provider-executed tool parts for UI compatibility. Check part.dynamic before assuming a tool part belongs to your typed tool set.

Host-executed tools example with weather tool

Example of defining a host-executed tool: const weather = tool({ description: 'Get the current temperature for a city.', inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => { const temperatures: Record<string, number> = { Paris: 12, Tokyo: 18, Reykjavik: 3 }; return { city, celsius: temperatures[city] ?? 20 }; }); Pass it to HarnessAgent with tools: { weather }.

Skills are reusable instruction bundles

Skills are reusable instruction bundles that can be useful for project conventions, workflow guidance, domain-specific procedures, or any other instructions that should be discoverable by the underlying harness runtime. Skills can be made available to a HarnessAgent for the lifetime of a session.

When to use skills versus instructions

Use skills for reusable instructions that should be available on demand, instead of always being loaded into the agent's context like regular `instructions`. Use `instructions` for broad agent behavior and current-session priorities.

Example HarnessAgent with skills

const agent = new HarnessAgent({ harness: claudeCode, sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000], }), skills: [ { name: 'careful-refactors', description: 'Make small, low-risk code changes.', content: 'Prefer minimal diffs. Preserve public APIs. Before editing, read references/checklist.md and follow it.', files: [ { path: 'references/checklist.md', content: '# Refactor checklist\n\n- Identify the smallest useful change.\n- Preserve public APIs.\n- Run the narrowest relevant test.', }, ], }, ], }); This example shows how to create a HarnessAgent with a skill that provides refactoring guidance and includes a checklist file.

Skill file paths are skill-relative POSIX paths

Additional files in a skill use skill-relative POSIX paths, such as `reference.md`, `references/codes.md`, or `templates/config.json`. These paths can be referenced from the `content` property when the agent should read them.

How to pass skills to HarnessAgent

Skills are passed to HarnessAgent using the `skills` setting in the HarnessAgent constructor. The skills parameter accepts an array of skill objects.

Skill object structure and properties

Each skill object has the following properties: `name` (stable identifier for the skill), `description` (short model-facing summary), `content` (full instruction content), and `files` (optional additional text files bundled with the skill).

AI SDK Harnesses overview and sections

The AI SDK Harnesses section covers a uniform API for running established agent harnesses such as Claude Code, Codex, and Pi. The harness abstraction includes Overview documentation on how harnesses relate to providers, models, agents, streams, and UI primitives; HarnessAgent for creating sessions, running turns, managing sandbox lifecycle, and resuming conversations; Tools for using built-in harness tools, host-executed AI SDK tools, approvals, and sandbox-aware execution; Skills for providing reusable instruction bundles to harness runtimes; Harness Adapters for available harness adapters; Workflow Utilities for running HarnessAgent turns as durable Workflow DevKit workflows; UI for streaming harness output to useChat and building chat routes that preserve harness sessions; and Terminal UI for running HarnessAgent in @ai-sdk/tui with a small session adapter.

HarnessAgent TUI example with codex and Vercel sandbox

Example creates HarnessAgent with codex harness and Vercel sandbox (runtime 'node24', ports [4000]), wraps it with AgentTUIAgent adapter, creates a session, runs runAgentTUI with options, and destroys the session in finally block.

@ai-sdk/tui renders harness streams, tool calls, reasoning, approval prompts

@ai-sdk/tui can render harness streams, tool calls, reasoning sections, and approval prompts in a terminal.

HarnessAgent requires session on every call, wrap with AgentTUIAgent adapter

Because HarnessAgent requires a session on every call, wrap it with a small AgentTUIAgent adapter that injects one session for the lifetime of the terminal UI.

Use one session per terminal run

Use one session per terminal run. For long-lived terminal tools, persist the state from session.detach() or session.stop() if you need to resume later.

AgentTUIAgent adapter pattern for HarnessAgent

The AgentTUIAgent adapter accepts an agent and session in its constructor. It implements version 'agent-v1', exposes the agent's id and tools, and wraps the agent's generate and stream methods to inject the session into requests.

runAgentTUI options: title, agent, tools, reasoning

runAgentTUI accepts options including title (string), agent (AgentTUIAgent), tools (with possible value 'auto-collapsed'), and reasoning (with possible value 'collapsed').

Terminal UI runs until user exits with Esc or Ctrl+C

The terminal UI runs until the user exits with Esc or Ctrl+C.

Workflow Utilities package

@ai-sdk/workflow-harness provides helpers for running HarnessAgent turns inside Workflow DevKit workflows. The package provides a serializable state machine and runners for time-sliced and semantic agent step turns.

HarnessAgent stopWhen configuration for semantic agent steps

When using semantic agent steps with workflows, set stopWhen to isStepCount(1) so one call to stream() completes one agent step. Omit stopWhen when using time slices.

runHarnessAgentStep function

runHarnessAgentStep() runs one semantic agent step and persists the harness turn after each agent step. It takes an object with two properties: agent (the HarnessAgent instance) and state (the HarnessWorkflowState). It returns a Promise<HarnessWorkflowState>.

Semantic agent step workflow structure

A semantic agent step workflow uses createHarnessWorkflowState() to initialize state, then loops calling agentStep() while state.status === 'ready_for_next_step'. After the loop, call finalizeHarnessWorkflow(state) to get the result or error.

HarnessWorkflowState status: ready_for_next_step

When HarnessWorkflowState.status === 'ready_for_next_step', the state carries a continueFrom property that lets the next Workflow step continue the same unfinished turn.

finalizeHarnessWorkflow returns result or throws

finalizeHarnessWorkflow() returns the workflow result if successful, or throws if the workflow failed.

runHarnessAgentTimeSlice function

runHarnessAgentTimeSlice() runs a long-running harness turn with wall-clock time boundaries. It takes an object with agent and state properties, and accepts an optional timeSliceSeconds parameter (default 750 seconds) to specify the time budget.

Time-sliced workflow structure

A time-sliced workflow uses createHarnessWorkflowState() to initialize state (omit stopWhen configuration), then loops calling timeSliceStep() while state.status === 'ready_for_next_step'. After the loop, call finalizeHarnessWorkflow(state) to get the result or error.

HarnessWorkflowState resumeFrom persistence

To continue a native harness session across separate user-turn workflow runs, persist the opaque resumeFrom state by sessionId. Load the previous resumeFrom before creating workflow state with createHarnessWorkflowState(), then persist the updated value after the execution loop completes.

createHarnessWorkflowState accepts resumeFrom

createHarnessWorkflowState() takes an object with messages, sessionId, and optional resumeFrom properties. The resumeFrom allows continuing a previous harness session.

Give your agent this brain