我最近基于 Learn Claude Code 这个项目,按章节把它的 Python 教程实现迁到了 TypeScript。 项目地址: https://github.com/ckvv/learn-claude-code
和教程首页强调的路线一致:先理解最小 loop,再逐层叠加 tool use、task system、background tasks、agent teams 和 worktree isolation。
- 先让模型和工具形成闭环
- 再补文件系统和 shell 能力
- 再把状态从上下文里拿出来
- 最后再处理多任务、长生命周期和执行隔离
一切都从最小 Agent Loop 开始
整个项目里,s01_agent_loop.ts。把一个零依赖 agent 的底层模式压缩到了几行逻辑里:
- 把当前消息历史发给模型
- 如果模型返回 tool call,就执行工具
- 把工具结果作为
tool消息继续喂回模型 - 如果没有 tool call,就结束本轮
这也是原教程反复强调的核心模式:模型本身不会“行动”,真正让 agent 动起来的是“模型决策 + 工具执行 + 结果回灌”这个闭环。
TypeScript 版本里,我没有引入额外 SDK 去包这层流程,而是尽量保留最直白的结构。原因很简单:如果你的目标是理解 agent 到底怎么工作,最不该做的事情,就是一开始先把 loop 藏进库里。
这样做有两个很直接的好处:
- 读代码时不会被库抽象打断
- 后续每一章新增的能力,都能看成是在这个 loop 上做增量改造
如果你自己也想做 agent,我很建议先亲手写一遍这种版本。很多所谓“高级能力”,其实都只是给这个循环加约束、加状态、加治理。
零依赖的关键,不是“不用库”,而是不提前把问题抽象错
这里说的“零依赖”,不是一种姿态,也不是为了刻意追求极简。它真正重要的地方在于:你可以先把运行时边界想清楚,再决定哪些东西值得抽象。
很多 agent 项目一上来就会先做这些事:
- 上 tool registry
- 上统一 message adapter
- 上 command bus
- 上 workflow engine
- 上各种 runtime hooks
这些东西当然都可能有价值,但问题在于,如果你还没亲手写过最小 loop,其实并不知道自己是在抽象真实问题,还是在抽象想象中的问题。
所以这次迁移里,一个很明确的决定是:不把教程代码过度封装成库。
仓库里只有一个真正被抽出来的公共模块:src/simple_fetch_client.ts。它做的事情非常少,只是:
- 读取环境变量
- 调用 OpenAI 兼容的
/chat/completions - 返回当前教程需要的最小消息结构
这个取舍看起来有点克制,但对“零依赖 agent”这件事很重要。因为一旦你把 tool registry、message adapter、runtime orchestration 全部抽成框架层,文章就会从“讲清楚机制”变成“讲如何使用你的封装”。
这也是为什么 TypeScript 版本仍然坚持“章节单文件、自包含”的风格。比如从 s01 到 s03,你能很直观地看到能力是怎么一点点加上去的:
s01只有bashs02开始加入read_file、write_file、edit_files03再加入todo,让模型显式维护执行计划
这条演进路径很重要,因为它揭示了一个经常被忽略的事实:agent 的能力不是靠 prompt 一次性“提示出来”的,而是靠运行时里一层层补出的状态和工具能力建立起来的。
零依赖实现里,最值得认真设计的是协议层,不是框架层
这套教程的原始版本是 Python + Anthropic 风格接口,而我在 TypeScript 里改成了 OpenAI 兼容调用。这个改动不只是换个客户端那么简单,它实际上逼着我重新想清楚一个更关键的问题:
教程里的“agent 概念”,哪些是模型接口无关的,哪些又是强绑定某家 SDK 的?
迁移后我越来越确定,真正稳定的是这几层:
- 消息历史
- tool schema
- tool dispatch
- 状态持久化
- 多轮循环控制
相对不稳定、需要适配的反而是 API 细节,比如消息格式、tool call 返回结构,以及不同模型供应商对内容块的表达方式。
这也是为什么我越来越倾向于把 agent 实现分成两部分:
- 核心 runtime 尽量简单,只关心 loop、tool dispatch 和状态
- 模型供应商差异收敛在一个很薄的协议适配层里
从 Todo 到 Task System,agent 才开始像一个真正的执行体
我觉得这个项目的分水岭在 s03_todo_write.ts 和 s07_task_system.ts。
在 s03 里,模型第一次有了显式的过程状态。todo 工具不是为了让输出更好看,而是为了让 agent 在多步任务里不至于失忆。你会发现,一旦任务不是“一问一答”,而是“先读代码、再改文件、再验证结果”,仅靠对话上下文并不够,模型需要一个外部化的任务面板。
再往后到 s07,任务状态不再只存在内存里,而是落成 .tasks/task_*.json。这一步很关键,因为它意味着:
- 状态开始跨轮次持久化
- 状态不再依赖上下文窗口
- 任务之间可以显式建立依赖关系
换句话说,agent 从“会调用工具的聊天机器人”,变成了“带控制平面的执行系统”。
这也是为什么我很认同教程后半段的方向。真正可扩展的零依赖 agent,重点不是继续往 prompt 里塞规则,而是把这些规则沉到外部状态机、任务系统和生命周期管理里。
Worktree 隔离说明:Agent 工程化的重点从“会不会改代码”转向“如何安全地改代码”
到了 s12_worktree_task_isolation.ts,这套教程终于从“会做事”走到了“能在真实团队里安全做事”。
这个章节里我最喜欢的一句注释是:
Tasks are the control plane. Worktrees are the execution plane.
它几乎可以当成整个工程化阶段的纲领。
前面的章节解决的是:
- agent 如何调用工具
- agent 如何分解任务
- agent 如何维持长期状态
而 worktree 这一层解决的是另一类问题:
- 多任务并行时如何隔离修改现场
- 同一个仓库里如何为不同任务分配独立执行空间
- 做完之后哪些 worktree 保留,哪些清理
- 整个生命周期如何可审计
所以 s12 里除了任务管理,还引入了 worktree 索引、事件日志、repo root 检测等机制。这些实现不一定复杂,但它们已经非常接近真实 coding agent 产品会面对的问题。
很多人做 agent demo 时,最关心的是“模型能不能自己改一个文件”。但如果你真的想把它变成一个长期可用的开发工具,更关键的问题其实是:
- 改动有没有隔离
- 状态能不能追踪
- 失败后能不能恢复
- 并行任务会不会互相污染
这也是我做完这次 TypeScript 迁移后最大的感受:agent 的难点,后期越来越不像“提示词工程”,而像“运行时系统设计”。
s_full.ts 让我确认了一件事:Claude Code 不是一个功能点,而是一组协同机制
看完整个仓库后,再回头看 s_full.ts 会很有意思。它并没有发明新的魔法,而是把前面章节里的机制组合起来:
bash/read/write/edittodo管理- 任务系统
- 团队消息与 inbox
- 后台任务
skills目录- transcript 和上下文压缩阈值
这就是我理解这类 agent 产品的正确方式:它们不是一个“超级 prompt”,而是一套多层机制叠加出来的操作系统雏形。
换句话说,如果你想复刻 Claude Code 的能力,不应该上来就问“系统 prompt 怎么写”,而应该先问:
- 状态放在哪里
- 工具怎么定义和调度
- 多轮执行如何终止
- 任务怎样持久化
- 多 agent 怎么通信
- 长上下文如何压缩
- 工作目录和权限如何隔离
这套教程最好的地方,就是把这些问题拆成了 12 个连续可运行的实验。
结语
Learn Claude Code 这套内容最有价值的地方,在于它没有把 agent 神秘化。它把一个看起来复杂的 AI coding system,还原成了一个个可以单独理解的机制层。
当你把最小 loop、tool dispatch、todo、task、background task、team protocol、worktree isolation 一层层接起来之后,你会发现,所谓“AI Coding Agent”,本质上是一个围绕模型构建出来的可执行 runtime。
而如果你真想把这件事学明白,最好的起点往往不是先找一个最强框架,而是先亲手实现一套零依赖版本。因为只有这样,你才会真正知道哪些东西是核心,哪些只是后来为了规模化协作才加上的壳。