会话生命周期与 prompt 回合
本章把「一次会话从握手到一轮对话结束」整条主线走通。这是理解 ACP 的主干,其余章节都是给这条主线补细节。
1. 三段式主干
ACP 的交互永远是这三段,严格有序:
① 握手 ② 建会话 ③ prompt 回合(可反复)
initialize ──▶ session/new ──▶ session/prompt ──▶ … ──▶ PromptResponse{stop_reason}
(协商版本+能力) (拿 sessionId) (一轮对话,流式)
下面逐段拆。
2. ① initialize —— 握手与能力协商
它要解决的小问题。 双方互不认识:协议版本兼容吗?对方支持哪些可选功能?要不要先登录?
思路。 连接一建立,Client 先发一次 initialize,一口气把这三件事谈完(agent.rs:4931-4940 的 ClientRequest::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 |
mcpServers | agent 应连接的 MCP 服务器列表 |
Agent 回一个 sessionId,后续所有请求都带它(agent.rs:1085-1089 NewSessionResponse)。SessionId 本身是个被包成强类型的字符串(v1/mod.rs:55 pub struct SessionId(pub Arc<str>))。
相关的会话方法(都受能力位控制,见各自文档):session/load(重放历史)、session/resume(不重放历史地续上)、session/list、session/delete、session/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 必须支持 Text 和 ResourceLink 两种,其它(图片、音频、内嵌资源)按能力可选(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 自发请求次数」的上限(防失控自循环) |
refusal | agent 拒绝继续;注释强调:这条 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-845RequestPermissionOutcome::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.rs | InitializeRequest / InitializeResponse |
| 鉴权 | …/src/v1/agent.rs | AuthenticateRequest |
| 建会话 | …/src/v1/agent.rs | NewSessionRequest / NewSessionResponse |
| 会话 id 类型 | …/src/v1/mod.rs | SessionId |
| prompt 回合 | …/src/v1/agent.rs | PromptRequest / PromptResponse |
| 停止原因 | …/src/v1/agent.rs | StopReason |
| 取消(整轮) | …/src/v1/agent.rs | CancelNotification(方法名 session/cancel) |
| 取消(单请求,unstable) | …/src/v1/protocol_level.rs | CancelRequestNotification($/cancel_request) |
| 版本号策略 | …/src/version.rs | ProtocolVersion(V0/V1/LATEST) |