上周我把一个叫 learn-claude-code 的项目从 s01 跑到了 s20。
它用 20 章从零手写了一个 nano 版的 Claude Code,标语只有一行:Bash is all you need。让我意外的是,一个能干活的 coding agent,内核真的就是一段二十行的循环。后面所有花哨的能力,记忆、子任务、多 Agent 协作、上下文压缩,全是围着这二十行往外长出来的。
读完我的感受是,它讲清楚了一件我一直没说明白的事:写 Agent 的人,到底在写什么。这篇是我的读书笔记,把它的核心机制一个个拆开,配上图和我自己的理解。
它的前提:Agent = 模型 + Harness
项目开篇就立了一个我认为很关键的前提:
Agency(感知、推理、行动的能力)来自模型训练,不来自外部代码编排。一个 Agent 产品 = 模型 + Harness。
它用一段历史佐证。2013 年 DQN 只看像素和分数就学会打 Atari,2019 年 OpenAI Five 自己跟自己打了 45000 年 Dota 干掉了 TI8 冠军,AlphaStar 上了星际宗师段位,腾讯绝悟在王者里让职业选手 15 局只赢 1 局。这些系统的架构是同一个:一个训练出来的模型,放进一个环境,给它感知和行动的接口。
模型负责智能,环境负责行动空间,合起来才是一个完整的 Agent。Atari 模拟器、Dota 客户端、星际引擎,到了 coding agent 这里,环境就是 IDE 和终端。
把这个前提立住,"做 Agent"就只剩两种可能:要么你在训模型(调权重、RLHF、收轨迹数据),要么你在造 Harness(写让模型能在某个领域干活的那套代码)。大部分人做的是后者。这个词项目里叫 harness engineering,我更愿意叫它"造车"。
模型是司机,你造的是车
Harness 在项目里有一个干净的公式:
Harness = Tools + Knowledge + Observation + Action + Permissions
Tools: 文件读写、shell、网络、数据库、浏览器
Knowledge: 产品文档、领域参考、API 规范、风格指南
Observation: git diff、错误日志、浏览器状态
Action: CLI 命令、API 调用、UI 交互
Permissions: 沙箱隔离、审批流、信任边界模型做决策,Harness 负责执行。模型推理,Harness 提供上下文。项目里有一句话我很认同:Claude Code 优雅的地方,恰恰在于它"不做什么"。它不试图代替模型决策,不强加固定 workflow,不用手写规则替代模型的判断。给工具、给知识、给上下文管理、给权限边界,然后让开。
你不是在写智能,你是在造智能栖身的那个世界。世界的质量,直接决定智能能发挥到几成。
二十行的内核
前提和分工讲清楚了,看代码。这段循环从第 1 章到第 20 章一行都没改过:
def agent_loop(messages):
while True:
response = client.messages.create(model, system, messages, tools)
messages.append(assistant_response)
if response.stop_reason != "tool_use":
return # 模型说停就停
# 否则:执行模型要的工具,把结果追加回 messages,继续循环模型要调工具就调,调完把结果塞回对话,循环到模型自己决定停下。代码不替它判断"任务完成了没",那是模型的事。
整个项目的精髓就在这里:这个 loop 不变,后面 19 章每一章只往它周围叠加一个机制,从不改写它本身。
Loop 属于 Agent,机制属于 Harness。
20 章我不逐章讲,挑其中最能体现设计思想的机制拆开。先放一张全图当地图。
20 章地图
| 章节 | 主题 | 一句话口号 |
|---|---|---|
| s01 | Agent Loop | 一个循环加 bash,就是一个 Agent |
| s02 | Tool Use | 加工具等于加 handler,loop 不动 |
| s03 | Permission | 先立边界,再给自由 |
| s04 | Hooks | 在 loop 周围挂钩子,别改 loop |
| s05 | TodoWrite | 没计划的 Agent 会跑偏 |
| s06 | Subagent | 大任务拆小,每个子任务给干净上下文 |
| s07 | Skill Loading | 知识按需加载,不要一次塞满 |
| s08 | Context Compact | 上下文总会满,得有腾地方的办法 |
| s09 | Memory | 记住重要的,忘掉无关的 |
| s10 | System Prompt | Prompt 运行时拼装,不是写死的 |
| s11 | Error Recovery | 出错不是终点,是重试的起点 |
| s12 | Task System | 大目标拆小任务,排序,落盘 |
| s13 | Background Tasks | 慢操作丢后台,Agent 继续想 |
| s14 | Cron Scheduler | 按时触发,不用人去戳 |
| s15 | Agent Teams | 一个 Agent 干不完,叫队友 |
| s16 | Team Protocols | 队友之间要有统一的通信规则 |
| s17 | Autonomous Agents | 队友自己看板认领,不用人派活 |
| s18 | Worktree Isolation | 各干各的目录,互不干扰 |
| s19 | MCP Plugin | 能力不够,用 MCP 插更多工具进来 |
| s20 | Comprehensive | 众多机制,回到一个 loop |
有个坑先提醒你:仓库里现在两套教程并存。根目录的 s01 到 s20 是新的正式版本,docs/ 和 web 平台还是旧的 12 章版本,章节号对不上。新读者直接读根目录。
下面的拆解没按章节号排,而是按主题归了组,从给 Agent 装手、立规矩,到管好喂进模型的上下文,再到扛住失败、扩展成团队。顺序跳一点,但读起来更成片。
加一个工具,就是加一个 handler
第 2 章解决一个最基础的问题:怎么给 Agent 加能力,又不动那个内核循环。
答案是一张分发表。每个工具就是一个函数,注册进一个名字到函数的字典里。模型说要调 bash,循环就去表里找 bash 对应的 handler 执行;说要调 read_file,就找 read_file。循环本身永远只做一件事:把模型点名的工具名,转给对应的处理函数。
这有点像总机话务员。来电报一个分机号,话务员只负责接通,不关心那头是销售还是客服。你想加一个新部门,只要在交换机上登记一个分机号,话务员的工作流程一个字都不用改。
这个设计的价值在于扩展成本恒定。加第 1 个工具和加第 100 个工具,对循环来说没区别,都是往字典里多塞一项。Claude Code 那么多工具,底层就是这么一张表。
先立边界,再给自由
工具越强,越危险。一个能跑 bash 的 Agent,理论上能 rm -rf 掉你的整个目录。第 3 章和第 4 章处理的就是这件事。
第 3 章是权限。它把每个工具调用过一遍判断:有些可以直接放行(读文件),有些必须拦下(删根目录),有些得停下来问人(写生产配置)。这就像给一个新来的实习生开权限,能做的列清楚,红线划明白,拿不准的先报备。
第 4 章是 hooks,在工具执行的前后插入扩展点。PreToolUse 在工具跑之前触发,PostToolUse 在之后触发。重点是它的口号:在 loop 周围挂钩子,别改 loop。想加日志、加审计、加自定义拦截,都从钩子进,内核循环依然纹丝不动。这跟前面那张分发表是一个哲学,所有扩展都发生在循环外围。
上下文总会满:四层压缩
装好手、立好规矩,Agent 就能动了。但任务一长,它立刻撞上同一堵墙:上下文窗口满了。接下来这几章都在解决同一件事,怎么管好喂进模型的那段上下文,也是我最喜欢的一组。第 8 章先接最直接的问题:窗口满了怎么办。它给的不是一个开关,是一条四层流水线,原则是"便宜的先做,贵的最后做"。
我会用收拾一张越堆越满的桌子来理解它。先把大件文件归进抽屉(把超大的工具结果落盘,上下文里只留一个引用),再扔掉中间没用的草稿(裁剪旧消息),再把不常翻的旧文件换成一张便利贴标记(旧工具结果换占位符)。这三步都很便宜。只有当桌子还是满,才花时间做一份会议纪要,把一摞纸压成一页(让模型对历史做全量摘要,这一步要花一次 API 调用,所以放最后)。万一摘要完 API 还是报超长,再走紧急兜底。
注释里写明这套执行顺序对齐了 Claude Code 的真实行为。把"上下文管理"从一个模糊的概念,落成一条有先后、有成本意识的流水线,这是我觉得这章最值钱的地方。
记住重要的,忘掉无关的
压缩解决的是单次会话内的空间,记忆解决的是跨会话的延续。第 9 章把记忆拆成三个子系统,我觉得这个拆法很接近人脑。
extraction 是随手记笔记,每轮对话结束,从原始消息里抽出值得留下的东西写进一个文件。selection 是用到时翻出相关的那几页,按文件名和描述把当前任务相关的记忆调进上下文,而不是把整本笔记本都背上。consolidation 项目里叫"做梦",周期性地把零碎笔记整理、合并、去重,免得记忆库越长越乱。
存储上它用一个 MEMORY.md 当索引,每条记忆一行,底下挂一堆带 YAML frontmatter 的单文件。这个"索引加按需加载"的结构,跟它处理技能、处理上下文是同一套思路:永远不要把全部内容一次性塞进去。
Prompt 不写死,运行时拼装
第 10 章处理 system prompt。大多数人会把它写成一个固定的大字符串,从头到尾不变。这一章的做法不一样:把 prompt 拆成一段段(身份、工具说明、工作目录、记忆等),每一轮根据当前真实状态现拼。
关键是"按真实状态",不是按关键词猜。比如记忆那一段,只有当 .memory/MEMORY.md 真的存在时才加进去,不存在就不占位置。拼好之后还做了一层确定性缓存,相同状态拼出来的 prompt 完全一致。
像出门前收拾背包。固定要带的钥匙钱包每次都在,下雨才塞把伞,出差才装电脑。你不会背一个塞满所有可能用到的东西、永远不变的大包出门。
这么做有两个好处。一是上下文干净,每轮只放当前真正相关的段。二是缓存友好,身份和工具说明这类稳定前缀可以被 prompt caching 命中,省钱也省延迟。本质上它和压缩、记忆是同一套哲学的又一次出现:能不放进上下文的,就别放。
脏活交给替身
把内容挡在上下文外面,还有一招更狠的:一整段脏活都不让它进主上下文。第 6 章的 subagent 干的就是这个,名字听着像多智能体那套玄学,其实本质就是上下文隔离。
主 Agent 遇到一件需要翻很多资料的脏活,比如"在整个代码库里找出所有调用了某个废弃接口的地方",它不自己干,而是派一个全新空白上下文的子 Agent 去。子 Agent 翻了几十个文件、跑了一堆 grep,过程极其啰嗦,但这些噪音全留在它自己的上下文里。干完它只把一句结论带回主线。
像你让助理去查档案。他翻了一上午,最后给你一页摘要,不会把整个档案室搬到你桌上。主 Agent 的上下文因此始终干净,省下来的空间留给真正重要的推理。
知识按需加载
第 7 章的 skill loading 是同一个"按需"哲学的又一次出现。
一个 Agent 可能会几十上百种技能,但你不能把它们全写进 system prompt,那会瞬间撑爆上下文,而且大部分技能这次任务根本用不上。它的做法是分两层:先给模型一份目录,列出有哪些 skill、每个大概干嘛,这份目录很轻;模型判断这次要用哪个,才把那一篇技能的完整内容展开注入。
就是图书馆的逻辑。你先看索引卡,知道某本书在哪、讲什么,需要哪本再去书架上取,没人会把整个书库搬回家。Agent 的能力上限,很大程度上就取决于这种"压缩与按需展开"做得好不好。
出错不是终点
上下文管到这儿就差不多了,但还有一个前面一直回避的现实:LLM 调用本身会失败,而且会用好几种方式失败。第 11 章把调用包进 try/except,按错误类型分了三条恢复路径。
我用开长途车来理解这三条路。话没说完就被打断(输出被 max_tokens 截断),那就深吸一口气接着说;后备箱塞太满关不上(prompt 超长),先扔掉点东西再上路(触发压缩);前面堵车、服务区排长队(被限流、服务过载),就等一会儿再走,等太久干脆换一辆备用车(fallback model)。
错误类型不同,恢复路径就不同。不是一律重试,也不是一遇错就退出。
这章看着不起眼,却是从 demo 走到能用的分水岭。真实的 Agent,难的从来不是顺利那条路,是各种 API 抽风时还能优雅地接住。把恢复策略按错误类型分门别类,是健壮性的起点。
一个人干不完,就叫队友
单个 Agent 再健壮也有上限,有些任务一个脑子装不下。从第 12 章到第 18 章,项目把它扩展成一个能协作的团队。这几章拼在一起是一套完整的多 Agent 最小实现,我把它们合着讲。
第 12 章先把目标拆成带依赖关系的任务,落盘成磁盘上的任务图,blockedBy 字段记着谁卡着谁。第 15、16 章给队友配了文件邮箱,一堆 .jsonl 文件当异步信箱,队友在后台线程里跑各自简化版的循环,消息会被注入回主 Agent 的对话历史,而不只是打印出来。第 17 章更进一步,队友自己去任务板上认领活,不需要主 Agent 一个个派。第 18 章给每个任务分配独立的 git worktree,各自在自己的分支目录里干活,互不踩脚。
像一个小团队运作:墙上贴着任务板和依赖关系,成员自己认领,通过信箱异步沟通,每个人在自己的工位上干活。把这套机制拆开看,所谓"多智能体"并不神秘,就是任务图加邮箱加目录隔离。
能力不够,就插上去
第 19 章是 MCP,把外部能力接进同一个工具池。
对模型来说,一个通过 MCP 接进来的外部工具,和一个本地内置的 bash,长得一模一样,都是工具池里的一项。这就像电脑的扩展坞:你不用拆开机箱改主板,插上一个坞,外接屏幕、网口、读卡器一下都有了。Agent 的能力边界,因此可以在不改内核的前提下不断外扩。
到第 20 章,前面所有机制汇总,重新围着那个二十行的循环组装成一个完整的 Agent。绕了一大圈,内核还是那个内核。
怎么读,怎么上手
每一章是一个文件夹,结构统一:一篇叙事 README.md(中文,含内联代码,还有英日翻译),一段独立可跑的 code.py,必要时配 SVG 图。复杂章节用 <details> 折叠深水区,想钻再展开。
读法很简单,从 s01 顺着读到 s20,后一章默认你读过前面,结尾会埋一个钩子勾到下一章。上手三行:
git clone https://github.com/shareAI-lab/learn-claude-code
pip install -r requirements.txt && cp .env.example .env # 配 ANTHROPIC_API_KEY
python s01_agent_loop/code.py # 从一个 loop 加 bash 起步跑完 s01 你大概率会上头,因为你会亲眼看到一个能干活的 coding agent,核心真的就是那二十行。再去 python s08_context_compact/code.py 看压缩流水线,python s20_comprehensive/code.py 看所有机制合体。项目还顺手给了两个产品化出口:Kode Agent CLI 和姊妹教程 claw0(讲 heartbeat 和 cron 怎么把一次性 Agent 变成常驻助手),读完主线想往前走,可以当路标。
我的思考
跑完这 20 章,几个判断对我来说更清楚了。
第一,"编排"这个词被高估了。市面上一大半 Agent 平台卖的是编排能力,拖拽、连线、配置分支。但编排的本质是在替模型做决策,是用确定性的代码去框住一个本该不确定的智能。模型越强,你框得越多,浪费得越多。真正该投入的不是编排,是工具的质量、知识的组织、上下文的管理、权限的边界,也就是 Harness 本身。
第二,Harness 是有壁垒的,而且壁垒在变深。模型是共享的,谁都能调 API。但同一个模型,套上一个好 Harness 和一个差 Harness,表现能差出一个数量级。Agent 的能力上限不在模型参数,在你给它的那套环境,而这部分恰恰是工程师能掌控、能积累、能形成差异的地方。
第三,克制是一种很高级的工程能力。这个项目最打动我的不是它实现了多少机制,是它有一个二十行的 loop 二十章都没动过。知道什么不该写,比知道什么该写更难。很多人写 Agent 的毛病,是手痒,是不信任模型,是总想多管一点。学会让开,是它给我的最大一课。
第四,轨迹即数据。项目里有一句被低估的话:Agent 在你的 Harness 里跑出来的每一段行动序列,都是训练信号。你今天造的车,跑出来的路径,是明天那个更强模型的原料。Harness 工程师不只是在用模型,也在为模型的下一代喂数据。这个闭环一旦想明白,造 Harness 这件事的长期价值就完全不一样了。
如果你也在做 Agent,我的建议是先把 s01 跑一遍,再回头看你手头那套系统,问自己一个问题:我写的这些代码,有多少在帮模型,有多少在替模型?