乐于分享
好东西不私藏

Claude Code 源码自检(三):深入机制 9 问

Claude Code 源码自检(三):深入机制 9 问

这是"框架理解自检"系列的第 3 篇,共 5 篇。本篇覆盖 Q13-Q21 ,深入 Claude Code 的 auth 验证、 microCompact 、 services 层、 Swarm 通信、自愈机制、并行 tool call 和 memory 系统。

前两篇测的是"能说清核心概念"。这一篇测的是"能分析机制和权衡"——不只是知道它做了什么,还要说出为什么这么做以及代价是什么

Q13 : Auth 验证发生在什么时候?

很多人以为启动时那个设置界面就是在验证 API key 。错了。

启动时做的不是认证,而是"准入检查"showSetupScreens() 包括 Onboarding 引导、工作区信任确认、 API Key 确认。这些都不验证 key 是否有效。

每次 API 请求时才是真正的认证getAnthropicClient() 每次创建请求时先检查并刷新 OAuth token (带文件锁 + double-check 的分布式刷新),然后根据认证方式注入凭证。

如果 token 在会话中间过期? OAuth 场景下会自动用 refresh_token 换新的(最多 5 次)。如果刷新失败, API 返回 401 , withRetry 引擎会尝试一次 auth 刷新重试。

一句话:启动时 = "你有没有资格坐在桌上",请求时 = "你的信用卡还能不能刷"。

Q14 : microCompact 为什么不需要调 LLM ?

因为它是纯字符串替换。直接把旧的 tool_result 内容替换为 [Old tool result content cleared],清除 Read 、 Bash 、 Grep 、 Glob 、 Edit 、 Write 等工具的旧结果。

为什么只保护"最近的" tool output ?时间衰减。越老的工具结果对当前任务的价值越低。最近的结果可能还会被模型引用,但 10 轮前读的文件内容大概率不需要完整保留了。

这是一个零成本的预清理步骤——在调用 LLM 做正式总结之前就能显著减少 token 数。如果你的对话有 100 次 Read 调用,光这一步就能砍掉大量冗余。

Q15 : Services 层的 10 大类

Claude Code 的 services 层约 21 个子目录,按职责可以分成 10 大类:

类别
核心职责
API 通信
请求组装、流式响应、重试引擎
工具执行
工具生命周期、并行编排
上下文压缩
microCompact + autoCompact + 全量/部分压缩
记忆提取
后台 fork agent 自动提取 user/feedback/project/reference 记忆
会话记忆
单文件增量更新的短期记忆
记忆整合
定期合并零散记忆文件
分析/遥测
GrowthBook feature flags 、 Statsig 、 Datadog
MCP 协议
连接管理、工具获取、认证
认证
OAuth 2.0 + PKCE
插件系统
安装/卸载/启用/禁用

还有辅助服务:用 Haiku 生成工具摘要、推测执行、 MagicDocs 等。这不是"放了 21 个杂物的抽屉",而是一个职责清晰的服务矩阵。

Q16 : Swarm 为什么用文件邮箱?

Swarm 中 Teammate 之间通过文件邮箱系统通信: JSON 文件 + proper-lockfile 加锁,轮询间隔约 500ms 。

为什么不用 WebSocket 或 RPC ?

天然持久化 — 崩溃后邮件还在,支持恢复
跨机器兼容 — 共享文件系统即可,不需要网络协议
无需 daemon — 没有额外进程开销
简单可调试 — cat 就能看到消息内容
并发安全 — 文件锁比 socket 更简单

代价是 ~500ms 的轮询延迟。但对于 LLM 响应速度(通常几秒到几十秒)来说, 500ms 完全不是瓶颈。

Q17 :buildTool() 的统一构建逻辑

buildTool() 为每个工具填充默认值:isConcurrencySafe() 默认 false (安全保守)、isReadOnly() 默认 false 、isEnabled() 默认 true 、maxResultSizeChars 默认值、shouldDefer 默认 false 。

为什么需要统一构建函数?

默认值一致性 — 不同开发者写的工具不需要重复声明默认行为
类型安全 — 确保所有 Tool 字段都被填充
集中变更 — 给所有工具加新字段只改一处
统一入口 — 所有工具经过相同的权限检查管道

这比"每个工具自己定义所有字段"省事得多,也不会出现某个工具忘记声明 isReadOnly 导致权限漏洞的问题。

Q18 :自愈机制 vs 简单 Retry

Claude Code 的自愈不是"失败了重试同一个请求",而是根据错误类型选择不同恢复策略

错误类型
恢复策略
prompt-too-long
collapse drain → reactive compact
max-output-tokens 截断
escalate 8k→64k → 注入 "Resume" 消息
API 错误
指数退避重试 + auth 刷新 + 模型降级

简单 retry 的问题是:如果上下文太长导致 prompt-too-long ,重试 100 次还是同样的错误。自愈机制会改变请求本身——压缩上下文、提升输出上限——来解决根因。

Q19 :并行 Tool Call 由谁决定?

并行调用由模型决定。 Claude 模型在一次回复中可以包含多个 tool_use content block 。

框架收到后做分批处理StreamingToolExecutor 在 API 还在流式返回时就开始执行——每收到一个完整的 tool_use block 就判断能否并行。如果 concurrencySafe=true 且没有非安全工具在跑,立即开始。

备选路径是 partitionToolCalls():连续的 concurrencySafe=true 工具合并为并行 batch ,非安全工具切开为独占 batch , batch 之间严格串行。最大并发数默认 10 。

关键点:模型决定"要调几个工具",框架决定"这些工具怎么安全地跑"。

Q20 : Session Memory vs Auto Memory

Session Memory:单文件增量更新, 9 个 section 的 markdown 模板,每 section 最多 2000 tokens 。由 post-sampling hook 基于 token 数 + 工具调用数阈值自动触发。不跨会话存活——每个会话有自己的 session memory ,核心用途是 compact 后恢复上下文。

Auto Memory:多文件分类提取, 4 种类型( user/feedback/project/reference )。后台 fork agent 在每轮对话结束后自动提取。 MEMORY.md 索引始终加载到 system prompt ,详情文件通过 Sonnet 智能召回按需注入。跨会话存活

一个是短期工作记忆(这次对话记住什么),一个是长期记忆(下次对话还记得什么)。

Q21 :斜杠命令 vs Skill

用户视角上都是 /xxx,但内部完全不同:

斜杠命令/clear/compact):纯本地执行,不消耗 token 。在命令注册表中查找并执行 call() 函数,不经过模型,不走 agentic loop 。

Skill/code-review/init):通过 SkillTool 这个元工具间接调用。模型识别到匹配的 skill → 调用 Skill() → 加载 .md 文件注入对话 → 模型读完后自己执行。消耗 token——Markdown 内容成为对话的一部分。

根本区别:斜杠命令是框架直接处理(确定性执行), Skill 是通过模型间接执行(模型理解指令后自主操作)。一个 O(1),一个 O(LLM)。


下一篇预告:"框架对比自检(四)"——如果用一个比喻区分两个框架? prompt caching 的结构性优势从何而来?记忆系统走了完全不同的路线各有什么优劣? 15 道架构设计题。