acpx-ai-harness-v0.0.1
Pre-releaseFirst public release of acpx-ai-harness: a Vercel AI SDK v7 HarnessV1 adapter built on the acpx runtime. Brings any ACP-protocol agent (Claude Code, Codex, Gemini, Copilot, Cursor) into the AI SDK Harnesses ecosystem alongside @ai-sdk/harness-claude-code, @ai-sdk/harness-codex, and @ai-sdk/harness-pi.
Experimental. Both this package and the upstream
@ai-sdk/harnessAPI are pre-1.0 and subject to breaking changes.
Install
npm install acpx-ai-harness @ai-sdk/harness ai acpx@ai-sdk/harness, ai, and acpx are declared as peer dependencies. A sandbox provider with port exposure is also required (see Sandbox setup below).
Quick start
import { HarnessAgent } from '@ai-sdk/harness/agent'
import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'
import { createAcpxHarness } from 'acpx-ai-harness'
const harness = createAcpxHarness({
agent: 'codex',
auth: { openai_api_key: process.env.OPENAI_API_KEY! },
})
const agent = new HarnessAgent({
harness,
sandbox: createVercelSandbox({ runtime: 'node22', ports: [4001] }),
})
const session = await agent.createSession()
try {
const result = await agent.stream({ session, prompt: 'Refactor user.ts to use Result<T, E>' })
for await (const part of result.fullStream) {
if (part.type === 'text-delta') process.stdout.write(part.delta)
}
} finally {
await session.destroy()
}What's in 0.0.1
Agents
The harness's bootstrap recipe pre-warms the ACP wrapper binary acpx invokes for each known agent. Vercel sandbox keys snapshots by the recipe hash, so the install cost is paid once and amortised across sessions.
agent setting |
Bootstrap pre-warm | settings.auth key |
|---|---|---|
'codex' (default) |
npx --yes @agentclientprotocol/codex-acp --version |
openai_api_key |
'claude' |
npx --yes @agentclientprotocol/claude-agent-acp --version |
anthropic_api_key |
'gemini' |
npm install -g @google/gemini-cli |
gemini_api_key |
| any other id | (skipped, bring your own install) | (varies, see acpx config docs) |
Lifecycle
acpxHarness implements the full HarnessV1Session surface except doCompact (acpx auto-compacts internally; manual compaction has no API and the method throws HarnessCapabilityUnsupportedError). The harness picks between two recovery rungs when createSession({ resumeFrom }) or createSession({ continueFrom }) is called:
- ATTACH when the saved bridge coords are still live (same sandbox id, reachable WebSocket). The host reconnects to the running bridge and replays buffered events past the saved cursor.
- RERUN if the bridge is gone (sandbox cycled, port lost). A fresh bridge spawns and the acpx session reloads from disk via
sessionKey. ForcontinueFrom, aContinue.nudge is sent so the agent picks up where it left off.
const state = await session.detach() // park, bridge stays alive
const resumed = await agent.createSession({
sessionId: session.sessionId,
resumeFrom: state,
})
const stopState = await session.stop() // bridge exits, state captured for resumePermission modes
| Harness mode | acpx mode | Behaviour |
|---|---|---|
allow-all (default) |
approve-all |
Every tool call auto-approved. |
allow-edits |
approve-all |
acpx's approve-all already covers the edits bucket. |
allow-reads |
approve-reads |
Reads / searches auto-approved; edits and shell raise an approval request via tool-approval-request. |
The host receives tool-approval-request stream parts for any tool the agent wants to call, and responds via HarnessV1PromptControl.submitToolApproval({ approvalId, approved, reason? }).
Built-in tools
The harness advertises the seven harness common-tool entries (read, write, edit, bash, grep, glob, webSearch) so cross-harness consumers can identify them. Per-agent native tool names (Claude's Bash, Codex's shell, Gemini's run_shell_command) are normalised to the common names on the wire; the original native name flows along on tool-call.nativeName.
Sandbox setup
acpx-ai-harness is a bridge-backed adapter: it spawns a Node.js process inside the sandbox that drives the ACP agent and serves a WebSocket to the host. The sandbox provider must support port exposure for the host to reach that WebSocket.
| Provider | Works? | Notes |
|---|---|---|
@ai-sdk/sandbox-vercel |
Yes | Cloud sandbox, supports port exposure + snapshots. Requires VERCEL_TOKEN, VERCEL_TEAM_ID, VERCEL_PROJECT_ID. |
@ai-sdk/sandbox-just-bash |
No | Local sandbox, doesn't expose ports. Bridge-backed adapters reject it at start. Useful for non-bridge harnesses. |
Other providers conforming to HarnessV1SandboxProvider work as long as their network sandbox session implements getPortUrl({ port, protocol: 'ws' }).
Auth
Per acpx config docs, standard provider env vars like OPENAI_API_KEY reach child processes but do not drive acpx's auth gate. Credentials must flow through acpx's own auth channels:
settings.authwrites~/.acpx/config.jsoninside the sandbox per session.- The same map is exported as
ACPX_AUTH_<METHOD_ID>env vars on the bridge spawn so acpx's runtime sees them on firstensureSession.
Both channels are populated per session, never via the bootstrap recipe, so credentials never end up in a Vercel sandbox snapshot.
The harness defaults authPolicy: 'fail' when auth is non-empty so a missing or invalid key surfaces immediately rather than as a downstream ACP error. Override with settings.authPolicy: 'skip' for legacy "let the adapter handle it" behaviour.
Verified end-to-end
generate() and stream() were exercised live against a real Vercel sandbox + a real codex turn before the release was cut: 2 pass / 0 fail / ~73 seconds cold start. Asserts in the smoke suite cover non-empty result.text and the presence of text-delta + finish parts on fullStream.
Current limitations
- Host AI SDK tools are not yet forwarded to the agent. Tools passed via
HarnessAgent({ tools })emit anunsupported-toolCallWarningonstream-startand are otherwise ignored. acpx's runtime only accepts wire-protocol MCP servers (stdio / http / sse); a host-side MCP server inside the bridge needs to land before this works end to end. Stdio / http / sse MCP servers passed via the start frame'smcpServersfield flow through verbatim today. - No manual compaction.
doCompact()throwsHarnessCapabilityUnsupportedError. acpx delegates compaction to the underlying agent. @ai-sdk/sandbox-just-bashis rejected because just-bash cannot expose ports.
Links
- npm: https://www.npmjs.com/package/acpx-ai-harness
- README: https://github.com/DaniAkash/acpx/tree/main/packages/acpx-ai-harness
- acpx runtime: https://acpx.sh
- AI SDK Harnesses: https://ai-sdk.dev/v7/docs/ai-sdk-harnesses/overview