Wires the ACP runtime plugin into the published CLI (RUNTIME_PLUGIN_IDS in tsup.config) and the on-demand BUILTIN_PLUGINS catalog (experimental), matching the untrusted-subprocess security posture. Adds the Risk S1 default-policy safety: an acpAllowUnrestricted acknowledgement (default false) — without it, a blanket allow on a sensitive category is escalated to approval rather than auto-approved under the allow-all default policy, applied in both the permission floor and fs write gating. Adds docs/acp-contract.md (launch/readiness + failure taxonomy), a README with the AGENTS.md-required upstream evidence (SDK repo/docs/release/integrity), a bundle-output test for the staged plugin, and a @runfusion/fusion minor changeset. Package green at 179 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.1 KiB
3.1 KiB
ACP (Agent Client Protocol) Runtime Contract
Date: 2026-06-03
Launch/readiness contract and failure taxonomy for fusion-plugin-acp-runtime,
which drives any external Agent Client Protocol
agent over JSON-RPC/stdio. Mirrors the shape of docs/cursor-cli-contract.md.
Transport
- Newline-delimited JSON-RPC 2.0 over stdio (no Content-Length framing).
Provided by
@agentclientprotocol/sdk(ndJsonStream+ClientSideConnection). - The client (Fusion) launches the agent as a subprocess with piped stdio. The agent's stdin is the JSON-RPC output stream; its stdout is the input stream.
stderris captured (redacted) for diagnostics, never parsed as protocol.
Invocation and binary detection
- Unlike a single-vendor CLI, ACP is a protocol — the agent binary + ACP-mode
flag are user-configured:
acpBinaryPath— e.g.gemini,npx, or an absolute path.acpArgs— the flag(s) that put the agent in ACP/stdio mode, e.g.["--acp"].
- The subprocess environment is built from the
acpEnvAllowListallow-list only (inheritedprocess.envis not forwarded — the agent is untrusted).
Readiness = the initialize handshake
There is no --version probe. Readiness is the protocol handshake itself:
- Spawn the agent subprocess.
- Send
initialize { protocolVersion: 1, clientCapabilities: { fs } }under a timeout (default 30s — research flagged Gemini-on-macOS OAuth and Claude-adaptersession/newstalls). - The agent responds with its integer
protocolVersion,agentCapabilities, andauthMethods. - The client compares the integer protocol version; an unsupported version is a hard failure (do not assume the agent errors first).
fs capabilities are advertised only when acpFsRead/acpFsWrite are
enabled (writes default OFF).
Failure taxonomy (probe.ts AcpProbeReason)
| Reason | Trigger |
|---|---|
ok |
Handshake completed (with authRequired: true when authMethods is non-empty) |
missing_binary |
Spawn ENOENT (binary not found, code 127) |
spawn_error |
Other spawn failure |
handshake_timeout |
initialize did not complete within the bound (code 124) |
incompatible_protocol |
Agent negotiated an unsupported integer protocol version |
unauthenticated |
Agent requires an auth method the client cannot satisfy |
Lifecycle / teardown
- The engine has no
AbortSignalin the runtime contract; teardown enters via an unawaited synchronousdispose()plus the process-registry kill. The registry SIGKILL is the authoritative no-orphan / no-deadlock guarantee; a best-effortsession/cancel+ pending-permission drain runs first when timing allows but is opportunistic.
Sources
- https://agentclientprotocol.com (introduction, schema, transports, initialization, tool-calls)
@agentclientprotocol/sdkv0.24.0 — https://www.npmjs.com/package/@agentclientprotocol/sdk- Validation: the SDK example echo agent (CI) + an in-repo controllable fixture
(
src/__tests__/fixtures/echo-agent.mjs); Gemini CLI / Claude-adapter for manual e2e.