dsh 里的委派是四组彼此独立的工具:subagent 与 subagent_fork 负责派生,subagent-control 负责发消息和打断,jobs 管理所有类型的后台工作,workflow 用 agent()、pipeline()、parallel() 做编排。ralph 是另一种形状——面向一个不可变目标、由全新子会话组成的前台循环。
多数编程 agent 的「委派」不过是把自己再 prompt 一遍。DeepSeek Harness 提供了四种实质不同的委派机制, 而选错的代价,是「能收敛的编排」和「在原地烧 token 的编排」之间的差别。
派生:subagent 与 subagent_fork
@deepseek-ai/dsh-tool-subagent 提供 subagent;挂载了 fork 后端时,还会注册 subagent_fork。
区别在于子 agent 从什么起步。普通子 agent 从你交给它的任务开始;fork 从当前会话状态开始——当子 agent 需要你已经建立起来的上下文时很有用,不需要时就是浪费。
一般规则:上下文本身就是任务时用 fork(「顺着这条路继续,但换个修法试试」);任务可分离时开全新的 (「去读这三十个文件,把发现报回来」)。
控制在跑的东西
@deepseek-ai/dsh-tool-subagent-control 给父级三个动词:
send_message——给正在后台跑的子 agent 追加一条消息interrupt_agent——打断它当前这一轮list_agents——看看到底哪些还活着
而 @deepseek-ai/dsh-tool-subagent-report 给子级一个动词:report,把结论传回父级。它只在可继续
的进程内子会话中可见——一个无法继续的子会话,也没有可以汇报进去的对象。
这是一个真正的消息传递面,而不是发完不管。父级可以起五个子 agent,用 list_agents 盯着,用
send_message 纠偏其中一个,杀掉跑歪的另一个——不必等五个全部结束。
Job
@deepseek-ai/dsh-tool-jobs——job_list、job_output、job_kill——管理所有类型的后台工作:
用 run_in_background 起的 bash、常驻终端、子 agent,一视同仁。
这种统一是有意义的。否则你得为「我丢到后台的构建」和「我派生的 agent」维护两套心智模型。在这里, 它们都是 job。
Workflow
@deepseek-ai/dsh-tool-workflow 用三个原语编排多 agent 工作:
agent()——跑一个 agentpipeline()——让每个条目流过各个阶段parallel()——并发跑一批任务
结构上最关键的选择是 pipeline() 与 parallel() 之争。pipeline 让每个条目独立走完全部阶段——条目 A
可以在第三阶段,而条目 B 还在第一阶段。parallel() 是屏障:全部完成才能往下走。
只有当后一阶段确实需要前一阶段的全部结果同时在手时才用屏障——比如跨全集去重,或者决定要不要继续。 否则 pipeline 的耗时是「最慢的那条单链」,而不是「每阶段最慢者之和」。
Ralph 循环
@deepseek-ai/dsh-tool-ralph 不是 workflow 的变体,而是另一种形状。harness 自己的术语表定义得很精确:
- Ralph loop:面向一个不可变目标的前台 fresh-agent workflow 运行。
- Ralph round:该循环中的一个全新子会话,不带父会话的对话种子。
- Ralph handoff:从上一个继续中的 round 传给下一个的、规范化的、有界的、结构化报告。
三条定义连起来读,设计意图就很清楚了。目标不会漂移,因为它不可变。上下文不会膨胀,因为每轮从头开始。 而连续性不靠搬运对话来维持,靠的是搬运一份有界的结构化报告。
这是对「长 agent 会话」失效模式的正面回答:一个进行到第三十轮的 agent,是在一堆考古材料里做推理。 Ralph 每轮把考古材料丢掉,只留 handoff。
适用:目标确实固定、且轮与轮之间进展可衡量——把测试套件磨绿、把一个清单干到空。
不适用:目标本该随着你的认知而改变的场合。不可变是它的卖点,而对探索来说这是错的属性。
Goal,不是 todo
还有两个概念容易混:
todo_write(@deepseek-ai/dsh-tool-todo)是会话内草稿状态——pending、in_progress、completed,
每次调用整体替换。
create_goal / get_goal / update_goal(@deepseek-ai/dsh-tool-goal)是持久的。术语表把 goal 定义为
挂在某个已有会话上的一个持久完成目标,带阶段和轮次上限,支持暂停、恢复、完成、阻塞状态。
goal round 是为当前 goal 准入的一次续跑循环,goal activation 则是准入下一轮的进程内许可。
值得注意的是那个轮次上限。一个没有边界的持久目标,就是 agent 循环变成无上限账单的方式。
Scope:为什么子 agent 的工具不一样
harness 的 scope 模型解释了那个让人意外的现象——子 agent 的工具集和父级不同。
scope 是工具、提示词这类贡献的按-agent 注册单位,要么全局,要么属于某一个 agent。shadowing 是最具体者胜的名字解析:scope 内的同名项只在该 scope 内替换全局项。restriction 则通过取交集, 为某个 scope 过滤全局工具集。
所以子 agent 拿到的不是你工具的副本,而是它自己 scope 下组装出的一套,可以遮盖、限制或扩展全局注册的 东西。这就是「只读不写的 reviewer」这种预设背后的机制——它是对某个 scope 的一条限制,而不是另写一个程序。
常见问题
subagent 和 subagent_fork 有什么区别?
两者都委派给独立 agent,fork 后端会额外注册为 subagent_fork。fork 让子 agent 从当前会话状态起步,而不是从零开始。
Ralph 循环是什么?
面向一个不可变目标的前台 fresh-agent workflow 运行。每一个 Ralph round 都是全新子会话,不带父会话的对话种子;轮与轮之间靠一份规范化、有界、结构化的报告(handoff)传递上下文。
为什么每轮开新会话,而不是继续同一个?
长会话会堆积大量与当前步骤无关的上下文,而无关上下文会稀释注意力。每轮重启、只带一份有界的结构化 handoff,能让每轮的上下文规模与眼前的活相称。
子 agent 和我共用工具吗?
注册是按 scope 的。scope 是工具、提示词这类贡献的按-agent 注册单位,而 scope 内的同名项只在该 scope 内遮盖全局项。