ACP native bridge

May 21, 2026 · View on GitHub

Bernstein speaks the Agent Client Protocol natively - editors that ship ACP support can plug Bernstein in as their backend with zero per-IDE plumbing.

The bridge is a protocol adapter: ACP prompt opens a Bernstein task, streamUpdate notifications tail the existing streaming-merge utility, cancel walks the standard drain pipeline, and setMode toggles the janitor approval gate. Cost-aware routing, HMAC audit, and sandbox-backend selection apply identically to ACP-initiated and CLI-initiated sessions.

Transports

TransportWhen to useCommand
stdio (line-delimited JSON-RPC)IDE embedding (default)bernstein acp serve --stdio
HTTP / SSEremote IDEs, CI, debuggingbernstein acp serve --http :8062

Stdio is the canonical IDE transport: the editor spawns bernstein acp serve --stdio as a subprocess and exchanges line-delimited JSON-RPC frames over its stdio pipes. The HTTP transport accepts a single JSON-RPC frame per POST /acp request and supports Accept: text/event-stream for streaming responses.

Zed settings.json

{
  "agent_servers": {
    "bernstein": {
      "command": "bernstein",
      "args": ["acp", "serve", "--stdio"],
      "env": {}
    }
  }
}

Generic ACP client (any editor)

{
  "name": "bernstein",
  "transport": "stdio",
  "command": ["bernstein", "acp", "serve", "--stdio"],
  "protocolVersion": "2025-04-01"
}

For HTTP-mode integrations, point the client at http://127.0.0.1:8062/acp and set Accept: text/event-stream to receive streaming streamUpdate and requestPermission notifications.

Methods supported

MethodDirectionNotes
initializeclient → serverreports server capabilities, available adapters, and configured sandbox backends
initializedclient → server (notification)acknowledged silently
promptclient → serveropens a task in the existing task store; returns a real Bernstein session id
streamUpdateserver → client (notification)tails the streaming-merge utility
cancelclient → serverwalks the standard drain + shutdown pipeline
setModeclient → servertoggles auto (always-allow) ↔ manual (interactive approval gate)
requestPermissionserver → client (prompt) and client → server (decision)maps onto the janitor approval gate

Observability

Two Prometheus metrics ship with the bridge:

  • bernstein_acp_messages_total{method, outcome} - JSON-RPC message count partitioned by method and outcome (ok, error, rejected, cancelled, permission_denied).
  • bernstein_acp_active_sessions - gauge of live ACP sessions.

Both are exported via the existing observability stack - the running task server's /metrics endpoint scrapes them automatically.

Audit

Every ACP-driven mutation produces an HMAC-chained audit entry that is byte-identical (modulo timestamp + chain HMAC) to the entry the CLI surface emits for the same operation. See tests/integration/acp/test_audit_parity.py for the parity guard.

Out of scope

  • Windows named-pipe transport - POSIX stdio + HTTP only.
  • File-edit primitives that are not in the ratified ACP spec.
  • ACP authentication beyond loopback - remote HTTP usage rides the existing tunnel wrapper.