Skip to content

Latest commit

 

History

History
320 lines (222 loc) · 8.85 KB

File metadata and controls

320 lines (222 loc) · 8.85 KB

ACP — Agent Client Protocol

ACP (Agent Client Protocol) is the stdio-based protocol used by IDEs such as Zed to drive full agent sessions in qcr. The IDE spawns qcr --acp as a child process and communicates over stdin/stdout using the official agent-client-protocol SDK (v0.9.3).


How ACP differs from MCP

ACP MCP
Purpose Drive full agent sessions (prompt, stream, cancel) Expose tools and resources to a host
Direction IDE → qcr qcr → external MCP server
Transport ndjson JSON-RPC 2.0 over stdio JSON-RPC 2.0 over stdio or HTTP/SSE
Sessions Stateful — per-session history, mode, model Stateless calls
Spawn IDE spawns qcr --acp qcr spawns or connects to MCP server

Starting ACP mode

qcr --acp

The process reads from stdin and writes to stdout using ndjson (newline-delimited JSON). All other flags (--config) are honoured — they supply defaults that the IDE can override per session.


Wire format

Every message is a single JSON object followed by a newline (\n). The protocol follows JSON-RPC 2.0 as implemented by the agent-client-protocol SDK.

Requests from the IDE:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-07-09","clientInfo":{"name":"zed","version":"1.0.0"},"capabilities":{}}}

Responses from qcr:

{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"qcr","version":"0.1.16"},"agentCapabilities":{}}}

Session-update notifications (no id, sent by the agent during prompt execution):

{"jsonrpc":"2.0","method":"notifications/session","params":{"sessionId":"acp-...","update":{"agentMessageChunk":{"content":{"type":"text","text":"Hello"}}}}}

Protocol flow

IDE                         qcr --acp
 |                                |
 |-- initialize ----------->     |
 |<- result (agentInfo,caps) -   |
 |                                |
 |-- notifications/initialized -> |
 |                                |
 |-- session/new ----------->    |
 |<- result (sessionId,modes) -  |
 |                                |
 |-- session/prompt -------->    |
 |<- notification (agentMessageChunk) * N
 |<- notification (toolCall)          * M
 |<- notification (toolCallUpdate)    * M
 |<- notification (plan)              * K
 |<- result (stopReason) --------    |
 |                                |
 |-- session/cancel -------->    |  (optional, mid-prompt)
 |                                |
 |-- [close stdin] -------->     |  (graceful shutdown)

Methods reference

initialize

Must be the first message sent. Returns protocol version, agent info, and capabilities.

Request params:

Field Type Description
protocolVersion string Protocol version (e.g. "2025-07-09")
clientInfo object { "name": "...", "version": "..." }
capabilities object Client capabilities (can be empty)

Response result:

Field Type Description
protocolVersion integer 1 (ProtocolVersion::V1)
agentInfo.name string "qcr"
agentInfo.version string Semver string from Cargo.toml
agentCapabilities object Agent capability flags

session/new

Create a new session. The IDE supplies the working directory.

Request params:

Field Type Required Description
cwd string Yes Working directory (absolute path)
mcpServers array Yes MCP servers to connect (can be [])

Response result:

Field Type Description
sessionId string Opaque session identifier (acp-{hex}-{hex})
modes object Available modes and current mode

Modes object:

Field Type Description
currentModeId string Initially "default"
availableModes array [{id, name}, ...] — Default, Plan, Auto-edit

session/prompt

Send a user prompt to a session and stream the agent response as session notifications. The response is sent after all notifications have been flushed.

Request params:

Field Type Required Description
sessionId string Yes Target session
prompt array Yes Array of ContentBlock objects

Each content block:

{"type": "text", "text": "your prompt here"}

Response result:

Field Type Description
stopReason string "endTurn" or "cancelled"

session/cancel

Cancel the currently running agent turn for a session.

Request params:

Field Type Required Description
sessionId string Yes Target session

No response — this is a notification.


Session-update notifications

While a session/prompt is in flight, qcr emits a stream of notifications via the SDK's session_notification method. Each notification carries the session ID and an update variant.

userMessageChunk

Echo of the user's prompt.

agentMessageChunk

A streamed chunk of the agent's reply text.

agentThoughtChunk

A chunk of the agent's chain-of-thought (thinking) text. Not all models emit this.

toolCall

A tool has started executing.

Field Type Description
toolCallId string Opaque call identifier
title string Human-readable tool invocation title
kind string Tool category (read, edit, execute, etc.)
status string "pending"
content array Empty at start
locations array File path + optional line
rawInput object Full JSON arguments

toolCallUpdate

A tool has completed or failed.

Field Type Description
toolCallId string Same ID as the corresponding toolCall
status string "completed" or "failed"
content array Output blocks (text or diff)
rawOutput object or null Raw output

plan

The agent's todo/plan list has been updated.

Field Type Description
entries array Ordered list of plan entries

Each plan entry: { "content": "...", "status": "pending|in_progress|completed", "priority": "low|medium|high" }


Tool kinds

kind Tools
read ReadFile, Glob, LS
edit EditFile, WriteFile
delete DeleteFile
move MoveFile
search Grep, WebSearch
execute Shell
think Think
fetch WebFetch
other all other tools

IDE configuration

Zed

Add to ~/.config/zed/settings.json:

{
  "agent_servers": {
    "qcr": {
      "type": "custom",
      "command": "/Users/you/.cargo/bin/qcr",
      "args": ["--acp"]
    }
  }
}

Architecture

The ACP server is implemented in crates/qcr-cli/src/acp/:

File Purpose
mod.rs run_acp_server() entry point — creates AgentSideConnection over stdio
agent.rs Qwen Code RustAgent implementing the SDK Agent trait
bootstrap.rs build_registry, build_loop_config, build_system_prompt_for_acp — mirrors app.rs setup
convert.rs Internal SessionUpdate → SDK SessionUpdate conversion
session.rs Per-session state and registry
transport.rs ndjson frame reader/writer

The SDK handles all JSON-RPC framing, method routing, and serialization. Qwen Code RustAgent implements the Agent trait methods (initialize, new_session, prompt, cancel) which delegate to qcr-core's agent loop, tool registry, and session persistence.


Session persistence

After every prompt completes, qcr saves the conversation history to ~/.qcr/sessions/<sessionId>.json.


Known Limitations

  • AskUserQuestion returns a canned fallback in ACP mode; full IDE round-trip is a future feature.
  • Cron jobs (CronCreate, CronList, CronDelete) are session-scoped and are lost when the session ends.

Changelog (v0.1.16)

  • Fixed empty responses in Zed: the PromptResponse was returned before all session notifications had been flushed. Now the prompt handler awaits the bridge and forwarder tasks to drain before returning.
  • LLM errors forwarded to IDE: AgentEvent::Error is now emitted as agentMessageChunk with [error] prefix instead of being silently swallowed.
  • Temperature fix: o-series (o1/o3/o4-mini) and codex (gpt-5.2-codex) models no longer receive unsupported temperature/top_p parameters.

Project Context

ACP sessions use the cwd provided by session/new as the project root. qcr then:

  • Loads QCR.md, AGENTS.md, and CLAUDE.md from the project root when present.
  • Reads steering files from <cwd>/.qcr/steering/.
  • Discovers plugins from ~/.qcr/plugins (global) and <cwd>/.qcr/plugins (project).

Shutdown

Close the process's stdin to trigger a clean shutdown. qcr will finish any in-flight notifications, flush stdout, and exit with code 0.