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

32 KiB
Raw Blame History

任务书 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 ②:交不出按内部错误)
      开发入口开着:派(方法.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 条,写"做到了"或"没做到、为什么"。