跳到主要内容

会话生命周期与 prompt 回合

本章把「一次会话从握手到一轮对话结束」整条主线走通。这是理解 ACP 的主干,其余章节都是给这条主线补细节。

1. 三段式主干

ACP 的交互永远是这三段,严格有序:

① 握手 ② 建会话 ③ prompt 回合(可反复)
initialize ──▶ session/new ──▶ session/prompt ──▶ … ──▶ PromptResponse{stop_reason}
(协商版本+能力) (拿 sessionId) (一轮对话,流式)

下面逐段拆。

2. ① initialize —— 握手与能力协商

它要解决的小问题。 双方互不认识:协议版本兼容吗?对方支持哪些可选功能?要不要先登录?

思路。 连接一建立,Client 先发一次 initialize,一口气把这三件事谈完(agent.rs:4931-4940ClientRequest::InitializeRequest 文档)。

请求里 Client 报上自己支持的最高协议版本和自己的能力(agent.rs:58 InitializeRequest):

{ "method": "initialize",
"params": {
"protocolVersion": 1,
"clientCapabilities": { "fs": { "readTextFile": true, "writeTextFile": true }, "terminal": true },
"clientInfo": { "name": "zed", "version": "1.0.0" }
} }

Agent 回它实际选定的版本 + 自己的能力 + 可用鉴权方式(agent.rs:134 InitializeResponse)。版本协商规则写在字段注释里:agent 返回「客户端要的版本(若支持)或自己支持的最新版本」;若 client 不支持返回的版本,应当断开(agent.rs:135-139)。

关键点:协议版本只在破坏性变更时才 +1;非破坏的新功能一律走「能力」(version.rs:6-8)。这把「版本爆炸」挡住了——见 04 章

3. 鉴权(可选)

如果 agent 在 initialize 里宣告了 authMethods,而当前未登录,那么建会话会收到 auth_required 错误。这时 Client 调 authenticate,带上一个之前宣告过的 methodId(agent.rs:295 AuthenticateRequest,字段注释「Must be one of the methods advertised in the initialize response」)。鉴权成功后再建会话就不会再报错(agent.rs:4942-4951)。

4. ② session/new —— 建立一个会话

它要解决的小问题。 一个连接想同时跑好几条独立的思路(并发 session);每条 session 有自己的工作目录、历史、状态。

NewSessionRequest 的关键字段(agent.rs:1010):

字段含义
cwd本会话的工作目录,必须是绝对路径(agent.rs:1011,也呼应 AGENTS.md:1「All paths should be absolute」)
additionalDirectories额外的工作区根,扩大文件可见范围而不改 cwd
mcpServersagent 应连接的 MCP 服务器列表

Agent 回一个 sessionId,后续所有请求都带它(agent.rs:1085-1089 NewSessionResponse)。SessionId 本身是个被包成强类型的字符串(v1/mod.rs:55 pub struct SessionId(pub Arc<str>))。

相关的会话方法(都受能力位控制,见各自文档):session/load(重放历史)、session/resume(不重放历史地续上)、session/listsession/deletesession/close。例如 session/load 的语义是「恢复上下文,并把整段历史通过通知流回 client」(agent.rs:4991-5000)。

5. ③ session/prompt —— 一次 prompt 回合(核心循环)

这是 ACP 跳动的心脏。一次 session/prompt 覆盖一整轮对话的生命周期。ClientRequest::PromptRequest 的文档把它分成几步(agent.rs:5055-5065):

用户消息 ──▶ ┌─────────────────────── 一次 prompt 回合 ───────────────────────┐
│ 1. 收用户消息(可带文件/图片等上下文) │
│ 2. 用 LLM 处理 │
│ 3. 把「内容 + 工具调用」流式报回 Client(sessionUpdate 通知) │
│ 4. 需要时反向请求权限(session/request_permission) │
│ 5. 执行被批准的工具调用(可能再调 fs/* 、terminal/*) │
│ 6. 回合完成,返回一个 stop_reason │
└────────────────────────────────────────────────────────────────┘

第 3 步的「流式」是 ACP 体验的灵魂:agent 不是憋到最后才回一坨,而是不断发 sessionUpdate 通知,把思考、回复、工具进度实时推给 UI(client.rs:90-99 SessionUpdate,完整种类见 03 章)。这正是文档说的「ACP 大量使用 JSON-RPC 通知来实时流更新」(architecture.mdx:24)。

prompt 的载荷是一组 ContentBlock,且协议规定了最低保证:agent 必须支持 TextResourceLink 两种,其它(图片、音频、内嵌资源)按能力可选(agent.rs:3266-3271)。

6. 回合怎么结束 —— stop_reason

一轮 prompt 不会无限跑;PromptResponse 一定带一个 stop_reason 告诉 Client「为什么停了」(agent.rs:3326 / 3393 StopReason):

stop_reason含义
end_turn正常结束
max_tokens触到 token 上限
max_turn_requests触到「一轮里 agent 自发请求次数」的上限(防失控自循环)
refusalagent 拒绝继续;注释强调:这条 prompt 及其之后的内容不会进下一轮,UI 应反映这点(agent.rs:3401-3404)
cancelled被 Client 用 session/cancel 取消

7. 取消:一个被设计得很「干净」的边界

取消不是「直接掐断连接」,而是一条通知 session/cancel(agent.rs:5320 CancelNotification,无响应)。妙处在于它把「取消」做成了一个协议级一致的收尾握手,而不是各实现各乱来:

  • agent 收到取消后,即使底层操作抛了异常,也必须捕获并返回 StopReason::Cancelled,以此确认取消成功(agent.rs:3405-3411 的注释明确写了这条 MUST)。
  • 与此同时,Client 对所有还悬着的 session/request_permission 请求,必须Cancelled 这个 outcome 回掉(client.rs:838-845 RequestPermissionOutcome::Cancelled 的注释)。

这样双方对「这一轮到底结束没」有确定的、对称的答案,不会留下半挂起的请求。

另有一个更底层的、unstable$/cancel_request(protocol_level.rs:81),用于取消「单个 JSON-RPC 请求」而非整轮 prompt;以 $/ 打头表示「实现相关、可被忽略」(protocol_level.rs:84-92)。两者不要混淆:session/cancel 取消一轮 prompt,$/cancel_request 取消一个请求。

8. 代码地图(本章)

主题文件符号
握手请求/响应…/src/v1/agent.rsInitializeRequest / InitializeResponse
鉴权…/src/v1/agent.rsAuthenticateRequest
建会话…/src/v1/agent.rsNewSessionRequest / NewSessionResponse
会话 id 类型…/src/v1/mod.rsSessionId
prompt 回合…/src/v1/agent.rsPromptRequest / PromptResponse
停止原因…/src/v1/agent.rsStopReason
取消(整轮)…/src/v1/agent.rsCancelNotification(方法名 session/cancel)
取消(单请求,unstable)…/src/v1/protocol_level.rsCancelRequestNotification($/cancel_request)
版本号策略…/src/version.rsProtocolVersion(V0/V1/LATEST)