dsh-agent-foreman
Verifieddsh-agent-foreman · v0.3.0 · MIT · Web UI
父智能体派单,承包商子智能体干活:每个承包商可以单独指定 provider / 模型 / 思考等级,产物回流成一块共享看板,父智能体逐个验收、不合格打回返工。
Install
dsh plugin add dsh-agent-foreman Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Readme
包工头 · dsh-agent-foreman
父智能体派单,子智能体干活,父智能体验收。 不预置角色、不写剧本:谁干活、用什么模型,由你把关。
它解决的问题
DSH 自带的「智能体团队」只能给每个成员一个名字和一段提示词——成员用哪个模型选不了。 而实际干活时,同一批任务里既有需要贵模型把关的部分,也有随手就能做的小改,全部继承 父会话的模型既费钱也慢。
包工头把这件事拆成两半:
- 承包商(contractor):你在设置页里定义的人,名字只是个标签,真正决定"谁干活"的是
provider + 模型 + 思考等级。想按专长分工就多雇几个实例、各起各的名字:便宜的配便宜模型 跑搬砖活,贵的专门挑错,还有一个直接跟父会话同模型。每个承包商还能带一段自己的提示词 (设置页里点名字后面的 👁 编辑),派单时原样塞进它的工单里。 - 工单(order):父智能体拆出来的活。每张工单指定任务与验收标准,可以点名某个承包商, 也可以省略点名、由引擎按实时负载自动分配。
父智能体只做两件事:拆单、验收。中间的过程全部由承包商在各自独立的子会话里完成, 产物回收成一块共享看板。
装上它
把仓库放到任意目录,然后在 DSH 里:
dsh plugin add E:/path/to/dsh-agent-foreman
或者手动安装(dev 用法):
- 把整个目录拷到
<profile>/node_modules/dsh-agent-foreman - 在
<profile>/package.json的dsh.profile.bundles里加上"dsh-agent-foreman" - 重启 DSH
装好后打开 设置 → 包工头,添加承包商。
用起来
1. 设置 → 包工头 → 添加承包商(名字、provider、模型、思考等级、并发各一格,👁 写提示词)
力工-前端 trae deepseek-v4.1-flash 默认 开 1
力工-渲染 workbuddy-global deepseek-v4.1-flash high 开 2
安全员 deepseek-official deepseek-flash 默认 关 1
分工靠名字:**包工头(父模型)拆单、力工干活、安全员挑错(可选)**。名册里只放你雇的人,
包工头就是驱动这一切的父智能体,不占名册。
**想要专长就多雇几个实例**(两个力工各用各的模型、各写各的提示词)。派单时 `contractor`
写名字就点某一个人,省略就**按实时负载挑人**:跳过正忙的、取最空闲的,负载打平才轮转。
负载跨会话可见,所以多个会话共用一个名册时不会都抢同一个人。
每个实例还带一个并发上限:默认 1,也就是同一个人串行干、不会自己跟自己抢文件;
调成 2 它就能同时接两张单。
**最简形态只有一个人:力工。** 所有工单都给它,靠它的并发上限和策略里的总并发控制节奏。
2. 在对话里直接说:"用包工头把这三件事分下去:……";或者用 `/foreman` 一句话派单(见下)
3. 父智能体调用 foreman_dispatch 派单 → 等全部落定 → 拿回看板 → 逐单打分
/foreman:一句话派单
插件在宿主里注册了一个运行时 skill(lib/skill.js,ctx.skills.register())。输入框里敲 /
能看到 foreman,选中或直接打 /foreman 加任务即可:
/foreman 把 GraphBlocked 抽成共享逻辑;给新逻辑补回归测试
分号或换行分段,一段一张工单。skill 正文规定四步:看名册 → 拆单(补齐背景/文件/验收)→
一次 foreman_dispatch → 逐单 foreman_review。这条路径不靠常驻系统提示词,只在用到时
注入一次,比每轮都挂着一段守则省。
父智能体手里的四个工具:
| 工具 | 作用 |
|---|---|
foreman_roster |
看名册:有哪些承包商、什么模型、提示词多少字、并发几张单、当前策略 |
foreman_dispatch |
派一轮工单,等全部落定,返回看板(含每单交付摘要) |
foreman_review |
逐单 pass / fail;fail 且还有额度就带着反馈重派给同一个人 |
foreman_status |
回看某一轮看板;省略 runId 取本会话最近一轮 |
成员在哪看
设置 → 包工头 → 实时工地:一块轮询 api/live 的看板,显示包工头在不在跑、
每个承包商的岗位与模型、本轮每张工单的状态 / 依赖 / 错误 / 产物路径。页面切到后台
就自动停表,不占资源。
关键一点:看板同时显示声明模型和真实模型。声明的是名册里配的那套,真实的是
从子会话的 header 里读回来的实际路由。两者不一致时该行会标出来——这正好回答了
"它到底跑的是谁的模型"。团队模式下则读宿主 listMembers() 回显的成员实际模型。
团队模式下承包商本身就是宿主的「智能体团队」成员,点会话头的 智能体团队 也能看到。
名册要点
- 承包商 = 名字 + 可选岗位(
role,只用于派单路由,不是固定角色体系)+ 提示词 + provider + 模型 + 思考等级 + 并发上限。provider 选「跟随当前会话」时,子智能体直接跑父会话当前的模型, 不需要知道它是什么。 - 想换模型就分两步:先选 provider,模型下拉只列这个 provider 自己的模型(由 provider 推出来, 没有模型的 provider 不会出现在下拉里)。
- 设置页把策略收进了「派单策略」折叠区,名册只留 名字 / 岗位 / provider / 模型 / 思考等级 / 并发 / 启用,点名字后面的 👁 编辑这个承包商的提示词(派单时塞进工单,和工单冲突时以工单为准)。
名册就是白名单
宿主自己有一份子智能体模型白名单(设置项 subagent-model-selection),它决定子智能体
被允许跑哪些 provider/模型。这份名单和名册是两处独立配置,一旦不一致就会出现"名册里写着
trae,实际却在跑别的模型"——而且没有任何报错。
本插件把这件事收成一处:名册里所有启用且配了 provider+model 的承包商,会自动汇总进宿主 白名单。你只需要维护名册。
- 插件启动时同步一次;设置页保存名册后再同步一次;也可
POST /api/whitelist/sync手动触发。 - 名册里没有任何显式路由(都用「跟随当前会话」)时不动宿主原配置——空名单会让宿主报错。
- 只影响新会话。宿主的白名单在会话首次建立时就被快照进该会话了,已经开着的会话改不了, 所以改完名册要开新会话才生效。
- 同步失败不会影响派单,只记日志;
/api/state的whitelist字段能看当前真实名单。

派单参数要点
orders[].task必须自足:子智能体看不到父会话的任何上下文,背景、要动的文件、 期望结果都要写进去。orders[].contractor可写承包商名字或 id,也可写role岗位名;名字/id 匹配优先于岗位匹配。 不存在的名字、id 或岗位会直接报错,不会悄悄换人。- 省略
contractor时按实时负载挑人,不是按名册顺序轮。规则:先只在还有空位的人里挑 (在跑张数 < 该人的maxParallel),再取在跑张数最少的,负载打平才用轮转游标打破平局。 负载是跨会话的——别的会话正在让力工干活时,这个会话派单会自动换人,不会两个会话一起 压给名册第一个人。轮转游标落盘在<数据目录>/rotate-cursors.json,重启 DSH 后继续, 不会回到第一个人。 所以:默认就该省略contractor;只有"这单必须由某个特定的人做"时才写名字。 把每张单都写成contractor: "力工"会绕过负载分配,其余承包商闲置、活全串行排队—— 只有名册里确实只有一个启用承包商时才这么写。 foreman_roster会显示每个人当前空闲/在跑几单,这是跨会话的真实状态,可据此决定要不要点名。orders[].dependsOn只能引用本轮内的工单。所有依赖都成功完成后,这张工单就会开工, 不需要等整张拓扑层跑完;无依赖关系的工单仍可并行,受总并发默认 4(上限 8)和单人并发上限约束。- 某张工单失败、超时或被取消后,它的下游工单标记为 skipped;被跳过的依赖继续阻断更下游, 不会在缺少上游产物时硬跑。看板会保留阻断原因,便于验收和排查。
waitSeconds是这一轮的总墙钟上限,到点会中止没跑完的工单并把现状返回——不会让父智能体 拿到一份假的"全部完成"。- 实时状态会显示依赖关系、错误和产物路径;它是状态视图,不另造一套实时 DAG 编辑界面。
验收与返工
foreman_review 打回时,反馈会原样进入下一次派单的提示词("上一轮被打回,原因:……")。
返工额度由策略里的 maxRework 控制(默认 1 次),额度用完后打回只会记录结论、不再重派,
这一轮转入 closed。
策略
设置页可调,也可以直接改数据文件里的 policy:
| 字段 | 默认 | 说明 |
|---|---|---|
concurrency |
4 | 同时在场的承包商数(1–8);单人的上限在名册里按人设 |
timeoutSeconds |
900 | 单张工单的墙钟上限 |
maxRework |
1 | 每张工单最多被返工几次 |
maxDepth |
1 | 子智能体还能不能再派子智能体 |
provider |
spawn |
走哪个 subagents provider(spawn / fork / cursor) |
数据放在哪
- Windows:
%LOCALAPPDATA%/dsh-agent-foreman/ - 其它平台:
<baseDir>/data/dsh-agent-foreman/
roster.json 保存名册(含岗位)与策略;运行快照保存在 runs/。重启后可以查询、验收已经落定的
看板快照,但中断的工作不会自动重新派发或续跑。这里的显式恢复是恢复可检查、可验收的状态,
不是偷偷挂一个后台执行引擎。完整交付正文在可用时另存于 artifacts/,看板通过产物路径关联。
名册坏了不会阻断启动:读不出来的名册会被当成空名册并记一条日志。
安全边界
- 所有 HTTP 接口只接受同源、且落在回环地址上的请求;跨站请求一律 403(有 CSRF 面)。
- 名册只保存
provider/model/ 思考等级等配置,不保存任何密钥。 - 承包商在独立子会话里跑,沙箱与权限沿用 DSH 当前生效的那一套。
开发
node --test test/*.test.mjs # 37 条用例:工具、引擎、路由、CSRF、台账、并发上限
测试用假宿主跑真实的插件半边(test/harness.test.mjs),不需要启动 DSH。
团队模式(实验)
默认关闭。在「设置 → 包工头 → 派单策略」勾上团队模式后,承包商不再走
subagents.startActivation,而是:
agentTeams.spawnTeammate把它加成宿主的智能体团队成员(自己的会话);sessionController.selectModel把成员会话的模型换成名册里配的那套;agentTeams.sendMessage把工单当团队消息发过去;- 等它那一轮跑完,从它的会话事件里把交付正文捞回来进看板。
这样承包商既能在「智能体团队」区看见,也能在它自己的会话里换模型。
为什么必须等成员 active 再投递:宿主 agent-teams 的 sendMessage 在成员还没
进入 active 阶段时会直接抛 active teammate "worker" not found。成员 spawn 之后
要先经历 provisioning(初始 prompt 被确认),才转 active。本插件在投递前会轮询
listMembers() 等这个状态;等不到(宿主没有该接口 / 成员 failed)就退回普通模式,
不把失败抛给用户。
这条链路依赖宿主的团队服务,因此任何一步不可用都会自动退回普通模式,
看板每单的 teamMode 字段会写明是「团队模式」还是「失败退回」。已知取舍:
- 成员名只能是 ASCII(宿主只收
lower-kebab-case),所以中文承包商统一落成worker、worker-2…,中文名与模型写在成员的描述里; - 成员第一轮用的是父会话的模型(
spawnTeammate本身不接受 model 字段,建成员时给不了), 切模型从第二轮才生效——所以建成员时只发一句「待命」,工单随后再发; - 成员的实际模型由宿主
listMembers()的model字段回显,会写进看板的teamMode备注;名册配的模型与它不一致时会并列写出; - 切模型受宿主给子会话的模型白名单限制,白名单里没有的 provider/模型会被拒, 这种单在看板上会写明「模型切换:failed · 原因」;
- 每轮派单结束时成员会留在团队里(可继续用,也可手动删除);
- 并发上限仍按名册的「并发」和策略的「同时在场承包商」卡。
与其它方案的区别
| 智能体团队(内置) | dsh-agent-teams | 包工头 | |
|---|---|---|---|
| 成员可选模型 | ✗ | ✗ | ✓ agentOptions 创建时锁定 |
| 跨轮记忆 | ✗ | ✓ previousResult | ✓ 历史拼 prompt |
| 原生团队标签页 | ✓ | ✓ | ✓ 共享任务 |
| 自报回收 | ✗ | ✓ update_task | ✓ foreman_report |
| 真实路由回显 | ✗ | ✗ | ✓ 声明 vs 实际模型对照 |
| 白名单同步 | ✗ | ✗ | ✓ 名册 = 宿主白名单唯一来源 |
| 工地看板 | ✗ | ✓ 活动面板 | ✓ 会话头「工地」+ 真实模型 |
| 依赖调度 + 失败阻断 | ✗ | ✓ DAG | ✓ 就绪调度 + skipped 递归阻断 |
| 返工闭环 | ✗ | 手动 | ✓ 带反馈自动重派 |
| 设置页 | ✓ | ✗ | ✓ 名册 + 模型目录 |
核心差异:包工头的模型可选 + 真实路由回显是其它方案没有的。dsh-agent-teams 的强项是完整任务 DAG(claim/attempt/quality gates),适合复杂多阶段项目;包工头侧重按模型拆单、逐单验收,工具更少(5 vs 14),上手更快。
版本说明
0.3.0(2026-10-29)
- 按实时负载派单:省略
contractor时不再按名册顺序轮,而是先筛出还有空位的人 (在跑张数 < 该人的maxParallel),再取在跑张数最少的,负载打平才用轮转游标打破平局。 负载跨会话可见——别的会话正占着力工时,这个会话派单会自动换人,多个会话共用一个 名册不会都抢同一个人。 - 轮转游标持久化:游标从
assignContractors的局部变量提到引擎实例并落盘到<数据目录>/rotate-cursors.json。修复了"每次派单都从名册第一个人开始"的缺陷, 重启 DSH 后也不会跳回第一个。 foreman_roster显示忙闲:每人一行标出「空闲 / 在跑 N 单」,是跨会话的真实状态。- 工具与 skill 文案纠正:明确"默认省略
contractor,只有必须指定某人才写名字"。 之前foreman_dispatch的描述在引导模型显式写contractor: "力工",那会完全绕过负载分配。 - 返工复用会话:同一承包商的返工不再新开子智能体,改为在既有会话里续写(宿主有
steer/submitPrompt时直接续写,否则重激活同一childId),成员不再每打回一次就换人。 - 返工提示词带轮次:被打回的工单会写明「本单第 N 轮提交」,避免复用会话时从头再来。
0.2.0(2026-10-28)
- 自建成员系统:
subagents.startActivation+agentOptions锁定模型,不再依赖spawnTeammate(后者无法指定模型) - 跨轮记忆:历史产出拼进 prompt(同 agent-teams 的 previousResult)
foreman_report自报工具:产物回收不依赖读成员会话(同 agent-teams 的 update_task)- 真实路由回显:
actualRoute/routeMismatch,声明模型 vs 实际模型对照 - 白名单同步:名册自动同步宿主子智能体模型白名单
- 工地看板:会话头「🏗 工地」按钮,实时显示承包商模型与工单状态
- 依赖就绪调度:不整层等待,失败递归阻断为
skipped - 快照恢复:
runs/原子快照,重启后可查可验收,绝不自动重跑 - 名册岗位:
role字段支持按岗位轮转派单
0.1.0
- 初始版本:名册管理、派单引擎、看板、设置页
License
MIT