Voicebox integrates external coding agents—such as Pi (lib/pi-acp.mjs) and Claude Code (lib/claude-acp.mjs)—using the Agent Client Protocol (ACP v1) over stdio JSON-RPC (lib/acp-client.mjs).
You can activate a host coding agent harness in either of two ways:
PUT /api/harnesses/active). The selection is saved in .harness-settings.json and activates the corresponding ACP executor immediately without restarting the server.VOICEBOX_HARNESS=pi npm start
# or
VOICEBOX_HARNESS=claude npm start
To verify installed harness binaries and adapter readiness from the terminal:
npm run doctor
When no harness is active, calling delegate_task returns executor-unavailable.
lib/acp-client.mjs)createAcpClient manages the JSON-RPC stdio lifecycle for both Pi and Claude Code:
initialize, creates a workspace session via session/new, configures model and reasoning options via session/set_config_option, and dispatches tasks via session/prompt.60,000ms ceiling for pi-acp; 120,000ms CLAUDE_ACP_TIMEOUT_CEILING_MS for claude-acp, meta-capped at 600,000ms) and a 64 KiB output ceiling.cancel() during an active turn sends session/cancel ({ ok: true, sent: true }). When the adapter confirms stopReason: "cancelled", the task settles as task-cancelled. A frame dropped because the harness was already gone reports sent: false — no notification left, and the transport’s closure is the verdict (voicebox-beads-cps6).cancel() when no task exists returns task-not-found.cancel() after a task has already settled returns task-not-running.TaskInterrupted (lib/task-interrupted.mjs) with reason harness-ended-outcome-unknown.
EPIPE, or asynchronously as an 'error' on the child’s stdin. That write failure is not the task’s outcome: the frame is dropped because there is nobody to receive it, and the exit descriptor (exit code, stage and stderr tail) is the diagnostic the caller sees. isPeerGoneWrite (lib/acp-client.mjs) names the peer-gone codes; any other write failure still surfaces as itself (tests/acp-client.test.mjs, voicebox-beads-cps6).lib/pi-acp.mjs)pi-acp (0.0.34) with pi (0.87.1). Override binary paths via VOICEBOX_ACP_ADAPTER and VOICEBOX_ACP_PI.openPiAcpProbe() for credential-free handshake verification inside bubblewrap isolation. Binds the active Node runtime (process.execPath) to /packages/node so the probe runs independent of host /usr/bin/node symlinks or user-profile locations (for Node runtimes depending on system libraries under /usr/lib and /lib64), and reports exit status on abnormal termination without exposing raw child stderr (voicebox-beads-7i5j).ANTHROPIC_API_KEY through to the pi-acp child environment so Anthropic-backed model selections succeed when Pi’s internal auth store has no separate credential (tests/pi-acp-options.test.mjs).lib/claude-acp.mjs)@agentclientprotocol/claude-agent-acp over runAcpTask. Override the underlying CLI binary via VOICEBOX_CLAUDE_CLI.lib/claude-acp.mjs omits ANTHROPIC_API_KEY from the spawned child environment so an ambient key does not override an authenticated claude.ai CLI login. Set VOICEBOX_CLAUDE_KEEP_API_KEY=1 (or keepApiKey: true) to explicitly retain the environment variable in the child process.core/harness-config.ts)When delegate_task targets a configured agent entry (core/harness-config.ts, lib/harness-config.mjs), the adapter applies and validates its settings before running the prompt:
session/set_config_option (configId: "model"). Unsupported models fail pre-flight with model-unsupported.model.thinking is specified, forwarded via session/set_config_option (configId: "thought_level"). Unsupported levels fail with thinking-level-unsupported.agentConfig.prompt): Prepended to the task prompt as system framing.agentConfig.reach.tools): Any tool request outside the agent’s declared reach list is refused with tool-not-in-agent-reach before consulting host policy.adapter-not-configured, adapter-version-unsupported, unsupported-runtime-capability, or unsupported-model-options.During ACP and Claude Code task execution, intermediate protocol events and console logs are surfaced live in the UI:
initializing, session/new, session/prompt, and tool calls) stream via onProgress to the task host and appear live on the Result Card (#task-card).stderr and tool call diagnostics stream into the live Work Activity panel (#activity-log-panel) and Result Card (#task-card-console).lib/redact.mjs): All streamed console output, progress notes, and task details pass through strict redaction that scrubs API keys (sk-ant-..., AIza...), Bearer/Basic auth headers, private keys, and ambient host environment tokens before reaching the client or audit log. The terminal answer and any failure partial are scrubbed at the durable boundary itself (settle() in lib/tasks.mjs), and the CLI harness executor redacts child output at the producer before any consumer sees it (voicebox-beads-fcx9).#session-log).Stdio ACP adapters (lib/pi-acp.mjs, lib/claude-acp.mjs) spawn local OS subprocesses and therefore run on machine and remote placements (lib/tasks.mjs). Zero-server browser environments (tests/acp-browser.test.mjs) use createBrowserTaskHost() in lib/task-placement.mjs (see 16-zero-server-delegation.md).
VOICEBOX_ACP_ADAPTER="$PI_ACP_INSTALL_DIR" VOICEBOX_ACP_PI="$PI_BINARY" \
node --test tests/acp-client.test.mjs tests/pi-acp.test.mjs tests/acp-browser.test.mjs tests/tasks.test.mjs tests/configured-harness.test.mjs