让 AI 带 AI:Fork-and-Delegate 多Agent协作架构全解

📅 2026/7/1 15:58:30
让 AI 带 AI:Fork-and-Delegate 多Agent协作架构全解
让 AI 带 AIFork-and-Delegate 多Agent协作架构全解《Claude Code 架构解密》读书笔记 · 第09篇 · 对应第6章前半6.1-6.5导语当一个 Agent 面对复杂任务时单打独斗力不从心——代码重构需要同时研究多个文件、实现变更、运行测试大型项目分析需要并行探索不同模块。Claude Code 没有引入一个复杂的工作流引擎而是通过七个精心设计的模式在现有 QueryEngine 循环之上渐进式叠加多Agent能力。本篇聚焦前五个模式Fork-and-Delegate、Agent Type Registry、Tool Sandboxing、Scoped Memory以及贯穿其中的不引入新执行模型这一最关键的架构洞察。一、为什么需要多Agent编排从一个真实场景说起用户对 Claude Code 说帮我把项目中所有的 JavaScript 文件迁移到 TypeScript同时保证测试通过。这个看似简单的请求包含了四个独立的子任务调研阶段扫描分析依赖、类型推断、迁移执行、验证修复。单个 Agent 串行完成这一切会面临两个困境困境具体表现上下文膨胀随着处理文件增多对话历史迅速膨胀重要信息被淹没效率瓶颈明明可以并行处理的独立文件却不得不逐个排队等待但多Agent引入了新的挑战——上下文共享、安全隔离、成本控制、生命周期管理、递归防护——每一个都是生产级系统必须回答的问题。最关键的架构洞察Claude Code 没有选择引入复杂的工作流引擎如 LangGraph 的有向图、或 AutoGen 的对话协议而是通过七个模式在现有 QueryEngine 循环之上渐进式叠加多Agent能力。这种不引入新的执行模型的策略是本章最重要的架构洞察。每个子Agent仍然运行同样的 while(true) 循环同样的工具注册表同样的权限检查——只是通过精细的配置和约束让同一个执行引擎呈现出不同的行为模式。二、Fork-and-Delegate——轻量级分叉委派从 Unix Fork 到 Agent Fork命名来自 Unix 的fork()——父进程创建子进程子进程继承完整内存空间但拥有独立执行路径。Claude Code 借用了同样的隐喻继承子Agent获得父Agent的完整对话历史独立子Agent拥有独立的 QueryEngine 循环隔离子Agent的执行结果不会自动合并回父Agent但与 Unix fork 不同的是Agent Fork 面临一个独特约束——LLM API 的成本。每次 Fork 意味着向 API 发送一份完整的对话历史N 个 Fork 的成本就是 N 倍。后文的 Context Cache Sharing6.8节将展示如何巧妙解决这个问题。消息构建占位符统一化Fork 的核心在于buildForkedMessages()函数。它将父Agent的对话历史精心改造后传递给子Agent// 三步构建 Fork 消息 1. 保留父Agent的 assistant 消息包含所有 tool_use 块 2. 为每个 tool_use 创建统一占位的 tool_result 3. 追加本 Fork 子Agent特有的指令最精妙的设计是占位符统一化。当父Agent同时发起3个Fork子Agent时Fork A: [系统提示][对话历史][tool_result:Fork started...][指令A] Fork B: [系统提示][对话历史][tool_result:Fork started...][指令B] Fork C: [系统提示][对话历史][tool_result:Fork started...][指令C]前三部分字节级相同这意味着 LLM Provider 的 Prompt Cache 可以被三个子Agent共享——只有最后的指令部分不同需要单独计算。这一设计将 Fork 的边际成本从完整上下文 × N降低到接近增量指令 × N。递归防护深度为1的硬限制多Agent系统的经典风险是无限递归——子Agent创建孙Agent孙Agent再创建曾孙Agent。Claude Code 通过两道防线阻止防线机制可靠性第一道消息历史标记扫描fork_boilerplate标签可能在上下文压缩时丢失第二道querySource属性检查不可变元数据始终可靠为什么需要两道防线因为消息历史标记可能在 autocompact 时被重写丢失而querySource是不可变的元数据。双重检查确保即使一道防线失效另一道仍然有效。三重互斥与功能开关isForkSubagentEnabled()实现了三重守卫Feature Flag 总开关通过 GrowthBook 控制灰度发布与 Coordinator 互斥两者同时启用会产生混乱的任务层级非交互式会话禁用SDK/API 模式没有终端UIFork子Agent的权限请求无法冒泡这种互斥设计体现了一个重要原则同一系统中不应存在两种语义重叠的编排机制。Fork 和 Coordinator 解决的都是任务并行化问题但方式不同——如果同时启用开发者和 LLM 都无法判断何时使用哪种方式。严格角色约束10条不可协商规则Fork 子Agent在创建时收到一组严格的行为规则不得创建子Agent、不得向用户提问、必须按固定格式输出结果、不得修改自身系统提示……共10条不可协商规则。这些规则与代码层的递归防护形成了纵深防御——即使 LLM 在极端情况下绕过了代码检查system prompt 中的硬性规则仍然会约束它的行为。这种代码提示双层防御策略贯穿全书。三、Agent Type Registry——代理类型注册表四层加载源与优先级覆盖Agent定义来自四个层级优先级从低到高内置Agent代码硬编码 ← 被覆盖 插件Agent通过插件系统注册 ← 被覆盖 用户自定义Agent~/.claude/agents/*.md ← 被覆盖 策略Agent企业管理策略下发 ← 最高优先级加载和合并的核心算法使用 Map 的后写覆盖语义——同名 Agent 只保留最后一次set的定义。由于数组按优先级从低到高排列最高优先级的定义总是最后被写入自然覆盖了低优先级的同名定义。一行代码的优雅无需显式的优先级比较逻辑Map 的天然去重特性就完成了覆盖。Markdown Frontmatter 定义格式Claude Code 做了一个出人意料的格式选择用 Markdown 文件定义 Agent---name:code-reviewerdescription:Reviews code for quality and security issuestools:-Read-Grep-Globmodel:sonnet---You are a code reviewer. Focus on:1. Security vulnerabilities 2. Performance issues 3. Code style consistency Never modify files. Only report findings.YAML frontmatter 包含元数据Markdown 正文就是 Agent 的 system prompt。这种设计有三个显著优势对人类可读非开发者可以直接编写和编辑 Agent 定义对程序可解析标准 YAML 解析器即可提取元数据版本控制友好Markdown 文件的 diff 清晰可读与 LangChain/AutoGen 使用 Python/TypeScript 代码定义 Agent 相比Markdown 方式将 Agent 定义外部化为配置文件使得创建和修改可以在不改代码的情况下完成。6种内置Agent的Feature Flag矩阵Agent特性开关可用工具general-purpose无始终可用全部工具ExploreEXPLORE_SUBAGENT只读工具PlanPLAN_SUBAGENT只读工具statusline-setup无Read EditverificationVERIFICATION_SUBAGENT只读 Bashclaude-code-guideCLAUDE_CODE_GUIDEWebFetchCoordinator 模式下使用require()而非import加载 Worker Agent 定义——这打破了循环依赖链builtInAgents.ts → workerAgent.ts → coordinatorMode.ts → builtInAgents.ts同时通过as typeof import(...)类型断言保留了完整的类型信息。MCP动态扩展除了文件系统中的静态定义Agent 还可以通过 MCP 动态注册。MCP 服务器通过listAgents()协议方法暴露新的 Agent 类型系统将它们与本地定义合并。这种静态定义动态扩展的双轨模式使得 Agent Type Registry 既有稳定的核心能力又有灵活的扩展空间。四、Tool Sandboxing——工具沙箱隔离三层过滤架构有了 Agent Type Registry 管理谁是谁下一步是解决谁能做什么。Tool Sandboxing 实现了三层工具过滤层1: MCP工具始终放行信任边界委托 层2: 全局禁止列表所有子Agent禁用 TaskOutput/ExitPlanMode/AskUserQuestion 等 层3a: 自定义Agent额外禁止AgentTool本身防止未授权嵌套 层3b: 异步Agent白名单限制只允许可自主完成的工具黑名单/白名单的混合策略是核心设计哲学风险级别策略适用场景低风险黑名单排除少量危险工具同步内置Agent保持最大灵活性高风险白名单明确列出允许工具异步后台Agent最小化攻击面工具权限矩阵Agent文件读写BashAskUser说明general-purpose✓✓✗最宽松的子AgentExplore只读✗✗纯只读快速探索Plan只读✗✗纯只读设计方案Coordinator✗✗✗只能编排不能执行Worker (async)✓✓✗可执行但不可嵌套核心原则编排者不执行执行者不编排。Coordinator 只能分派任务给 WorkerWorker 只能执行具体操作不能再创建子Agent。这种职责分离防止了复杂的嵌套层级也简化了权限推理。MCP工具的特殊地位过滤算法第一行if (tool.name.startsWith(mcp__)) return true——MCP 工具始终放行不受任何过滤规则限制。这是一个务实的设计决策。MCP 工具由外部服务器提供Claude Code 无法预知其名称和功能无法纳入静态的白/黑名单体系。同时MCP 服务器本身负责自己工具的安全控制。这种信任边界委托策略避免了在两个系统之间重复实现安全检查。五、Scoped Memory——分层记忆作用域Agent的长期记忆需求多Agent系统的关键挑战是记忆。不同于单次对话中的短期记忆由上下文窗口承载Agent还需要跨会话的长期记忆——记住用户偏好、项目约定、之前的学习成果。但不同类型的记忆有不同的共享需求记忆类型共享范围示例全局偏好所有项目共享“用户喜欢简洁的代码风格”项目约定与项目团队共享“本项目使用 Prettier缩进2空格”本地配置仅本机保留“本地开发环境的数据库端口是5433”三级作用域设计Claude Code 通过目录层级自然映射三种共享需求User作用域: ~/.claude/agent-memory/type/ ← 跨项目共享 Project作用域: .claude/agent-memory/type/ ← 通过 VCS 与团队共享 Local作用域: .claude/agent-memory-local/type/ ← 仅本机.gitignore排除无需引入任何数据库或远程存储服务就实现了三种不同粒度的记忆持久化。这是利用现有基础设施的典范——User 作用域天然跨项目Project 作用域天然随 Git 共享Local 作用域约定俗成被忽略。路径安全防护记忆目录路径涉及文件系统操作必须防止路径遍历攻击如 Agent 类型名包含../../// normalize() startsWith() 经典路径安全模式constnormalizedPathnormalize(absolutePath)if(normalizedPath.startsWith(join(memoryBase,agent-memory)sep))returntrue先规范化路径消除..、.等相对元素再检查前缀匹配。这与第8章安全纵深防御的路径遍历防护策略一脉相承。Fire-and-Forget目录创建记忆加载函数需要确保目录存在但目录创建是异步IO操作而记忆加载是同步函数它是 system prompt 构建流程的一部分不能阻塞。voidensureMemoryDirExists(memoryDir)// 异步创建不等待结果returnbuildMemoryPrompt({memoryDir,...})void关键字明确表示有意忽略 Promise 结果。成立的前提是即使第一次加载时目录尚未创建完成Agent 也能正常工作——因为真正的记忆读写发生在后续的工具调用中到那时目录早已创建完毕。自动会话记忆提取双阈值触发判断条件1: token增量超过阈值说明对话有了新的实质性内容 条件2: 工具调用次数超过阈值说明Agent执行了有意义的操作两个条件必须同时满足避免了在用户只是闲聊或Agent在思考时频繁触发无意义的记忆提取。记忆提取本身通过 Fork 子Agent完成且该子Agent被严格限制为只能编辑指定的记忆文件// 最小权限原则的典范if(tool.nameFILE_EDIT_TOOL_NAMEinput.file_pathmemoryPath)return{behavior:allow,updatedInput:input}return{behavior:deny,message:...}记忆提取Agent只能编辑一个特定文件不能读取其他文件、不能执行命令、不能创建子Agent。这是最小权限原则的教科书式应用。横向对比维度Claude CodeLangGraphAutoGen编排模型隐式Fork或 Prompt 引导Coordinator显式有向图对话协议灵活性LLM可动态决定任务分解方式图结构编译时确定Agent间协商可预测性较低LLM决策不确定较高图结构确定中等角色隔离三层工具过滤权限矩阵节点级隔离对话上下文隔离成本优化Prompt Cache 共享Fork边际成本趋近于零无特殊优化额外LLM调用开销递归防护代码Prompt双层防御图结构天然无递归协议层约束Agent定义Markdown外部化Python代码Python代码扩展方式静态文件MCP动态注册代码修改代码修改Claude Code 的选择反映了它的定位通用 AI 编程助手任务类型不可预知需要 LLM 自主判断如何分解。而 LangGraph 更适合预定义工作流AutoGen 更适合多Agent对话场景。实战启示1. 不引入新执行模型多Agent能力不需要新的执行引擎。Claude Code 证明了同一个 while(true) 循环通过精细的配置和约束就能呈现完全不同的行为模式。这对自研系统的启示是——先做配置化再做新引擎。2. 占位符统一化是成本优化的基石Fork 的占位符设计看似简单但它解决了 LLM 系统最核心的成本问题。在自研系统中任何同一前缀、不同后缀的并行请求场景都可以用这个模式将边际成本降到接近零。3. 黑名单/白名单的混合策略低风险场景用黑名单灵活高风险场景用白名单安全。这种混合策略比一刀切更务实。关键是定义清楚什么场景是低风险、什么是高风险——判断标准是失败后果的可逆性。4. 利用现有基础设施做分层三级记忆作用域没有引入任何数据库——利用目录层级天然映射共享范围。在自研系统中先想想现有基础设施能否满足需求再决定是否引入新组件。5. 纵深防御代码提示双层保险递归防护既在代码层检查又在 prompt 层约束。在 LLM 系统中永远不要只依赖一层防线——代码可能被绕过prompt 可能被注入但两者同时失效的概率极低。下期预告第10篇将深入第6章后半6.6-6.12解析Coordinator-Worker 结构化工作流编排——编排者为何只能拥有4个编排工具Worker之间如何通过 Scratchpad 间接通信五阶段异步生命周期如何保证资源可靠清理以及 Fork vs Coordinator 的选型决策指南。本系列持续更新中欢迎关注。上一篇权限状态机与渐进式授权