文章持续完善
跟读 learn-claude-code:17 个 harness 机制怎么挂到一个循环上
本文总结一门开源课程从一个 while 循环出发,把权限、压缩、记忆、任务图、团队、MCP 一个个挂上去。这篇是跟读总览:每个机制挂在循环哪里,换个位置会坏在哪。
创建 2026-11-05更新 2026-11-05阅读约 9 分钟
为什么跟读这门课
主线第 8 站 说过,Harness 是包在模型外面的运行时,管循环、工具、权限和记录。那一站讲了需要哪些零件,没讲零件怎么装。
learn-claude-code 正好补这一块。它用 Python 和 Anthropic SDK,从一个 150 行左右的循环开始,每章加一个机制,第 15 章把前 14 个机制装进同一个 3330 行的运行时,第 16、17 章再加固定编排和目标闸门。每章都有可以跑的代码和锁定设计约束的测试。
我按章读了源码和测试,把 17 章归成 7 站写成笔记。本文是这 7 站的总览。
两点阅读提示:
- 课程仓库里同时有新版 s01 到 s17 和旧版 12 章(
agents/、docs/),编号不同。本系列只跟新版。 - 这是教学代码,不是 Claude Code 的源码。笔记里"和真实 Claude Code 的差别"一节,凡是没有公开资料支撑的都标了"推断"。
只有一个循环
整门课的骨架就是这几行:
messages[] → 调模型 → 回复里有 tool_use 吗?
├─ 没有 → 结束
└─ 有 → 逐个执行 → tool_result 回填 → 回到开头
后面 16 章没有再写第二个循环。每章做的事,要么是在这个循环的某个位置挂一个东西,要么是换掉循环里的一个零件。所以我读每一章时只问两个问题:
- 这个机制挂在循环的哪个位置?
- 换个位置挂,会坏在哪?
第二个问题的答案,就是这一章的设计理由。
机制挂在哪里
下面是第 15 章集成版的一次迭代,括号里是机制来自哪一章:
用户输入 → [UserPromptSubmit 钩子 s04]
① 注入 cron 到点任务 s12,后台完成通知 s11,todo 提醒 s05
② 预算 大结果落盘 → 裁中间 → 旧结果换路径 → LLM 摘要 s08
③ 上下文 记忆召回 s09,skill 目录 s07
④ 工具池 内置工具 s02 + mcp__server__tool s14(每轮重建)
⑤ 调用 组装 system prompt,重试与降级 s15
⑥ 判定 有没有 tool_use 块 s01
没有 → [Stop 钩子 s04] → 目标判断器 s17 → 记忆抽取 s09
⑦ 执行 [PreToolUse:权限 s03 → 日志 s04]
→ 后台分支 s11 / 普通 handler s02
(子代理 s06,load_skill s07,任务图 s10,Workflow s16)
→ [PostToolUse s04]
⑧ 回填 所有 tool_result 放进一条 user 消息,附上已完成的后台通知
迭代之间:队友信箱 s13,cron 唤醒 s12,后台唤醒 s11
有几个位置我想了比较久:
- 所有注入都放在迭代开头。 这里上一轮的 tool_result 已经回填完,是唯一不会把 tool_use 和 tool_result 拆开的位置。cron 线程如果在两者中间插一条消息,API 直接返回 400。
- 权限排在后台分支前面。 反过来排,
run_in_background就成了绕过审批的后门。 - 压缩排在注入之后、记忆召回之前。 注入的内容也要算进预算;召回要基于真正发出去的那份上下文。
- 工具池每轮重建。 会话中途连上的 MCP server,下一轮就能用,权限策略同轮更新。
课程编号连续,代码不是一章叠一章
这是我读的时候最容易搞错的地方。很多章是直接建在第 4 章上的,测试里还专门断言"本章没有第 10 章的 Task 类"之类:
s01 → s02 → s03 → s04 ─┬─ s05 TodoWrite
├─ s06 子代理
├─ s07 skill → s08 压缩 → s09 记忆
├─ s10 任务图 → s13 团队
├─ s11 后台
├─ s12 cron
├─ s14 MCP
└─ s17 目标循环(独立运行)
s01 到 s14 → s15 集成 → s16 workflow
读第 11 章时要拿它和第 4 章比,不要和第 10 章比。
贯穿全课的几条规矩
| 规矩 | 违反了会怎样 | 出现在 |
|---|---|---|
| 每个 tool_use 都有配对的 tool_result,紧跟在下一条 user 消息里 | API 返回 400,会话卡死 | s01、s03(被拒也要回填)、s08(压缩要成对保留)、s11(占位结果)、s15 |
是否继续,看回复里有没有 tool_use 块,不看 stop_reason |
兼容接口不一致时漏执行,留下悬空调用 | s01、s15 |
| 工具出错也作为 tool_result 交回模型 | 一次异常打断整轮 | s02、s03、s15 |
| 摘要、记忆、外部工具描述都当数据,不当指令 | 注入的文字经过压缩,变得像是权威指令 | s08、s09、s14、s15 |
| 权限检查覆盖所有执行路径 | 后台、子代理、队友成了后门 | s03、s04、s11、s15 |
| 模型说做完了,不等于做完了 | 提前收工 | s05、s10、s17 |
| 交付语义要想清楚,至少一次就要幂等 | 重复执行或丢任务 | s12、s15、s16 |
我拿自己站上的导游 Agent 对照过这张表,循环的停止条件和工具配对这两处最容易出问题。这也是第 1 站写得最细的部分。
七站
| 站 | 章节 | 回答什么问题 |
|---|---|---|
| 1 跟读 learn-claude-code(1):循环内核 | s01–s04 | 循环怎么停;工具怎么分发;权限放在哪;钩子怎么让循环不再变 |
| 2 计划、子代理与上下文预算 | s05、s06、s08 | 模型怎么不跑偏;大任务怎么隔离上下文;上下文满了先丢什么 |
| 3 跟读 learn-claude-code(3):跨会话记忆 | s09 | 什么值得记;记忆什么时候选、什么时候写 |
| 4 任务图、后台与定时:让工作撑过一轮对话 | s10–s12 | 工作怎么跨轮、跨时间不丢 |
| 5 多 Agent 团队:持久队友、信箱与原子认领 | s13 | 子代理不够用时,队友怎么认领任务、怎么通信 |
| 6 技能、MCP 与集成 | s07、s14、s15 | 工具从外部来时谁说了算;14 个机制装在一起会互相卡在哪 |
| 7 固定编排与目标闸门:Workflow 运行时和 Goal Loop | s16、s17 | 什么时候把编排写死;谁来判断"真的做完了" |
每站的结构相同:要解决的问题,机制怎么工作,换个做法会坏在哪,教学版的坑,和真实 Claude Code 的差别,自测题。
怎么读
- 只有半天:第 1 站,然后直接看第 6 站里第 15 章的部分。前者是内核,后者讲 14 个机制放在一起时的顺序和冲突,看懂这两站就有了整门课的骨架。
- 按问题补:关心"模型不跑偏"读 2、4、7 站;关心"上下文"读 2、3 站;关心"并发和异步"读 4、5 站;关心"工具来源和治理"读 6、7 站。
- 准备面试:每站最后的自测题,先遮住参考答案自己讲,按问题、机制、例子、边界的顺序讲。被问到为什么这么排时,最有说服力的回答是讲清换个排法会坏在哪。
动手前的环境提醒
- 课程用
.env里的MODEL_ID和ANTHROPIC_BASE_URL选模型,可以接兼容 Anthropic 接口的其他服务商。 - 第 13 章依赖
fcntl和对 stdin 的select,原生 Windows 跑不起来,要用 WSL。第 15 章对fcntl做了导入保护。 - 在 Windows 上跑测试,还会遇到创建符号链接要权限、换行符和反斜杠路径这三类失败。它们和课程逻辑无关。
连接
- Coding / Harness:主线第 8 站讲 Harness 有哪些职责,本系列讲这些职责在一份真实代码里怎么落地。
- Agent 核心:Agent Loop 的面试题,第 1 站和第 7 站给出了代码层面的答案。
- 主线:一个报销请求走完整个 Agent 系统:报销案例走的是同一个循环,换了工具和领域规则。
- 可靠执行:状态、幂等与恢复:第 4 站的至少一次交付、第 7 站的 journal 回放,都是那一篇里幂等和恢复的具体实现。
- 安全边界:让权限落到工具层:第 1 站的权限闸门和第 6 站的宿主策略,是"权限落到工具层"的两个例子。
这是一篇可以继续生长的笔记。沿着关系继续阅读 ↗