dsh-task-steward(任务管家)
本仓库 100% 由 AI 生成 —— 需求、代码、测试、文档都是人与 AI 对话里由 AI 写出来的,人只负责验收和拍板。 实验性 · 自用性质 · 非商业:它紧贴 DeepSeek Harness(DSH)的宿主接缝(
apply/inject/ client bundle / 配置解析),只在写它的那个 DSH 版本上验证过,宿主升级后可能直接失效。 安装前必读:DSH 的 profile 加载是全有或全无——一个插件import失败会连坐整个 profile,甚至把配置写残。装之前先备份 profile 里的package.json与cordis.patch.yml。 没有发布到 npm:把本目录放进 profile 的node_modules(或"<包名>": "link:<本目录绝对路径>"),再在package.json的dsh.profile.bundles里登记,重启 DSH。 卸载/回滚:从dsh.profile.bundles摘掉 →pnpm remove <包名>或删掉 junction → 重启;出问题就用备份的那两个文件还原。
给 DSH 的会话装一个"管家":任务跑起来之后,它盯着一件事——这个任务还在正常推进吗?
- 卡死(真静默)、空转(同一个调用反复)、思考链复读、连续多步没有进展证据 → 提醒。
- 禁区命令(删根、格式化磁盘、
--force强推、读凭据、往外传文件)→ 拦截。 - 读网页/MCP/文件之后马上调特权工具 → 判定为"可能被注入" → 拦住问人。
- 预算烧穿、要提权/换沙箱 → 停住问人,不自动放。
- 消极怠工:连续多步只有空话没有动作、todo 一直不开 → 提醒。
M0 只记账不干预。 默认 policy: report:所有信号都算出来、都记进账本,但不会拦一次工具、不会停一次任务。跑几天看 scripts/bill.mjs 出的账单,误报低了再翻 auto。
为什么是"确定性 + 零模型调用"
管家不该比被看管的任务还贵。这里所有判断都是规则/状态机:工具名 + 参数指纹、事件时间戳、在飞计数、进展证据。默认配置下一次模型调用都不发。
内置的 dsh-experimental-auto-review(已在 profile 里、默认关)负责"单次调用的风险复核",我们负责"跨步的进程监督",两者是两条线,不重复。
四档干预(policy: auto 才生效)
| 档 | 动作 | 怎么落地 |
|---|---|---|
| tier 0 | record |
只写账本 |
| tier 1 | nudge |
往会话插一条 user/message:管家提醒(带证据 + 你可以怎么回),不唤醒模型 |
| tier 2 | block |
tools/pre-execute 返回 {kind:'deny'},把理由交给宿主原生审批链 |
| tier 3 | stop |
agent.cancel({kind:'hook', reason}),附停机交接 |
同一信号连续出现 tierUpAfter 次(默认 2)升一档;任何行为进展都会重新计数;resetAfterMs 后清零。
allowStop: false(默认)时 tier 3 降级为 nudge——M0/M1 阶段绝不自行杀任务。
信号
| 信号 | 判据 | 默认阈值 |
|---|---|---|
repeat.exact |
同一工具 + 规范化参数在窗口内重复 | 3 次 / 回看 12 次调用 |
repeat.no-effect |
同一调用且结果摘要不变 | 2 次 |
loop.thinking |
思考链字面/标点变体/长块/整行复读 | 体量 > 80k 字符才扫 |
stall.silent |
运行中 + 无事件 + 无在飞操作 | 120s |
stall.thinking |
只有思考文本、无工具调用、距上次进展过久 | 180s |
progress.none |
连续多步没有进展证据 | 8 步 |
forbidden.command |
禁区命令规则命中 | 11 条规则 |
taint.sink |
读不可信来源后调特权工具 | first-taint-wins |
budget.exceeded |
步数/时长/(可选)token 超预算 | 0 = 不设限 |
escalation.request |
要提权、要换沙箱、被 sandbox 拒绝 | 立即 tier 2 |
进展证据只有四类:写/改文件、关掉一条 todo、验证命令通过(test|build|lint|tsc|vitest 且 exitCode 0)、用户新消息。没有这四类,说得再热闹也不算进展——这就是"消极怠工"的判据。
在飞豁免:有工具调用没回来时,永不判卡死。长下载、长构建不会被误杀。
配置(cordis.patch.yml)
- insert:
- id: task-steward
name: 'dsh-task-steward'
config:
policy: report # report | auto
stall: { silentMs: 120000 }
escalation: { allowStop: false }
可调项:policy、stewardName、repeat.*、thinking.*、stall.*、progress.*、budget.*、escalation.*、ledger.*(retentionDays 30 / maxBytes 20MB)。完整默认值见 lib/policy.mjs 的 DEFAULT_CONFIG。
账本与账单
- 账本:
$DSH_HOME/task-steward/events.jsonl(一行一条,坏了不会拖垮插件;超过maxBytes轮转成.1)。 - 心跳:
$DSH_HOME/task-steward/boot.json——apply的第一件事写的自证物(at/pid/version/policy/ledger),只依赖node:fs。装载失败时还会留apply-error.json。 - 观察覆盖:
$DSH_HOME/task-steward/stats.json——sessionEvents/turns/steps/toolCalls/hits/records/lastEventAt,每statsFlushMs(默认 30s)和每个turn/end落一次。 - 账单:
node scripts/bill.mjs --days 7→ 先「管家存活」段(心跳/覆盖/告警),再按天/信号/动作汇总 + 最近 20 条。 - 三种空账本要分清:没有
boot.json= 插件没被 loader 起来;有boot.json但sessionEvents=0= 影子期没跑过任务;有事件但零命中 = 判据对这段活动是干净的(通常是好事)。 - 证据可离线复核:
progress.none的evidence.recentTools是最近 8 个工具名——连续read/grep/glob属于正常查证,连续同一bash空跑才是空转。stall.thinking的evidence.idleMs记录当时静默了多久,用来判断阈值是不是贴太紧。 - 三步放权:①逐条标真问题/误报 ②误报多先调阈值 ③连续几天都干净再把
policy改成auto,allowStop最后放。
工程纪律(照抄本机能跑住的插件)
- 插件契约:
export const name/export const inject/export function apply(ctx);配置走cordis.patch.yml的config:。 - 所有 waterfall 监听在放行路径必须
return next(),只有真要拦才占据调用。 - 拦截理由是给模型看的,必须放顶层
reason,不能塞进info。 - 审计/账本永不携带匹配原文(凭据就地脱敏)。
- 一切上界:扫描长度、每规则一次命中、ring buffer、账本轮转。
- 不依赖不存在的服务:
ctx.get()取不到就降级,不崩。 - 装载是全有或全无:装之前先过
dsh-capability-ledger的 preflight 闸门。
验证
cd <repo>
node --test test/*.test.mjs # 纯逻辑 + 接缝形状 + 真 Cordis 装载(106 项)
node scripts/bill.mjs --help
test/materialize.test.mjs 会用锚点树里宿主自己的 Cordis 把插件真装一遍(ctx.plugin + 真实 inject 解析 + 真 effect),并断言"派生 context 上 emit 的 agent/status 收得到"和"在飞工具调用期间不误判卡死"。没解锚点(node tools\extract-anchor.mjs)的机器上它会自动 skip。
装进 profile 之后:
- 重启,日志里看到
[task-steward] 管家已启用(policy=report,账本=…,心跳=…)。 - 立刻
node scripts/bill.mjs:应该先看到心跳 ✅。看不到boot.json就别去调阈值——那说明插件没挂上。 - 跑一个真任务,再看账单里「观察覆盖」有没有涨:涨了但零命中 = 判据干净;连事件数都不涨 = 监听没接上。
装载与自证(0.1.2 起)
插件在 apply 的第一行就写 $DSH_HOME/task-steward/boot.json,之后持续写 stats.json,装载抛错留 apply-error.json。判"有没有生效"只看这三个文件 / 账单头部,不看闸门绿不绿:
node <repo>\scripts\bill.mjs --days 7
- 心跳 ❌ + 没有账本 → 插件没被 loader 起来(看
boot.json在不在); - 心跳 ✅ +
sessionEvents=0→ 挂上了但影子期没跑任务; - 心跳 ✅ + 有事件 + 零命中 → 判据对这段活动是干净的。
教训(0.1.0 → 0.1.2):apply 里绝对不能读没在 inject 声明的 ctx 服务属性(例如 ctx.config)——cordis 的 ctx 是注入守卫,一读就抛,而 fiber 失败只写 stderr、桌面端看不见。配置只用 apply(ctx, config) 的第二个参数。test/host.test.mjs 的假 ctx 已改成"读 .config 就抛错",防止回退。
No comments yet. Be the first to write one.