Files
InfoRSI/任务书/12-flow-1-骨架与第零轮.md
T

433 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 任务书 12:flow 第 1 部分(骨架、研究开始、第零轮)
flow 是整个系统的核心。参考项目的 flow 太长,很难验证,用户认为"之前其实很多问题都是 flow 没有做好"。所以 flow 拆成四部分写,每部分验证通过以后再写下一部分(`docs/结构.md` 第三部分"flow 分四部分写,各自验证")。这是第 1 部分。
这部分写完以后,程序能做到:给它一份配置,从零开始冻结配置、跑一次基线、让几位研究者走完第零轮(写入口、讨论、认领),程序分好入口后停下;中途被杀掉,重新启动能接着跑。
**这部分的验收看研究做得好不好,不只看跑没跑通。** 第零轮产出的入口方向对不对、分没分开,用真模型、真 Jev、真 Docker 跑 3 次来判(第五部分)。
本任务书已由用户和监督者在 2026-10-03 一起确定。下面说的"监督者",是在参考项目目录里和用户讨论设计的另一个会话。
## 开工前先读
- `CLAUDE.md`:全部。尤其是"测试"一节里新加的两条:flow 的验收看研究质量;正式代码和检验代码完全分开。另外看"罕见情况怎么办""不设默认值""中断后怎么恢复"。
- `docs/方法.md`:
- 4.1 研究开始:基线每份研究都跑一次,不复用别的研究的结果(用户 2026-10-03 定);
- 4.2 第零轮;
- 第七部分:预算与扣次;
- 第九部分:中断与恢复,含开发入口。
- `docs/结构.md`:
- 第二部分:flow 一行和"几条边界";
- 第三部分:步骤清单,以及"flow 分四部分写,各自验证";
- 第四部分:账本记录;
- 第八部分:config、ledger、runner、tasks、workspace、materials、agents、submissions 各节。flow 只经这些模块的 `index.ts` 用它们。
- `WORK_LOG.md`:"未完成"里标"给 flow"的条目,第十部分列了哪几条归这部分。
- 参考项目(只读,不要改)。只用来理解它怎么做的,不照搬结构,第二部分末尾写了为什么:
- `/home/iomgaa/Projects/RivalBits/src/controller/loop.ts`、`next-step.ts`、`resume.ts`、`task-runner.ts`;
- `steps/round-zero.ts`、`steps/forward.ts` 里的 `runB0`;
- `src/runner/service.ts` 的 `dispatch`(运行怎么登记、启动、恢复)。
## 一、这部分管哪些事
1. **从账本看出"现在到哪了"。** 账本不存"进行到第几步",每次从记录现算。
2. **"下一步做什么"的纯函数。** 输入账本记录和冻结的步骤清单,输出下一步要做的一件事。步骤的先后只写在这一处。
3. **主循环**:读账本 → 问下一步 → 做这一步 → 回到开头。
4. **恢复入口**:每次启动都走同一条路,不分第一次启动、正常重启还是被杀后重启。
5. **两个共用动作**:派一个模型任务、跑一次正式运行。后面三部分都用这两个,这里一次写好。
6. **研究开始和第零轮**:冻结配置、建镜像、存基线代码、跑基线;写入口 → 讨论(1 或 2 次)→ 认领 → 程序分配。
走到"开第 1 轮"时,程序停下,原因写"第 1 轮还没写(任务书 13)"。第 2 部分会把这一句换掉。
## 二、正式代码:`src/flow/`
对外只导出一个函数(经 `src/flow/index.ts`),以后 plugin 也用它:
- `runStudy(input)`:输入研究目录、配置、"停下"信号,都必填。
- 配置:新研究交 `checkConfig` 检查过的配置;接着跑一份已有的研究时写 `null`,以账本里冻结的那份为准。
- 交回两种结局之一:
- 停下,附原因;
- 被用户停下。
"研究结束"这种结局第 4 部分才走得到,这次不写。
文件怎么分由你定。参考:`state.ts`、`next-step.ts`、`loop.ts`、`recover.ts`、`model-task.ts`、`formal-run.ts`,加上 `steps/` 下每个步骤一个小文件。步骤文件里出现公式、字段名单或"谁能看什么"的判断,说明知识漏进来了,要挪回下层模块。
### 2.1 研究目录里放什么
全由 flow 管:
| 路径 | 内容 |
|---|---|
| `ledger.sqlite` | 账本 |
| `repo.git` | workspace 的裸仓库 |
| `runs/<运行编号>/` | 一次正式运行:取出的代码、运行目录、判分目录、日志目录。每次运行一个新目录("给 flow":日志目录每次运行一个新的) |
| `calls/<调用编号>/` | 一次模型调用:工作目录、会话文件目录、能读的代码版本(materials 的 `codeRoot`) |
取出代码、建运行目录、建判分目录时,目标目录都是新建的空目录("给 flow":取出时目标目录必须是空的)。
### 2.2 每一步以哪条记录为准算做完
参考项目的毛病是"这一步做完没有"靠隐含约定去猜,例如"这条记录公开了没有"。新项目定死:**每一步做完,以下表那条记录出现为准。** 写进代码注释,后面三部分每加一步就加一行。
| 步骤(config 的 `STUDY_STEPS`) | 看到哪条记录算做完 |
|---|---|
| `freezeConfig` 冻结配置 | 研究配置记录(`config`) |
| `baselineRun` 基线 | 基线代码记录(`code`,对象是 materials 的 `ORIGINAL_BASELINE`),而且有一次基线运行的结果不是"没启动" |
| `entryDraft`、`discussion1`、`discussion2`、`claim` | 这个阶段的关闭记录(`stageClose`) |
| `assignEntries` 分配 | 第 0 轮的入口分配决定(`decision`,种类 `entries`) |
| 按配置跳过的步骤 | 一条"按配置跳过"事件,写明步骤和 config 给的原因 |
基线运行结果是"没启动"的,不算做完:起一个新的运行编号重跑(结构.md 第四部分"确认没启动、退还名额后要重试,就起一个新编号")。基线判为不算数时,停下,原因写明。
### 2.3 "下一步做什么"
`STUDY_STEPS` 从前往后,第一个没做完的步骤就是下一步:
- **程序自己做的步骤**(冻结配置、基线、分配):做这一步。
- **研究者阶段**:
- 还没开:开阶段。名单是全部研究者,编号用 materials 的编号规则(见第三部分第 2 条),阶段编号由 flow 起。
- 已开、名单上的人都有一份被收下的提交:关阶段。只有交齐了才关("给 flow":阶段关闭只在名单上的人交齐后写)。
- 已开、有人还没交上:派这些人的任务。每个人按他最近一次调用怎么结束的来定:
| 最近一次调用 | 怎么办 |
|---|---|
| 没有调用 | 派 |
| 被用户停下 | 派 |
| 出错(包括恢复时补写的"进程中断") | 派 |
| 没交(模型自己停下、一份没收下) | 配置里开发入口关着:停下,原因写"交不出合格提交",附调用编号(方法.md 4.3 ②:交不出按内部错误)<br>开发入口开着:派(方法.md 第九部分"开发入口") |
能派的先全派,派完还有人停在"没交",才停下。
- 走到 `rounds`:停下,原因见第一部分最后一段。
这个函数是纯函数:不读文件、不调模型,同样的记录永远得到同样的下一步。
### 2.4 主循环与停下
- 每做完一步都重新读账本,再问下一步。
- **一次派发里有人没交上(没交或出错),等这次派发的会话全部结束后停下**,写一条停下事件。
- 不打断别人:别人已经在花钱了。
- 出错的那个人,下次启动时会按上表重新派(模型服务的问题不是模型的错;Pi 自己已经重试过,`src/agents/session.ts`)。
- 收到"停下"信号:正在跑的模型会话立即停(agents 已经支持);正在跑的 Docker 不管它,进程直接返回"被用户停下"。下次启动时,恢复入口会去问 Docker 这个容器怎么样了,不另写"停下"的分支。
### 2.5 恢复入口
每次启动按顺序做:
1. 打开账本。没有研究配置记录就是新研究,主循环从"冻结配置"开始。配置为 `null` 而账本里也没有配置:报错。
2. 已有配置:
- 用 config 的 `checkConfig` 把冻结的配置再检查一遍。
- 核对冻结的镜像编号本机还在(`runner.hasImage`),不在就停下,不重建(结构.md"依赖跟着任务走")。
- 核对提示词目录里每个文件的哈希和冻结时一样;不一样而开发入口关着,就停下。开发入口开着就照常往下走:每次模型调用开始时已经存下了当时用的材料。
3. `ledger.pending()` 里"开始了还没结束"的模型调用,各补一条结束记录:出错,原因"进程中断",墙钟时间测不到(结构.md 第四部分第 10 种)。
4. `ledger.pending()` 里"登记了还没结果"的正式运行,按容器名去问 Docker(`runner.status`):
| 有没有"确认启动" | Docker 的回答 | 怎么办 |
|---|---|---|
| 没有 | 没有这个容器,或建了没启动 | 写结果"没启动"(退还名额);建了没启动的删掉 |
| 没有 | 在跑,或已结束 | 写"确认启动"(扣次),然后等它、判分、写结果 |
| 有 | 在跑,或已结束 | 等它、判分、写结果 |
| 有或没有 | 问不到 | 写一条"状态未知"事件,停下,等用户决定(方法.md 第七部分) |
"已经确认启动、容器却不见了"真实运行里没出现过,不专门写:`runner.wait` 会报错,按内部错误停下。
5. 进入主循环。
### 2.6 共用动作一:派一个模型任务
一个研究者阶段里,名单上要派的人同时跑,各自一个会话。一个人在同一阶段的几个任务按先后跑;第零轮每人每阶段只有一个任务。
一个任务按这个顺序:
1. 用 materials 的 `buildMaterial` 生成材料。`StudyFacts` 从冻结的配置和任务事实取;`changes`、`artifacts` 第零轮写 `null`。
2. 按材料里"能读的代码版本",把每个版本取出到它写的目录(`workspace.checkoutVersion`,目录新建)。
3. 写"模型调用开始":调用编号用 materials 的 `callId`,尝试序号是这个任务已有的调用数加 1;材料整份存进去。先开始、后结束的顺序由这个入口保证("给 flow")。
4. 用 agents 的 `runSession` 开会话:
- **查询**:交给 `queryMaterial`。
- **提交**:
- 形状不合格(agents 交来的原样参数和原因):直接写一条退回,不经 submissions("给 flow":形状不合格的提交由 flow 直接记退回)。
- 形状合格:交给 submissions 的 `receive`,对号清单用材料里的那份,提交之后才知道的东西(`known`)第零轮三种都是 `null`。收下就写提交,退回就写退回。
- 替换、版本号照 ledger 的约定。回给模型的编号、单份种类没写"替换哪一份"又交第二次怎么办,照 agents、ledger、materials 已有的约定。约定没写清的,在报告第 6 条列出你怎么做的。
- **用量**:每次回复写一条用量。
- **写私人笔记**:写一条笔记记录。
- **停下**信号:传给会话。
5. 写"模型调用结束",带结局和墙钟时间。
### 2.7 共用动作二:跑一次正式运行
按这个顺序,前后由这个入口保证("给 flow":确认启动前要先登记运行):
1. 起运行编号。基线用 `base-1`,没启动就重跑,用 `base-2`,依次往后。
2. 写"登记要跑"(占名额)。内容:运行编号、用途、轮次、对象、运行定义;运行定义用 tasks 的 `formalRun`。
3. 取出代码,用 tasks 的 `prepareRun` 准备运行目录。
4. `runner.start`:
- 启动了:写"确认启动"(扣次)。
- 没启动:写结果"没启动",退还名额,把 Docker 的报错原文记进结果。
5. `runner.wait`,日志写进这次运行自己的日志目录。
6. 判分:`prepareJudging`,需要时让 runner 在镜像里跑校验器,再 `judge`。判分不扣次数,不写"登记要跑"。
7. 写结果,删掉容器。
容器名:
- 正式运行用 `inforsi-<研究编号>-<运行编号>`,判分容器另起名字。两份研究同时跑不会撞名,这是参考项目 9b 真实出过的问题("给 flow":容器名在本机所有研究之间唯一)。
- `docker run` 因重名失败时,runner 已经按名字再查一次。
- 恢复时重新判分:判分目录重新建空的;判分容器如果还在,先删掉再用。
**种子**:同一份研究的全部运行用同一个种子。由研究编号经 vocab 的 `hash` 算出,取前 8 位十六进制转成整数,冻结进研究配置记录。
### 2.8 研究开始和第零轮各步
- **冻结配置**:
1. 读任务(`loadTask`)、读提示词(`readPrompts`);
2. 建镜像(`runner.buildImage`,标签带研究编号);
3. 建裸仓库;
4. 一次写进研究配置记录:配置、步骤清单(config 的 `stepList`)、提示词每个文件的哈希、镜像编号、任务事实、种子。
这条记录的形状写在 materials 里(第三部分第 3 条),export 以后也要读。之后只认这份记录,不再读配置文件和 task.json。参考项目推进时还在读当前的配置,这是它的毛病之一。
- **基线**:
1. 用 `saveVersion` 存任务的基线目录,"不存"清单用任务事实里的 `unversionedNames`;
2. 写代码记录:对象 `ORIGINAL_BASELINE`,位置表 `null`;
3. 用共用动作二跑一次。
方法.md 4.1:每份研究都跑,计入运行次数。
- **第零轮的三个研究者阶段**:用 2.3 和 2.6,阶段名对上 vocab 的 `STAGES`。讨论开 1 次还是 2 次,看步骤清单。
- **分配**:
1. 从账本取每人最后一份认领、最终有效的入口;
2. 交给 rules 的 `assignEntries`;
3. 写一条决定:种类 `entries`,第 0 轮,规则名和版本照 rules 登记表的写法(`assignEntries` 不在登记表里,规则名就写 `assignEntries`,版本 1),种子 `null`,输入是全部志愿和入口,输出是分配结果。
决定的形状照 materials 的 `DecisionShapes`。"最终有效的入口"materials 已经算过一遍,导出来共用,不另写一份(第三部分第 1 条)。
- **按配置跳过的步骤**:只有一位研究者时不讨论,config 的步骤清单已经标了跳过。flow 写一条"按配置跳过"事件,接着往下走。
### 2.9 参考项目不搬的
| 内容 | 为什么 |
|---|---|
| 写权续租(`loop.ts`) | 以后 plugin 用文件锁 |
| 本轮重来、取代清单(`resume.ts` 后半) | 方法.md 第九部分:不支持中途作废重做 |
| 换模板登记、哈希比对后写更换记录 | 改成核对提示词哈希,加上开发入口 |
| 疑似运行暂停、违规事故处置、核销 | 方法.md 第八部分:不设"暂停等人处置" |
| 基线单独公开、"公开了没有"这套判断 | 新项目阶段关闭就是公开;做完以第 2.2 节的记录为准 |
| 判分时问 Jev 审查段、Jev 审实验员命令 | 方法.md 已删 |
| 最大轮数、最后一轮预留 | 不设轮数上限 |
| "做完一步账本没多就暂停"的空转检测 | 每一步都以一条确定的记录为准,没写成的步骤会被重做或报错,不会悄悄空转 |
| 步骤先后散在 8 处 | 只写在"下一步做什么"一处 |
## 三、要改的别的模块(都很小)
1. **materials 导出"最终有效的入口"**:现在 `src/materials/blocks.ts` 的 `validEntries(board)` 只在内部用。导出一个按账本取的版本给 flow 分配时用,认领的对号清单和分配用同一份,不另写。
2. **materials 加研究者编号规则**:`researcherId(i)`,第 i 位研究者(从 0 数)编号 `researcher-<i+1>`,和其他编号规则放在一起。这样正好和参考项目的写法一样,回放时不用改编号(参考项目入口编号 `researcher-1-e4` 对新项目的 `e-researcher-1-4`)。
3. **materials 加研究配置记录的形状**:`StudyRecord`,内容见 2.8,和 `FrozenProtocol` 等放在一起。materials 不能引用 config、tasks,配置和任务事实的部分先写成 Json,flow 读回时用 `checkConfig`、`checkTaskFacts` 再检查。
4. **dependency-cruiser 和 tsconfig**:
- `npm run check` 也检查 `eval/`(tsc 的 include 加上 `eval/**/*.ts`,depcruise 加上 `eval`);
- 加一条规则:`eval/` 只能经 `src/<模块>/index.ts` 用正式代码;
- `src/` 不能引用 `tests/`、`eval/`,现有的 `outside-src` 规则已经管住了,确认一下。
## 四、正式代码和检验代码完全分开(用户 2026-10-03 特别强调)
用户原话:经常"把这两个混在一起……完全没法用。因为其实我们现在是有一大堆的测试代码是需要监控我们正式代码,是不是按照我们效果运行的"。
| 目录 | 放什么 | 怎么跑 |
|---|---|---|
| `src/flow/` | 正式代码,以后 plugin 直接用 | — |
| `tests/` | 不花钱、不联网的测试,几秒到几十秒 | `npm test` |
| `eval/` | 真实运行的检验:起研究、回放记录、杀进程、查账本、评研究质量。要花钱、要联网、要 Docker | `npm run eval:…` |
硬规矩,核对时逐条查:
1. `src/` 里不出现为检验写的任何东西:参数、环境变量开关、"回放模式"、假实现、只给检验用的导出、`if (测试)` 分支。`runStudy` 的参数就是 plugin 将来要传的那几样。
2. 检验代码像用户一样用正式代码:写一份配置、调 `runStudy`、跑完用 `Ledger.open` 读账本。它不改正式代码的行为,不替换模块里的函数,不往账本里补写或改写记录。
3. 回放用的假模型,照 `tests/agents.test.ts` 的做法:本机起一个兼容 OpenAI 的服务,用 Pi 自己的 `PI_CODING_AGENT_DIR` 指一份 models.json 过去。正式代码不知道对面是假模型。
4. 检验跑出来的研究目录和报告放在仓库外:`/home/iomgaa/inforsi-eval/<日期-名字>/`,不提交。
5. `eval/` 里的东西按用途分开:
- `eval/lib/`:后面三部分也要用的工具:
- 起研究的命令行入口;
- 回放假模型;
- 给检验用的 Jev 客户端;
- 读账本的小工具。
- `eval/round-zero/`:这部分专用的:
- 想法清单;
- 检验用的配置;
- 回放脚本;
- 质量评估;
- 杀进程脚本。
6. 检验用的 Jev 问题是检验自己的问题,写在 `eval/` 里,不加进 `src/jev/`。Jev 客户端在 `eval/lib/` 自己写一个小的,请求格式参照 `src/jev/jev.ts` 和 `/home/iomgaa/Projects/RivalBits/research-wiki/reference/参考-Jev使用指南.md`。不为了检验去改 `src/jev/` 的导出。
7. 密钥:Jev 用环境变量 `TYPESAFE_API_KEY`,值在 `/home/iomgaa/Projects/RivalBits/.env`。在子 shell 里用 `set -a && . /home/iomgaa/Projects/RivalBits/.env && set +a` 加载,不要打印,不要复制进项目。
## 五、怎么验收:研究质量(重点)
### 5.1 尺子:关键想法清单和死路清单
26 圆填圆这道题,同类系统和参考项目的高分运行走过的路,监督者已经调研过,结论如下。写成 `eval/round-zero/ideas.json`:每条一个编号、中文名、给 Jev 看的英文说明(照下表"是什么"写,一两句)、出处。出处照抄下表。
我们的基线(0.9598)和 ShinkaEvolve 的起点程序逐字节相同,OpenEvolve、ThetaEvolve、AdaEvolve 也是同一个布局,所以它们的路径可以直接拿来当标杆。
**关键想法**(高分系统几乎都按"起点 → 联合优化 → 扰动重优化 → 收尾"的顺序走):
| 编号 | 名字 | 是什么 | 出处与作用 |
|---|---|---|---|
| K1 | 联合优化 | 把 26 个圆的圆心和半径(78 个数)一起交给带约束的连续优化器(如 scipy SLSQP),约束是都在正方形内、两两不重叠 | 最大的一跳:OpenEvolve 2.377→2.634;AdaEvolve 2.541→2.610;参考项目 12-real-1、12-real-2 第 1 轮采纳;16-real-1 关掉它少 0.132 |
| K2 | 扰动重优化 | 从当前最好解出发,给圆心加中等幅度扰动(约 0.01–0.06),再跑联合优化,只收更好的;包括退火式接受、停滞后加大扰动 | AlphaEvolve、ShinkaEvolve、Discovery Loop;参考项目 12-real-2 第 2 轮靠它从 2.6155 到 2.635983,15-tight-2 也到了 2.635983 |
| K3 | 结构化起点与多起点 | 从六角、错排行布局,或很多个随机起点出发,各跑一次优化,取最好 | ThetaEvolve、TTT-Discover 只靠"错排起点 + 一次 SLSQP"就到 2.635983;CodeEvolve 10 次重启 |
| K4 | 固定圆心求最优半径 | 圆心不动,用线性规划求半径和最大 | ShinkaEvolve 容差 0 那次的第一步、Discovery Loop 的种子;16-real-1 消融撑 28% |
| K5 | 严格合法收尾 | 最后统一缩一个极小量或精修,让结果在容差 0 下合法 | ShinkaEvolve、ThetaEvolve 严格版;本任务容差为 0(TASK.md) |
**死路**:
| 编号 | 名字 | 是什么 | 证据 |
|---|---|---|---|
| D1 | 只做几何构造 | 圆心按手排的规则摆(六角、方格、同心环),不用连续优化去移动圆心,只调排法和参数 | 各家卡在 2.2–2.54;等半径六角的上限是 2.438966;OpenEvolve 第一阶段提示词要求只写构造,卡在 2.377;参考项目 9a、9b-real-2 卡在 2.517、2.541 |
| D2 | 只优化半径 | 圆心不动,只改怎么分半径,当作整条路线(不是当辅助步骤) | 上限受布局限制,2.5414 是反复出现的平台 |
### 5.2 六个问题
用户关心的不是程序跑没跑通,而是第零轮产出的东西好不好。第零轮跑完,对它的产出问下面六个问题。全部只看第零轮的产出就能回答。要等第 1 轮以后才看得到的(分工能维持多久、入口真的产出分数没有),留给第 2 部分。
"入口全体"指分配时最终有效的全部入口;"分到的入口"指分配结果里每人分到的那个。
| # | 问题 | 怎么判 | 通过的条件 |
|---|---|---|---|
| 1 | 方向对不对:入口全体里有没有关键想法 | 每个入口对 K1–K5 各问一次 Jev:"按这个入口走,会直接走到这个想法吗" | 入口全体里至少有一个指向 K1。K1–K5 各被几个入口覆盖,照实报 |
| 2 | 分没分开:分到的几个入口是不是不同的路 | 分到的入口两两问 Jev:"从这两个入口出发的两位研究者,多半会做同一类代码改动吗" | 没有一对判"是" |
| 3 | 好方向分出去没有 | 用第 1 问的结果看分到的入口;再对分到的入口各问一次 Jev:"这个入口的路线是不是只停在 D1 或 D2" | 分到的入口里至少有一个指向 K1,而且没有一个判为只停在 D1、D2 |
| 4 | 入口是不是改代码的路线 | 每个入口问 Jev:"这是一条改程序代码来提高分数的路线吗?还是只读规则、只测量、只分析,没有打算改代码" | 分到的入口全部判"是" |
| 5 | 入口有没有写答案 | 每个入口问 Jev:"入口有没有写出具体应该把代码改成什么,或者预计的分数、提升量"(方法.md 4.2:只写起点) | 分到的入口全部判"否";入口全体里判"是"的照实报 |
| 6 | 讨论有没有用 | 闭门写完时的入口全体(写入口阶段的提交)和讨论后的入口全体,各算一次:不同作者的入口两两问第 2 问的那个问题,算判"是"的比例(重合度);再用第 1 问算 K1–K5 覆盖了几条 | 不设通过线,报讨论前后两组数,和参考项目的对照放在一起看 |
**问 Jev 的规矩**:
- 问题用英文,一问只问一件事,照 `src/jev/questions.ts` 的写法:带材料说明、判"是"判"否"的说明、反例。
- 入口原文是中文,原样放进材料。
- 这六问的阈值都没有校准过:一律以概率大于 0.5 判"是",报告里同时给出原始概率,并注明"阈值未校准"。
- 监督者会翻原文抽查 Jev 判得对不对。
**通过**:同样的配置真实跑 3 次,第 1 到 5 问每一次都通过,才算通过。只有一次做到不算。
**质量不通过时怎么办**:
1. 先查是不是 flow 或 materials 写错了。看第一次运行每个阶段"模型调用开始"里存的材料,是否符合方法.md 4.2 那张表:
- 写入口时:有任务说明、基线代码、基线分数,看不到别人的入口;
- 讨论时:有全部入口(标明作者)和之前各次讨论;
- 认领时:有最终的入口和全部讨论。
错了就修,修完重跑 3 次。
2. 材料对,质量仍不通过,那是研究方法或提示词的问题,是要交给用户的发现。**不许为了通过去改提示词、改 Jev 问题、改阈值、改想法清单。** 照实报告,附上你看到的原因。
### 5.3 对照:参考项目 16-real-1 的第零轮
16-real-1 是参考项目唯一跑过第零轮的运行,也是唯一从头跑到尾没暂停的。它的第零轮原样放进新 flow 回放一遍,有两个用处:
1. **验证分配**。把它三位研究者当年交的入口、讨论、认领,按原顺序交给新 flow。分完以后,每人分到的入口、是第几志愿,必须和参考项目当年的分配记录相同:researcher-1 → e4,researcher-2 → e2,researcher-3 → e6,都是第 1 志愿。第零轮不问 Jev、没有随机,所以要求完全相同。不同就说明 flow 取志愿、取入口时出了错。
2. **当质量对照**。回放出来的账本也按 5.2 的六问打一遍分,作为"参考项目第零轮的成绩",和新项目 3 次真实运行的成绩放在同一张表里。
已知的情况:讨论中研究者 2 指出"圆心和半径一起交给优化器"这条路没人站,研究者 1、3 随后补了这样的入口(e5、e7),但认领时三人选的是 e4、e2、e6。也就是说,K1 在入口全体里,却没有分出去。
**怎么取当年的记录**:
- 账本:`/home/iomgaa/rb-acceptance/16-real-1/board.sqlite`,只读,一律用 `sqlite3 -readonly` 或 node 的只读方式打开。
- 第零轮的提交在 `submissions` 表,按 `stages.phase` 区分:`entry-draft`、`entry-discuss`、`entry-claim`。按 `created_seq` 排序,在 21–77 之间,第零轮没有退回。
- 分配结果在 `facts` 表 `kind='entry-assignment'`。
- 格式转换:
- 当年入口是 `{name, startFrom, whyWorth}`,新项目是 `{name, body}`。`body` 拼成"从哪里开始看:……"加一个空行加"为什么值得走:……"。
- 讨论的 `body` 照搬;讨论里有 `entries` 的,同样转换。
- 认领的 `choices` 从 `researcher-1-e4` 改成 `e-researcher-1-4`。
- 回放的假模型:每位研究者一个模型名,按这个人当年交的先后,每次被派任务时交出下一份,然后回一句话结束。新 flow 退回了其中某一份时,假模型没有别的可交,回放到这里就停。这时照实报告:哪一份、退回原因、按方法谁对。不改回放内容去凑。
- 回放时基线照样用真 Docker 跑。
### 5.4 程序能不能接着跑(必要,但不是重点)
用户说过,这类检查重要,但不是他最关心的。做一遍,报告里简短写结果。
**杀进程**:在下面三处各杀一次控制进程(`SIGKILL`),再启动,让它跑完第零轮:
1. 基线容器正在跑;
2. 研究者 2 正在写入口;
3. 某个阶段最后一份提交刚收下、阶段还没关。
第 3 处时间窗口很短,打不中就报告实际杀在哪里:杀之前记下账本最后一条记录。
每次跑完,查这几条:
- 基线只扣了 1 次(`runStarted` 只有一条有效的),Docker 里没有第二个基线容器;
- 被杀那次调用有结束记录,结局"出错",原因"进程中断";
- 被杀时没交上的人被重新派了,交过的人没被重派;
- 每个阶段最后都关上了;每人每阶段只有一份最终收下的提交;
- 分配照常写出。
**两份研究同时跑**:两个不同研究编号的研究同时启动,都跑到基线结束。两边各扣各的、容器不撞名。
### 5.5 检验用的配置
`eval/round-zero/research.json`:
| 字段 | 值 |
|---|---|
| 研究者 | 3 位,模型都用 glm-5.3(provider 名和思考档位照 `tests/agents-pi.ts` 用的那份) |
| 讨论次数 | 2 |
| `runs` | 13 |
| `batchLimit` | 2 |
| `proposalLimit` | 1 |
| `factorLimit` | 3 |
| `devEntry` | `false` |
| 其余方法参数 | 照方法.md 6.11 的现行值 |
运行次数、每批上限、提案上限、研究者人数和 16-real-1 相同,方便对照。
## 六、不花钱的测试(`tests/`)
只测"下一步做什么"这个纯函数:手写几组账本记录,看它给出的下一步。至少覆盖:
- 空账本 → 冻结配置;
- 有配置、没有基线 → 跑基线;
- 基线最近一次是"没启动" → 用新编号再跑;
- 阶段开着、一人已交、两人没交 → 派那两人;
- 都交了 → 关阶段;
- 有人最近一次是"没交":开发入口关着停下,开着重新派;
- 有人最近一次是"出错"或"进程中断" → 重新派;
- 只有一位研究者 → 讨论写跳过;
- 分配做完 → 停下,原因"第 1 轮还没写"。
其余都由第五部分的真实运行来证明,不另写假模型、假 Docker 的测试(用户 2026-10-03 定:一律真 Jev、真 Docker)。
## 七、审阅
这部分涉及运行登记、扣次和恢复(CLAUDE.md"工作方式")。写完后另派一个全新上下文的子 Agent 审阅一轮,只看 2.5 恢复入口和 2.7 跑一次正式运行。审阅只防 CLAUDE.md"罕见情况怎么办"里的三类情况,提了别的照列不改。
## 八、规模
- `src/flow/` 这部分预期 900–1200 行,不含测试和检验。超过 1200 行就停下来检查:是需求本身复杂,还是专门路径在变多。
- materials 的改动预期 40 行以内。materials 上限 1900 行,现在 1868 行。
- `eval/` 预期 600–1000 行,不算进 flow。
## 九、文档
完成后更新:
- `docs/结构.md` 第八部分:新加 flow 一节,写 `runStudy`,以及 2.2 那张"做完以哪条记录为准"的表。表只写在这里,代码注释指向它。materials 一节补上第三部分的三处。
- `docs/结构.md` 第七部分:flow 的预期行数先不改。
- `package.json`:加 `eval:` 开头的几个命令。每个命令要能直接跑,命令名和用途写进结构.md 第八部分 flow 一节的末尾。
- `WORK_LOG.md`:
- "已完成"补一条;
- 删掉第十部分列的、这次已经做到的"给 flow"条目;
- 3 次真实运行的研究目录路径写进"已完成"那一条。
不新建 md 文件。
## 十、"给 flow"的条目里归这部分的
WORK_LOG"未完成"里的:
- 确认启动前要先登记运行;模型调用要先开始再结束;阶段要先开再关(2.6、2.7、2.3);
- 容器名要在本机所有研究之间唯一;日志目录每次运行一个新的(2.1、2.7);
- 取出时目标目录必须是空的(2.1);
- 阶段关闭只在名单上的人交齐后写(2.3)。同一条里冻结协议、解封的写入时机归第 2、4 部分,那半句留着;
- 形状不合格的提交由 flow 直接记退回,不经 submissions(2.6)。同一条里"修复的差异块按本批基线算"归第 3 部分,那半句留着。
其余"给 flow"条目归后面三部分,这次不做。
## 十一、验收与报告
做完后本地提交,然后在回复里交一份报告。监督者会对着实际文件、账本和检验输出逐项核对:
1. 提交号;新增和修改的文件列表,分 `src/`、`tests/`、`eval/` 三组列;每个文件的行数;`src/flow` 总行数、`eval` 总行数、materials 总行数。
2. `npm run check`、`npm test` 的完整输出。
3. **研究质量**:
- 一张表:行是 3 次真实运行加 16-real-1 回放,列是六问的结果和是否通过;
- 每次运行分到的三个入口的名称,以及入口全体的名称;
- Jev 原始概率所在的 JSON 文件路径;
- 3 次运行的研究目录路径。
4. 16-real-1 回放的分配结果,和参考项目当年的对照;回放中如果有提交被退回,列出来。
5. 杀进程和两份同时跑的结果,按 5.4 那几条逐条写。
6. 你和这份任务书、结构.md、方法.md 不一致的地方,以及为什么。
7. **规则不清楚的地方**:哪里让你犹豫、哪里你是靠猜的。没有就写"没有"。没有真实证据的可能情况,在这里提一句就够,不写进 WORK_LOG。
8. 正式代码和检验代码分开的自查:逐条对照第四部分的 7 条,写"做到了"或"没做到、为什么"。