Subagent
“Specialists for Every Job”
Subagents give each subtask a clean context while preserving the main thread.
OpenCode 的子代理系统提供 10 个命名代理,每个都有明确的专长文档。Oracle 负责架构分析,explore 搜索代码库,librarian 查阅文档,依此类推。子代理拥有隔离的上下文和干净的消息历史,可通过 @mention 或编程方式调度。
架构流程图
问题:单代理系统中的上下文污染
在单代理系统中,每个工具的执行结果、错误消息和中间思考都会污染同一个对话空间。Agent 必须持续从不断增长的噪音中重新筛选相关信息。子代理通过创建隔离的上下文来解决这一问题。每个子代理都是一个拥有独立消息历史的 agent 循环。父级调度一个任务后,子代理独立完成工作,只返回最终结果。这防止了父级的上下文被子代理所需的探索过程所稀释。
通过隔离实现组合
每个子代理都运行自己的 agentLoop(),即 s01 中介绍的那个函数。这意味着子代理继承了所有能力(工具、钩子、权限),无需编写任何特殊代码。唯一的区别在于消息历史从空白开始,系统提示定制了具体行为。父级可以并行调度多个子代理(详见 s09),并在它们完成后收集结果。这将 agent 转变为一个编排器:分解复杂任务,将各个部分委托给专家代理,最后综合所有结果。
真实实现:Task Tool 和 BackgroundJob
OpenCode 实际的子代理调度使用 task tool (packages/opencode/src/tool/task.ts) 结合 BackgroundJob (packages/core/src/background-job.ts)。与直接函数调用不同,task tool 创建一个带有独立权限范围的子会话。该范围继承父级的 deny 规则,并叠加子代理自身的规则集。子代理支持两种运行模式。在前台模式下,父级通过 background.wait() 等待结果。在后台模式下,调用立即返回,子代理完成后注入结果。task_id 参数支持跨多次调用恢复同一子代理会话,这与简化版本每次调用都创建新上下文的方式不同。
设计决策
子代理以干净上下文被调度,但父级可传递相关的上下文片段。提示构建函数仅提取对话中与子代理专长相关的部分。
子代理的结果以结构化数据形式返回,而非以纯文本字符串附加到父级的消息历史中。这使得父级能够解析、验证并有选择性地整合子代理的输出。
对比 Claude Code
两者都支持子代理委派。Claude Code 通过其 agent 模式实现,而 OpenCode 则使用其子代理系统。OpenCode 的方法粒度更细:每个子代理拥有自己的循环、工具集和权限上下文。OpenCode 中的 Explorer、Librarian 和 Oracle 等专用代理类型,对应了真实的工程角色。每种代理都配有经过调优的系统提示和工具访问权限。父代理可以并行向多个子代理分派任务,并综合它们的结果。
深入设计决策
命名代理优于匿名 Worker
每个子代理有名字和文档化的专长。用户和编排器可以通过名字引用代理,建立对团队的认知模型。
每个子代理有独立的上下文
子代理从干净的消息历史开始,只包含其任务特定的系统提示和指令。它们不继承父会话,防止上下文污染。