九个AI Agent协作:从零构建命令行编程助手
最近有个念头在我脑子里转了很久那些能在终端里直接听懂人话、自己翻代码、改文件、跑命令的商用命令行AI编程助手底层到底是怎么串起来的与其一直拿现成工具当黑盒用不如亲自动手从零到一复刻一个。于是就有了这个项目——我用九个各司其职的AI Agent搭出了一个完整的“命令行AI编程助手”雏形我给它起了个内部代号AgentNine。这个项目解决的核心问题是把“一个全能模型直接生成代码”的简单模式升级成“多个专业Agent协作完成复杂任务”的系统模式。如果你也感兴趣多Agent编排、工具调用循环、上下文管理这些话题或者想自己动手搭一套可扩展的AI开发工具这篇完整复盘应该能帮你省下不少摸索时间。我会从架构设计、Agent分工、编排机制、实操代码、踩坑记录一条线讲下来尽量把我试过、改过、最后留下的方案都写清楚。1. 为什么要拆成九个Agent而不是一个巨型提示词1.1 单体提示词的瓶颈能跑但撑不住真实任务最开始我确实尝试过用一个单体模型来做这件事给一个很长的系统提示词让它扮演“全能编程助手”一次对话里既读代码、又改文件、还跑命令。简单任务确实能跑通但一碰到稍微复杂一点的需求就露馅了。问题出在三个地方。第一是上下文爆炸一次流程里要包含文件内容、命令输出、测试报告、历史对话全部堆在一个上下文窗口里很快就把token预算吃光了。第二是职责混乱同一个模型既要做规划又要做执行还要做审查输出风格和思考路径经常互相干扰一会儿像个工程师一会儿像个测试员。第三是调试困难流程跑挂了很难定位是理解错了、执行错了还是某个工具调用出了偏差因为所有逻辑都揉在一个Prompt里。所以我决定换个思路把能力拆开让每个环节都有独立的Agent负责每个Agent只做一件小事但做到极致。1.2 九个Agent的分工总览拆分的依据不是拍脑袋而是盯着“一个IDE里到底发生了什么”来拆。你让一个编程助手改代码至少要经过理解意图、制定计划、找代码、改代码、跑命令验证、写测试、审查质量、记住上下文、把结果展示给你——这正好对应九种不同的能力也是九个Agent的由来。编号Agent名称核心职责关键工具输入输出1指挥官意图识别与任务分派意图分类器、路由表用户原始指令结构化任务描述2规划者目标拆解与计划生成计划模板、依赖分析任务目标可执行的步骤计划3检索者仓库理解与代码定位符号索引、全文搜索查询语义相关代码片段与文件路径4编辑者文件读写与代码生成文件系统、Diff工具编辑指令变更后的文件与Diff5执行者命令运行与日志分析沙箱Shell、超时控制Shell指令执行输出与退出码6测试者测试用例生成与回归验证测试框架、覆盖率工具功能需求测试代码与运行结果7审查者代码质量把关与Bug发现静态分析、审查Prompt代码Diff问题清单与修改建议8记忆者上下文存储与摘要压缩向量库、摘要模型对话/操作记录结构化记忆9展示者流式渲染与交互反馈终端渲染、流式输出事件流格式化界面输出这个表单看起来很简单但真正落地时每个Agent的内部结构都需要仔细设计后面我会逐个讲。1.3 协作模式不是人人平等而是主从加审计多Agent系统最怕的是“人群讨论型”的组织方式——九个Agent平级对话你一言我一语最后谁也说服不了谁。我一开始就否掉了这个方案采用的是一种更接近真实团队的模式指挥官发起规划者拆解执行Agent干活审查者把关记忆者全程记录。这种结构的核心是主从关系指挥官是唯一的入口所有用户指令必须经过它做意图分类规划者是唯一的拆解器任何Agent想改别人的计划都不被允许只能通过反馈回路让规划者重新规划。审查者没有修改权限只能提意见修改权永远在编辑者手里。这像极了真实团队里的分工架构师定方案工程师改代码测试提bug项目经理排风险。好消息是Agent不会闹情绪但坏消息是如果你不把权限边界定清楚它们互相干扰起来比人还乱。2. Agent基座统一接口与工具注册机制2.1 每个Agent的本质角色提示词加工具集加消息循环九个Agent形态各异但底层抽象完全一致。每一个都是三件套组合一段体系提示词定义它的角色和边界一组可调用的工具决定它能干什么事一个消息循环保证它在执行时能不断观察工具结果并调整下一步动作。理解这个很重要。很多初学者会把Agent想象成某种魔法模型其实本质上Agent Prompt Tools Loop。模型本身不直接改文件它只是决定“要调用哪个工具”真正操作文件系统、运行Shell命令的是工具层。这种解耦设计让调试变得极其舒服——如果输出错了先看是模型判断错了还是工具执行错了很容易二分定位。2.2 统一Agent抽象代码骨架为了让九个Agent写起来不重复我先定义了一个最简基类。这个基类干三件事管理上下文消息列表、执行工具调用循环、返回最终文本结果。用Python伪代码表示大概长这样class Agent: def __init__(self, name, system_prompt, tools, model): self.name name self.system_prompt system_prompt self.tools tools # dict: 工具名 - 工具函数 self.model model self.messages [{role: system, content: system_prompt}] async def run(self, task, contextNone): messages self.messages.copy() if context: messages.append({role: user, content: json.dumps(context, ensure_asciiFalse)}) messages.append({role: user, content: task}) while True: response await self.model.respond(messages, toolsself.tools) if response.tool_calls: for call in response.tool_calls: result await self.tools[call.name](**call.args) messages.append({ role: tool, tool_call_id: call.id, content: str(result)[:10000] }) continue final_text response.text self.messages.append({role: assistant, content: final_text}) return final_text注意两个细节工具结果被截断到10000字符这是为了防止一次命令输出直接把上下文灌爆消息循环里如果模型连续调用工具我们必须在没有更多工具调用时才退出循环。2.3 工具注册机制与权限控制工具不是随便往里塞就完事安全是第一优先级。我在工具注册阶段做了三层控制白名单机制没有注册的工具根本不存在路径沙箱文件读写强制限制在项目根目录内绝对路径越界直接拒绝命令拦截执行Shell命令前会对命令文本做危险指令检测比如递归删除、重定向覆盖关键文件、下载并执行外部脚本等。权限这块我踩过坑后面会专门讲。基座搭好之后九个Agent就可以像插卡一样快速组装了。3. 编排层从“九个独立Agent”到“一个可用的系统”3.1 编排器的核心职责九个Agent如果全部独立运行那不是一个系统是九个互相不知道对方存在的孤岛。真正让它们产生化学反应的是编排器这一层。编排器要做三件核心的事流程调度决定当前该跑哪个Agent、按什么顺序跑消息路由决定上下文该传给谁哪些信息要保留状态管理记录整个任务的进行到哪一步异常时能恢复到最近可用状态。我画了一条最基础的工作流主线用户输入 → 指挥官分类 → 规划者定计划 → 对每个计划步骤执行“检索→编辑→执行→测试”循环 → 全部完成后审查者验收 → 展示者输出。这有点像一个流水线但每个环节都可能产生反馈回路比如审查者发现Bug会重新触发规划者生成修复任务。3.2 状态机与工作流流转编排器内部我用了一个简单的状态机来管理流程。状态包括INIT、PLANNING、EXECUTING、VERIFYING、REVIEWING、DONE、FAILED。每个状态对应一个Agent或者一组Agent的执行入口。状态机的好处是流程永远可控不会出现Agent自发乱跳的情况。from enum import Enum class WorkflowState(Enum): INIT init PLANNING planning EXECUTING executing VERIFYING verifying REVIEWING reviewing DONE done FAILED failed class Orchestrator: def __init__(self, agents): self.agents agents self.state WorkflowState.INIT self.plan [] self.results {} async def start(self, user_input): task await self.agents[commander].run(user_input) self.state WorkflowState.PLANNING self.plan await self.agents[planner].run(task) for step in self.plan[steps]: self.state WorkflowState.EXECUTING await self._execute_step(step) self.state WorkflowState.REVIEWING issues await self.agents[reviewer].run(self.results) self.state WorkflowState.DONE if not issues else WorkflowState.FAILED return self.results这个代码骨架简单但它已经能支撑一次完整的任务闭环。我在真实实现里增加了断点持久化把状态机快照写到本地文件这样进程崩溃后还能恢复继续跑而不是从头再来。3.3 上下文路由哪些信息该传给谁上下文路由是编排器里最容易忽略也最容易出问题的地方。每个Agent的上下文都应该只包含它完成当前任务所需的最小集合。检索者只需要拿到查询语句不需要拿到全量Git日志编辑者只需要拿到要改的文件路径和检索者提供的代码片段不需要拿测试报告全文审查者反而要拿全部Diff和测试结果。一开始我没做精细化路由所有上下文全局转发结果就是每个Agent都被海量无关信息污染输出质量急剧下降。后来改成按需路由每个Agent在编排器注册时声明自己需要哪些上下文键编排器只把对应的值塞给它。这个改动让整体准确率提升了一个台阶。4. 九个Agent的分工设计与实现细节4.1 指挥官Agent意图识别与任务分派指挥官是用户和系统之间的第一道门。它的任务不是直接解决问题而是把用户含糊的一句话转成一个结构化任务描述。比如用户说“这个项目跑不起来”指挥官不能立刻去改代码它要先判断是环境问题、启动配置问题还是代码Bug是需要诊断还是直接修复我的做法是让指挥官输出一个JSON结构包含任务类型、目标描述、涉及模块的可选字段、优先级和约束条件。这个JSON是后续所有Agent的输入基准。为了让指挥官靠谱它在调用模型之前会先做一次本地预处理把用户输入和项目根目录的README、配置文件列表一起提供给模型做参考。实测下来提前给一点项目背景指挥官的判断准确率会高很多。4.2 规划Agent目标拆解与计划生成规划者接到的输入是指挥官的任务JSON输出是一份有序的执行计划。计划里的每个步骤都必须是原子操作比如“读取user_service.py”“把第48行到第73行重构为异步实现”“运行go test ./internal/user”。原子操作的意义在于它可以直接被后续的Agent执行不需要再做理解。规划者内部挂了一套计划模板库针对常见任务类型预置骨架。比如“重构”任务模板是全量检索结构 → 识别耦合点 → 逐个模块改写 → 跑测试 → 全量回归。“Bug修复”任务模板是定位错误日志 → 追踪调用链 → 复现现场 → 修复 → 补测试。预置模板不是为了省事是给模型一个思维脚手架避免规划时漏掉关键步骤。4.3 检索Agent仓库理解与符号定位检索者专门解决“代码在哪儿”的问题。它有三个杀手锏工具符号索引表会在项目初始化时扫描全部代码文件提取函数名、类名、全局变量名并建立倒排索引全文搜索用关键词模糊匹配所有文件内容文件摘要缓存会在第一次读取大文件时生成摘要并缓存避免后面反复读同一份大文件浪费token。检索者的输出格式很严格必须是文件路径加行号范围加代码摘要不允许直接输出大段代码。这是为了避免编辑者拿到过量的代码块后“自我发挥”。我见过最典型的路径是检索者把整个文件复制给编辑者编辑者基于文件末尾上下文误删了前面的逻辑。所以检索者只定位不搬运。4.4 编辑Agent文件读写与代码生成编辑者是最容易出事的Agent因为代码写错带来的破坏是直接且难以自动恢复的。我给它设计了极强的约束只能基于检索者提供的真实代码片段进行修改禁止凭空生成文件路径修改前必须先生成Diff预览所有写入操作强制走文件锁。文件锁是我在踩了坑之后加的。一开始没有锁测试Agent和编辑Agent如果同时改同一个文件后写的人会把先写的覆盖掉。现在这个锁很简单就是一个进程内的写队列编辑者获取锁之后才能写文件而且写文件前要再次校验目标文件的当前哈希值跟读取时一致不一致就说明文件被别人动过自动放弃这次写入。这个机制看着保守但它在真实协作里避免了大量互相踩踏的问题。4.5 执行Agent命令运行与日志分析执行者负责跑命令。它的工具层包了一个沙箱Shell核心配置是命令白名单只允许运行项目开发中常见的命令比如npm install、go build、go test、python -m pytest、git status之类超时控制每条命令默认60秒超时超时直接杀死进程树工作目录锁定所有命令在项目根目录下运行禁止cd到项目外。执行者拿到命令后不会盲目执行它会先检查命令是否匹配白名单再检查参数中是否有危险模式。比如“rm -rf /”“curl xxx | sh”这种直接拒绝执行。执行结束后执行者会把标准输出、标准错误、退出码统一结构化返回尤其是退出码编排器用它来判断当前步骤是否成功。日志分析则交给执行者内置的一段小Prompt让它从输出里提取关键错误行而不是把全部日志塞给下游。4.6 测试Agent用例生成与回归验证测试者在整个链条里的位置很容易被低估。我的经验是测试Agent不是可有可无的“锦上添花”它是整个系统能否自纠错的关键。没有它编辑者改完代码后系统没法判断改对了没有。测试者的流程是这样的拿到功能的变更描述和Diff后先生成针对性的测试用例尽量覆盖正常路径和边界条件然后调用执行者跑测试如果测试失败它会整理失败信息并附上自己的根因分析打回给规划者重新生成修复计划。这个过程会循环直到测试通过或者达到最大重试次数。这里有个细节测试者生成的测试代码也会写入项目里但它写入时同样要走文件锁和Diff预览而且它跑测试前会把项目恢复到干净状态避免上一个失败测试留下的临时文件影响结果。4.7 审查Agent质量把关与Bug发现审查者是一个只读Agent它永远没有修改权。它的输入是本次任务的全部代码Diff和执行测试结果输出是问题清单每条问题包含严重等级、位置、问题描述和修改建议。不直接改代码这个定位是故意的——如果审查者也能改代码那它和编辑者的权限边界就糊了容易出现两边互相覆盖。审查者内置了一套检查清单是否引入语法错误、是否泄漏了敏感信息、是否忽略了错误处理、是否有明显的性能隐患、代码风格是否与项目现有风格一致。它不是静态分析工具而是让模型在完整Diff上下文中做推理效果比我预想的好很多。它逮到过好几次我在重构时忽略的返回值错误和边界条件问题。4.8 记忆Agent上下文存储与摘要压缩记忆者可能是这九个Agent里最“隐形”但最影响体验的一个。它的任务就是时刻盯着上下文开销。每个Agent的上下文长度不是无限的跑几轮工具调用之后可能就逼近模型的token上限了。记忆者做的事情有两件一是长期记忆把项目结构摘要、之前的决策记录、已经完成的步骤存到结构化文件里跨会话时还可以恢复二是短期压缩当一个Agent的消息列表超过阈值时它会调用摘要模型把早期对话压缩成一小段“任务历史摘要”替换掉原始对话记录。这个设计彻底解决了长任务跑着跑着上下文爆掉的问题代价是早期细节可能被摘要丢失但对于大多数开发任务来说保留结论比保留全部讨论过程更重要。4.9 展示Agent流式渲染与交互反馈最后一个是展示者它负责把系统内部的动态实时呈现给用户。它与前面的Agent不同它本身不调用模型做推理更像一个格式化渲染器。它从编排器订阅事件流把“规划完成”“正在编辑文件xx”“命令执行中”“测试失败正在重试”等状态转换成终端里的友好输出。展示者的存在不是为了好看它其实解决了多Agent系统的一个重要问题可感知性。如果用户等十几秒屏幕上毫无反馈他大概率会以为程序卡死了。把内部流程可视化之后用户能清楚地看到当前在进行哪一步、卡在哪一步体验提升非常明显。我在实现时给了它两个模式详细模式展示所有Agent的动态简洁模式只展示关键节点。5. 实操中的坑与常见问题排查5.1 多个Agent同时改一个文件导致互相覆盖这是整个项目中最折磨人的问题没有之一。症状是编辑者刚改完一个函数测试者跑的测试里却看不到那个改动或者审查者看到的最新代码里混着两个不同版本的重复逻辑。根因是多个Agent没有统一的写入仲裁。我的解决方案在前面提到过就是文件级写锁加哈希校验。任何Agent写文件之前必须拿到写锁写之前还要重新读文件哈希如果不匹配就放弃写入把这个情况上报给编排器由编排器决定是否重新加载最新内容再重试。这个方案没把并发效率做到最高但它保证了正确性。在多Agent协作里正确性永远排第一效率往后放。5.2 上下文无限增长导致token超限跑一条大任务比如让系统把整个项目从一个语言重写到另一个语言中间会产生大量的中间结果。如果不做压缩到中途就会碰到上下文上限任务直接报错。我的解决思路分三层首先是局部截断每次工具调用的返回结果最多保留10000字符超出部分截断然后是摘要压缩记忆者会定期把早期消息折叠成摘要最后是分段执行规划者把大任务拆成多个独立小步骤每个步骤完成后及时把上下文累积清空只保留必要的结论信息。这三层加在一起实测一个重构任务从头跑到尾每个Agent的最大上下文占用只有初始设计的40%左右。5.3 Shell命令失控有些命令运行起来会一直挂着不退出比如启动一个开发服务器、运行一个会监听端口的程序、执行一个等待用户输入的脚本。如果执行者不设超时整个编排流程就会被永远卡死。我在沙箱Shell里给每条命令都加了默认60秒的超时超时后直接发送终止信号给进程组确保子进程也不残留。另外对明显是“长驻进程”的命令比如server、watch、dev这类命令编排器会提示执行者改用短时验证模式比如启动后在几秒内检测端口是否监听成功然后立刻退出而不是傻等着。症状根因排查思路实际修复方案两个Agent改同一文件互相覆盖缺少写入仲裁检查对同一文件的写时间线加文件级写锁和哈希校验任务跑到一半上下文超限上下文累积无压缩监测消息长度增长曲线记忆者做摘要压缩加截断命令长时间无响应长驻进程或等待输入检查执行者日志中的卡点命令Shell层加超时与进程组清理编辑者改出语法错误基于幻觉代码写入对比编辑内容与检索结果的吻合度强制基于真实代码片段修改审查者反复提无意义问题上下文缺少任务目标检查审查者收到的上下文包含哪些字段路由时携带完整任务目标5.4 模型幻觉“读到”不存在的代码这是最隐蔽的坑。检索者本来应该读某个文件并把真实内容传给编辑者但模型在输出工具调用结果时可能因为上下文不完整或者自身幻觉凭空脑补出一些“看起来合理”但根本不存在的代码。编辑者基于这些幻觉代码修改文件结果引入了大量不存在的符号引用。我后来强制规定检索者必须返回文件路径、行号、以及从文件系统真实读取的内容片段并且这些内容必须经过工具层记录确认。编辑者在写入前也会做一次校验如果修改引用了某个函数或变量但这个符号不存在于项目源码中编辑者要主动标记“该符号未找到”不能硬写。5.5 常见问题速查表上面几个问题只是高频踩坑的代表。我把实际项目里遇到过的典型问题整理成了速查表方便你搭建同类系统时直接对照参考。这些问题的共性是它们都不是模型能力不够而是系统设计上缺少约束。多Agent系统的稳定本质是靠机制兜底不靠模型自觉。6. 一次完整实测把项目从Python重构到Go光说不练没有说服力。我挑一次最完整的实测过程来讲。任务很简单一句话“把模拟项目X的核心模块从Python重构成Go保留原有接口含义。”指挥官先把这句话解析成任务JSON任务类型是“重构”目标是核心模块约束条件是“保留接口语义”。规划者基于重构模板生成了七步计划盘点Python源码结构 → 梳理模块间依赖 → 按依赖顺序翻译核心逻辑 → 生成Go模块骨架 → 编写对应测试 → 运行编译与测试 → 全量审查回归。检索者开始干活它返回了整个项目的目录树和核心模块的函数签名清单标注了哪些函数是外部调用入口。编辑者先建了Go模块目录然后按依赖顺序逐个文件翻译。执行者跑go build时第一次报错原因是某个工具函数的参数顺序在翻译时颠倒了。测试者补了两个边界用例后跑出失败审查者顺着失败链定位到一个goroutine泄漏的隐患。这条链路让我最满意的地方是没有一个Agent需要被“提醒”下一步做什么。规划者给的是原子化指令执行Agent天然知道下一步是验证还是修订。整个过程大概持续了六分钟中间模型调用了一百多次工具最后输出的代码通过了我额外准备的一轮人工Review。虽然离商用工具还有距离但对一个从零搭的系统来说已经达到能实际用的水准。做完这个项目我个人最大的体会是单个Agent的能力取决于模型系统级Agent的能力取决于编排。九个Agent放在那里并不会自动产生智能真正起作用的是它们之间的分工边界、权限控制和反馈回路。如果你也想自己搭一套多Agent系统我的建议是先不要追求Agent数量多把指挥官、规划者、审查者这三个核心角色跑通再加执行类和工具类Agent这样每加一个角色都能明显感受到系统能力的边界在往外扩一点。最后分享一个我在做记忆Agent时的取舍心得很多人一提到记忆系统就往向量库里钻但对命令行编程助手这个场景文本摘要就够用了反而轻量可靠。嵌入检索的成本和复杂度未必换来对应的效果提升先想清楚你的上下文里到底什么信息需要被长期精确查找再决定要不要上向量库。这个判断原则放在整套Agent架构设计里也一样成立。