claude code 拆解

前言

Learn Claude Code

这是claude code的学习网站,我在网上游荡的时候偶然发现的。其实学习claude code的底层架构是个不小的学习量,一般人都不太想学的,但我不太一样,因为有此面试被问到了学没学过claude code底层,我。。。

工具与执行

Loop

一个最简单的agent一个循环就实现了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def agent_loop(messages):
while True:
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=messages,
tools=TOOLS, max_tokens=8000,
)
messages.append({"role": "assistant", "content": response.content})

if response.stop_reason != "tool_use":
return

results = []
for block in response.content:
if block.type == "tool_use":
output = run_bash(block.input["command"])
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
messages.append({"role": "user", "content": results})

说白了就是从之前我们作为LLM与ide之间的传话者这个过程被自动化了。

Tool Use

最基本的工具就是bash、文件读写,后面加工具只需:

  1. 定义工具:在 TOOLS 数组里加一条描述
  2. 注册处理函数:在 TOOL_HANDLERS 字典里加一个映射

一般LLM一次会返回多个工具调用,cc一般把工具调用拆分为多个batch,然后按batch顺序并行或串行执行:[read A, read B, glob *.py, bash “rm x”, read C]
→ batch1(并发): [read A, read B, glob *.py]
→ batch2(串行): [bash “rm x”]
→ batch3(并发): [read C]

当然运行时也会在这做个并发安全判断的

Permission

不肯能所有的工具想执行就执行,必须靠代码来约束权限。

用一个列表维护哪些工具是allow/deny/ask

Hooks

hook是被当作扩展加入agent编排的,初看和工具调用贼像,但其实很不同:

维度 Hook 工具调用 (Tool Call)
触发者 框架/运行时自动触发 LLM 主动决策发起
决策来源 预定义的规则/事件 模型的推理
控制流 拦截、修改、观察 请求-执行-返回结果
是否进 LLM 上下文 通常不进 进(作为 tool result)
典型时机 事件前后(before/after) 任意需要外部能力时

比如用户输入的敏感词校验、日志记录啥的,LLM对hook的工作是无感知的。并且hook是在agent编排的某个生命周期固定会触发执行的。

规划与协调

TodoWrite

随着上下文的增长,为防止Agent对主要任务的注意力被稀释,新增 todo_write工具,作用是让模型理清楚任务,自主维护步骤列表(覆盖式更新)。

什么时候调用?
让模型隐式判断任务复杂度,复杂的话就调用,同时用一个reminder检测模型多久没调用todo_write了,要是超过三次,就自动注入一条提醒。

Subagent

这和我收到一个大任务,然后派其他人去并行完成各个小任务的思想以及流程都一样的。

subagent会新开一个上下文。在这个上下文中:

主 agent 要"描述好"的两层

1. 任务本身

  • 要做什么、做到什么程度
  • 期望的输出格式(结构化?diff?一段总结?)
  • 边界和约束(不要动什么、权限范围)

2. 任务相关上下文

  • 相关文件路径、代码片段
  • 已知的背景、前置结论
  • 用户的原话或关键需求
  • 为什么做这件事(意图)

最后只拿subagent的结果,subagent在主进程内或者子进程内运行都可。

Skills

要知道的首先是渐进式加载,只有真正要用时才加载skill的全部内容,不然只加载描述,这里新增一个load_skill工具来加载所有.skills下的skill描述。

我们在实际开发时,遇到某些任务其实也可以让AI自己把整个流程总结为一个skill,下次就可以很快的调用skill完成任务。

System Prompt

一个思想:prompt 是组装出来的,不是写死的

运行时根据真实状态按需拼接,并缓存结果避免重复组装

具体要掌握的是这几个可迁移的设计思路:(可把下面复制让AI举例理解)

  1. 分段(section 化):把 prompt 按职责拆开,独立维护、独立演进。这是后面所有"按需加载"的基础。
  2. 按真实状态决定加载什么:section 是否加载,取决于工具是否注册、文件是否存在这类客观状态,而不是在用户消息里搜关键词。这是保证行为可预测的关键。
  3. 缓存要有正确的 key:用 json.dumps(sort_keys=True) 而非 hash(),因为 Python 的 hash() 有进程随机化,且遇到 list/dict 会报错。这是一个很实际的工程细节。(这里就是每次要执行拼接提示词时,把当前 context (持有哪些工具、有无记忆文件等)序列化成一个 key,跟上次的 key 比一比;相同就直接返回缓存,不相同才重新拼接。)

Error Recovery

生产环境中 API 错误是常态。三种最常见的故障模式:输出被截断(模型话说一半 token 用完了)、上下文超限(压缩后还是太长)、临时故障(429 限流 / 529 过载)。一个不处理错误的 Agent 就像一个一碰就熄火的车。当然cc远不止这几个错误码

对于输出被截断:

直接把 max_tokens 从 8K 升级到 64K(8 倍空间),重试同一请求——此时不追加截断输出到 messages,保持原始请求不变。如果 64K 还是不够,才保存截断输出并注入续写提示让模型接着刚才的话继续说,最多 3 次。

对于上下文超限:

触发 reactive compact——比 auto compact 更激进。教学版只保留最后 5 条消息模拟压缩效果;真实实现会调用 LLM 生成 compact 摘要再重试。压缩后重试。但如果压缩过一次还是超限,只能退出——再压缩也不会变小

对于临时故障:

网络抖动、429 限流、529 过载——这些不是 bug,是分布式系统的常态。429 和 529 统一走指数退避 + 抖动:第一次等 0.5 秒,第二次等 1 秒,第三次等 2 秒,最多 10 次。加随机抖动让并发请求不在同一时刻重试。连续 3 次 529 过载 → 切换到备用模型。

记忆管理

Context Compact

采用多层压缩策略(按顺序的):

1、大结果落盘:统计最后一条 user 消息里所有 tool_result 的总大小,超过 200KB 就按大小排序,从最大的开始把完整内容写入磁盘(.task_outputs/tool-results/),上下文里只留 <persisted-output> 标记 + 前 2000 字符预览

2、裁掉无关的旧对话:当消息数超过 50 条时,保留头部 3 条(初始上下文)和尾部 47 条(当前工作),把中间的部分裁掉,并在切口处插入一条占位消息

3、旧工具结果占位:只保留最近 3 条 tool_result 的完整内容,更旧的如果内容超过 120 字符,就替换成一行占位符:[Earlier tool result compacted. Re-run if needed.]

4、LLM 全量摘要(得调API):

a.保存 transcript:完整对话写入 .transcripts/,JSONL 格式。

b.LLM 生成摘要:把对话历史发给 LLM,要求保留当前目标、重要发现、已改文件、剩余工作、用户约束等关键信息。

c.替换消息列表:所有旧消息被替换成一条摘要消息。

​ (熔断器):连续失败 3 次后停止重试,防止死循环浪费 API 调用。

5、应急:reactive_compact:API 仍然返回 prompt_too_long(413),说明上下文增长速度超过了压缩触发速度。比 compact_history 更激进——同样先保存 transcript、生成摘要,但从尾部回退,只保留最近几条消息(默认回退到倒数第 5 条),同时避免留下孤立的 tool_result。

memory

压缩会丢细节, 要有一层不丢的" — 文件仓库 + 索引 + 按需加载,跨压缩、跨会话。

什么应该作为长期记忆呢?

四类记忆,各有用途:

类型 回答什么 示例
user 你是谁 “用 tab 不用空格”
feedback 怎么做事 “别 mock 数据库”
project 正在发生什么 “auth 重写是合规驱动”
reference 东西在哪找 “pipeline bug 在 Linear INGEST”

每个记忆是一个 .md 文件,YAML frontmatter 记录元数据(像skill那样),每次加载仅加载memory.md到system prompts,这是一个记录了*.md的索引的文件,一行一个链接:
- user-preference-tabs — User prefers tabs for indentation

写入新记忆时自动重建索引。

要是索引太多了,就相关记忆按需注入,每次调LLM 做一次轻量 side-query,选出相关的文件名。

然后记忆内容是用户显式说“记住”或表达稳定偏好时,提取器会保存到记忆文件中;
同时记忆需要进行整理(去重、合并矛盾、淘汰过时)

并发

Background Tasks

也就是把慢操作丢后台运行

cc通过run_in_background: boolean 参数控制命令是否丢后台

已完成的任务通过独立的task_notification推入消息队列,在下一次loop循环前,检查消息队列,把任务结果合并进上下文

Cron Scheduler

比如我希望agent能在每天早上9点跑一下全量测试,这就得用上定时调度器了。

四层模型:

  1. Scheduler:独立 daemon 线程,每秒轮询判断时间到没到
  2. Queue(cron_queue):调度线程写入已触发任务
  3. Queue Processor:发现队列非空且 Agent 空闲,拉起一轮 agent_loop
  4. Consumer(agent_loop):从队列消费,注入到 messages

三层通过 cron_queue、cron_lock、agent_lock 解耦:生产者(调度线程)、交付者(queue processor)、消费者(agent_loop)。

定时任务会在时间点到时进入创建定时任务的那个会话窗口中等待被消费。

多Agent平台

Learn Claude Code

不想学了,感觉力竭了,这部分我粗看了一下,大概就是讲团队agent协作,与subagent的单向汇报有所区别。这个得在cc中把这一功能打开,然后在聊天框中说:

“请审查这个代码库的问题。安排:

  • 一名队友专注安全漏洞
  • 一名队友检查性能瓶颈
  • 一名队友分析测试覆盖率
    最后汇总成报告。”

除此之外,还有讲了任务之间的依赖关系,B任务必须等A任务完成了才能开始,它们之间用文档协议来冻结具体依赖事项。

Icon
致谢名单
本作品由 Hafsun 于 2026-09-16 15:22:13 发布
作品地址:claude code 拆解
除特别声明外,本站作品均采用 CC BY-NC-SA 4.0 许可协议,转载请注明来自 欢迎回家
Logo
上一篇上海代码一行科技(初创)-agent研发-初面下一篇to Danni