DeepSeek Harness 插件

DarknessLED/dsh-loom

分类 工作流与自动化 收录于 2026-11-10

人在环的自我改进循环:给一个目标,工作链在子会话里一轮轮迭代,主 agent 始终保持空闲。

安装

# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)

dsh plugin --profile web add github:DarknessLED/dsh-loom

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。

README

ci

Human-in-the-loop recursive self-improvement for DeepSeek Harness. Hand the main agent one objective and a budget; dsh-loom turns it into a repeatable work chain (scout → several candidates → mechanical verifier → score → promote) and runs it round after round in child sessions, so the main agent stays idle and available to you. It only wakes you for real decisions — accept a patch, raise the budget, or unstick a loop — and every round lands in an append-only event log you can replay, audit and reproduce.

主 agent 保持空闲;一个目标 + 预算 + 工作链在后台子会话里自己迭代;只有需要人判断时才叫醒你。

名字取自「织机」:经线是目标与预算,纬线是每一轮的工作链,人只负责换梭与验收。

侧栏面板:中文文案 + 战役树

0.2.1(2026-11-10):这一版几乎全在修「provider 挂了以后循环怎么办」——静默死亡任意时长都重试一次、重试预算按 attempt 计、死前先读子会话自己的日志、基建故障不再算成 no-gain、模型链可以跨 provider 回退、连续三轮静默就暂停战役、campaign 能自带模型链(不用改 profile 重启)。另外:超时但已经改对的候选现在会被抢救而不是丢掉(实测白扔过 1,203 秒),git worktree prune 不再删掉兄弟候选正在写的注册,规划者的「空手而归」有了数字(117 轮里 46 轮白付),rsi_doctor 把一直存在却摸不到的自检暴露出来。变更清单见 CHANGELOG.md,上一版说明见 RELEASE-0.2.0.md。


1. 它解决什么问题

普通 agent 干活的形态是:人发一条指令 → 主 agent 忙一整个回合 → 人再发下一条。 RSI 想要的是另一种形态:

  1. 人说一句「把 X 做到 Y 水平,预算 Z」;
  2. 系统自己把这句话变成可重复执行的工作链(侦察 → 多候选实现 → 机械验证 → 评分 → 晋升);
  3. 链条在后台一轮轮跑,每轮都在前一轮的产地上继续改进;
  4. 主 agent 不参与每一轮,因此大部分时间是空闲的,随时可以和人聊别的;
  5. 只有「要不要接受这个补丁 / 预算够不够 / 循环卡住了」这类判断,才通过检查点叫醒人;
  6. 所有轮次都落成 append-only 事件日志,可回放、可审计、可复现。

本插件把这些做成 DSH 里的真实能力,而不是一个提示词模板。


2. 现在的状态(M0 – M6 已跑通)

能力 状态
战役(campaign)生命周期:创建 / 暂停 / 恢复 / 停止 / 改预算 / 加约束 ✅
append-only 事件日志 + 纯函数 reduce + 物化视图 views/state.json ✅
工作链 spec(agent / verifier / human / map 四种节点)与两套经验模板 ✅
每轮:git worktree 隔离 → 派子 agent(provider spawn)→ 机械验证 → diff → 评分 → 晋升门 ✅
晋升门:验证通过 + 有补丁 + 不劣于 incumbent;autoApply 否则挂检查点等人 ✅
检查点唤醒:sessionController.prompt(mode:"queue") 投递到发起战役的会话 ✅
成本核算:汇总子会话 assistant/message.usage,按可配置单价折 USD,双预算闸门 ✅
系统提示注入:主 agent 空闲也能在系统提示里看到各战役状态 ✅
工具面:rsi_start / rsi_status / rsi_control(含全局开关) / rsi_run / rsi_chain / rsi_evalset / rsi_decide / rsi_report / rsi_lean / rsi_replay / rsi_dashboard / rsi_export / rsi_doctor ✅
开工自检:rsi_doctor 一次回答「这台机器现在能跑吗」——git 仓库、git status 是否干净、Node 版本、loom home 可写、这个窗口是否持有循环;每条失败都带可执行的 fix ✅
监管地基:每次尝试写租约(租约 + 会话事件水位)、看门狗写掉僵死尝试、崩溃重启对账 ✅ M2
结构化交接:worker 输入是编译过的简报(≤6000 字符),输出强制 AttemptReport schema;不传 transcript ✅ M2
同级交流:同轮候选共享一块有界黑板(障碍/发现),最多 5 条、每轮结束按上限剪枝 ✅ M2
准入控制:全局 maxConcurrent 轮数上限 + 每尝试墙钟上限 + 预算三闸 ✅ M2
知识树:lesson 陈述式投放、explore 阶段零投放、holdout 家族隔离、配对影响审计与自动 quarantine ✅ M3
连续减半:任务声明 screen 检查后先筛后跑(4→2),失败候选不进入昂贵回合 ✅ M3
Lean 4 数学门:无 sorry/axiom/native_decide,#print axioms 白名单,编译不过不晋升 ✅ M4
任务级数学门:任务声明 lean.source / lean.file 后,证明与测试同为硬门;没有 Lean 工具链时停在 setup 检查点,不浪费回合 ✅ M4
L2 循环策略:策略即数据(minGain / 候选数 / 知识 / 减半),回放重算历史决策,人审后生效 ✅ M5
看板:rsi_dashboard 一次看到所有战役的进度、花费、检查点、知识量与活尝试 ✅ M5
L1 链条自优化:四类小变异(候选数+1 / 最小改动 / 不许改测试 / 声明不确定),同一任务配对比较,≥6 个配对任务 + 增益 + 无单任务回归才允许晋升,人审后激活 ✅ M6
Lean imagine 阶段:先产出候选陈述,再用结构规则筛掉空/重复/平凡/sorry/超长,只把值得证的交给编译器 ✅ M4
GUI 面板:侧栏 RSI 面板,每 5 秒刷新战役卡片(轮次/花费/最佳分/知识量/活尝试/检查点),可直接 pause/resume/stop 与 approve/reject ✅ M7
多窗口安全:任何新窗口都能拿到全部工具;循环所有权按心跳最新者持有,旧窗口自动降级为只读观察,不会两个窗口同时派发 ✅ M7
对抗加固(chaos):子会话死掉 / 模型额度用尽换模型 / 上下文爆掉缩小简报重试一次 / ledger 漂移(重复 seq、乱序、torn 尾行)/ 重启后撤回「记了落地但没提交」的假成功 ✅ M8

测试:scripts/verify.mjs 115 项、scripts/e2e.mjs 41 项(真 git worktree + 真 git apply,stub agent),以及 scripts/test-all.mjs 的 115 个套件 / 152 个规格文件——包含 712 条原始单元断言、tests/chaos/* 的对抗套件(子进程死亡、模型回退、上下文压缩重试、ledger 漂移、重启恢复、视图漂移、worktree 竞态)、tests/lifecycle/* 的 provider 故障套件(静默死亡换父重试、跨 provider 回退、基建轮不计证据、连续故障自动暂停)、tests/self*/ tests/tri*/ tests/balance/* tests/ui/* tests/dag*/* tests/preflight/* 这批由循环自己写代码通过、随后钉进 standing 的规格。


3. 安装

从插件市场装(收录后):设置 → 插件 → 插件市场里搜 dsh-loom,一键安装。目录条目是 data/plugins/DarknessLED__dsh-loom.yml。

从 GitHub 装(现在就能用):

dsh plugin --profile desktop add DarknessLED/dsh-loom

或者手工把 dsh-loom 加进 profile 的 dsh.profile.bundles。仓库根有 cordis.patch.yml,dsh plugin add 认这个 bundle manifest。

已经装进当前开发机的 profile(desktop):

  • C:\Users\Darkness\.dsh\profiles\desktop\package.json:dependencies["dsh-loom"] = "file:C:/Projects/RSI",dsh.profile.bundles 追加 dsh-loom
  • node_modules\dsh-loom → 指向 C:\Projects\RSI 的目录联接
  • 备份:package.json.bak-loom

重启 DeepSeek Harness 后生效。 回滚 = 用备份覆盖 package.json、删掉 node_modules\dsh-loom 联接,再重启。

数据全在 ~/.dsh/loom/(可用环境变量 LOOM_HOME 覆盖),删掉即清空,不影响 DSH 本身。


4. 一次典型使用

# 1. 立项(会自动提议 code-fix 模板链)
rsi_start(objective:"把 src/parser 的测试覆盖率做到 90% 以上,不许改公开 API",
          workspace:"C:/Projects/demo", budgetUsd:3, budgetRounds:10, template:"code-fix")

# 2. 注册任务集(每个任务 = 提示词 + 机械验证器)
rsi_evalset(action:"register", tasks:[
  { id:"t1", prompt:"补 parser 对行内注释的测试", verify:{ cmd:"npm test -- --coverage", timeoutMs:600000 } },
  { id:"t2", prompt:"补 parser 对嵌套模板字符串的测试", verify:{ cmd:"npm test -- --coverage", timeoutMs:600000 } }
], holdout:["t2"])

# 3. 激活链条并开跑(之后插件自己按 tickMs 推进,主 agent 空闲)
rsi_chain(action:"activate", version:1)
rsi_control(action:"start")

之后:

  • 每 20 秒 tick 一次,有 running 战役就跑一轮(一次最多 candidates 个候选,各自一个 worktree、一个子会话);
  • 每轮结束写 attempt.finished / gate.evaluated / artifact.promoted / round.finished;
  • 需要人拍板时挂 checkpoint.raised,并通过 sessionController.prompt 给发起会话塞一条消息(这是唯一会叫醒主 agent 的路径);
  • 人回 rsi_decide(decision:"approve") → 插件把补丁 git apply 进工作区;reject 则继续下一轮;stop 停战役。

随时用 rsi_status / rsi_report 查看,不会打断循环。

写规格之前,先证明它可通

一条过不了的规格和一道难题,在循环眼里长得一模一样:它照样派候选、照样被验证器打回、照样烧预算。我自己踩过两次——第一次把断言写成了不存在的 done(n,'msg') 调用,第二次让 '\\n' 穿过三层引号变成字面量反斜杠 n,各白烧了两轮(约 45 分钟)。

node scripts/spec-reachability.mjs \
  --spec tests/tri11/holdout-rotation.test.mjs \
  --patch ~/.dsh/loom/campaigns/<id>/artifacts/r3a1.patch

它在临时目录里克隆 HEAD、覆盖上工作树的 tests/,然后要求两件事同时成立:

  1. 不带补丁时必须失败(否则这条规格没断言任何新东西,通过了也不能说明问题被解决);
  2. 带上补丁时必须通过(否则这条规格根本不可能被满足,循环只是在替一个 bug 打工)。

任一不成立即非零退出。缓存里的补丁就够用——战役的 artifacts/ 里一直有。

总开关(随时开关循环)

循环有一个全局开关,不写进任何战役日志——翻转只改 ~/.dsh/loom/switch.json,另在 switch.jsonl 留一行审计。因此:

rsi_control(action:"disable", reason:"今晚不跑")   # 立即停止派发
rsi_control(action:"enable")                        # 下次 tick 继续,从原来的轮次接着走
  • 关掉后:不再派发新回合,但所有工具照常可用(rsi_status、rsi_report、rsi_dashboard、rsi_decide、rsi_lean…),已挂起的检查点也还在等你处理。
  • 历史不偏移:不追加任何 campaign 事件,seq 不动,恢复时不需要重放或重新总结;重启后开关状态从磁盘读回,不依赖任何摘要。
  • 想只花一轮、不重启循环:rsi_run(id:"...") —— 手动跑一轮,走完全一样的 worktree / 验证器 / 迁升路径,即使开关是关的。
  • 开关只管派发;已经跑起来的那一轮会自己收尾并如实记账。想让在飞的那轮也停:rsi_control(action:"stop", id:"...") 会中断该战役正在跑的尝试(尽力而为),尝试仍走自己的收尾路径并记下 aborted,不会伪造结果。
  • 面板右上角有 loop on / loop off 按钮,走同一条 POST /dsh-loom/state。

面板

重启后侧栏面板列表里会出现 RSI 入口(客户端注册 sidebar.panellist 与 main 槽位),点开即面板。面板每 5 秒轮询 GET /dsh-loom/state,按钮走 POST /dsh-loom/state 同一路由,可以直接 pause / resume / stop 以及 approve / reject 当前检查点;面板改状态时同样要过所有权检查。

多开窗口是安全的:每个窗口都注册了全部工具,但只有心跳最新的那个窗口会真正派发回合;旧窗口自动降级为只读观察(工具仍可读,改状态时会提示「another dsh-loom window is driving the loop」)。

面板文案是中文,状态词有固定译名(running 运行中、done 已完成、promoted 已晋升、no-gain 未晋升、healthy/stalled/regressing 健康/停滞/退步、检查点 gate 晋升待审、stuck 门不可达、exhausted 额度耗尽);未知取值原样透出,不吞、不猜。

面板从上到下是四块,顺序按「谁需要你」排:

区块 内容
顶栏 循环 开 / 循环 关 开关、↻ 刷新
概览条 战役 / 运行中 / 轮数 / 待处理 / 花费 / 知识
待你处理 跨战役汇总的开放检查点,按风险排序(高风险靠前),每行可直接 批准 / 驳回 / 暂缓
战役 一屏一张卡(宽的时候自动多列):默认只有「需要你」的那张展开 —— 优先挑有开放检查点的,否则挑第一个运行中的;其余收起成一行(状态点、名字、状态、待 N、三个关键数字、›)。展开后是一株树……
知识 按 事实 / 风险 / 做法 分组,按累计收益排序,显示 claim 与收益值;隔离条数单列。宽度够时自动排成多列短条目,不再是一长条灰字

别把面板做成一根永远展开的柱子:五张卡全部展开,读者要滚过五段不需要看的细节才能到真正等他的那一行。所以折叠是默认,全收起 / 全展开 一键切换,折叠状态记在 localStorage['dsh-loom-collapsed'];展开与否是三态(true 收起、false 展开、undefined 走默认),否则「全展开」会被默认值立刻推翻。网格用 repeat(auto-fill,minmax(296px,1fr)),不写 viewport 媒体查询:侧栏宽度不是窗口宽度,列数必须由容器给的空间决定(320px 一列、720px 两列,panel:audit 会把这条当缺陷检查)。

每张战役卡的头部还有一行体(vitals):最近 8 轮的分数柱(纯 CSS,span.pt[data-tone],按状态着色、按分数给高度)、三条 2px 预算条(轮数 / 花费 / 时间,到 75% 转警示色,到顶转红),下面一行 mono 的三角读数:96秒/轮 · 并行 ×2.3 · 验证器 38% · 18.4k tok · $0.08/晋升。没测过的一律写 —,缺整条三角时只显示一个「未测量」,不拿 0 冒充。

树是画出来的,不是排出来的:没有 SVG,也没有字符画 —— 每个节点是一个块,data-depth 决定缩进,::before 在分支行画一条 1px 竖轨、::after 画一条 9px 肘线,data-last="1" 的分支把竖轨截到肘线高度,于是视觉上就是一棵树(与智能体编队插件同一个画法:一条竖轨 + 每行一条肘线)。状态点只画在分支上(轮/待审/进行中),子节点不画点 —— 300px 宽里三十多个点只是噪音。

只有中间那栏滚动,开关与概览永远在视野里。数据全部来自同一条 GET /dsh-loom/state,与 rsi_dashboard / rsi_report 同源(面板不读战役文件,也不自己派发回合)。

视觉规范从哪里来

面板视觉按 OpenDesign(opendesign.cc)的 Linear pack 重做,不是凭感觉配色。取用的真实 tokens:画布 #08090A、墨色 #F7F8F8、发丝线 11%、圆角 4/6px、动效 150ms cubic-bezier(.25,.46,.45,.94)、数字与 id 一律 mono + tabular-nums、颜色只留给 6px 状态点;禁用清单(亮渐变、多层阴影、高饱和主色、圆润玩味形状)在 tests/panel-view.test.mjs 里是断言,不是建议。

浅色/深色不用两套主题:所有层级色都是 color-mix(in srgb, var(--loom-base) N%, transparent),--loom-base 在挂载时由 paintInk() 从宿主主题的 getComputedStyle 读出真实墨色并钉住(每 5 秒轮询时重钉一次,读者切主题无需刷新)。不要改回 currentColor:子标签继承的是父级半透明色,再混一次 alpha 会相乘(0.68 × 0.5 → 0.29),9.5px 的字会掉到 1.8:1 —— 这个坑被 chaos 后的对比度审计抓到过。

两条可复跑的验证命令

没有视觉能力不等于只能自称好看,所以面板有两条机械验证:

命令 做什么
npm run panel:preview 用 React 四函数 shim 重放 client/index.js 的真实组件,生成 docs/panel-preview.html(深色 + 浅色两块画布并排)
npm run panel:audit 用 headless Chrome 打开那份预览,量横向溢出/滚动高度/发丝线条数/阴影数,逐个元素算 WCAG 对比度(含 alpha 合成),并读出关键的 6 个选择器是否真的落在 mono 字体上;任一缺陷即非零退出

审计抓出过四个真缺陷:.loom-name/.loom-meta 缺 min-width:0 导致横向滚动条、color-mix 结果被 Chrome 序列化成 color(srgb … .62)(0–1 浮点,按 0–255 解析会让对比度恒算成 1.0x)、上面那条嵌套 alpha 相乘,以及预览脚本自己的渲染错误:它把函数类型的组件节点当 <div> 序列化,于是「预览」里根本没有战役卡 —— 审计一度在测一页空壳。现在 html() 遇到函数型节点会像 React 一样调用它(html(type(props))),预览从 19KB 变成 34KB,9 条发丝线、34 个状态点才真的出现在审计里。改完 npm run panel:audit 输出 OK — every canvas reads, nothing overflows, contrast clears the floor.;对应的不变量也进了单测(tests/panel-client.test.mjs 用 WCAG 公式钉住状态点色值,tests/panel-view.test.mjs 钉住「不许混 currentColor」、paintInk 的降级行为、以及 treeLines 的树形)。

Lean 4

本机已装 Lean 4.35.0(elan 在 ~/.elan,toolchain 在 ~/.elan/toolchains/leanprover--lean4---v4.35.0)。用 rsi_lean(action:"probe") 确认。

注意:Windows 上 elan 的 shim 每次调用约 34 秒(要解析 toolchain 并检查更新),所以插件直接调用 toolchain 里的真实二进制(toolchains/*/bin/lean.exe / lake.exe),并把该目录前置进子进程 PATH;shim 只在没有 toolchain 时兜底。


5. 目录结构

src/
  index.js            apply():单实例锁、工具注册、系统提示段、唯一 ticker、优雅 dispose
  core/               paths / atomic(原子写)/ lock / ledger / reduce(纯函数折叠)/ config
  chain/              spec 校验、code-fix 与 refactor 模板
  exec/               worktree、verifier 子进程、git diff/apply
  dispatch/session.js 派子 agent(provider: spawn),超时可中断
  runtime/            runtime(战役/调度)、round(一轮的完整流程)、cost、notify、lease、watchdog
  dispatch/           session.js(子会话派发)、brief.js(简报编译 + AttemptReport 解析)
  collab/             board.js(同轮候选之间的有界黑板)
  knowledge/          select.js(陈述式投放策略)、impact.js(配对影响与派生)
  chain/              spec.js(校验)、templates.js、mutate.js(L1 变异算子)、evidence.js(配对证据与晋升判据)
  lean/               scan(源码静态门)、probe(工具链探测)、gate(编译 + 公理检查)、task(任务级门)、imagine(陈述产出与筛选)
  loop/               policy.js(策略即数据)、replay.js(回放重算历史决策)
  tools/              10 个工具的定义与渲染
scripts/              verify.mjs(单元)、e2e.mjs(真 worktree 端到端)
tests/                gate / exec / lease / watchdog / board / brief / select / knowledge / lean / loop / chain / imagine 的纯函数测试
docs/                 design-v1.md(设计稿)、design-v2-addendum.md(Lean / 知识树 / 监管)、contracts.md(内部契约)
research/             三份调研报告(DSH 插件 API / Gear / Open Dream-RSI)

事件类型(~/.dsh/loom/campaigns/<id>/events.jsonl): campaign.created campaign.status objective.updated constraint.added|removed budget.adjusted|spent chain.proposed|activated evalset.registered round.started|finished attempt.finished|abandoned candidate.proposed gate.evaluated artifact.promoted checkpoint.raised|answered note.added lesson.added|impact|quarantined policy.proposed|promoted campaign.policy(战役自己的策略覆盖,由 rsi_control action:"policy" 写入)

监管产物(都在战役目录内,可删可重建):

  • leases/<attemptId>.json — 这次尝试「本该在跑」的凭据;state ∈ running/done/abandoned/failed。重启时凡是 running 一律判为 abandoned(host-restart)。
  • rounds/<n>/board.jsonl — 同轮候选之间的黑板(obstacle / finding),每轮最多留 20 条,读侧去重。
  • artifacts/r<n>a<i>.patch — 每次尝试的补丁;views/state.json — 物化视图(权威永远是事件日志)。

6. 安全边界(有意为之)

  • 不自动改用户工作区:默认 loopPolicy.autoApply=false,晋升只挂检查点;人点 approve 才 git apply,且工作区必须干净。
  • 每轮都在独立 worktree/分支里跑,失败不污染主仓库;keepWorktrees 可留证据。
  • 双重预算:轮数上限 + 美元上限(按 token 用量与配置单价折算);任一触顶即收敛并挂检查点。
  • 单实例锁:同一 LOOM_HOME 只允许一个宿主进程驱动循环,避免双份 ticker。
  • ticker 从不抛异常:宿主进程里未捕获异常会直接杀掉应用,所以 tick / 定时器 / 回调全部 try/catch。
  • 插件零运行时依赖(只用 node 内置模块),没有外部进程常驻。

7. 与参考项目的关系

  • rsi-gear/gear:它的 outer loop 优化的是「harness 本身(提示词/工具/skill/上下文管理)」,跑在 Docker 里、Windows 不在官方支持范围。我们借了它的四阶段(Diagnose → Evolve → Evaluate → Promote)与「证据完整性准入 + 最小增益 + held-out 不回归」的晋升判据,但把粒度下沉到「一个战役 = 一个目标」。
  • open-dream-rsi:它冻结模型、只优化任务内策略(policy_program / policy_parameters / recipe),编排层是硬编码的(interval_seconds=300 等常数),作者明确说 cadence 不属于学习系统。我们借了它的三件判据(回放门控晋升、append-only 事件溯源 + 可重建物化视图、termination_reason 分类),而编排循环本身正是本插件要优化的对象(M3)。

8. 对抗测试(M8)

循环跑在别人的进程、别人的额度和别人的时钟上,所以「正常路径正确」不够。tests/chaos/ 用故障注入把五类坏情况钉成规格,每一条都对应一处真实修复:

故障 注入方式 现在的行为
子会话死掉 dispatcher 返回 stopReason:"error" / 直接抛错 attempt.finished.failureKind="died",回合照常收尾、租约清掉、不晋升;验证器在工作树上的「恰好通过」不算证据(gateOk 还要求 failureKind==="ok")
模型额度用尽 首个模型报 context length exceeded、max-tokens、quota 按 models.fallback 换下一个模型并记录 modelSwitch;全部用尽 → failureKind="exhausted",回合记 exhausted 并挂检查点,不再把空转伪装成 no-gain
上下文压缩失败 compaction failed: prompt is too long 丢 notes/lessons/plan,用约一半长度的简报只重试一次,记 retried:"smaller-brief";再失败即 context,不晋升
历史漂移 重复 seq、乱序行、torn 尾行、双写者 Ledger 排序去重(同 seq 先到先得)、丢 torn 行、从磁盘最大 seq 继续;appendJsonl 先补换行,避免坏尾行吞掉新记录
视图漂移 views/state.json 被删、写一半、或声称「已 done / 已 landed」 视图只是缓存:runtime.open() 永远从事件日志 reduce,坏缓存不解析、旧缓存赢不了日志(假 done、假 landed、假 incumbent 都推翻);flush() 用日志的结果重建文件
重启恢复(含「假恢复」) 账本有 autopilot.applied{committed:false} + 脏工作区 + 活租约 recovery.js 判定「未证明的落地」→ round.unlanded(任务重新变回未解决)+ 只清理补丁自己新建的文件、git checkout -- 恢复被改的已跟踪文件;没有未提交证据时不动用户自己的脏工作区;连跑两次第二次零写入(幂等)
门根本不可达(任务写坏了) 连续三轮「补丁在变、验证器 stderr 一模一样」(tests/chaos3) attempt.finished.verifySignature(exit code + stderr 尾部 20 行的 sha1 前 12 位);同一任务连续 3 轮签名相同 → 挂一个 checkpoint{kind:"stuck"} 并继续跑,不把预算烧在不可能完成的任务上;签名在变(真的在推进)不报

这条最后加得最值:它来自一次真实事故——作者自己写的两条 chaos2 规格调用了 done(n, msg) 而不是 ok(msg),harness 里 done() 要求至少一次 ok(),于是规格永远不可能通过。循环在它上面连烧 4 轮才发现人肉去看。现在的规则是:补丁在变而验证器的说法一字不变 = 门有问题,交给人。

边界(有意不做):git clean -fd 从不使用(会删用户未跟踪文件);凭据/权限失败不重试(重试不会让账号有钱);换模型只为额度类失败买单,进程死掉换模型只会翻倍账单。


9. 三角:效率 / 质量 / 成本

以前循环只能回答两件事:第几轮、有没有晋升。轮多久、验证器占了多少、花掉多少 token —— 全是空白。实测 17 个战役、70 轮、晋升率 50%、每轮固定 2 个候选,而 round.finished 的载荷只有 {n, status, score, promoted}:三角里只有质量一条腿有数字。在这种状态下谈「优化」只能靠感觉。

现在每轮收尾自带测量(round.finished 的 durationMs / attemptCount / attemptMs / verifierRuns / verifierMs / tokens / costUsd),分工明确:

腿 由什么回答
质量 promotionRate、noGainRate、attemptsPerRound(口径不变,方便和历史比)
效率 secondsPerRound、parallelFactor = Σ候选耗时 / 轮墙钟、planAwareFactor = Σ候选耗时 / attemptWindowMs(attemptWindowMs = 轮墙钟 − 规划 − 留出集门)、verifierShare,以及每个 verifier.finished 的 exit code 与耗时
成本 tokens(有 usage 用实测,没有则按 (简报 + 回包)/4 估)、usd、tokensPerRound、usdPerPromotion

src/runtime/triangle.js 的 campaignTriangle(rounds) 是唯一口径:面板、scripts/loom-metrics.mjs、报告都调它,避免「三处各算一遍」。未测量的腿返回 null 而不是 0 —— 「没测」和「免费」是两件事,混起来正是之前 spend 恒 $0.00 却没人发现的原因。

第一个真测量(loom-triangle 第 1 轮,由循环自己跑出来的):12.7 分钟一轮、两个候选合计 15.2 分钟计算量(并行因子 1.2,说明两个候选基本没重叠)、验证器 93 秒占 12%、5,018 tokens。其中 recon(规划)一个节点就占 211 秒 ≈ 28% —— 这是下一个要动的杠杆,有数字支撑而不是猜。

循环自己提的第一个效率改动(第 1 轮晋升,提交 2e63f39):候选补丁改为在验证之前生成,于是 src/runtime/verifier-reuse.js 能用补丁字节做键;两个候选产出逐字节相同的补丁时,第二个继承前一次的裁决(verifier.finished{reused:true, durationMs:0}、exitCode 照抄),只有真正 spawn 才计入 verifierRuns,所以一回合的「验证器跑了 1 次」仍然是诚实的。

第二个(budget-preflight):在派发之前用本战役已测量回合的平均成本估算这一轮,spent + estimate 越过 USD 上限就不派发 —— 回合以 status:"budget" 收尾、挂一个 budget 检查点,而不是把钱花完再报 no-gain。

第三个(plan-share):round.finished.planMs 把规划节点单独量出来,于是「候选等了多久」和「候选跑了多久」第一次能分开看。第四个(plan-delta):base 变了但计划点名的文件没变时,复用上一轮的计划(node.finished{id:'recon', reused:true, planBasis:'delta', durationMs:0})。

第五个(attempt-window / holdout-split,提交 6d73e31、67f68a9):并行因子原来除以整轮墙钟,而留出集门是候选跑完之后才付的时间 —— 于是一轮 4 秒候选、2 秒规划、3 秒留出集的回合被读成 0.57、「串行」。现在 attemptWindowMs = max(0, 轮墙钟 − 规划 − 留出集门),planAwareFactor 除它;窗口为 0 返回 null(不制造因子),未计时的回合沿用旧回退。留出集自己也开始记账:round.finished 带 holdoutMs / holdoutRuns,通过与失败都记 —— 失败的门最容易被当成「候选不够好」,其实是时间被记到了错的账上。

内容来自项目 README(GitHub)↗

链接

同类插件

查看整个分类 →

社区评论

评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。