Skip to content

acpx-ai-harness-v0.0.1

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Jun 08:43
· 79 commits to main since this release
931629d

First 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/harness API 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:

  1. 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.
  2. RERUN if the bridge is gone (sandbox cycled, port lost). A fresh bridge spawns and the acpx session reloads from disk via sessionKey. For continueFrom, a Continue. 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 resume

Permission 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.auth writes ~/.acpx/config.json inside 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 first ensureSession.

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 an unsupported-tool CallWarning on stream-start and 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's mcpServers field flow through verbatim today.
  • No manual compaction. doCompact() throws HarnessCapabilityUnsupportedError. acpx delegates compaction to the underlying agent.
  • @ai-sdk/sandbox-just-bash is rejected because just-bash cannot expose ports.

Links