Harness Agents
Who is this for: host authors wiring a full coding runtime (Claude Code, Codex, Pi) into Agent-Native as the agent. Building an app? Start with Creating Templates.
A harness agent is a full agent runtime — Claude Code, Codex, Pi, and similar —
that owns its own loop, workspace, native file tools, session state, compaction,
approval model, and sandbox behavior. Agent-Native runs these through the
AgentHarness substrate in @agent-native/core/agent/harness, streams their
events into the normal transcript, and persists their native session so a thread
can pause and resume.
This is different from the built-in chat agent and from bringing your own chat
runtime. The built-in agent and AgentEngine are for one model round trip
beneath runAgentLoop. A harness is not an AgentEngine provider — it runs its
own loop end to end, so Agent-Native drives it as a session, not as a single
model call.
Which coding doc do I want?
| You want to… | Use |
|---|---|
| Run Claude Code / Codex / Pi as the agent, with their own loop + tools | Harness agents (this page) |
| Render a Claude-Code/Codex-style coding workspace UI | Agent-Native Code UI |
Swap the backend that runs the agent's run-code tool |
Adapters |
Wrap a CLI tool (gh, ffmpeg) for the agent to call |
Adapters |
Adjacent surfaces: put an agent you built elsewhere behind Agent-Native's chat
UI with AgentChatRuntime; let an
external MCP host call into your app via External Agents;
spawn background / sub-agent runs with Custom Agents & Teams.
Built-in harnesses
registerBuiltinAgentHarnesses() registers three adapters backed by the AI SDK
HarnessAgent:
| Name | Runtime | Sandbox | Approvals |
|---|---|---|---|
ai-sdk-harness:claude-code |
Claude Code | yes | yes |
ai-sdk-harness:codex |
Codex | yes | no |
ai-sdk-harness:pi |
Pi | no | yes |
Their runtime packages are optional peer dependencies and load lazily, so an
app that never uses a harness does not pay for it. Each adapter carries an
installPackage hint (for example @ai-sdk/harness@latest @ai-sdk/harness-codex@latest); resolveAgentHarness throws a clear install
error if the packages are missing, and isAgentHarnessPackageInstalled(entry)
lets you check first.
registerBuiltinAgentHarnesses() also registers the ACP harnesses
(acp, acp:gemini, acp:claude-code).
For sandboxed built-in harnesses, pass a sandbox provider through
agentOptions.sandbox when registering them. The provider must implement
resumeSession for rehydration after a process restart; a caller-owned
sandbox session only covers initial creation.
ACP agents
Agent-Native can act as an ACP (Agent Client Protocol) client and drive a local coding agent — Gemini CLI, Claude Code, or any ACP-compliant agent — through this same substrate. The agent runs as a local subprocess that speaks newline-delimited JSON-RPC over stdio; ACP's editor ↔ agent model is exactly this shape.
This adapter is scoped to local coding. The child process inherits the
parent environment, so the agent reuses whatever local CLI login it already has
(for example gemini or claude auth in the user's home dir). It is not a
hosted or sandboxed transport, and it is not a chat/A2A transport — for those,
see Agent Surfaces.
| Name | Default command | Resumable* |
|---|---|---|
acp |
(supply command/args via config) |
yes |
acp:gemini |
npx -y @google/gemini-cli --experimental-acp |
yes |
acp:claude-code |
npx -y @zed-industries/claude-code-acp |
yes |
*Resume works when the agent advertises the loadSession capability and
degrades to a fresh session otherwise.
import {
registerBuiltinAgentHarnesses,
resolveAgentHarness,
} from "@agent-native/core/agent/harness";
registerBuiltinAgentHarnesses();
// A built-in preset (command/args are overridable through the resolve config):
const adapter = resolveAgentHarness("acp:gemini");
// Or any ACP agent by command:
const custom = resolveAgentHarness("acp", {
command: "gemini",
args: ["--experimental-acp"],
});The protocol transport (@zed-industries/agent-client-protocol) is an optional
dependency loaded lazily through the installPackage hint, just like the AI SDK
harnesses. The agent binary itself (@google/gemini-cli,
@zed-industries/claude-code-acp, …) is a separate external CLI; the presets
launch it through npx and the command/args stay overridable because agent ACP
entry flags still evolve.
permissionMode maps onto ACP session/request_permission using the tool-call
kind the agent reports: reads always run, edits run under allow-edits, and
everything risky prompts unless allow-all. Approvals surface as the normal
approval-request events. The adapter serves fs/read_text_file and
fs/write_text_file against the session workspace (refusing paths that escape
it) and writes emit file-change events; terminal methods are not advertised,
so the agent uses its own shell.
Codex auth: Code UI vs harness adapters
There are two Codex surfaces:
- Agent-Native Code / Desktop runs
codex execon the user's machine and reuses the local Codex CLI login reported bycodex login status. ai-sdk-harness:codexuses the upstream AI SDK Harness native subscription flow where supported. No extra setting is required: credentials stay on the host, explicit API-key credentials take precedence, and the resulting auth is bridged into the sandbox without copying the local auth file. SetharnessOptions.authto"ai-gateway"to use AI Gateway only; that mode never reads a native subscription.
The built-in ai-sdk-harness:codex adapter only supports
permissionMode: "allow-all", because that is the stable Codex runtime's only
accepted mode. Omit permissionMode to use that default;
allow-reads and allow-edits are rejected for this adapter.
Register and resolve
import {
registerBuiltinAgentHarnesses,
resolveAgentHarness,
} from "@agent-native/core/agent/harness";
registerBuiltinAgentHarnesses();
const adapter = resolveAgentHarness("ai-sdk-harness:codex");resolveAgentHarness(name, config?) returns an AgentHarnessAdapter. The
optional config is forwarded to the adapter factory — for the AI SDK adapters
that maps to AiSdkHarnessAdapterOptions (label, description,
permissionMode, harnessOptions, and agentOptions). Use
listAgentHarnesses() to enumerate what is registered for a picker.
Run a turn
startAgentHarnessRun bridges a harness session into the shared run-manager
lifecycle. It creates (or reuses) the native session, persists it, streams the
turn, translates each harness event into transcript events, and detaches the
resumable state when the turn completes.
import { startAgentHarnessRun } from "@agent-native/core/agent/harness";
const run = startAgentHarnessRun({
runId,
threadId,
adapter,
input: { prompt },
createSession: {
sessionId,
resumeState, // opaque value from a previous turn, if resuming
instructions,
sandbox, // required for sandboxed harnesses — see Sandbox Adapters
permissionMode: "allow-all",
tools, // a narrow, intentional set of host tools (see below)
},
ownerEmail,
orgId,
});startAgentHarnessRun returns the ActiveRun from the run-manager, so the turn
shows up through the existing run routes, transcript, and cancellation just like
any other agent run. Pass an already-created session instead of createSession
to continue a session you are holding in memory.
Sessions and resume
A harness owns long-lived native session state. Agent-Native persists it in SQL
so a thread can survive across turns, processes, and deploys. The resumeState
is opaque — Agent-Native stores it and hands it back, but never inspects or
interprets it.
import {
getLatestAgentHarnessSessionForThread,
listAgentHarnessSessions,
} from "@agent-native/core/agent/harness";
const last = await getLatestAgentHarnessSessionForThread(threadId);
// Feed last?.resumeState into createSession.resumeState on the next turn.The store also exposes saveAgentHarnessSession, updateAgentHarnessSession,
getAgentHarnessSession, getAgentHarnessSessionByRunId,
markAgentHarnessSessionStopped, and ensureAgentHarnessSessionTables.
startAgentHarnessRun calls the save/update/stop paths for you; reach for them
directly only in a custom host.
Host tools and permissions
A harness brings its own native tools (read, edit, write, shell, and so on), so
you do not re-expose file editing as host tools. Pass only a narrow,
intentional set of Agent-Native actions through createSession.tools when you
want the harness to reach specific app operations — and keep defineAction
auth, request context, timeouts, truncation, and read-only metadata intact when
you do.
permissionMode gates what the harness may do without approval:
| Mode | Meaning |
|---|---|
allow-reads |
Default. Reads run; edits and risky actions prompt |
allow-edits |
Reads and edits run; other risky actions prompt |
allow-all |
No approval gating |
When a harness pauses for approval it emits an approval-request event and the
session is marked idle with the pending approval recorded, so the UI can
surface it and resume on the user's decision. See
Human Approval for the approval surface.
Events
A harness session streams AgentHarnessEvent values, which Agent-Native
translates to the standard AgentChatEvent stream with
agentHarnessEventToAgentChatEvents. The event union covers text-delta,
thinking-delta, activity, tool-start, tool-done (which can carry an
mcpApp payload for native widgets), approval-request, file-change,
compaction, usage, error, and done. Because tool results flow through the
same translation, action-declared native widgets still render — see
Native Chat UI.
Background runs and the UI
Harness runs project into the shared BackgroundAgentRun shape with
createAgentHarnessBackgroundAgentController() and are available through the
existing run routes as goalId=agent-harness. That means a long-running Claude
Code or Codex session appears in the same background-run and transcript surfaces
as Agent Teams and other adapters, with listAgentHarnessBackgroundRuns,
listAgentHarnessBackgroundTranscriptEvents, getAgentHarnessBackgroundRun, and
stopAgentHarnessBackgroundRun available for custom hosts.
Custom adapters
To wrap a runtime that is not one of the built-ins, implement
AgentHarnessAdapter and register it. The adapter declares its capabilities and
creates sessions; a session exposes streamTurn and optional continueTurn,
approve, detach, stop, and destroy.
import {
registerAgentHarness,
type AgentHarnessAdapter,
} from "@agent-native/core/agent/harness";
const myHarness: AgentHarnessAdapter = {
name: "acme:my-coder",
label: "Acme Coder",
description: "Runs the Acme coding agent.",
installPackage: "@acme/coder",
capabilities: {
sandbox: true,
resumable: true,
approvals: true,
hostTools: true,
fileEvents: true,
},
async createSession(opts) {
// Build your native session and adapt it to AgentHarnessSession.
return createAcmeSession(opts);
},
};
registerAgentHarness({
name: myHarness.name,
label: myHarness.label,
description: myHarness.description,
installPackage: myHarness.installPackage,
capabilities: myHarness.capabilities,
create: () => myHarness,
});Keep the runtime package optional with a dynamic import in createSession and an
installPackage hint. For bridge-backed coding harnesses, require a real
sandbox/workspace provider rather than running an arbitrary coding agent in the
host process — see Sandbox Adapters. The AI SDK adapter
(createAiSdkHarnessAdapter, backed by HarnessAgent from @ai-sdk/harness) is
one implementation of this contract, not the public abstraction.
Don't
- Don't add Claude Code, Codex, Cursor, Mastra, or Pi as an
AgentEngine. They own their loop; running one underAgentEngine.stream()double-runs the loop and loses session lifecycle semantics. - Don't replay full Agent-Native chat history into a harness each turn. Resume
the harness session with its
resumeStateinstead. - Don't store
resumeStateinapplication_state. It belongs in the harness session SQL table. - Don't expose every app action to every harness session by default. Hand it a small, intentional tool set.
What's next
- Native Chat UI — put your own agent behind the chat UI with
AgentChatRuntime. - Agent Surfaces — choose headless, chat, sidecar, or full-app.
- Agent-Native Code UI — the reusable coding workspace surface.
- Custom Agents & Teams — background runs and sub-agent delegation.
- Sandbox Adapters — pluggable execution backends for coding harnesses.
- Human Approval — the approval surface harness runs use.