1. 从“单打独斗”到“团队协作”:子代理到底解决了什么痛点
如果你最近半年一直在用各类 AI 编程助手写代码,大概率经历过这样的场景:让它重构一个模块,它改着改着就忘了前面的约束;让它同时处理前端样式和后端接口,它顾此失彼,最后两边都写得半吊子。这不是模型不够聪明,而是单线程的对话模式天然不适合处理多目标、多约束的复杂任务。
子代理(Subagents)这个概念的落地,本质上是在解决一个工程问题:如何让一个主控智能体把复杂任务拆解成若干可独立执行的子任务,再分派给专门的子代理去并行或串行处理,最后汇总结果。你可以把它理解成一个技术主管带着几个专项工程师干活——主管负责拆需求、定接口、验收,工程师各自埋头写自己那块,互不干扰。
这次围绕 Codex CLI 生态出现的子代理能力,核心价值在于三点。第一是上下文隔离,每个子代理有自己独立的对话历史和上下文窗口,不会因为一个子任务的冗长推理污染整个任务的记忆。第二是角色专精,你可以给不同子代理配置不同的系统提示词、不同的模型、甚至不同的工具权限,比如一个专门做代码审查、一个专门写测试、一个专门查文档。第三是可编排性,主代理通过配置文件定义子代理的调用关系,形成一条可复现的工作流,而不是每次靠临时对话去碰运气。
适合谁来用?如果你只是偶尔让 AI 帮你写个正则、改个报错,那子代理对你来说是杀鸡用牛刀。但如果你在做多文件重构、跨模块功能开发、需要反复迭代的工程任务,或者你本身就在搭建基于 CLI 的自动化开发流水线,那这套东西值得你花时间吃透。下面我会从配置结构、实操流程、踩坑经验几个维度,把这件事讲清楚。
2. 核心机制拆解:子代理的配置结构与运行逻辑
2.1 配置文件是整个体系的中枢
Codex CLI 这类工具的子代理能力,几乎全部依赖一个核心配置文件来驱动。通常这个文件叫config.toml,放在用户主目录下的工具配置目录里。它的结构大致分为三层:全局设置、模型提供方定义、以及子代理定义。
全局设置里管的是默认模型、默认提供方、日志级别、超时时间这些。模型提供方定义决定了你调用的是哪家服务、走什么接口地址、用什么密钥。子代理定义则是每个子代理的独立档案,包括它的名字、描述、系统提示词、绑定的模型、可用的工具集。
我见过太多人卡在第一步:配置文件写好了,一运行就报model provider 'openai' not found。这个报错的根源几乎都是提供方名称和引用名称不一致。比如你在[model_providers.xxx]里定义的名字是myprovider,但在子代理里写的是provider = "openai",那自然找不到。配置文件的引用是严格按字符串匹配的,不认别名。
# 全局默认 model = "gpt-4o" model_provider = "myprovider" [model_providers.myprovider] name = "MyProvider" base_url = "https://your-endpoint.example.com/v1" env_key = "MY_API_KEY" wire_api = "chat" [subagents.reviewer] description = "专门做代码审查,只读不写" model = "gpt-4o" model_provider = "myprovider" system_prompt = "你是一名严格的代码审查员,只指出问题,不修改代码。" tools = ["read_file", "search"] [subagents.tester] description = "根据代码生成单元测试" model = "gpt-4o-mini" model_provider = "myprovider" system_prompt = "你负责为给定函数生成边界条件完整的单元测试。" tools = ["read_file", "write_file", "run_command"]上面这段配置里,env_key指定了从哪个环境变量读取密钥,这是比把密钥硬编码在文件里安全得多的做法。wire_api决定了通信协议格式,不同服务商可能要求chat或responses两种模式,选错了会直接导致请求失败。
2.2 主代理如何调度子代理
主代理调度子代理的机制,通常有两种模式。一种是显式调用,你在对话里直接说“让 reviewer 看一下这段代码”,主代理就会把任务转给对应的子代理。另一种是自动编排,主代理根据任务描述自己判断该调用哪个子代理,甚至并行调用多个。
自动编排的可靠性取决于子代理的description写得够不够精准。这个描述不是给人看的,是给主代理做路由决策用的。我实测下来,描述里包含触发场景关键词效果最好。比如不要写“代码审查代理”,而要写“当用户要求检查代码质量、发现潜在 bug、评估安全性时调用此代理”。
子代理执行完毕后,结果会回传给主代理,主代理再决定是继续调用下一个子代理,还是汇总输出给用户。这个过程中,每个子代理的上下文是独立的,它看不到主代理和其他子代理的完整对话历史,只能看到主代理显式传给它的任务描述。这个设计的好处是防止上下文爆炸,坏处是你需要在任务描述里把必要的背景信息交代清楚,否则子代理会因为信息不足而给出泛泛的结果。
2.3 工具权限的隔离设计
子代理能调用哪些工具,是在配置里显式声明的。这个设计非常关键,因为权限最小化原则在这里直接决定了系统的安全性。一个只做审查的子代理,不应该有写文件和执行命令的权限;一个只做文档查询的子代理,不应该有修改代码的权限。
我踩过的一个坑是:给测试子代理开了run_command权限,结果它生成的测试代码里包含了一条删除临时目录的命令,虽然最终没造成损失,但那次之后我就把命令执行权限收紧了,改成只允许运行特定前缀的命令。如果你用的工具支持命令白名单,一定要配上。
3. 从零搭建一套可用的子代理工作流
3.1 环境准备与安装确认
第一步永远是确认 CLI 本身装好了。在 Windows 上,很多人会遇到“命令行里codex --version能显示版本,但在 Windows Terminal 里就是跑不起来”的情况。这通常是环境变量 PATH 在两种终端会话里不一致导致的。解决办法是在系统环境变量里把 CLI 的安装路径加到 PATH 的最前面,然后完全重启终端,而不是只开一个新标签页。
安装完成后,用codex --version和codex auth status两条命令确认版本和认证状态。如果认证状态显示 token 不可用,需要重新走一遍登录流程。这里有个细节:认证 token 的存储位置和配置文件的位置可能不在同一个目录,迁移机器的时候两个都要带走,否则会出现配置在但认证失效的情况。
3.2 模型提供方的接入配置
接入模型提供方时,base_url的填写是最容易出错的环节。很多服务要求 URL 以/v1结尾,有些则不需要。判断方法是看服务商的接口文档里给出的完整请求路径。如果文档写的是POST /v1/chat/completions,那你的base_url就应该填到/v1为止,工具会自动拼接后面的路径。
密钥的管理我强烈建议走环境变量。在 Linux 和 macOS 上,可以在 shell 的配置文件里 export;在 Windows 上,用系统属性里的环境变量设置界面添加。配置文件中通过env_key引用变量名,这样配置文件本身可以安全地提交到版本控制里,不会泄露密钥。
注意:不要把密钥直接写在 config.toml 里然后提交到 Git 仓库。即使后来删掉了,历史提交记录里依然能翻出来。这是新手最常犯的安全错误。
3.3 子代理的提示词设计要点
子代理的系统提示词,和普通对话的提示词写法有本质区别。普通对话你可以写得比较宽松,让模型自由发挥;但子代理的提示词必须极度明确地界定职责边界和输出格式,因为它是在一个自动化流程里被调用的,输出格式不稳定会直接导致下游处理失败。
我的经验是,一个好的子代理提示词包含四个部分:角色定义、任务范围、输出格式、禁止事项。角色定义一句话说清楚它是谁;任务范围列出它能做什么、不能做什么;输出格式最好给出一个具体的模板或示例;禁止事项明确列出它绝对不可以执行的操作。
举个例子,一个负责生成数据库迁移脚本的子代理,提示词里应该明确写“只生成 SQL 语句,不执行任何命令”、“所有表名和字段名必须使用反引号包裹”、“如果信息不足,输出 NEED_MORE_INFO 而不是猜测”。最后这条尤其重要,让子代理在信息不足时主动报错,比让它瞎猜要安全得多。
3.4 完整工作流的编排示例
假设我们要完成一个“给现有项目添加用户头像上传功能”的任务。用子代理工作流来做,可以拆成四步。
第一步,主代理调用一个analyzer子代理,任务是扫描项目结构,找出路由定义文件、数据库模型文件、前端组件目录,输出一份结构报告。这个子代理只有读权限。
第二步,主代理把结构报告传给planner子代理,让它产出具体的修改方案:需要新增哪些文件、修改哪些文件、数据库需要加什么字段。这个子代理也只有读权限,但它的提示词里包含了项目的技术栈约束。
第三步,主代理把方案拆成后端和前端两个子任务,分别调用backend_dev和frontend_dev两个子代理。这两个子代理有写权限,但被限制在各自的目录范围内。后端子代理只能写server/下的文件,前端子代理只能写client/下的文件。
第四步,主代理调用reviewer子代理,对两个开发子代理的产出做交叉审查,检查接口定义是否一致、字段命名是否统一。审查通过后,再调用tester子代理生成测试用例。
整个流程下来,每个子代理的上下文都很干净,不会出现“改到后面忘了前面”的情况。而且因为权限被隔离了,即使某个子代理跑偏,影响范围也可控。
4. 实操中绕不开的坑与排查手册
4.1 常见报错速查
| 报错信息 | 根本原因 | 解决方向 |
|---|---|---|
model provider 'xxx' not found | 配置文件中提供方名称与引用名称不匹配 | 检查[model_providers.xxx]的 xxx 和子代理里model_provider的值是否完全一致 |
auth token is unavailable | 认证信息缺失或过期 | 重新执行登录命令,确认 token 存储目录存在且可写 |
failed to locate the codex cli binary | CLI 未安装或 PATH 未生效 | 确认安装路径已加入系统 PATH,重启终端 |
model is not supported when using codex with a ... | 模型名称与服务商支持的列表不匹配 | 查阅服务商文档,使用其明确支持的模型标识 |
| 请求超时或连接被重置 | base_url 填写错误或网络策略限制 | 确认 URL 协议、端口、路径前缀是否正确 |
4.2 子代理不按预期被调用的排查
有时候你配置了子代理,但主代理就是不调用它,或者调用了错误的子代理。排查顺序是这样的:先看子代理的description是否包含了任务描述里的关键词;再看主代理的提示词里有没有限制它只能使用某些工具;最后检查子代理的model是否可用,如果模型本身调用失败,主代理可能会静默跳过。
我遇到过一次很隐蔽的情况:子代理配置完全正确,但主代理始终不调用。最后发现是配置文件的解析顺序问题——子代理定义写在了全局设置之前,导致解析器还没读到全局的model_provider就遇到了子代理的引用,直接报错跳过了。把子代理定义移到文件末尾就解决了。这个坑在文档里完全没提,是我对着日志一行行翻出来的。
4.3 上下文传递的信息损耗问题
子代理之间传递信息时,最容易出现的是关键约束丢失。比如主代理告诉后端子代理“用户 ID 用 UUID 类型”,但传给前端子代理时忘了说,结果前端按整数类型处理,接口对不上。
我的应对方法是:在编排层维护一份共享约束清单,每次调用子代理时都把这份清单完整附在任务描述里。清单不用长,但必须包含所有跨模块的约定,比如数据类型、命名规范、接口路径前缀、错误码格式。这份清单由主代理在规划阶段生成,后续所有子代理调用都带上。
4.4 性能与成本的平衡
子代理模式天然比单代理模式消耗更多的 token,因为每个子代理都要加载自己的系统提示词和上下文。如果子代理数量多、任务拆分细,成本会明显上升。
我的优化策略是:把不需要独立上下文的步骤合并回主代理。比如简单的文件读取、格式转换这类操作,没必要单独开一个子代理,主代理直接做就行。只有那些需要独立上下文窗口、或者需要不同权限级别的任务,才值得拆成子代理。另外,给子代理选用更便宜的模型也是个办法,审查、分类这类任务用轻量模型完全够用。
5. 进阶玩法:把子代理接入现有开发流程
5.1 与版本控制的结合
子代理产生的代码修改,最好通过分支来隔离。可以让每个开发类子代理在独立分支上工作,完成后由主代理发起合并请求,再由审查子代理做 diff 审查。这样即使子代理写出了有问题的代码,也不会直接污染主分支。
配置上可以给子代理的run_command权限加上 git 命令白名单,只允许git checkout -b、git add、git commit这类操作,禁止git push --force这种危险命令。
5.2 在 CI 流程中调用子代理
如果你有持续集成环境,可以在流水线里加一个步骤,调用审查子代理对本次提交的代码做自动审查。这个子代理只需要读权限,输出一份结构化的审查报告,流水线根据报告里的严重级别决定是否阻断合并。
这种用法的关键是输出格式必须机器可解析。让子代理输出 JSON 格式的报告,包含文件路径、行号、问题类型、严重级别四个字段,流水线脚本直接解析 JSON 做判断,比解析自然语言可靠得多。
5.3 多模型混合编排
不同子代理可以绑定不同的模型。规划类任务用推理能力强的模型,执行类任务用速度快、成本低的模型,审查类任务用对代码理解好的模型。这种混合编排能在保证质量的前提下把成本压下来。
配置上就是每个子代理的model字段填不同的值。但要注意,不同模型对提示词的敏感度不一样,同一个提示词在 A 模型上表现很好,换到 B 模型可能就不行了。切换模型后一定要重新跑一遍验证用例。
6. 一些个人体会
这套子代理体系我用了几个月,最大的感受是:它把 AI 辅助开发从“碰运气”变成了“可工程化”。以前让 AI 写代码,每次都要重新交代背景、重新纠正方向,产出质量波动很大。现在把常用的工作流固化成子代理配置,每次调用都是同样的角色、同样的约束、同样的输出格式,稳定性提升非常明显。
但也要清醒地认识到,子代理不是银弹。任务拆分的粒度、子代理之间的接口约定、权限的边界划定,这些都需要人来设计。配置写得不好,子代理之间互相扯皮、信息传递丢失、权限过大导致误操作,这些问题都会出现。我的建议是从两三个子代理的小工作流开始,跑顺了再逐步扩展,不要一上来就搞十几个子代理的大编排,那样排查问题会让你怀疑人生。
另外,配置文件的版本管理很重要。每次调整子代理的提示词或权限,都提交一次 Git,写清楚改了什么、为什么改。过两个月回头看,你会感谢自己留了这些记录。