pi-acpinator

A fast, tiny ACP (Agent Client Protocol) adapter for the pi coding agent.


Keywords
acp, agent, coding-agent, llm, pi
License
MIT

Documentation

pi-acpinator

A fast, tiny ACP (Agent Client Protocol) adapter for the pi coding agent, written in Rust.

It speaks ACP JSON-RPC 2.0 over stdio to any ACP client (e.g. the Zed editor) and drives pi --mode rpc underneath. Because it spawns pi rather than embedding a JS runtime, the adapter is a small native binary — low memory, fast startup.

Why pi-acpinator?

Every other pi ACP adapter is a Node.js process. pi-acpinator is a single native Rust binary, so the adapter layer costs almost nothing. Measured head-to-head at the ACP initialize stage (node scripts/bench-compare.mjs, Apple M-series, medians):

Adapter Architecture Cold start Idle RSS
pi-acpinator Rust native binary, spawns pi ~4 ms ~3 MiB
pi-acp (svkozak, 472★) Node, spawns pi ~100 ms ~76 MiB
@victor-software-house/pi-acp Node, embeds the pi SDK in-process + background daemon heavier still¹ heavier still¹

That's roughly 20× faster startup and ~24× less memory than the most popular adapter — before pi itself is even launched. The reasons it wins across the board:

  • No runtime tax. No Node/V8/Bun to boot or resident — the Node adapters carry ~76 MiB and ~100 ms of runtime overhead per adapter process, before doing any work. pi-acpinator is ~3 MiB and starts in single-digit milliseconds.
  • Process isolation, not embedding. Like svkozak's, it spawns pi as a child (clean lifecycle, kill_on_drop). The victor/harms-haus adapters embed the entire pi Node SDK inside the adapter, duplicating the runtime; victor additionally needs a separate daemon.
  • Lower streaming overhead. Delta bursts are coalesced (~45× fewer ACP frames in the benchmark), both pipes are bounded for backpressure, and a dropped pi fails the turn loudly instead of hanging.
  • More capability, not less. It implements session/request_permission (a real tool permission gate) and a separate agent_thought_chunk reasoning stream — both of which the Node adapters document as not implemented — plus tool diffs, thinking modes, model selection, and session/load history replay.
  • Trivial to ship and run. One ~2 MB static binary (musl included): npx pi-acpinator, cargo install pi-acpinator, or a prebuilt release binary.

¹ @victor-software-house/pi-acp embeds the full pi Node SDK and requires a background daemon, so its resident footprint is strictly larger than a spawn-based Node adapter; it does not run standalone, so it's described architecturally rather than benchmarked here.

Status

Working today (live-verified against real pi):

  • initialize handshake; advertises load_session (no auth method — pi provider keys are configured externally via the pi CLI)
  • session/new — spawns and supervises a persistent pi --mode rpc session; advertises pi's thinking levels (off..xhigh) as session modes and pi's models as a config option
  • session/prompt — streams assistant output + reasoning as agent_message_chunk / agent_thought_chunk, coalescing delta bursts into far fewer frames; forwards image content blocks to pi
  • tool calls — pi tool execution maps to tool_call / tool_call_update (kind, status, output, cwd-resolved locations); write/edit surface as structured diffs
  • session/request_permission — a bundled pi extension gates tools via ctx.ui.confirm, surfaced to the client as ACP permission requests (scope: off | mutating | all)
  • session/set_mode — switches pi's thinking level
  • session/set_config_option — switches pi's model (validated; a bad model / missing key surfaces as an error)
  • session/load — resumes a persisted session and replays its history to the client; reuses an already-live session instead of spawning a second pi
  • session/cancel — aborts the turn and resolves it with StopReason::Cancelled
  • fails a turn loudly if pi exits before completing it

Measured (deterministic bench, node scripts/bench.mjs): 1.95 MiB binary, ~3 ms cold start, ~1M deltas/s with ~45x delta coalescing, ~5.6 MiB peak RSS. Both pi stdin and the event stream are bounded, so a slow peer applies backpressure instead of buffering without limit.

Install

# prebuilt binary via npm (no Rust toolchain needed)
npx pi-acpinator

# or from source
cargo install pi-acpinator     # from crates.io
cargo build --release          # from a checkout -> target/release/pi-acpinator

Use with an ACP client (Zed)

"agent_servers": {
  "pi": {
    "type": "custom",
    "command": "npx",
    "args": ["-y", "pi-acpinator"],
    "env": {}
  }
}

Or point command at a cargo build --release binary. Set PI_ACPINATOR_APPROVAL to off | mutating (default) | all to control tool permission prompts.

Requires pi on PATH (npm install -g @earendil-works/pi-coding-agent), configured with your model provider / API key.

Develop

cargo test                    # unit + transport tests (framing, translation, coalescing, correlation)
node scripts/component-test.mjs   # end-to-end component test against a scripted fake pi (no model)
node scripts/bench.mjs            # performance benchmarks (deterministic)
node scripts/bench-compare.mjs    # head-to-head vs other pi ACP adapters (installs them)
RUST_LOG=debug cargo run

License

MIT