Subagent Providers
一个接口,多个可互换的子代理后端。
委托契约不变,后端随便换。
60 行提供者抽象
问题
s06 的子代理只能"进程内新开一个循环"。但现在你想:
- 让子代理跑在另一个产品里(Claude Code?Codex?一个 ACP 服务器?);
- 让子代理继承父历史而不是从零开始;
- 一个进程里同时存在多个不同后端的子代理。
把委托逻辑写死在 subagent 工具里?每换一个后端就要改工具。能力应该有一个稳定的接口,后端是可插拔的提供者。
解决方案
提供者抽象:
// 委托契约:start(request) -> run,run 返回 { runId, output }
const providers = new Map();
function registerProvider(name, provider) { providers.set(name, provider); }
// 进程内提供者:s06 的实现包一层
registerProvider("in-process", {
async start({ prompt, system }) {
return { runId: `run-${++seq}`, output: await agentLoop(prompt, { system }) };
},
});
// 外部提供者:通过任意传输协议委托(示意)
registerProvider("acp", {
async start({ prompt }) {
const output = await acpCall("agent", { prompt }); // ACP 协议调用
return { runId: `run-${++seq}`, output };
},
});
// 工具只认接口,不认具体后端
define("subagent", {
type: "object",
properties: { prompt: { type: "string" }, provider: { type: "string" } },
required: ["prompt"],
}, async ({ prompt, provider = "in-process" }) => {
const p = providers.get(provider);
if (!p) return `未知提供者:${provider}`;
const run = await p.start({ prompt });
return `[${run.runId}] ${run.output}`;
});
工作原理
第 1 步:契约三问——每个提供者都要回答:怎么启动(start)、怎么知道完成(run 解析)、怎么传结果(output)。
第 2 步:模型侧工具永不改变。换后端 = 换配置/换提供者注册,工具 schema 不变——这正是"能力缝"(Service Definition / Provider / Consumer 三角)的价值。
第 3 步:多提供者共存。同一次会话里,一个子代理跑在进程内、另一个委托给外部 Agent——父循环只看到统一的 [run-N] output 格式。
试一下
运行 s17:
- 注册两个提供者(in-process + 模拟的 external),用
provider参数切换 - 观察:工具描述不变,模型是否感知得到后端差异?
- 换一个"慢速提供者",看委托的阻塞特性是否依旧(s13 后台任务可以解决)
观察重点:接口稳定但后端可换——这带来的可测试性红利:测试时用 fake 提供者,生产用真实 Agent。
以下内容基于 packages/subagent 的核查。DSH 的子代理提供者族(s06 提过)就是本课的全部内容:
| 提供者 | 后端 | 场景 |
|---|---|---|
subagent-spawn-in-process |
全新进程内子代 | 干净上下文(s06 的标准形态) |
subagent-fork-in-process |
从父历史 fork | 需要父上下文延续的任务 |
subagent-acp |
ACP 服务器 | 自动化协议(s20 会再遇到 ACP) |
subagent-codex |
真实 Codex app-server | 委托给 Codex |
subagent-claude-code |
真实 Claude Code(官方 SDK) | 委托给 Claude Code |
subagent-dsh-sdk |
进程外 Harness 子进程 | 跨进程隔离 |
配套:subagent-in-process-driver 提供共享的进程内运行驱动;tool-subagent-control 提供对延续子代理的消息与列表控制。
DSH 的关键差异:提供者注册在 ctx.subagents 上,多个可共存(与你的 Map 一致);委托返回结构化运行结果(runId、状态、输出),支持可延续后台子代理(父代理稍后 send_message 继续同一子代理对话)。
一句话:本课没有任何新机制——只有"接口与实现分离"。它是整个 DSH 哲学(能力缝)在子代理上的缩影:模型侧契约稳定,后端世界开放。