@wierdbytes/pi-acp
v0.1.3
Published
ACP (Agent Client Protocol) adapter for the pi coding agent — bridges buzz-acp (JSON-RPC over stdio) to `pi --mode rpc`.
Maintainers
Readme
pi-acp
ACP (Agent Client Protocol) adapter for the pi coding agent
(@earendil-works/pi-coding-agent). Lets pi join a
Buzz community as a first-class headless agent
via the buzz-acp harness.
Buzz Relay ──WS(NIP-42)──→ buzz-acp ──stdio(ACP JSON-RPC)──→ pi-acp ──stdio(pi rpc JSONL)──→ pi --mode rpc
│
bash tool → `buzz` CLI → relaypi has no native ACP support; its headless surface is pi --mode rpc (JSON
lines). pi-acp is a thin bridge: an ACP server upstream (initialize,
session/new, session/prompt, session/cancel, session/update streaming) and a
pi rpc client downstream (prompt/steer/abort → agent events).
Design/plan: docs/plans/20260723-Init-plan.md.
Install
npm install && npm run build
npm link # → `pi-acp` on PATHRequires Node ≥ 18 and a working pi (authenticated via ~/.pi/agent/auth.json).
Run under buzz-acp
export BUZZ_PRIVATE_KEY="<agent-secret-hex-or-nsec>"
export BUZZ_RELAY_URL="wss://your-relay"
export BUZZ_AUTH_TAG='["auth","<owner-pk>","","<sig>"]' # NIP-OA path only
export BUZZ_ACP_RESPOND_TO="anyone" # default is owner-only
export BUZZ_ACP_AGENT_COMMAND="pi-acp"
# BUZZ_ACP_AGENT_ARGS defaults to "acp" — pi-acp accepts and ignores it.
# Teach pi the buzz CLI (skill from the buzz repo) and make `buzz` reachable:
export PI_ACP_SKILLS="$BUZZ_REPO/.agents/skills/sprout-cli"
export PATH="$BUZZ_REPO/target/release:$PATH"
buzz-acpThe pi child inherits the whole environment, so BUZZ_PRIVATE_KEY /
BUZZ_RELAY_URL / BUZZ_AUTH_TAG reach the buzz CLI that pi runs through
its bash tool — that is how replies actually land in the channel (buzz-acp
itself only logs the streamed chunks).
Relay onboarding (key generation, membership/NIP-OA attestation, kind:0 profile, channel membership) is documented step-by-step in plan §11.
Configuration
| Flag | Env | Default | Meaning |
|---|---|---|---|
| --pi-bin <path> | PI_ACP_PI_BIN | pi | pi binary to spawn |
| --skill <path> (repeatable) | PI_ACP_SKILLS (comma-sep) | — | forwarded to pi as --skill |
| --pi-arg <arg> (repeatable) | PI_ACP_PI_ARGS (comma-sep) | — | raw extra args for pi (e.g. --no-session) |
| --model <value> | PI_ACP_MODEL | pi's default | forwarded as --model |
| --thinking <level> | PI_ACP_THINKING | pi's default | forwarded as --thinking |
| --log-file <path> | PI_ACP_LOG_FILE | — | wire-level debug log |
| --debug | PI_ACP_DEBUG=1 | off | verbose wire logging on stderr |
Behavior notes
- initialize reports
protocolVersion: 2— required, or buzz-acp won't forwardsystemPromptinsession/new(pool.rs::session_new_system_prompt). - One ACP session = one pi process.
session/new.systemPromptbecomes--append-system-promptat spawn;session/new.cwdis the child's cwd. - Turn settlement:
session/promptresolves{stopReason:"end_turn"}on pi'sagent_settled(auto-retries keep the turn open; a 15s fallback covers a missingagent_settled);session/cancel→ piabort→{stopReason:"cancelled"}(30s force-settle safety valve). - Streaming: assistant text →
agent_message_chunk, thinking →agent_thought_chunk, tools →tool_call/tool_call_update(thetool_callupdate explicitly resets buzz-acp's idle timer). A keepalive update is emitted after 45s of total silence so long quiet tools surviveBUZZ_ACP_IDLE_TIMEOUT. - Non-cancelling steer: pi-acp advertises
_meta.goose.activeRunIdviasession_info_update, so buzz-acp can inject mid-turn messages through_goose/unstable/session/steer→ mapped to pi's nativesteercommand. - Headless hardening: pi extension UI requests are auto-answered
(confirm→yes, input→"", select/editor→cancelled);
session/request_permissionis never emitted — pi runs fully autonomous. - mcpServers: ACP stdio MCP servers from
session/neware written to a temp config and passed as--mcp-config(requires thepi-mcp-adapterextension to be installed in pi). - Model switching:
session/newadvertises pi's model catalog as the unstable ACPmodelsstate ({ currentModelId, availableModels }, ids in pi'sprovider/idform, e.g.qwen-token-plan/qwen3.8-max-preview), andsession/set_modelmaps to pi's nativeset_modelrpc. This is how a per-agentmodelconfigured in Buzz (applied by buzz-acp after everysession/new) reaches pi — without it pi silently stays on its owndefaultModelfrom~/.pi/agent/settings.json. The--modelflag still works as a spawn-time default.
Smoke tests
npm run smoke # initialize + authenticate (no pi spawn)
npm run smoke:session # + session/new (spawns pi, no LLM call)
npm run smoke:model # + models catalog + session/set_model (no LLM call)
npm run smoke:prompt # + prompt→stream→end_turn (1 small LLM call)
npm run smoke:cancel # + cancel during a long bash tool (1 small LLM call)