用9个AI Agent从零复刻Claude Code:多Agent协作架构全解析
这段时间AI编程工具圈最火的名字应该就是Claude Code了。一个跑在终端里的AI助手能自己翻代码、改代码、跑命令、边干边汇报看起来像是给程序员配了个远程实习生。我一边用它干活一边忍不住琢磨它的核心机制到底是什么如果不用官方实现纯靠我自己设计一套多Agent协作架构能不能把同样的事也做出来于是就有了这个项目我用9个AI Agent从0到1复刻了一个功能完整的Claude Code。这篇文章会把完整的拆解思路、每个Agent的职责定界、Agent之间的协作协议、上下文管理策略以及我实际踩过的坑全部写出来。对multi-agent系统和AI编程工具感兴趣的朋友这篇应该能给你一些网上找不到的实操经验。1. 项目拆解先行复刻Claude Code到底在复刻什么开始动手之前我花了整整两天研究Claude Code的使用体验把它的能力一项项列出来。这个步骤很多人会跳过直接开写但我觉得这是整个项目里最重要的一步——你不知道终点长什么样跑得再快也是白跑。拆到最后Claude Code的核心能力其实就五块终端里的交互式对话用户输入自然语言指令它实时输出思考过程和执行结果支持流式展示和中断。代码库感知与检索能理解当前的Git仓库结构定位文件、函数、类做关键词搜索从全局理解项目。工具调用能力读写文件、执行Shell命令、运行测试、安装依赖把“理解代码”转成“操作代码”。长任务规划与记忆面对多步骤任务能拆解计划跨对话保存上下文和项目约定。质量反馈闭环每轮执行后能自查、测试、修订而不是干完就完。想明白这一点后我又问了自己一个问题为什么不用一个“超级全能Agent”一把梭而是要用9个Agent一起协作这其实是这个项目最关键的设计决策。单Agent模式最大的问题是上下文污染。一个Agent既要读代码、又要改代码、还要跑命令、还要做测试所有信息都堆在同一个对话上下文里。任务一复杂上下文就爆炸模型开始“忘记”前面的指令生成质量断崖式下跌。我实测过一个包含20个文件修改的完整任务单Agent跑到第10个文件之后经常出现改错变量名、漏改引用这种低级错误。多Agent分而治之的本质是把上下文按职责隔离。每个Agent只关心自己该管的那部分信息全局状态通过明确定义的消息协议来传递。这样做的好处非常明显排查问题的时候你只需要看对应Agent的日志而不是在一堆混杂信息里大海捞针。所以我最终确定9个Agent每个专职干一件事。用人类团队打比方就像你给一个软件项目配了产品经理、架构师、后端开发、前端开发、QA测试和运维各司其职高效协同。2. 九个Agent的详细分工与职责边界Agent拆分的粒度是这类项目的灵魂。拆太粗每个Agent还是背着一大堆职责上下文隔离的优势就没了拆太细Agent之间的通信开销会吃掉所有收益跑一个任务要来回广播几十轮消息。我最后定下的9个Agent每一个都对应一条清晰的能力线。2.1 Conductor总编排者Conductor是所有任务的入口也是唯一直接面对用户的Agent。用户输入自然语言指令后由它来做意图理解、任务拆解、子任务分发和最终结果汇总。它不直接接触代码文件只做调度决策。我在设计Conductor的System Prompt时重点强调了几件事第一把大任务拆成有依赖顺序的小任务哪些可以并行哪些必须串行第二每个子任务要给出明确的完成标准防止下游Agent做一半就交回来第三全局信息只保留任务状态列表、当前目标、注意约束其余细节一律不留在Conductor的上下文里。这个“轻量化编排”的原则非常重要Conductor一旦开始存细节它就变成单Agent了。2.2 Searcher代码库探索员Searcher负责回答“代码在哪”的问题。给定一个关键词、一个函数名或一个需求描述它负责扫描项目目录结构定位相关文件、类、函数定义和引用关系。具体实现上我给它封装了三个核心工具目录树扫描、glob文件匹配和代码内容搜索。代码内容搜索走的是rgripgrep性能比grep好一个量级对大型仓库尤其重要。Searcher的输出是一份结构化的“位置清单”文件路径、行号范围、匹配片段、相关文件之间的依赖关系。这个Agent不需要读懂整段代码逻辑它的任务就是把范围缩小给下游Reader省掉漫无目的的扫描。我踩过的一个坑是Searcher返回的匹配结果太多时Reader会加载大量无关注释和空行白白消耗token。后来我给Searcher加了一条规则——每个匹配结果必须附上“相关度评分”默认只返回评分最高的前10个避免信息过载。2.3 Reader代码阅读分析员Searcher告诉你代码在哪Reader负责把代码真正读进去。它在拿到文件路径和行号后读取目标内容并对代码逻辑做概要分析输出结构化的代码理解摘要而不是把原始文件原封不动交给后面的Agent。这个Agent是我花了最多心思调优的。我给它定义了专门的输出格式代码块的职责描述、关键函数签名与入参出参、对当前任务的影响点、推荐修改的位置和修改思路。这样做的好处是Editor拿到Reader的分析结果后不需要再从头读一遍代码直接照着摘要开工就行。Reader还内置了一个大文件拆分策略。文件超过300行时会自动分段读取并分别生成摘要最后再合并成一份总体分析。否则一个上千行的源码文件塞进上下文后面所有Agent的性能都会暴跌。这个策略虽然简单但效果立竿见影。2.4 Planner方案规划师Planner是整个团队里的“架构师”。它根据Reader产出的代码理解和用户原始需求输出一份明确的实施计划。计划格式是固定的Markdown表格包含修改步骤、涉及文件与行号、每个步骤的具体操作、所需测试用例、依赖关系和风险点。一开始我是让Conductor兼职做规划的结果发现Conductor输出计划过于模板化缺少针对性。后来把规划独立给Planner让它可以针对具体代码结构定制方案Conductor只负责审核计划是否覆盖了所有需求点。这个改动让最终计划的质量提升非常明显。Planner还有一个隐藏功能——生成“反向计划”。也就是先想清楚“我怎么知道这个任务完成了”再倒推出执行步骤。这可能是我在这个项目里学到的最有价值的一个思路以终为始地做规划任务的完成度就不会跑偏。2.5 Editor文件操作员Editor是整个系统里唯一被允许写文件的Agent。它拿到Planner的计划、Reader的分析摘要之后执行精确的文件修改。精确修改是Editor的底线原则。我要求它默认输出Unified Diff格式的补丁而不是整文件覆盖。这样做有两个原因一是减少上下文中传输的数据量二是让修改历史可追踪、可回滚。我在代码层预留了一个apply_patch工具专门解析diff并应用到目标文件如果diff应用失败系统会直接报错而不是让Editor自己“凭着感觉改”这能防止上下文不一致导致的静默错误。写工具权限也有严格限制。Editor只能修改白名单目录内的文件对于依赖文件、构建产物、配置文件一律拒绝。我还在Editor的prompt里反复强调写完文件后必须调用一次文件内容校验工具确认改动已生效再返回结果。这一步是为了堵住“工具调了但实际没改上”的空档。2.6 Executor命令执行员Executor负责所有Shell命令的执行包括跑测试、运行构建工具、安装依赖、启动本地服务等。它和其他Agent最大的不同在于它不读代码只执行命令并反馈结果。我把Executor设计成了沙箱模式。默认情况下它运行在隔离的临时目录非白名单命令一概拒绝执行。命令超时时间设为60秒超过就终止并返回超时错误。所有高危命令比如删除操作、全局安装需要二次确认才能放行。执行结果回传也有讲究。命令输出过长时我会让Executor做三件事截断前100行保留关键部分、提取退出码和错误信息、给出一句话的结果判断。否则跑一个测试命令输出几千行日志下游Agent的上下文就直接被灌爆了。2.7 Reviewer代码审查员Reviewer充当QA的角色。它在任务执行完成后读取改动后的文件以及生成的diff从代码质量、逻辑正确性、测试覆盖、命名规范、潜在bug等维度做审查最终输出一份问题清单。每个问题都标注严重级别并且必须给出具体的修复建议和定位信息。如果Reviewer发现问题它会将问题单打回给Conductor由Conductor决定是重新走一遍Editor修复还是直接忽略。这个反馈闭环是整个系统质量稳定的核心保障。我在实验中发现没有Reviewer的时候Editor经常在某些局部优化上“自作聪明”比如顺手改掉了和任务无关的代码。Reviewer出现之后这种问题几乎绝迹了因为它会在审查结果里明确标注哪些改动是任务范围外的。2.8 Memory记忆管理存储员Memory负责系统唯一的持久化状态。它记录三类信息项目级约定类似Claude Code的CLAUDE.md、当前任务的执行状态快照、跨会话的历史决策记录。每次任务开始前Memory会生成一份项目上下文摘要注入到Conductor的初始上下文中每次任务结束后Memory会同步更新项目约定库把新学到的信息写进去。比如项目里约定“接口返回值统一用Result 包装”这个规则一旦被Memory记下来后续所有Agent在处理相同逻辑时都会自动遵守。这里有个容易忽略的细节Memory写入的数据必须经过“去重摘要”处理。否则项目跑上一周约定库里全是重复和冗余信息反而拖慢上下文。我给它设定了一个原则——只记录能够影响未来决策的高价值信息其余全部丢弃。2.9 UI Agent终端交互器最后一个Agent专门负责交互界面。它处理用户输入解析、流式输出格式化、进度条展示和中断控制。由于独立封装成Agent交互逻辑和其他AI能力完全解耦想换一套前端展示层的时候其他Agent可以完全不动。UI Agent实现了几个我觉得很实用的小功能分阶段展示当前由哪个Agent在干活、支持按Esc中断当前任务、支持输出折叠查看详细日志。这些功能让多Agent协作的过程“可感知”用户等结果的时候不会抓瞎调试的时候也能清楚地看到是哪个环节卡住了。3. 让九个Agent顺畅协作的消息协议与上下文管理Agent分好了工接下来的难题是怎么让它们在同一个系统里顺畅协作而不是各说各话。这一步需要有明确的通信协议、路由策略、状态同步机制以及一套能控制上下文膨胀的方案。3.1 统一消息格式与路由策略所有Agent之间的消息我统一用JSON格式定义结构非常固定{ type: task, task_id: task_001, agent: searcher, action: search_symbol, payload: { keyword: validateLoginRequest, scope: src/ }, status: pending }每条消息必须有唯一的task_id这是整个追踪体系的基础。Agent通过消息队列收发消息Conductor作为路由中心维护task_id与Agent状态之间的映射关系。任务完成或失败时Agent把结果以新消息的形式返回给ConductorConductor再决定下一步分发给谁。这种设计本质上是一个轻量级的Actor模型每个Agent是独立的执行单元互相之间不直接通信只和Conductor交互。这样做有一个巨大的好处——没有任何两个Agent之间产生循环依赖调试时可以按task_id把整条链路完整回放出来。3.2 上下文隔离与Token预算控制多Agent系统最大的开销是token。如果每个Agent各自维护一大段历史记录成本会非常吓人。我的方案是每个Agent的上下文只包含它的System Prompt、当前任务输入、任务输出历史对话全部交给Memory管理Agent本身不做记忆。我按照不同的工作阶段设了一套token预算参考值实测下来比较稳Agent上下文预算约说明Conductor8k只存任务列表与状态Searcher4k只存搜索关键词与结果清单Reader16k需要读文件详情Planner8k输入摘要输出计划Editor16k需要读diff与目标文件Executor4k只存命令与结果摘要Reviewer12k需要读diff与代码片段Memory8k只存项目约定摘要UI2k交互状态这个预算控制的效果很直接整个系统跑完一个包含修改测试的任务token消耗大约是单Agent方案的一半左右而且输出质量的稳定性提升非常明显。3.3 任务状态机与断点恢复我再给整个系统设计了一个简单的任务状态机状态流转是pending待调度running执行中succeeded成功failed失败canceled中断所有任务状态都实时写入一个状态文件。这意味着如果某个Agent执行到一半进程崩溃重启后Conductor能从状态文件恢复整个任务树标记出哪些子任务已完成、哪些需要重跑。这个机制一开始我没做结果有一次跑了40分钟的批量任务崩了全部白干从那以后我就老老实实把状态机补上了。断点恢复对长任务的实用性极高。实测中一个包含30个子任务的大型重构如果中间某一个文件修改失败我可以指定从失败任务重新跑而不是整个任务全部从头来一遍。3.4 Agent间知识传递的格式约束Agent之间传递的不只是文字还有结构化知识。如果每个Agent输出格式不统一下游解析起来就是灾难。我花了很长时间做的一件事就是为每个Agent定义严格的输出Schema并且用程序去校验。比如Searcher的输出Schema是{ matches: [ { file: src/auth/login.ts, line_start: 14, line_end: 22, content: function validateLoginRequest(req) {...}, relevance: 0.92 } ] }Schema校验不通过的结果系统会直接打回重新生成。这个约束看起来死板但它对系统稳定性的贡献是决定性的——你可以想象如果Reader输出一段不规范的分析Editor可能就会拿错误信息去改代码最后改出来的东西根本不是想要的。4. 从0到1搭建的实操过程与关键实现理论设计说完了这部分是实打实的搭建过程。我把整个项目的完整流程拆成几个阶段每个阶段都备注了当时踩的坑和做的关键决策方便你想复刻的时候有章可循。4.1 第一步搭建Agent运行时框架我的技术栈选型是Node.js TypeScript。选它的原因很直接——Claude Code本身就是Node生态终端交互用Node处理很顺TypeScript的类型系统对面这种多Agent、多工具调用的复杂结构非常有帮助。框架层我没有上LangGraph和n8n这些现成的Agent编排框架而是自己写了一套轻量级的运行时。核心模块就三个消息队列单进程内存队列、Agent注册表维护Agent类型、能力、可用工具、调度器按任务依赖图分发任务。我解释一下这个选型逻辑现成框架学习成本高、抽象层级重而且多Agent协作里最关键的消息结构、任务状态流转、上下文管理都可以自己控制。用自研框架出问题时候你可以直接看底层代码不用跟框架作者博弈。如果你第一次做类似项目我建议先小规模自研跑通之后再研究是否引入框架优化。4.2 第二步实现Agent基类和工具层每个Agent都继承自一个统一基类基类里封装了接收消息、解析任务、执行任务、返回结果、错误处理、日志记录这些通用能力。真正不同的只有每个Agent的System Prompt和它能调用的工具清单。工具层的设计参考了MCP的思路——每个工具都有统一的输入输出格式独立注册到工具注册表中。下面是一个工具定义示例const searchFilesTool { name: search_files, description: 搜索指定目录下的文件支持glob模式, parameters: { pattern: { type: string, required: true }, path: { type: string, required: false, default: . } }, execute: async (params) { // 实际实现返回结构化结果 } };工具注册表里维护了每个工具可以被哪些Agent调用。比如“write_file”工具只在Editor的手里Searcher只能调用“search_files”和“read_file”不能越权。这个权限控制是系统安全的底线一定不能省。4.3 第三步写System Prompt并在联调中反复修订这个阶段是我整个项目里最耗时的环节比写代码难多了。每个Agent的System Prompt一开始给我感觉写得够清楚了但联调时还是频繁出各种奇奇怪怪的问题。最后总结下来一份好用的Agent Prompt必须包含四部分角色定义、工作流程、输出规范、禁区边界。以Editor为例它的Prompt结构大致是角色你是项目中唯一的文件修改者负责所有代码变更。工作流程接收分析摘要→生成diff→应用diff→校验文件变更→返回结果。输出规范每次修改必须输出diff摘要、变更文件列表、修改后的关键代码片段。禁区不得修改白名单外文件不得删除测试代码不得改动与任务无关的行不确定时必须询问Conductor。联调阶段的教训是Prompt永远不要追求一次性写完美要跑真实任务然后在失败案例里反推哪里没说清楚。比如Editor第一次总尝试用“整文件覆盖”的方式改代码我在禁区里加了“禁止输出完整文件内容只允许输出diff片段”这一条之后行为立刻纠正了。4.4 第四步接入模型API并设计多模型兼容层Agent的核心推理依赖大模型API。我最初直接接的是Anthropic的API但考虑到成本、稳定性和灵活性我专门做了一层模型适配层让整个系统可以无缝切换不同的模型提供商。目前我的实现里已经适配了Anthropic、DeepSeek和几款本地模型。兼容层的设计其实就是统一了一下请求参数和响应格式。上层Agent以为自己在和同一个模型对话底层实际用哪个模型完全由配置文件决定{ model_provider: anthropic, model_name: claude-sonnet-4-5, temperature: 0.2, max_output_tokens: 4096 }有一点我建议你注意不同模型对工具调用的支持差异很大有的模型在few-shot、工具定义特别多时表现会崩塌。我的做法是在兼容层做模型能力探测如果某个模型对复杂工具Schema支持不好就自动切换成简化版工具描述避免任务挂掉。4.5 第五步用一个真实任务跑通全链路系统搭建完成后我选了一个真实任务做全链路验证“给登录模块增加密码强度校验”。这个任务横跨了全部9个Agent而且涉及搜索、阅读、规划、编码、执行、审查、记忆写入非常能检验系统的完整性。实际执行链路是这样的用户在终端输入需求UI Agent把指令转给ConductorConductor把任务拆成“定位登录模块”、“分析校验逻辑”、“产出修改方案”、“执行修改”、“运行测试”等子任务Searcher找到相关文件Reader分析代码逻辑Planner给出修改方案Editor实施修改Executor跑测试Reviewer审查改动Memory记录项目约定最后UI Agent把结果输出给用户。整个流程跑下来大概用了5分钟模型token消耗约3万。首次全链路跑通时候我确实挺兴奋的因为这意味着整个架构不仅设计上可行而且真实任务也真的能跑通。后面我又拿几个不同类型的项目反复测了好几轮修掉了不少边界问题系统的稳定性和效果才逐渐真正进入可用的状态。5. 多Agent系统最容易翻车的几个点和排查方法这个部分我想重点聊聊实际调试中遇到的典型问题。多Agent系统的调试难度比单Agent高出不少因为问题可能在任何一个环节也可能在Agent之间的衔接中。我整理了一张问题速查表然后挑几个最重要的展开讲。现象可能原因排查方法下游Agent拿到了上游的脏数据消息Schema未校验在消息入口加JSON Schema校验多个Agent同时改同一个文件缺少文件锁写操作全部串行化加文件级锁任务执行一半Agent“失忆”上下文被无关信息挤爆严格限制任务上下文大小工具调用返回成功但文件没变diff应用失败但没报错增加文件内容对比校验一次任务拆了几百个子任务任务拆分粒度过细给Conductor限制最大子任务数token消耗飙到预期两倍输出带了过多重复内容在Prompt中强制精简输出格式先说最常见的一个坑下游Agent拿到错误的上游数据。这个问题的根源基本就是Searcher或Reader的输出不规范多带了一堆无关代码块Editor识别不了自己到底该改哪里就开始“自由发挥”。我的解法就是在Agent间通信的入口处加一层严格的Schema校验校验不通过就直接重试而不是把错误数据继续往下丢。这套“校验前置”的思路比让下游Agent自己辨别数据合法性强太多了。再有一个高频问题是多轮循环的上下文遗忘。某一次跑一个涉及8个文件的核心重构时我明明在Conductor里存了完整的任务状态但跑到第6个文件时Editor给出的修改还是跑偏了因为它自己的上下文里存的前面文件的修改记录太多把当前目标信息给挤没了。后面我把Editor的任务上下文重构为“只接收当前任务摘要、不携带任何历史修改记录”问题才彻底解决。单个Agent的上下文一定要做“极简主义”这是多Agent协作中绝对不能妥协的原则。另外还有一个运维层面的问题模型输出格式不稳定。比如我要求Editor输出JSON格式的结果摘要时偶尔会遇到模型返回标准的代码块包裹JSON解析器直接把整段文本当成字符串处理导致下游流程卡死。解决办法是不断在Prompt里强调“不要用代码块包裹JSON直接输出纯JSON”同时在解析层做兼容优先提取代码块内容实在解析不了再重试。最后是资源冲突的问题。多个子任务并行执行时如果同时修改同一个文件就会出现冲突。我现在的策略是文件级写锁 同文件串行化。具体实现是Editor写入之前要申请文件锁拿不到锁就排队等待防止并发写坏文件。实测下来并发场景的出错率从之前的10%以上降到了几乎为零。6. 这套9-Agent架构的扩展价值与我的实践心得项目跑通之后我又用这套架构做了不少其他的事情。我发现这套多Agent协作模板的迁移能力比预期要强得多不只是能当Claude Code的替代品很多场景都能复用。第一是代码库智能问答和文档生成。只需要把Editor和Executor这两个写操作型Agent换掉留下Searcher、Reader、Planner和Memory就能做成一个“懂你项目”的代码问答机器人。它可以快速回答“这个项目的支付模块是怎么设计的”、“哪些地方用了Redis”这类问题生成的答案质量明显比直接把整个仓库灌进上下文效果好。第二是自动化测试补全和缺陷定位。把Reviewer和Executor结合起来可以让系统自动读测试覆盖报告、定位薄弱环节、生成补充用例、跑完并汇报结果。这个流程以前我要花一两个小时现在只要丢给系统一个任务描述就行剩下的全自动。第三是轻量级的自动化运维巡检。把Executor从“跑开发命令”扩展成“跑运维脚本”配合Memory定时记录状态可以实现准实时的系统巡检和异常发现。虽然没有专业监控系统那么全面但胜在零成本起步、完全可定制。从个人实践的角度我对这套方案的体会是多Agent的真正价值不在于“听起来高大上”而在于它把复杂任务拆成了一个个可独立优化、可独立调试的小闭环让原来一把梭做不好的事情变得可控。项目里那几个关键决策我认为最值得迁移到其他项目的经验有这么几条单一Agent的上下文必须“极简”做到只关注当前任务的那点信息别的什么都不管。消息Schema校验是系统的骨架越早加越好。它能把很多潜在bug扼杀在发生之前。职责隔离要彻底写文件的Agent绝不读测试结果读测试结果的Agent绝不碰文件。越权往往就是Bug的来源。状态机不仅是为了可恢复更是为了可观测。没有状态机你根本说不清一个复杂任务执行到一半到底发生了什么。最后再分享一个小技巧。如果你也想复刻类似的项目我建议第一版千万不要追求功能齐全先搭一个最小可用闭环——比如只保留Conductor、Searcher、Editor、Executor这4个Agent跑通一个最简单的“修改一行代码并运行测试”的任务然后再逐步加Agent、加工具。因为多Agent系统最难的从来不是单个环节而是Agent之间的协作稳定性和异常处理逻辑。先把链路跑通再谈丰富能力这个顺序能让你的开发效率提升很多。