<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>EVILSTAR</title><link>https://80b55ee1.hugo-blog-ddc.pages.dev/</link><description>Recent content on EVILSTAR</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 16 May 2026 14:01:46 +0800</lastBuildDate><atom:link href="https://80b55ee1.hugo-blog-ddc.pages.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>关于宗教，信仰，死亡的一些思考</title><link>https://80b55ee1.hugo-blog-ddc.pages.dev/post/some-thoughts-on-religion-faith-and-death-ytsg4.html</link><pubDate>Sat, 16 May 2026 14:01:46 +0800</pubDate><guid>https://80b55ee1.hugo-blog-ddc.pages.dev/post/some-thoughts-on-religion-faith-and-death-ytsg4.html</guid><description>&lt;p&gt;东方宗教和西方宗教对于死亡的看法很不一样，以佛教为例，认为人死后灵魂进行转世，根据生前的所做所为进行评判，如果生前行善积德，转世生到富贵人家；如果作恶多端，沦为畜生或者饿鬼，在十八层地狱下经受磨难。佛教来自印度教，轮回的概念也是一脉相承下来。对此我有一些疑问，评判的标准是什么，善恶的标准又是什么，如果一个人拥有很多钱财，热衷于慈善事业，拯救了很多濒临死亡的穷人；但同时为了获得财富，他杀掉了一些竞争者。他杀掉的人可能只有几个，但是拯救的人可能有几千个，几万个。那么这个人转世之后会成为人还是成为畜生呢？ 如果没有客观的标准，那么应该会有一个全知全能的神来判决，神如何判决呢，作为人应该不得而知，不可知的东西为什么会让这么多人深信不疑呢？ 无论怎样，死亡不是终点，死亡即是新生，轮回无休无止，只有拥有大功德的真佛才可以涅槃超脱。&lt;/p&gt;
&lt;p&gt;西方宗教都确信上帝的存在，人死后经由上帝审判，升上天堂或是堕入地狱。天堂和地狱的评判标准我也深表怀疑，虽然圣经里会有对评判规则的阐述，但是我觉得世界太复杂，人类也太复杂，没有绝对的好人和坏人，也无绝对的善恶。升入天堂的灵魂一定是纯洁的吗？会始终保持纯洁吗？ 堕入地狱的灵魂一定是肮脏的吗？&lt;/p&gt;
&lt;p&gt;人是有自由意志的，但是一到死亡，就会被审判，被发配，所以我们可以说死亡是自由意志的终结吗？&lt;/p&gt;
&lt;p&gt;伊曼纽·史威登堡认为人死之后并不会被审判，而是进入灵界，灵界和现实世界保持一致，死去的人照常工作，生活，但是世界的色彩会越来越鲜明，然后有一些陌生人逐渐进入他们的生活，这些人一部分是来自地狱的魔鬼，另外的是来自天堂的天使，如果受到魔鬼的引诱则会堕入地狱，如果遵循天使的引导就会升入天堂。也就是说天堂或者地狱并不是由上帝判决的，而是死去的人自己选择的，自由意志并没有因为死亡而终结。堕入地狱的人并不会向往天堂的生活，因为欺诈，卑鄙，偷盗等等行为只有在地狱才可以肆无忌惮的施展，他们无法适应天堂的纯洁的生活。同样升上天堂的人也无法适应地狱的生活。天堂和地狱，并非上帝判决，而是人自己的选择，自由意志从生命到死亡，贯穿始终。&lt;/p&gt;
&lt;p&gt;我觉得伊曼纽·史威登堡的观点很有意思，值得深入研究一下。&lt;/p&gt;</description></item><item><title>为了找回散落的 session，我做了一个 Claude Code / Codex 会话管理器</title><link>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/agent-session-manage-architecture/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/agent-session-manage-architecture/</guid><description>&lt;p&gt;最近这段时间，我在本地同时用 &lt;strong&gt;Claude Code&lt;/strong&gt; 和 &lt;strong&gt;Codex&lt;/strong&gt; 做开发的频率越来越高。&lt;/p&gt;
&lt;p&gt;工具一多，一个很烦的问题就开始反复出现：&lt;strong&gt;session 太难找了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;有些对话在 Claude 里，有些在 Codex 里；有些项目我开了很多个 worktree；有时候我只记得一句提问、一个报错，或者记得那次对话大概发生在哪个分支上，但就是想不起来它到底在哪个 session 里。&lt;/p&gt;
&lt;p&gt;还有一个更现实的问题：两个工具也不是总能顺手可用。&lt;/p&gt;
&lt;p&gt;有时候 Claude 这边刚好不方便用，有时候 Codex 状态不对；还有些时候，Claude 这轮表现不太符合我预期，我会很自然地想：&lt;strong&gt;这段上下文能不能直接切到 Codex 继续？&lt;/strong&gt; 反过来也一样。&lt;/p&gt;
&lt;p&gt;但实际操作往往很别扭。你要先把旧对话翻出来，再复制上下文，再尝试在另一边接上。如果之前那个 session 本身就已经埋在一堆历史记录里，光是“找到它”这一步，就足够把思路打断。&lt;/p&gt;
&lt;p&gt;所以我做了这个项目：&lt;strong&gt;Agent Session Manage&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;项目地址：&lt;a href="https://github.com/evilstar9527/agent-session-manage"&gt;https://github.com/evilstar9527/agent-session-manage&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;它不是一个新的 agent，也不是一个新的聊天工具。更准确地说，它是一个本地的会话管理器：把 Claude Code 和 Codex 的历史会话统一索引起来，让我可以更快地搜索、查看、恢复、导出，并且在需要的时候，尽量平滑地把对话切换到另一个工具里继续。&lt;/p&gt;
&lt;h2 id="我想做的其实不是另一个聊天应用"&gt;我想做的，其实不是“另一个聊天应用”&lt;/h2&gt;
&lt;p&gt;这个项目一开始的定位就很明确：&lt;strong&gt;我不想重新发明 Claude Code 或 Codex，我只是想把它们已经产生出来的 session 管理得更顺手一点。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以它解决的问题也非常具体：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;历史 session 不好找&lt;/li&gt;
&lt;li&gt;想恢复旧对话时路径不顺手&lt;/li&gt;
&lt;li&gt;想在 Claude 和 Codex 之间切换时很麻烦&lt;/li&gt;
&lt;li&gt;想把有价值的对话沉淀下来时，没有一个舒服的出口&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也决定了它的设计方向：它不接管原始数据，不改变原有 CLI 的工作方式，而是站在旁边，做一层 &lt;strong&gt;索引、检索和操作层&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果只用一句话概括，我会这样描述它：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;它是一个以本地 Claude Code / Codex 会话文件为输入、以统一会话模型为中间层、以 SQLite 为索引层、同时提供 CLI 和桌面 UI 的本地会话管理器。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里面最重要的是三件事：&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;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLite 只是索引层，不是 source of truth&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我很喜欢这种结构，因为它很克制。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude 的会话还是 Claude 的会话&lt;/li&gt;
&lt;li&gt;Codex 的会话还是 Codex 的会话&lt;/li&gt;
&lt;li&gt;这个工具只是让它们更容易被找到和继续使用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;哪怕有一天本地索引库删了，重新扫描也就回来了。&lt;/p&gt;
&lt;h2 id="这套架构大概长什么样"&gt;这套架构大概长什么样&lt;/h2&gt;
&lt;p&gt;先看一张总图：&lt;/p&gt;
&lt;div class="mermaid"&gt;flowchart LR
subgraph Entry["入口层"]
CLI["CLI\nsrc/cli.ts"]
UI["React UI\nsrc/ui/App.tsx"]
end
subgraph Desktop["桌面端桥接层"]
Main["Electron Main\nsrc/desktop/main.ts"]
IPC["IPC Handlers\nsrc/desktop/ipc.ts"]
Watcher["Session Watcher\nsrc/desktop/session-watcher.ts"]
end
subgraph Core["核心业务层"]
Service["SessionService\nsrc/app/session-service.ts"]
Indexer["Indexer\nsrc/indexer/index.ts"]
Discovery["Discovery\nsrc/discovery/*"]
Parsers["Parsers\nsrc/parsers/*"]
Model["CanonicalSession\nsrc/model/session.ts"]
Repo["SessionRepository\nsrc/store/repo.ts"]
Export["Markdown Export\nsrc/export/markdown.ts"]
Convert["Format Convert\nsrc/convert/*"]
end
subgraph Storage["存储层"]
SourceFiles["源会话文件\n~/.claude/projects\n~/.codex/sessions\n~/.codex/archived_sessions"]
SQLite["本地索引库\n~/.agent-session-manage/index.sqlite"]
end
CLI --&gt; Service
UI --&gt; IPC --&gt; Service
Main --&gt; IPC
Main --&gt; Watcher
Watcher --&gt; Service
Service --&gt; Indexer
Indexer --&gt; Discovery
Discovery --&gt; SourceFiles
Indexer --&gt; Parsers
Parsers --&gt; Model
Service --&gt; Repo
Model --&gt; Repo
Repo --&gt; SQLite
Service --&gt; Export
Service --&gt; Convert
Convert --&gt; SourceFiles
&lt;/div&gt;
&lt;p&gt;如果用更直白的话来说，它的工作方式其实很简单：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;先找到本地会话文件
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 解析成统一格式
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 写进本地索引库
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 在此基础上提供搜索、查看、恢复、导出、转换能力
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;整个项目最关键的点，不是 Electron，也不是 SQLite，而是中间那层统一模型。因为只有先把不同来源的 session 整理成同一类对象，后面的搜索、导出、恢复、切换这些功能，才值得做，也才不会越写越乱。&lt;/p&gt;
&lt;h2 id="为什么我坚持先做统一模型"&gt;为什么我坚持先做“统一模型”&lt;/h2&gt;
&lt;p&gt;如果没有这层抽象，事情会很快变得很糟。&lt;/p&gt;
&lt;p&gt;你很快就会遇到这种问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;搜索 Claude 和搜索 Codex 的逻辑不一样&lt;/li&gt;
&lt;li&gt;导出 Claude 和导出 Codex 的逻辑不一样&lt;/li&gt;
&lt;li&gt;恢复命令生成也不一样&lt;/li&gt;
&lt;li&gt;转换逻辑会散在一堆条件分支里&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以我一开始就把核心抽象放在 &lt;code&gt;src/model/session.ts&lt;/code&gt; 的 &lt;code&gt;CanonicalSession&lt;/code&gt; 上。&lt;/p&gt;
&lt;p&gt;它的含义并不复杂：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;不管输入来自 Claude 还是 Codex，最后都尽量整理成同一类会话对象。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个对象里最重要的信息包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;session 基本信息&lt;/li&gt;
&lt;li&gt;project path&lt;/li&gt;
&lt;li&gt;git branch&lt;/li&gt;
&lt;li&gt;title / summary&lt;/li&gt;
&lt;li&gt;messages&lt;/li&gt;
&lt;li&gt;tool calls&lt;/li&gt;
&lt;li&gt;metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一旦这层统一了，很多能力都会顺着长出来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;搜索&lt;/li&gt;
&lt;li&gt;查看详情&lt;/li&gt;
&lt;li&gt;pin / archive&lt;/li&gt;
&lt;li&gt;resume-command&lt;/li&gt;
&lt;li&gt;export markdown&lt;/li&gt;
&lt;li&gt;Claude / Codex 间转换&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它们不需要为每个来源单独再做一遍。&lt;/p&gt;
&lt;h2 id="会话是怎么被发现解析和导入的"&gt;会话是怎么被发现、解析和导入的&lt;/h2&gt;
&lt;p&gt;这部分主要分三步：&lt;strong&gt;发现文件、解析格式、写入索引&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="第一步发现文件"&gt;第一步：发现文件&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/discovery/claude.ts&lt;/code&gt; 会去递归找 Claude 的 &lt;code&gt;.jsonl&lt;/code&gt;，&lt;code&gt;src/discovery/codex.ts&lt;/code&gt; 会扫描 Codex 的 &lt;code&gt;rollout-*.jsonl&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这里我没有做什么“万能规则引擎”，而是明确针对两种来源分别适配。&lt;/p&gt;
&lt;p&gt;我现在越来越喜欢这种写法：&lt;strong&gt;知道格式不一样，就老老实实分别处理。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这通常比“看起来很优雅但到处例外”的抽象更稳。&lt;/p&gt;
&lt;h3 id="第二步解析格式"&gt;第二步：解析格式&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/parsers/claude.ts&lt;/code&gt; 和 &lt;code&gt;src/parsers/codex.ts&lt;/code&gt; 会把原始 JSONL 整理成统一模型。&lt;/p&gt;
&lt;p&gt;它们做的不是简单地把 JSON 读出来，而是做一层有目的的提炼，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;抽出用户和助手消息&lt;/li&gt;
&lt;li&gt;整理 tool call / tool result&lt;/li&gt;
&lt;li&gt;推断标题&lt;/li&gt;
&lt;li&gt;记录 project path、branch、source session id&lt;/li&gt;
&lt;li&gt;留一些 metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里有一个现实我很早就接受了：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这种转换不可能 100% 无损。&lt;/p&gt;
&lt;/blockquote&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;我想找回旧对话&lt;/li&gt;
&lt;li&gt;我想搜里面提到过什么&lt;/li&gt;
&lt;li&gt;我想恢复上下文继续工作&lt;/li&gt;
&lt;li&gt;我想把它导出来，或者迁到另一个工具里继续&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而不是去做一个协议层面的完美镜像。&lt;/p&gt;
&lt;h3 id="第三步写入索引"&gt;第三步：写入索引&lt;/h3&gt;
&lt;p&gt;扫描导入流程在 &lt;code&gt;src/indexer/index.ts&lt;/code&gt;，真正的落库存取在 &lt;code&gt;src/store/repo.ts&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这里我比较满意的一个点是：&lt;strong&gt;它不是每次都傻傻地全量重建。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;repo 层会保存文件指纹，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;size&lt;/li&gt;
&lt;li&gt;mtime&lt;/li&gt;
&lt;li&gt;quickHash&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果文件没变，就直接跳过，不重复解析。&lt;/p&gt;
&lt;p&gt;这个优化听起来不大，但对本地工具很重要。因为 session 一旦真的积累起来，数量会涨得很快。如果每次都全量扫一遍，体验很快就会变差。&lt;/p&gt;
&lt;h2 id="为什么我把业务逻辑集中在-sessionservice"&gt;为什么我把业务逻辑集中在 SessionService&lt;/h2&gt;
&lt;p&gt;这个项目里我比较刻意的一件事，是把业务能力尽量都收敛到 &lt;code&gt;src/app/session-service.ts&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它统一暴露了这些接口：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scan()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pin()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;archive()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delete()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exportMarkdown()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;convert()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getResumeCommand()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;launchResume()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样做最大的好处就是：&lt;strong&gt;CLI 和桌面端都可以变得很薄。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/cli.ts&lt;/code&gt; 只需要负责参数解析、调用 service、打印结果。桌面端的 &lt;code&gt;src/desktop/ipc.ts&lt;/code&gt; 也只是把前端操作映射到 service 上。&lt;/p&gt;
&lt;p&gt;这对我来说很重要，因为 UI 通常是变化最快的部分，但 discovery、parser、store、service 这些底层能力往往更稳定。只要边界划清楚，后面加功能时就不会到处互相牵扯。&lt;/p&gt;
&lt;h2 id="桌面端为什么还要做-watcher"&gt;桌面端为什么还要做 watcher&lt;/h2&gt;
&lt;p&gt;如果只有 CLI，心智其实很简单：想刷新就手动 &lt;code&gt;scan&lt;/code&gt;，想找东西就用命令查。&lt;/p&gt;
&lt;p&gt;但桌面端不一样。桌面端一旦存在，用户就会自然期待：&lt;strong&gt;它应该自己知道数据变了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以我做了 &lt;code&gt;src/desktop/session-watcher.ts&lt;/code&gt;，去监听：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude projects 目录&lt;/li&gt;
&lt;li&gt;Codex sessions 目录&lt;/li&gt;
&lt;li&gt;Codex archived_sessions 目录&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;文件变化后，它不会立刻全量扫描，而是做一个小防抖，再通知窗口刷新列表。&lt;/p&gt;
&lt;p&gt;这个功能很朴素，但体验差别非常大。没有它的时候，桌面应用更像一个“偶尔手动刷新的浏览器”；有了它以后，它才更像一个真正的本地工作台。&lt;/p&gt;
&lt;h2 id="对我来说最有价值的不是搜索而是可以继续用"&gt;对我来说，最有价值的不是搜索，而是“可以继续用”&lt;/h2&gt;
&lt;p&gt;我后来越来越觉得，这个项目最值钱的地方，并不是“我终于能搜到某个 session 了”，而是搜到之后，&lt;strong&gt;我真的可以继续拿它工作&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="1-可以直接恢复原生会话"&gt;1. 可以直接恢复原生会话&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SessionService.getResumeCommand()&lt;/code&gt; 会根据来源生成原生命令：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude 走 &lt;code&gt;claude --resume&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Codex 走 &lt;code&gt;codex resume&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而且桌面端还能直接帮我在终端里打开它。&lt;/p&gt;
&lt;p&gt;这件事的关键不在命令本身，而在于它让“找回旧对话”和“继续工作”真正连起来了。&lt;/p&gt;
&lt;h3 id="2-可以导出成-markdown"&gt;2. 可以导出成 Markdown&lt;/h3&gt;
&lt;p&gt;很多 agent 对话并不只是一次性的。我经常会碰到一些值得留存的内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;某次排障过程&lt;/li&gt;
&lt;li&gt;某次重构思路&lt;/li&gt;
&lt;li&gt;某段工具调用和输出&lt;/li&gt;
&lt;li&gt;某轮讨论过的取舍&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当它能导出成 Markdown，它就更像一份资料，而不是一段只能被埋在工具目录里的聊天历史。&lt;/p&gt;
&lt;h3 id="3-可以在-claude-和-codex-之间切换"&gt;3. 可以在 Claude 和 Codex 之间切换&lt;/h3&gt;
&lt;p&gt;这其实最接近我做这个项目的初始动机。&lt;/p&gt;
&lt;p&gt;我并不是想做一个“理论上完美兼容”的格式转换器，我真正想要的是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当我对 Claude 当前这轮表现不满意，或者 Claude 这边暂时不好用时，我能不能尽量少折腾地把上下文迁到 Codex 去继续？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;反过来也一样。&lt;/p&gt;
&lt;p&gt;所以 conversion 的目标从来不是无损复刻，而是&lt;strong&gt;尽可能保住上下文的实用价值&lt;/strong&gt;。只要它能让我少做一次上下文重建，少打断一次工作流，这个能力就已经很有意义了。&lt;/p&gt;
&lt;h2 id="这套架构为什么我觉得是对的"&gt;这套架构为什么我觉得是对的&lt;/h2&gt;
&lt;p&gt;如果回头看，这个项目能站住，核心原因其实就一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我没有把它当成“另一个聊天应用”，而是把它当成“已有会话的管理层”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个定位影响了几乎所有设计决策。&lt;/p&gt;
&lt;p&gt;它不接管原始数据，不试图取代 Claude Code 或 Codex，也不追求协议层面的完美洁癖。它优先解决的是工作流问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;能不能找到 session&lt;/li&gt;
&lt;li&gt;能不能快速恢复&lt;/li&gt;
&lt;li&gt;能不能把上下文切到另一边继续&lt;/li&gt;
&lt;li&gt;能不能把历史沉淀下来&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我觉得这才是它真正应该服务的场景。&lt;/p&gt;
&lt;h2 id="当然它现在也还不完美"&gt;当然，它现在也还不完美&lt;/h2&gt;
&lt;p&gt;至少在我自己看来，还有几件事是后面会继续做的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;搜索还能更强，比如更好的全文索引和过滤组合&lt;/li&gt;
&lt;li&gt;前端现在还能继续拆，尤其是 session 详情和筛选部分&lt;/li&gt;
&lt;li&gt;转换能力永远有边界，这件事只能尽量做好，不能假装它是完全无损的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但这些都不影响它现在已经解决了我最在意的那部分问题：&lt;strong&gt;把那些原本很容易沉下去的 session，重新变成可以找、可以看、可以接着用的东西。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;我做 &lt;code&gt;Agent Session Manage&lt;/code&gt; 的原因其实不复杂：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;session 越来越多，越来越难找&lt;/li&gt;
&lt;li&gt;我经常在 Claude Code 和 Codex 之间切换&lt;/li&gt;
&lt;li&gt;有时候其中一边不能用&lt;/li&gt;
&lt;li&gt;有时候只是单纯想把一段上下文换到另一边继续&lt;/li&gt;
&lt;li&gt;我也希望把这些历史对话变成可以搜索、恢复、导出、沉淀的东西&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以最后做出来的，不是一个新的 agent，而是一个本地会话管理器。&lt;/p&gt;
&lt;p&gt;如果再用一句话来概括它，我现在更愿意写成这样：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这不是一个“重新发明 agent”的项目，而是一个把 Claude Code 和 Codex 的历史会话重新组织起来，让它们更容易被找到、更容易被继续使用的工具。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对我来说，这种工具真正重要的地方，不是技术栈本身，而是它能不能减少上下文切换的摩擦，能不能把那些本来很容易沉下去的 session，重新变成可以用起来的工作资产。&lt;/p&gt;</description></item><item><title>Claude Code `/compact` 机制分析</title><link>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/claude-code-compact-mechanism/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/claude-code-compact-mechanism/</guid><description>&lt;h2 id="context为什么需要这份分析"&gt;Context（为什么需要这份分析）&lt;/h2&gt;
&lt;p&gt;用户想弄清楚 Claude Code 的 &lt;code&gt;compact&lt;/code&gt; 功能在源码层面是如何实现的。这不是一个实现任务，而是一次对 &lt;code&gt;/Users/jishihe/work/civil-engineering-cloud-claude-code-source-v2.1.88/01-claude-code-source-crack/claude-code-source&lt;/code&gt; 这份泄露源码的逆向阅读。产出就是这份文档——没有要改的代码。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一总体架构三层压缩"&gt;一、总体架构：三层压缩&lt;/h2&gt;
&lt;p&gt;Claude Code 对上下文的管理不是单一 compact，而是&lt;strong&gt;按&amp;quot;代价从小到大&amp;quot;四层递进&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层级&lt;/th&gt;
&lt;th&gt;目标&lt;/th&gt;
&lt;th&gt;是否调 LLM&lt;/th&gt;
&lt;th&gt;关键文件&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;snip&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;裁剪被 REPL 显式标记丢掉的旧消息（UI 滚动历史）&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/services/compact/snipCompact.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;microcompact&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;不改消息数、只把旧 &lt;code&gt;tool_result&lt;/code&gt;（Read/Bash/Grep/Glob/…）内容清空，靠 cache editing 保住 prompt cache&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/services/compact/microCompact.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;session memory compact&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;把靠前的消息裁掉 + 塞进一个已经由 background 线程提取好的 &amp;ldquo;session memory&amp;rdquo;，&lt;strong&gt;保留最近若干条消息原文&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;否（预先提取好）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/services/compact/sessionMemoryCompact.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;full compact（经典 /compact）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;用 LLM 生成结构化摘要，&lt;strong&gt;替换掉整段历史&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/services/compact/compact.ts&lt;/code&gt; + &lt;code&gt;prompt.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;在 query 主循环里依次执行（&lt;code&gt;src/query.ts:400-468&lt;/code&gt;）：snip → microcompact → contextCollapse → autoCompactIfNeeded。前面几层没把 token 压到阈值以下，才会走到 LLM 级 compact。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二触发路径"&gt;二、触发路径&lt;/h2&gt;
&lt;h3 id="21-手动-compact-自定义指令"&gt;2.1 手动 &lt;code&gt;/compact [自定义指令]&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;入口定义在 &lt;code&gt;src/commands/compact/index.ts&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;compact&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;local&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;compact&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;description&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Clear conversation history but keep a summary in context. Optional: /compact [instructions for summarization]&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;isEnabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; () &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;isEnvTruthy&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DISABLE_COMPACT&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;supportsNonInteractive&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;load&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; () &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;import&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;./compact.js&amp;#39;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;实际执行在 &lt;code&gt;src/commands/compact/compact.ts:40&lt;/code&gt; 的 &lt;code&gt;call()&lt;/code&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;getMessagesAfterCompactBoundary(messages)&lt;/code&gt; —— 只截取&lt;strong&gt;上一次 compact 边界之后&lt;/strong&gt;的消息，避免重复摘要。&lt;/li&gt;
&lt;li&gt;没有自定义指令时先尝试 &lt;code&gt;trySessionMemoryCompaction&lt;/code&gt;（轻量路径）。&lt;/li&gt;
&lt;li&gt;否则先 &lt;code&gt;microcompactMessages&lt;/code&gt; 缩一轮，再 &lt;code&gt;compactConversation(...)&lt;/code&gt; 走 LLM 摘要。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="22-自动-autocompact容量触发"&gt;2.2 自动 autocompact（容量触发）&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/services/compact/autoCompact.ts&lt;/code&gt; 定义阈值：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 为 LLM 输出留 20K tokens
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MAX_OUTPUT_TOKENS_FOR_SUMMARY&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;_000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AUTOCOMPACT_BUFFER_TOKENS&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;13&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;_000&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 距离硬限还剩 13K 就触发
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MANUAL_COMPACT_BUFFER_TOKENS&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;_000&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 3K 就硬阻塞
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;getEffectiveContextWindowSize(model)&lt;/code&gt; = 模型 context window - 为 summary 预留的 output tokens。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getAutoCompactThreshold(model)&lt;/code&gt; = 有效窗 - 13K buffer。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shouldAutoCompact()&lt;/code&gt; 在每轮 query 开始前估算 token 数，超过阈值就返回 true。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;熔断&lt;/strong&gt;：连续 3 次 compact 失败后停止尝试（&lt;code&gt;MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES&lt;/code&gt;），防止 session 死锁空转烧钱。&lt;/li&gt;
&lt;li&gt;递归保护：querySource 是 &lt;code&gt;'session_memory'&lt;/code&gt; / &lt;code&gt;'compact'&lt;/code&gt; / &lt;code&gt;'marble_origami'&lt;/code&gt; 时直接 return，避免 forked agent 自己再去 compact。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="三核心压缩算法compactconversation"&gt;三、核心压缩算法：&lt;code&gt;compactConversation&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;位于 &lt;code&gt;src/services/compact/compact.ts:387&lt;/code&gt;。端到端流程：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌─ 1. PreCompact hooks ─────────────────────────────────────────────────┐
│ executePreCompactHooks({ trigger:&amp;#39;auto&amp;#39;|&amp;#39;manual&amp;#39;, customInstructions })
│ hook 可以改写 customInstructions / 加显示文案 │
├─ 2. 拼 summary prompt ────────────────────────────────────────────────┤
│ getCompactPrompt(customInstructions) │
│ = NO_TOOLS_PREAMBLE + BASE_COMPACT_PROMPT + (Additional Instructions)│
│ + NO_TOOLS_TRAILER │
├─ 3. 走 forked agent 调模型 ───────────────────────────────────────────┤
│ runForkedAgent({ │
│ promptMessages: [summaryRequest], │
│ cacheSafeParams, ← 复用主线程 system prompt / tools / 消息前缀 │
│ canUseTool: createCompactCanUseTool(), ← 拒绝所有工具调用 │
│ maxTurns: 1, │
│ skipCacheWrite: true │
│ }) │
│ // ↑ 复用 prompt cache 是关键。experiment 表明主路 98% 命中率， │
│ // 否则每次 compact 会多烧几十 B tokens/day │
├─ 4. 退化路径：forked agent 失败 → queryModelWithStreaming 裸调 │
│ system = &amp;#34;You are a helpful AI assistant tasked with summarizing │
│ conversations.&amp;#34; │
│ thinking = disabled，tools 仅 [FileReadTool] 保留 │
│ messages = stripImages(stripReinjectedAttachments( │
│ getMessagesAfterCompactBoundary(messages) + │
│ summaryRequest)) │
├─ 5. prompt_too_long 兜底 ─────────────────────────────────────────────┤
│ summary 开头 = &amp;#34;API Error: prompt is too long&amp;#34; → truncateHeadFor │
│ PTLRetry() 砍掉最老的一组 API round，重试（最多 MAX_PTL_RETRIES 次） │
├─ 6. 清状态 + 重建附件 ────────────────────────────────────────────────┤
│ context.readFileState.clear() │
│ 并行生成： │
│ ┌ createPostCompactFileAttachments —— 把压缩前被读过的文件重读 │
│ │ （上限 5 个文件，每文件 5K tokens，总 50K tokens） │
│ ├ createAsyncAgentAttachmentsIfNeeded │
│ ├ createPlanAttachmentIfNeeded / createPlanModeAttachmentIfNeeded │
│ ├ createSkillAttachmentIfNeeded —— 已调用过的 skill 原文 │
│ │ （上限 5 个，每个 5K，总 25K） │
│ └ getDeferredToolsDeltaAttachment / AgentListing / MCP Instructions│
│ // 总之：把&amp;#34;主动上下文&amp;#34;重新塞回去，LLM 摘要里没覆盖的死信息（刚读 │
│ // 的代码、当前 plan、用到的 skill）靠这些 attachment 复活 │
├─ 7. SessionStart hooks（复用 &amp;#39;compact&amp;#39; 触发语义） │
├─ 8. 构造 boundary + summaryMessage ───────────────────────────────────┤
│ boundaryMarker = createCompactBoundaryMessage(&amp;#39;auto&amp;#39;|&amp;#39;manual&amp;#39;, …) │
│ // ↑ type:&amp;#39;system&amp;#39;, subtype:&amp;#39;compact_boundary&amp;#39;，content=&amp;#34;Conversa │
│ // tion compacted&amp;#34;。未来 getMessagesAfterCompactBoundary 会反向 │
│ // 扫描、只保留这之后的消息。 │
│ summaryMessages = [ createUserMessage({ │
│ content: getCompactUserSummaryMessage(summary, suppressFollow…) │
│ isCompactSummary: true, │
│ isVisibleInTranscriptOnly: true │
│ }) ] │
├─ 9. formatCompactSummary —— 去 &amp;lt;analysis&amp;gt;，把 &amp;lt;summary&amp;gt; 变成 │
│ &amp;#34;Summary:\n...&amp;#34; 可读文本 │
├─10. 埋点 tengu_compact（preToken / postToken / 缓存命中率 / …） │
├─11. notifyCompaction() —— 重置 prompt cache 基线，避免后续把 compact │
│ 自身导致的 cache drop 误报成 cache break │
├─12. PostCompact hooks ────────────────────────────────────────────────┤
└─13. return CompactionResult { boundaryMarker, summaryMessages, attach…│
hookResults, userDisplayMessage, … } │
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调用方（&lt;code&gt;src/commands/compact/compact.ts&lt;/code&gt; 或 &lt;code&gt;autoCompact.ts&lt;/code&gt;）拿到 result 后再做 &lt;code&gt;runPostCompactCleanup()&lt;/code&gt;、&lt;code&gt;suppressCompactWarning()&lt;/code&gt;、&lt;code&gt;markPostCompaction()&lt;/code&gt;、&lt;code&gt;notifyCompaction()&lt;/code&gt;，然后把新消息数组交回 REPL。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四摘要提示词这才是compact-究竟让-llm-做什么的答案"&gt;四、摘要提示词（这才是&amp;quot;compact 究竟让 LLM 做什么&amp;quot;的答案）&lt;/h2&gt;
&lt;p&gt;都在 &lt;code&gt;src/services/compact/prompt.ts&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="41-前导no_tools_preamble"&gt;4.1 前导（&lt;code&gt;NO_TOOLS_PREAMBLE&lt;/code&gt;）&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.
- Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.
- You already have all the context you need in the conversation above.
- Tool calls will be REJECTED and will waste your only turn — you will fail the task.
- Your entire response must be plain text: an &amp;lt;analysis&amp;gt; block followed by a &amp;lt;summary&amp;gt; block.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;注释里写得很露骨：fork 出去的 agent 继承了主线程&lt;strong&gt;全部工具定义&lt;/strong&gt;（为了 cache key 对上才能命中 prompt cache），但 Sonnet 4.6+ 的 adaptive-thinking 有 2.79% 概率无视弱指令去调工具 → maxTurns=1 浪费掉 → 所以把&amp;quot;不准调工具&amp;quot;作为最粗暴的前置指令。&lt;/p&gt;
&lt;h3 id="42-主体base_compact_prompt"&gt;4.2 主体（&lt;code&gt;BASE_COMPACT_PROMPT&lt;/code&gt;）&lt;/h3&gt;
&lt;p&gt;要求模型产出&lt;strong&gt;九段结构化摘要&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Primary Request and Intent&lt;/strong&gt; —— 用户原始请求&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key Technical Concepts&lt;/strong&gt; —— 技术名词列表&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Files and Code Sections&lt;/strong&gt; —— 看过/改过的文件，&lt;strong&gt;要求贴代码片段&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Errors and fixes&lt;/strong&gt; —— 坑 + 用户反馈&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Problem Solving&lt;/strong&gt; —— 解决了什么&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All user messages&lt;/strong&gt; —— &lt;strong&gt;所有非 tool_result 的用户消息全列出来&lt;/strong&gt;（最关键、最怕丢）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pending Tasks&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Current Work&lt;/strong&gt; —— 压缩前最后在干什么，要贴文件名 + 代码片段&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional Next Step&lt;/strong&gt; —— 下一步，&lt;strong&gt;要求引用最近对话原话&lt;/strong&gt;避免漂移&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;产出格式固定为：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;analysis&amp;gt;
[思考草稿；formatCompactSummary 会剥掉]
&amp;lt;/analysis&amp;gt;
&amp;lt;summary&amp;gt;
1. Primary Request and Intent: …
…
9. Optional Next Step: …
&amp;lt;/summary&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="43-结尾no_tools_trailer"&gt;4.3 结尾（&lt;code&gt;NO_TOOLS_TRAILER&lt;/code&gt;）&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;REMINDER: Do NOT call any tools. Respond with plain text only — an
&amp;lt;analysis&amp;gt; block followed by a &amp;lt;summary&amp;gt; block. Tool calls will be
rejected and you will fail the task.
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="44-两个变体"&gt;4.4 两个变体&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PARTIAL_COMPACT_PROMPT&lt;/code&gt; —— 只摘要&amp;quot;最近消息&amp;quot;，前面保留原文（&lt;code&gt;partialCompactConversation&lt;/code&gt;，用于 &lt;code&gt;direction='from'&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PARTIAL_COMPACT_UP_TO_PROMPT&lt;/code&gt; —— 摘要前半、后半保留原文，会命中 cache prefix。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="45-用户自定义指令拼接"&gt;4.5 用户自定义指令拼接&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;customInstructions&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;customInstructions&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;trim&lt;/span&gt;() &lt;span style="color:#f92672"&gt;!==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;prompt&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;n&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;nAdditional Instructions:&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;customInstructions&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/compact focus on test failures&lt;/code&gt; 里的 &lt;code&gt;focus on test failures&lt;/code&gt; 就拼在这里。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五压缩后的会话长什么样"&gt;五、压缩后的会话长什么样&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;getCompactUserSummaryMessage&lt;/code&gt;（&lt;code&gt;prompt.ts:337&lt;/code&gt;）产出的&lt;strong&gt;单条 user 消息&lt;/strong&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;This session is being continued from a previous conversation that ran
out of context. The summary below covers the earlier portion of the
conversation.
Summary:
1. Primary Request and Intent:
...
…
9. Optional Next Step:
...
If you need specific details from before compaction (like exact code
snippets, error messages, or content you generated), read the full
transcript at: &amp;lt;transcriptPath&amp;gt;
Recent messages are preserved verbatim. ← 仅在保留尾部时追加
Continue the conversation from where it left off without asking the
user any further questions. Resume directly — do not acknowledge the
summary, do not recap what was happening, do not preface with &amp;#34;I&amp;#39;ll
continue&amp;#34; or similar. Pick up the last task as if the break never
happened. ← suppressFollowUpQuestions
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;autocompact 会设 &lt;code&gt;suppressFollowUpQuestions=true&lt;/code&gt;，手动 &lt;code&gt;/compact&lt;/code&gt; 不会。最终新消息序列：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[旧 boundary / 旧 summary] ← 若本会话之前就 compact 过，这里保留
[SystemCompactBoundaryMessage] ← 本次分隔符
[UserMessage（上面那段摘要）]
[file attachments] ← 重读的 ≤5 个文件
[plan / skill / 工具 delta attachments]
[SessionStart hook messages]
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="六其它关键设计点"&gt;六、其它关键设计点&lt;/h2&gt;
&lt;h3 id="61-cache-sharing-fork"&gt;6.1 Cache-sharing fork&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;runForkedAgent&lt;/code&gt; 保证 fork 出去的 compact 调用与主线程 &lt;code&gt;cacheSafeParams&lt;/code&gt; 完全一致（system prompt、tools、消息前缀、thinking 配置），这样 Anthropic API 侧 prompt cache 能命中主线程那一份——省出 ~38B tokens/day 的 cache_creation。&lt;/p&gt;
&lt;h3 id="62-图片文档剥离"&gt;6.2 图片/文档剥离&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;stripImagesFromMessages&lt;/code&gt; 把 user 消息和 &lt;code&gt;tool_result&lt;/code&gt; 里嵌套的 &lt;code&gt;image&lt;/code&gt; / &lt;code&gt;document&lt;/code&gt; block 换成 &lt;code&gt;[image]&lt;/code&gt; / &lt;code&gt;[document]&lt;/code&gt; 文本占位——一方面摘要用不着，另一方面图片会把 compact 自身的 request 撑爆。&lt;/p&gt;
&lt;h3 id="63-prompt_too_long-重试"&gt;6.3 prompt_too_long 重试&lt;/h3&gt;
&lt;p&gt;如果 compact 请求&lt;strong&gt;自己&lt;/strong&gt;触发了 413，用 &lt;code&gt;truncateHeadForPTLRetry&lt;/code&gt; 砍掉最老一组 API round 再试（最多 &lt;code&gt;MAX_PTL_RETRIES&lt;/code&gt; 次）。这是 CC-1180 bug 的修复——以前用户直接卡死无法恢复。&lt;/p&gt;
&lt;h3 id="64-post-compact-文件重读"&gt;6.4 Post-compact 文件重读&lt;/h3&gt;
&lt;p&gt;Compact 后 &lt;code&gt;context.readFileState.clear()&lt;/code&gt; 会被清掉，&lt;code&gt;createPostCompactFileAttachments&lt;/code&gt; 会挑最近读过的最多 5 个文件、每个最多 5K tokens、共 50K tokens 重新 Read 一遍塞回来。&lt;strong&gt;这就是为什么 compact 后代码细节通常不会丢&lt;/strong&gt;——摘要里可能写了&amp;quot;修改了 foo.ts 的 bar 函数&amp;quot;，但真正可供 LLM 继续编辑的原文是通过 attachment 再喂一次进来的。&lt;/p&gt;
&lt;h3 id="65-boundary-概念"&gt;6.5 Boundary 概念&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;createCompactBoundaryMessage&lt;/code&gt; 插一条 &lt;code&gt;subtype: 'compact_boundary'&lt;/code&gt; 的 system 消息到历史里。REPL UI 还是能看到全历史（可以滚回去），但是所有发给 API 的消息集合都是 &lt;code&gt;getMessagesAfterCompactBoundary(messages)&lt;/code&gt; 反向扫到第一条 boundary 之后的。这就是 UI 和 API 视图解耦的方式。&lt;/p&gt;
&lt;h3 id="66-session-memory-compact实验路径"&gt;6.6 Session memory compact（实验路径）&lt;/h3&gt;
&lt;p&gt;无自定义指令时优先尝试：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;后台线程一直在抽取 session memory（工具输出纲要 + 决策点）。&lt;/li&gt;
&lt;li&gt;触发时不调 LLM，直接用已提取好的 memory 文本生成&amp;quot;摘要&amp;quot;，&lt;strong&gt;保留最近 10K–40K tokens 的原消息&lt;/strong&gt;（&lt;code&gt;DEFAULT_SM_COMPACT_CONFIG&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;更便宜、对最近上下文无损；有自定义指令时回退到经典路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="67-reactive-compact"&gt;6.7 Reactive compact&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;REACTIVE_COMPACT&lt;/code&gt; feature flag 下，autocompact 关闭、只有当 API 真返回 413 (&lt;code&gt;prompt_too_long&lt;/code&gt;) 时才就地压缩——更激进地利用 context。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七一句话总结"&gt;七、一句话总结&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Claude Code 的 compact = &amp;ldquo;让模型用一份固定的九段式结构化 prompt 对整段对话做自我摘要 → 插一条 boundary → 清文件缓存 → 把最近读过的文件/调过的 skill/当前 plan 作为 attachment 重新注入&amp;rdquo;&lt;/strong&gt;。配合 microcompact 的 tool_result 清空、session memory 的后台预提取、forked agent 的 prompt cache 复用、PTL 重试熔断，形成一套&amp;quot;压缩代价阶梯 + 摘要永远可重入 + 缓存不破坏&amp;quot;的组合拳。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="关键文件索引"&gt;关键文件索引&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/commands/compact/index.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/compact&lt;/code&gt; 命令注册&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/commands/compact/compact.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/compact&lt;/code&gt; 的本地命令 &lt;code&gt;call()&lt;/code&gt; 逻辑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/services/compact/prompt.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;全部摘要 prompt 字面量 + &lt;code&gt;formatCompactSummary&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/services/compact/compact.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;compactConversation&lt;/code&gt; / &lt;code&gt;partialCompactConversation&lt;/code&gt; / &lt;code&gt;streamCompactSummary&lt;/code&gt; / 附件重建&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/services/compact/autoCompact.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;阈值计算、&lt;code&gt;shouldAutoCompact&lt;/code&gt;、&lt;code&gt;autoCompactIfNeeded&lt;/code&gt;、熔断&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/services/compact/microCompact.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;清理旧 tool_result 的轻量预压缩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/services/compact/sessionMemoryCompact.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不调 LLM 的 session-memory 版压缩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/services/compact/postCompactCleanup.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;压缩后通用清理钩子&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/utils/messages.ts:4530+&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;createCompactBoundaryMessage&lt;/code&gt; / &lt;code&gt;findLastCompactBoundaryIndex&lt;/code&gt; / &lt;code&gt;getMessagesAfterCompactBoundary&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/query.ts:400-468&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;主循环里 snip → microcompact → contextCollapse → autocompact 的调度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/utils/forkedAgent.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;runForkedAgent&lt;/code&gt;（复用主线程 prompt cache 调 compact）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="验证建议如果想动手跑一遍"&gt;验证建议（如果想动手跑一遍）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在项目根执行 &lt;code&gt;rg -n &amp;quot;NO_TOOLS_PREAMBLE&amp;quot; src/&lt;/code&gt; 确认 prompt 字面量只此一家。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DEBUG=true&lt;/code&gt; 跑一次会话，找 log &lt;code&gt;autocompact: tokens=... threshold=... effectiveWindow=...&lt;/code&gt; 观察阈值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=50&lt;/code&gt; 可把触发阈值压到 50%，便于手动复现。&lt;/li&gt;
&lt;li&gt;触发后在 &lt;code&gt;~/.claude/projects/*/&lt;/code&gt; 的 transcript 里搜 &lt;code&gt;subtype&amp;quot;:&amp;quot;compact_boundary&amp;quot;&lt;/code&gt; 看实际边界消息结构。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Claude Code Task 架构分析</title><link>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/claude-code-task-architecture/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/claude-code-task-architecture/</guid><description>&lt;h2 id="1-先说结论"&gt;1. 先说结论&lt;/h2&gt;
&lt;p&gt;这份源码里的 &lt;code&gt;task&lt;/code&gt; 不是一个单一概念，而是两个相关但不同的子系统：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;运行时后台任务系统
&lt;ul&gt;
&lt;li&gt;管理正在运行或已结束的后台 bash、agent、remote session 等&lt;/li&gt;
&lt;li&gt;核心文件：&lt;code&gt;Task.ts&lt;/code&gt;、&lt;code&gt;tasks.ts&lt;/code&gt;、&lt;code&gt;utils/task/framework.ts&lt;/code&gt;、&lt;code&gt;AppStateStore.ts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TodoV2 任务清单系统
&lt;ul&gt;
&lt;li&gt;管理“要做什么”的结构化任务列表&lt;/li&gt;
&lt;li&gt;核心文件：&lt;code&gt;utils/tasks.ts&lt;/code&gt;、&lt;code&gt;useTasksV2.ts&lt;/code&gt;、&lt;code&gt;TaskCreateTool&lt;/code&gt;、&lt;code&gt;TaskUpdateTool&lt;/code&gt;、&lt;code&gt;TaskListTool&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两个系统名字都叫 task，但职责完全不同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;后台任务系统回答的是 “谁正在跑、输出在哪、怎么停”&lt;/li&gt;
&lt;li&gt;任务清单系统回答的是 “还有哪些工作项、谁负责、依赖关系是什么”&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="2-总体关系图"&gt;2. 总体关系图&lt;/h2&gt;
&lt;div class="mermaid"&gt;flowchart TB
A["Model / User"] --&gt; B["Task Tools"]
B --&gt; C["Runtime Background Task System"]
B --&gt; D["TodoV2 Task List System"]
subgraph Runtime Background Task System
C1["Task.ts contract"]
C2["AppState.tasks"]
C3["LocalShellTask / LocalAgentTask / RemoteAgentTask"]
C4["task output on disk"]
C5["stop / output / notifications"]
end
subgraph TodoV2 Task List System
D1["utils/tasks.ts"]
D2["task json files + lock"]
D3["TaskCreate / Update / Get / List"]
D4["useTasksV2 watcher"]
end
B1["TaskStopTool / TaskOutputTool"] --&gt; C
B2["TaskCreateTool / TaskUpdateTool / TaskGetTool / TaskListTool"] --&gt; D
&lt;/div&gt;
&lt;p&gt;最容易误读源码的地方就在这里：&lt;code&gt;TaskStopTool&lt;/code&gt; 停的是运行中的后台任务，不是 TodoV2 清单里的任务项；&lt;code&gt;TaskUpdateTool&lt;/code&gt; 更新的是清单任务，不是后台进程状态。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="3-运行时后台任务系统"&gt;3. 运行时后台任务系统&lt;/h2&gt;
&lt;h3 id="31-核心抽象"&gt;3.1 核心抽象&lt;/h3&gt;
&lt;p&gt;后台任务的统一协议在 &lt;code&gt;src/Task.ts&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TaskType&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskStatus&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskStateBase&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Task&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;generateTaskId()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createTaskStateBase()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它的设计很克制，&lt;code&gt;Task&lt;/code&gt; 本体只保留：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kill(taskId, setAppState)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，当前这一层不是一个完整的 OO 基类体系，而是一个最小 kill-dispatch 协议。&lt;br&gt;
任务的 spawn、progress、notification、output 逻辑，下沉到了各个具体 task 实现中。&lt;/p&gt;
&lt;h3 id="32-运行时任务分层图"&gt;3.2 运行时任务分层图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart TB
A["Task.ts"] --&gt; B["tasks.ts registry"]
B --&gt; C["getTaskByType"]
C --&gt; D["Concrete task impl"]
D --&gt; E["LocalShellTask"]
D --&gt; F["LocalAgentTask"]
D --&gt; G["RemoteAgentTask"]
D --&gt; H["Dream/Workflow/Monitor optional tasks"]
E --&gt; I["AppState.tasks"]
F --&gt; I
G --&gt; I
H --&gt; I
I --&gt; J["framework.ts register/update/evict"]
J --&gt; K["notifications + sdk events"]
J --&gt; L["task output on disk"]
&lt;/div&gt;
&lt;p&gt;这里的关键思想是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;task 类型是开放集合，但统一通过 registry 查找&lt;/li&gt;
&lt;li&gt;公共状态收口在 &lt;code&gt;AppState.tasks&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;公共框架只管注册、更新、驱逐、通知&lt;/li&gt;
&lt;li&gt;真正的业务生命周期由具体 task 自己实现&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="4-appstate-为什么是任务系统的中心"&gt;4. AppState 为什么是任务系统的中心&lt;/h2&gt;
&lt;p&gt;后台任务统一存在 &lt;code&gt;AppState.tasks&lt;/code&gt; 里，而不是每种任务自己维护一套 store。&lt;/p&gt;
&lt;p&gt;这在 &lt;code&gt;AppStateStore.ts&lt;/code&gt; 里很明显：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tasks: { [taskId: string]: TaskState }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;foregroundedTaskId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;viewingAgentTaskId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;remoteBackgroundTaskCount&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="41-状态视图图"&gt;4.1 状态视图图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart LR
A["AppState.tasks"] --&gt; B["taskId -&gt; TaskState"]
A --&gt; C["background task list UI"]
A --&gt; D["foregroundedTaskId"]
A --&gt; E["viewingAgentTaskId"]
A --&gt; F["coordinator/task panel"]
A --&gt; G["TaskStopTool lookup"]
A --&gt; H["TaskOutputTool lookup"]
&lt;/div&gt;
&lt;p&gt;这样做有几个直接收益：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有 UI 都从一个状态源读取&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskStopTool&lt;/code&gt; 不需要知道 task 存在哪，只查 &lt;code&gt;AppState.tasks&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;前后台切换只需要改 task state，不需要迁移数据结构&lt;/li&gt;
&lt;li&gt;不同 task type 可以复用统一生命周期字段&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="5-frameworkts-是运行时任务框架层"&gt;5. framework.ts 是运行时任务框架层&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src/utils/task/framework.ts&lt;/code&gt; 是后台任务系统的核心公共层。它主要负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;registerTask()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;updateTaskState()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evictTerminalTask()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;generateTaskAttachments()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;applyTaskOffsetsAndEvictions()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pollTasks()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="51-框架职责图"&gt;5.1 框架职责图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart TB
A["Concrete Task"] --&gt; B["registerTask"]
A --&gt; C["updateTaskState"]
B --&gt; D["AppState.tasks"]
C --&gt; D
D --&gt; E["pollTasks"]
E --&gt; F["generateTaskAttachments"]
F --&gt; G["offset update"]
F --&gt; H["terminal eviction"]
F --&gt; I["notification enqueue"]
&lt;/div&gt;
&lt;p&gt;几个关键点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;updateTaskState()&lt;/code&gt; 是统一写入口，避免各处直接改 &lt;code&gt;AppState.tasks&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;registerTask()&lt;/code&gt; 在状态登记外，还会发 &lt;code&gt;task_started&lt;/code&gt; SDK 事件&lt;/li&gt;
&lt;li&gt;终态任务不是立刻全删，而是受 &lt;code&gt;notified&lt;/code&gt;、&lt;code&gt;retain&lt;/code&gt;、&lt;code&gt;evictAfter&lt;/code&gt; 约束&lt;/li&gt;
&lt;li&gt;&lt;code&gt;generateTaskAttachments()&lt;/code&gt; 不直接负责 completed 通知，completed 通知大多由各 task 类型自己发&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这说明作者有意把 framework 限制在“状态基础设施”层，而不是做成一个吞掉所有差异的超大调度器。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="6-输出系统为什么-task-output-单独做了一层"&gt;6. 输出系统：为什么 task output 单独做了一层&lt;/h2&gt;
&lt;p&gt;后台任务最核心的问题之一是输出管理。这里拆成了两层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DiskTaskOutput&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskOutput&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="61-输出架构图"&gt;6.1 输出架构图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart LR
A["Running task"] --&gt; B["TaskOutput"]
B --&gt;|"pipe mode"| C["in-memory buffer"]
B --&gt;|"overflow"| D["DiskTaskOutput"]
B --&gt;|"file mode"| D
D --&gt; E["session temp dir / taskId.output"]
E --&gt; F["TaskOutputTool"]
E --&gt; G["Read tool"]
B --&gt; H["shared poller for progress"]
&lt;/div&gt;
&lt;p&gt;设计上有两个重点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对 bash 这种文件模式，stdout/stderr 尽量绕过 JS，直接落文件&lt;/li&gt;
&lt;li&gt;对 hook/pipe 模式，先缓存在内存，超阈值再 spill to disk&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;diskOutput.ts&lt;/code&gt; 还专门做了几件工程化处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;session 级输出目录隔离，避免并发 session 互相踩&lt;/li&gt;
&lt;li&gt;&lt;code&gt;O_NOFOLLOW&lt;/code&gt; 防止符号链接攻击&lt;/li&gt;
&lt;li&gt;5GB disk cap&lt;/li&gt;
&lt;li&gt;fire-and-forget 写操作跟踪，避免测试 teardown 时出现异步悬挂&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这说明 task 输出在这里不是附属功能，而是后台任务系统的一级公民。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="7-localshelltask后台-bash-任务怎么实现"&gt;7. LocalShellTask：后台 bash 任务怎么实现&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;LocalShellTask&lt;/code&gt; 体现的是“把 shell command 变成可管理任务”。&lt;/p&gt;
&lt;h3 id="71-生命周期图"&gt;7.1 生命周期图&lt;/h3&gt;
&lt;div class="mermaid"&gt;sequenceDiagram
participant T as BashTool / caller
participant S as spawnShellTask
participant A as AppState.tasks
participant O as TaskOutput
participant N as Notification
T-&gt;&gt;S: spawnShellTask(shellCommand)
S-&gt;&gt;A: registerTask(status=running, isBackgrounded=true)
S-&gt;&gt;O: taskOutput already attached to shellCommand
S-&gt;&gt;S: shellCommand.background(taskId)
S-&gt;&gt;S: startStallWatchdog()
S-&gt;&gt;S: await shellCommand.result
S-&gt;&gt;A: update status completed/failed/killed
S-&gt;&gt;N: enqueueShellNotification()
S-&gt;&gt;O: evictTaskOutput()
&lt;/div&gt;
&lt;p&gt;这个实现里有几个很实用的点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;startStallWatchdog()&lt;/code&gt; 会观察输出 tail，检测像 &lt;code&gt;(y/n)&lt;/code&gt; 这样的交互提示&lt;/li&gt;
&lt;li&gt;前台运行过久后也可以登记成 foreground task，再 background&lt;/li&gt;
&lt;li&gt;shell task 结束后会统一发 task notification，而不是只改 state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这说明 shell task 在这里不是 “子进程句柄”，而是“带观察、通知、恢复语义的后台作业”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="8-localagenttask后台-agent-任务怎么实现"&gt;8. LocalAgentTask：后台 agent 任务怎么实现&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;LocalAgentTask&lt;/code&gt; 其实比 shell task 更复杂，因为它不只是运行，还要支持：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;进度统计&lt;/li&gt;
&lt;li&gt;activity 摘要&lt;/li&gt;
&lt;li&gt;前后台切换&lt;/li&gt;
&lt;li&gt;teammate transcript 视图&lt;/li&gt;
&lt;li&gt;retain / evictAfter&lt;/li&gt;
&lt;li&gt;AbortController 父子链&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="81-agent-任务结构图"&gt;8.1 Agent 任务结构图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart TB
A["AgentTool / caller"] --&gt; B["registerAsyncAgent or registerAgentForeground"]
B --&gt; C["LocalAgentTaskState"]
C --&gt; D["AppState.tasks"]
D --&gt; E["progress update"]
D --&gt; F["summary update"]
D --&gt; G["pending messages"]
D --&gt; H["retain / diskLoaded / evictAfter"]
D --&gt; I["backgroundAgentTask"]
D --&gt; J["killAsyncAgent"]
D --&gt; K["completeAgentTask / failAgentTask"]
&lt;/div&gt;
&lt;h3 id="82-关键设计点"&gt;8.2 关键设计点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;registerAsyncAgent()&lt;/code&gt; 是一开始就后台化&lt;/li&gt;
&lt;li&gt;&lt;code&gt;registerAgentForeground()&lt;/code&gt; 是前台执行，但保留后续 background 的可能&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backgroundAgentTask()&lt;/code&gt; 的本质是改 &lt;code&gt;isBackgrounded&lt;/code&gt; 并触发等待 promise&lt;/li&gt;
&lt;li&gt;transcript 输出不是简单写文件，而是通过 symlink 指到 agent transcript&lt;/li&gt;
&lt;li&gt;progress 不是靠任务系统轮询 tool output，而是从 agent message 流里聚合出来&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是一个很重要的区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;shell task 的“真相”主要在进程输出&lt;/li&gt;
&lt;li&gt;agent task 的“真相”主要在消息流和 transcript&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以两者虽然都叫 task，但底层观测模型完全不同。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="9-remoteagenttask后台任务还能落到远端-session"&gt;9. RemoteAgentTask：后台任务还能落到远端 session&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;RemoteAgentTask&lt;/code&gt; 说明这套 task 系统并不绑定本地进程。&lt;br&gt;
它把远端 session 也包装成统一 task state，纳入同一个 &lt;code&gt;AppState.tasks&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="91-远端任务图"&gt;9.1 远端任务图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart LR
A["Remote spawn request"] --&gt; B["RemoteAgentTaskState"]
B --&gt; C["sessionId + metadata"]
C --&gt; D["pollRemoteSessionEvents"]
D --&gt; E["appendTaskOutput"]
D --&gt; F["update task state"]
F --&gt; G["task notification"]
F --&gt; H["archive / cleanup metadata"]
&lt;/div&gt;
&lt;p&gt;这说明 task 子系统的抽象层级是“可被统一管理的异步工作单元”，而不是“本地线程/进程”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="10-停止与取出输出运行时任务对模型的桥"&gt;10. 停止与取出输出：运行时任务对模型的桥&lt;/h2&gt;
&lt;p&gt;后台任务系统主要通过两个 tool 暴露给模型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TaskStopTool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskOutputTool&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="101-停止链路图"&gt;10.1 停止链路图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart TD
A["TaskStopTool"] --&gt; B["stopTask"]
B --&gt; C["AppState.tasks lookup"]
C --&gt; D["getTaskByType"]
D --&gt; E["concreteTask.kill"]
E --&gt; F["update state / abort / cleanup"]
F --&gt; G["suppress or emit notifications"]
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;stopTask.ts&lt;/code&gt; 的设计很干净：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先查 task&lt;/li&gt;
&lt;li&gt;验证是否 running&lt;/li&gt;
&lt;li&gt;按 &lt;code&gt;task.type&lt;/code&gt; 找实现&lt;/li&gt;
&lt;li&gt;调 &lt;code&gt;kill()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，停止路径是真正用到了 &lt;code&gt;Task&lt;/code&gt; registry 的多态分发。&lt;/p&gt;
&lt;h3 id="102-输出链路图"&gt;10.2 输出链路图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart TD
A["TaskOutputTool"] --&gt; B["lookup task in AppState.tasks"]
B --&gt; C{"block?"}
C --&gt;|"false"| D["read current output"]
C --&gt;|"true"| E["waitForTaskCompletion"]
E --&gt; F["read final output"]
D --&gt; G["getTaskOutputData"]
F --&gt; G
G --&gt; H["type-specific normalization"]
H --&gt; I["tool result"]
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;TaskOutputTool&lt;/code&gt; 的一个重要信号是：它已经被标记为 deprecated，推荐直接用 &lt;code&gt;Read&lt;/code&gt; 去读 task output file。&lt;br&gt;
这说明任务系统的输出最终被收敛成“可读文件路径”这个更通用的抽象。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="11-todov2-任务清单系统"&gt;11. TodoV2 任务清单系统&lt;/h2&gt;
&lt;p&gt;另一套 &lt;code&gt;task&lt;/code&gt; 是 &lt;code&gt;utils/tasks.ts&lt;/code&gt; 驱动的结构化任务清单系统。&lt;/p&gt;
&lt;p&gt;它不是运行时进程管理，而是轻量任务数据库，底层直接用文件系统：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个 task 一个 json 文件&lt;/li&gt;
&lt;li&gt;目录按 taskListId 划分&lt;/li&gt;
&lt;li&gt;用高水位文件保证 ID 不回退&lt;/li&gt;
&lt;li&gt;用 lockfile 避免并发写冲突&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="111-任务清单存储图"&gt;11.1 任务清单存储图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart TB
A["getTaskListId"] --&gt; B["tasks dir"]
B --&gt; C["1.json"]
B --&gt; D["2.json"]
B --&gt; E["3.json"]
B --&gt; F[".highwatermark"]
B --&gt; G["lock file"]
H["createTask/updateTask/deleteTask/listTasks/blockTask"] --&gt; B
H --&gt; I["notifyTasksUpdated signal"]
&lt;/div&gt;
&lt;h3 id="112-为什么-tasklistid-设计得这么复杂"&gt;11.2 为什么 taskListId 设计得这么复杂&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;getTaskListId()&lt;/code&gt; 的优先级并不简单，因为它要让：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;独立 session&lt;/li&gt;
&lt;li&gt;team lead&lt;/li&gt;
&lt;li&gt;in-process teammate&lt;/li&gt;
&lt;li&gt;tmux/iTerm teammate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;都能落到同一套任务清单里，而不是各自有一份。&lt;/p&gt;
&lt;p&gt;所以这套系统本质上是一个“共享工作分解板”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="12-taskcreate--update--list--get-是清单系统的-api-层"&gt;12. TaskCreate / Update / List / Get 是清单系统的 API 层&lt;/h2&gt;
&lt;p&gt;这些工具并不操作 &lt;code&gt;AppState.tasks&lt;/code&gt;，而是操作 &lt;code&gt;utils/tasks.ts&lt;/code&gt; 的文件化任务清单。&lt;/p&gt;
&lt;h3 id="121-清单工具关系图"&gt;12.1 清单工具关系图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart LR
A["TaskCreateTool"] --&gt; E["utils/tasks.ts"]
B["TaskUpdateTool"] --&gt; E
C["TaskListTool"] --&gt; E
D["TaskGetTool"] --&gt; E
E --&gt; F["json task files"]
E --&gt; G["notifyTasksUpdated"]
G --&gt; H["useTasksV2 store"]
H --&gt; I["task list UI"]
&lt;/div&gt;
&lt;p&gt;几个关键点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TaskCreateTool&lt;/code&gt; 创建新任务，并自动展开 task 面板&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskUpdateTool&lt;/code&gt; 支持状态更新、owner、metadata、依赖关系修改&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskListTool&lt;/code&gt; 会过滤掉内部任务，并对已完成依赖做清理展示&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskGetTool&lt;/code&gt; 是按 ID 读取详情&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意：这里的 status 是 &lt;code&gt;pending / in_progress / completed&lt;/code&gt;，和后台任务系统的 &lt;code&gt;pending / running / completed / failed / killed&lt;/code&gt; 不是一套状态机。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="13-usetasksv2清单系统的-ui-同步层"&gt;13. useTasksV2：清单系统的 UI 同步层&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;useTasksV2.ts&lt;/code&gt; 做的不是业务逻辑，而是把文件系统任务清单转成稳定的前端 store。&lt;/p&gt;
&lt;h3 id="131-同步图"&gt;13.1 同步图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart TD
A["TasksV2Store"] --&gt; B["listTasks()"]
B --&gt; C["#tasks cache"]
C --&gt; D["useSyncExternalStore consumers"]
E["fs.watch"] --&gt; A
F["onTasksUpdated signal"] --&gt; A
G["fallback poll"] --&gt; A
A --&gt; H["hide timer"]
H --&gt; I["all completed for 5s"]
I --&gt; J["resetTaskList"]
&lt;/div&gt;
&lt;p&gt;这层设计得很务实：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多个组件共享一个 watcher，避免 mount/unmount 抖动&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fs.watch + signal + fallback poll&lt;/code&gt; 三路兜底&lt;/li&gt;
&lt;li&gt;全部完成 5 秒后自动隐藏并 reset&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以 TodoV2 清单系统不是“每次 render 去读文件”，而是一个有缓存、有 watcher、有生命周期的轻量本地数据源。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="14-两套-task-系统的关系"&gt;14. 两套 task 系统的关系&lt;/h2&gt;
&lt;p&gt;这两套系统不是上下级，而是并列存在、分别解决不同问题。&lt;/p&gt;
&lt;h3 id="141-关系图"&gt;14.1 关系图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart TB
A["Runtime Background Tasks"] --&gt; A1["what is running"]
A --&gt; A2["how to stop"]
A --&gt; A3["where is output"]
B["TodoV2 Task List"] --&gt; B1["what should be done"]
B --&gt; B2["who owns it"]
B --&gt; B3["what blocks what"]
C["Agent / Model workflow"] --&gt; A
C --&gt; B
A -. may inspire updates .-&gt; B
B -. may drive delegation .-&gt; A
&lt;/div&gt;
&lt;p&gt;用一句话概括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;后台任务系统管理“执行中的异步工作”&lt;/li&gt;
&lt;li&gt;TodoV2 管理“结构化工作计划”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两者会互相配合，但不是同一个状态模型。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="15-设计优点与代价"&gt;15. 设计优点与代价&lt;/h2&gt;
&lt;h3 id="优点"&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;后台任务和任务清单职责清晰，虽然同名但边界明确&lt;/li&gt;
&lt;li&gt;后台任务统一收口到 &lt;code&gt;AppState.tasks&lt;/code&gt;，易于做 UI 和 tool 集成&lt;/li&gt;
&lt;li&gt;输出系统独立成层，兼容本地进程、agent transcript、远端 session&lt;/li&gt;
&lt;li&gt;TodoV2 直接基于文件系统，简单、可共享、易恢复&lt;/li&gt;
&lt;li&gt;task registry 只负责最小多态分发，没有过度抽象&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="代价"&gt;代价&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;“task” 一词严重重载，初读源码很容易混淆两套系统&lt;/li&gt;
&lt;li&gt;后台任务和 TodoV2 各有自己的状态机，理解成本高&lt;/li&gt;
&lt;li&gt;某些行为跨文件分散，比如通知在具体 task 里，轮询在 framework 里，输出在 utils/task 里&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskOutputTool&lt;/code&gt; 已经开始退场，说明接口层正在演进，历史兼容负担仍在&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="16-一句话总结"&gt;16. 一句话总结&lt;/h2&gt;
&lt;p&gt;Claude Code 的 &lt;code&gt;task&lt;/code&gt; 实现不是单一模块，而是两套系统并存：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一套是运行时后台任务内核，负责异步执行、停止、输出、通知&lt;/li&gt;
&lt;li&gt;一套是 TodoV2 任务清单，负责工作分解、依赖、归属和 UI 展示&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果只看名字会觉得混乱，但从职责上看，这个切分其实是合理的。&lt;/p&gt;</description></item><item><title>Claude Code Tools 设计分析</title><link>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/claude-code-tools-architecture/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/claude-code-tools-architecture/</guid><description>&lt;h2 id="1-结论"&gt;1. 结论&lt;/h2&gt;
&lt;p&gt;Claude Code 的 &lt;code&gt;tools&lt;/code&gt; 不是一个简单的函数注册表，而是一套统一的能力运行时协议。它把同一个 tool 同时投影到四个面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模型侧：tool schema、prompt、strict、defer loading&lt;/li&gt;
&lt;li&gt;执行侧：参数校验、调用、进度、结果、中断&lt;/li&gt;
&lt;li&gt;权限侧：全局规则、工具特化规则、交互确认&lt;/li&gt;
&lt;li&gt;UI 侧：tool use、progress、result、error、grouped render&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;核心抽象集中在：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/Users/jishihe/work/civil-engineering-cloud-claude-code-source-v2.1.88/01-claude-code-source-crack/claude-code-source/src/Tool.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/Users/jishihe/work/civil-engineering-cloud-claude-code-source-v2.1.88/01-claude-code-source-crack/claude-code-source/src/services/tools/toolExecution.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/Users/jishihe/work/civil-engineering-cloud-claude-code-source-v2.1.88/01-claude-code-source-crack/claude-code-source/src/services/tools/toolOrchestration.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/Users/jishihe/work/civil-engineering-cloud-claude-code-source-v2.1.88/01-claude-code-source-crack/claude-code-source/src/hooks/useCanUseTool.tsx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/Users/jishihe/work/civil-engineering-cloud-claude-code-source-v2.1.88/01-claude-code-source-crack/claude-code-source/src/utils/api.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/Users/jishihe/work/civil-engineering-cloud-claude-code-source-v2.1.88/01-claude-code-source-crack/claude-code-source/src/utils/toolSearch.ts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="2-总体架构图"&gt;2. 总体架构图&lt;/h2&gt;
&lt;div class="mermaid"&gt;flowchart TB
A["Model / API"] --&gt; B["Tool Definition Layer"]
B --&gt; C["Execution Layer"]
B --&gt; D["Permission Layer"]
B --&gt; E["UI / Transcript Layer"]
B --&gt; F["Concrete Tools"]
C --&gt; G["tool_result back to conversation"]
D --&gt; C
F --&gt; C
F --&gt; E
subgraph Tool Definition Layer
B1["Tool interface"]
B2["buildTool defaults"]
B3["inputSchema / prompt / render / permission hooks"]
end
subgraph Execution Layer
C1["runTools"]
C2["runToolUse"]
C3["checkPermissionsAndCallTool"]
end
subgraph Permission Layer
D1["hasPermissionsToUseTool"]
D2["tool.checkPermissions"]
D3["interactive/coordinator/swarm handlers"]
end
subgraph UI / Transcript Layer
E1["renderToolUseMessage"]
E2["renderToolUseProgressMessage"]
E3["renderToolResultMessage"]
E4["extractSearchText"]
end
subgraph Concrete Tools
F1["FileReadTool"]
F2["BashTool"]
F3["MCPTool"]
F4["AgentTool"]
end
&lt;/div&gt;
&lt;p&gt;这个图表达的重点是：&lt;code&gt;Tool&lt;/code&gt; 不是只服务执行器，而是四条链路共享的合同对象。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="3-tool-对象结构图"&gt;3. Tool 对象结构图&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Tool&lt;/code&gt; 接口定义在 &lt;code&gt;Tool.ts&lt;/code&gt;，&lt;code&gt;buildTool()&lt;/code&gt; 负责补全默认实现。默认值明显偏 fail-closed：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;isConcurrencySafe -&amp;gt; false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isReadOnly -&amp;gt; false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isDestructive -&amp;gt; false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toAutoClassifierInput -&amp;gt; ''&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="mermaid"&gt;classDiagram
class Tool {
+name
+aliases
+searchHint
+inputSchema
+inputJSONSchema
+outputSchema
+call()
+description()
+prompt()
+validateInput()
+checkPermissions()
+isConcurrencySafe()
+isReadOnly()
+isDestructive()
+interruptBehavior()
+preparePermissionMatcher()
+toAutoClassifierInput()
+mapToolResultToToolResultBlockParam()
+renderToolUseMessage()
+renderToolUseProgressMessage()
+renderToolResultMessage()
+renderToolUseErrorMessage()
+renderGroupedToolUse()
+extractSearchText()
+shouldDefer
+alwaysLoad
+strict
+maxResultSizeChars
}
class ToolDef {
&lt;&lt;partial definition&gt;&gt;
}
class buildTool {
+fills safe defaults
}
ToolDef --&gt; buildTool
buildTool --&gt; Tool
&lt;/div&gt;
&lt;p&gt;这里的设计意图很明确：tool 的定义阶段就把“执行、权限、UI、模型暴露”全部收拢，而不是散落在多个 registry 中。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="4-执行时序图"&gt;4. 执行时序图&lt;/h2&gt;
&lt;p&gt;执行主链路从 &lt;code&gt;runTools()&lt;/code&gt; 到 &lt;code&gt;runToolUse()&lt;/code&gt; 再到 &lt;code&gt;checkPermissionsAndCallTool()&lt;/code&gt;。&lt;/p&gt;
&lt;div class="mermaid"&gt;sequenceDiagram
participant M as Model
participant O as toolOrchestration.runTools
participant E as toolExecution.runToolUse
participant T as Concrete Tool
participant P as Permission Bridge
participant U as UI/Transcript
M-&gt;&gt;O: tool_use blocks
O-&gt;&gt;O: partitionToolCalls()
O-&gt;&gt;E: runToolUse(tool_use)
E-&gt;&gt;E: findToolByName()
E-&gt;&gt;E: inputSchema.safeParse()
E-&gt;&gt;T: validateInput()
E-&gt;&gt;P: canUseTool(...)
P-&gt;&gt;P: hasPermissionsToUseTool()
P-&gt;&gt;T: checkPermissions()
P--&gt;&gt;E: allow / deny / ask
E-&gt;&gt;T: call(...)
T--&gt;&gt;E: progress events
E--&gt;&gt;U: progress message
T--&gt;&gt;E: ToolResult
E-&gt;&gt;T: mapToolResultToToolResultBlockParam()
E--&gt;&gt;U: tool_result message
U--&gt;&gt;M: result enters next conversation turn
&lt;/div&gt;
&lt;p&gt;链路被拆成三类关口：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;结构校验：&lt;code&gt;inputSchema.safeParse()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;语义校验：&lt;code&gt;validateInput()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;权限校验：&lt;code&gt;canUseTool()&lt;/code&gt; + &lt;code&gt;checkPermissions()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样做的好处是错误原因能被准确归类，模型也更容易修正下一次调用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="5-权限设计图"&gt;5. 权限设计图&lt;/h2&gt;
&lt;p&gt;权限系统不是单一 &lt;code&gt;allow/deny&lt;/code&gt; 开关，而是“全局规则 + 工具特化 + 交互流程”的组合。&lt;/p&gt;
&lt;div class="mermaid"&gt;flowchart TD
A["tool call request"] --&gt; B["hasPermissionsToUseTool"]
B --&gt; C{"behavior"}
C --&gt;|"allow"| D["direct allow"]
C --&gt;|"deny"| E["reject"]
C --&gt;|"ask"| F["build permission description"]
F --&gt; G{"mode / environment"}
G --&gt; H["coordinator handler"]
G --&gt; I["swarm worker handler"]
G --&gt; J["interactive dialog"]
H --&gt; K["final decision"]
I --&gt; K
J --&gt; K
K --&gt; L["allow with updatedInput"]
K --&gt; M["deny"]
B --&gt; N["tool.checkPermissions"]
N --&gt; C
&lt;/div&gt;
&lt;p&gt;关键点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;全局系统负责统一规则匹配&lt;/li&gt;
&lt;li&gt;每个工具保留 &lt;code&gt;checkPermissions()&lt;/code&gt;，处理自己才懂的语义&lt;/li&gt;
&lt;li&gt;&lt;code&gt;updatedInput&lt;/code&gt; 允许权限层改写调用参数&lt;/li&gt;
&lt;li&gt;Bash 还接了 classifier 和 speculative check&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这能避免把所有工具差异都堆进一个巨大的统一权限函数里。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="6-并发与批处理图"&gt;6. 并发与批处理图&lt;/h2&gt;
&lt;p&gt;并发不是“统一线程池策略”，而是每个 tool 自己声明 &lt;code&gt;isConcurrencySafe(input)&lt;/code&gt;，由编排器按调用顺序动态分组。&lt;/p&gt;
&lt;div class="mermaid"&gt;flowchart LR
A["tool_use list"] --&gt; B["partitionToolCalls"]
B --&gt; C{"isConcurrencySafe?"}
C --&gt;|"yes"| D["append to concurrent batch"]
C --&gt;|"no"| E["start serial batch"]
D --&gt; F["runToolsConcurrently"]
E --&gt; G["runToolsSerially"]
F --&gt; H["collect queued context modifiers"]
H --&gt; I["apply modifiers after batch"]
G --&gt; J["apply modifier immediately"]
&lt;/div&gt;
&lt;p&gt;这套设计的特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;并发安全是 tool 自声明，不是 orchestration 硬编码&lt;/li&gt;
&lt;li&gt;非安全工具串行，避免状态冲突&lt;/li&gt;
&lt;li&gt;并发批次内的 &lt;code&gt;contextModifier&lt;/code&gt; 延迟到批次完成后统一应用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这说明作者把“调度语义”也纳入了 tool contract。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="7-toolsearch--deferred-loading-图"&gt;7. ToolSearch / Deferred Loading 图&lt;/h2&gt;
&lt;p&gt;这部分是这套架构比较成熟的一点。工具太多时，问题不再是“支不支持 tool”，而是“是否值得在首轮 prompt 暴露全部 schema”。&lt;/p&gt;
&lt;div class="mermaid"&gt;flowchart TB
A["Registered tools"] --&gt; B{"shouldDefer / MCP / ToolSearch mode"}
B --&gt;|"inline"| C["toolToAPISchema normal"]
B --&gt;|"defer"| D["toolToAPISchema + defer_loading"]
C --&gt; E["API request tools array"]
D --&gt; E
E --&gt; F["Model sees inline tools"]
E --&gt; G["Deferred tools discoverable via ToolSearch"]
G --&gt; H["Model calls ToolSearchTool"]
H --&gt; I["tool_reference / discovered-tool set"]
I --&gt; J["Retry actual deferred tool"]
&lt;/div&gt;
&lt;p&gt;关键实现点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;toolToAPISchema()&lt;/code&gt; 负责把 &lt;code&gt;Tool&lt;/code&gt; 转成 API schema&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strict&lt;/code&gt;、&lt;code&gt;eager_input_streaming&lt;/code&gt;、&lt;code&gt;cache_control&lt;/code&gt;、&lt;code&gt;defer_loading&lt;/code&gt; 都在这里统一处理&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getToolSearchMode()&lt;/code&gt; 和 token/char threshold 控制是否启用动态工具加载&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个设计解决了两个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prompt 太大&lt;/li&gt;
&lt;li&gt;MCP / 扩展工具太多时首轮 schema 暴露成本过高&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="8-典型工具对比图"&gt;8. 典型工具对比图&lt;/h2&gt;
&lt;div class="mermaid"&gt;flowchart TB
subgraph Read["FileReadTool"]
R1["isConcurrencySafe = true"]
R2["isReadOnly = true"]
R3["path backfill"]
R4["read permission"]
R5["maxResultSizeChars = Infinity"]
end
subgraph Bash["BashTool"]
B1["isConcurrencySafe = isReadOnly"]
B2["command parsing"]
B3["bash permission"]
B4["progress streaming"]
B5["background task"]
B6["persist large output"]
end
subgraph MCP["MCPTool"]
M1["isMcp = true"]
M2["passthrough schema"]
M3["dynamic override by mcp client"]
M4["permission passthrough"]
end
subgraph Agent["AgentTool"]
A1["dynamic prompt from agents + MCP + permissions"]
A2["subagent/team/fork lifecycle"]
A3["agent-specific permission check"]
A4["tool-based delegation model"]
end
&lt;/div&gt;
&lt;p&gt;四个工具分别体现了不同设计重点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FileReadTool&lt;/code&gt;：安全读取和上下文控制&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BashTool&lt;/code&gt;：受控任务执行系统&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MCPTool&lt;/code&gt;：外部能力适配模板&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AgentTool&lt;/code&gt;：把子 agent 调度也纳入 tool 协议&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="9-分层职责表"&gt;9. 分层职责表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层&lt;/th&gt;
&lt;th&gt;主要对象&lt;/th&gt;
&lt;th&gt;解决的问题&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;抽象层&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tool&lt;/code&gt;, &lt;code&gt;ToolDef&lt;/code&gt;, &lt;code&gt;buildTool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;统一描述 tool 的能力、权限、UI、模型暴露&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API 映射层&lt;/td&gt;
&lt;td&gt;&lt;code&gt;toolToAPISchema&lt;/code&gt;, ToolSearch&lt;/td&gt;
&lt;td&gt;把本地 tool 转成模型能消费的 schema，控制 prompt 成本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;编排层&lt;/td&gt;
&lt;td&gt;&lt;code&gt;runTools&lt;/code&gt;, &lt;code&gt;partitionToolCalls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;基于 tool 声明的并发属性做批处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;执行层&lt;/td&gt;
&lt;td&gt;&lt;code&gt;runToolUse&lt;/code&gt;, &lt;code&gt;checkPermissionsAndCallTool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;完成 lookup、校验、权限、执行、结果回写&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;权限层&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hasPermissionsToUseTool&lt;/code&gt;, &lt;code&gt;tool.checkPermissions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;统一权限规则与工具专属规则组合&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;具体工具层&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FileReadTool&lt;/code&gt;, &lt;code&gt;BashTool&lt;/code&gt;, &lt;code&gt;MCPTool&lt;/code&gt;, &lt;code&gt;AgentTool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实现各类能力的具体运行逻辑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;表现层&lt;/td&gt;
&lt;td&gt;render / extract / grouping APIs&lt;/td&gt;
&lt;td&gt;让工具结果对用户可见、可检索、可折叠&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="10-设计优点与代价"&gt;10. 设计优点与代价&lt;/h2&gt;
&lt;h3 id="优点"&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一个 tool 对象就能覆盖模型、执行、权限、UI 四个面&lt;/li&gt;
&lt;li&gt;&lt;code&gt;buildTool()&lt;/code&gt; 提供保守默认值，安全边界清晰&lt;/li&gt;
&lt;li&gt;并发是声明式的，扩展新工具成本低&lt;/li&gt;
&lt;li&gt;权限支持工具特化，不会退化成全局 &lt;code&gt;switch(tool.name)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;ToolSearch 明确把 prompt budget 当成系统级问题处理&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="代价"&gt;代价&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Tool&lt;/code&gt; 接口很重，接入新工具需要理解的维度较多&lt;/li&gt;
&lt;li&gt;UI 渲染逻辑和执行协议耦合在同一个对象中，抽象不够纯&lt;/li&gt;
&lt;li&gt;重工具如 &lt;code&gt;BashTool&lt;/code&gt;、&lt;code&gt;AgentTool&lt;/code&gt; 已经接近子系统复杂度&lt;/li&gt;
&lt;li&gt;feature flag、provider、model 能力会影响行为，阅读成本高&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="11-一句话总结"&gt;11. 一句话总结&lt;/h2&gt;
&lt;p&gt;Claude Code 的 &lt;code&gt;tools&lt;/code&gt; 设计本质上是一套“统一能力运行时协议”。&lt;br&gt;
它并不是把模型调用转发给几个本地函数，而是把 schema、权限、执行、并发、UI 和 transcript 统一建模为同一个 Tool 合同。&lt;/p&gt;</description></item><item><title>Claude Code 源码架构文档</title><link>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/claude-code-source-architecture/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/claude-code-source-architecture/</guid><description>&lt;p&gt;基于 &lt;code&gt;@anthropic-ai/claude-code&lt;/code&gt; v2.1.88 还原源码梳理。&lt;/p&gt;
&lt;h2 id="1-架构结论"&gt;1. 架构结论&lt;/h2&gt;
&lt;p&gt;Claude Code 不是一个“简单 CLI”，而是一个**单进程宿主（host）+ 会话引擎（conversation engine）+ 工具平台（tool platform）+ 多代理任务系统（multi-agent task runtime）**的 TypeScript/Bun 单体应用。&lt;/p&gt;
&lt;p&gt;它的核心特征有 4 个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;单进程多入口&lt;/strong&gt;
&lt;code&gt;src/entrypoints/cli.tsx&lt;/code&gt; 先做轻量分流，按命令进入普通 REPL、headless print/SDK、bridge、daemon、remote control 等不同运行形态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统一会话内核&lt;/strong&gt;
无论是交互式 REPL 还是非交互 SDK，核心都汇聚到 &lt;code&gt;QueryEngine&lt;/code&gt; / &lt;code&gt;query()&lt;/code&gt; 这条消息循环。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具优先的 Agent 运行时&lt;/strong&gt;
模型只负责生成消息和 tool_use，真正执行文件、shell、MCP、子代理、远端任务的是本地工具与任务系统。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;产品线式编译&lt;/strong&gt;
&lt;code&gt;build.ts&lt;/code&gt; 用 Bun 的 &lt;code&gt;feature()&lt;/code&gt; 做编译期开关，很多内部能力通过 dead code elimination 被裁掉，因此“Claude Code 架构”本质上是一个&lt;strong&gt;可裁剪产品线架构&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-整体架构图"&gt;2. 整体架构图&lt;/h2&gt;
&lt;div class="mermaid"&gt;flowchart TD
U["User / SDK / Remote Caller"]
subgraph Build["Build-Time Product Line"]
B1["build.ts"]
B2["Feature Flags&lt;br/&gt;feature()"]
B3["MACRO constants"]
B4["dist/cli.js"]
B1 --&gt; B2
B1 --&gt; B3
B2 --&gt; B4
B3 --&gt; B4
end
U --&gt; E1
subgraph Entry["Entry &amp; Bootstrap"]
E1["src/entrypoints/cli.tsx"]
E2["src/main.tsx"]
E3["src/entrypoints/init.ts"]
E4["src/setup.ts"]
E1 --&gt; E2
E2 --&gt; E3
E2 --&gt; E4
end
subgraph Surface["Runtime Surface"]
S1["Interactive REPL&lt;br/&gt;screens/REPL.tsx"]
S2["Headless / SDK&lt;br/&gt;cli/print.ts"]
S3["Bridge / Remote Control&lt;br/&gt;bridge/*"]
S4["Remote Session Viewer&lt;br/&gt;remote/*"]
end
E2 --&gt; S1
E2 --&gt; S2
E1 --&gt; S3
E2 --&gt; S4
subgraph UI["Terminal UI Layer"]
UI1["src/ink/*"]
UI2["components/*"]
UI3["state/AppStateStore.ts"]
end
S1 --&gt; UI1
S1 --&gt; UI2
S1 --&gt; UI3
subgraph Core["Conversation Core"]
C1["QueryEngine.ts"]
C2["query.ts"]
C3["context.ts"]
C4["constants/prompts + system prompt assembly"]
end
S1 --&gt; C1
S2 --&gt; C1
C1 --&gt; C2
C1 --&gt; C3
C1 --&gt; C4
subgraph Exec["Execution Plane"]
T1["Tool.ts / tools.ts"]
T2["services/tools/*"]
T3["tools/*"]
T4["tasks/*"]
T5["tools/AgentTool/*"]
end
C2 --&gt; T2
T2 --&gt; T1
T2 --&gt; T3
T3 --&gt; T4
T3 --&gt; T5
T5 --&gt; C2
subgraph Integrations["Integrations &amp; Services"]
I1["services/api/*"]
I2["services/mcp/*"]
I3["services/lsp/*"]
I4["services/analytics/*"]
I5["remoteManagedSettings / policyLimits / settingsSync"]
I6["sessionStorage / memory / CLAUDE.md"]
end
C2 --&gt; I1
T3 --&gt; I2
S1 --&gt; I3
E2 --&gt; I4
E2 --&gt; I5
C1 --&gt; I6
&lt;/div&gt;
&lt;h3 id="纯文本分层图"&gt;纯文本分层图&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Shell / SDK / Remote caller
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; entrypoints/cli.tsx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; main.tsx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; init.ts + setup.ts
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; REPL (interactive) / print.ts (headless) / bridge / remote
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; QueryEngine + query loop
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; Claude API stream
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; Tool orchestration
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; local tools
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; MCP tools/resources
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; shell/file tools
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; AgentTool / subagent
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; remote/background tasks
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; session persistence / telemetry / memory / policy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="3-源码分层"&gt;3. 源码分层&lt;/h2&gt;
&lt;p&gt;按目录统计，源码最重的部分不是“入口”而是“能力面”：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;目录&lt;/th&gt;
&lt;th style="text-align: right"&gt;文件数（近似）&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utils&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;567&lt;/td&gt;
&lt;td&gt;共用基础设施、状态操作、IO、git、session、权限、模型工具&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;components&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;390&lt;/td&gt;
&lt;td&gt;终端 UI 组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;commands&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;209&lt;/td&gt;
&lt;td&gt;slash command / 子命令体系&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tools&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;190&lt;/td&gt;
&lt;td&gt;供模型调用的工具实现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;services&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;133&lt;/td&gt;
&lt;td&gt;API、MCP、LSP、analytics、compact、memory 等服务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hooks&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;104&lt;/td&gt;
&lt;td&gt;交互与生命周期 hook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ink&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;98&lt;/td&gt;
&lt;td&gt;自研终端渲染层&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这说明它本质上是一个&lt;strong&gt;宿主型应用&lt;/strong&gt;：入口薄，运行时能力厚。&lt;/p&gt;
&lt;h2 id="4-启动架构"&gt;4. 启动架构&lt;/h2&gt;
&lt;h3 id="41-第一层入口srcentrypointsclitsx"&gt;4.1 第一层入口：&lt;code&gt;src/entrypoints/cli.tsx&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这一层的职责是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提供超轻量 fast path，例如 &lt;code&gt;--version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在真正加载大模块前分流特殊模式&lt;/li&gt;
&lt;li&gt;通过动态 import 避免不必要的模块求值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可理解为一个 &lt;strong&gt;boot dispatcher&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="42-第二层入口srcmaintsx"&gt;4.2 第二层入口：&lt;code&gt;src/main.tsx&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这一层是真正的主控器，职责非常重：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始化 warning handler、信号处理&lt;/li&gt;
&lt;li&gt;处理深链路、direct connect、ssh、assistant、bridge 等特殊入口&lt;/li&gt;
&lt;li&gt;用 Commander 建立完整 CLI 语义&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;preAction&lt;/code&gt; 中统一调用 &lt;code&gt;init()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;将运行模式导向 REPL、print、bridge、remote 等不同表面&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此 &lt;code&gt;main.tsx&lt;/code&gt; 是&lt;strong&gt;运行时编排器&lt;/strong&gt;，不是单纯参数解析器。&lt;/p&gt;
&lt;h3 id="43-初始化srcentrypointsinitts"&gt;4.3 初始化：&lt;code&gt;src/entrypoints/init.ts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;init()&lt;/code&gt; 负责做“可信但尽量轻”的全局初始化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;启用配置系统&lt;/li&gt;
&lt;li&gt;应用安全环境变量&lt;/li&gt;
&lt;li&gt;配置 mTLS / proxy / preconnect&lt;/li&gt;
&lt;li&gt;初始化 telemetry、remote managed settings、policy limits&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;h3 id="44-会话级准备srcsetupts"&gt;4.4 会话级准备：&lt;code&gt;src/setup.ts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;setup()&lt;/code&gt; 偏向“当前会话环境落地”，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cwd 与项目根建立&lt;/li&gt;
&lt;li&gt;hook snapshot 与 file watcher 初始化&lt;/li&gt;
&lt;li&gt;worktree / tmux 建立&lt;/li&gt;
&lt;li&gt;session memory、release notes、terminal backup 恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;init.ts&lt;/code&gt; 更像&lt;strong&gt;进程级初始化&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setup.ts&lt;/code&gt; 更像&lt;strong&gt;会话级初始化&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="5-交互表面架构"&gt;5. 交互表面架构&lt;/h2&gt;
&lt;p&gt;Claude Code 有多个运行表面，但复用同一套内核。&lt;/p&gt;
&lt;h3 id="51-交互式模式"&gt;5.1 交互式模式&lt;/h3&gt;
&lt;p&gt;主链路：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;main.tsx -&amp;gt; createRoot() -&amp;gt; replLauncher.tsx -&amp;gt; components/App -&amp;gt; screens/REPL.tsx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/ink.ts&lt;/code&gt; 与 &lt;code&gt;src/ink/root.ts&lt;/code&gt; 提供自研终端 React 渲染根&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/screens/REPL.tsx&lt;/code&gt; 是主交互容器&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/state/AppStateStore.ts&lt;/code&gt; 管理 UI、MCP、任务、bridge、remote viewer 等状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套设计说明 Claude Code 不是“命令执行器包一层 UI”，而是&lt;strong&gt;先有终端应用框架，再承载 Agent&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="52-headless--sdk-模式"&gt;5.2 Headless / SDK 模式&lt;/h3&gt;
&lt;p&gt;主链路：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;main.tsx -&amp;gt; cli/print.ts -&amp;gt; QueryEngine.ask()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这一层负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;结构化输入输出&lt;/li&gt;
&lt;li&gt;stream-json / text / json 输出协议&lt;/li&gt;
&lt;li&gt;SDK control 消息&lt;/li&gt;
&lt;li&gt;权限请求桥接&lt;/li&gt;
&lt;li&gt;headless 的会话恢复与插件/MCP 装配&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此 print 模式本质上是&lt;strong&gt;无 UI 的协议适配层&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="53-bridge--remote--viewer"&gt;5.3 Bridge / Remote / Viewer&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bridge/*&lt;/code&gt;：本机作为远端可控执行环境&lt;/li&gt;
&lt;li&gt;&lt;code&gt;remote/*&lt;/code&gt;：连接远端 session，接收 SDK 消息和权限请求&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server/*&lt;/code&gt;：direct connect / session creation 一类能力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是 Claude Code 向“本地 CLI”之外扩展的关键：它开始具备&lt;strong&gt;双向远程会话宿主&lt;/strong&gt;能力。&lt;/p&gt;
&lt;h2 id="6-会话内核queryengine--query"&gt;6. 会话内核：&lt;code&gt;QueryEngine&lt;/code&gt; + &lt;code&gt;query()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;这是整个系统最核心的层。&lt;/p&gt;
&lt;h3 id="61-queryenginets"&gt;6.1 &lt;code&gt;QueryEngine.ts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;QueryEngine&lt;/code&gt; 持有一段会话生命周期内的核心状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mutableMessages&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readFileState&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;permission denials&lt;/li&gt;
&lt;li&gt;usage 累计&lt;/li&gt;
&lt;li&gt;abort controller&lt;/li&gt;
&lt;li&gt;discovered skills / memory path 等 turn/session 级上下文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它的角色是&lt;strong&gt;面向会话的外观层（session facade）&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="62-queryts"&gt;6.2 &lt;code&gt;query.ts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;query()&lt;/code&gt; 才是真正的 turn loop：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;拼接系统提示与上下文&lt;/li&gt;
&lt;li&gt;处理 message normalization&lt;/li&gt;
&lt;li&gt;发起 Claude API 请求&lt;/li&gt;
&lt;li&gt;处理 streaming 消息&lt;/li&gt;
&lt;li&gt;提取 &lt;code&gt;tool_use&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;执行工具&lt;/li&gt;
&lt;li&gt;将 &lt;code&gt;tool_result&lt;/code&gt; 回注消息流&lt;/li&gt;
&lt;li&gt;继续下一轮直到终止&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它的角色是&lt;strong&gt;面向单次 agentic turn 的状态机&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="63-共享内核的意义"&gt;6.3 共享内核的意义&lt;/h3&gt;
&lt;p&gt;REPL 和 print/SDK 都复用这套内核，意味着架构上做了明确分离：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上层负责“人机交互/协议”&lt;/li&gt;
&lt;li&gt;下层负责“消息循环/工具回路”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是本项目最正确的一刀。&lt;/p&gt;
&lt;h2 id="7-单轮执行时序图"&gt;7. 单轮执行时序图&lt;/h2&gt;
&lt;div class="mermaid"&gt;sequenceDiagram
participant User as User / SDK
participant Surface as REPL or print.ts
participant Engine as QueryEngine
participant Loop as query()
participant API as services/api/claude.ts
participant Tools as services/tools/*
participant Impl as tools/* / MCP / tasks
User-&gt;&gt;Surface: prompt / input event
Surface-&gt;&gt;Engine: submitMessage() / ask()
Engine-&gt;&gt;Engine: processUserInput()
Engine-&gt;&gt;Loop: query(params)
Loop-&gt;&gt;API: stream message request
API--&gt;&gt;Loop: assistant deltas / tool_use
Loop-&gt;&gt;Tools: runTools() / StreamingToolExecutor
Tools-&gt;&gt;Impl: execute tool
Impl--&gt;&gt;Tools: tool_result / progress
Tools--&gt;&gt;Loop: Message updates + new context
Loop-&gt;&gt;API: next round with tool_result
API--&gt;&gt;Loop: final assistant message
Loop--&gt;&gt;Engine: messages + usage + terminal result
Engine--&gt;&gt;Surface: SDKMessage / UI state updates
&lt;/div&gt;
&lt;h2 id="8-工具平台架构"&gt;8. 工具平台架构&lt;/h2&gt;
&lt;h3 id="81-元模型srctoolts"&gt;8.1 元模型：&lt;code&gt;src/Tool.ts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这里定义了工具系统的核心抽象：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tool schema&lt;/li&gt;
&lt;li&gt;permission context&lt;/li&gt;
&lt;li&gt;tool use context&lt;/li&gt;
&lt;li&gt;progress / UI 回调&lt;/li&gt;
&lt;li&gt;app state 访问&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;ToolUseContext&lt;/code&gt; 很关键，它不是简单参数包，而是&lt;strong&gt;运行时能力注入容器&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="82-工具注册srctoolsts"&gt;8.2 工具注册：&lt;code&gt;src/tools.ts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tools.ts&lt;/code&gt; 负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;聚合所有内建工具&lt;/li&gt;
&lt;li&gt;按 feature flag / env / policy 暴露工具&lt;/li&gt;
&lt;li&gt;根据运行环境裁剪工具集&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此工具系统不是“扫描目录自动发现”，而是&lt;strong&gt;显式装配、可裁剪、可做 prompt cache 稳定控制的工具池&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="83-工具调度servicestools"&gt;8.3 工具调度：&lt;code&gt;services/tools/*&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;关键模块：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;toolOrchestration.ts&lt;/code&gt;：按并发安全性分批执行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StreamingToolExecutor.ts&lt;/code&gt;：边流式产生 tool_use 边执行，并维护有序产出&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toolExecution.ts&lt;/code&gt;：单个工具执行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里的设计很成熟：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只读 / 并发安全工具可并行&lt;/li&gt;
&lt;li&gt;有副作用工具串行&lt;/li&gt;
&lt;li&gt;即使并行执行，也保证结果按原始 tool_use 顺序回吐&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这说明架构目标不是“最大吞吐”，而是&lt;strong&gt;有约束的并行&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="9-多代理与任务系统"&gt;9. 多代理与任务系统&lt;/h2&gt;
&lt;p&gt;这是 Claude Code 区别于普通 CLI Agent 的另一核心。&lt;/p&gt;
&lt;h3 id="91-agenttool"&gt;9.1 AgentTool&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tools/AgentTool/AgentTool.tsx&lt;/code&gt; 是多代理入口，支持：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新子代理&lt;/li&gt;
&lt;li&gt;指定 agent type&lt;/li&gt;
&lt;li&gt;背景运行&lt;/li&gt;
&lt;li&gt;worktree 隔离&lt;/li&gt;
&lt;li&gt;remote 隔离&lt;/li&gt;
&lt;li&gt;team / teammate 模式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它既是一个工具，也是&lt;strong&gt;任务系统的控制面入口&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="92-runagent"&gt;9.2 runAgent&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tools/AgentTool/runAgent.ts&lt;/code&gt; 做的事情包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;组装 agent 的 system prompt&lt;/li&gt;
&lt;li&gt;为 agent 初始化专属 MCP server&lt;/li&gt;
&lt;li&gt;克隆 / 隔离 ToolUseContext&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;query()&lt;/code&gt; 运行子代理&lt;/li&gt;
&lt;li&gt;记录 sidechain transcript&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，子代理不是一个特别的协议对象，本质上仍然是&lt;strong&gt;另一个 QueryEngine/query runtime&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="93-任务系统"&gt;9.3 任务系统&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Task.ts&lt;/code&gt; + &lt;code&gt;tasks/*&lt;/code&gt; 定义统一任务抽象，当前主要有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;local_bash&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;local_agent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;remote_agent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;in_process_teammate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dream&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LocalAgentTask&lt;/code&gt; 管后台本地 agent 的状态、输出文件、通知、前后台切换&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RemoteAgentTask&lt;/code&gt; 管远端 Claude.ai/CCR session 的轮询、恢复、归档&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LocalShellTask&lt;/code&gt; 管 Bash/PowerShell 等 shell 任务&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此任务系统的作用不是“仅做 UI 展示”，而是&lt;strong&gt;把长生命周期执行单元从 Query 主循环中拆出来管理&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="10-mcp-架构"&gt;10. MCP 架构&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;services/mcp/client.ts&lt;/code&gt; 是另一个核心模块。&lt;/p&gt;
&lt;p&gt;它负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;连接 MCP server（stdio / SSE / streamable HTTP / websocket）&lt;/li&gt;
&lt;li&gt;OAuth / auth 处理&lt;/li&gt;
&lt;li&gt;拉取 tools / prompts / resources&lt;/li&gt;
&lt;li&gt;将 MCP 工具转成本地可用 Tool&lt;/li&gt;
&lt;li&gt;做工具结果截断、持久化、二进制落盘&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;MCPConnectionManager.tsx&lt;/code&gt; 则把连接管理嵌入到 UI 上下文。&lt;/p&gt;
&lt;p&gt;所以 MCP 在 Claude Code 里不是附属插件，而是&lt;strong&gt;一级能力平面&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="11-状态架构"&gt;11. 状态架构&lt;/h2&gt;
&lt;p&gt;Claude Code 有两层状态：&lt;/p&gt;
&lt;h3 id="111-进程级全局状态bootstrapstatets"&gt;11.1 进程级全局状态：&lt;code&gt;bootstrap/state.ts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这里放的是进程级 latch 和统计：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cwd / sessionId / model / telemetry&lt;/li&gt;
&lt;li&gt;prompt cache 相关 sticky flag&lt;/li&gt;
&lt;li&gt;invoked skills&lt;/li&gt;
&lt;li&gt;global counters&lt;/li&gt;
&lt;li&gt;session lineage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这层更像&lt;strong&gt;process runtime registry&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="112-ui--会话视图状态stateappstatestorets"&gt;11.2 UI / 会话视图状态：&lt;code&gt;state/AppStateStore.ts&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这里放的是当前交互态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tasks&lt;/li&gt;
&lt;li&gt;mcp clients/tools/resources&lt;/li&gt;
&lt;li&gt;plugin state&lt;/li&gt;
&lt;li&gt;permission context&lt;/li&gt;
&lt;li&gt;当前 viewing agent / foregrounded task&lt;/li&gt;
&lt;li&gt;bridge / remote viewer 状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这层更像&lt;strong&gt;presentation-oriented session state&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="12-上下文与记忆架构"&gt;12. 上下文与记忆架构&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;context.ts&lt;/code&gt; 暴露两类核心上下文：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;getSystemContext()&lt;/code&gt;：git 状态、cache breaker 等系统级上下文&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getUserContext()&lt;/code&gt;：CLAUDE.md、日期、memory 文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这说明 Claude Code 的“记忆”首先不是向量库，而是&lt;strong&gt;文件化上下文 + 会话附着上下文&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLAUDE.md&lt;/li&gt;
&lt;li&gt;memory files&lt;/li&gt;
&lt;li&gt;session transcript&lt;/li&gt;
&lt;li&gt;task output sidechain&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这和 IDE/代码代理场景非常一致，也降低了外部存储依赖。&lt;/p&gt;
&lt;h2 id="13-编译期产品线架构"&gt;13. 编译期产品线架构&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;build.ts&lt;/code&gt; 非常重要，因为它决定“最终产品到底长什么样”。&lt;/p&gt;
&lt;p&gt;它通过：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;featureFlags&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MACRO.*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Bun bundler 的 &lt;code&gt;feature()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;做编译期裁剪。&lt;/p&gt;
&lt;p&gt;这意味着：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;源码里存在一套比公开 npm 包更大的能力面&lt;/li&gt;
&lt;li&gt;外部版本只是该产品线上的一个裁剪结果&lt;/li&gt;
&lt;li&gt;架构分析必须区分“源码全量能力”与“external build 默认能力”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这是理解 Claude Code 源码时最容易被忽略的一点。&lt;/p&gt;
&lt;h2 id="14-关键设计判断"&gt;14. 关键设计判断&lt;/h2&gt;
&lt;h3 id="141-它是单体但不是臃肿单体"&gt;14.1 它是单体，但不是臃肿单体&lt;/h3&gt;
&lt;p&gt;虽然文件很多，但主分层其实很清晰：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;入口层&lt;/li&gt;
&lt;li&gt;交互层&lt;/li&gt;
&lt;li&gt;查询内核&lt;/li&gt;
&lt;li&gt;工具执行层&lt;/li&gt;
&lt;li&gt;集成服务层&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是一种&lt;strong&gt;宿主型单体（host monolith）&lt;/strong&gt;，不是业务脚本堆积。&lt;/p&gt;
&lt;h3 id="142-query-loop-是绝对中心"&gt;14.2 Query loop 是绝对中心&lt;/h3&gt;
&lt;p&gt;真正的中心不是 UI、不是 commands、也不是 tools，而是：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;QueryEngine -&amp;gt; query() -&amp;gt; API/tool round-trip&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;其它几乎都围绕这条环工作。&lt;/p&gt;
&lt;h3 id="143-tool-与-task-是两个层次"&gt;14.3 Tool 与 Task 是两个层次&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Tool：模型可调用的能力接口&lt;/li&gt;
&lt;li&gt;Task：长生命周期、可恢复、可后台化的执行单元&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两层分开是对的，否则后台 agent、remote session、shell job 会把 query loop 搞得非常混乱。&lt;/p&gt;
&lt;h3 id="144-repl-与-sdk-共享内核是最值钱的抽象"&gt;14.4 REPL 与 SDK 共享内核是最值钱的抽象&lt;/h3&gt;
&lt;p&gt;这让 Claude Code 同时具备：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;终端产品&lt;/li&gt;
&lt;li&gt;SDK 运行时&lt;/li&gt;
&lt;li&gt;远端会话宿主&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而不用维护三套 agent 内核。&lt;/p&gt;
&lt;h2 id="15-一句话总结"&gt;15. 一句话总结&lt;/h2&gt;
&lt;p&gt;Claude Code 的源码可以概括为：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一个用 Bun 构建、以 &lt;code&gt;query()&lt;/code&gt; 为中心、以 Tool/Task 为执行平面、以 REPL/SDK/Bridge 为多表面的可裁剪单体 Agent 运行时。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果后续要继续拆专题，建议优先再写 4 篇：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;QueryEngine/query&lt;/code&gt; 详细执行流&lt;/li&gt;
&lt;li&gt;Tool/Task/AgentTool 三层关系&lt;/li&gt;
&lt;li&gt;MCP 接入与权限/认证模型&lt;/li&gt;
&lt;li&gt;REPL UI 状态机与自研 Ink 渲染层&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Claude Code 系统提示词设计整理</title><link>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/2026-05-11-claude-code-system-prompt-design/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/2026-05-11-claude-code-system-prompt-design/</guid><description>&lt;h2 id="目标"&gt;目标&lt;/h2&gt;
&lt;p&gt;这份文档整理 Claude Code 在一次 query 中如何构造最终发给模型的 prompt，重点说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;system prompt 由哪些模块组成&lt;/li&gt;
&lt;li&gt;哪些内容其实不在 system，而是在 messages 里&lt;/li&gt;
&lt;li&gt;CLAUDE.md、memory、git status、日期、MCP 指令分别从哪里进入&lt;/li&gt;
&lt;li&gt;prompt cache 为什么要把 system prompt 切成静态和动态两段&lt;/li&gt;
&lt;li&gt;本地日志默认能看到什么，不能看到什么&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里说的“最终 prompt”不是单一字符串，而是一次 API 请求中的两部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;system&lt;/code&gt; blocks&lt;/li&gt;
&lt;li&gt;&lt;code&gt;messages&lt;/code&gt; 数组&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Claude Code 的设计重点不是把所有东西硬拼成一大段文本，而是把不同来源的信息按职责分层，再在发请求前统一装配。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一总览最终请求是怎么拼出来的"&gt;一、总览：最终请求是怎么拼出来的&lt;/h2&gt;
&lt;p&gt;核心链路可以概括成：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;getSystemPrompt()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+ getSystemContext()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+ getUserContext()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+ 当前会话消息
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;query.ts 中组装
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services/api/claude.ts 中规范化并追加前缀
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;buildSystemPromptBlocks()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;anthropic.beta.messages.create(...)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;更具体一点：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;静态 system sections
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+ 动态 system sections
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+ systemContext(git status 等)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;= fullSystemPrompt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;userContext(claudeMd, currentDate 等)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;+ 原始 messages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;= messages with prepended meta context
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fullSystemPrompt + normalized messages + tools
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;= 最终 API 请求
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关键文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/query.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/utils/queryContext.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/constants/prompts.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/context.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/utils/api.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/services/api/claude.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/constants/system.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/utils/claudemd.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/memdir/memdir.ts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="二最上游先取三类基础材料"&gt;二、最上游：先取三类基础材料&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;src/utils/queryContext.ts&lt;/code&gt; 里，Claude Code 会先取三类基础输入：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;defaultSystemPrompt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;userContext&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;systemContext&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对应实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fetchSystemPromptParts()&lt;/code&gt; in &lt;code&gt;src/utils/queryContext.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getSystemPrompt()&lt;/code&gt; in &lt;code&gt;src/constants/prompts.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getUserContext()&lt;/code&gt; in &lt;code&gt;src/context.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getSystemContext()&lt;/code&gt; in &lt;code&gt;src/context.ts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一步的设计很清楚：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;system prompt&lt;/code&gt; 负责长期稳定的行为规则&lt;/li&gt;
&lt;li&gt;&lt;code&gt;systemContext&lt;/code&gt; 负责当前环境快照&lt;/li&gt;
&lt;li&gt;&lt;code&gt;userContext&lt;/code&gt; 负责以 meta message 形式注入的补充上下文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，Claude Code 并没有把所有上下文都塞进 system prompt，而是故意拆成了两条通道。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三system-prompt-的主体getsystemprompt"&gt;三、system prompt 的主体：&lt;code&gt;getSystemPrompt()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src/constants/prompts.ts&lt;/code&gt; 是 system prompt 的主装配器。&lt;/p&gt;
&lt;h3 id="31-静态部分"&gt;3.1 静态部分&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;getSystemPrompt()&lt;/code&gt; 的返回结果里，前半段是静态内容，也就是跨 turn 尽量稳定的部分。主要包含：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Intro&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;说明“你是 Claude Code，Anthropic 的官方 CLI”&lt;/li&gt;
&lt;li&gt;说明核心任务是帮助用户完成软件工程任务&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;# System&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户看到的文本规则&lt;/li&gt;
&lt;li&gt;工具权限模型&lt;/li&gt;
&lt;li&gt;如何对待 &lt;code&gt;&amp;lt;system-reminder&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如何对待外部工具返回数据&lt;/li&gt;
&lt;li&gt;hook 机制&lt;/li&gt;
&lt;li&gt;上下文自动压缩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;# Doing tasks&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;软件工程任务的默认行为规范&lt;/li&gt;
&lt;li&gt;不要过度设计&lt;/li&gt;
&lt;li&gt;优先修改已有文件&lt;/li&gt;
&lt;li&gt;安全要求&lt;/li&gt;
&lt;li&gt;不要虚报验证结果&lt;/li&gt;
&lt;li&gt;遇到 Claude Code 本身的问题时如何建议用户 &lt;code&gt;/issue&lt;/code&gt; 或 &lt;code&gt;/share&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;# Executing actions with care&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;哪些操作需要谨慎&lt;/li&gt;
&lt;li&gt;哪些操作风险大&lt;/li&gt;
&lt;li&gt;为什么不能随便用破坏性命令&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;# Using your tools&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优先用专用工具&lt;/li&gt;
&lt;li&gt;何时用 Bash&lt;/li&gt;
&lt;li&gt;何时用 Agent&lt;/li&gt;
&lt;li&gt;何时用 Skill&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;# Tone and style&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输出风格&lt;/li&gt;
&lt;li&gt;引用代码位置格式&lt;/li&gt;
&lt;li&gt;工具调用前不要写冒号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;# Communicating with the user&lt;/code&gt; 或 &lt;code&gt;# Output efficiency&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户可见文本的写法&lt;/li&gt;
&lt;li&gt;什么时候要给进度更新&lt;/li&gt;
&lt;li&gt;简洁但不能丢关键信息&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些静态 sections 主要在 &lt;code&gt;src/constants/prompts.ts&lt;/code&gt; 里定义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;getSimpleIntroSection()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getSimpleSystemSection()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getSimpleDoingTasksSection()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getActionsSection()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getUsingYourToolsSection()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getSimpleToneAndStyleSection()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getOutputEfficiencySection()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="32-动态部分"&gt;3.2 动态部分&lt;/h3&gt;
&lt;p&gt;在静态部分后面，Claude Code 会追加动态 sections。动态 sections 的定义在 &lt;code&gt;src/constants/prompts.ts&lt;/code&gt; 的 &lt;code&gt;dynamicSections&lt;/code&gt; 数组。&lt;/p&gt;
&lt;p&gt;这部分通常包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;session_guidance&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前会话下的工具使用建议&lt;/li&gt;
&lt;li&gt;例如什么时候应该让用户自己跑 &lt;code&gt;! command&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;什么时候该用 Agent 或 Explore&lt;/li&gt;
&lt;li&gt;skills 如何触发&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;memory&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;memory 机制说明&lt;/li&gt;
&lt;li&gt;告诉模型 persistent memory 在哪里&lt;/li&gt;
&lt;li&gt;哪些记忆该存，哪些不该存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MEMORY.md&lt;/code&gt; 如何做索引&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ant_model_override&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anthropic 内部场景下的额外 suffix&lt;/li&gt;
&lt;li&gt;external 构建通常不会有&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;env_info_simple&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前环境信息&lt;/li&gt;
&lt;li&gt;working directory、是否 git repo、平台、shell、OS、模型信息、知识截止时间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;language&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户配置了语言偏好时追加&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;output_style&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户配置了自定义输出风格时追加&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;mcp_instructions&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;各个 MCP server 自己提供的 instructions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;scratchpad&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;scratchpad 相关指令&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;frc&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;function result clearing 相关指令&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;summarize_tool_results&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;工具结果摘要策略&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;feature flag 控制的额外 section&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;numeric length anchors&lt;/li&gt;
&lt;li&gt;token budget&lt;/li&gt;
&lt;li&gt;brief&lt;/li&gt;
&lt;li&gt;proactive 相关能力&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这部分的意义是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;静态 prompt 负责通用行为原则&lt;/li&gt;
&lt;li&gt;动态 prompt 负责当前会话和当前环境的变化面&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样才能兼顾稳定性和灵活性。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四cli-sysprompt-prefix-其实只有一句话"&gt;四、&lt;code&gt;CLI sysprompt prefix&lt;/code&gt; 其实只有一句话&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;src/services/api/claude.ts&lt;/code&gt; 中，真正发送请求前，还会把 &lt;code&gt;getCLISyspromptPrefix()&lt;/code&gt; 的结果插到最前面。&lt;/p&gt;
&lt;p&gt;这个前缀定义在 &lt;code&gt;src/constants/system.ts&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它一共有 3 个候选值：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;默认交互式 CLI&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;You are Claude Code, Anthropic&amp;#39;s official CLI for Claude.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;
&lt;li&gt;非交互式，并且带 appendSystemPrompt&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;You are Claude Code, Anthropic&amp;#39;s official CLI for Claude, running within the Claude Agent SDK.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="3"&gt;
&lt;li&gt;非交互式，但没有 appendSystemPrompt&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;You are a Claude agent, built on Anthropic&amp;#39;s Claude Agent SDK.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;所以 &lt;code&gt;CLI sysprompt prefix&lt;/code&gt; 不是一大段规则，它只是一个“身份和运行形态声明”。&lt;/p&gt;
&lt;p&gt;它不包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tools 说明&lt;/li&gt;
&lt;li&gt;git status&lt;/li&gt;
&lt;li&gt;CLAUDE.md&lt;/li&gt;
&lt;li&gt;日期&lt;/li&gt;
&lt;li&gt;memory 内容&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些都来自别的层。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五真正发出前system-prompt-还会再包两层"&gt;五、真正发出前，system prompt 还会再包两层&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;src/services/api/claude.ts&lt;/code&gt; 中，system prompt 最后会被组装成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;getAttributionHeader(fingerprint)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getCLISyspromptPrefix(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;...systemPrompt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;某些可选附加指令
&lt;ul&gt;
&lt;li&gt;advisor instructions&lt;/li&gt;
&lt;li&gt;chrome tool search instructions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以最终的 &lt;code&gt;systemPrompt&lt;/code&gt; 不是只有 &lt;code&gt;prompts.ts&lt;/code&gt; 出来的正文，还多了两层外围包装：&lt;/p&gt;
&lt;h3 id="51-attribution-header"&gt;5.1 Attribution header&lt;/h3&gt;
&lt;p&gt;这部分来自 &lt;code&gt;src/constants/system.ts&lt;/code&gt; 的 &lt;code&gt;getAttributionHeader()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它大概长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;x-anthropic-billing-header: cc_version=...; cc_entrypoint=...;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它更像一个通过 request body 携带的内部标记，而不是面向模型语义的提示词内容。&lt;/p&gt;
&lt;h3 id="52-cli-prefix"&gt;5.2 CLI prefix&lt;/h3&gt;
&lt;p&gt;就是上一节说的那句身份声明。&lt;/p&gt;
&lt;p&gt;这个设计说明 Claude Code 并不把“身份”硬编码在正文 sections 里，而是作为独立前缀插入，方便缓存分层和后续拆分。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六systemcontextgit-status-这类信息其实走-system-通道"&gt;六、systemContext：git status 这类信息其实走 system 通道&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src/context.ts&lt;/code&gt; 里的 &lt;code&gt;getSystemContext()&lt;/code&gt; 会构造 systemContext。&lt;/p&gt;
&lt;p&gt;默认最重要的一项是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gitStatus&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其内容包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前 branch&lt;/li&gt;
&lt;li&gt;main branch&lt;/li&gt;
&lt;li&gt;git user&lt;/li&gt;
&lt;li&gt;工作区 status snapshot&lt;/li&gt;
&lt;li&gt;最近 commits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意这里强调的是“snapshot at the start of the conversation”，也就是一个会话开始时的快照，不会自动更新。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;src/query.ts&lt;/code&gt; 里，Claude Code 会这样做：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fullSystemPrompt&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;asSystemPrompt&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;appendSystemContext&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;systemPrompt&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;systemContext&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而 &lt;code&gt;appendSystemContext()&lt;/code&gt; 在 &lt;code&gt;src/utils/api.ts&lt;/code&gt; 里的实现非常直接：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把 &lt;code&gt;systemContext&lt;/code&gt; 的 key/value 直接拼成文本&lt;/li&gt;
&lt;li&gt;追加到 system prompt 末尾&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以像下面这些东西，其实属于 system prompt 的一部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gitStatus&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;某些 cache breaker 注入&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是为什么你在 prompt 结构里应该把 git status 视为 system 层附加上下文，而不是 user message。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七usercontextclaudemd-和日期其实走首条-meta-user-message"&gt;七、userContext：CLAUDE.md 和日期其实走首条 meta user message&lt;/h2&gt;
&lt;p&gt;与 &lt;code&gt;systemContext&lt;/code&gt; 不同，&lt;code&gt;userContext&lt;/code&gt; 不是 append 到 system prompt，而是通过 &lt;code&gt;prependUserContext()&lt;/code&gt; 插入到 &lt;code&gt;messages&lt;/code&gt; 最前面。&lt;/p&gt;
&lt;p&gt;实现位于 &lt;code&gt;src/utils/api.ts&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它会构造一条 meta user message，内容格式类似：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;system-reminder&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;As you answer the user&amp;#39;s questions, you can use the following context:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# claudeMd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# currentDate
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IMPORTANT: this context may or may not be relevant...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/system-reminder&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;也就是说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;claudeMd&lt;/code&gt; 不在 system prompt 里&lt;/li&gt;
&lt;li&gt;&lt;code&gt;currentDate&lt;/code&gt; 也不在 system prompt 里&lt;/li&gt;
&lt;li&gt;它们在 messages 数组里，而且是第 0 条 meta user message&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个设计非常重要，因为它说明 Claude Code 区分了两种东西：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;system-level behavior rules&lt;/li&gt;
&lt;li&gt;conversation-level contextual hints&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;CLAUDE.md 更接近第二种。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="八claudemd-的真实来源不是一个文件而是一组文件汇总"&gt;八、&lt;code&gt;claudeMd&lt;/code&gt; 的真实来源不是一个文件，而是一组文件汇总&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src/utils/claudemd.ts&lt;/code&gt; 负责加载用户和项目指令文件。&lt;/p&gt;
&lt;p&gt;它的加载层级大致是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;managed memory&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;例如 &lt;code&gt;/etc/claude-code/CLAUDE.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;user memory&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;project memory&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;项目根目录及向上路径中的 &lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.claude/CLAUDE.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.claude/rules/*.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;local memory&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CLAUDE.local.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;而且支持 &lt;code&gt;@include&lt;/code&gt;，所以一份 CLAUDE.md 还可能再引入别的文本文件。&lt;/p&gt;
&lt;p&gt;最后这些内容会被 &lt;code&gt;getClaudeMds(...)&lt;/code&gt; 汇总成一大段 &lt;code&gt;claudeMd&lt;/code&gt; 字符串，进入 &lt;code&gt;userContext&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以从设计上看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLAUDE.md 是用户/项目定制规则层&lt;/li&gt;
&lt;li&gt;它不是底层 runtime system prompt 模板本身&lt;/li&gt;
&lt;li&gt;它是通过“上下文注入”覆盖默认行为的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也解释了为什么代码里专门有一句：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IMPORTANT: These instructions OVERRIDE any default behavior...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Claude Code 想让模型把这些文件视为高优先级补充规则。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="九memory-prompt-和-claudemd-不是一回事"&gt;九、memory prompt 和 CLAUDE.md 不是一回事&lt;/h2&gt;
&lt;p&gt;源码里这两个概念很容易混，但职责完全不同。&lt;/p&gt;
&lt;h3 id="91-loadmemoryprompt"&gt;9.1 &lt;code&gt;loadMemoryPrompt()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这个函数在 &lt;code&gt;src/memdir/memdir.ts&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它生成的是“记忆系统使用规则”，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;memory 存放目录在哪里&lt;/li&gt;
&lt;li&gt;有哪些 memory type&lt;/li&gt;
&lt;li&gt;哪些该存、哪些不该存&lt;/li&gt;
&lt;li&gt;怎么写 frontmatter&lt;/li&gt;
&lt;li&gt;怎么维护 &lt;code&gt;MEMORY.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这部分进入 system prompt 的动态 section。&lt;/p&gt;
&lt;h3 id="92-claudemd"&gt;9.2 &lt;code&gt;claudeMd&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这个来自 &lt;code&gt;src/utils/claudemd.ts&lt;/code&gt;，是实际的用户和项目规则正文。&lt;/p&gt;
&lt;p&gt;它不是 memory 机制说明，而是具体内容，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;项目协作偏好&lt;/li&gt;
&lt;li&gt;输出语言要求&lt;/li&gt;
&lt;li&gt;操作规范&lt;/li&gt;
&lt;li&gt;用户自己的工作方式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这部分进入首条 meta user message。&lt;/p&gt;
&lt;p&gt;简化说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;memory prompt = “如何使用记忆系统”&lt;/li&gt;
&lt;li&gt;claudeMd = “用户和项目到底要求你做什么”&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="十messages-不是原样直发还会再规范化"&gt;十、messages 不是原样直发，还会再规范化&lt;/h2&gt;
&lt;p&gt;到了 &lt;code&gt;src/services/api/claude.ts&lt;/code&gt;，Claude Code 会对消息流做进一步处理。&lt;/p&gt;
&lt;p&gt;主要步骤有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;normalizeMessagesForAPI(messages, filteredTools)&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;统一 message 结构&lt;/li&gt;
&lt;li&gt;规范 tool use/tool result 格式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;针对 model 能力做后处理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不支持 tool search 的模型会 strip 掉对应字段&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ensureToolResultPairing(messagesForAPI)&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修复 tool_use / tool_result 不成对的问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;strip 掉不兼容 blocks&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;比如 advisor block 没开 beta 时去掉&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;控制媒体数量&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;超过上限时裁剪旧媒体&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;某些场景下注入额外 meta user message&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;例如 &lt;code&gt;&amp;lt;available-deferred-tools&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这说明“最终 messages”也不是单纯的用户对话历史，而是一个经过修正、注入、约束后的规范化消息流。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十一prompt-cache-的关键设计静态和动态边界"&gt;十一、prompt cache 的关键设计：静态和动态边界&lt;/h2&gt;
&lt;p&gt;Claude Code 在 prompt 设计上有一个非常重要的优化点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SYSTEM_PROMPT_DYNAMIC_BOUNDARY&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;定义在 &lt;code&gt;src/constants/prompts.ts&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它的作用是把 system prompt 分成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;静态部分&lt;/li&gt;
&lt;li&gt;动态部分&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;然后 &lt;code&gt;splitSysPromptPrefix()&lt;/code&gt; in &lt;code&gt;src/utils/api.ts&lt;/code&gt; 会据此拆分缓存作用域：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;attribution header: 不缓存或特殊处理&lt;/li&gt;
&lt;li&gt;CLI prefix: org scope&lt;/li&gt;
&lt;li&gt;静态正文: global scope&lt;/li&gt;
&lt;li&gt;动态正文: 不走全局缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这背后的设计目的很明确：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通用规则尽量跨 session 复用缓存&lt;/li&gt;
&lt;li&gt;会变化的内容，如 env、language、MCP、memory prompt 等，单独挂在后面&lt;/li&gt;
&lt;li&gt;避免因为一个小变动把整个大 system prompt 的 cache key 打碎&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也是为什么 Claude Code 的 system prompt 不是“随便 join 一下字符串”，而是显式建模成 &lt;code&gt;string[]&lt;/code&gt; blocks 再拆。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十二从模型视角看最终请求结构可以近似理解为这样"&gt;十二、从模型视角看，最终请求结构可以近似理解为这样&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SYSTEM:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1. x-anthropic-billing-header ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2. You are Claude Code, Anthropic&amp;#39;s official CLI for Claude.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;3. 静态 system sections
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - intro
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - system
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - doing tasks
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - actions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - using tools
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - tone/style
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - output efficiency
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;4. 动态 system sections
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - session guidance
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - memory prompt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - env info
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - language/output style
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - MCP instructions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - scratchpad / summarize tool results / frc / brief / token budget 等
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;5. systemContext
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - gitStatus
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - cacheBreaker(如果有)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MESSAGES:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;0. prependUserContext 生成的 meta user message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - claudeMd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - currentDate
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1. 额外 meta user message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 例如 available-deferred-tools
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2. 用户真实输入
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;3. 历史 assistant messages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;4. tool use / tool result messages
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个结构基本就是 Claude Code prompt 设计的本质。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十三为什么要把-claudemd-放在-usercontext而不是-system-prompt-里"&gt;十三、为什么要把 CLAUDE.md 放在 userContext，而不是 system prompt 里&lt;/h2&gt;
&lt;p&gt;从设计上看，这么做有几个好处。&lt;/p&gt;
&lt;h3 id="131-减少基础-system-prompt-波动"&gt;13.1 减少基础 system prompt 波动&lt;/h3&gt;
&lt;p&gt;如果把 CLAUDE.md 直接拼到 system prompt 里，那么每个项目、每个用户、每次规则变化都会直接打碎 system cache。&lt;/p&gt;
&lt;p&gt;放到 &lt;code&gt;prependUserContext()&lt;/code&gt; 里，可以把“默认系统行为模板”和“用户项目自定义说明”分层。&lt;/p&gt;
&lt;h3 id="132-语义更准确"&gt;13.2 语义更准确&lt;/h3&gt;
&lt;p&gt;Claude Code 自带规则是产品级 runtime contract。&lt;/p&gt;
&lt;p&gt;而 CLAUDE.md 更像“当前工作上下文中的高优先级补充说明”。&lt;/p&gt;
&lt;p&gt;把它包装成 &lt;code&gt;&amp;lt;system-reminder&amp;gt;&lt;/code&gt; user meta message，语义上更接近“这里有额外上下文，请按需使用，但优先级很高”。&lt;/p&gt;
&lt;h3 id="133-更适合项目级定制"&gt;13.3 更适合项目级定制&lt;/h3&gt;
&lt;p&gt;CLAUDE.md 本来就是用户和项目自定义层，天然更接近 conversation context，而不是产品内核提示词模板。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十四本地日志能看到哪一层"&gt;十四、本地日志能看到哪一层&lt;/h2&gt;
&lt;p&gt;从源码看，默认本地能看到的是 transcript，不是完整最终请求。&lt;/p&gt;
&lt;h3 id="141-默认-transcript"&gt;14.1 默认 transcript&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/utils/sessionStorage.ts&lt;/code&gt; 会把会话记录写到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;~/.claude/projects/&amp;lt;project-slug&amp;gt;/&amp;lt;sessionId&amp;gt;.jsonl&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里通常能看到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;user message&lt;/li&gt;
&lt;li&gt;assistant message&lt;/li&gt;
&lt;li&gt;tool_use&lt;/li&gt;
&lt;li&gt;tool_result&lt;/li&gt;
&lt;li&gt;attachment&lt;/li&gt;
&lt;li&gt;一些 metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但通常看不到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;完整 system prompt blocks&lt;/li&gt;
&lt;li&gt;完整 tools schema&lt;/li&gt;
&lt;li&gt;最终 &lt;code&gt;anthropic.beta.messages.create(...)&lt;/code&gt; 的原始 request body&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="142-history"&gt;14.2 history&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;~/.claude/history.jsonl&lt;/code&gt; 主要是输入历史，不是完整 prompt。&lt;/p&gt;
&lt;h3 id="143-dump-prompts"&gt;14.3 dump-prompts&lt;/h3&gt;
&lt;p&gt;真正会把 request body 落盘的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/services/api/dumpPrompts.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;输出目录：&lt;code&gt;~/.claude/dump-prompts/&amp;lt;sessionOrAgentId&amp;gt;.jsonl&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里会写：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;init&lt;/code&gt; 记录：system、tools、metadata&lt;/li&gt;
&lt;li&gt;&lt;code&gt;message&lt;/code&gt; 记录：新 user messages&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response&lt;/code&gt; 记录：模型响应&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但这条路径默认有条件，external 用户通常不开。&lt;/p&gt;
&lt;p&gt;所以默认本地日志只够看“对话和工具轨迹”，不够看“完整最终 prompt”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十五这一套设计的几个核心取舍"&gt;十五、这一套设计的几个核心取舍&lt;/h2&gt;
&lt;h3 id="151-不是单字符串而是分层-block-设计"&gt;15.1 不是单字符串，而是分层 block 设计&lt;/h3&gt;
&lt;p&gt;好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;方便缓存&lt;/li&gt;
&lt;li&gt;方便附加前缀&lt;/li&gt;
&lt;li&gt;方便只替换动态部分&lt;/li&gt;
&lt;li&gt;方便做不同 cache scope&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="152-把-runtime-行为规则和项目上下文分离"&gt;15.2 把 runtime 行为规则和项目上下文分离&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;产品内核规则进 system&lt;/li&gt;
&lt;li&gt;项目/用户规则进 userContext&lt;/li&gt;
&lt;li&gt;环境快照进 systemContext&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这让每一层的职责很清楚。&lt;/p&gt;
&lt;h3 id="153-把可缓存部分和高波动部分分离"&gt;15.3 把可缓存部分和高波动部分分离&lt;/h3&gt;
&lt;p&gt;这是 Claude Code prompt engineering 里最工程化的一点。&lt;/p&gt;
&lt;p&gt;重点不是“提示词写得多聪明”，而是“提示词结构怎么减少 token churn”。&lt;/p&gt;
&lt;h3 id="154-把-message-流也当成-prompt-设计的一部分"&gt;15.4 把 message 流也当成 prompt 设计的一部分&lt;/h3&gt;
&lt;p&gt;Claude Code 没有把 prompt 只理解成 system 文本。&lt;/p&gt;
&lt;p&gt;它同样重视：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;哪些 meta message 要 prepend&lt;/li&gt;
&lt;li&gt;工具消息怎样规范化&lt;/li&gt;
&lt;li&gt;哪些内容该留在 messages 层而不是塞进 system&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个视角比“只研究 system prompt 正文”更接近真实实现。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十六结论"&gt;十六、结论&lt;/h2&gt;
&lt;p&gt;Claude Code 的最终 prompt 设计，可以归纳成一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;用静态 system 模板定义默认行为，用动态 system sections 注入当前能力和环境，用 systemContext 追加运行时快照，用首条 meta user message 注入 CLAUDE.md 和日期，再把整个消息流规范化后发送给模型。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果只盯着某一段 system 文本，很容易误判实际结构。真正重要的是这四层：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CLI prefix 和 attribution header&lt;/li&gt;
&lt;li&gt;system prompt sections&lt;/li&gt;
&lt;li&gt;systemContext&lt;/li&gt;
&lt;li&gt;prepended userContext + normalized messages&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Claude Code 的 prompt 设计本质上是一个分层上下文装配系统，不只是一个长 prompt 模板。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="附最值得读的源码入口"&gt;附：最值得读的源码入口&lt;/h2&gt;
&lt;p&gt;如果后续还要继续深入，优先看这几个入口：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;src/query.ts&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;看 query 时如何组装 &lt;code&gt;fullSystemPrompt&lt;/code&gt; 和 &lt;code&gt;messages&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;src/constants/prompts.ts&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;看 system prompt 的静态和动态 sections&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;src/context.ts&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;看 &lt;code&gt;getUserContext()&lt;/code&gt; 和 &lt;code&gt;getSystemContext()&lt;/code&gt; 的边界&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;src/utils/api.ts&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;看 &lt;code&gt;appendSystemContext()&lt;/code&gt;、&lt;code&gt;prependUserContext()&lt;/code&gt;、&lt;code&gt;splitSysPromptPrefix()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;src/services/api/claude.ts&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;看最终发送 API 前的规范化和包装&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;src/utils/claudemd.ts&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;看 CLAUDE.md 汇总逻辑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;src/memdir/memdir.ts&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;看 memory prompt 的生成逻辑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Claude Code 缓存设计架构文档</title><link>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/claude-code-caching-architecture/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/claude-code-caching-architecture/</guid><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;源码依据&lt;/strong&gt;：&lt;code&gt;/Users/jishihe/work/civil-engineering-cloud-claude-code-source-v2.1.88/01-claude-code-source-crack/claude-code-source/src&lt;/code&gt;
所有行号与路径都指向该目录。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="1-背景与问题域"&gt;1. 背景与问题域&lt;/h2&gt;
&lt;p&gt;LLM Agent 的每一轮请求都要把 &lt;code&gt;system + tools + 完整消息历史&lt;/code&gt; 再发给模型。随着对话变长，延迟与成本线性增长。Anthropic 官方提供了 &lt;strong&gt;prompt caching&lt;/strong&gt;（&lt;code&gt;cache_control: { type: 'ephemeral' }&lt;/code&gt;）：服务端按请求前缀的字节比对命中缓存，5min 或 1h TTL 内复用已经预填（prefill）过的 KV，延迟下降一个数量级。&lt;/p&gt;
&lt;p&gt;能用好这个能力的前提是：&amp;ldquo;下一次请求的前缀字节必须完全等于上一次&amp;rdquo;。Claude Code 的缓存设计整个就是围绕这一条约束来组织的。&lt;/p&gt;
&lt;p&gt;设计目标：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每轮请求尽可能多地命中服务端缓存（&lt;code&gt;cache_read_input_tokens&lt;/code&gt; 最大化）。&lt;/li&gt;
&lt;li&gt;系统 prompt 的动态变化（时间、cwd、git、CLAUDE.md）不能污染可缓存前缀。&lt;/li&gt;
&lt;li&gt;工具集在会话内字节稳定（GrowthBook flag 翻转不能引起 tools 漂移）。&lt;/li&gt;
&lt;li&gt;长对话能在不丢失语义的前提下&amp;quot;回收&amp;quot;老的 tool_result 负载。&lt;/li&gt;
&lt;li&gt;当缓存意外失效时，能自动定位并报告根因。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="2-顶层架构请求的三层前缀结构"&gt;2. 顶层架构：请求的三层前缀结构&lt;/h2&gt;
&lt;p&gt;一个发给 Anthropic API 的请求被切成三段，每段各自管理缓存：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│ system: TextBlockParam[] │
│ ├── [0] attribution header (cache_scope=null) │
│ ├── [1] 静态指令 + 工具说明 (cache_scope=global) │ ← 块边界 cache_control
│ └── [2] 动态上下文 (时间/cwd/git) (cache_scope=null) │
├─────────────────────────────────────────────────────────────┤
│ tools: BetaToolUnion[] │
│ ├── tool_1 │
│ ├── tool_2 │
│ └── tool_N (ttl=1h, scope=org) │ ← 最后一个 tool 上的 cache_control
├─────────────────────────────────────────────────────────────┤
│ messages: MessageParam[] │
│ ├── msg_1 (user) │
│ ├── msg_2 (assistant) │
│ ├── ... ← 旧 tool_result 携带 cache_reference │
│ └── msg_N (user) 最后一个 content block (ttl=1h) │ ← 全局唯一 cache_control 断点
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;关键约束&lt;/strong&gt;：一次请求里 &lt;code&gt;cache_control&lt;/code&gt; 断点 ≤ 4（API 上限），且&lt;strong&gt;最后一个断点必须落在 messages 的最后一条&lt;/strong&gt;——只有这样，下一轮新追加的内容才会被写入&amp;quot;可读前缀&amp;quot;里。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="3-分层实现"&gt;3. 分层实现&lt;/h2&gt;
&lt;h3 id="31-system-prompt静态--动态边界"&gt;3.1 System Prompt：静态 / 动态边界&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件&lt;/strong&gt;：&lt;code&gt;utils/api.ts:321 splitSysPromptPrefix()&lt;/code&gt;
&lt;strong&gt;常量&lt;/strong&gt;：&lt;code&gt;constants/prompts.ts:114 SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;system prompt 被设计成&lt;strong&gt;字符串数组&lt;/strong&gt;，不是单个大字符串。数组中插入一个哨兵字符串 &lt;code&gt;__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__&lt;/code&gt; 作为&amp;quot;静态 | 动态&amp;quot;的分水岭。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// constants/prompts.ts: 拼装 system prompt
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;BILLING_HEADER&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;CLI_SYSPROMPT_PREFIX&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 包含版本、identity
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...&lt;span style="color:#a6e22e"&gt;STATIC_INSTRUCTIONS&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 工作方式、Tool usage 规范
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...&lt;span style="color:#a6e22e"&gt;TOOL_USAGE_DESCRIPTIONS&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;SYSTEM_PROMPT_DYNAMIC_BOUNDARY&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// &amp;lt;-- 边界
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...&lt;span style="color:#a6e22e"&gt;SESSION_SPECIFIC_GUIDANCE&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 依赖 isNonInteractive / hasSkills 等运行期标志
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;CWD_AND_TIME&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;CLAUDE_MD_CONTENT&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;切分规则&lt;/strong&gt;（&lt;code&gt;splitSysPromptPrefix&lt;/code&gt; 输出 3~4 个 &lt;code&gt;TextBlockParam&lt;/code&gt;）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;段&lt;/th&gt;
&lt;th&gt;cacheScope&lt;/th&gt;
&lt;th&gt;合并规则&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;attribution header&lt;/td&gt;
&lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不打 cache_control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI 前缀&lt;/td&gt;
&lt;td&gt;&lt;code&gt;null&lt;/code&gt; 或 &lt;code&gt;org&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;账单级别的小变量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;边界之前的所有静态段&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'global'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;合并成一个 TextBlock，&lt;strong&gt;所有组织共享同一个服务端缓存条目&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;边界之后&lt;/td&gt;
&lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不缓存&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;关键注释&lt;/strong&gt;（&lt;code&gt;constants/prompts.ts:343-350&lt;/code&gt;）：边界之后的每个条件开关都会&amp;quot;让 Blake2b 前缀 hash 翻倍（2^N 种变体）&amp;quot;，所以凡是运行期才确定的文案必须放到边界之后。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MCP 降级路径&lt;/strong&gt;：如果当前会话接了 MCP 工具（tool 集本身就是组织特定的），&lt;code&gt;splitSysPromptPrefix&lt;/code&gt; 被传入 &lt;code&gt;skipGlobalCacheForSystemPrompt=true&lt;/code&gt;，三段全部降到 &lt;code&gt;org&lt;/code&gt; 作用域，不再走 &lt;code&gt;global&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="32-tools会话内字节稳定--末尾断点"&gt;3.2 Tools：会话内字节稳定 + 末尾断点&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件&lt;/strong&gt;：&lt;code&gt;utils/api.ts:119 toolToAPISchema()&lt;/code&gt;、&lt;code&gt;utils/toolSchemaCache.ts&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;每个 tool schema 的生成分两步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Base schema 会话级缓存&lt;/strong&gt;（&lt;code&gt;toolSchemaCache&lt;/code&gt;）：&lt;code&gt;name / description / input_schema / strict / eager_input_streaming&lt;/code&gt; 这些不变量计算一次就缓存。
&lt;ul&gt;
&lt;li&gt;Cache key 通常是 &lt;code&gt;tool.name&lt;/code&gt;；MCP / StructuredOutput 工具带 &lt;code&gt;inputJSONSchema&lt;/code&gt;，key 改用 &lt;code&gt;${name}:${stringify(schema)}&lt;/code&gt; 以避免冲突。&lt;/li&gt;
&lt;li&gt;动机：避免 GrowthBook flag (&lt;code&gt;tengu_tool_pear&lt;/code&gt;、&lt;code&gt;tengu_fgts&lt;/code&gt;) 或 &lt;code&gt;tool.prompt()&lt;/code&gt; 本身的输出抖动把 tools 字节搅乱。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per-request overlay&lt;/strong&gt;：在 base schema 上叠加本次请求的 &lt;code&gt;defer_loading&lt;/code&gt; 和 &lt;code&gt;cache_control&lt;/code&gt;——通过显式字段拷贝，不污染 base。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BetaToolWithExtras&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BetaTool&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;strict?&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;boolean&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;defer_loading?&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;boolean&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cache_control&lt;/span&gt;&lt;span style="color:#f92672"&gt;?:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;ephemeral&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;scope&lt;/span&gt;&lt;span style="color:#f92672"&gt;?:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;global&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;org&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ttl&lt;/span&gt;&lt;span style="color:#f92672"&gt;?:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;5m&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;1h&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;断点位置&lt;/strong&gt;：tools 数组上通常只给&lt;strong&gt;最后一个 tool&lt;/strong&gt; 挂 &lt;code&gt;cache_control&lt;/code&gt;，整个 tools 段成为可缓存前缀的一部分。&lt;/p&gt;
&lt;h3 id="33-messages全局唯一断点位于最后一条"&gt;3.3 Messages：全局唯一断点位于最后一条&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件&lt;/strong&gt;：&lt;code&gt;services/api/claude.ts:3063 addCacheBreakpoints()&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;markerIndex&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;skipCacheWrite&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messages&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;2&lt;/span&gt; : &lt;span style="color:#66d9ef"&gt;messages.length&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messages&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;map&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;addCache&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;markerIndex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;为什么只打一个&lt;/strong&gt;（&lt;code&gt;claude.ts:3078-3088&lt;/code&gt; 原注释）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Exactly one message-level cache_control marker per request. Mycro&amp;rsquo;s turn-to-turn eviction frees local-attention KV pages at any cached prefix position NOT in &lt;code&gt;cache_store_int_token_boundaries&lt;/code&gt;. With two markers the second-to-last position is protected and its locals survive an extra turn even though nothing will ever resume from there — with one marker they&amp;rsquo;re freed immediately.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;断点只打在最后一条消息的&lt;strong&gt;最后一个 content block&lt;/strong&gt; 上：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;userMessageToMessageParam&lt;/code&gt; (&lt;code&gt;claude.ts:588&lt;/code&gt;) 和 &lt;code&gt;assistantMessageToMessageParam&lt;/code&gt; (&lt;code&gt;claude.ts:633&lt;/code&gt;) 都只给 &lt;code&gt;content[length-1]&lt;/code&gt; 挂 &lt;code&gt;cache_control&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Assistant 消息会跳过 &lt;code&gt;thinking&lt;/code&gt; 和 &lt;code&gt;redacted_thinking&lt;/code&gt; 块（它们不能带 cache_control）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;skipCacheWrite 模式&lt;/strong&gt;（fire-and-forget 子代理）：marker 挪到倒数第二条——这样写操作落在&amp;quot;已经存在的前缀边界&amp;quot;上，服务端去重为 no-op 合并，子代理不会把自己的尾巴写进 KV cache 污染主线程。&lt;/p&gt;
&lt;h3 id="34-tool-resultcache_reference-的引用机制"&gt;3.4 Tool Result：cache_reference 的引用机制&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件&lt;/strong&gt;：&lt;code&gt;services/api/claude.ts:3164-3207&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在 cache 断点&lt;strong&gt;之前&lt;/strong&gt;的所有 &lt;code&gt;tool_result&lt;/code&gt; 块，会被追加一个 &lt;code&gt;cache_reference&lt;/code&gt; 字段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;j&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Object.&lt;span style="color:#a6e22e"&gt;assign&lt;/span&gt;({}, &lt;span style="color:#a6e22e"&gt;block&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cache_reference&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;block.tool_use_id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这让服务端可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;按 &lt;code&gt;tool_use_id&lt;/code&gt; 从缓存中取回之前那次的 tool_result 完整内容；&lt;/li&gt;
&lt;li&gt;客户端下一轮可以把该 tool_result 的 &lt;code&gt;content&lt;/code&gt; 清空（只留引用），大幅缩小 request body；&lt;/li&gt;
&lt;li&gt;配合 microcompact 做&amp;quot;负载回收但保留语义&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="4-横切关注点"&gt;4. 横切关注点&lt;/h2&gt;
&lt;h3 id="41-getcachecontrol统一的-cache_control-工厂"&gt;4.1 &lt;code&gt;getCacheControl()&lt;/code&gt;：统一的 cache_control 工厂&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件&lt;/strong&gt;：&lt;code&gt;services/api/claude.ts:358&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;getCacheControl&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;scope&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;querySource&lt;/span&gt; } &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {}) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;ephemeral&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...(&lt;span style="color:#a6e22e"&gt;should1hCacheTTL&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;querySource&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;ttl&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;1h&amp;#39;&lt;/span&gt; }),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...(&lt;span style="color:#a6e22e"&gt;scope&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;global&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;scope&lt;/span&gt; }),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;所有地方需要 &lt;code&gt;cache_control&lt;/code&gt; 的地方都走这一个工厂，保证 TTL / scope 的取值&lt;strong&gt;在一次请求内一致&lt;/strong&gt;——一旦不一致，服务端会把它当成新的缓存条目。&lt;/p&gt;
&lt;h3 id="42-ttl-决策should1hcachettl-的会话级-latch"&gt;4.2 TTL 决策：&lt;code&gt;should1hCacheTTL()&lt;/code&gt; 的会话级 latch&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件&lt;/strong&gt;：&lt;code&gt;services/api/claude.ts:393&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;决定 1h 还是 5m 的逻辑包含：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Bedrock 第三方&lt;/strong&gt;：通过 env &lt;code&gt;ENABLE_PROMPT_CACHING_1H_BEDROCK&lt;/code&gt; 自助开启。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第一方&lt;/strong&gt;：只对 Anthropic 员工账号或订阅用户开启，且不在 overage 状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GrowthBook allowlist&lt;/strong&gt;：按 &lt;code&gt;querySource&lt;/code&gt; 前缀匹配（如 &lt;code&gt;repl_main_thread*&lt;/code&gt;、&lt;code&gt;sdk&lt;/code&gt;、&lt;code&gt;agent:*&lt;/code&gt;）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;关键设计&lt;/strong&gt;：这两个判断结果在 session 启动时 latch 到 bootstrap state (&lt;code&gt;getPromptCache1hEligible&lt;/code&gt;, &lt;code&gt;getPromptCache1hAllowlist&lt;/code&gt;)。原因：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Latch eligibility in bootstrap state for session stability — prevents mid-session overage flips from changing the cache_control TTL, which would bust the server-side prompt cache (~20K tokens per flip).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;即：&lt;strong&gt;同一会话的 TTL 值必须不变&lt;/strong&gt;，否则每一次翻转都是一次 cache break。&lt;/p&gt;
&lt;h3 id="43-缓存失效检测phase-1-记录--phase-2-诊断"&gt;4.3 缓存失效检测：Phase 1 记录 / Phase 2 诊断&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件&lt;/strong&gt;：&lt;code&gt;services/api/promptCacheBreakDetection.ts&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;一个独立的诊断子系统，分两阶段工作：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1 &lt;code&gt;recordPromptState()&lt;/code&gt; (L247)&lt;/strong&gt;：每次 API 调用&lt;strong&gt;之前&lt;/strong&gt;，把本次的状态指纹化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;systemHash&lt;/code&gt; = 剥离 cache_control 后的 system 字节 hash&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toolsHash&lt;/code&gt; = 剥离 cache_control 后的 tools 字节 hash&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cacheControlHash&lt;/code&gt; = 只保留 cache_control 字段的 hash（用于检测 scope/TTL 翻转）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;perToolHashes&lt;/code&gt; = 每个 tool 的独立 hash（解释&amp;quot;77% 的 tool break 来自 schema 描述漂移&amp;quot;）&lt;/li&gt;
&lt;li&gt;以及 model / fastMode / betas / autoMode / overage / effort / extraBody 等&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;与上一次对比，差异写入 &lt;code&gt;pendingChanges&lt;/code&gt; 暂存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2 &lt;code&gt;checkResponseForCacheBreak()&lt;/code&gt; (L437)&lt;/strong&gt;：响应返回后，读 &lt;code&gt;cache_read_input_tokens&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若 &lt;code&gt;cacheReadTokens &amp;gt;= prevCacheRead * 0.95&lt;/code&gt; 或绝对下跌 &lt;code&gt;&amp;lt; 2000&lt;/code&gt;，&lt;strong&gt;不视作 break&lt;/strong&gt;（正常抖动）。&lt;/li&gt;
&lt;li&gt;否则把 pending changes 翻译成人类可读原因，比如：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;model changed (sonnet-4-6 → opus-4-7)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tools changed (+1/-0 tools)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;system prompt changed (+120 chars)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;possible 5min TTL expiry (prompt unchanged)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;写 &lt;code&gt;tengu_prompt_cache_break&lt;/code&gt; 事件 + &lt;code&gt;cache-break-xxxx.diff&lt;/code&gt; 供工程师排查。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;跟踪键隔离&lt;/strong&gt;：&lt;code&gt;getTrackingKey()&lt;/code&gt; 按 &lt;code&gt;querySource&lt;/code&gt; + &lt;code&gt;agentId&lt;/code&gt; 分桶，子代理并发不会相互污染。容量 &lt;code&gt;MAX_TRACKED_SOURCES=10&lt;/code&gt;，LRU 淘汰。&lt;/p&gt;
&lt;h3 id="44-上下文压缩microcompact--cache_edits"&gt;4.4 上下文压缩：microcompact &amp;amp; cache_edits&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件&lt;/strong&gt;：&lt;code&gt;services/compact/microCompact.ts&lt;/code&gt;、&lt;code&gt;services/compact/compact.ts&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;两种触发场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;时间触发&lt;/strong&gt;（&lt;code&gt;timeBasedMicrocompact&lt;/code&gt;, L402）：距离上一次主循环助理消息超过阈值（默认 5min），主动压缩老的 tool_result。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token 触发&lt;/strong&gt;：接近 context 上限时触发。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;压缩动作&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;扫描所有 &lt;code&gt;tool_result&lt;/code&gt; 块，按 &lt;code&gt;COMPACTABLE_TOOLS&lt;/code&gt; 白名单筛选。&lt;/li&gt;
&lt;li&gt;保留最近 &lt;code&gt;keepRecent&lt;/code&gt; 条完整内容，更早的：
&lt;ul&gt;
&lt;li&gt;将本地 content 清空或置为简短摘要；&lt;/li&gt;
&lt;li&gt;在最后一条 user message 里插入 &lt;code&gt;cache_edits&lt;/code&gt; 块，声明 &lt;code&gt;{ type: 'delete', cache_reference: &amp;lt;tool_use_id&amp;gt; }&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;服务端按 reference 从缓存读取原文，但本地不再持有长文本。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;这些 &lt;code&gt;cache_edits&lt;/code&gt; 被 &lt;code&gt;pin&lt;/code&gt; 到 &lt;code&gt;pinnedEdits[]&lt;/code&gt;，下次请求会再次在同一位置插入，保证服务端视图一致。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notifyCacheDeletion()&lt;/code&gt; 告知 cache break 检测器&amp;quot;下一次 &lt;code&gt;cache_read&lt;/code&gt; 下跌是预期的&amp;quot;，避免误报。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;compact vs microcompact&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;microcompact：只动 tool_result 负载，保留消息结构，不触发额外 LLM 调用。&lt;/li&gt;
&lt;li&gt;全量 compact：触发一个 LLM 调用总结整段历史，用总结替换历史消息；之后调用 &lt;code&gt;notifyCompaction()&lt;/code&gt; 重置 cache baseline。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="5-端到端请求装配流程"&gt;5. 端到端请求装配流程&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;用户输入
↓
构造 messages (追加 user turn)
↓
┌────────────────────────────┐
│ recordPromptState() │ Phase 1: 状态指纹化
│ - hash system / tools ... │
│ - diff vs previous state │
└────────────────────────────┘
↓
timeBasedMicrocompact() 检查是否需要压缩老 tool_result
↓
┌────────────────────────────────────────────────┐
│ buildSystemPromptBlocks(systemPrompt) │
│ → splitSysPromptPrefix() │
│ → 3~4 个 TextBlockParam │
│ → 静态段挂 cache_control scope=global │
├────────────────────────────────────────────────┤
│ getTools() → toolToAPISchema(每个) │
│ → toolSchemaCache 读/写 │
│ → 最后一个 tool 挂 cache_control │
├────────────────────────────────────────────────┤
│ addCacheBreakpoints(messages) │
│ → 最后一条消息的最后一个 block 挂 marker │
│ → 旧 tool_result 挂 cache_reference │
│ → 插入 pinnedEdits (如有) │
└────────────────────────────────────────────────┘
↓
POST /v1/messages
↓
响应 usage: { cache_creation_input_tokens, cache_read_input_tokens, ... }
↓
┌────────────────────────────┐
│ checkResponseForCacheBreak │ Phase 2: 用 pendingChanges 解释意外下跌
└────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="6-设计原则小结"&gt;6. 设计原则小结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;字节稳定优先&lt;/strong&gt;：任何会让同一会话 tools/system 字节变化的机制（feature flag、schema 重生成）都要被冻结在 session boot 时。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;静态与动态分离&lt;/strong&gt;：高频变化的值（时间、cwd、git）必须位于所有 cache 断点之后；需要随时间变化但仍想缓存的值（effort、TTL 资格）必须 latch。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单一断点在末尾&lt;/strong&gt;：不贪多断点，让&amp;quot;新内容&amp;quot;恰好增量写入末尾。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;引用替代重传&lt;/strong&gt;：长 tool_result 用 cache_reference，让服务端缓存成为内容存储。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可观测即正确&lt;/strong&gt;：每一次缓存未命中都要有自动归因，否则你不知道什么时候悄悄退化了。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="7-源码索引表"&gt;7. 源码索引表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;主题&lt;/th&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;关键位置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;system prompt 切分与 global scope&lt;/td&gt;
&lt;td&gt;&lt;code&gt;utils/api.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;splitSysPromptPrefix&lt;/code&gt; L321&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;动态边界哨兵&lt;/td&gt;
&lt;td&gt;&lt;code&gt;constants/prompts.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SYSTEM_PROMPT_DYNAMIC_BOUNDARY&lt;/code&gt; L114&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;session-specific 指引（必须在边界后）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;constants/prompts.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getSessionSpecificGuidanceSection&lt;/code&gt; L352&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tool schema 组装 + cache 覆盖&lt;/td&gt;
&lt;td&gt;&lt;code&gt;utils/api.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;toolToAPISchema&lt;/code&gt; L119&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tool schema 会话级缓存&lt;/td&gt;
&lt;td&gt;&lt;code&gt;utils/toolSchemaCache.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getCacheControl&lt;/code&gt; 工厂&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/api/claude.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;L358&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTL 资格 latch&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/api/claude.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;should1hCacheTTL&lt;/code&gt; L393&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;user/assistant 消息挂 cache_control&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/api/claude.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;L588-674&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;唯一 message-level marker&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/api/claude.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;addCacheBreakpoints&lt;/code&gt; L3063-3106&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tool_result 加 cache_reference&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/api/claude.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;L3164-3207&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;构建 system blocks（请求时）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/api/claude.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;buildSystemPromptBlocks&lt;/code&gt; L3213&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;缓存失效检测 Phase 1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/api/promptCacheBreakDetection.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;recordPromptState&lt;/code&gt; L247&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;缓存失效检测 Phase 2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/api/promptCacheBreakDetection.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;checkResponseForCacheBreak&lt;/code&gt; L437&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;压缩后重置 baseline&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/api/promptCacheBreakDetection.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;notifyCompaction&lt;/code&gt; L689&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;时间触发 microcompact&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/compact/microCompact.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;timeBasedMicrocompact&lt;/code&gt; L402&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;基于 cache_edits 的 microcompact&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/compact/microCompact.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;L253, L296&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;compact 后清理&lt;/td&gt;
&lt;td&gt;&lt;code&gt;services/compact/postCompactCleanup.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="8-可以借鉴到自建-agent-的四条最小规则"&gt;8. 可以借鉴到自建 Agent 的四条最小规则&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;把 system prompt 拆成&amp;quot;静态段 + 动态段&amp;quot;两个 TextBlock&lt;/strong&gt;，仅给静态段挂 &lt;code&gt;cache_control: { type: 'ephemeral', ttl: '1h' }&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tools 数组的最后一个工具&lt;/strong&gt;挂一个 &lt;code&gt;cache_control&lt;/code&gt;，让整段 tools 跟在 system 静态段之后形成可缓存前缀。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每次请求只在 &lt;code&gt;messages[-1]&lt;/code&gt; 的最后一个 content block 上打一个 &lt;code&gt;cache_control&lt;/code&gt;&lt;/strong&gt;；旧消息保持原样，不要修改字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每次请求后打印 &lt;code&gt;cache_read_input_tokens / cache_creation_input_tokens&lt;/code&gt;&lt;/strong&gt;；若 cache_read 连续两次为 0 或骤降，立即比对本次 vs 上次 request 的 &lt;code&gt;json.dumps(system)&lt;/code&gt; / &lt;code&gt;json.dumps(tools)&lt;/code&gt; 差异，就能定位到具体哪个字段漂了。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="9-如何验证本文档对应的实现行为"&gt;9. 如何验证本文档对应的实现行为&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;阅读 &lt;code&gt;services/api/claude.ts:3063-3106&lt;/code&gt; 确认 markerIndex 逻辑。&lt;/li&gt;
&lt;li&gt;阅读 &lt;code&gt;utils/api.ts:321-435&lt;/code&gt; 确认三种切分模式。&lt;/li&gt;
&lt;li&gt;运行 Claude Code 并开启 &lt;code&gt;--debug&lt;/code&gt;，观察 &lt;code&gt;[PROMPT CACHE BREAK]&lt;/code&gt; 日志与 &lt;code&gt;cache-break-*.diff&lt;/code&gt; 产物。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;tengu_api_success&lt;/code&gt; 事件里的 &lt;code&gt;cache_read_input_tokens / cache_creation_input_tokens&lt;/code&gt; 验证稳态下 read/create 比。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Durable Run 作业化迁移方案</title><link>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/durable-run-plan/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/durable-run-plan/</guid><description>&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;把当前“HTTP 请求内跑 agent + SSE 仅负责附着输出”的模型，改成“后台 durable job 驱动执行，HTTP 只负责创建 run、附着流、暂停/恢复/注入消息”。&lt;/p&gt;
&lt;p&gt;目标效果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;浏览器断开后，run 继续执行，不依赖原始请求协程存活。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;任意客户端可通过 run_id 重新附着同一个执行中的 run。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务重启后，未完成 run 可被 worker 扫描并恢复或标记失败。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当前 ProjectDraft / ProjectTurn / WorkspaceRunState / SSE buffer 继续保留，但职责更清晰。&lt;/p&gt;
&lt;p&gt;默认选择：先做“队列作业化 durable run”，不直接引入外部 workflow 引擎。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="key-changes"&gt;Key Changes&lt;/h2&gt;
&lt;h3 id="1-抽出-run-领域模型停止让-chatpy-直接承载执行状态机"&gt;1. 抽出 Run 领域模型，停止让 chat.py 直接承载执行状态机&lt;/h3&gt;
&lt;p&gt;新增一个明确的 ChatRun 持久化对象，职责是描述一次 agent 执行本身，而不是复用 SSE buffer 或 turn payload 隐式表达。&lt;/p&gt;
&lt;p&gt;建议新增表：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;chat_runs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;核心字段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;run_id&lt;/li&gt;
&lt;li&gt;project_id&lt;/li&gt;
&lt;li&gt;session_id&lt;/li&gt;
&lt;li&gt;draft_id&lt;/li&gt;
&lt;li&gt;turn_id&lt;/li&gt;
&lt;li&gt;status: queued | acquiring_lock | restoring_context | running | waiting_for_user | paused | publishing | completed | failed | cancelled&lt;/li&gt;
&lt;li&gt;worker_id&lt;/li&gt;
&lt;li&gt;lease_expires_at&lt;/li&gt;
&lt;li&gt;continuation_of_run_id&lt;/li&gt;
&lt;li&gt;last_checkpoint_seq&lt;/li&gt;
&lt;li&gt;last_error&lt;/li&gt;
&lt;li&gt;created_at / started_at / updated_at / ended_at&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;保留现有 ProjectTurn 作为对话与产物视图，但不要再让它兼任运行时真相来源。
ProjectTurn.assistant_message_json.resume_event_id 继续用于前端展示恢复位置，但运行恢复以 chat_runs 为准。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-把执行循环搬到独立-worker不再依赖-http-streamingresponse"&gt;2. 把执行循环搬到独立 worker，不再依赖 HTTP StreamingResponse&lt;/h3&gt;
&lt;p&gt;新增后台 worker 模块，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;apps/api/services/chat_run_worker.py&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;apps/api/services/chat_run_executor.py&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;apps/api/services/chat_run_queue.py&lt;/p&gt;
&lt;p&gt;职责拆分：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;API 路由：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建 run 记录&lt;/li&gt;
&lt;li&gt;入队&lt;/li&gt;
&lt;li&gt;立即返回 run_id&lt;/li&gt;
&lt;li&gt;SSE/轮询仅做附着和消费事件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Worker：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;抢占 run lease&lt;/li&gt;
&lt;li&gt;加载上下文&lt;/li&gt;
&lt;li&gt;执行 agent loop&lt;/li&gt;
&lt;li&gt;持续写 checkpoint、事件流、turn/draft 状态&lt;/li&gt;
&lt;li&gt;处理暂停、用户回答、proposal apply/reject、enqueue 消息&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Queue：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可先用 Redis list / stream + DB lease&lt;/li&gt;
&lt;li&gt;不要求首版引入 Celery / Temporal / Inngest&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;执行入口从“POST /chat 直接跑”改为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;POST /projects/{id}/chat 创建 chat_run&lt;/li&gt;
&lt;li&gt;记录 ProjectTurn(status=running) 与 draft&lt;/li&gt;
&lt;li&gt;将 run_id 推入队列&lt;/li&gt;
&lt;li&gt;返回 run_id&lt;/li&gt;
&lt;li&gt;前端立刻连 /chat/stream/{run_id} 或沿用现有 /chat/resume/{run_id}&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-定义可恢复-checkpoint恢复执行而不只是恢复-sse"&gt;3. 定义可恢复 checkpoint，恢复执行而不只是恢复 SSE&lt;/h3&gt;
&lt;p&gt;当前已有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;WorkspaceRunState&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ProjectDraft.partial_response_json&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;model_messages_json&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RunEventBuffer&lt;/p&gt;
&lt;p&gt;但这些还不足以在进程死掉后真正恢复执行。
新增 RunCheckpoint 持久化结构，按“工具边界”保存，而不是按 token 级别保存。&lt;/p&gt;
&lt;p&gt;建议 checkpoint 内容：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;run_id&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;seq&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;phase&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;message_history_json&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;assistant_steps_json&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tool_calls_json&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;task_plan_json&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;workspace_run_state_json&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;pending_user_interrupt_json&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;active_tool_name&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;continuation_prompt&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;resume_from_checkpoint_kind&lt;/p&gt;
&lt;p&gt;checkpoint 触发点：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每次 tool result 完成后&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每次进入 waiting_for_user&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每次完成 queued message 注入后&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;publish 前&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;publish 完成后&lt;/p&gt;
&lt;p&gt;不要尝试在任意 token 中间恢复。
恢复语义限定为：从最近一个“已完成 tool-return 的稳定边界”继续，这和你们现有 _can_resume_from_tool_checkpoint() 思路一致，但现在要落到持久化 checkpoint，而不是只在单次请求重试里使用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-重新定义-api创建-run附着流控制-run"&gt;4. 重新定义 API：创建 run、附着流、控制 run&lt;/h3&gt;
&lt;p&gt;建议把接口语义改成 run-first：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;POST /projects/{project_id}/chat-runs&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建新 run 或 continuation run&lt;/li&gt;
&lt;li&gt;返回 run_id, stream_url, status&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GET /projects/{project_id}/chat-runs/{run_id}/stream&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSE 附着到运行事件&lt;/li&gt;
&lt;li&gt;内部仍可复用现有 RunEventBuffer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GET /projects/{project_id}/chat-runs/{run_id}&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查询 run 状态、当前阶段、waiting reason、saved_files&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;POST /projects/{project_id}/chat-runs/{run_id}/pause&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;POST /projects/{project_id}/chat-runs/{run_id}/resume&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对 paused 或 waiting_for_user run 生效&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;POST /projects/{project_id}/chat-runs/{run_id}/input&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;统一承载 queued user message / question answer / proposal decision&lt;/li&gt;
&lt;li&gt;不再分散为 continuation 请求参数和多个分支语义&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现有 /chat/resume/{chat_run_id} 可以短期兼容，内部转发到新的 run stream。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="5-让-worker-具备-lease-和-crash-recovery"&gt;5. 让 worker 具备 lease 和 crash recovery&lt;/h3&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;worker 抢 run 时更新 worker_id + lease_expires_at&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;执行期间周期性续租&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果 worker 崩溃，lease 过期后其他 worker 可接管&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接管逻辑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读取最新 checkpoint&lt;/li&gt;
&lt;li&gt;恢复 sandbox 连接或按 draft manifest 重建&lt;/li&gt;
&lt;li&gt;从 checkpoint 指定的 continuation point 继续&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果接管时发现 checkpoint 不可恢复：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将 run 标记 failed&lt;/li&gt;
&lt;li&gt;写 terminal 事件&lt;/li&gt;
&lt;li&gt;不让 run 永远挂在 running&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一步是 durable run 的核心。
没有 lease，就只是“后台任务”；有 lease + checkpoint，才是“可恢复执行”。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="6-把-sandbox-生命周期从请求-keepalive改成worker-keepalive"&gt;6. 把 sandbox 生命周期从“请求 keepalive”改成“worker keepalive”&lt;/h3&gt;
&lt;p&gt;当前 keepalive 在 chat.py 的执行循环里。迁移后改为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;worker 负责 refresh_sandbox_lifecycle&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;worker 在 lease 续租时顺带刷新 sandbox TTL&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;run 附着的 SSE 客户端不再承担任何 keepalive 责任&lt;/p&gt;
&lt;p&gt;恢复时的 sandbox 策略：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优先 reconnect 当前 session.e2b_sandbox_id&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;失效时按 ProjectDraft.code_manifest_key 恢复 workspace&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;再执行 reconcile_sandbox_runtime&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只有在 checkpoint 标记“已进入 publishing 且未完成”时，才允许重试 publish；否则只恢复到 pre-publish 状态&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="7-收敛当前-chatpy-的职责"&gt;7. 收敛当前 chat.py 的职责&lt;/h3&gt;
&lt;p&gt;apps/api/routers/chat.py 需要逐步瘦身到三类逻辑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;request validation / auth&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;run creation / control / stream attach&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;legacy compatibility glue&lt;/p&gt;
&lt;p&gt;从中迁出的逻辑：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;agent loop&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;queue interrupt 注入&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;finalize workspace&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;checkpoint flush&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;detached drain&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;worker keepalive&lt;/p&gt;
&lt;p&gt;这样 chat.py 不再是 runtime 内核，只是 transport adapter。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="test-plan"&gt;Test Plan&lt;/h2&gt;
&lt;p&gt;必须覆盖以下场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建 run 后立即断开 SSE，worker 继续执行并最终完成。&lt;/li&gt;
&lt;li&gt;两个客户端同时附着同一 run_id，都能看到一致事件流。&lt;/li&gt;
&lt;li&gt;worker 进程在 tool result 后崩溃，run 被新 worker 从最近 checkpoint 接管并继续。&lt;/li&gt;
&lt;li&gt;worker 在 text streaming 中崩溃，恢复后从最近稳定 checkpoint 继续，不重复执行已完成工具。&lt;/li&gt;
&lt;li&gt;waiting_for_user 状态下服务重启，用户提交 answer 后 run 继续。&lt;/li&gt;
&lt;li&gt;proposal apply / reject 在 run-first API 下仍能正确更新资源并继续执行。&lt;/li&gt;
&lt;li&gt;sandbox 已被回收时，run 接管流程能用 draft manifest 恢复。&lt;/li&gt;
&lt;li&gt;publish 过程中失败时，不产生重复版本，也不丢失 draft。&lt;/li&gt;
&lt;li&gt;pause 请求到达后，run 最终进入 paused，后续 resume 可继续。&lt;/li&gt;
&lt;li&gt;同一 project 同时只允许一个持有 lock 的活跃 run，冲突时返回明确错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="assumptions"&gt;Assumptions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;首版继续使用 Redis + Postgres，不引入 Temporal / Celery / Inngest。&lt;/li&gt;
&lt;li&gt;恢复粒度限定为“tool checkpoint 级”，不追求 token 级精确续跑。&lt;/li&gt;
&lt;li&gt;RunEventBuffer 继续保留，职责是“事件回放与多端附着”，不是运行时真相。&lt;/li&gt;
&lt;li&gt;ProjectDraft 继续作为 workspace 草稿真相来源；ChatRun 只表示执行状态。&lt;/li&gt;
&lt;li&gt;首版只支持单 run 单 project lock，不做同项目多并发 agent run。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Open Agents 项目架构文档</title><link>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/open-agents-project-architecture/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://80b55ee1.hugo-blog-ddc.pages.dev/posts/open-agents-project-architecture/</guid><description>&lt;h2 id="1-文档目的"&gt;1. 文档目的&lt;/h2&gt;
&lt;p&gt;本文档基于当前仓库代码，对 &lt;code&gt;open-agents&lt;/code&gt;（代码中也大量使用 &lt;code&gt;open-harness&lt;/code&gt; 命名）进行面向实现的架构梳理。目标不是复述 README，而是回答以下问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个项目的核心分层是什么&lt;/li&gt;
&lt;li&gt;一个会话是如何被创建、初始化 sandbox、启动 agent、持续流式返回结果的&lt;/li&gt;
&lt;li&gt;为什么这个系统能够支持长时间运行、断线重连、sandbox 休眠恢复和自动化 GitHub 工作流&lt;/li&gt;
&lt;li&gt;当前项目的扩展点、边界和主要风险在哪里&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文档以当前代码为准，重点覆盖：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;apps/web&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;packages/agent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;packages/sandbox&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;与之直接相关的数据库、GitHub/Vercel 集成、workflow 与流恢复机制&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="2-系统总览"&gt;2. 系统总览&lt;/h2&gt;
&lt;p&gt;这个项目本质上不是“一个集成了 LLM 的 Next.js 聊天页”，而是一个三层系统：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Web 控制面&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Durable Agent Workflow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cloud Sandbox 执行面&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;代码仓库中的官方架构摘要也是这条主链路：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Web -&amp;gt; Agent -&amp;gt; Sandbox
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中最关键的设计决策是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Agent 不直接运行在 sandbox 中&lt;/li&gt;
&lt;li&gt;浏览器请求不直接承载 agent 的完整生命周期&lt;/li&gt;
&lt;li&gt;Web 层负责控制、持久化和恢复&lt;/li&gt;
&lt;li&gt;Sandbox 只是执行环境，不是控制平面&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这使项目具备以下性质：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户关闭页面后，后台任务仍可继续&lt;/li&gt;
&lt;li&gt;浏览器连接断开后，可按 workflow run 恢复流&lt;/li&gt;
&lt;li&gt;sandbox 可以单独休眠、恢复、快照、超时延长&lt;/li&gt;
&lt;li&gt;agent 模型选择、工具系统、sandbox provider 可以独立演进&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="3-仓库结构"&gt;3. 仓库结构&lt;/h2&gt;
&lt;p&gt;这是一个使用 &lt;code&gt;Bun + Turborepo&lt;/code&gt; 的 monorepo。&lt;/p&gt;
&lt;h3 id="31-顶层结构"&gt;3.1 顶层结构&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apps/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; web/ Next.js Web 控制面
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;packages/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent/ Agent runtime、工具系统、subagent、skills
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sandbox/ Sandbox 抽象层与 Vercel 实现
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; shared/ 共享 hooks/lib
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tsconfig/ 共享 TS 配置
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;scripts/ 运维和辅助脚本
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docs/agents/ 面向 agent 的仓库说明与架构摘要
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="32-关键包职责"&gt;3.2 关键包职责&lt;/h3&gt;
&lt;h4 id="appsweb"&gt;&lt;code&gt;apps/web&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;承担控制面职责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户认证&lt;/li&gt;
&lt;li&gt;session / chat / message 持久化&lt;/li&gt;
&lt;li&gt;会话创建与 sandbox 初始化&lt;/li&gt;
&lt;li&gt;聊天工作流启动与流式返回&lt;/li&gt;
&lt;li&gt;GitHub / Vercel 集成&lt;/li&gt;
&lt;li&gt;sandbox 生命周期编排&lt;/li&gt;
&lt;li&gt;使用统计与设置管理&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="packagesagent"&gt;&lt;code&gt;packages/agent&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;承担 agent runtime 职责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模型选择与 provider 默认配置&lt;/li&gt;
&lt;li&gt;system prompt 拼装&lt;/li&gt;
&lt;li&gt;工具注册&lt;/li&gt;
&lt;li&gt;subagent 委派&lt;/li&gt;
&lt;li&gt;skills 发现与加载&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="packagessandbox"&gt;&lt;code&gt;packages/sandbox&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;承担执行环境抽象职责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义统一的 &lt;code&gt;Sandbox&lt;/code&gt; 接口&lt;/li&gt;
&lt;li&gt;将状态对象转换为实际 sandbox 连接&lt;/li&gt;
&lt;li&gt;目前唯一后端为 Vercel Sandbox&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="4-高层架构图"&gt;4. 高层架构图&lt;/h2&gt;
&lt;h3 id="41-组件架构图"&gt;4.1 组件架构图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart LR
U["Browser UI"] --&gt; W["Next.js Web App"]
W --&gt; DB[("Postgres / Drizzle")]
W --&gt; WF["Workflow Runtime"]
WF --&gt; AG["Open Harness Agent"]
AG --&gt; TOOLS["Tools / Skills / Subagents"]
TOOLS --&gt; SB["Sandbox Abstraction"]
SB --&gt; VS["Vercel Sandbox VM"]
W --&gt; VO["Vercel OAuth"]
W --&gt; GH["GitHub App + GitHub APIs"]
W --&gt; VC["Vercel Project APIs"]
GH --&gt; VS
VC --&gt; VS
&lt;/div&gt;
&lt;h3 id="42-分层关系图"&gt;4.2 分层关系图&lt;/h3&gt;
&lt;div class="mermaid"&gt;flowchart TB
subgraph ControlPlane["Web 控制面"]
UI["App Router Pages / Hooks"]
API["API Routes"]
STORE["DB State: sessions/chats/messages"]
INT["GitHub / Vercel / Auth Integration"]
end
subgraph Runtime["Agent 运行时"]
WF["Workflow-based durable execution"]
OA["openHarnessAgent"]
SA["Subagents"]
SK["Skills"]
TL["Tool Loop"]
end
subgraph Execution["执行面"]
SIF["Sandbox Interface"]
VSB["VercelSandbox"]
VM["Persistent VM / Repo / Shell / Ports"]
end
UI --&gt; API
API --&gt; STORE
API --&gt; WF
WF --&gt; OA
OA --&gt; TL
TL --&gt; SA
TL --&gt; SK
TL --&gt; SIF
SIF --&gt; VSB
VSB --&gt; VM
API --&gt; INT
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="5-核心设计原则"&gt;5. 核心设计原则&lt;/h2&gt;
&lt;h3 id="51-控制面与执行面分离"&gt;5.1 控制面与执行面分离&lt;/h3&gt;
&lt;p&gt;Agent 不在 VM 中常驻运行。它通过工具接口远程操作 sandbox：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读写文件&lt;/li&gt;
&lt;li&gt;执行 shell&lt;/li&gt;
&lt;li&gt;搜索代码&lt;/li&gt;
&lt;li&gt;启动 dev server&lt;/li&gt;
&lt;li&gt;操作 git&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着 sandbox 是“可替换的执行后端”，而不是应用的核心调度器。&lt;/p&gt;
&lt;h3 id="52-请求生命周期与任务生命周期分离"&gt;5.2 请求生命周期与任务生命周期分离&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;POST /api/chat&lt;/code&gt; 并不在请求内同步跑完整个 agent 流程。它只是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;校验权限&lt;/li&gt;
&lt;li&gt;连接 sandbox&lt;/li&gt;
&lt;li&gt;装配 agent 选项&lt;/li&gt;
&lt;li&gt;启动 workflow&lt;/li&gt;
&lt;li&gt;将 workflow 的可读流转发给前端&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;真正的任务生命周期由 workflow 承载，因此它可以长时间持续。&lt;/p&gt;
&lt;h3 id="53-一切关键状态可持久化"&gt;5.3 一切关键状态可持久化&lt;/h3&gt;
&lt;p&gt;数据库中不仅有业务数据，也有运行时控制状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sessions.sandboxState&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sessions.lifecycleState&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sessions.sandboxExpiresAt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chats.activeStreamId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;workflow_runs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;workflow_run_steps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;usage_events&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这为断线恢复、幂等控制和后台清理提供了基础。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="6-web-控制面架构"&gt;6. Web 控制面架构&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;apps/web&lt;/code&gt; 是整个系统最重的一层。它既是 UI 容器，也是系统控制平面。&lt;/p&gt;
&lt;h3 id="61-ui-组织"&gt;6.1 UI 组织&lt;/h3&gt;
&lt;p&gt;从目录上看，主要页面和路由分成几类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;app/sessions/...&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;主工作区，用户在这里和 agent 交互&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app/settings/...&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;模型、偏好、账号与统计设置&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app/shared/...&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;分享态只读页面&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app/api/...&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;控制面接口&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前端核心交互围绕“session -&amp;gt; chat -&amp;gt; messages”展开。&lt;/p&gt;
&lt;h3 id="62-api-路由分组"&gt;6.2 API 路由分组&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;app/api&lt;/code&gt; 基本可分为以下几类：&lt;/p&gt;
&lt;h4 id="认证与账号"&gt;认证与账号&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/api/auth/signin/vercel&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/auth/vercel/callback&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/auth/github/...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/auth/info&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/auth/signout&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="聊天与-workflow"&gt;聊天与 workflow&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/api/chat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/chat/[chatId]/stream&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/chat/[chatId]/stop&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="session--chat--message-数据"&gt;session / chat / message 数据&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/api/sessions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/sessions/[sessionId]/...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/sessions/[sessionId]/chats/...&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="sandbox-生命周期"&gt;sandbox 生命周期&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/api/sandbox&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/sandbox/status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/sandbox/reconnect&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/sandbox/extend&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/sandbox/snapshot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/sandbox/activity&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="github--vercel--pr-相关"&gt;GitHub / Vercel / PR 相关&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/api/github/...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/vercel/...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/generate-pr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/check-pr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/pr&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="配置与统计"&gt;配置与统计&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/api/settings/...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/usage&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/models&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/api/transcribe&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="63-认证模型"&gt;6.3 认证模型&lt;/h3&gt;
&lt;p&gt;当前“平台登录”以 Vercel OAuth 为核心：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户进入 &lt;code&gt;/api/auth/signin/vercel&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;系统生成 PKCE 参数与 state&lt;/li&gt;
&lt;li&gt;用户完成 Vercel OAuth&lt;/li&gt;
&lt;li&gt;callback 交换 token，拉取用户信息&lt;/li&gt;
&lt;li&gt;用户数据写入 &lt;code&gt;users&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;生成加密 JWE session cookie&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;GitHub 更像是“附加能力连接”而不是主登录源，主要用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;获取 repo 访问权限&lt;/li&gt;
&lt;li&gt;安装 GitHub App&lt;/li&gt;
&lt;li&gt;创建分支、push、PR&lt;/li&gt;
&lt;li&gt;接收 webhook 同步 PR / installation 状态&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="64-session-创建"&gt;6.4 Session 创建&lt;/h3&gt;
&lt;p&gt;会话创建分两步理解：&lt;/p&gt;
&lt;h4 id="第一步创建-session-记录"&gt;第一步：创建 session 记录&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;POST /api/sessions&lt;/code&gt; 负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;校验用户&lt;/li&gt;
&lt;li&gt;应用 managed template trial 限制&lt;/li&gt;
&lt;li&gt;校验 repo owner/name&lt;/li&gt;
&lt;li&gt;解析用户偏好&lt;/li&gt;
&lt;li&gt;计算默认标题、默认模型、auto-commit 选项&lt;/li&gt;
&lt;li&gt;创建 &lt;code&gt;session + initialChat&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一步结束后：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;session.status = running&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;session.sandboxState = { type: &amp;quot;vercel&amp;quot; }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;session.lifecycleState = provisioning&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，session 创建并不等于 sandbox 已准备完成。&lt;/p&gt;
&lt;h4 id="第二步初始化-sandbox"&gt;第二步：初始化 sandbox&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;POST /api/sandbox&lt;/code&gt; 负责真正创建或恢复 Vercel sandbox，并将最新状态回写到 &lt;code&gt;sessions.sandboxState&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="7-durable-workflow-架构"&gt;7. Durable Workflow 架构&lt;/h2&gt;
&lt;p&gt;这是项目能够支持长任务和断线恢复的关键。&lt;/p&gt;
&lt;h3 id="71-为什么要用-workflow"&gt;7.1 为什么要用 workflow&lt;/h3&gt;
&lt;p&gt;如果 agent 直接跑在 API 请求内，会遇到典型问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;请求超时&lt;/li&gt;
&lt;li&gt;页面刷新导致任务丢失&lt;/li&gt;
&lt;li&gt;网络闪断导致流中断&lt;/li&gt;
&lt;li&gt;长时间工具调用无法稳定承载&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个项目的解决方案是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /api/chat&lt;/code&gt; 只做启动和接线&lt;/li&gt;
&lt;li&gt;真实执行逻辑放进 &lt;code&gt;runAgentWorkflow&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="72-聊天-workflow-主循环"&gt;7.2 聊天 workflow 主循环&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;runAgentWorkflow&lt;/code&gt; 的职责：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将 UI messages 转成 model messages&lt;/li&gt;
&lt;li&gt;生成 assistant message id&lt;/li&gt;
&lt;li&gt;打开 workflow writable stream&lt;/li&gt;
&lt;li&gt;循环执行 agent step&lt;/li&gt;
&lt;li&gt;将每步输出流式写回前端&lt;/li&gt;
&lt;li&gt;在合适的时候停止，或者继续下一步&lt;/li&gt;
&lt;li&gt;持久化消息、sandbox state、usage、workflow run&lt;/li&gt;
&lt;li&gt;在自然完成后可选执行 auto-commit / auto-PR&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="73-step-继续条件"&gt;7.3 step 继续条件&lt;/h3&gt;
&lt;p&gt;一个 step 完成后，如果 &lt;code&gt;finishReason === &amp;quot;tool-calls&amp;quot;&lt;/code&gt;，且工具并未停在“需要用户确认/输入”的状态，则 workflow 会继续推进下一步。&lt;/p&gt;
&lt;p&gt;这使得系统支持：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多步工具链&lt;/li&gt;
&lt;li&gt;自动 tool loop&lt;/li&gt;
&lt;li&gt;中途暂停等待用户输入&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="74-workflow-运行记录"&gt;7.4 workflow 运行记录&lt;/h3&gt;
&lt;p&gt;数据库中有两类执行记录：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;workflow_runs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;workflow_run_steps&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其作用包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UI 或后台分析执行时长&lt;/li&gt;
&lt;li&gt;记录 step 级 finish reason&lt;/li&gt;
&lt;li&gt;回溯异常执行&lt;/li&gt;
&lt;li&gt;后续做可观测性扩展&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="8-agent-runtime-架构"&gt;8. Agent Runtime 架构&lt;/h2&gt;
&lt;h3 id="81-主-agent-形态"&gt;8.1 主 agent 形态&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;packages/agent/open-harness-agent.ts&lt;/code&gt; 中定义了主 agent：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;类型：&lt;code&gt;ToolLoopAgent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;默认模型：&lt;code&gt;anthropic/claude-opus-4.6&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;prepareCall()&lt;/code&gt; 动态注入运行上下文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注入的上下文包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sandbox state&lt;/li&gt;
&lt;li&gt;working directory&lt;/li&gt;
&lt;li&gt;current branch&lt;/li&gt;
&lt;li&gt;environment details&lt;/li&gt;
&lt;li&gt;技能列表&lt;/li&gt;
&lt;li&gt;主模型和 subagent 模型选择&lt;/li&gt;
&lt;li&gt;自定义附加 instructions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="82-工具系统"&gt;8.2 工具系统&lt;/h3&gt;
&lt;p&gt;主 agent 暴露的核心工具如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;todo_write&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;write&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;edit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grep&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;glob&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bash&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;task&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ask_user_question&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;skill&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;web_fetch&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以把它理解为一个“统一的 agent OS API”。&lt;/p&gt;
&lt;h3 id="83-模型网关"&gt;8.3 模型网关&lt;/h3&gt;
&lt;p&gt;模型统一经由 AI SDK gateway 访问。&lt;code&gt;packages/agent/models.ts&lt;/code&gt; 做了两件关键事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把 provider 差异抽象到统一的 &lt;code&gt;gateway(modelId)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;对不同 provider 应用默认策略&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anthropic 4.6 默认 adaptive thinking&lt;/li&gt;
&lt;li&gt;OpenAI GPT-5 默认 &lt;code&gt;store: false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;GPT-5 开 reasoning/encrypted content 支持&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上层逻辑不需要知道 provider 细节&lt;/li&gt;
&lt;li&gt;模型切换主要体现在配置层&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="84-subagent-体系"&gt;8.4 Subagent 体系&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;task&lt;/code&gt; 工具不是普通外部工具，而是“启动另一个 agent”：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;explorer&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;只读探索、追踪代码、回答问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;executor&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;有文件修改能力，适合实现工作&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;design&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;专注高质量前端/UI 设计实现&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Subagent 机制的价值在于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;让主 agent 保持较短上下文&lt;/li&gt;
&lt;li&gt;将复杂任务拆给专门子角色&lt;/li&gt;
&lt;li&gt;提升多步工作时的结构清晰度&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="85-skills-体系"&gt;8.5 Skills 体系&lt;/h3&gt;
&lt;p&gt;skills 不是硬编码的 prompt 片段，而是运行时从 sandbox 文件系统中发现的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;扫描 skill 目录&lt;/li&gt;
&lt;li&gt;查找 &lt;code&gt;SKILL.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;解析 frontmatter&lt;/li&gt;
&lt;li&gt;注入到 agent system prompt / tool 体系中&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这意味着 skill 是一种“文件系统插件”：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;repo 内可以自带项目级 skill&lt;/li&gt;
&lt;li&gt;用户还可以安装全局 skill&lt;/li&gt;
&lt;li&gt;Web 在创建 sandbox 后会按 session 安装 global skills&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="9-sandbox-架构"&gt;9. Sandbox 架构&lt;/h2&gt;
&lt;h3 id="91-抽象接口"&gt;9.1 抽象接口&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;packages/sandbox/interface.ts&lt;/code&gt; 中定义了统一的 &lt;code&gt;Sandbox&lt;/code&gt; 接口，主要能力包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件读写&lt;/li&gt;
&lt;li&gt;目录遍历&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exec&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;execDetached&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;端口映射 &lt;code&gt;domain(port)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stop()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;extendTimeout()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;snapshot()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getState()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这使 agent 工具不需要关心底层具体是 Vercel、Docker 还是别的执行后端。&lt;/p&gt;
&lt;h3 id="92-当前唯一实现vercel-sandbox"&gt;9.2 当前唯一实现：Vercel Sandbox&lt;/h3&gt;
&lt;p&gt;当前 &lt;code&gt;connectSandbox()&lt;/code&gt; 最终会走 &lt;code&gt;connectVercel()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;VercelState&lt;/code&gt; 支持几种状态来源：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sandboxName&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;连接/恢复持久 sandbox&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;source&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;从 repo 创建新 sandbox&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;snapshotId&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;从快照恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="93-persistent-sandbox-模型"&gt;9.3 Persistent Sandbox 模型&lt;/h3&gt;
&lt;p&gt;每个 session 通常会绑定一个命名的持久 sandbox：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;session 创建后可初始化 sandbox&lt;/li&gt;
&lt;li&gt;之后再次打开会话时，Web 侧通过 &lt;code&gt;sandboxState&lt;/code&gt; 重新连接&lt;/li&gt;
&lt;li&gt;sandbox 超时或休眠后，session 中保留状态用于恢复/清理&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="94-github-凭据代理"&gt;9.4 GitHub 凭据代理&lt;/h3&gt;
&lt;p&gt;一个重要安全设计是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub token 不直接“裸注入”为 sandbox 内环境变量&lt;/li&gt;
&lt;li&gt;而是通过 Vercel Sandbox 的 network policy 做 credential brokering&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样做的好处是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sandbox 可以访问 GitHub clone/push&lt;/li&gt;
&lt;li&gt;但 token 暴露面更小&lt;/li&gt;
&lt;li&gt;控制逻辑依然留在 Web/控制面&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="95-sandbox-初始化时做的额外动作"&gt;9.5 Sandbox 初始化时做的额外动作&lt;/h3&gt;
&lt;p&gt;创建或恢复 sandbox 后，Web 还会做几件控制面动作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同步 Vercel CLI 登录态到 sandbox&lt;/li&gt;
&lt;li&gt;安装 session 绑定的 global skills&lt;/li&gt;
&lt;li&gt;更新 &lt;code&gt;sessions.sandboxState&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;启动 sandbox 生命周期 workflow&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="10-端到端运行时序"&gt;10. 端到端运行时序&lt;/h2&gt;
&lt;h3 id="101-会话创建与-sandbox-初始化"&gt;10.1 会话创建与 sandbox 初始化&lt;/h3&gt;
&lt;div class="mermaid"&gt;sequenceDiagram
participant B as Browser
participant W as Web API
participant DB as Postgres
participant SB as Sandbox Layer
participant VM as Vercel Sandbox
B-&gt;&gt;W: POST /api/sessions
W-&gt;&gt;DB: create session + initial chat
W--&gt;&gt;B: session created (provisioning)
B-&gt;&gt;W: POST /api/sandbox
W-&gt;&gt;SB: connectSandbox(state, options)
SB-&gt;&gt;VM: create or resume persistent sandbox
VM--&gt;&gt;SB: sandbox ready
W-&gt;&gt;DB: persist sandboxState + lifecycle active
W--&gt;&gt;B: sandbox ready
&lt;/div&gt;
&lt;h3 id="102-一次聊天请求时序"&gt;10.2 一次聊天请求时序&lt;/h3&gt;
&lt;div class="mermaid"&gt;sequenceDiagram
participant B as Browser
participant W as /api/chat
participant DB as Postgres
participant WF as Workflow
participant AG as Agent
participant VM as Sandbox
B-&gt;&gt;W: POST /api/chat
W-&gt;&gt;DB: verify ownership / load chat / load session
W-&gt;&gt;DB: persist latest user message
W-&gt;&gt;WF: start(runAgentWorkflow)
W-&gt;&gt;DB: CAS set chats.activeStreamId
W--&gt;&gt;B: stream workflow readable
loop multi-step tool loop
WF-&gt;&gt;AG: webAgent.stream(...)
AG-&gt;&gt;VM: read/write/bash/task/skill
VM--&gt;&gt;AG: tool results
AG--&gt;&gt;WF: streamed UI chunks
WF--&gt;&gt;B: streamed chunks
end
WF-&gt;&gt;DB: persist assistant message
WF-&gt;&gt;DB: persist sandbox state
WF-&gt;&gt;DB: record workflow run + usage
WF-&gt;&gt;DB: clear activeStreamId
WF--&gt;&gt;B: finish
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="11-数据架构"&gt;11. 数据架构&lt;/h2&gt;
&lt;h3 id="111-为什么数据库是控制数据库"&gt;11.1 为什么数据库是“控制数据库”&lt;/h3&gt;
&lt;p&gt;这套数据库不仅存业务对象，还存运行控制状态。&lt;/p&gt;
&lt;h3 id="112-核心表"&gt;11.2 核心表&lt;/h3&gt;
&lt;h4 id="users"&gt;&lt;code&gt;users&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;平台登录用户。当前主登录来源是 Vercel。&lt;/p&gt;
&lt;h4 id="accounts"&gt;&lt;code&gt;accounts&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;外部账号连接。目前最关键的是 GitHub 用户账号及其 token。&lt;/p&gt;
&lt;h4 id="github_installations"&gt;&lt;code&gt;github_installations&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;GitHub App installation 记录，用于 repo 访问授权与安装同步。&lt;/p&gt;
&lt;h4 id="vercel_project_links"&gt;&lt;code&gt;vercel_project_links&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;repo 与 Vercel project 的用户级关联。&lt;/p&gt;
&lt;h4 id="sessions"&gt;&lt;code&gt;sessions&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;最重要的聚合根。它同时承载：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;repo 上下文&lt;/li&gt;
&lt;li&gt;分支信息&lt;/li&gt;
&lt;li&gt;Vercel project 关联&lt;/li&gt;
&lt;li&gt;auto-commit / auto-PR session override&lt;/li&gt;
&lt;li&gt;global skill refs&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sandboxState&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;lifecycle 状态&lt;/li&gt;
&lt;li&gt;diff / snapshot / PR 元数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="chats"&gt;&lt;code&gt;chats&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;session 下的对话线程。关键控制字段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;modelId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;activeStreamId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lastAssistantMessageAt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="chat_messages"&gt;&lt;code&gt;chat_messages&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;以 JSON 方式存储完整消息 parts，兼容 text、reasoning、tool parts、data parts。&lt;/p&gt;
&lt;h4 id="workflow_runs--workflow_run_steps"&gt;&lt;code&gt;workflow_runs&lt;/code&gt; / &lt;code&gt;workflow_run_steps&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;workflow 运行与 step 记录。&lt;/p&gt;
&lt;h4 id="user_preferences"&gt;&lt;code&gt;user_preferences&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;用户默认模型、subagent 模型、global skills、启用模型、auto-commit 等偏好。&lt;/p&gt;
&lt;h4 id="usage_events"&gt;&lt;code&gt;usage_events&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;记录 token 使用量与工具调用次数。&lt;/p&gt;
&lt;h3 id="113-数据模型关系图"&gt;11.3 数据模型关系图&lt;/h3&gt;
&lt;div class="mermaid"&gt;erDiagram
users ||--o{ sessions : owns
users ||--o{ accounts : links
users ||--o{ github_installations : installs
users ||--o{ usage_events : generates
users ||--|| user_preferences : configures
sessions ||--o{ chats : contains
sessions ||--o{ workflow_runs : records
chats ||--o{ chat_messages : contains
chats ||--o{ shares : exposes
workflow_runs ||--o{ workflow_run_steps : contains
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="12-流式传输与稳定性设计"&gt;12. 流式传输与稳定性设计&lt;/h2&gt;
&lt;p&gt;这是当前项目最有工程含量的部分之一。&lt;/p&gt;
&lt;h3 id="121-问题背景"&gt;12.1 问题背景&lt;/h3&gt;
&lt;p&gt;链路实际上很长：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;LLM Provider -&amp;gt; AI SDK / Gateway -&amp;gt; Workflow Runtime -&amp;gt; Next.js Route -&amp;gt; Browser
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果寄希望于“一条长连接永远不断”，系统会非常脆弱。因此当前实现选择的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任务与连接分离&lt;/li&gt;
&lt;li&gt;流断开可恢复&lt;/li&gt;
&lt;li&gt;关键状态入库&lt;/li&gt;
&lt;li&gt;客户端主动探测恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="122-activestreamid-作为恢复锚点"&gt;12.2 &lt;code&gt;activeStreamId&lt;/code&gt; 作为恢复锚点&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;chats.activeStreamId&lt;/code&gt; 是聊天流恢复的核心锚点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;workflow 启动后，原子写入 runId&lt;/li&gt;
&lt;li&gt;浏览器刷新后，通过这个 id 找回当前 workflow&lt;/li&gt;
&lt;li&gt;workflow 完成或失败后，清理该字段&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;并发控制依赖 compare-and-set：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只有当前值匹配预期时才更新&lt;/li&gt;
&lt;li&gt;避免重复请求启动多个 workflow&lt;/li&gt;
&lt;li&gt;避免旧 workflow 清掉新 workflow 的状态&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="123-apichatchatidstream"&gt;12.3 &lt;code&gt;/api/chat/[chatId]/stream&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;这个接口的作用不是启动任务，而是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读取 chat 的 &lt;code&gt;activeStreamId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;getRun(runId)&lt;/code&gt; 连接到已有 workflow&lt;/li&gt;
&lt;li&gt;将其 &lt;code&gt;ReadableStream&lt;/code&gt; 重新暴露给前端&lt;/li&gt;
&lt;li&gt;如果 run 已结束或不存在，则清理陈旧状态&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="124-前端恢复机制"&gt;12.4 前端恢复机制&lt;/h3&gt;
&lt;p&gt;前端使用 &lt;code&gt;AbortableChatTransport&lt;/code&gt;，目的是修补 AI SDK 在 reconnect 场景下的 abort 问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通 fetch 可取消&lt;/li&gt;
&lt;li&gt;reconnect fetch 也可取消&lt;/li&gt;
&lt;li&gt;route unmount 时不泄露连接&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同时前端还实现了自动恢复策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;页面重新 visible&lt;/li&gt;
&lt;li&gt;window focus&lt;/li&gt;
&lt;li&gt;浏览器恢复 online&lt;/li&gt;
&lt;li&gt;长时间没有收到首段输出&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此时客户端会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先 probe 当前 chat 是否仍在 streaming&lt;/li&gt;
&lt;li&gt;如果服务端还在跑，则走 &lt;code&gt;resumeStream()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果用户明确点过 stop，则不自动重连&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="125-readablestream-取消修补"&gt;12.5 &lt;code&gt;ReadableStream&lt;/code&gt; 取消修补&lt;/h3&gt;
&lt;p&gt;workflow runtime 返回的 stream 默认取消处理不够稳，因此项目在 Web 层包了一层 &lt;code&gt;createCancelableReadableStream()&lt;/code&gt;，专门处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;client disconnect&lt;/li&gt;
&lt;li&gt;abort race&lt;/li&gt;
&lt;li&gt;run not found&lt;/li&gt;
&lt;li&gt;late 404 / aborted 响应&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="126-设计结果"&gt;12.6 设计结果&lt;/h3&gt;
&lt;p&gt;系统保障的不是“连接永不断”，而是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;浏览器断开，workflow 还在&lt;/li&gt;
&lt;li&gt;页面刷新后可恢复现有流&lt;/li&gt;
&lt;li&gt;stale stream id 会被清理&lt;/li&gt;
&lt;li&gt;本地 transport 可安全 abort / retry&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;仍然不能完全保证的部分是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;provider 到 workflow 之间单 step 中途失败&lt;/li&gt;
&lt;li&gt;用户已经看到但尚未被 server side 持久化的部分 token&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;换句话说，当前系统对“连接中断”已经有强恢复能力，但对“provider 侧 step 中断”的恢复仍然以重试和重新发起为主。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="13-sandbox-生命周期管理"&gt;13. Sandbox 生命周期管理&lt;/h2&gt;
&lt;h3 id="131-生命周期状态"&gt;13.1 生命周期状态&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sessions.lifecycleState&lt;/code&gt; 目前包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;provisioning&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;active&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hibernating&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hibernated&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;restoring&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;archived&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;failed&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="132-为什么单独做-lifecycle-workflow"&gt;13.2 为什么单独做 lifecycle workflow&lt;/h3&gt;
&lt;p&gt;如果只在用户请求时顺便清理 sandbox，会出现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户离线后无人触发回收&lt;/li&gt;
&lt;li&gt;sandbox 超时或长期空闲无法主动处理&lt;/li&gt;
&lt;li&gt;DB 状态和真实 VM 状态逐渐漂移&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此项目单独引入了 &lt;code&gt;sandboxLifecycleWorkflow&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定期唤醒&lt;/li&gt;
&lt;li&gt;查看是否到达 inactivity / expiry 时间点&lt;/li&gt;
&lt;li&gt;如果 session 仍在跑 workflow，则跳过&lt;/li&gt;
&lt;li&gt;否则停止 sandbox，并更新会话为 hibernated&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="133-生命周期驱动信号"&gt;13.3 生命周期驱动信号&lt;/h3&gt;
&lt;p&gt;生命周期的“due time”由两类时间共同决定：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;inactivity timeout&lt;/li&gt;
&lt;li&gt;sandbox 过期时间减去 buffer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;取较早者触发休眠判断。&lt;/p&gt;
&lt;h3 id="134-lifecycle-lease"&gt;13.4 lifecycle lease&lt;/h3&gt;
&lt;p&gt;为避免多个后台任务同时处理同一个 session，项目使用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sessions.lifecycleRunId&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它相当于一个 lease：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先 claim&lt;/li&gt;
&lt;li&gt;claim 成功者才启动 lifecycle workflow&lt;/li&gt;
&lt;li&gt;stale lease 可被识别和回收&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="135-reconnect--status-路由的作用"&gt;13.5 reconnect / status 路由的作用&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;/api/sandbox/status&lt;/code&gt; 和 &lt;code&gt;/api/sandbox/reconnect&lt;/code&gt; 是控制面与真实 sandbox 状态对齐的安全阀：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;检查 runtime state 是否还有效&lt;/li&gt;
&lt;li&gt;发现 lifecycle failed 但 runtime 仍在时，修正状态&lt;/li&gt;
&lt;li&gt;发现 sandbox 已不可用时，将 session 转为 hibernated / expired&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="14-github--vercel-集成架构"&gt;14. GitHub / Vercel 集成架构&lt;/h2&gt;
&lt;h3 id="141-github-集成分成两层"&gt;14.1 GitHub 集成分成两层&lt;/h3&gt;
&lt;h4 id="github-用户-token"&gt;GitHub 用户 token&lt;/h4&gt;
&lt;p&gt;用途：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户级 API 调用&lt;/li&gt;
&lt;li&gt;repo clone / push&lt;/li&gt;
&lt;li&gt;安装同步&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;存储加密&lt;/li&gt;
&lt;li&gt;支持 refresh token 刷新&lt;/li&gt;
&lt;li&gt;每次按需解密取出&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="github-app-installation"&gt;GitHub App installation&lt;/h4&gt;
&lt;p&gt;用途：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对 repo / org 做正式安装授权&lt;/li&gt;
&lt;li&gt;创建 PR&lt;/li&gt;
&lt;li&gt;接收 webhook&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GitHub webhook 还会回写系统状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;installation 变化同步 &lt;code&gt;github_installations&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;PR closed / merged 会更新 session 的 &lt;code&gt;prStatus&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;部分场景下触发 session 归档&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="142-vercel-集成"&gt;14.2 Vercel 集成&lt;/h3&gt;
&lt;p&gt;Vercel 集成也分两层：&lt;/p&gt;
&lt;h4 id="vercel-oauth-登录"&gt;Vercel OAuth 登录&lt;/h4&gt;
&lt;p&gt;这是平台身份认证层。&lt;/p&gt;
&lt;h4 id="vercel-project-关联"&gt;Vercel Project 关联&lt;/h4&gt;
&lt;p&gt;这是 repo -&amp;gt; deployable project 的映射层，主要用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;找到 repo 对应的 Vercel project&lt;/li&gt;
&lt;li&gt;同步 dev env 配置的潜在能力&lt;/li&gt;
&lt;li&gt;在 sandbox 中同步 Vercel CLI 登录态&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="143-自动-commit--pr"&gt;14.3 自动 Commit / PR&lt;/h3&gt;
&lt;p&gt;聊天 workflow 自然结束后，若 session 或用户偏好允许：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查 sandbox 内是否存在可提交更改&lt;/li&gt;
&lt;li&gt;执行 auto-commit&lt;/li&gt;
&lt;li&gt;如满足条件，再执行 auto-create-PR&lt;/li&gt;
&lt;li&gt;将结果以 data parts 追加到 assistant message 中&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以 commit / PR 不是独立后台任务，而是聊天 workflow 的 post-finish 阶段。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="15-分享只读视图与多聊天模型"&gt;15. 分享、只读视图与多聊天模型&lt;/h2&gt;
&lt;h3 id="151-session-与-chat-的关系"&gt;15.1 session 与 chat 的关系&lt;/h3&gt;
&lt;p&gt;设计上：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个 session 对应一个代码工作区与 sandbox&lt;/li&gt;
&lt;li&gt;一个 session 下可有多个 chat&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着多个对话线程可以复用同一工作区上下文。&lt;/p&gt;
&lt;h3 id="152-fork-through-message"&gt;15.2 fork through message&lt;/h3&gt;
&lt;p&gt;系统支持基于已有 assistant message 分叉 chat：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新 chat 复制原 chat 到某条 assistant message 为止的历史&lt;/li&gt;
&lt;li&gt;新 chat 不继承 &lt;code&gt;activeStreamId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;共享原 session 的 sandbox 与 repo 上下文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是“分支式对话”而非“新建执行环境”。&lt;/p&gt;
&lt;h3 id="153-分享能力"&gt;15.3 分享能力&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;shares&lt;/code&gt; 表与 &lt;code&gt;/app/shared/[shareId]&lt;/code&gt; 页面提供只读分享视图：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;共享的是 chat 视图&lt;/li&gt;
&lt;li&gt;不是新的执行 session&lt;/li&gt;
&lt;li&gt;分享页会感知 &lt;code&gt;activeStreamId&lt;/code&gt; 判断是否正在 streaming&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="16-构建与部署模型"&gt;16. 构建与部署模型&lt;/h2&gt;
&lt;h3 id="161-monorepo-构建"&gt;16.1 Monorepo 构建&lt;/h3&gt;
&lt;p&gt;根脚本负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bun run web&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;turbo build&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;turbo typecheck&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bun run ci&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="162-数据库迁移策略"&gt;16.2 数据库迁移策略&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;apps/web&lt;/code&gt; 的 &lt;code&gt;build&lt;/code&gt; 脚本会在 &lt;code&gt;next build&lt;/code&gt; 前执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bun run db:migrate:apply &amp;amp;&amp;amp; next build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这意味着部署时会自动应用待执行迁移。&lt;/p&gt;
&lt;h3 id="163-当前运行依赖"&gt;16.3 当前运行依赖&lt;/h3&gt;
&lt;p&gt;从代码路径看，当前真正的硬依赖包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Postgres&lt;/li&gt;
&lt;li&gt;JWE secret / encryption key&lt;/li&gt;
&lt;li&gt;Vercel OAuth&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;若要启用完整的 repo 操作与 PR 工作流，还需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub App 配置&lt;/li&gt;
&lt;li&gt;GitHub webhook secret&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="17-扩展点"&gt;17. 扩展点&lt;/h2&gt;
&lt;h3 id="171-新增-sandbox-provider"&gt;17.1 新增 sandbox provider&lt;/h3&gt;
&lt;p&gt;理论上可以通过 &lt;code&gt;packages/sandbox&lt;/code&gt; 增加新的 provider：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只要实现 &lt;code&gt;Sandbox&lt;/code&gt; 接口&lt;/li&gt;
&lt;li&gt;再扩展 &lt;code&gt;SandboxState&lt;/code&gt; 与 &lt;code&gt;connectSandbox()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但当前 Web 控制面有较多地方默认假定 provider 为 &lt;code&gt;vercel&lt;/code&gt;，因此真实接入仍需要改动：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;session schema&lt;/li&gt;
&lt;li&gt;sandbox lifecycle&lt;/li&gt;
&lt;li&gt;reconnect/status 路由&lt;/li&gt;
&lt;li&gt;UI 状态文案&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="172-新增工具"&gt;17.2 新增工具&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;packages/agent/tools&lt;/code&gt; 添加工具后：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在主 agent 注册&lt;/li&gt;
&lt;li&gt;如涉及 UI state，需要扩展 message part 渲染&lt;/li&gt;
&lt;li&gt;如涉及使用量统计，需要考虑 task usage 聚合&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="173-新增-skill-来源"&gt;17.3 新增 skill 来源&lt;/h3&gt;
&lt;p&gt;当前 skill 是文件系统驱动，未来可扩展为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;远端 skill registry&lt;/li&gt;
&lt;li&gt;per-org skill source&lt;/li&gt;
&lt;li&gt;skill 签名与版本管理&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="174-新增前端工作区能力"&gt;17.4 新增前端工作区能力&lt;/h3&gt;
&lt;p&gt;由于 session 与 sandbox 已经稳定绑定，理论上可以在现有 chat UI 之上继续叠加：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件树编辑&lt;/li&gt;
&lt;li&gt;diff review&lt;/li&gt;
&lt;li&gt;终端视图&lt;/li&gt;
&lt;li&gt;预览端口管理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些都属于控制面新功能，而不需要重写 agent runtime。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="18-当前架构优势"&gt;18. 当前架构优势&lt;/h2&gt;
&lt;h3 id="181-优势"&gt;18.1 优势&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;控制面、推理面、执行面边界清晰&lt;/li&gt;
&lt;li&gt;天然适合长任务和断线重连&lt;/li&gt;
&lt;li&gt;sandbox 生命周期可独立管理&lt;/li&gt;
&lt;li&gt;agent 工具与 skill 体系可扩展&lt;/li&gt;
&lt;li&gt;session/chat/message 数据模型能支撑复杂 UI&lt;/li&gt;
&lt;li&gt;GitHub / Vercel 集成不是硬编码在 agent 内，而是控制面能力&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="182-代价"&gt;18.2 代价&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;apps/web&lt;/code&gt; 负担很重，既是 UI 又是 orchestration layer&lt;/li&gt;
&lt;li&gt;session 表承载信息较多，聚合根很强但也容易膨胀&lt;/li&gt;
&lt;li&gt;对 Vercel sandbox provider 的耦合仍然较深&lt;/li&gt;
&lt;li&gt;LLM step 级中断恢复还没有做到事务化&lt;/li&gt;
&lt;li&gt;技能、subagent、工具、workflow 交叉后，调试复杂度较高&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="19-架构风险与后续建议"&gt;19. 架构风险与后续建议&lt;/h2&gt;
&lt;h3 id="191-当前主要风险"&gt;19.1 当前主要风险&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;apps/web&lt;/code&gt; 控制面逻辑较集中，继续扩展后可能需要更明确的 service layer&lt;/li&gt;
&lt;li&gt;provider 侧 step 中途失败时，部分流式可见内容可能还未持久化&lt;/li&gt;
&lt;li&gt;sandbox lifecycle 与 reconnect 逻辑已经较复杂，需要持续用测试保护&lt;/li&gt;
&lt;li&gt;多种状态来源同时存在时，DB state 与真实 sandbox state 仍可能产生短暂漂移&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="192-建议的演进方向"&gt;19.2 建议的演进方向&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;把 &lt;code&gt;apps/web&lt;/code&gt; 中的 orchestration 进一步模块化为 session/chat/sandbox/github service&lt;/li&gt;
&lt;li&gt;为 provider step 失败引入更细粒度的中间态持久化&lt;/li&gt;
&lt;li&gt;为 sandbox provider 抽象增加更明确的 capability model&lt;/li&gt;
&lt;li&gt;为 workflow / reconnect / lifecycle 增加更统一的可观测性事件流&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="20-总结"&gt;20. 总结&lt;/h2&gt;
&lt;p&gt;Open Agents 当前的核心不是“聊天 UI”，而是一个围绕 coding agent 构建的完整执行系统：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Web&lt;/code&gt; 是控制面，负责认证、持久化、调度、恢复与集成&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Workflow + Agent&lt;/code&gt; 是推理与工具编排层，负责多步决策与任务推进&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Sandbox&lt;/code&gt; 是执行面，负责真实文件系统、shell、git 与预览端口&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;项目最重要的工程价值在于，它把“长时间运行的 coding task”从一次 HTTP 请求中解耦出来，并通过：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;workflow 持久化&lt;/li&gt;
&lt;li&gt;&lt;code&gt;activeStreamId&lt;/code&gt; 流恢复&lt;/li&gt;
&lt;li&gt;sandbox lifecycle&lt;/li&gt;
&lt;li&gt;DB 控制状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把一个容易中断的链路，改造成了“连接可断、任务可续、状态可对齐”的系统。&lt;/p&gt;
&lt;p&gt;如果把当前项目用一句话概括：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;它是一个以 Next.js 为控制面、以 Workflow 为执行编排器、以 Vercel Sandbox 为执行后端的可恢复型 coding agent 平台。&lt;/p&gt;
&lt;/blockquote&gt;</description></item></channel></rss>