用9个AI Agent从零复刻Claude Code:多Agent协作架构全解析
2026/9/14 17:31:03 网站建设 项目流程

这段时间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文件匹配和代码内容搜索。代码内容搜索走的是rg(ripgrep),性能比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把结果以新消息的形式返回给Conductor,Conductor再决定下一步分发给谁。

这种设计本质上是一个轻量级的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把指令转给Conductor;Conductor把任务拆成“定位登录模块”、“分析校验逻辑”、“产出修改方案”、“执行修改”、“运行测试”等子任务;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之间的协作稳定性和异常处理逻辑。先把链路跑通,再谈丰富能力,这个顺序能让你的开发效率提升很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询