<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>607&apos;s Blog</title><description>随机漫游</description><link>https://rosemary1812.github.io</link><item><title>RAG or Grep：关于代码搜索</title><link>https://rosemary1812.github.io/blog/rag-or-grep-code-search</link><guid isPermaLink="true">https://rosemary1812.github.io/blog/rag-or-grep-code-search</guid><description>为什么 coding agent 的主力检索工具不是 RAG，而是更朴素的 Grep？从 RAG 到 Grep 再到 CodeGraph，代码检索正在发生什么变化。</description><pubDate>Sun, 17 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;过去很多人习惯用 RAG 理解 AI 检索：先把文档切块，做 embedding，存进向量库；用户提问时，把问题也转成向量，再找最相近的片段塞回上下文。&lt;/p&gt;
&lt;p&gt;这套方法放在知识库问答里很自然。用户问的是一个概念，文档里可能是另一种说法，问题和答案不一定共享同一批词。语义相似度可以补上这种词汇不匹配。&lt;/p&gt;
&lt;p&gt;代码库则不同，Claude Code、Codex 这类 coding agent 做代码搜索时，主力工具经常不是传统 RAG，而是更朴素的 Grep，准确说是 &lt;code&gt;ripgrep&lt;/code&gt;。这不是倒退，代码本身的形态决定了很多检索问题更适合精确匹配。&lt;/p&gt;
&lt;h2&gt;RAG 解决什么问题&lt;/h2&gt;
&lt;p&gt;RAG 的目标是把模型没有记住、或者不该靠记忆回答的外部信息取回来。&lt;/p&gt;
&lt;p&gt;典型链路是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先把文档切成 chunk。&lt;/li&gt;
&lt;li&gt;用 embedding 模型把 chunk 转成向量。&lt;/li&gt;
&lt;li&gt;把向量、元数据和原文位置存进向量数据库。&lt;/li&gt;
&lt;li&gt;用户提问时，把 query 也转成向量。&lt;/li&gt;
&lt;li&gt;做 top-K 相似度搜索，必要时 rerank。&lt;/li&gt;
&lt;li&gt;把命中的片段组装进模型上下文。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这套链路的长处是语义召回。比如用户问&quot;怎么处理用户退出登录&quot;，文档里可能写的是 &lt;code&gt;session invalidation&lt;/code&gt; 或 &lt;code&gt;revoke refresh token&lt;/code&gt;。关键词不一致，但语义接近，向量检索有机会把它找出来。&lt;/p&gt;
&lt;p&gt;所以 RAG 很适合自然语言知识库、产品文档、客服问答、论文资料库。它处理的是&quot;我不知道答案在哪里，甚至不知道该搜什么词&quot;的场景。&lt;/p&gt;
&lt;p&gt;代码检索也会遇到这种问题，但这不是 coding agent 最常遇到的那类问题。&lt;/p&gt;
&lt;h2&gt;Coding Agent 为什么更常用 Grep&lt;/h2&gt;
&lt;p&gt;coding agent 面对代码库时，大量任务不是开放式问答，而是定位、追踪和验证：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;login&lt;/code&gt; 在哪里实现？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createUser&lt;/code&gt; 谁在调用？&lt;/li&gt;
&lt;li&gt;这个配置项有没有被读取？&lt;/li&gt;
&lt;li&gt;某个错误信息是哪里抛出来的？&lt;/li&gt;
&lt;li&gt;这条 API 请求最后落到哪个 handler？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题往往能落到代码里的精确字符串、符号、路径、类型、函数名、错误文本、配置键。&lt;/p&gt;
&lt;p&gt;代码里的标识符本身就带着语义。&lt;code&gt;getUserById&lt;/code&gt; 不只是一个字符串，它已经把动作、对象和约束写进名字里。错误文案、路由路径、环境变量、class 名、interface 名也一样。对这类信息，精确匹配通常比语义相似度更可靠。&lt;/p&gt;
&lt;p&gt;Claude Code 的搜索过程可以概括成一个循环：LLM 自己决定搜什么，调用 Grep / Glob / Read，读到结果后再决定下一轮搜什么，直到信息足够。&lt;/p&gt;
&lt;p&gt;它不是一次检索给出答案，而是多轮探索：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;模型看到用户问题和可用工具。&lt;/li&gt;
&lt;li&gt;模型把问题转成搜索词，比如函数名、类名、路径片段、报错文本。&lt;/li&gt;
&lt;li&gt;调用 Grep 找候选文件。&lt;/li&gt;
&lt;li&gt;读取少量相关文件片段。&lt;/li&gt;
&lt;li&gt;根据新线索调整关键词继续搜。&lt;/li&gt;
&lt;li&gt;最后把多轮发现综合成答案或修改方案。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Grep 在这里不是单纯的文本搜索，而是模型推理过程里的探针。模型提出假设，Grep 用很低的成本验证假设。&lt;/p&gt;
&lt;h2&gt;Grep 绕开了很多工程问题&lt;/h2&gt;
&lt;p&gt;coding agent 偏爱 Grep，不是因为 Grep 更聪明，而是因为它把很多工程问题都绕开了。&lt;/p&gt;
&lt;p&gt;RAG 的前提是预处理。要切 chunk，要做 embedding，要建索引，还要保证索引和真实代码同步。这个链路一旦放进开发环境，就会遇到很具体的问题：第一次打开仓库要不要等索引完成？文件刚改完但索引还没更新怎么办？重命名、删除、生成文件、monorepo 里的排除规则怎么处理？&lt;/p&gt;
&lt;p&gt;Grep 的做法很直接：直接查当前磁盘。用户刚打开仓库，agent 就能搜；代码刚保存，下一次搜索看到的就是新内容。它没有&quot;索引是否过期&quot;的状态，也不需要解释为什么检索结果和编辑器里看到的不一致。&lt;/p&gt;
&lt;p&gt;权限和隐私也是类似的逻辑。很多 RAG 架构需要把代码块上传到服务端生成 embedding 或做向量检索，即使中间做了加密、去标识化、生成后丢弃原文，对开发者来说依然多了一层信任成本。Grep 在本地文件系统上工作，安全边界很清楚。&lt;/p&gt;
&lt;p&gt;Grep 的结果也适合 agent 做下一步决策。它命中了哪个文件、哪一行、哪个字符串，一眼就能判断要不要继续读。向量检索返回的是相似片段，有时语义上接近，工程上却不相关；一旦把 agent 带进错误模块，后面的推理就会越走越偏。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ripgrep&lt;/code&gt; 本身也够快。它会遵守 &lt;code&gt;.gitignore&lt;/code&gt;，跳过二进制文件，多线程搜索，并用高效的正则实现和 SIMD 优化。在 MB 到几百 MB 级别的本地项目里，暴力扫一遍通常不是瓶颈。真正花时间的，往往是模型如何从搜索结果里组织下一步探索。&lt;/p&gt;
&lt;p&gt;Grep 的价值在于把代码检索压回一个很稳定的工程接口：当前文件系统里有没有这个字符串。这个接口不优雅，但可靠、实时、可解释，适合让 LLM 在多轮循环里反复试探。&lt;/p&gt;
&lt;h2&gt;Grep 也有代价&lt;/h2&gt;
&lt;p&gt;Grep 路线的问题也很明显。&lt;/p&gt;
&lt;p&gt;它会产生多轮工具调用。一次搜索拿到文件列表后，agent 还要读文件；读完发现新符号，再继续搜；复杂问题可能要反复十几次。这会消耗时间、token 和上下文空间。&lt;/p&gt;
&lt;p&gt;它也依赖模型提出好关键词。如果模型不知道应该搜 &lt;code&gt;refreshToken&lt;/code&gt;、&lt;code&gt;session&lt;/code&gt; 还是 &lt;code&gt;auth middleware&lt;/code&gt;，就可能绕远路。对于自然语言描述很强、代码命名又不直观的问题，纯 Grep 会吃亏。&lt;/p&gt;
&lt;p&gt;它还缺少结构关系。Grep 能告诉你字符串出现在哪里，但不知道哪个函数调用了哪个函数，哪个 route 绑定哪个 controller，哪个 class 继承了哪个 base class。模型可以通过多轮搜索拼出这些关系，但成本不低。&lt;/p&gt;
&lt;p&gt;Claude Code 后来引入 LSP，也是这个原因。&lt;code&gt;go to definition&lt;/code&gt;、&lt;code&gt;find references&lt;/code&gt; 这类语义精确操作，可以替代一部分 Grep + Read 的路径。它不是向量 RAG，而是代码语言服务提供的确定性语义检索。&lt;/p&gt;
&lt;h2&gt;从 GraphRAG 到 CodeGraph&lt;/h2&gt;
&lt;p&gt;Grep 不是终点。它解决了快速找到文本线索的问题，但它不理解代码结构。谁调用了谁，哪个路由绑定哪个 handler，某个符号的影响范围有多大，这些关系需要 agent 通过多轮 Grep 和 Read 慢慢拼出来。&lt;/p&gt;
&lt;p&gt;这里可以提 GraphRAG，但要避免把它和 CodeGraph 混成一类。GraphRAG 仍然属于 RAG 家族，只是在文本检索之外引入实体、关系和社区结构，用图改善自然语言资料的召回与综合。CodeGraph 关心的不是如何让文档问答更准，而是如何把代码里的符号和调用关系直接暴露给 agent。两者都重视结构，面对的对象和工程目标不同。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/colbymchenry/codegraph&quot;&gt;CodeGraph&lt;/a&gt; 是一个比较新的代码场景案例。它的 GitHub 描述是 &lt;code&gt;Pre-indexed code knowledge graph for Claude Code&lt;/code&gt;，也就是给 Claude Code 准备的预索引代码知识图谱。它用 tree-sitter 解析源码，把函数、类、方法、调用关系、import、继承、框架路由等信息抽取成图结构，存进本地 SQLite 数据库，再通过 MCP 工具暴露给 agent 查询。它给 agent 的不是相似文本片段，而是更接近 IDE / LSP / 静态分析工具的结构化上下文。&lt;/p&gt;
&lt;p&gt;这和传统 RAG 的差异很关键。RAG 通常围绕 chunk 和 embedding 工作，目标是从语义相似度里找可能相关的文本；CodeGraph 索引的是代码结构，节点可以是 symbol、route、文件、函数、类，边可以是 calls、imports、references、extends。检索问题也跟着变了：不是哪些片段和我的问题语义相似，而是这个入口点在哪里、调用链怎么走、改这个符号会影响哪些地方。&lt;/p&gt;
&lt;p&gt;README 里的 benchmark 也在讲这件事。没有 CodeGraph 时，Explore agent 会大量使用 grep、find、ls、Read，先花时间发现文件，再拼调用关系；有 CodeGraph 时，agent 用少数几次图查询就能拿到相关上下文，甚至不再读取文件。它减少的不是模型思考，而是反复发现文件和拼接关系的机械成本。&lt;/p&gt;
&lt;p&gt;这不意味着 CodeGraph 就是比 Grep 或 RAG 更高级的下一代答案。它只是换了一组取舍：为了让 agent 更快拿到结构关系，它重新引入了初始化、索引、语言适配、增量同步和图构建成本。小项目里，直接 Grep 可能更轻；大型仓库、跨模块理解、调用链追踪、影响分析这类任务里，图结构才更容易显示优势。&lt;/p&gt;
&lt;p&gt;CodeGraph 更适合作为一个观察窗口：代码检索的新探索不是简单回到传统向量 RAG，也不是说 Grep 已经足够解决一切，而是把全文搜索、符号检索、调用图、路由识别、影响分析这些能力组合起来，让 agent 用更少的探索成本理解代码库。&lt;/p&gt;
&lt;p&gt;这不是线性的进化路线。不是 RAG 落后，Grep 更先进，Graph 又比 Grep 更先进。不同检索对象需要不同结构。自然语言文档需要语义召回，代码编辑现场需要实时精确搜索，复杂代码理解需要显式关系图。&lt;/p&gt;
&lt;h2&gt;不同对象需要不同检索结构&lt;/h2&gt;
&lt;p&gt;把 RAG、Grep 和 Graph 放在一起看，&lt;strong&gt;RAG 面对的是自然语言材料。&lt;/strong&gt; 自然语言的麻烦在于词汇不稳定，同一个意思可以有很多表达方式。用户问&quot;怎么退出登录&quot;，文档可能写 &lt;code&gt;invalidate session&lt;/code&gt;；用户问&quot;权限怎么校验&quot;，文档可能写 &lt;code&gt;policy enforcement&lt;/code&gt;。在这种场景里，embedding 的语义召回很有价值。&lt;strong&gt;Grep 面对的是正在变化的本地代码。&lt;/strong&gt; 代码里的很多关键信息不是模糊语义，而是稳定标识符：函数名、类名、路径、错误文本、配置键、路由字符串。这里更看重实时、确定、可解释。agent 可以用 LLM 把自然语言任务翻译成搜索词，再通过多轮 Grep / Read 主动探索。&lt;strong&gt;Graph 面对的是代码结构。&lt;/strong&gt; 它关心的不只是某个词在哪里出现，而是这些符号之间有什么关系。当问题从定位一个函数，变成理解一条调用链、一个模块边界、一组路由入口或一次修改的影响范围时，图结构就比纯文本结果更有优势。&lt;/p&gt;
&lt;p&gt;真正的分歧不是谁取代谁，而是你把代码库看成什么。把它看成文档集合，就会自然走向 RAG；把它看成当前磁盘上的文本，就会走向 Grep；把它看成符号和关系组成的系统，就会走向 Graph。&lt;/p&gt;
&lt;h2&gt;变化发生在检索方式上&lt;/h2&gt;
&lt;p&gt;过去讨论 RAG，默认检索发生在回答之前：系统先检索，再把结果交给模型回答。&lt;/p&gt;
&lt;p&gt;coding agent 的检索方式不同。模型不是被动接收 top-K 片段，而是在任务过程中主动探索。它会先搜宽泛关键词，再根据命中结果缩小范围；看到一个函数名后继续找调用方；发现一个类型后再查定义；读到一个测试文件后反推业务入口。&lt;/p&gt;
&lt;p&gt;这种主动探索让 Grep 重新变得有用。单看 Grep，它只是字符串匹配；放到 LLM 的循环里，它变成了可迭代、可验证、可调整的搜索动作。&lt;/p&gt;
&lt;p&gt;CodeGraph 也是沿着这个方向发展。它没有把代码库当成一堆自然语言片段，而是把代码当成一个可查询的结构系统。agent 不再只问哪些 chunk 和我的问题语义相似，而是问：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个 symbol 在哪里定义？&lt;/li&gt;
&lt;li&gt;谁调用了它？&lt;/li&gt;
&lt;li&gt;它调用了谁？&lt;/li&gt;
&lt;li&gt;改它会影响哪些测试？&lt;/li&gt;
&lt;li&gt;这个 route 最终进入哪个 handler？&lt;/li&gt;
&lt;li&gt;这条跨语言调用链怎么走？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代码检索和普通文档检索最大的区别也在这里。代码不是文章，代码有名字、有路径、有类型、有调用关系、有执行入口。好的检索系统应该利用这些结构，而不是把它们全部压平成 embedding chunk。&lt;/p&gt;
&lt;h2&gt;结论&lt;/h2&gt;
&lt;p&gt;Grep 回归，是因为代码检索的高频需求本来就偏精确匹配；LLM 又能把自然语言任务动态翻译成一连串搜索词，让 Grep 从静态命令变成主动探索工具。&lt;/p&gt;
&lt;p&gt;CodeGraph 的出现，则说明除了向量 RAG 和纯 Grep 之外，还有一类更重视代码结构的本地索引方案：全文搜索负责找名字和文本，图结构负责找关系，LSP / tree-sitter / 静态分析负责提供确定性语义。&lt;/p&gt;
&lt;p&gt;今天的代码检索不是 RAG 和 Grep 二选一。Grep 负责快速、实时、精确地触达代码；CodeGraph 这类工具负责把重复探索和结构推理前置；RAG 仍然适合真正需要语义召回的自然语言资料和模糊概念搜索。&lt;/p&gt;
&lt;p&gt;coding agent 用更多 Grep，不是因为 RAG 不高级，而是因为在代码世界里，最有价值的线索往往藏在名字、路径和调用关系里。谁能更低成本地把这些线索拿出来，谁就更适合 agent 的工作流。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>从 Hermes Agent 到自学习 Skill 机制：三轮迭代的设计总结</title><link>https://rosemary1812.github.io/blog/from-hermes-agent-to-self-learning-skill-mechanism</link><guid isPermaLink="true">https://rosemary1812.github.io/blog/from-hermes-agent-to-self-learning-skill-mechanism</guid><description>最近手搓coding agent的一些思考和踩过的坑</description><pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;从 Hermes Agent 到自学习 Skill 机制：三轮迭代的设计总结&lt;/h1&gt;
&lt;p&gt;最近在做 Coding Agent，踩了不少坑，也想明白了一些事。这篇文章是对整个 Skill 自学习机制设计过程的完整复盘，包括灵感来源、三轮迭代的细节，以及途中遇到的关键判断。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;从 Hermes Agent 获得的启发&lt;/h2&gt;
&lt;p&gt;设计 Skill 机制时，核心参考来自 Hermes Agent。&lt;/p&gt;
&lt;p&gt;Hermes 是 Nous Research 开发的一个开源自改进 Agent，它内置了一个完整的学习闭环：从经验中自动创建 Skill，在使用过程中持续改进，并在会话之间保持记忆。在 Nous Research 自己的基准测试中，拥有 20 个以上自创 Skill 的 Agent，完成研究任务的速度比零 Skill 的全新实例快了 40%。&lt;/p&gt;
&lt;p&gt;从 Hermes 里借鉴的最重要的东西，是 &lt;strong&gt;AI Agent 从经验中自主学习的完整闭环&lt;/strong&gt;——经验提取、知识储存、智能检索、执行验证，再到自动改进。这个闭环有四个环节，每一个在不同的时间点触发，共同构成一个持续运转的反馈周期，Memory、Skill 和会话搜索都是这个过程的输出。&lt;/p&gt;
&lt;p&gt;另一个关键的借鉴是 &lt;strong&gt;Skill 和 Memory 的明确分工&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Memory&lt;/strong&gt; 是持久化的事实和偏好，记录那些跨会话依然有效的稳定信息，比如用户的环境配置、习惯偏好——这是&lt;strong&gt;陈述性知识&lt;/strong&gt;，适合存&quot;是什么&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skill&lt;/strong&gt; 则是&lt;strong&gt;程序性知识&lt;/strong&gt;——执行任务的 workflow、步骤、以及踩过的坑。在 Hermes 里，Skill 是一段结构化的 Markdown 文档，记录任务的解决路径、边界情况，以及在过程中发现的领域知识——适合存&quot;怎么做&quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个分工决定了你把什么东西往哪里沉淀，设计之初就要想清楚。&lt;/p&gt;
&lt;p&gt;此外，Hermes 的 Skill 遵循 agentskills.io 开放标准，也就是说 Skill 是可移植的、可共享的。这一点直接影响了我的设计判断——Skill 不只是当前 Agent 的私有知识，它是一套可以分发、可以给别人在自己 Agent 上直接复用的知识资产。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Skill 机制的三轮迭代&lt;/h2&gt;
&lt;h3&gt;第一轮：粗糙的落地&lt;/h3&gt;
&lt;p&gt;一开始只是想把自学习机制作为实验性功能先跑起来，实现得非常粗糙。具体做法是：在 AI coding 过程中，让它识别&quot;需要学习&quot;的情况，然后写出一个 Skill，仅此而已。&lt;/p&gt;
&lt;p&gt;很快就发现了问题——大模型对意图的理解出现了偏差。它的实现逻辑是：识别工具调用次数，一旦某个工具被反复调用超过几次，就把它记录成一个 Skill。&lt;/p&gt;
&lt;p&gt;这种做法非常机械。有时候确实需要反复调用同一种工具，比如连续用几次 &lt;code&gt;read&lt;/code&gt;、&lt;code&gt;grep&lt;/code&gt;、&lt;code&gt;glob&lt;/code&gt;，但这并不意味着需要把它沉淀成 Skill。识别这一环有问题，解析这一环也一样粗糙——它直接解析工具调用序列然后复用，但 Agent 需要学到的是&lt;strong&gt;经验&lt;/strong&gt;，而不是工具调用序列本身。&lt;/p&gt;
&lt;p&gt;这一轮说实话只能算是想法的落地，甚至算不上真正的实验性功能。&lt;/p&gt;
&lt;h3&gt;第二轮：引入 LLM 判断&lt;/h3&gt;
&lt;p&gt;把其他功能做得差不多后，专门回头调试 Skill 机制。这一轮把机械的计数逻辑改成了：累积 20 轮对话之后，看这些对话里用了几次工具，如果超过两次、并且没有命中任何已有 Skill，就让大模型读这段对话，判断是否有 Skill 需要生成。&lt;/p&gt;
&lt;p&gt;判断的输出方式是：如果认为有必要，就直接输出一段结构化的 Markdown 文档作为 Skill；如果认为没必要，就返回一个 JSON，包含 &lt;code&gt;true/false&lt;/code&gt;、置信度 &lt;code&gt;confidence&lt;/code&gt;，以及简短的判断理由 &lt;code&gt;reasoning&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;第三轮：统一决策 + 维护机制&lt;/h3&gt;
&lt;p&gt;在仔细阅读了介绍 Claude Code Agent 自学习机制的博客之后，意识到第二轮还有几个明显不足。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一，什么样的对话值得被学习成 Skill？&lt;/strong&gt; 标准应该是：复杂任务、比较棘手的错误、特殊的 workflow 信号，而不是简单地看工具调用次数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二，光生成新 Skill 是不够的，还要维护已有的 Skill。&lt;/strong&gt; 如果只是不断新增，会造成 Skill 膨胀。这些膨胀的 Skill 你可能用不到，但每次加载时都会占用 token——每个 Skill 的元数据大概消耗几十到一百多个 token，积累下来是很明显的开销。所以发现某个 Skill 过时、有错误、或者有可以补充的地方时，都需要及时更新。&lt;/p&gt;
&lt;p&gt;基于这两点，做了最终的优化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优化提示词，让 LLM 更准确地判断是否值得学习&lt;/li&gt;
&lt;li&gt;把输出从&quot;学了就输出 Markdown，不学就输出 JSON&quot;改成&lt;strong&gt;统一的三态决策&lt;/strong&gt;：&lt;code&gt;skip&lt;/code&gt;（跳过）、&lt;code&gt;create&lt;/code&gt;（创建新 Skill）、&lt;code&gt;patch&lt;/code&gt;（修改已有 Skill）&lt;/li&gt;
&lt;li&gt;无论 &lt;code&gt;create&lt;/code&gt; 还是 &lt;code&gt;patch&lt;/code&gt;，都先生成草稿，经过人工审批之后，再正式写入 Skill 列表，下次对话加载时就可以被注册调用&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;一个关键的方向性决策：Skill vs. 生成工具&lt;/h2&gt;
&lt;p&gt;整个过程中走过一个很典型的误区：自学习机制学到的东西，究竟应该沉淀成 Skill，还是直接生成一个新工具？&lt;/p&gt;
&lt;p&gt;在和 AI 讨论了一段时间之后，彻底意识到这是个严重的方向性错误。&lt;/p&gt;
&lt;p&gt;原因有几点。首先，Agent 应该学到的是经验，而不是工具。如果生成了很多工具，它们之间的功能可能会重叠，Agent 不知道该调用哪个。&lt;/p&gt;
&lt;p&gt;但这还不是最关键的。&lt;strong&gt;最关键的是成本和收益完全不对称。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果生成一个工具，后续需要去调试它，这已经是软件层面的问题，会真实影响整个系统的稳定性。但如果生成的是 Skill，即使质量不高，最多也就是加载时多消耗了几十个 token，代价极低。&lt;/p&gt;
&lt;p&gt;另外，Skill 是可迁移的。Skill 的本质是一段结构化的 Markdown 文档，是封装好的提示词，和底层用的是什么 Agent 框架无关。不管是 Claude Code 还是其他框架，都可以直接复用，也可以分享给别人。&lt;strong&gt;它是一套可沉淀、可复制、成本低、效用高的资产。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AI Coding 的方法论反思&lt;/h2&gt;
&lt;p&gt;做这个项目对 AI coding 有了一些新的感悟，尤其是在 Agent 这种相对新的领域。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;意图对齐的问题在 Agent 开发里会被放大。&lt;/strong&gt; 传统的 Web 开发，大模型对很多 common sense 是有感知的，你可以适当省略一些细节，它也能按预期实现。但 Agent 开发这个领域的训练数据相对少得多，所以你稍微模糊一点，意图对齐就会出问题。这次 Skill 机制做成那么机械的样子，本质上就是因为只说了&quot;加入自学习机制&quot;，没有给出清晰的定义和实现步骤。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;你不知道模型不知道&quot;，这是个盲区。&lt;/strong&gt; 在结果呈现出来之前，你根本不知道它在某个地方的理解是偏的。所以建议在开始开发之前，先让模型讲讲它对方案的理解，方案里哪里写得不够详细就让它展开讲，不能带着一个模糊的大方案直接开始写代码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自上而下的设计在 AI coding 里非常重要。&lt;/strong&gt; AI 写代码的速度非常快，但正因为如此，一旦设计上有遗漏，后期再去重构或修改会很麻烦。基础一定要在早期打好。当然，Agent 本来就是在实践中不断演化的，不可能一开始就设计完备——这里说的不是要做到完美，而是要有足够清晰的方向和足够细化的初始设计。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;测试与评测设计&lt;/h2&gt;
&lt;p&gt;做完一个新模块，怎么判断它真的有效？这需要数据来说话。&lt;/p&gt;
&lt;p&gt;数据集设计覆盖了 Skill 的整个生命周期，从生成到调用，分三类场景：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;生成测试&lt;/strong&gt;：给它一些对话，看能不能正确识别哪些值得学习、提取出什么内容&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;维护测试&lt;/strong&gt;：对话里包含的信息和已有 Skill 有部分重叠，看能不能判断出需要修整已有 Skill，而不是新建一个&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跳过测试&lt;/strong&gt;：对话本身没什么价值，看能不能正确判断并跳过&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在调用侧，会编排一些明确指向某个 Skill 的对话，看能不能触发对应的 Skill，以及触发之后相比没有 Skill 的情况，完成效率和步数是否有提升。&lt;/p&gt;
&lt;p&gt;这种方法本身也有局限性：它是特定设置的场景，不是真实的使用环境。所以如果条件允许，还会补充一个更真实的统计维度——在用户实际使用 Agent 的过程中，统计自动生成的 Skill 被调用的频率，来衡量它们是否真正有价值。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;以上是整个 Skill 自学习机制的设计历程。核心结论其实很简单：&lt;strong&gt;Skill 的价值在于低成本、可迁移、能持续演化&lt;/strong&gt;。生成工具是软件问题，生成 Skill 是知识问题，两者的代价和可控性天壤之别。理清这一点，很多设计决策自然就清晰了。# 从 Hermes Agent 到自学习 Skill 机制：三轮迭代的设计总结&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Learn Claude Code 学习笔记</title><link>https://rosemary1812.github.io/blog/learn-claude-code-notes</link><guid isPermaLink="true">https://rosemary1812.github.io/blog/learn-claude-code-notes</guid><description>Claude Code 核心设计学习笔记，涵盖工具设计、TodoWrite、子 Agent 上下文隔离、Skill Loader、上下文压缩、任务系统等核心概念。</description><pubDate>Sun, 29 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文为作者学习Learn claude code过程中记下的笔记。
Learn Claude Code仓库：&lt;a href=&quot;https://github.com/shareAI-lab/learn-claude-code&quot;&gt;https://github.com/shareAI-lab/learn-claude-code&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;1. 工具的设计&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;只有 bash 时, 所有操作都走 shell。cat 截断不可预测, sed 遇到特殊字符就崩, 每次 bash 调用都是不受约束的安全面。专用工具 (read_file, write_file) 可以在工具层面做路径沙箱。&lt;/p&gt;
&lt;p&gt;这句话是在解释 &lt;strong&gt;为什么需要专门的工具&lt;/strong&gt;，而不是什么都用 bash（shell命令）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.1 问题：只用 Bash 的缺点&lt;/h3&gt;
&lt;p&gt;假设只有 bash 工具，LLM 想读文件时会这样：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;cat myfile.txt           # 问题1: 文件大时输出超长
sed -i &apos;s/old/new/g&apos; f   # 问题2: 特殊字符会破坏命令 (如 `&amp;#x26;`, `/`, `$`)
rm important.txt         # 问题3: bash 就是个安全漏洞，任何危险命令都能执行
ls / | some_command      # 问题4: 可以访问任意目录
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;| 缺点         | 例子                | 问题               |
| ---------- | ----------------- | ---------------- |
| &lt;strong&gt;输出不可控&lt;/strong&gt;  | &lt;code&gt;cat&lt;/code&gt; 大文件         | 可能刷爆终端           |
| &lt;strong&gt;特殊字符崩溃&lt;/strong&gt; | &lt;code&gt;sed $var&lt;/code&gt;        | &lt;code&gt;$&lt;/code&gt; 被 shell 解析出错 |
| &lt;strong&gt;不安全&lt;/strong&gt;    | &lt;code&gt;rm -rf /&lt;/code&gt;        | 没有限制，LLM 可能误操作   |
| &lt;strong&gt;路径逃逸&lt;/strong&gt;   | &lt;code&gt;cat /etc/passwd&lt;/code&gt; | 可以访问工作目录外的文件     |&lt;/p&gt;
&lt;h3&gt;1.2 解决方案：专用工具 + 路径沙箱&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def safe_path(p: str) -&gt; Path:
    path = (WORKDIR / p).resolve()           # 把相对路径转为绝对路径
    if not path.is_relative_to(WORKDIR):      # 检查是否在允许范围内
        raise ValueError(f&quot;Path escapes workspace: {p}&quot;)
    return path

def run_read(path: str, limit: int = None) -&gt; str:
    text = safe_path(path).read_text()        # 只能在 WORKDIR 内读取
    # ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样 &lt;strong&gt;即使 LLM 被诱导&lt;/strong&gt; 执行 &lt;code&gt;read_file(path=&quot;/etc/passwd&quot;)&lt;/code&gt;，也会被 &lt;code&gt;safe_path&lt;/code&gt; 拦截，因为 &lt;code&gt;/etc/passwd&lt;/code&gt; 不在 &lt;code&gt;WORKDIR&lt;/code&gt; 内。&lt;/p&gt;
&lt;h3&gt;1.3 总结&lt;/h3&gt;
&lt;p&gt;| 方案          | 优点          | 缺点            |
| ----------- | ----------- | ------------- |
| &lt;strong&gt;只用 bash&lt;/strong&gt; | 灵活强大        | 危险、不可预测、路径不可控 |
| &lt;strong&gt;专用工具&lt;/strong&gt;    | 安全、可控、可限制范围 | 需要事先定义好要暴露的功能 |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心思想&lt;/strong&gt;：给 LLM 的能力要&quot;最小化原则&quot;——只给它完成目标需要的工具，而不是把所有权限都放开。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. ToDoWrite&lt;/h2&gt;
&lt;h3&gt;2.1 问题&lt;/h3&gt;
&lt;p&gt;多步任务中, 模型会丢失进度 -- 重复做过的事、跳步、跑偏。对话越长越严重: 工具结果不断填满上下文, 系统提示的影响力逐渐被稀释。一个 10 步重构可能做完 1-3 步就开始即兴发挥, 因为 4-10 步已经被挤出注意力了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;通过 TodoWrite 让计划可见&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们不让模型在思维链中默默规划，而是强制通过 TodoWrite 工具将计划外化。每个计划项都有可追踪的状态（pending、in_progress、completed）。这有三个好处：(1) 用户可以在执行前看到 agent 打算做什么；(2) 开发者可以通过检查计划状态来调试 agent 行为；(3) agent 自身可以在后续轮次中引用计划，即使早期上下文已经滚出窗口。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;同一时间只允许一个任务进行中&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TodoWrite 工具强制要求任何时候最多只能有一个任务处于 in_progress 状态。如果模型想开始第二个任务，必须先完成或放弃当前任务。这个约束防止了一种隐蔽的失败模式：试图通过交替处理多个项目来&apos;多任务&apos;的模型，往往会丢失状态并产出半成品。顺序执行的专注度远高于并行切换&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 子 Agent 上下文隔离&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&quot;大任务拆小, 每个小任务干净的上下文&quot;&lt;/em&gt; -- 子agent用独立 messages[], 不污染主对话&lt;/p&gt;
&lt;p&gt;agent工作越久, messages 数组越胖。每次读文件、跑命令的输出都永久留在上下文里。&quot;这个项目用什么测试框架?&quot; 可能要读 5 个文件, 但父agent只需要一个词: &quot;pytest。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;父agent有一个 &lt;code&gt;task&lt;/code&gt; 工具。子agent拥有除 &lt;code&gt;task&lt;/code&gt; 外的所有基础工具 (禁止递归生成)。&lt;/li&gt;
&lt;li&gt;子agent以 &lt;code&gt;messages=[]&lt;/code&gt; 启动, 运行自己的循环。只有最终文本返回给父agent。&lt;/li&gt;
&lt;li&gt;子agent可能跑了 30+ 次工具调用, 但整个消息历史直接丢弃。父agent收到的只是一段摘要文本, 作为普通 &lt;code&gt;tool_result&lt;/code&gt; 返回。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;4. Skill Loader&lt;/h2&gt;
&lt;p&gt;这是 &lt;strong&gt;两层 Skill 注入&lt;/strong&gt; 的设计。让我逐步解释：&lt;/p&gt;
&lt;h3&gt;4.1 核心设计思想&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;不要把所有 skill 内容都塞进 system prompt（太长了）
只在 system prompt 放 skill 名称列表（Layer 1）
等 LLM 需要时，再通过 tool_result 把完整内容注入给 LLM（Layer 2）
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 Layer 1：System Prompt（只放元数据）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;SYSTEM = f&quot;&quot;&quot;You are a coding agent at {WORKDIR}.
Skills available:
{SKILL_LOADER.get_descriptions()}&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出长这样：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;You are a coding agent at C:\Users\wsmdm\Desktop\learn-claude-code.
Skills available:
  - pdf: Process PDF files
  - code-review: Review code changes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;只占几十个 token&lt;/strong&gt;，告诉 LLM&quot;有哪些技能可以用&quot;。&lt;/p&gt;
&lt;h3&gt;4.3 Layer 2：按需加载（完整内容）&lt;/h3&gt;
&lt;p&gt;当 LLM 调用 &lt;code&gt;load_skill(&quot;pdf&quot;)&lt;/code&gt; 时：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&quot;load_skill&quot;: lambda **kw: SKILL_LOADER.get_content(kw[&quot;name&quot;]),
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回的是 &lt;strong&gt;完整的 skill 文档&lt;/strong&gt;，被包装成 &lt;code&gt;&amp;#x3C;skill&gt;&lt;/code&gt; 标签：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;#x3C;skill name=&quot;pdf&quot;&gt;
## PDF Processing

1. Use pymupdf to read PDF
2. Extract text with...
3. ...
&amp;#x3C;/skill&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这通过 &lt;code&gt;tool_result&lt;/code&gt; 机制注入：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;results.append({&quot;type&quot;: &quot;tool_result&quot;, &quot;tool_use_id&quot;: block.id, &quot;content&quot;: str(output)})
messages.append({&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: results})
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.4 为什么这样设计？&lt;/h3&gt;
&lt;p&gt;| 问题             | System Prompt 全塞进去 | 按需加载（两层） |
| -------------- | ------------------ | -------- |
| token 消耗       | 巨大（10个skill可能几万元）  | 极低       |
| LLM 能用到的 skill | 全部（但可能上下文不够）       | 只有用到的那些  |
| 灵活性            | 差                  | 好        |&lt;/p&gt;
&lt;h3&gt;4.5 流程图&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;LLM 说：&quot;我要处理 PDF&quot;
         ↓
调用 load_skill(name=&quot;pdf&quot;)
         ↓
返回 &amp;#x3C;skill&gt;pdf 完整文档&amp;#x3C;/skill&gt; 作为 tool_result
         ↓
这条 tool_result 附加到 messages 里发回给 LLM
         ↓
LLM 现在&quot;看到了&quot;完整的 PDF 处理指南
         ↓
继续执行任务
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;简单说&lt;/strong&gt;：System Prompt 只告诉你&quot;有哪些工具箱&quot;，等你真的要开某个工具时，才把工具箱里的说明书给你看。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 上下文压缩&lt;/h2&gt;
&lt;h3&gt;5.1 三层压缩策略&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;每轮运行的微压缩&lt;/strong&gt;：几乎零成本：它截断旧消息中的 tool_result 块，去除不再需要的冗长命令输出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;token 数超过阈值时触发的自动压缩&lt;/strong&gt;：调用 LLM 生成对话摘要，代价高但能大幅缩减上下文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用户触发的手动压缩&lt;/strong&gt;：用于明确的&apos;重新开始&apos;场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;分层意味着低成本操作持续运行（保持上下文整洁），而高成本操作很少触发（仅在真正需要时）。&lt;/p&gt;
&lt;h3&gt;5.2 摘要替换全部消息，而非保留部分历史&lt;/h3&gt;
&lt;p&gt;自动压缩触发时，生成摘要并==替换全部消息历史==，不会在摘要旁保留最近的 N 条消息。&lt;/p&gt;
&lt;p&gt;这避免了一个微妙的连贯性问题：如果同时保留近期消息和旧消息的摘要，模型会看到重叠内容的两种表示。摘要可能说&apos;我们决定使用方案 X&apos;，而近期消息仍在展示讨论过程，产生矛盾信号。&lt;/p&gt;
&lt;p&gt;干净的摘要是一个连贯的单一叙述。&lt;/p&gt;
&lt;h3&gt;5.3 完整对话以 JSONL 格式归档到磁盘&lt;/h3&gt;
&lt;p&gt;完整的未压缩对话仍会追加到磁盘上的 JSONL 文件中。每条消息、每次工具调用、每个结果都不会丢失。&lt;/p&gt;
&lt;p&gt;压缩对内存上下文是有损操作，但对永久记录是无损的。事后分析（调试 agent 行为、计算 token 用量、提取训练数据）始终可以基于完整记录进行。JSONL 格式仅追加写入，对并发写入安全，易于流式处理。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6. 任务系统&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&quot;大目标要拆成小任务, 排好序, 记在磁盘上&quot;&lt;/em&gt; -- 文件持久化的任务图, 为多 agent 协作打基础。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;写入磁盘，建立依赖关系，实现任务持久化&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;s03 的 TodoManager 问题：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;todos = [
    {&quot;content&quot;: &quot;任务 A&quot;},
    {&quot;content&quot;: &quot;任务 B&quot;},
    {&quot;content&quot;: &quot;任务 C&quot;},
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.1 只有内容 + 是否完成，没有结构&lt;/h3&gt;
&lt;p&gt;真实目标的样子：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    A[A 完成] --&gt; B
    A --&gt; C
    B &amp;#x26; C --&gt; D &amp;#x26; E
    D &amp;#x26; E --&gt; F
    style A fill:#90EE90
    style B fill:#fff
    style C fill:#fff
    style D fill:#fff
    style E fill:#fff
    style F fill:#fff
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;A (完成) → B 和 C 可以并行&lt;/li&gt;
&lt;li&gt;D 和 E 都完成后才能做 F&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;为什么&quot;agent分不清&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不知道什么能做&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;Agent: &quot;还有啥任务？&quot;
系统:  [A, B, C, D, E, F]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Agent 不知道 B 被 A 卡住，也不知道 C 和 D 可以同时跑 → 只能瞎猜或者一股脑全做&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;不知道什么被卡住
A 没做完时，B 实际上&quot;被阻塞&quot;了,但清单里 B 就是一条记录，状态还是 &quot;todo&quot;,Agent 以为能做，其实做了也白做（等 A 成果）&lt;/li&gt;
&lt;li&gt;不能表达并行&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;C 和 D 独立，没有任何依赖&lt;/li&gt;
&lt;li&gt;但清单里跟 B 排在一起，看不出谁和谁相关&lt;/li&gt;
&lt;li&gt;→ Agent 可能串行做 C、D，白白浪费时间&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;上下文压缩就丢了&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;6.2 TaskManager: JSON 文件持久化&lt;/h3&gt;
&lt;h4&gt;S03: 内存中的列表&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;self.todos = []
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;S06 的 micro_compact 会压缩消息
如果任务状态只存在于 Messages 列表里，压缩后任务清单就消失了！&lt;/p&gt;
&lt;h4&gt;S07 的改进&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;{
    &quot;id&quot;: 1, &quot;subject&quot;: &quot;A&quot;, &quot;status&quot;: &quot;completed&quot;, &quot;blockedBy&quot;: [], &quot;blocks&quot;: [2, 3]
}
{
    &quot;id&quot;: 2, &quot;subject&quot;: &quot;B&quot;, &quot;status&quot;: &quot;pending&quot;, &quot;blockedBy&quot;: [1], &quot;blocks&quot;: [4]
}
{
    &quot;id&quot;: 3, &quot;subject&quot;: &quot;C&quot;, &quot;status&quot;: &quot;pending&quot;, &quot;blockedBy&quot;: [1], &quot;blocks&quot;: [4]
}
{
    &quot;id&quot;: 4, &quot;subject&quot;: &quot;D&quot;, &quot;status&quot;: &quot;pending&quot;, &quot;blockedBy&quot;: [2, 3], &quot;blocks&quot;: []
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;文件持久化: .tasks/task_1.json 在磁盘上，s06 压缩不影响&lt;/li&gt;
&lt;li&gt;blockedBy 字段: 显式表达&quot;被谁阻塞&quot;&lt;/li&gt;
&lt;li&gt;blocks 字段: 双向维护，D 知道自己在等谁&lt;/li&gt;
&lt;li&gt;scan_unclaimed_tasks(): 能筛选出 pending + 无 owner + 无 blockedBy 的任务&lt;/li&gt;
&lt;li&gt;并行检测: 多个任务同时 blockedBy: [] → 可以并行&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.6 核心区别&lt;/h3&gt;
&lt;p&gt;| 维度    | s03 扁平清单      | s07+ 有向图                       |
| ----- | ------------- | ------------------------------ |
| 依赖关系  | 无             | blockedBy/blocks               |
| 状态    | done/not done | pending/in_progress/completed |
| 持久化   | 内存            | JSON 文件                        |
| 可执行判断 | 不知道           | blockedBy 为空才能做                |
| 并行识别  | 不知道           | 无依赖的任务可并行                      |&lt;/p&gt;
&lt;h3&gt;6.7 后台任务&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&quot;慢操作丢后台, agent 继续想下一步&quot;&lt;/em&gt; -- 后台线程跑命令, 完成后注入通知。&lt;/p&gt;
&lt;p&gt;启动后台任务不等待，任务完成后放队列，LLM调用前取走结果，队列保证不重复通知&lt;/p&gt;
&lt;h3&gt;6.8 Agent Team&lt;/h3&gt;
&lt;h4&gt;6.8.1 实现架构&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant User as 用户
    participant Agent as 主线程 (Agent)
    participant BG as 后台线程
    participant LLM as LLM API

    Note over Agent: agent_loop()
    Agent-&gt;&gt;Agent: drain_notifications()
    Note over Agent: 从通知队列取结果，注入到 messages

    Agent-&gt;&gt;LLM: client.messages.create()
    LLM--&gt;&gt;Agent: response

    loop for block in response.content
        Agent-&gt;&gt;BG: BG.run(command)
        BG--&gt;&gt;Agent: 立即返回 (启动线程)
    end

    Note over BG: subprocess.run(command)
    Note over BG: 命令执行中... (30分钟)
    BG-&gt;&gt;BG: 执行完毕/超时/出错
    BG-&gt;&gt;BG: with lock: queue.append(结果)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;6.8.2 关键组件&lt;/h4&gt;
&lt;h5&gt;6.8.2.1 守护线程 (daemon=True)&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;thread = threading.Thread(
    target=self._execute,      # 线程执行的函数
    args=(task_id, command),   # 函数参数
    daemon=True                 # 守护模式
)
thread.start()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;守护线程 = 主进程退出时会被强制终止的线程&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant User as 用户
    participant Main as 主进程
    participant BG as 守护线程

    User-&gt;&gt;Main: quit
    Main-&gt;&gt;Main: 退出
    Note over Main: 守护线程被杀死 (不管任务跑没跑完)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么用守护线程？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户 quit 后，不需要后台任务结果了&lt;/li&gt;
&lt;li&gt;避免主进程卡住等慢任务&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;6.8.2.2 线程锁 (threading.Lock)&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;self._lock = threading.Lock()

with self._lock:
    self._notification_queue.append({…})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么需要锁？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;时间线&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant Agent as Agent 线程
    participant BG as 后台线程

    Note over Agent: drain() 读队列 (还没数据)
    BG-&gt;&gt;BG: 命令执行完，写队列
    BG-&gt;&gt;BG: with lock: queue.append(结果)
    Agent-&gt;&gt;Agent: 看到空队列，返回 []
    Note over Agent: 结果丢了，没人拿到
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加锁保证：读写队列不会同时发生。&lt;/p&gt;
&lt;h5&gt;6.8.2.3 通知队列&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;_notification_queue = []
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后台线程执行完，写入：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with self._lock:
    _notification_queue.append({
        &quot;task_id&quot;: &quot;abc&quot;,
        &quot;status&quot;: &quot;completed&quot;,
        &quot;result&quot;: &quot;…&quot;
    })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agent 线程取出：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with self._lock:
    notifs = list(_notification_queue)
    _notification_queue.clear()  # 清空
return notifs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么要清空？&lt;/p&gt;
&lt;p&gt;第1轮: 队列 = [结果A]
drain → notifs = [结果A]，队列 = []&lt;/p&gt;
&lt;p&gt;第2轮: 队列 = []
drain → notifs = []，队列 = []&lt;/p&gt;
&lt;p&gt;如果不清空:
第2轮: 队列 = [结果A]
drain → notifs = [结果A]  ← 重复通知，LLM 会困惑&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;6.8.3 完整流程&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;用户: &quot;编译项目&quot;

Agent:
    │ LLM 调用
    ├─→ 返回 background_run(&quot;make&quot;)
    │
    BG.run(&quot;make&quot;)
        │
        ├── task_id = &quot;abc123&quot;
        ├── 创建守护线程
        ├── thread.start()  ← 立即返回
        └── 返回 &quot;后台任务 abc123 已启动&quot;

Agent:
    │ 继续工作…
    │
    │ (编译在后台跑了30分钟)

30分钟后:
Agent:
    │ LLM 调用前
    │  ↓
    drain_notifications()
        │
        ├── 取走 [结果]
        └── 队列清空
    │
    ├─→ 注入 &quot;&amp;#x3C;background-results&gt;…编译完成…&amp;#x3C;/background-results&gt;&quot;
    │
    │ LLM 调用
    ├─→ LLM 看到 &quot;编译完成了&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;6.8.3 完整流程&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant User as 用户
    participant Agent as Agent
    participant BG as 后台线程

    User-&gt;&gt;Agent: &quot;编译项目&quot;

    Agent-&gt;&gt;Agent: LLM 调用
    Agent--&gt;&gt;Agent: 返回 background_run(&quot;make&quot;)

    Agent-&gt;&gt;BG: BG.run(&quot;make&quot;)
    Note over BG: 创建守护线程，返回 task_id
    BG--&gt;&gt;Agent: &quot;后台任务 abc123 已启动&quot;
    Agent-&gt;&gt;Agent: 继续工作...

    Note over BG: 命令执行中... (30分钟)

    BG-&gt;&gt;BG: 执行完成，写入队列

    Agent-&gt;&gt;Agent: LLM 调用前 drain_notifications()
    Note over Agent: 取走结果，队列清空
    Agent-&gt;&gt;Agent: 注入 &quot;&amp;#x3C;background-results&gt;编译完成&amp;#x3C;/background-results&gt;&quot;
    Agent-&gt;&gt;LLM: LLM 调用
    Agent--&gt;&gt;User: &quot;编译完成了&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;7. Agent Teams&lt;/h2&gt;
&lt;h3&gt;7.1 Teammates + Mailboxes&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&quot;任务太大一个人干不完, 要能分给队友&quot;&lt;/em&gt; -- 持久化队友 + JSONL 邮箱。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Harness 层&lt;/strong&gt;: 团队邮箱 -- 多个模型, 通过文件协调&lt;/p&gt;
&lt;p&gt;子agent (s04) 是一次性的: 生成、干活、返回摘要、消亡。没有身份, 没有跨调用的记忆。后台任务 (s08) 能跑 shell 命令, 但做不了 LLM 引导的决策。&lt;/p&gt;
&lt;p&gt;真正的团队协作需要三样东西:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(1) 能跨多轮对话存活的持久agent,&lt;/li&gt;
&lt;li&gt;(2) 身份和生命周期管理&lt;/li&gt;
&lt;li&gt;(3) agent之间的通信通道。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;s09_agent_teams.py - Agent 团队

持久化命名 Agent，基于文件的 JSONL 收件箱。

每个队友在独立线程中运行自己的 Agent 循环。通过仅追加的收件箱通信。

    子 Agent (s04):  spawn -&gt; execute -&gt; return summary -&gt; destroyed

    队友 (s09):      spawn -&gt; work -&gt; idle -&gt; work -&gt; … -&gt; shutdown

    .team/config.json                   .team/inbox/

    +----------------------------+      +------------------+
    | {&quot;team_name&quot;: &quot;default&quot;,  |      | alice.jsonl      |
    |  &quot;members&quot;: [              |      | bob.jsonl        |
    |    {&quot;name&quot;:&quot;alice&quot;,        |      | lead.jsonl       |
    |     &quot;role&quot;:&quot;coder&quot;,        |      +------------------+
    |     &quot;status&quot;:&quot;idle&quot;}       |
    |  ]}                        |      send_message(&quot;alice&quot;, &quot;fix bug&quot;):
    +----------------------------+         open(&quot;alice.jsonl&quot;, &quot;a&quot;).write(msg)

                                      read_inbox(&quot;alice&quot;):
    spawn_teammate(&quot;alice&quot;,&quot;coder&quot;,…)    msgs = [json.loads(l) for l in …]
         |                                open(&quot;alice.jsonl&quot;, &quot;w&quot;).close()
         v                                return msgs  # drain

    Thread: alice              Thread: bob

    +------------------+       +------------------+
    | agent_loop       |       | agent_loop       |
    | status: working  |       | status: idle     |
    | … runs tools     |       | … waits …        |
    | status -&gt; idle   |       |                  |
    +------------------+       +------------------+

    5 种消息类型 (全部声明，此处未全部处理):

    +-------------------------+-----------------------------------+
    | message                 | 普通文本消息                       |
    | broadcast               | 发送给所有队友                     |
    | shutdown_request        | 请求优雅关闭 (s10)                |
    | shutdown_response       | 批准/拒绝关闭 (s10)                |
    | plan_approval_response  | 批准/拒绝计划 (s10)               |
    +-------------------------+-----------------------------------+

核心思想: &quot;可以相互对话的队友。&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.2 设计决策&lt;/h3&gt;
&lt;h4&gt;7.2.1 与subagent的区别&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;在 s04 中，子agent是临时的：创建、执行一个任务、返回结果、销毁。它们的知识随之消亡。&lt;/li&gt;
&lt;li&gt;在 s09 中，队友是具有身份（名称、角色）和配置文件的持久化线程。队友可以完成任务 A，然后被分配任务 B，并携带之前学到的所有知识。持久化队友积累项目知识，理解已建立的模式，不需要为每个任务重新阅读相同的文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;我理解的teamate就是状态不隔离，持久化，可互相通信的subagent&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;7.2.2 团队配置持久化&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;团队结构（成员名称、角色、agent ID）存储在 JSON 配置文件中，而非任何 agent 的内存中。&lt;/li&gt;
&lt;li&gt;任何 agent 都可以通过读取配置文件发现队友——无需发现服务或共享内存。如果 agent 崩溃并重启，它读取配置即可知道团队中还有谁。&lt;/li&gt;
&lt;li&gt;这与 s07 的理念一致：文件系统就是协调层。配置文件人类可读，便于手动添加或移除团队成员、调试团队配置问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;7.2.3 工具分发&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;团队组长获得 ALL_TOOLS（包括 spawn、send、read_inbox 等），而队友获得 TEAMMATE_TOOLS（专注于任务执行的精简工具集）。&lt;/li&gt;
&lt;li&gt;队友专注于做事（编码、测试、研究）&lt;/li&gt;
&lt;li&gt;组长专注于协调（创建任务、分配工作、管理沟通）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;8. team_protocols&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&quot;队友之间要有统一的沟通规矩&quot;&lt;/em&gt; -- 一个 request-response 模式驱动所有协商。&lt;/p&gt;
&lt;p&gt;基于 s09 的团队消息系统，加了 Shutdown 协议 和 Plan Approval 协议，两个协议都通过 request_id 关联请求和响应。&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;8.1 核心：request_id 关联模式&lt;/h3&gt;
&lt;p&gt;发请求时生成 ID → 响应时带上同样 ID → 精准匹配&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant Lead
    participant Alice

    Lead-&gt;&gt;Alice: req_id = &quot;abc123&quot;
    Note over Alice: 处理中…
    Alice--&gt;&gt;Lead: 带上 &quot;abc123&quot; 响应
    Note over Lead: 收到响应，查 &quot;abc123&quot;，知道是谁在回应
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;8.2 协议一：Shutdown 关闭协议&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;目的&lt;/strong&gt;：优雅关闭队友，而不是强制杀死线程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;流程&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Lead 想关闭 alice&lt;/li&gt;
&lt;li&gt;生成 req_id = &quot;abc123&quot;&lt;/li&gt;
&lt;li&gt;把 req_id 存到 tracker&lt;/li&gt;
&lt;li&gt;发送 shutdown_request 给 alice&lt;/li&gt;
&lt;li&gt;alice 收到，决定批准还是拒绝&lt;/li&gt;
&lt;li&gt;发送 shutdown_response 带上 req_id&lt;/li&gt;
&lt;li&gt;Lead 更新 tracker 状态&lt;/li&gt;
&lt;li&gt;alice 线程退出或继续工作&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;状态机&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;pending → approved (线程退出)
        → rejected (继续工作)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;8.3 协议二：Plan Approval 计划审批协议&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;目的&lt;/strong&gt;：队友做重大操作之前，必须获得 Lead 批准。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;流程&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;alice 要做大事 (比如重构整个模块)&lt;/li&gt;
&lt;li&gt;生成 req_id = &quot;xyz789&quot;&lt;/li&gt;
&lt;li&gt;把计划和 req_id 发给 Lead&lt;/li&gt;
&lt;li&gt;Lead 看计划，决定批准还是拒绝&lt;/li&gt;
&lt;li&gt;alice 收到响应
&lt;ul&gt;
&lt;li&gt;approve=true → 开始执行&lt;/li&gt;
&lt;li&gt;approve=false → 修改计划，重新提交&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;8.4 图解&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant Lead
    participant Alice

    Note over Lead,Alice: request_id 关联请求和响应

    rect rgb(200, 220, 255)
        Note over Lead,Alice: Shutdown 协议
        Lead-&gt;&gt;Alice: shutdown_request + req_id
        Alice--&gt;&gt;Lead: shutdown_response + req_id
        Note over Lead: 线程退出
    end

    rect rgb(220, 255, 220)
        Note over Lead,Alice: Plan Approval 协议
        Alice-&gt;&gt;Lead: plan_approval + req_id + 计划内容
        Note over Lead: 看到计划，决定
        Lead--&gt;&gt;Alice: plan_approval_response + req_id + approve?
        Note over Alice: 执行/修改计划
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;8.5 设计决策&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;JSONL 收件箱文件而非共享内存&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;收发信息即维护JSON，只加不减。保证并发写入时的安全，不破坏数据；如果写入时崩溃仅留下不完整数据，读取者可以跳过。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;五种消息类型覆盖所有协调模式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;消息系统恰好支持五种类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(1) message 用于两个 agent 间的点对点通信；&lt;/li&gt;
&lt;li&gt;(2) broadcast 用于全团队公告；&lt;/li&gt;
&lt;li&gt;(3) shutdown_request 用于优雅终止；&lt;/li&gt;
&lt;li&gt;(4) shutdown_response 用于确认终止；&lt;/li&gt;
&lt;li&gt;(5) plan_approval_response 用于组长批准或拒绝队友的计划。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这五种类型映射到基本协调模式：直接通信、广播、生命周期管理和审批流程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;每次 LLM 调用前检查收件箱&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;队友在每次 agent 循环迭代的顶部、调用 LLM API 之前检查收件箱文件。这确保了对传入消息的最大响应性：响应快，成本低（读取小文件消耗token少），可以影响下次调用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;9. 自主Agent&lt;/h2&gt;
&lt;h3&gt;9.1 Scan Board, Claim Tasks&lt;/h3&gt;
&lt;h3&gt;9.2 设计决策&lt;/h3&gt;
&lt;h4&gt;9.2.1 轮询未认领任务而非事件驱动通知&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;自主队友每隔约 1 秒轮询共享任务板任务。&lt;/li&gt;
&lt;li&gt;轮询从根本上比发布/订阅更简单：
&lt;ul&gt;
&lt;li&gt;没有订阅管理&lt;/li&gt;
&lt;li&gt;没有事件路由&lt;/li&gt;
&lt;li&gt;没有事件丢失的 bug&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在基于文件的持久化下，轮询保证响应速度，降低文件系统开销。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;9.2.2 空闲 60 秒后自动终止&lt;/h4&gt;
&lt;p&gt;不会因为任务完成到新任务创建之间的短暂间隔而导致过早关闭；又足够短，不会让闲置队友浪费资源。&lt;/p&gt;
&lt;h4&gt;9.2.3 上下文压缩后重新注入队友身份&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;自动压缩对话时，生成的摘要会丢失关键元数据：队友的名称、所属团队和 agent_id。没有这些信息，队友无法认领任务（任务按名称归属）、无法检查收件箱（收件箱文件以 agent_id 为键）、也无法在消息中表明身份。&lt;/li&gt;
&lt;li&gt;因此每次自动压缩后，系统会向对话中重新注入一个结构化的身份块：&apos;你是 [team] 团队的 [name]，你的 agent_id 是 [id]，你的收件箱在 [path]。&apos;这是队友在记忆丢失后保持功能所需的最小上下文。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;10. Worktree + 任务隔离&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;(codex的worktree应该也是这个思路)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;任务管目标, worktree 管目录, 按 ID 绑定&lt;/p&gt;
&lt;p&gt;agent已经能自主认领和完成任务。但所有任务共享一个目录。两个agent同时重构不同模块 -- A 改 &lt;code&gt;config.py&lt;/code&gt;, B 也改 &lt;code&gt;config.py&lt;/code&gt;, 未提交的改动互相污染, 谁也没法干净回滚。&lt;/p&gt;
&lt;p&gt;任务板管 &quot;做什么&quot; 但不管 &quot;在哪做&quot;。解法: 给每个任务一个独立的 git worktree 目录, 用任务 ID 把两边关联起来。&lt;/p&gt;
&lt;p&gt;用 git worktree 做目录级隔离，让多个任务真正并行执行而互不干扰。&lt;/p&gt;
&lt;h3&gt;10.1 场景&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;核心问题&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任务 A: 重构登录模块&lt;/li&gt;
&lt;li&gt;任务 B: 修复支付 bug&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果都在同一个目录改代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;A 在改 auth.py  ←──────────────┐
B 也在改 auth.py  ──────────────┼──→ 冲突！代码乱套
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：谁先提交谁覆盖，谁都可能被卡住等对方&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;解决方案：目录隔离&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;主仓库
├── auth.py (原始版本)
│
├── .worktrees/
│    ├── auth-refactor/     ← A 的工作目录 (wt/auth-refactor 分支)
│    │    └── auth.py        (重构版本)
│    │
│    └── payment-fix/        ← B 的工作目录 (wt/payment-fix 分支)
│         └── auth.py        (支付修复版本)

A 和 B 在各自目录改代码
互不干扰，可以同时工作
完成后可以选择：
  - keep: 保留这个 worktree，合并到主分支
  - remove: 删除 worktree，代码丢弃
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;10.2 三个核心组件&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TaskManager - 控制平面&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;任务 A (id=1): &quot;重构登录&quot; → worktree: &quot;auth-refactor&quot;
任务 B (id=2): &quot;修复支付&quot; → worktree: &quot;payment-fix&quot;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任务是&quot;想法&quot;，存在 .tasks/ 目录&lt;/li&gt;
&lt;li&gt;记录谁在做，状态是什么&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;WorktreeManager - 执行平面&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;创建 worktree → 绑定到某个任务
在 worktree 里执行命令
完成后 keep 或 remove&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;EventBus - 可观测性&lt;/strong&gt;
| 事件                     | 说明   |
| ---------------------- | ---- |
| worktree.create.before | 创建前  |
| worktree.create.after  | 创建后  |
| worktree.remove.before | 删除前  |
| worktree.remove.after  | 删除后  |
| task.completed         | 任务完成 |
用于追踪整个生命周期，出问题了能查日志&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;10.3 工作流程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;创建任务&lt;/strong&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;task_create(&quot;重构登录模块&quot;)
     ↓
任务写入 .tasks/task_1.json
status: pending
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创建 worktree 并绑定&lt;/strong&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;worktree_create(&quot;auth-refactor&quot;, task_id=1)
     ↓
git worktree add -b wt/auth-refactor .worktrees/auth-refactor
     ↓
任务状态: pending → in_progress
worktree 绑定到任务
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在 worktree 里工作&lt;/strong&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;worktree_run(&quot;auth-refactor&quot;, &quot;git status&quot;)
worktree_run(&quot;auth-refactor&quot;, &quot;python test.py&quot;)
     ↓
所有命令在隔离目录里执行
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完成后选择&lt;/strong&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;worktree_keep(&quot;auth-refactor&quot;)
    → 保留，继续在 auth-refactor 分支工作

worktree_remove(&quot;auth-refactor&quot;, complete_task=True)
    → 删除 worktree，任务标记完成
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;10.4 架构图&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TB
    subgraph 控制平面
        TM[TaskManager]
        TM --&gt;|&quot;管理&quot;| T1[task_1.json&amp;#x3C;br/&gt;subject: 重构&amp;#x3C;br/&gt;worktree: auth-refactor]
        TM --&gt;|&quot;管理&quot;| T2[task_2.json&amp;#x3C;br/&gt;subject: 支付&amp;#x3C;br/&gt;worktree: payment-fix]
    end

    TM --&gt;|&quot;绑定&quot;| WM

    subgraph 执行平面
        WM[WorktreeManager]
        WM --&gt;|&quot;创建&quot;| WT1[wt/auth-refactor/]
        WM --&gt;|&quot;创建&quot;| WT2[wt/payment-fix/]
    end

    subgraph 主仓库
        ROOT[auth.py 原始]
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;目录结构&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;.git/                           # 主仓库
├── auth.py                     # 原始文件
├── .worktrees/
│   ├── index.json             # 工作树索引
│   ├── events.jsonl           # 生命周期事件日志
│   ├── wt/
│   │   ├── auth-refactor/     # 任务 A 的 worktree (独立分支)
│   │   └── payment-fix/       # 任务 B 的 worktree (独立分支)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;这篇文章梳理了 Claude Code 的 10 个核心设计：&lt;/p&gt;
&lt;p&gt;| 章节 | 设计                | 核心价值          |
| -- | ----------------- | ------------- |
| 1  | 专用工具 + 路径沙箱       | 安全可控          |
| 2  | TodoWrite         | 计划可见、状态可追踪    |
| 3  | 子 agent 上下文隔离     | 避免污染、保持简洁     |
| 4  | Skill Loader 两层注入 | 按需加载、节省 token |
| 5  | 上下文压缩             | 平衡成本与历史       |
| 6  | 任务系统 + 后台任务       | 持久化、并行化       |
| 7  | Agent Teams       | 多 agent 协作    |
| 8  | Team Protocols    | 统一的沟通协议       |
| 9  | 自主 agent          | 自动认领任务        |
| 10 | Worktree 隔离       | 任务级目录隔离       |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心思想&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可控性&lt;/strong&gt;：最小权限原则，只暴露必要的工具&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持久化&lt;/strong&gt;：磁盘文件作为协调层，重启不丢失&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隔离性&lt;/strong&gt;：子 agent 独立上下文、任务独立目录&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;按需加载&lt;/strong&gt;：Skill 按需注入，控制 token 成本&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些设计的共同目标是让 agent 在长期、多任务、复杂场景下保持稳定、可控、可扩展。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>AI Agent 的三层架构：上下文工程、记忆系统与 MCP</title><link>https://rosemary1812.github.io/blog/ai-agent-architecture</link><guid isPermaLink="true">https://rosemary1812.github.io/blog/ai-agent-architecture</guid><description>从&quot;能对话&quot;到&quot;能干活&quot;，深入解析 AI Agent 的三大核心机制</description><pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;刚开始用 ChatGPT 的时候，很明显能感受到它的局限：每次新开对话就是一张白纸，之前聊过的东西全没了；问它今天发生了什么，给你说的是训练数据截止前的事；让它帮你订机票，它只能告诉你&quot;你可以去 xx 网站订&quot;。&lt;/p&gt;
&lt;p&gt;现在这些问题陆续被解决了——有的产品加了记忆功能，有的能联网搜索，有的能真的帮你完成操作。但这些能力并非凭空出现，而是背后有一套具体的工程机制在支撑。&lt;/p&gt;
&lt;p&gt;大语言模型本身是无状态的，没有记忆，不连网，也没法真的执行操作。那现在那些&quot;能干活&quot;的 AI Agent，是怎么突破这些限制的？&lt;/p&gt;
&lt;p&gt;答案其实可以拆成三层：&lt;strong&gt;上下文工程、记忆系统与 RAG、MCP 协议&lt;/strong&gt;。它们各自解决不同的问题，但在一个跑起来的 Agent 里是同时协作的。在这篇文章里我们将用一个具体的任务场景把它们串起来。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;先设定一个场景&lt;/h2&gt;
&lt;p&gt;假设有一个 Agent，任务是：读取本周的会议记录文件，结合项目历史背景，生成一份进度摘要，发邮件给团队。&lt;/p&gt;
&lt;p&gt;执行这个任务，Agent 面临三个本质上不同的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;当下&lt;/strong&gt;：该把什么信息放进模型的上下文窗口？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;历史&lt;/strong&gt;：项目背景、上周的结论——这些超出当前窗口的信息，怎么按需取用？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;行动&lt;/strong&gt;：读文件、发邮件，这些实际操作怎么执行？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三个问题，分别对应三层机制。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第一层：上下文工程&lt;/h2&gt;
&lt;h3&gt;上下文腐蚀&lt;/h3&gt;
&lt;p&gt;有一个大家在日常使用中可以明显感受到的现象：随着上下文里的内容越堆越多，模型从中提取信息的准确率反而会下降。研究里把这个叫做&lt;strong&gt;上下文腐蚀（Context Rot）&lt;/strong&gt;，几乎在所有主流模型上都能观察到。&lt;/p&gt;
&lt;p&gt;所以上下文工程的核心不是尽可能多地写入信息，而是在有限的窗口里放尽量高密度、高相关性的信息。&lt;/p&gt;
&lt;h3&gt;上下文的结构&lt;/h3&gt;
&lt;p&gt;工程化的上下文通常由三部分构成。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;系统提示&lt;/strong&gt;定义模型的角色和行为规则。两种常见的失败方式：写得太空泛，缺乏细节（&quot;你是一个有帮助的助手&quot;，没有任何具体信号），或者写得太臃肿，加入了太多细枝末节的内容（把所有边界情况全堆进去，需求一变全要改）。比较好的做法是分区组织：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&amp;#x3C;background&gt; 你是一个项目管理助手，负责软件开发团队的周报整理 &amp;#x3C;/background&gt;
&amp;#x3C;tools&gt; 你可以使用 read_file 读取文件，send_email 发送邮件 &amp;#x3C;/tools&gt;
&amp;#x3C;output_format&gt; 返回 Markdown 格式，包含：本周进展、阻塞项、下周计划 &amp;#x3C;/output_format&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;工具定义&lt;/strong&gt;告诉模型有哪些工具可用。工具集不是越大越好——如果两个工具的功能边界不清晰，模型选工具时就会出问题。维护一个精简的、每个工具职责单一的集合，稳定性往往更好。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;少样本示例（Few-shot）&lt;/strong&gt; 直接给几个输入输出的对照例子，比长篇指令更直接有效。挑典型的、有代表性的，不需要穷举所有情况。&lt;/p&gt;
&lt;h3&gt;长任务的上下文管理&lt;/h3&gt;
&lt;p&gt;单次任务相对来的比较易于处理，但如果 Agent 要执行一个持续很长时间的任务，上下文窗口迟早会满，这时候我们主要有三种应对方式：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;压缩整合（Compaction）&lt;/strong&gt;：接近上限时对历史进行总结，保留关键决策和未解决的问题，用摘要重启一个新窗口，继续执行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结构化笔记&lt;/strong&gt;：Agent 把关键信息主动写进外部文件，需要时再读取。这不是记忆系统，是对外部状态的主动管理。Hello-Agents 里有一个 &lt;code&gt;NoteTool&lt;/code&gt;，就是做这个的，用 Markdown + YAML 存状态，支持 Git 版本控制，人也可以直接读和编辑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;子代理架构&lt;/strong&gt;：主 Agent 负责规划，把子任务分给多个子 Agent，每个子 Agent 在干净的上下文窗口里独立工作，最后只把摘要返回主 Agent。这样复杂的上下文留在子 Agent 那边，主 Agent 的窗口保持干净。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第二层：记忆系统与 RAG&lt;/h2&gt;
&lt;h3&gt;Agent 的记忆&lt;/h3&gt;
&lt;p&gt;记忆系统要解决的问题是：哪些信息应该保留，保留多久，什么时候取出来用。&lt;/p&gt;
&lt;p&gt;按时间维度可以分三类。&lt;strong&gt;短期记忆&lt;/strong&gt;就是当前对话的上下文，直接存在模型的上下文窗口里，保证对话连贯。&lt;strong&gt;工作记忆&lt;/strong&gt;是任务执行过程中的临时缓冲，比如推理的中间步骤，任务结束后清空或选择性归档。&lt;strong&gt;长期记忆&lt;/strong&gt;跨越不同会话持久存储，用向量数据库实现——把历史信息转成向量，需要时通过相似度检索取回来。&lt;/p&gt;
&lt;p&gt;核心操作流程是四步：写入、存储、检索、反思。写入是把交互内容存进去；存储阶段会做压缩，要么总结成摘要，要么转成 Embedding 存入数据库；检索是新请求来了从记忆库里找最相关的片段注入上下文；反思是 Agent 审视过去的经验，从中提炼规律，这也是 Agent 能持续&quot;成长&quot;的关键。&lt;/p&gt;
&lt;h3&gt;RAG 所解决的问题&lt;/h3&gt;
&lt;p&gt;记忆系统管的是 Agent 从与用户交互中积累的信息。但还有另一类知识——公司文档、技术手册、最新资讯——这些不来自交互过程，而是外部知识库。这是 RAG（检索增强生成）要解决的问题。&lt;/p&gt;
&lt;p&gt;核心思路是：知识不需要内嵌在模型里，用的时候再查就行。文档切片 → 向量化 → 存入向量数据库，查询时检索相关片段，注入 Prompt，模型基于原文生成答案。这样知识库可以随时更新，也不依赖模型的训练数据截止时间。&lt;/p&gt;
&lt;h3&gt;检索时真正麻烦的问题&lt;/h3&gt;
&lt;p&gt;用户的提问和文档里的表述，往往不在同一个语义空间。比如用户问&quot;怎么修 Python 内存泄漏&quot;，文档里写的是&quot;tracemalloc 可定位泄漏位置&quot;，直接向量匹配很难命中。&lt;/p&gt;
&lt;p&gt;两种进阶策略：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多查询扩展（MQE）&lt;/strong&gt;：对同一个问题自动生成多种不同表述，并行检索，合并结果。用词多样性导致的漏检问题，基本上靠这个解决。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;假设文档嵌入（HyDE）&lt;/strong&gt;：让模型先生成一个假设性的答案，再用这个假设答案去检索真实文档：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;用户问题：&quot;如何修复 Python 中的内存泄漏？&quot;
         ↓ LLM 生成假设回答
&quot;通常需要先使用 tracemalloc 或 memory_profiler 定位泄漏位置，
 然后检查循环引用...&quot;
         ↓ 用假设回答做向量检索
命中：《Python 内存管理最佳实践》《Python 性能调优指南》
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假设答案和真实文档都是陈述性表达，语义距离比原始问句更近。内容不需要完全正确，只要领域术语和表述风格接近，检索精度就会有明显提升。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;第三层：MCP——标准化连接外部世界&lt;/h2&gt;
&lt;h3&gt;没有标准协议时的问题&lt;/h3&gt;
&lt;p&gt;前两层处理的都是&quot;思考&quot;的问题。现在 Agent 要真正行动了——读文件、发邮件、查数据库。&lt;/p&gt;
&lt;p&gt;如果为每个服务单独写适配器，接入 GitHub 一套代码，接入 Slack 一套，接入数据库又一套，某个 API 改了接口就要到处改。随着接入服务增多，维护成本会越来越高。&lt;/p&gt;
&lt;p&gt;MCP（Model Context Protocol）定义了一套 Agent 与外部工具通信的标准协议，让不同的工具和不同的 Agent 框架都能通过同一套接口交互。&lt;/p&gt;
&lt;h3&gt;三层架构&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;用户 ↔ Host（Claude Desktop / 你的 Agent 应用）
         ↕ MCP Client（内嵌在 Host 中）
         ↕ MCP Server（文件系统 / GitHub / 数据库 / 邮件...）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Host&lt;/strong&gt; 是用户交互的界面，管理对话流程；&lt;strong&gt;Client&lt;/strong&gt; 是协议层，负责连接 Server、发请求、收响应；&lt;strong&gt;Server&lt;/strong&gt; 是实际执行功能的服务。&lt;/p&gt;
&lt;p&gt;这个设计的关键是：开发者只需要写 Server，不用关心 Host 和 Client 的实现。用户不管用哪个支持 MCP 的客户端，都能用同一套工具。&lt;/p&gt;
&lt;h3&gt;模型怎么决定用哪个工具&lt;/h3&gt;
&lt;p&gt;MCP Client 连上 Server 后，先调用 &lt;code&gt;list_tools()&lt;/code&gt; 拿到所有工具的名称、描述和参数定义，然后把这个列表转成模型能读懂的格式，注入系统提示。模型根据用户问题和工具描述，自己决定要不要调用工具、调哪个。&lt;/p&gt;
&lt;p&gt;所以工具的描述质量直接影响它能不能被正确调用。一个描述含糊的工具，和没有工具差不多。&lt;/p&gt;
&lt;h3&gt;三种能力类型&lt;/h3&gt;
&lt;p&gt;| 能力 | 特点 | 例子 |
|------|------|------|
| Tools | 主动执行，有副作用 | 发邮件、写文件、调用 API |
| Resources | 只读，被动提供数据 | 读文件内容、查询数据库 |
| Prompts | 提供标准化指令片段 | 代码审查模板、报告格式 |&lt;/p&gt;
&lt;h3&gt;和 Function Calling 的关系&lt;/h3&gt;
&lt;p&gt;这两个概念经常被混淆，Function Calling 是模型层的能力，解决的是&quot;模型什么时候、怎么生成工具调用参数&quot;；MCP 是工程层的协议，解决的是&quot;工具和模型怎么标准化连接&quot;。两者不冲突，在同一个系统里协同工作。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;三层协同：回到最初的场景&lt;/h2&gt;
&lt;p&gt;把三层放在一起，&quot;整理会议记录发邮件&quot;这个任务的信息流大概是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;用户输入
     ↓
[上下文工程]
系统提示 + 工具定义 + 压缩后的历史摘要
     ↓
[记忆 &amp;#x26; RAG] ← 运行时动态注入
情景记忆（上周的讨论结论）
语义记忆（用户偏好、项目规则）
RAG 检索（相关文档片段）
     ↓
模型推理 → 决定调用工具
     ↓
[MCP]
通过 MCP Server 执行实际操作（读文件 / 发邮件）
     ↓
结果注入上下文 → 模型生成最终输出
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上下文工程管的是&quot;当下这一刻模型能看到什么&quot;，记忆与 RAG 负责&quot;历史信息和外部知识按需可达&quot;，MCP 负责&quot;实际执行操作&quot;。三层缺一不可，也正是这三层组合在一起，让 Agent 从&quot;能对话&quot;变成了&quot;能干活&quot;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;本文内容基于 &lt;code&gt;https://github.com/datawhalechina/Hello-Agents&lt;/code&gt; 开源项目的学习笔记整理。&lt;/em&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>分享一段 Prompt：如何基于 Nanobot 在面试中介绍 OpenClaw</title><link>https://rosemary1812.github.io/blog/nanobot-interview-prompt</link><guid isPermaLink="true">https://rosemary1812.github.io/blog/nanobot-interview-prompt</guid><description>面试场景下的 Prompt 设计思路，以及 Agent 类项目的源码阅读方法</description><pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、背景：nanobot 是什么，我为什么从它切入&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;OpenClaw&lt;/strong&gt; 是一个本地 AI 助手网关（local AI assistant gateway）——让 LLM 连接到 WhatsApp、Telegram、Discord 这类消息平台，赋予它工具调用能力，能在用户本机执行命令、读文件、跑脚本，7×24 运行，支持持久化 memory 和多用户 session 管理。代码量约 43 万行，52+ 模块，70+ 依赖。&lt;/p&gt;
&lt;p&gt;直接读 OpenClaw 有没有必要呢？有的。但是是不是非读 OpenClaw 不可呢？未必。&lt;/p&gt;
&lt;p&gt;我选择了 &lt;strong&gt;nanobot&lt;/strong&gt;：OpenClaw 的教学级简化复刻，约 4000 行 Python，比 OpenClaw 小 99%。它保留了 agent 的核心骨架——agent loop、context 构建、持久化 memory、模块化 tool 系统，以及消息平台接入——裁掉了 100+ skills、浏览器控制、语音、复杂权限系统等生产级功能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么从 nanobot 入手，而不是直接啃 OpenClaw？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;核心原因是：nanobot 的设计目标是「可读性优先」。当一个功能只有一种实现方式，没有 fallback、没有兼容层、没有 feature flag，你才能看清它的本质结构。我读 nanobot 的目标是理解 agent 的架构骨架，搞清楚核心机制是怎么运转的，而不是去对照 OpenClaw 的具体实现——在 AI 成为代码能力杠杆的时代，具体实现可以先放下，架构思想才是最值得提炼的东西。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;二、Prompt 设计思路 + Prompt 正文&lt;/h2&gt;
&lt;h3&gt;2.1 我是怎么设计这个 Prompt 的&lt;/h3&gt;
&lt;p&gt;整个过程大概是这样的：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：描述意图，让 Claude 先搜索背景&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我先用自然语言告诉 Claude 我的场景：面试中需要介绍一个我研读过的 agent 项目，面试官可能会追问架构细节、对比分析、改进思路。然后让它搜索 OpenClaw 和 nanobot 的基本信息，建立共同背景。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步：让它写初稿&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给一个宽松的结构要求，让 Claude 先跑一遍。为什么呢？我们对于「需要讲哪些部分」通常只有模糊概念，如果一边想一边写很低效，还容易遗漏。不如直接让 Claude 出一个完整的初稿，然后在它上面增删、纠偏、补深度——改比从零写快得多，也更全面。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步：逐段提出具体要求，迭代改进&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这一步是最关键的。我们需要站在面试官的角度给 AI 出有针对性的指令，而不是笼统地告诉它&quot;优化 XX 方面&quot;。我会这么要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对比分析段：补充对 Claude code 和 OpenClaw 的对比&lt;/li&gt;
&lt;li&gt;追问部分：预测面试官会根据你的回答继续追问什么——不一定和面试场景下的提问相近，但是基于面试官视角的提问能启发我们思考更多&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第四步：最终收口&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;整合所有迭代结果，加入角色设定、语气要求、输出约束，形成最终 Prompt。&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;2.2 Prompt 正文&lt;/h3&gt;
&lt;p&gt;以下是最终版本，附我的设计注释（&lt;code&gt;# →&lt;/code&gt; 开头）：&lt;/p&gt;
&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;你现在是我的面试备考教练。

请完整阅读当前项目（nanobot）的所有代码文件。
nanobot 是 OpenClaw 的极简复刻版（~4000 行 Python，比 OpenClaw 小 99%），
我通过研读 nanobot 来理解 OpenClaw 的架构思想。

面试场景：面试官问「介绍一下这个项目，以及你从中学到了什么」
请帮我生成一份口语化的面试回答稿 + 追问准备材料。
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;# → 开头的角色设定&lt;/strong&gt;：「面试备考教练」比「助手」更精准。它隐含了两个要求：输出要面向实战、要帮我预判面试官的思路，而不只是生成漂亮的文档。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;【你需要先理解的背景】

OpenClaw 是什么：
- 一个本地 AI 助手网关（local AI assistant gateway）
- 让 LLM 连接到 WhatsApp、Telegram、Discord 等消息平台
- 赋予 LLM 工具调用能力，能在用户本机执行命令、读文件、跑脚本
- 7x24 运行，持久化 memory，多用户 session 管理
- 代码量 ~43 万行，52+ 模块，70+ 依赖

nanobot 是什么：
- OpenClaw 的教学级简化复刻
- 保留核心骨架：消息接入 → agent 循环 → tool 调用 → 结果回写
- 裁掉了大量生产级功能，换来可读性和可审计性

Claude Code 是什么（对比参照）：
- Anthropic 出品的 coding agent，terminal-native
- 定位是开发工作流：读代码库、改文件、跑命令、集成 CI/CD
- client-server 架构，本地执行但调用 Anthropic 云端 API
- 有权限系统、context 自动压缩、MCP 集成、subagent 并行

⚠️ 重要：OpenClaw 和 Claude Code 不是同类竞品，
是两种不同的 agent 产品形态，面向完全不同的使用场景。
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;# → 背景补充&lt;/strong&gt;：给 Claude 一个「知识基准线」，避免它从网上检索到错误信息，同时明确 OpenClaw vs Claude Code 不是竞品对比，防止它在对比段落里走偏。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;【角色设定】
- 我研读过这个项目，对架构有自己的理解，正在向技术面试官介绍
- 语气：自信但不夸张，像真人在聊，不是念 PPT
- 中文回答，专有名词保留英文（tool call、MCP、session 等）
- 我对整体框架了解，部分细节不确定，
  适当使用「我当时的理解是 X，背后的考量是 Y」的句式
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;【回答正文结构】

1. 一句话定位
2. 我为什么读这个项目
3. 通过 nanobot 理解到的 OpenClaw 核心架构
   - 整体分层
   - 核心数据流
   - 最有价值的 2-3 个设计决策（从代码里找真实存在的，说清楚背后的考量）
4. OpenClaw vs Claude Code：两种 agent 形态的对比分析【重点段】
   a) 产品定位的本质差异
   b) 交互模型的差异（及架构影响）
   c) 执行环境与安全模型的差异
   d) Memory 与持久化的差异
   e) 我的技术选型思考（要体现自己的判断）
5. 如果让你改进或二开 OpenClaw，你会做什么？
   格式：发现了什么问题 → 我会怎么改 → 为什么这么改 → 预期提升
6. 源码阅读方法论 + 对 agent 设计的启发
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;# → 结构设计的逻辑&lt;/strong&gt;：面试回答的结构不是「从头到尾讲完」，而是「先给定位，再给证据，最后给自己的判断」。第 4 段（对比分析）是核心，考察的是技术视野而不只是对某个项目的了解。第 5 段（改进思路）考察的是「你能不能把理解转化为工程决策」。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;【追问准备材料】

预测面试官会根据你的介绍继续追问的 12 道题，分三组：

【A 组：架构理解类】（4题）
考察你真的读懂了 nanobot 的代码，而不只是会背概念

【B 组：对比分析类】（4题）
考察你的技术视野和独立判断能力

【C 组：延伸思考类】（4题）
考察你对 agent 这个方向的理解深度

每道题格式：
- 问题
- 考察点（面试官真正想听什么）
- 我应该怎么答（关键词 + 角度）
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;# → 追问材料的本质&lt;/strong&gt;：这里的核心不是「准备标准答案」，而是推演对话走向——你说了 A，面试官大概率会问 B。把这个推演做出来，延伸思考。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;【输出约束】
- 代码相关内容直接从 nanobot 代码事实出发，不要编造
- 对比分析段可以结合背景知识展开，但对比观点要有自己的立场
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;三、关于 Agent 类项目源码的阅读思路&lt;/h2&gt;
&lt;p&gt;我读 agent 类项目，用的不是「从入口文件开始逐层追」的方式，而是先让 AI 把项目消化成一份结构化文档，再自己去看核心代码。&lt;/p&gt;
&lt;p&gt;具体是三步：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：先把项目跑起来，体验用户视角&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在看任何一行代码之前，先把这个 Agent 部署起来用一用——发条消息、触发一个 tool、看看效果如何。这一步的目的是建立对一个项目感性上的认知。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果做得不好，那么你祛魅了（在 Fomo 情绪严重的时期这很有意义）你可以带着&quot;如果让我来设计，我会如何改进&quot;的思考走进读源码环节。&lt;/li&gt;
&lt;li&gt;如果这次体验让你眼前一亮，你可以思考，怎么做到这种效果？这个项目和以往读到的有什么不一样？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第二步：让 AI 生成 TELLME.md，建立整体认知&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;跑起来之后，我会给 Claude 一个 prompt，让它深度分析整个代码库，生成一份 TELLME.md。这份文档围绕四个方向建立结构化认知：&lt;strong&gt;技术栈全景&lt;/strong&gt;（语言 / 框架 / 工具库分层）、&lt;strong&gt;模块职责地图&lt;/strong&gt;（完整目录树 + 入口文件 + 各模块说明）、&lt;strong&gt;运行环境配置&lt;/strong&gt;（依赖安装 / 环境变量 / 启动命令）、&lt;strong&gt;Agent 技术体系&lt;/strong&gt;（LLM 调用方式 / memory 机制 / tool 注册调用链 / 数据流图）。&lt;/p&gt;
&lt;p&gt;生成出来之后，我会对着 TELLME.md 读一遍，知道整体骨架是什么样的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步：再去看核心代码&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有了整体认知之后，再去看具体模块才有意义——你知道自己在看什么、它在整个系统里处于什么位置、它和哪些模块有交互。这时候看代码是在「验证和深化理解」，而不是在「盲目扫描」。&lt;/p&gt;
&lt;p&gt;这个方法的好处是：TELLME.md 把代码库压缩成了一个可以快速遍历的指引，省去了大量「找入口、找依赖、找数据流」的时间成本，直接进入有效阅读。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;四、面向面试 Vs 面向读懂源码&lt;/h2&gt;
&lt;h3&gt;面向面试的 Prompt 设计&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;1. 明确场景边界
   → 面试题是什么？面试官是谁？考察什么能力层级？

2. 给定背景知识基准线
   → 把 Claude 不一定知道或可能搜到错误信息的内容直接喂给它

3. 设定「说话人」角色
   → 我是谁？我对这个项目的了解深度是什么？
     在我看来我应该怎么介绍，主要介绍什么，如何才能在展示自己对架构设计的理解的同时兼顾产品 sense？

4. 结构里加入「考官视角」
   → 不只是「我要说什么」，还要设计「考官想听什么」
   → 追问材料的核心是推演对话走向，给出提示回答方向的关键词，而不是准备标准答案

5. 迭代方式：逐段提具体要求，不要整体重写
   → 初稿出来后，找思考不够全面或方向偏了的段落，给具体的补充指令
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;面向读懂源码的 Prompt 设计&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;1. 先把项目跑起来，建立用户直觉
   → 在看代码之前，先体验一遍用户视角
   → 知道输入是什么、输出是什么、系统对外表现如何

2. 用 TELLME.md prompt 生成结构化认知地图
   → 让 AI 分析整个代码库，输出技术栈、模块职责、数据流图
   → 目标是快速建立整体认知，而不是从入口文件开始逐行扫描

3. 有了认知地图之后，再去看核心代码
   → 这时候看代码是「验证和深化理解」，不是「盲目扫描」
   → 你知道自己在看什么、它在整个系统里的位置是什么

4. 对看不懂的设计，构造反例来验证理解
   → 「如果 X 不这样设计，会有什么问题？」
   → 能回答这个问题，说明你真的理解了这个设计决策

5. 最后整理「我还不确定的问题清单」
   → 读完之后，把剩余不确定的点列出来
   → 这个清单就是下一轮深读的入口
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;核心差异在哪里？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;面向面试的 Prompt，核心是「输出导向」：怎样体现我对该项目的理解。结构、角色、考官视角都服务于这个目标。&lt;/p&gt;
&lt;p&gt;面向读懂源码的 Prompt，核心是「理解导向」：我还不知道什么、怎么一步步逼近真相、怎么验证我的理解是对的。所以先跑起来、生成认知地图、再深入核心代码，是这条路的主线。&lt;/p&gt;
&lt;p&gt;前者是把已有的理解「结构化输出」，后者是用 AI 作为「思维伙伴」来加速理解的初步建立。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>