DeepSeek Harness 进阶:subagent 招一帮「手下」,脏活累活拆出去干
生命不息,折腾不止。主对话上下文越堆越贵,今天教你用 subagent 把独立的脏活拆给一群「手下」,自己只收结果。
一、为什么要把活拆给「手下」
前面几期我们把一个 Agent 折腾得挺明白:装钩子拦截、监听事件流、上下文压缩、立 goal 自动续跑、长任务丢后台。但有个问题一直没正面解决——上下文是稀缺资源,什么都往主对话里塞,既费 token 又容易把模型带偏。
打个比方:你让一个 Agent「把仓库里所有 Markdown 文档里的 API 地址中文改英文,并给每个文件写一段英文摘要」。它得先把几十个文件读进上下文,再逐个改、逐个翻译。这活儿干到一半,主对话已经撑满 100K,前面的关键指令早被挤出去了。
subagent 就是来干这个的——把一件「自包含」的小任务丢给一个只干这一件事的「手下」,它干完只回你一个最终结果,中间过程完全不占主对话。几个关键点先记住:
- 子代理在自己的独立上下文里干活,只把最终答案交回来,不吐中间步骤;
- 一个父 Agent 可以同时挂好几个子代理,各干各的;
- 子代理想干就干、干完就散,也可以「常驻」下来,后面继续给它发消息。
二、四个包,搭出最小可用配置
subagent 不是单一功能,是「服务 + 后端 + 工具」三件套拼出来的。核心就四个包:
| 包 | 角色 |
|---|---|
@deepseek-ai/dsh-subagent |
服务层,管路由、结果回传、子代理生命周期(ctx.subagents) |
@deepseek-ai/dsh-subagent-spawn-in-process |
进程内后端:开一个全新干净的子代理 |
@deepseek-ai/dsh-subagent-fork-in-process |
进程内后端:开一个带着父对话已完成轮次的子代理 |
@deepseek-ai/dsh-tool-subagent |
面向模型的 subagent 工具,把上面后端暴露给 Agent 用 |
最小配置(挂进程内的 spawn 后端):
1 | - name: '@deepseek-ai/dsh-subagent' |
挂 fork 后端也一样,把两个中间包的名字和 provider 换成 fork 即可:
1 | - name: '@deepseek-ai/dsh-subagent' |
@deepseek-ai/dsh-tool-subagent 上能配的字段,常用的这几条(完整清单以官方 config-catalog 为准):
| 字段 | 默认 | 含义 |
|---|---|---|
provider |
必填 | 后端名:spawn / fork / acp 等 |
toolName |
subagent |
模型看到的工具名,多实例时必须各起一个 |
backgroundMode |
one-shot |
默认等结果;continuable 则在后台起持久子代理并返回 id |
enableRunInBackground |
true |
是否暴露 run_in_background 参数 |
agentOptions |
— | 给子代理单独指定 provider / model / reasoningEffort / maxTokens |
persona |
— | 给子代理定制人设 |
toolFilter |
— | 限制子代理能用哪些工具 |
maxDepth |
1 | 委派深度上限,0 表示禁止子代理再往下派活 |
注意:挂载后如果 backgroundMode 用 continuable,那么下一步还要再补一个「控制三件套」,第五节细说。
三、spawn 和 fork 怎么选?关键是「要不要继承对话」
两个进程内后端的运行机制一模一样,唯一区别就是子代理开起来时带不带父对话。
spawn:开一个全新、干净的子代理,它看不到你当前这段对话。适合那些完全独立的小任务——「去调研 X 的用法」「把这段代码重构一下」「分析这个数据集」。派活时一定要把背景交代清楚(第五节会讲 prompt 怎么写)。
fork:开子代理时,把父对话里已经结束的轮次作为「种子」喂给它,让它知道你俩前面聊到哪了。适合「顺着当前对话往下干的活儿」——比如「接着上面的结论写个评审」「基于刚才讨论的方案出个实现」。这样不用把背景复述一遍。
fork 的种子有个讲究:它只复制到最后一个 turn/end(已完成轮次)为止的连续前缀。你发 subagent 这个工具调用的那轮还没结束,这轮不会被带进去——不然子代理会收到一条「有工具调用、没工具结果」的对不齐的残缺对话。所以:
- 如果你在还没完成任何一轮时就 fork,种子是空的,子代理表现跟 spawn 一模一样;
- 种子只在 fork 那一刻快照一次,之后父对话再往下聊,子代理是收不到的。
一句话口径:子任务要「续当前对话」就用 fork,要「从头独立干」就用 spawn。
四、怎么派活、怎么收结果
挂好之后,Agent 会多出一个 subagent 工具(如果用 fork 后端,还能看到 subagent_fork 这个固定路由的别名,以及 list_subagent_models 这类发现工具)。派活就是调用它,传几个关键参数:
1 | { |
几个要点:
prompt必须自包含。spawn 后端下子代理看不到你的当前对话,别写「像刚才那样改」,要把它当成一个新同事,把要求一次说全;cwd指定子代理的工作目录,相对路径会以父代理当前目录为基准解析;不传就用父代理的目录。- 如果你启用了模型选择(
agentOptions相关),还能额外传provider/model/reasoning_effort给子代理换模型、换推理强度,以及用agent_preset指定独立预设。
收结果就更省心了:子代理干完,最终答案会通过「完成通知」(completion notice)自动塞回父代理。父代理拿到的只有它最后一条非空 assistant 消息(也就是最终结论),中间的读文件、改文件、思考过程全都不吐回来。这也解释了上面说的「不占主上下文」——过程在子代理自己的上下文里,回来只有一坨结论。
如果怕子代理跑飞,还可以给它加 outputSchema(结构化输出约束,让它按你规定的 JSON 格式回报)和 maxDepth(限制它不能再往下派子代理)。
五、可继续子代理 + 控制三件套
默认 one-shot 模式下,子代理干完一票就散场。如果你想让某个子代理「常驻」——比如一个负责盯日志的、一个负责管数据库的,后面还要反复支使它——就把 backgroundMode 设成 continuable,再补挂一个控制包:
1 | - name: '@deepseek-ai/dsh-subagent' |
这个控制包一次注册三个全局工具:
send_message:给指定子代理发消息(用agent_id定位)。它只管「投递」,返回的是消息被接受的messageId,不是回复;子代理的回话会落到它自己的会话里。投出去之后你可以继续在父对话里追问。interrupt_agent:打断子代理。注意区别——外部后端(Codex / Claude Code 等)一被打断就永久停了;本地子代理只停当前这一轮,排队的消息先挂着,后面还能继续支使。list_agents:列出当前 Agent 名下的可继续子代理,带状态(running在跑 /inactive空闲)、parent 和 depth,帮你记住自己派了多少「手下」、谁还活着。
这里容易和上期《后台任务三件套》搞混,一句话区分:
job_*管的是「命令」级后台任务(bash / PTY 那类),job_list看的是进程;send_message/interrupt_agent/list_agents管的是「Agent」级子代理,看的是另一套独立的会话。两者不是一个东西,别拿job_kill去杀子代理。
六、子代理也能换模型,接中转站 + 挂外部子代理
subagent 最有意思的地方是能搞「多模型」分工——有些活儿适合便宜模型,有些适合强模型。通过 agentOptions(或启用模型选择后按请求传 provider/model/reasoning_effort),你可以:
- 让主力 Agent 用 DeepSeek 干编排,把「大量机械的文本处理」派给便宜模型当子代理;
- 碰到要啃硬骨头的(复杂代码、长文精读),把那个子代理换到更强的模型上。
DeepSeek Harness 作为国产框架,卖点本来就在「多模型对接 + 便宜」,你去 list_subagent_models 里看看有哪些 provider、哪些模型能选。像我这种不想在各个官方后台绑卡的,就统一走中转站 ai.aklibk.com(OpenAI 兼容接口),一个 key 把 DeepSeek、Claude、GPT 等各种模型都接进来,人民币按量付费、便宜还不用分开充值——子代理换模型这一下就特别顺手,主从两条腿各用各的模型,互不干扰。
再进阶一步,rc.8 起官方把 Codex 和 Claude Code 也做成了外部子代理后端(@deepseek-ai/dsh-subagent-codex、@deepseek-ai/dsh-subagent-claude-code,可按需装成 Profile Bundle)。它们是「一次性」的外部进程:在父会话的工作目录里拉起一个全新的 Codex / Claude Code 进程、独立上下文,只把最终答案(或一条安全失败诊断)交回父 Agent——不继承父对话,也不能被 send_message 后续追加。适合把「写这段代码」「跑这个调研」这种大活整个外包给外部专家 Agent,主 Agent 只当甩手掌柜。
七、踩坑清单
写这篇时我翻官方 README 和子系统文档,几个容易翻车的地儿提前给你标出来:
- 只挂
dsh-subagent服务是没用的——服务 + 后端 + 委托工具三者缺一,Agent 里根本不会出现subagent工具; - prompt 别偷懒写「像刚才那样」——spawn 看不到当前对话,一定要自包含;
- fork 的种子是快照——fork 之后父对话的新内容不会自动同步给子代理;
send_message返回的是 messageId 不是回复——别等它「返回答案」,子代理的回话在它自己的会话里;- 别用
job_*管子代理——那是命令级后台任务,跟 Agent 级子代理两套体系; - 编码规范这块——命令、字段名、接口名按官方原文保留(比如
backgroundMode、send_message、turn/end),具体字段的最终口径都以官方config-catalog/tool-catalog为准,这个框架还是开发者预览期,字段偶尔会动。
生命不息,折腾不止。下一篇是 DeepSeek Harness 进阶系列的收尾实战:用「一个主编 Agent + 三个研究员 subagent」并行查资料、写报告、出代码,把 hook、事件流、上下文压缩、goal、后台任务、subagent 这六件套串成一个能真正落地的多 Agent 项目,敬请期待。