07. Claude 后端执行链
ClaudeAgent 是 Claude Agent SDK 的产品化适配器。它不自己实现模型循环,而是在 SDK query 周围加入 Craft 的 prompt、source、session tools、权限、恢复、事件归一化与持久生命周期。
源码主文件:packages/shared/src/agent/claude-agent.ts。
7.1 组件关系
Section titled “7.1 组件关系”flowchart LR SM["SessionManager"] --> CA["ClaudeAgent"] CA --> BA["BaseAgent modules"] CA --> SDK["Claude Agent SDK query()"] CA --> EA["ClaudeEventAdapter"] SDK --> H["PreToolUse / PostToolUse hooks"] H --> P["runPreToolUseChecks"] SDK --> ST["session MCP tools"] SDK --> MT["MCP proxy tools"] SDK --> EA EA --> EV["AgentEvent"] EV --> SM7.2 构造与 postInit()
Section titled “7.2 构造与 postInit()”构造阶段保存 config、建立 BaseAgent modules、创建 event adapter,并读取 branch 参数。它不急着调用 SDK query。
postInit() 位于 claude-agent.ts,负责首 turn 前的准备:
- 解析 Anthropic API key/OAuth/Bedrock 等 auth env;
- 清理冲突的 provider routing 环境变量;
- 检查 SDK 可执行 runtime;
- 建初始 source/session tools;
- 返回
PostInitResult,让 SessionManager 将 auth warning 告知 UI。
Auth 注入必须在 query 子进程启动前完成,因为 SDK 往往从环境读取 credential。
7.3 一次 chatImpl() 的阶段
Section titled “7.3 一次 chatImpl() 的阶段”入口:ClaudeAgent.chatImpl。可以拆成七段:
1. 前置状态
Section titled “1. 前置状态”- 防止重入;
- 设置当前 abort controller/query;
- 清理上轮临时状态;
- 消费一次性 recovery/branch/transfer context;
- 刷新 model/thinking/source 配置。
2. 构建输入
Section titled “2. 构建输入”- 用户文本;
- attachments 转 SDK text/image/document block;
- stable/volatile context;
- skill/source mentions;
- provider resume/fork options。
Claude 路径把 Craft context blocks 放在 user-turn tail;这是为了配合 SDK system prompt/resume 行为,具体缓存差异在第 10 章讲。
3. 构建 SDK options
Section titled “3. 构建 SDK options”关键项:
- model、cwd、env、system prompt;
- allowed tools / MCP servers;
- thinking/effort;
- hooks;
permissionMode: bypassPermissions风格配置;- resume/fork;
- abort controller;
- persistent streaming input 选项。
“bypass SDK permission”听起来危险,但这里是把权限决定权移到 Craft 的统一 PreToolUse,不是无条件放行。
4. 启动/复用 query
Section titled “4. 启动/复用 query”普通模式每 turn 调 query({prompt, options});keep-background-tasks-alive 模式可使用 createPushableInputStream() 建一个长期 query,后续 turn 把 SDKUserMessage push 进去。
5. 消费 SDK message
Section titled “5. 消费 SDK message”ClaudeEventAdapter 把 system/assistant/user/result/stream message 映射为 AgentEvent,ToolIndex 用 toolUseId 关联 start/result,并提取 workflow 子 agent 事件。
6. 恢复/重试
Section titled “6. 恢复/重试”对无效 resume、branch cutoff 锚点缺失、auth 刷新、上下文溢出等做有限 fallback;每次 fallback 要清理对应 provider state,避免下次启动无限重试同一个坏句柄。
记录 usage/session id,排空 source activation,处理未投递 steer,yield complete/error,清 query 引用。
7.4 SDK session 恢复与分支
Section titled “7.4 SDK session 恢复与分支”普通恢复:
{ resume: this.sessionId }严格分支:
{ resume: branchFromSdkSessionId, forkSession: true, resumeSessionAt: branchFromSdkTurnId}源码:claude-agent.ts。
branchFromSdkCwd 也必须生效,因为 SDK 从按 cwd hash 的目录找 parent transcript。若目标 cwd 在当前机器不存在,backend 会通知上层原子清掉全部 branch fork metadata,转 seeded/fallback;只清 sdkSessionId 会导致重启后坏 branch 字段再次复活。
ensureBranchReady() 会在创建 UI session 前实际触发/验证 fork,见 claude-agent.ts。
7.5 PreToolUse:统一权限的接入点
Section titled “7.5 PreToolUse:统一权限的接入点”SDK hook 位于约 1260–1520。流程:
sequenceDiagram participant SDK as Claude SDK participant CA as ClaudeAgent hook participant C as Central checks participant UI as Permission callback
SDK->>CA: PreToolUse(tool_name, input) CA->>CA: normalize / image-size preflight CA->>C: runPreToolUseChecks(context) alt allow C-->>CA: allow or modified input CA-->>SDK: continue else block C-->>CA: block(reason) CA-->>SDK: deny else ask C-->>CA: ask(request metadata) CA->>UI: permission_request UI-->>CA: approve / deny CA-->>SDK: continue / deny end在中央检查前还有一个图片 Read guard:过大的图片一旦进入 SDK tool result,可能已经超过 API base64 限制,所以必须在执行 Read 前请求 host resize 或阻止。
7.6 canUseTool 为什么还存在
Section titled “7.6 canUseTool 为什么还存在”代码仍提供 canUseTool callback,但注释说明所有真实权限逻辑在 PreToolUse hook。它主要满足 SDK API/兜底,不应成为第二套 policy。若未来把某些检查塞回 canUseTool,Claude 与 Pi 就会产生安全漂移。
7.7 Session-scoped MCP tools
Section titled “7.7 Session-scoped MCP tools”Claude SDK 可直接接 createSdkMcpServer()。Craft 每次构建 fresh wrapper,但复用内部 tool definitions,避免 SDK transport 报 “Already connected to a transport”。
这些工具包括 plan、config/source validation、OAuth/credential、browser、spawn session、call_llm、session status/labels、task 与 inter-session message。
具体业务通过 callback registry 回到当前 session 的 SessionManager,不让工具闭包长期持有已销毁 manager 状态。
7.8 Source tools
Section titled “7.8 Source tools”Source 的远程 MCP/API connection 由主进程 McpClientPool 持有,Claude 看见 proxy tool definition。执行 proxy 时回 pool;这样:
- source token 刷新可重连 pool;
- Claude/Pi 工具列表一致;
- credential 不必写 SDK cache 文件;
- 多 session 可复用连接生命周期策略。
7.9 事件适配
Section titled “7.9 事件适配”ClaudeEventAdapter 负责把 SDK message 变成稳定事件:
- assistant content text → delta/complete;
- tool_use →
tool_start; - tool_result →
tool_result; - result usage →
complete.usage; - task notification → background/workflow events;
- SDK error → typed Craft error。
把映射从巨型 chat loop 抽出来有两个价值:provider SDK schema 变化集中修,且可用纯 fixture 测试事件转换。
7.10 Persistent input 与后台任务
Section titled “7.10 Persistent input 与后台任务”Claude SDK 的某些 background task notification 会在原 turn 结束后到达。若每 turn query 都销毁,通知会丢。代码支持长期 streaming-input query:
first turn: create input stream + query()next turn: input.push(user message)between turns: query 仍接收 task notificationdestroy: input.end() + abort querySessionManager 通过 background event sink 接收没有活跃 for await 消费者时的事件。
这增加了复杂性:currentQuery 必须始终指向 live query,redirect/abort 不能打到一个已结束的临时对象。
7.11 Redirect 的 Claude 策略
Section titled “7.11 Redirect 的 Claude 策略”Claude 没有像 Pi 一样直接 session.steer() 的通用路径。实现会保存 pending steer message,并尝试在下一个 PreToolUse hook 通过 additional context 注入;若 turn 再也不调用工具,消息可能无法送达,于是结束时发 steer_undelivered,让 SessionManager/UI 明确处理,而不是静默丢消息。
源码:redirect。
这也解释了为什么 connection 默认可以选择 queue:原生 steer 能力并不对称。
7.12 恢复策略
Section titled “7.12 恢复策略”Resume 失败
Section titled “Resume 失败”可能原因:
- transcript 不存在/跨机器;
- sdkCwd 不匹配;
- SDK schema 升级;
- parent branch anchor 不存在。
处理原则:
- 识别可恢复错误;
- 清失效 sdk id;
- 从最近持久消息构建隐藏 recovery context;
- 新 SDK session 重试一次;
- 把新 sdk id 回写 Craft session。
Branch cutoff 失败
Section titled “Branch cutoff 失败”优先 retry/fallback 到已建立 child fork 或 seeded summary;不能继续把 parent 完整未来上下文带入,否则违反硬截止。
7.13 Thinking 与 context window
Section titled “7.13 Thinking 与 context window”resolveClaudeThinkingOptions() 处理:
- Claude adaptive thinking;
- Haiku/非 Claude 模型差异;
- Mythos/Fable 类 always-on thinking;
off/low/...到 effort/max tokens;- mini completion 强制降低 thinking。
它不是简单 thinkingLevel === off ? disabled,因为有些模型拒绝 disabled。支持 1M context 的 Opus 还由 config 开关控制,以平衡能力和 quota。
7.14 Mini completion
Section titled “7.14 Mini completion”runMiniCompletion 要求 miniModel,用最小 query options 消费最终文本。它服务:
- 标题;
- conversation/branch summary;
- large response summary;
call_llm等辅助动作。
错误通常返回 null 或向上抛(取决于调用语义),不能把失败当主会话 error。
7.15 销毁
Section titled “7.15 销毁”destroy() 需要:
- abort current query;
- end persistent input;
- 清 tool/session callback 与 source activation;
- dispose event adapter/index;
- 调 BaseAgent cleanup;
- 防止迟到 SDK event 被继续投影。
源码:claude-agent.ts。
7.16 设计评价
Section titled “7.16 设计评价”优点:
- 充分利用 SDK 原生 agent loop、resume/fork;
- 权限和事件被拉回产品统一层;
- persistent input 支持跨 turn background;
- recovery 清楚区分 provider transcript 与 Craft messages。
代价:
- hooks、SDK message types、query 生命周期耦合较深;
- provider SDK 的重复/乱序事件需要 ToolIndex 去重;
- redirect 不是完全原生,存在“没有下一个 PreToolUse”的不可投递窗口;
- chatImpl 分支仍很大,升级 SDK 需要高强度回归。
7.17 本章小结
Section titled “7.17 本章小结”ClaudeAgent 的本质是“in-process 控制器 + SDK 子进程协议的产品适配层”。它把 Craft 的稳定语义注入 SDK hooks,再把 SDK 输出归一化回来。
下一章看完全不同的 Pi 路线:为什么再加一层独立 Node/Bun 子进程,以及 parent/subprocess 如何通过双向 JSONL 实现工具、权限、steer 与压缩。