A reactive session/turn store + permission broker for the Agent Client Protocol.
The official @agentclientprotocol/sdk
gives you the wire: the fluent client() builder, typed handlers, stdio/HTTP/SSE/WebSocket
transports. acpq adds the state stratum an embedding app needs — for web UIs, notebooks,
dashboards, and editors hosting coding agents:
- Reactive session store —
session/updatestreams fold into observable per-session state (messageText,toolCallswith live statuses,plan,availableCommands,currentMode, stop reasons, plus the raw update log).subscribe()isuseSyncExternalStore-ready, and@johnhenry/acpq/reactships hooks (useSession,useToolCalls,usePermissions) built directly on it. - Permission broker — ACP's
session/request_permission(typedallow_once/always/reject_once/alwaysoptions) routes through the sharedInteractionBroker: trust policy auto-answers, "ask" queues for your approval UI (resolvewith anoptionId), every outcome audited. With no broker configured, requests fail safe (reject). - In-process mock agent (
@johnhenry/acpq/testing) — the SDK's ownagent()builder wired straight to the client (connect(mockAcpAgent(...))): real protocol, no transport, withsay/toolCall/askPermissionturn helpers.
import { AcpQuery, InteractionBroker } from "@johnhenry/acpq";
const broker = new InteractionBroker();
const q = new AcpQuery({ interactions: broker });
q.connect(myAgentStream); // ndJsonStream over stdio, WebSocket, ... or an AgentApp
const sid = await q.newSession("/workspace");
q.subscribe(sid, () => render(q.session(sid))); // live turn state
await q.prompt(sid, "refactor the auth module");
// broker.list() -> pending permission requests for your approval inboxAlso ships: React hooks (@johnhenry/acpq/react — useSession,
useToolCalls, usePermissions, plus the re-exported core hooks and
<AgentQueryDevtools> panel), opt-in fs/terminal client capabilities
(config-supplied callbacks only, default OFF, writes gated through the broker
via gateWrites), a devtools wire tap (instrumentAcpStream — every
JSON-RPC message alongside the semantic event stream), session/list /
session/load / slash-command caching, and AcpSessionHandle
(attach() / newAttachedSession() — bound-session ergonomics with a
states() async-iterable over folded snapshots).
npm install @johnhenry/acpq@rcUse the rc dist-tag, not latest — latest is still pinned to acpq's very
first publish (0.1.0-rc.1); every subsequent release, including the current
one, ships under rc until acpq cuts a stable 1.0.0.
acpq supports ACP wire protocol v1 only. v2 (schema alpha as of this writing) is explicitly out of scope until it stabilizes — tracked in #5.
- Built on
@agentclientprotocol/sdk@1.3.0, pinned exactly (bothdependencies/peerDependenciesand the dev pin) — not a caret range. The SDK's own semver (1.3.0) is independent of ACP's wire protocol version; acpq tracks the SDK version, and the SDK reportsPROTOCOL_VERSION = 1. - The SDK package was renamed from
@zed-industries/agent-client-protocol(now deprecated on npm) to@agentclientprotocol/sdkas governance moved out of Zed Industries into its ownagentclientprotocolorg. acpq depends on the new package only. - A
schema-v2.0.0-alphais in flight upstream with known breaking renames (semantic string types, diff patch → text, a terminal surface,cancelledvariants). acpq deliberately does not track or support it — issue #5 stays open, watching for v2 to stabilize before any work starts.
- API reference — every export, with an example each (including the permission decision → wire mapping and the mock agent's helpers).
- Design — why ACP is a stream-fold, not a cache; the
fold vocabulary; the full policy × options → outcome permission table; the
cancel contract; observability (status semantics, devtools events, why
prompt()is never retried); what the SDK provides vs what acpq adds. examples/— eleven graded, runnable examples (in-process mock agent, no transport): basic turn → tool calls → permission inbox → policy rules → multi-session → cancel → devtools timeline → client capabilities → wire timeline → session list/load → attached session.npm run example:01…example:11(seeexamples/README.mdfor the full table).
Cancellation honors the ACP contract end to end: cancel(sessionId) sends
session/cancel and resolves that session's pending permission requests
with {outcome: "cancelled"}, so blocked turns finish with
stopReason: "cancelled" instead of hanging.
Status: release candidate (0.1.0-rc.4, on @agentclientprotocol/sdk@1.3.0
pinned, wire protocol v1). Part of the
agent-query family — shared engine
@johnhenry/agent-query-core; siblings @johnhenry/mcpq (MCP) and @johnhenry/a2aq (A2A).
MIT