← 返回全部章节
04

Subagent 调度与 Model Gateway

什么时候并行才有价值,以及如何隔离上下文、权限、成本和 provider 差异。

4 分钟 04-subagents-gateway.md

什么时候值得用 Subagent#

适合:

  • 可独立并行探索多个模块;
  • 需要隔离大量中间上下文;
  • 不同角色有不同工具权限;
  • 一个任务可以定义清晰输入输出契约;
  • 失败可局部重试,不污染主轨迹。

不适合:

  • 子任务高度共享状态、频繁同步;
  • 任务很小,通信成本大于收益;
  • 多个 Agent 会编辑相同文件;
  • 没有独立 verifier,只是“多叫几个人想想”;
  • 只是为了追逐架构潮流。

Orchestrator–Worker 模式#

flowchart TB O[Main Agent / Orchestrator] O --> P[Plan Agent<br/>只读、无 Shell] O --> E1[Explore Agent A<br/>模块一] O --> E2[Explore Agent B<br/>模块二] O --> C[Coder Agent<br/>隔离 worktree] P --> R[结构化结果] E1 --> R E2 --> R C --> R R --> O O --> V[Verifier]

子任务契约:

objective: 找出 token refresh 竞态的最小根因
scope:
  read:
    - src/auth/**
    - tests/auth/**
  write: []
deliverable:
  - 根因
  - 证据路径和行号
  - 最小复现建议
budget:
  max_steps: 20
  timeout_minutes: 8

主 Agent 不应把完整子轨迹全部塞回上下文,只接收结果摘要、证据引用、状态和 usage;需要时可按句柄展开。

多 Agent 如何避免写冲突?#

首选把写集按目录/任务分区;需要同时写时使用独立 worktree/branch,让主 Agent 审查并合并 patch。调度器基于声明和实际 file-set 做冲突检测。共享单工作区且并发编辑是最后选择,必须有版本检查和三方合并。Git index 等全局状态不能无保护共享。

如何防止 Subagent 失控或递归爆炸?#

  • 限制最大深度、fan-out、总并发和总预算;
  • 只有特定 Agent 有 spawn 权限;
  • 子 Agent 继承不超过父 Agent 的权限;
  • 每个任务有 deadline、取消传播和心跳;
  • 主任务结束前回收后台任务;
  • 汇总 usage 与成本到父任务;
  • 结构化返回,禁止无边界聊天;
  • 全局 scheduler 做公平性和背压。

模型路由#

路由维度:

  • 任务难度与风险;
  • 上下文长度;
  • 工具调用可靠性;
  • 延迟/成本;
  • 多模态需求;
  • 数据合规;
  • provider 健康状态。

不要只用一个分类器“猜难度”。可以先用低成本模型执行,出现特定信号再升级:

  • 多次无进展;
  • verifier 失败;
  • 跨模块复杂任务;
  • 高风险决策;
  • 小模型工具 schema 错误率高。

同一 session 中途切模型有什么坑?#

  • 不同 provider 消息角色和 tool-call 格式不同;
  • tool call ID、thinking block、签名可能不兼容;
  • tokenizer 与 context limit 不同;
  • 对 system prompt 和并行工具支持不同;
  • 历史消息可能需规范化/修复;
  • 行为变化会破坏恢复可重复性。

应保存规范化内部 IR,再由 provider adapter 编码;trace 中记录实际模型与 adapter 版本。


Model Gateway 与 Prompt / Tool 兼容层#

内部统一表示#

建议统一:

Message:
  role
  content_blocks[text | image | tool_call | tool_result | reasoning_ref]
  provenance
  timestamp

ToolCall:
  id
  name
  arguments

ModelResponse:
  blocks
  finish_reason
  usage
  latency
  provider_request_id

adapter 负责:

  • 请求编码和流式事件归一化;
  • tool schema 差异;
  • usage 与 finish reason 统一;
  • provider 错误分类;
  • 最大输入/输出预算;
  • 重试与幂等边界;
  • history repair;
  • 能力声明。

流式解析#

模型可能分片输出:

  • 文本 token;
  • reasoning;
  • tool name;
  • tool arguments 的增量 JSON;
  • usage;
  • finish reason。

Runtime 应在流结束后对 arguments 做最终 schema 验证。不要边收到半个 JSON 边执行。若连接中断,不能假设工具调用完整;记录 interrupted response 并依据 provider 的幂等能力决定是否重试。

缓存#

可缓存:

  • 稳定 system/tool 前缀的 provider prompt cache;
  • repo index 与文件摘要;
  • 不变的工具资源;
  • 相同内容 hash 的 embedding;
  • eval replay 的确定性工具结果。

缓存键必须包含:

model + provider + prompt/tool version + content hash
+ permission/scope + repo revision + relevant config

缓存最危险的不是 miss,而是错误 hit:把旧分支代码、旧权限或旧工具描述带入当前任务。

Temperature 设为 0,Agent 是否可复现?#

不能保证。provider 后端、模型版本、采样实现、并行工具时序、搜索结果、文件状态都会变化。评测要固定可控变量、记录版本并多次运行;调试可以用 trace replay 隔离模型和环境。目标通常是统计稳定性,而非逐 token 一致。

Subagent 的收益来自隔离,不来自数量#

我判断是否拆 Subagent 时,会先估算三项成本:

收益 = 被隔离的无关上下文 + 可并行的等待时间 + 专用权限带来的安全性
成本 = 任务描述损耗 + 结果汇总损耗 + 额外模型调用 + 并发冲突风险

例如调查一次跨 authbillinggateway 三个模块的超时问题,三个只读 explore Agent 并行通常合理:它们各自读取大量日志和调用链,最后只返回证据。若任务是修改一个 80 行函数,拆成“分析 Agent、编码 Agent、审查 Agent”往往只是把一次本可连贯完成的推理切碎。

一个可执行的子任务契约#

自然语言 prompt 不应是唯一契约。调度器还应持有结构化元数据:

task_id: investigate-auth-refresh
goal_revision: 7
objective: 解释刷新请求为什么在高并发下重复发送
inputs:
  workspace_revision: 91e83d
  evidence:
    - artifact://trace/refresh-timeout
scope:
  read: [src/auth/**, tests/auth/**]
  write: []
capabilities: [read_file, search_text, run_readonly_test]
deliverable_schema:
  root_cause: string
  evidence_refs: list
  confidence: low | medium | high
  unresolved: list
budget:
  max_turns: 16
  max_tokens: 60000
  deadline_seconds: 480

返回结果必须绑定 workspace_revisiongoal_revision。如果主 Agent 在子任务运行期间已经修改相关文件,结果不能直接当作当前事实,只能作为历史线索重新验证。

并发调度要看实际 effect,而不是 Agent 名字#

两个 explore Agent 通常只读,但它们仍可能同时启动构建、占用同一端口或污染共享缓存;两个 coder Agent 也可能因为独立 worktree 而完全不冲突。因此调度器应组合静态声明与运行时观测:

declared_effects  = task contract + tool capabilities
observed_effects  = 实际打开的路径、进程、端口、Git 状态
conflict          = overlap(declared, observed, active_tasks)

发现未声明写入时,最安全的处理不是事后合并,而是立刻暂停该子任务并记录 policy violation。并发上限还要同时受 provider 配额、CPU、内存和用户级公平调度约束,不能只设一个 max_agents=8

Gateway 的核心不是换 URL,而是保存语义#

模型 provider 的协议差异常被低估。一个内部 tool_result 在不同接口里可能要求:

  • 紧邻对应 tool call;
  • 使用特定 role;
  • 保留或删除中断的 reasoning block;
  • tool call ID 满足特定格式;
  • 并行调用按原顺序回填;
  • 图片、缓存标记和系统指令放在不同位置。

因此 adapter 不只是字段重命名。它应执行一套可版本化转换:

Internal IR
→ capability negotiation
→ history validation / repair
→ provider-specific encoding
→ streamed block assembly
→ finish reason normalization
→ usage and error normalization

我会把“修复历史”限制为保持语义的操作,例如为已明确中断的调用补一个 interrupted result。若无法确认 call/result 对应关系,Gateway 应拒绝请求,而不是猜测一个看起来能通过 provider 校验的历史。

切换模型时应该重建哪些东西#

同一 session 从模型 A 切到模型 B,至少重新计算:

项目 原因
token 预算 tokenizer 与窗口不同
tool schema 集合 provider 能力与 schema 限制不同
system/tool 稳定前缀 prompt cache 边界不同
并行调用策略 有的模型更容易生成依赖冲突
历史消息编码 role、block 和 call/result 约束不同
completion policy 阈值 模型自报完成的可靠性可能不同

但是任务目标、权限、workspace revision 和历史副作用不能随模型变化。模型是可替换决策器,Runtime 状态不是它的私有记忆。

Kimi Code 的公开文档显示,Subagent 使用独立上下文,只将最终结果带回主 Agent;session 中每个 Agent 也有独立事件流用于恢复和 replay。这类设计的价值正是把上下文污染和运行状态分开,而不是简单增加并行度。参见 Agents and Sub-AgentsSessions and context