Files
InfoRSI/docs/结构.md
T
iomgaa 0694303153 docs: 结构.md flow 一节补第 4 部分(消融到收尾、轮末解封、研究结束、eval:full 命令)
来源:任务书 17 第五部分。materials 一节无新增导出(消融格的角色编号复用正向的
experimenterId/verifierId)。
2026-10-04 14:14:44 -04:00

730 lines
96 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.
# InfoRSI 代码结构
这份文件说明代码分成哪些模块、模块之间怎么依赖、流程按什么步骤推进、账本记哪些记录。研究方法本身以 `docs/方法.md` 为准;这里只说代码怎么组织来实现它。
来源:
- 用户 2026-10-01 认可"按负责一件事来切"、下面这套模块与可替换点,以及步骤清单和账本记录种类。
- 用户 2026-10-01 同意:提交的字段定义放 vocab,submissions 只判收不收;vocab 可放规范序列化和哈希两个函数,上限调到 600 行;执行后实际 1030 行,用户同意上限改为 1100 行;任务书 09 加对号清单时用户 2026-10-03 同意改为 1150 行,vocab 只放对号清单,任务材料的其余类型放 materials。
- 用户 2026-10-02 同意:"全部只追加":账本用一张只追加的表,全部记录都不能改。
- 用户 2026-10-02 指出可见性、截止序号、通用唯一键都是过度设计:"每次给到派的材料有哪些"和"一开始灌进去还是让它去查"是分开的两件事;Jev 本质上是一种可以代替程序的判别器。据此:谁能看什么归 materials(按任务定材料),ledger 只存;只在方法要求"只能一次"处加唯一约束;Jev 不单独成记录。ledger 预期 500–900 行。
- 用户 2026-10-02 同意:依赖跟着任务走(一份研究不能自己改依赖),凡是执行任务代码的事都由 runner 在任务镜像里跑,runner 与 tasks 的行数按第七部分。
- 用户 2026-10-02 同意:workspace 用 git,按独立分析给出的窄用法(版本号用目录内容编号、自己列文件存、不读用户配置);实验员目录里除了任务给的"不存"清单,其余都算候选。
- 依据是两次开发调研。一次是模块切分,参考了 Parnas 1972 与 1979、Ousterhout、Cockburn 和同类项目的源码;一次是同类研究的实验设计。
---
## 一、切分原则
不按研究步骤切,按"以后可能会改的设计决定"切,一个模块藏住一个决定。换掉某个决定时,只改这一个模块。
判断一刀切得对不对,问这 7 个问题:
1. 这个模块藏住的是哪一个决定?说不出来,或者说出来的是"第几步",就是切错了。
2. 换掉这个决定,要改几个模块?应该只改 1 个。
3. 对外露出的函数和类型,是否比内部实现简单得多?
4. 依赖有没有绕成圈?下层能不能离开上层单独测试?
5. 纯计算的模块里,有没有读写文件、数据库、网络或模型?
6. 每样外部的东西,是不是只有一个模块直接碰它?
7. 同一份知识(阶段名单、谁能看见什么、某种提交有哪些字段)是不是只在一处定义?
---
## 二、模块
| 层 | 模块 | 藏住的决定 | 依赖 |
| --- | --- | --- | --- |
| 入口 | plugin(插件入口) | 用户在 Pi 里怎么操作:命令、状态显示、配置对话;文件锁;把各模块装配起来 | 装配时用到各模块 |
| 组织 | flow(流程) | 按步骤清单推进研究;每次启动走同一条恢复路径 | ledger、rules、materials、submissions、agents、runner、tasks、workspace、config、jev |
| 组织 | export(导出) | 导出给人看的格式 | ledger、rules、materials |
| 组织 | config(配置) | research.json 的字段和校验(每个字段必填,不补默认值);把配置翻译成步骤清单 | rules |
| 组织 | materials(材料) | 每个任务能看到哪些材料(方法.md 第三部分);第一条消息放哪些、什么顺序、怎么写;在这些材料范围内回答模型的查询 | ledger、rules |
| 组织 | submissions(收件) | 一份提交收下还是退回:先程序检查,再问 Jev(字段定义在 vocab) | jev |
| 外部 | ledger(账本) | 记录怎么存、怎么保证不可改、怎么从记录算出当前状态;花钱动作的登记 | 无 |
| 外部 | agents(角色会话) | Pi 的接口:开子会话、发工具和提示词、收提交、记用量 | 无 |
| 外部 | runner(运行器) | Docker 的用法:建镜像、限时、限核、断网、记资源、查一次运行的状态 | 无 |
| 外部 | tasks(任务) | 任务目录里的事实和文件(指标、ε、时限、核数、镜像的 Dockerfile 与依赖清单);一个任务怎么跑、怎么从原始产物独立算分(填圆的合法化也在这里) | 无 |
| 外部 | workspace(代码版本) | git 的用法:一份研究一个裸仓库,版本号是目录内容的编号;存版本(自己列文件,不用 `git add`)、取出、比较两个版本;不读用户的 git 配置 | 无 |
| 外部 | jev(Jev 判别) | 问法、阈值(暂用参考项目的值,等真实研究的数据再校准) | 无 |
| 纯计算 | rules(研究规则) | 全部公式和常数;信念本、战绩、余额的计算 | 无 |
另有一个 **vocab**(共用词汇),所有模块都可以引用,上限 1150 行(多出来的主要是发给模型的字段说明和对号清单)。一样东西要有两个互相不能引用的模块都用到,才放进 vocab;只有一个模块用的,放在那个模块里。它放:
- 共用的类型和名单:角色、研究者阶段、提交种类、指标定义、运行定义与运行结果、模型用量;
- 每种提交的字段定义(用 TypeBox 写,同一份定义既是类型,也用来检查收到的数据,也是发给模型的提交工具参数);
- 只有两个函数:规范序列化和哈希,保证"内容相同就得到同一个指纹"只有一种算法。
依赖只能往下,图中同一行的模块之间不互相依赖。谁具体能依赖谁,以上面的模块表为准,`npm run check` 按那张表检查。
```
入口 plugin
组织 flow
export
materials submissions config
外部 ledger agents runner tasks workspace jev
纯计算 rules vocab(谁都能用)
```
### 几条边界
- **给模型看什么只归 materials。** 每个任务开始时,materials 按方法.md 第三部分定下这个任务能看到的材料。哪些放进第一条消息、哪些等模型用查询工具去读,是同一份材料的两种给法,查询工具查不到这份材料以外的东西。ledger 不判断谁能看什么。
- **收不收一份提交,只在 submissions 一处判。** 跟状态有关的两条由 ledger 在写入时拒绝:这个阶段还开着吗;要替换的那一份是不是本人刚交的。
- **信念本**:数字由 rules 算,写成文字由 materials 负责,不单独存,每次现算。
- **runner 只管怎么跑,tasks 只管怎么判。** 两者互不认识,由 flow 串起来:先向 tasks 要运行定义,交给 runner 去跑,再把产物交回 tasks 算分。凡是要执行任务代码的事(正式运行、判分时跑校验器、轻量检查)都由 runner 在任务镜像里跑,tasks 自己不起进程;只有正式运行登记、扣次数。
- **依赖跟着任务走。** 镜像由任务目录里的 Dockerfile 和依赖清单建出来,研究开始时建一次,镜像编号冻结进账本的研究配置记录;恢复时只核对,本机没有这个镜像就报错停下,不重建。一份研究不能自己改依赖。
- **"下一步做什么"只写在 flow 的一个纯函数里。** 每个步骤一个小文件。步骤文件里如果出现公式、字段名单或可见性判断,说明知识漏进来了,要挪回下层。
---
## 三、步骤清单
研究开始时,config 根据配置生成这份研究的步骤清单,连同每个决策点用哪条规则,一起冻结进账本。flow、导出、恢复都只读这份清单,不再读配置。按配置不做的步骤(第零轮只讨论 1 次时的第 2 次讨论、只有一位研究者时的讨论)不执行,账本里记一条"按配置跳过"。
**整份研究**
```
1. 冻结配置
2. 基线运行
3. 第零轮:写入口 → 讨论(1 或 2 次)→ 认领 → 分配
4. 一轮接一轮,直到余额用完
5. 导出
```
**每一轮**
```
1. 开轮:放信念本,处理留池候选
2. 提案
3. 判重合并
4. 第一次预测(看原始提案)
5. 挑刺
6. 回应与统一 → 协议冻结
7. 第二次预测(看冻结协议)
8. 选批 〔规则:选批规则〕
9. 对每个入选候选:实现 → 核验 →(不通过:修复 → 再核验)→ 正式运行
10. 整批公开,记分数账和信息账
11. 采纳
12. 若采纳的改法有 2 个以上因子:
列格 → 解释 → 选格 → 对每个选中的格:消融实现 → 核验 → 运行 → 结算
〔规则:选格规则〕
13. 收尾,更新信念本
```
### flow 分四部分写,各自验证
用户 2026-10-03 定:参考项目的 flow 太长、很难验证(用户原话:"我觉得我们之前其实很多问题都是 flow 没有做好"),所以拆成四部分,一部分验证通过后再写下一部分,每部分一份任务书。
| 部分 | 管哪些步骤 | 自己的验证目标 | 拿哪份真实记录对照 |
| --- | --- | --- | --- |
| 1. 骨架、研究开始、第零轮 | 从账本算出现在到哪;"下一步做什么"的纯函数(步骤先后只写这一处);主循环;恢复入口;派一个模型任务、跑一次正式运行两个共用动作;冻结配置、建镜像、基线;第零轮 | 第零轮的研究质量六问(任务书 12):入口里有没有关键想法;分到的入口分没分开;好方向分出去没有;入口是不是改代码的路线;入口有没有写答案;讨论有没有用。另杀进程再启动,不重跑、不重扣、不漏写 | 关键想法和死路清单取自同类系统与参考项目高分运行的研究路径;16-real-1 第零轮回放,分配和参考项目相同,并用同样六问打分当对照 |
| 2. 开轮到选批 | 4.3 ①–⑧(选批由用户 2026-10-03 定并进来) | 第 1 轮的整体研究质量(任务书 14):提案在研究路径上处在哪一步、多样性与判重、提案质量、挑刺与回应、预测是否合理、选批、过程健康;同一轮的提示词按调研结论先诊断再改 | 不回放(用户 2026-10-03 定)。参考项目高分运行(12-real-2、14-real-1、15-real-1、15-tight-2、16-real-1)第 1 轮的文字和我们的 3 次运行一起去掉来历,交全新上下文的审阅者按同一套方面打分比较 |
| 3. 实现到采纳 | ⑨–⑫ | 扣掉的次数等于确认启动的运行数;修复用完不跑、不扣、记事件 | 15-tight-2 第 1 轮的修复 |
| 4. 消融到收尾 | ⑬–⑱ 与研究结束 | 解释冻结早于任何一格启动;每格只结算一次 | 不回放(用户 2026-10-04 同意)。从头真实跑 3 份完整研究到余额用完,当四部分合起来的验收;消融与收尾和参考项目 15-real-1、15-tight-2、16-real-1 并排看 |
**怎么验证**(用户 2026-10-03 定):一律用真 Jev、真 Docker,不做假的 Jev、Docker、任务,flow 不为此开替换口子。同样的输入真实跑几次,结果都符合要求就算通过;真实运行里没出现过的情况不专门造出来测。用户原话:"我建议用一律用真 Jev、真 Docker。因为其实我们现在是在做科学研究,不是在开发程序……我们只需要重复运行多次"。模型一侧两种都做:
- 回放:用本机假模型服务,把参考项目真实记录里模型交的东西按原顺序转成新格式再交一遍,走真实的 Pi。新 Jev 的判断可能和当时不同,两边走向分开以后不再对照,按方法判断分开的地方谁对。
- 真模型:用配置里的模型真实跑几次。
**参考项目的记录不是标准答案**:对照前先问"要证明哪一句"。故意和参考项目不同的地方不对齐:第一次预测在挑刺之前、修复用完不暂停、Jev 等于阈值时收下。真实记录里只有 16-real-1 从头跑到尾没暂停;判重合并、留池、一人多条提案、修复用完在全部记录里都没出现过。
---
## 四、账本记录
账本只存发生过的事。信念本、战绩、余额、"现在进行到哪一步"都不存,每次从记录现算。
**全部只追加。** 所有记录放在同一张表里,每一行是:序号(自动递增,就是时间先后)、种类、内容(JSON)、内容的哈希。整张表禁止修改、禁止删除,由数据库触发器保证。要更正就追加一条新记录;"开"和"关"、"替换"也都写成新记录。
恢复时只核对、不重做写入(CLAUDE.md"中断后怎么恢复"),所以记录照写就是,不需要通用的去重。有自己编号、按定义只发生一次的记录,按编号加数据库唯一约束,重复了就报错:阶段开、阶段关、运行登记、确认启动、运行结果、模型调用开始、模型调用结束、消融结算(按轮次:每轮最多采纳一个改法,所以最多一次消融)。没有这种编号的记录(提交的各个版本、事件、用量)照写。前后顺序(比如启动前要先登记)由 flow 的同一个入口保证,ledger 不查。运行编号由 flow 起;确认没启动、退还名额后要重试,就起一个新编号。
| # | 记录 | 内容 | 谁写入 |
| --- | --- | --- | --- |
| 1 | 研究配置 | 冻结的配置、步骤清单、每个决策点用哪条规则、方法参数、提示词哈希 | flow(内容由 config 生成) |
| 2 | 阶段 | 每个阶段开一条、关一条;关闭即公开 | flow |
| 3 | 提交 | 各种提交的每一版:入口、讨论、志愿、提案、两次预测、挑刺、回应、解释、收尾、实现、核验:阶段关闭后以最后一版为准,此后由 ledger 拒绝再写。私人笔记的每一条:不属于任何阶段,以最后写入的一条为准。每一版带收下时用到的 Jev 判断(含原始概率和结论,没用到 Jev 的为空)。被退回的提交也记,附对象(同提交的 target:候选、原提案或消融格,没有对象的为空)、退回理由和用到的 Jev 判断 | flow(经 submissions 判定后) |
| 4 | 冻结协议 | 每个候选的冻结版本:方法.md 4.3 ⑥ 列的因子清单、保障清单、指标登记、正文、实现条款,以及轮次、出发的基线、合并了哪些原提案。答复不在其中,留在回应或统一的提交记录里(用户 2026-10-03 定) | flow(主体由 submissions 拼) |
| 5 | 作者自评 | 封存时的三个数;解封时追加一条解封记录 | flow |
| 6 | 冻结代码 | 每份候选代码和消融格代码的版本号,以及因子位置表 | flow(版本号由 workspace 给出) |
| 7 | 运行 | 每次正式运行三条:登记要跑(占名额);确认启动(扣次);结果(分数、合法化、指标、答案路径、资源) | flow(结果由 runner 与 tasks 给出) |
| 8 | 决定 | 程序做的每个决定:选批、采纳、选格、押的因子、入口分配、判重合并。每条存规则名、规则版本、方法参数、种子、全部输入、输出;用到 Jev 的(判重合并),输入里带 Jev 的原始概率 | flow(由 rules 计算) |
| 9 | 结算 | 每格的结局、撑几成、配合关系标签、每条解释的确认或否定、世界比特、惊讶 | flow(由 rules 计算) |
| 10 | 用量 | 模型调用三条:开始(附这次任务的全部材料)、每次回复的用量、结束(带这次调用的墙钟时间;中断后恢复时补写的结束记录,结局记出错、原因写"进程中断",墙钟时间记测不到)。正式运行的资源用量记在运行结果里。只记录 | flow(用量由 agents 交出) |
| 11 | 事件 | 违规记录、"按配置跳过"、"没测"及原因、停下的原因 | flow |
Jev 是一个判别器,和程序检查地位相同,不单独成一种记录:它的原始概率和结论,记在它判断的那件事里(第 3 种的提交和退回、第 8 种的判重合并)。
**写入时由 ledger 拒绝的几种情况**:阶段已关闭还要写提交;要替换的那一份不是本人在这个阶段刚交的;没有"确认启动"就写运行结果("确认没启动"这种结果除外);这次消融已经有格启动了才冻结解释。
---
## 五、可替换点
论文实验要换掉的部件,按种类用最轻的办法换:
| 种类 | 例子 | 换法 |
| --- | --- | --- |
| 外部服务 | Pi 会话、Docker 运行器、任务、Jev | 接口 |
| 决策规则 | 选批:批内最好期望、均匀随机。选格:信息价值减成本、全测、随机同格数、都不测。以后可能加合并方式 | 纯函数,登记在 rules 里的"名字 → 函数"表里,配置写名字 |
| 方法参数 | N、M、批量、运行次数、因子数上限、u、衰减、开放预留 | 配置值,不写成常数,也不用环境变量,每个都必须写在配置文件里。分两层:用户配置(配置对话逐项问);方法参数(做实验时才改,配置对话按方法.md 6.11 的现行值写进文件、给用户看过) |
| 模型与提示词 | 每个角色、每位研究者用哪个模型;提示词目录 | 配置值;账本记下每份提示词文件的哈希 |
步骤开关和对照组"朴素循环":config 里的字段和步骤清单保留(任务书 04 已写好),后面会用到;flow、materials 先只实现完整流程全开,跑稳之后再补对照组(方法.md 第十一部分)。
为了让换规则的实验做得对,结构上守住下面几条:
1. **配置只在一处被翻译**:就是第三部分的步骤清单,flow 不直接读配置里的规则名。
2. **决策函数是纯函数,输入全部从账本读。** 每条决策记录存下规则名、规则版本、方法参数、随机种子、全部输入和输出。种子由研究编号、轮次、版本算出,不用全局随机数。平票按固定规则处理。决策函数要导出,重放脚本和核对脚本直接调用它,不另抄一份公式。这样同一批记录换一条规则,就能离线重算。
3. **衡量好坏的尺子只有一把。** 比特、惊讶、覆盖率永远用同一个固定算法,从每个人的原始范围算出来,不跟着决策规则一起变。
4. **一份研究的配置在开始时冻结进账本**,恢复时以冻结的那份为准。
5. **只提供跑通过的组合。** 每条可换的规则至少有一条小规模的跑通测试。
---
## 六、模块化程度与检查
- 一个 npm 包,每个模块一个目录。别的模块只能通过目录下的 `index.ts` 引用它。
- 用 dependency-cruiser 在 `npm run check` 里强制以下规则:
- 不许循环引用;
- 每个模块只能依赖第二部分表里列出的模块;
- 不许绕过 `index.ts` 直接引用模块内部的文件;
- rules 和 vocab 不许引用文件、数据库、网络、子进程和 Pi;
- 只有 ledger 能用 SQLite,只有 agents 和 plugin 能用 Pi,只有 runner 和 workspace 能起子进程;
- tasks 不许引用 runner。
- 不拆成多个包,也不做 TypeScript 分项目编译。
---
## 七、规模
下表是粗估。动手写某个模块之前,由用户逐个确认;写的过程中超过预期一倍,就停下来检查。
| 模块 | 预期行数 |
| --- | --- |
| plugin | 600–1000 |
| config | 300–600 |
| flow | 2000–3500 |
| ledger | 500–900 |
| rules | 1500–2500 |
| submissions | 400–600 |
| jev | 400–600 |
| materials | 1200–1950(多出的部分是用户 2026-10-03 让 materials 定义的账本内容形状、冻结时给指标名加前缀;第 2 部分要导出已有函数、加作废编号标注,用户 2026-10-03 把上限放到 1950) |
| agents | 600–700 |
| workspace | 200–300 |
| runner | 250–450 |
| tasks | 公共部分与填圆合计 400–700,另有任务目录里的 Python 文件 |
| export | 500–900 |
| vocab | 上限 1150 |
---
## 八、各模块对外的函数与类型
写到哪个模块时,再把它对外的函数和类型补在这里。
### vocab
下面是 `src/vocab/index.ts` 导出的全部内容。标"字段定义"的,是一份 TypeBox 定义,同名的 TypeScript 类型也一起导出:同一份东西既当类型用,也能在运行时检查数据(`Value.Check`),还能直接当发给模型的工具参数。
**两个函数**
- `canonical(value)`:规范序列化。对象的键排序后再拼成字符串,数字必须是有限数,只接受纯 JSON;内容相同就一定得到同一个字符串。
- `hash(value)`:对规范序列化的结果算 sha256,得到 64 位十六进制的指纹。
**基础**
- `Json`(类型):任意纯 JSON 值。
- `Id`(字段定义):编号的格式,只许字母、数字和 `._-`,首字符是字母或数字,最长 128 个字符,因为编号会被拼进文件路径和命令行。
- `DIRECTIONS`、`Direction`(字段定义):指标方向,只有越大越好(maximize)和越小越好(minimize)两种。
- `Measured`(字段定义):测到 / 测不到。测到带值,测不到带原因,所有可能测不到的量都用它,不拿 0 或 null 冒充。
**名单**
- `ROLES`、`Role`:三个角色,研究者、实验员、核验员。
- `STAGES`、`Stage`:有人交提交的各个阶段,按流程先后排;程序自己做的步骤不在里面。
- `SUBMISSION_KINDS`、`SubmissionKind`:每种提交属于哪个阶段、由哪个角色交,各模块都从这张表取。
**指标**
- `METRIC_LAYERS`、`MetricLayer`:指标的三个层次,最终、常规辅助、假设专用。
- `MISSING_POLICIES`、`MissingPolicy`:测不到时怎么办的两档,缺了只记缺失、缺了整次作废。
- `MISSING_POLICY_EFFECTS`:两档各自后果的白话说明,字段说明和给模型的材料共用这一份。
- `HypothesisMetric`(字段定义):研究者在提案、回应、统一里登记的一条假设专用指标。编号是短名:字母、数字和 `_-`,不能含 `.`,最长 64 个字符。冻结时改为全名"候选编号.短名"(materials 的 `freezeProtocol`,用户 2026-10-03 定),冻结协议和 tasks 收的指标沿用这个类型,编号是全名;短名不带 `.`、全名都带,两者不会重名。
- `MetricDefinition`(字段定义):一条完整的指标定义,带编号和层次。
**运行**
- `RunDefinition`(字段定义):tasks 交给 runner 的运行定义,写明命令、工作目录、按 sha256 钉死的镜像和运行时、时限、核数和环境变量;候选进程不继承宿主的环境变量。
- `ProcessResult`(字段定义):runner 交出的进程层面结果,分没启动、正常退出、超时、从外面被杀四种;启动过的带资源用量,从外面被杀的三项资源都记测不到。
- `VERDICTS`、`Verdict`:一次运行的判定,算数、不算数二选一。
- `ANSWER_PATH_KEY`、`SAFEGUARD_TRIGGERS_KEY`:候选在指标文件里多写的两个保留键名,`answer-path` 和 `safeguard-triggers`。
- `AnswerPath`(字段定义):读到的答案路径原样字符串,或读不到及原因;在不在位置表里由拿得到位置表的模块现算。
- `SafeguardTriggers`(字段定义):读到的"保障短名 → 动手次数"原样内容,或读不到及原因。
- `JudgedResult`(字段定义):tasks 从原始产物独立判分的结果,包括判定和理由、分数、各指标取值、答案路径、保障动手次数和任务自己的附加记录。
- `ModelUsage`(字段定义):一次模型调用的用量,四种 token 数和价格,每一项都可能测不到。
**提交**
- `Range`(字段定义):三个数 q1、q5、q9,即一个分数有一成、一半、九成可能低于多少。
- `SUBMISSION_SCHEMAS`:每种提交的字段定义,键与 `SUBMISSION_KINDS` 相同;同一份定义是类型、收件格式检查和提交工具参数。
- `Submission<K>`(类型):某种提交的内容类型,例如 `Submission<"proposal">`。
**Jev**
- `JevJudgment`(字段定义):Jev 对一个问题的一次判断:问题名、问题全文的哈希(措辞、示例、阈值一起算)、阈值、原始概率、结论(概率是否超过阈值)、Jev 返回的请求编号。jev 交出它,ledger 在提交和退回记录里原样存它,两个模块互不引用,所以放在这里。
- `Checklist`、`ChecklistOf<K>`(类型):对号清单,materials 为一次任务算出、submissions 拿来判收不收;按提交种类区分,每种只放它要用的(入口和讨论的入口个数范围;认领的入口编号和至少排几个;提案的 M、因子数上限、能延续的因子、能引用的假设专用指标(全名)、已占用的指标编号(任务的指标和两个保留键);两次预测的候选;挑刺的原提案和它的因子;回应、统一的挑刺编号(统一另有被合并的原提案);实现、修复、核验的条款、因子、保障(核验另有没有归属的改动);解释的格和 M;消融实现的格、要关的因子、全部因子;消融核验的格和全部因子;收尾无)。用户 2026-10-03 决定:vocab 只放这一个,任务材料的其余类型放 materials。
### rules
下面是 `src/rules/index.ts` 导出的全部内容。rules 是纯计算:输入数字,输出数字,不碰文件、数据库和模型,也不用全局随机数。λ、ε、已知最好、每批上限、余额这些参数都由调用方传进来,rules 里只写死方法.md 6.11 标"固定"的几个常数。三个数一律用 vocab 的 `Range`。编号(候选、研究者、入口、因子、解释)一律按自然序比较:数字部分按数值大小比,所以 c2 排在 c10 前面。
几个反复出现的词:**折线**是把一个人的三个数画成"分数低于 x 的把握"的分段直线(6.2);**合并分布**是几条折线按权重加起来的结果(6.3);**改进量**是分数比基线好了多少,正数表示变好,越小越好的任务也换算成正数为好(6.1)。决定类函数的返回值里带着写决定记录要用的中间数,不只返回"选了谁"。
**常数与基础**
- `INTERACTION_CUT`:配合关系标签里相对交互量的界限 0.2(6.6"配合关系标签")。
- `INDISPENSABLE_SUPPORT`:判"缺一不可"的撑几成界限 0.9(6.6)。
- `NET_TOLERANCE`:选格时比较净值的容差 1e-12,差距在它以内的组合当作一样好(6.7"选格")。
- `GRID_COMBINATION_CAP`:只测一部分格时,网格组合总数的上限 4,000,000(6.7"信息价值")。
- `GRID_SUBDIVISIONS`:网格里每段最多再等分 20 份(6.7"信息价值")。
- `epsilonFloor(ε)`:算 ε/100;采纳线下限、剩余空间下限、比特格宽三处共用这一个函数,保证末位一致(6.11"跟随 ε")。
- `improvement(from, to, direction)`:从 from 到 to 按指标方向好了多少,越大越好时是 to − from,越小越好时是 from − to(6.1)。
- `compareIds(a, b)`:按自然序比较两个编号,供所有排序和"编号最小"的判断使用;p01 和 p1 这类逐段相同的编号再按整串定先后,不会判成相等。
- `combinations(n, k)`:列出从 0..n−1 里选 k 个的全部组合,按字典序排,选批和选格列组合时用。
**范围与合并分布(6.1–6.3)**
- `Point`、`Knots`(类型):折线上的一个点(横坐标,低于它的把握),以及按横坐标严格递增排列的一串点。
- `Component`、`Mixture`(类型):合并分布的一个成分(一条折线加它的权重),以及成分的列表,权重加起来是 1。
- `personKnots(range)`:把一个人的三个数变成五个点的折线,两端各补一段与相邻半宽相同的尾巴(6.2)。
- `below(knots, x)`:读折线在 x 处"低于 x 的把握"。
- `knotsMean(knots)`:折线所代表的分数的平均值(期望)。
- `density(knots, x)`:折线在 x 处的斜率,也就是把握在这一点有多集中(密度);押的因子更新权重时用(6.8)。
- `mapKnots(knots, f, decreasing)`:把折线的横坐标换到另一把刻度上;换算是反向时点序倒过来、把握换成 1 减原把握。
- `reserveKnots(lines)`:开放预留,即给"大家都没想到的结果"留的那一份:取所有折线的最低点到最高点,左右各再放宽一半,在上面均匀铺开(6.3)。
- `authorShares(authors)`:每条解释的占比,研究者之间等权,同一人的几条平分(6.3"合并"、6.7"成分与权重")。
- `mixtureBelow(m, x)`:合并分布在 x 处"低于 x 的把握"。
- `breakpoints(m)`:合并分布全部折点的横坐标,去重、升序。
- `mapMixture(m, f, decreasing)`:把合并分布的每个成分都换到另一把刻度上,权重不变。
- `forwardMixtures(candidates, λ)`:正向每个候选的合并分布,(1 − λ) × 各人折线平均 + λ × 开放预留,开放预留由这次选批的全部候选、全部人共用(6.3"正向")。
- `ablationCellMixture(entries, λ)`:消融一格的合并分布,各条解释按 `authorShares` 分 1 − λ,这一格自己的开放预留占 λ(6.3"消融")。
- `toImprovement(m, b, direction)`:把合并分布从分数原值换算成相对基线 b 的改进量;越小越好时刻度反向,一成和九成随之对调(6.1)。
- `mixtureQuantile(m, p)`:合并分布的分位点,即"有 p 的把握低于多少";只给人看,不参与决定。
- `mixtureRange(m)`:合并分布的一成、一半、九成,按分数原值,消融格显示"大家合起来的范围"用。
- `forwardMergedRange(imp, b, direction)`:正向候选"大家合起来的范围",在改进量上读三个数再换回分数原值。
- `integrateUpperTail(ds, from)`:按段精确算 ∫[1 − ∏F] dt,正向的批内最好期望和消融全测时的信息价值共用这一个积分。
**正向:入池、选批、采纳(6.4、6.5,4.3 ⑫)**
- `lossProbability(imp, ε)`:候选"比基线低 ε 以上"的把握;严格小于一半才入池(6.4"入池")。
- `batchValue(imps)`:一组候选的批内最好期望 V(S),即这批里最好的那个预计比基线好多少,低于基线按 0 算(6.4)。
- `BatchInput`(类型):两条选批规则共用的输入:候选及各人的三个数、基线、方向、ε、λ、每批上限、余额、种子。
- `BatchDecision`(类型):选批结果,含 6.4"选批记录写入"的全部内容:参数、批量 k、每个候选的合并范围和是否入池、各组合的 V、选中的组合及其 V、候选池的 V、分数尺读数。
- `selectBestExpected(input)`:选批规则"批内最好期望",在候选池里列出全部组合取 V 最大的,V 完全相等时取编号小的组合(6.4)。
- `selectUniformRandom(input)`:选批规则"均匀随机",用种子从全部候选里抽,不看入池门槛;入池、合并范围、分数尺读数照样算出来记下(第十一部分)。
- `AdoptionLineInput`(类型):算采纳线要的基线 B、已知最好 K\*(没有时为 null)、ε 和方向。
- `adoptionLine(input)`:采纳线(6.5),返回线的值和 (K\* − B) ÷ 4 的原值;没有已知最好时是 ε,结果不低于 ε/100。
- `adopt(input)`:采纳判断,只看有有效分数的候选,最好的那个比基线高出采纳线以上才采纳;返回采纳线、最好的候选及其改进量和采纳结果,够写采纳记录。
**消融结算(6.6,4.3 ⑬ ⑰)**
- `CellOutcome`(类型):一格的五种结局:有效分数、证据不足、没测、跑不起来、一起关(带实际关掉的因子和可能的分数)。
- `AblationCell`(类型):一格,即一个因子和它的结局。
- `AblationExplanation`(类型):一条解释,带作者和它对每一格给的三个数。
- `listCells(factors)`:列格,被采纳改法的每个因子各一格,按因子编号排(4.3 ⑬)。
- `totalGain(baseline, full, direction)`:总提升 G = f(完整) − f(基线);G ≤ 0 说明上游选错了改法,直接报错(6.6"撑几成")。
- `supportOf(baseline, full, off, direction)`:某个因子撑几成,即关掉它后损失的分数占总提升的比例,不截断(6.6)。
- `SynergyLabel`(类型):配合关系标签的代号,六种:关掉反而更好、不全、缺一不可、互补、替代、各管各;中文说明以后由 materials 写。
- `synergyLabel(supports)`:按 6.6 那张表从上往下判出标签,同时返回各格撑几成之和与相对交互量(有格没测出有效分数时这两个数为 null)。
- `sharedSettings(cells)`:判断哪些"一起关"的格其实是同一种设置(关的因子集合相同),给出该沿用哪一格的结果(6.6)。
- `ExplanationVerdict`(类型):一条解释在一格上的判定,确认或否定。
- `judgeExplanations(explanations, cells)`:每条解释在每个有效格上,实测落在它的一成到九成之间(含两端)记确认,否则记否定(6.6)。
- `inseparableGroups(judgments)`:找出在所有有效格上确认与否定完全相同的解释,归成"现有测法分不开"的组(6.6)。
- `ablationFinding(input)`:消融中发现,找出比当前基线高出采纳线以上的格,几格都过线时取分数最好的、同分取因子编号小的;返回采纳线、(K\* − B) ÷ 4、全部过线的格和选中的格(4.3 ⑰)。
- `SettleInput`(类型):一次消融结算的输入:基线、完整分数、方向、ε、已知最好、λ、被采纳改法的因子清单、各格结局、各条解释。清单里每个因子都要有一格,缺了直接报错。
- `settleAblation(input)`:一次消融的完整结算,给出 G、每格的撑几成和合并范围、有效格的世界比特、惊讶和分歧量、配合关系标签、确认与否定、分不开的组、消融中发现(4.3 ⑰、6.6、6.9);押的因子另由 `chooseBet` 算。
**信息价值与选格(6.7、6.8)**
- `OPEN_RESERVE`:成分名单里开放预留的名字,排在最后。
- `AblationModelInput`(类型):建成分模型要的输入:基线、完整分数、方向、已知最好、ε、λ、因子编号、各条解释。
- `AblationModel`(类型):成分模型:因子、成分(各条解释加开放预留)、起始权重、G、剩余空间 h、每格每成分的折线(分数原值和撑几成两种刻度)及期望、每格的事前期望撑几成。
- `remainingSpace(full, knownBest, ε, direction)`:剩余空间 h,有已知最好时是 max(K\* − f(完整), ε/100),没有时是 1(6.7"方向收益")。
- `ablationModel(input)`:建成分模型,每条解释一个成分、开放预留一个成分,解释合起来占 1 − λ(研究者等权、同一人平分),开放预留占 λ(6.7"成分与权重")。
- `gridSize(k)`:测 k 格时每格的小段数 m,即满足 m^k ≤ 4,000,000 的最大整数(6.7"信息价值")。
- `cellGrid(knots, k)`:把一格按折点切成小段,返回各小段中点和每个成分在各小段上的把握;小段数的规则按 6.7 原文。
- `measuredGrid(model, c, k)`:模型里第 c 格的网格,在分数原值上切段,再把中点换算成撑几成。
- `informationValueExact(model)`:全部格都测时的信息价值,逐段精确积分。
- `informationValueGrid(model, S)`:用网格算的信息价值,不论测几格都走网格,主要用来和精确算法互相对照。
- `informationValue(model, S)`:信息价值 VOI(S),即测了 S 这几格之后,挑出最该押的因子能比现在多挣多少(6.7);全测走精确积分,只测一部分走网格,空集是 0。
- `ComboParams`(类型):算价值与成本要的余额、每批上限、知识被用上的概率 u、衰减 r。
- `ComboValue`(类型):一个测格组合的 VOI、R̂、m′、价值、成本、净值(6.7"价值减成本")。
- `evaluateCombos(model, params)`:列出格数不超过"余额 − 1"的全部非空组合并算价值、成本、净值,另给 ĝ₀ 的原值。
- `CellRuleInput`(类型):四条选格规则共用的输入,即成分模型的输入加 `ComboParams` 和种子。
- `UntestedReason`(类型):一格没测的原因:剩余次数不够、信息价值不足以抵过成本、随机没抽中、规则是"都不测"。
- `CellDecision`(类型):选格结果,含 6.7 要求附上的全部数字:参数、种子、G、h、成分与起始权重、各因子的事前期望收益、ĝ₀、全部组合、选中的格及其净值,以及每一格测不测、没测的原因和含它的净值最大组合。
- `topCombo(combos)`:在给定的组合里挑净值最大的,差在 1e-12 以内算一样好,再先取格数少的、后按因子编号比(6.7"选格"第 2 条)。它不管净值是否大于 1e-12,所以也用来挑"含这一格的最好组合"。
- `pickByNet(combos)`:按净值挑要测的组合。先只留净值大于 1e-12 的组合,一个都没有就一格都不测;再在留下的组合里用 `topCombo` 挑。
- `selectByValue(input)`:选格规则"信息价值减成本"(6.7"选格")。
- `selectAll(input)`:选格规则"全测",按因子编号测前"余额 − 1"格(第十一部分)。
- `selectRandomSameCount(input)`:选格规则"随机抽同样多的格",格数和"信息价值减成本"会选的一样,用种子抽(第十一部分)。
- `selectNone(input)`:选格规则"都不测"(第十一部分)。
- `BetResult`(类型):押的因子的结果:更新后的成分权重、权重是否无从更新、各因子的方向收益(实测换算的或估计的)、挑出的因子及其预测收益、押或不押的两种情况、关掉反而更好的格。
- `chooseBet(input)`:押的因子,用有效格的实测更新各成分的权重,再挑方向收益最大的因子;有格关掉反而更好或估计收益不为正时不押,实测落在所有成分之外时保持起始权重(6.8)。
**信息账(6.9)**
这一组是方法里"唯一的尺子",不随选批、选格规则变,一律在分数原值上从本批基线 b 起按格宽切小格。
- `bitWidth(ε)`:格宽 Δ = ε/100。
- `entropyBits(ps)`:一组把握的拿不准程度 −Σ p log₂ p,单位是比特。
- `worldBits(m, origin, width)`:世界比特,即合并分布在宽 Δ 的小格上有多拿不准。
- `cellIndex(origin, width, v)`:实测 v 落在第几个小格,和切格用同一套边界。
- `surprise(m, origin, width, value)`:惊讶 = −log₂(事前给实测所在小格的把握),返回格号、事前把握和惊讶值;事前把握为 0 时惊讶记"测不到"并写明原因。
- `disagreement(entries, origin, width)`:分歧量,大家平均后的世界比特减去各人世界比特的平均,量的是人与人之间的差别,不掺开放预留,只记录。
- `Placement`(类型):落点四段:低于一成、一成到一半、一半到九成、高于九成。
- `placement(range, v)`:实测落在一个人范围的哪一段。
- `covers(range, v)`:实测是否落在一成到九成之间(含两端),也是 6.6 里"确认"的判据。
- `coverage(samples)`:落点四段的计数和覆盖率;一条样本都没有时覆盖率记"测不到",不记 0。
**余额与战绩(第七部分、6.10 第 4 条)**
- `RunState`(类型):一次正式运行在账上的三种状态:已预留还没核对出结果、确认启动、确认没启动。
- `runBalance(total, runs)`:从运行记录数出已扣次数、占用名额和余额;确认启动的扣一次,已预留的先占着名额,确认没启动的退还。只数运行次数,不算计算时间。
- `forwardBatchSize(batchCap, balance)`:本轮正向批量 k = min(每批上限, 余额)。
- `ablationCellLimit(balance)`:消融最多测"余额 − 1"格,余额为 0 或 1 时是 0。
- `Phase`(类型):正向或消融。
- `Tally`(类型):一个人的战绩:总数、中位数偏高几次、偏低几次、被穿过上沿几次、下沿几次、包住几次、落点四段计数。
- `trackRecord(samples)`:按研究者、按正向与消融分开数战绩,只用来显示,不改任何权重(6.9、6.10)。
- `authorVsMerged(items, direction)`:"作者自评 vs 大家合并",给出作者比大家乐观多少,以及实测落在两个范围的哪一段;只收已经跑过、有有效成绩的候选(6.10 第 4 条)。
**入口分配、种子与规则登记表(4.2、第九部分、第十一部分)**
- `EntryAssignment`(类型):一个研究者分到的入口,以及它是第几志愿(按剩余入口分到的为 null)。
- `assignEntries(input)`:第零轮的入口分配,按志愿一轮一轮分,争同一个入口时先给作者、再给编号最小的人,志愿用完的人按编号拿剩下的入口;结果里带志愿序号,够写分配记录(4.2"分配规则")。
- `decisionSeed(input)`:决定的种子,由研究编号、轮次、版本经 vocab 的 `hash` 算出(第九部分)。
- `seededRandom(seed)`:从种子确定地展开一串随机整数,同一个种子永远得到同一串数,不用 `Math.random`。
- `seededSample(items, size, seed)`:用种子从一组编号里不放回地均匀抽 size 个,结果只取决于种子和编号集合,与传入次序无关;候选和因子都这样抽。
- `BATCH_RULES`、`BatchRuleName`:选批规则登记表,名字 → 函数和版本号,两条:批内最好期望、均匀随机。
- `CELL_RULES`、`CellRuleName`:选格规则登记表,四条:信息价值减成本、全测、随机抽同样多的格、都不测。
- `runBatchRule(name, input)`:按名字跑一条选批规则,结果带上规则名和版本,flow 写决定记录时从这里取。
- `runCellRule(name, input)`:按名字跑一条选格规则,结果带上规则名和版本。
### ledger
下面是 `src/ledger/index.ts` 导出的全部内容。账本是一个 SQLite 文件里的一张只追加的表;外面拿不到数据库连接,只能经下面这些函数读写。ledger 不判断谁能看到哪些记录,那归 materials。
**打开与写入**
- `Ledger.open(path)`:打开一份研究的账本,新文件就建表,旧文件核对表结构版本号(现为 4),对不上就报错。
- `ledger.close()`:关闭数据库。
- `ledger.append(kind, content)`:追加一条记录并返回它。先按种类检查内容,再做第四部分说的四种拒收检查(第 1 条是只收开着的阶段的提交,即有开阶段记录、没有关阶段记录),都在同一个事务里。有自己编号、按定义只发生一次的记录按编号加了唯一约束,重复写就报错:阶段开、阶段关、运行登记、确认启动("结果是没启动"也算在这一条里,一次运行两样只能有一样)、运行结果、模型调用开始、模型调用结束、消融结算(按轮次,一轮一条);其余记录照写。记录之间的先后(例如结果前要先有登记)不查。写提案时,作者自评从提案里拆出来,在同一个事务里另存成一条封存。
- `ledger.transaction(fn)`:里面的几次追加要么全部写进去,要么一条都不写;fn 必须是同步函数,返回值类型排除了 Promise,异步函数在类型检查时就被挡住;不能嵌套。
**读取**
- `ledger.records()`:全部记录,按序号排,读出时按种类重新检查内容。
- `ledger.stageClosed(stageId)`:这个阶段关了没有。
- `ledger.latestSubmission(slot)`:某份提交(阶段、作者、对象、编号)的最新一版。
- `ledger.pending()`:未完成的动作,即登记了还没有结果的运行(带着有没有确认启动)和开始了还没有结束的模型调用,恢复时由 flow 逐个核对。
- `Pending`(类型):`pending()` 的返回值。
**类型**
- `Kind`:记录的种类,共 19 种:`config`(研究配置)、`stageOpen` / `stageClose`(阶段开、关)、`submission`(提交的每一版,外层带收下时用到的 `JevJudgment` 数组,没用到 Jev 的为 null)、`rejection`(退回,带对象 `target`(与提交的 target 同义,没有对象的写 null;同一人在同一阶段可能对几个对象各做一次同种任务,materials 按它找本人这个对象的上一次退回)、原样内容和理由;Jev 退回的,原样存这次用到的一条或几条 `JevJudgment`,程序检查退回的这一项写 null)、`note`(私人笔记,每次修改追加一条,以最后写入的一条为准,不带版本号)、`protocol`(冻结协议)、`seal` / `unseal`(作者自评的封存、解封,都用记录序号指明是哪一条)、`code`(冻结代码)、`runPlanned` / `runStarted` / `runResult`(运行的登记要跑、确认启动、结果)、`decision`(决定)、`settlement`(一轮的消融结算,按轮次唯一)、`callStarted` / `callUsage` / `callEnded`(模型调用的开始,附这次任务的全部材料;每次回复的用量;结束,带墙钟时间(秒,`Measured`:中断后恢复时补写的结束记录,结局记出错、原因写"进程中断",墙钟时间记测不到),结局与 agents 的会话结局同名:交了、没交(带原因)、出错(带原因)、被停下)、`event`(事件)。
- `AppendKind`:调用方能追加的种类,除了 `seal`(封存只由 ledger 在写提案时一并写)。
- `Content<K>`:某种记录存下的内容。
- `Input<K>`:追加时交的内容;只有提交和存下的不同:提案带着作者自评。
- `LedgerRecord<K>`:一条记录:序号、种类、内容的哈希、内容。
- `SubmissionSlot`(类型):一份提交不论第几版:阶段、作者、提交种类(版本号只在同一种提交里往下接)、针对谁(候选或格,没有就空)、这一份的编号(只有提案、挑刺这两种一人能交好几份的才有,其余必须为空)。
- `StoredSubmission`、`SubmissionInput`(类型):存下的提交和追加时交的提交,按提交种类区分内容类型;版本号从 1 起,替换时加 1,被替换的就是上一版;`jev` 和 `content` 并列,存这一版收下时用到的 Jev 判断,不进 `content`。
### config
下面是 `src/config/index.ts` 导出的全部内容。config 依赖 rules,只为从规则登记表取规则名和版本。账本里"研究配置"那条记录只存 Json(ledger 在更下层,不能引用 config),flow 从账本读回配置时,再用这里的 `checkConfig` 检查一遍。
**读取与校验**
- `checkConfig(value)`:按字段定义检查一份已经解析好的配置,合格就原样返回带类型的配置;不合格就报错,一次列出全部错误,每条写成"字段路径:原因"。读 research.json 后、从账本读回冻结的配置时都用它。
- `loadConfig(path)`:读 research.json,解析 JSON(不是合法 JSON 时报错带文件路径),再交给 `checkConfig`。
**字段定义**
- `ResearchConfig`(字段定义):research.json 的全部字段,全部必填,不认识的字段报错。每个字段带中文说明(`description`)和类别(`category`:用户配置或方法参数),配置对话照着它问,不另写一份。`models` 里除了各角色的模型,还有 Jev 用的模型名(`models.jev`,用户配置);Jev 的密钥不进配置,从环境变量读。流程是两选一:完整流程写四个开关、选批规则、它才用得到的 λ、u、衰减;朴素循环只写 `preset`,别的都写不进去。
- 研究编号 `studyId` 只许小写字母、数字和 `._-`,首字符是小写字母或数字:它会进 Docker 镜像名,镜像名只认小写。(来源:用户 2026-10-03 同意,任务书 13)
- `task`、`promptDir` 必须是绝对路径:从别的目录接着跑一份研究时,相对路径会读到别处,而任务目录没有哈希核对挡着。(来源:用户 2026-10-03 同意,任务书 13)
- `FIELD_CATEGORIES`、`FieldCategory`:字段的两种类别,用户配置、方法参数。
- `THINKING_LEVELS`、`ThinkingLevel`:Pi 0.85.1 接受的七个思考档位,出处写在代码注释里;config 不能引用 Pi,所以照抄一份。
**步骤清单**
- `stepList(config)`:纯函数,把校验过的配置翻译成整份研究和每一轮的步骤清单。每一步标"执行"或"按配置跳过"(带原因),被跳过的也列出;选批、选格两步带规则名和版本。
- `StepList`(类型):`preset`(完整流程还是朴素循环,materials 据此决定给什么材料)、整份研究的步骤、每一轮的步骤。
- `Step<S>`(类型):一步:编号、规则(只有选批、选格有,其余为空)、执行或跳过及原因。
- `RuleRef`(类型):决策点用的规则,名字和版本。
- `STUDY_STEPS`、`StudyStepId`:整份研究的步骤,按先后排。
- `ROUND_STEPS`、`RoundStepId`:每一轮的步骤,按先后排;比第三部分拆得更细(例如"回应与统一"和"协议冻结"分成两步,挑刺与回应关掉时协议照样冻结)。
### runner
下面是 `src/runner/index.ts` 导出的全部内容。runner 用 docker 命令行做事,不碰账本,自己也不存状态:依据都在 Docker 里,所以新进程里新建的 `Runner` 能接着等早先启动的容器。Docker 不应答时,查状态答"问不到",其余函数直接报错,都不重试。
- `new Runner(dockerHost)`:Docker 守护进程的地址必须给出;写 null 表示不加 `-H`,由 docker 命令按自己的环境变量和 context 决定。
- `runner.buildImage(contextDir, tag)`:用目录里的 Dockerfile 建镜像,交回镜像编号(`docker image inspect -f '{{.Id}}'` 的值)。
- `runner.hasImage(digest)`:本机有没有这个镜像编号。
- `runner.start(name, definition, runDir)`:起一个容器,运行目录挂到运行定义的工作目录;容器里以 root 运行的计量 shell 把命令降到宿主当前用户再跑,结局、退出码、墙钟、CPU 秒、峰值内存写在容器内只有 root 可写的目录里,命令改不了。`docker run` 失败时按名字再查一次,容器不存在或建了没启动算没启动(建了的删掉),在跑或已结束算启动了。
- `runner.wait(name, logDir)`:等容器结束,交回 `ProcessResult`,把标准输出、标准错误和从容器里拷出的结果文件存成日志目录里的 `stdout.log`、`stderr.log`、`usage`。有结果文件按文件定正常退出或超时;没有就是从外面被杀。
- `runner.status(name)`:没有这个容器、建了没启动、在跑、已结束、问不到,五种之一。
- `runner.remove(name)`:删掉一个已结束或没启动的容器,flow 写完结果后调用。
- `ContainerStatus`、`StartOutcome`(类型):查状态的五种回答;启动了,或没启动和 Docker 的报错原文。
容器名由调用方给,必须是确定的(恢复时按名字去问);runner 只检查它符合 Docker 的命名要求。没有取消函数:方法里没有程序主动取消一次已启动运行的场合。
### tasks
下面是 `src/tasks/index.ts` 导出的全部内容。tasks 不起子进程:凡是要执行任务代码的事(正式运行、判分时跑校验器、轻量检查)都只交出 `RunDefinition`,由调用方交给 runner 在任务镜像里跑,再把产物交回这里解读。任务种类到实现是 `task.ts` 里的一张对照表,现在只有填圆。各目录(运行目录、判分目录、检查目录)都由调用方建好、传空目录进来。
**任务事实**
- `TASK_KINDS`、`TaskKind`:任务种类名单,现在只有 `circle_packing`。
- `TaskFacts`(字段定义):task.json 的字段:任务种类、最终指标的编号和方向、ε、已知最好(没有写 null)、单次时限、核数、Docker 运行时、不许改的文件名单、存代码版本时跳过的名字(`unversionedNames`,只写名字不带 `/`,在任何一层都跳过;填圆是 `__pycache__`、`.git`、`solution.json`、`metrics.json`)。全部必填,不认识的字段报错。
- `checkTaskFacts(value)`:严格检查一份已解析的 task.json,一次列出全部错误,每条写成"字段路径:原因"。
- `Task`(类型):读进来的一个任务:task.json 的内容、任务目录(Dockerfile 所在,给 runner 建镜像)、基线目录、TASK.md 全文。
- `loadTask(taskDir)`:读任务目录,检查 task.json,交回 `Task`;"不存"清单在 `task.facts.unversionedNames` 里,flow 存版本时把它交给 workspace。
- `metricDefinitions(task)`:最终指标和常规辅助指标的定义(墙钟、CPU 秒、峰值内存、最小间隙、原分数、原违反量、扣掉的分);辅助指标的定义写在填圆的代码里。
- `protectedChanges(task, changedFiles)`:改动过的文件里属于"不许改"名单的那些。
**运行**
- `prepareRun(task, candidateDir, runDir, hypothesisMetrics)`:把候选代码、任务的 harness 和这次要测的假设专用指标说明(metrics-spec.json)放进运行目录。
- `formalRun(task, imageDigest, seed)`:正式运行的运行定义;种子由调用方给,同一份研究的全部运行用同一个,经环境变量交给 harness。
- `lightCheck(task, candidateDir, checkDir, imageDigest, seconds)`:把候选代码和检查脚本放进检查目录,交回轻量检查的运行定义;只看语法和入口函数,不执行候选代码,次数上限不归这里管。
- `readLightCheck(task, checkDir, result)`:解读轻量检查:通过,或不通过及原因;检查脚本自己没正常跑完时报错停下。
- `LightCheckOutcome`(类型):轻量检查的结论。
**判分**
- `prepareJudging(task, runDir, judgeDir, imageDigest, formal, hypothesisMetrics)`:判分第一步。正式运行超时、从外面被杀、退出码非 0,或者没有产物,直接交回不算数的判分结果;否则把产物和宿主保管的校验器复制进判分目录,交回校验器的运行定义。
- `judge(task, runDir, judgeDir, formal, validator, hypothesisMetrics)`:判分第二步。读校验器写的判定和候选写的指标文件,加上正式运行的结果,交回 `JudgedResult`:校验器判合法(含合法化后合法)算数;判不合法、或"缺了整次作废"的假设专用指标测不到,不算数。合法化的三个数既进 `extra`,也是常规辅助指标的取值;测不到的指标记测不到并附原因。校验器自己没正常跑完、判定文件认不出、修后分数高于原分数时报错停下,不当成候选的失败。
- `JudgingStep`(类型):判分第一步的结果,已判定或要跑校验器。
### workspace
下面是 `src/workspace/index.ts` 导出的全部内容。一份研究一个裸仓库,位置由调用方给;版本号是目录树编号(tree id),不造提交,每个版本挂一个 `refs/versions/<版本号>` 引用防 `git gc` 清理。"上一版是谁""现在的基线是哪个版本"都由 flow 记进账本,git 里不记。只用 git 的底层命令,每个 git 子进程都不读系统和用户的配置与 attributes 文件,`GIT_DIR` 只放进子进程的环境。git 命令失败直接报错,写明命令和 git 的报错原文,不重试。
- `createRepository(repo)`:在给定位置建一个空的裸仓库。
- `saveVersion(repo, dir, excludedNames)`:把目录现在的内容存成一个版本,交回版本号;自己遍历目录列文件(不用 `git add`,`.gitignore` 不起作用),名字在清单里的文件或目录在任何一层都跳过,空目录不进版本,可执行位记成 100755、其余 100644。遇到符号链接、设备文件这类既不是普通文件也不是目录的东西,交回"存不了"和那个路径。同样的内容永远得到同一个版本号。
- `checkoutVersion(repo, version, dir)`:把一个版本的文件原样写进调用方建好的空目录,还原可执行位;目录里没有 `.git`。
- `compareVersions(repo, from, to)`:比较两个版本,交回改动过的文件(路径;新增、删除还是修改;是否只改了可执行位;是否二进制;非二进制文件每一块改动在新旧两边的行号范围),以及给模型看的带 3 行上下文的 `git diff` 原文,不加颜色。
- `SaveOutcome`(类型):存好了和版本号,或者存不了、路径和原因。
- `LineRange`、`Hunk`(类型):一块改动在一侧是"第几行到第几行";这一侧没有行时写成"在第几行之后"(0 表示文件开头),同 `git diff -U0` 的写法。
- `FileChange`、`Comparison`(类型):一个改动过的文件,二进制文件不拆块;比较的全部结果。块可以为空:空文件的新增、删除和只改可执行位都没有块,按整个文件改了算。
### materials
下面是 `src/materials/index.ts` 导出的全部内容。谁能看什么每种任务一个函数,对着方法.md 第三部分、4.2、4.3 写,不写通用判定(用户 2026-10-02)。materials 只读账本,不写;任务材料由 flow 整份存进"模型调用开始"记录。依赖只有 ledger、rules、vocab;代码比较结果、运行产物、提示词由 flow 传进来。
- `buildMaterial(req)`:生成一份任务材料:给模型看的各节、可查询的记录、拼好的第一条消息、对号清单、能读的代码版本。研究者的材料按"任务说明 → 你自己的 → 公共材料 → 现在交什么"排;实验员、核验员的按"任务说明 → 分给你的材料 → 现在交什么"排,不给信念本和成绩。同一人重新发起同一任务时,最前面放上一次被退回的原因:按阶段、作者、提交种类、对象找本人最后一条退回记录。"现在交什么"由 vocab `SUBMISSION_SCHEMAS` 的字段说明生成。几种任务的取法:
- 讨论、认领:展示讨论原文时,原文引用的入口编号已经作废的(讨论时整份替换过清单),在它后面就地标"(已作废)",读的人才分得清哪些编号还有效(用户 2026-10-03 同意,任务书 14 第 2.5 节)。前后紧挨字母、数字或"-"的不算,`e-x-1` 不会匹配到 `e-x-12` 里。
- 第二次预测:冻结协议不带答复,"挑刺与答复"从这个候选已公开的最后一份回应或统一里读(回应另给答复正文);留池候选读它冻结那一轮的(用户 2026-10-03 定)。
- 核验:不放 Jev 对这份实现的判断。能走到核验说明 Jev 已判过,Jev 是判别条件,不是给核验员的提示。
- 消融实现:这一格已有公开的消融核验(核验不通过、重做)时,和修复一样从上一次交的这一格代码接着改(用户 2026-10-03 定):工作目录写"上一次交的这一格代码的副本",能读的版本里加上它,完整代码照旧列出作对照;另放"退回意见":最近一次核验里关错了的因子和核验员写的依据,写法同修复的退回意见。
- 指标:冻结之后一律用全名"候选编号.短名":已登记指标清单、对号清单的 `reusableMetrics`、实验员材料的"要实现的指标定义"(候选代码在结果文件里也用全名当键)。提案、回应、统一的对号清单里,已占用的指标编号只有任务的指标、`answer-path` 和 `safeguard-triggers` 两个保留键(用户 2026-10-03 定);已登记的全名带 `.`,新登记的短名不带,不用列进来。
- `Board`(类):materials 读账本的那一份,构造参数是账本记录数组。flow 的"下一步做什么"是只拿得到记录数组的纯函数,它和 flow 的三个程序步骤都经这一份读账本,不另写一份(任务书 14 第 2.4 节)。flow 用到的:`stages`、`stage`(阶段的轮次、名单、关没关)、`published`(已公开的某种提交,最后一版为准)、`decision`(某轮某种决定)、`proposal`、`proposalsOf`、`authorsOf`(候选合并了哪些原提案、作者按编号排)、`answerOf`(冻结出候选的最后一份回应或统一)、`protocol`、`protocols`、`carried`(留池候选)、`baselineOf`(第 r 轮的本批基线)、`baselineScore`、`runStates`。任务书 14 写的是导出 `baselineOf`、`baselineScore` 两个函数,实际导出了整个类,原因是纯函数只拿得到记录数组,要用的远不止这两样(这一处偏离待用户确认)。
- `protocolMetrics(board, protocol)`:把冻结协议自定义及引用的指标合成原登记定义,实验员材料与 flow 问 Jev、正式运行共用(来源:任务书 16 第 2.2、2.4 节)。
- `finalEntries(ledger)`:按账本取的最终有效的入口(各人最后一版入口清单里的每个入口,带作者和正文);flow 分配入口时用,和认领的对号清单是同一份(任务书 12 第三部分)。
- `queryMaterial(material, id)`:只在这份材料的记录里按编号找,交回全文;找不到交回 null(agents 的查询回调据此答"看不到")。
- `readPrompts(dir)`:读 `roles/<角色>.md` 和 `tasks/<提交种类>.md`,交回文字和每个文件原始字节的 sha256;研究开始时 flow 把哈希冻结进研究配置记录(方法.md 4.1)。
- `beliefBook(ledger, study, round)`:信念本全文(方法.md 6.10 五条),数字全部用 rules 现算,只写已公开的记录;`round` 是读者所在轮次,导出时写 null。选批历史的每次运行后面原样写出任务的附加记录(判分结果的 `extra`,合法化扣掉的分在里面,字段由任务定)。消融格记"证据不足"时,有运行的原因取判分,没有运行的(核验不通过、重做次数用完)原因取这一格的 `CELL_NOT_RUN` 事件。
- `TaskMaterial`、`MaterialSection`、`MaterialRecord`、`ReadableVersion`(类型):任务材料,以及其中的一节、一条可查询记录(编号、标题、全文)、一个能读的代码版本(版本号、说明、取出目录)。
- `TaskRequest`、`StudyFacts`、`CodeChanges`、`Prompts`、`PromptFile`(类型):flow 传进来的东西,全部必填;`CodeChanges` 与 workspace 的比较结果同形(照 jev `codeHunks` 的先例,不引用 workspace)。
- 账本里只存成 Json 的内容的形状(用户 2026-10-03 决定 2:materials 定义,flow 照着写,ledger 不改):`StudyRecord`(研究配置记录:配置、步骤清单、提示词每个文件的哈希、镜像编号、任务事实、种子;配置和任务事实存 Json,flow 读回时用 `checkConfig`、`checkTaskFacts` 再检查,任务书 12 第三部分)、`FrozenProtocol`(冻结协议:方法.md 4.3 ⑥ 列的因子、保障、指标、正文、实现条款,加轮次、出发的基线、合并了哪些原提案;不带答复,主体由 submissions 的 `protocolBody` 拼,指标编号是全名)、`LocationTable`(代码记录的位置表)、`DecisionKind`、`DECISION_KINDS`、`DecisionShapes`(六种决定:入口分配、判重合并、选批、采纳、选格、押的因子,输入输出用 rules 的类型;没采纳时也写一条采纳决定)、`DedupOutput`(判重合并的输出)、`Settlement`(结算)。
- `freezeProtocol(body, candidate, round, baseline, proposals)`:flow 写冻结协议时调用。输入 submissions 交回的冻结协议主体、候选编号、轮次、基线、合并了哪些原提案,交回 `FrozenProtocol`;新登记的假设专用指标改用全名"候选编号.短名"(例如 `r2-c1.restart-gain`),引用的本来就是全名,照抄。加前缀只在这一处:同一轮的研究者互相看不到提案,会起同一个短名、定义却不同,候选编号判重合并之后才有,所以只能在冻结时加(用户 2026-10-03 定)。
- `experimenterId(candidate)`、`verifierId(candidate)`:每个候选固定一位实验员、一位核验员,修复及再次核验沿用编号(来源:任务书 16 第 2.4 节)。
- `FORWARD_NOT_RUN`、`forwardNotRunEvent(候选, 原因)`:正向候选修复用完仍未通过核验时的“不跑”事件,`objects` 只有该候选;不扣次(来源:任务书 16 第 2.2 节)。
- 事件:`CELL_NOT_RUN`、`cellNotRunEvent(格, 原因)`:一格消融核验不通过、重做次数用完而不跑时,flow 用它写事件记录(`objects` 只有这一格),结算记证据不足(方法.md 4.3 ⑯、6.6)。
- 编号规则:`ORIGINAL_BASELINE`、`researcherId`(第 i 位研究者,从 0 数,编号 `researcher-<i+1>`,任务书 12 第三部分)、`entryId`、`proposalId`、`critiqueId`、`candidateId`、`factorId`、`cellId`、`splitCell`、`explanationId`、`callId`,以及协议内的局部编号 `factorKey`(p1…)、`clauseKey`(c1…)。入口编号按作者一直往后编,讨论时整份替换也不复用旧编号。
### agents
下面是 `src/agents/index.ts` 导出的全部内容。agents 是程序里唯一直接用 Pi 的模块:开一个子会话,按角色发工具,把模型的查询、提交、笔记、轻量检查和每次回复的用量转给调用方的回调,等会话结束交回结局。它不碰账本,不决定给模型看什么,也不判断交上来的东西好不好。Pi 的设置不读用户和工作目录里的 settings.json,用到的几项(重试、压缩上下文、HTTP 超时)写在 `session.ts` 里;模型服务配置照常用本机 Pi 的 models.json 和 auth.json。
- `runSession(task)`:跑一次会话,交回结局。建好会话后先核对实际装了什么(模型和思考档位没被换、没有扩展、技能、提示模板和上下文文件、系统提示词只多了 Pi 自己加的工作目录一行、工具表正好是该给的那几个且是给的实现),不通过就一个请求都不发,交回出错;回调抛错按调用方的内部错误处理,会话立即停下,错误原样抛出。
- `SessionTask`(类型):跑一次会话的输入,按角色区分,全部必填:模型、角色提示词和第一条消息(materials 拼好的文字)、工作目录、会话文件目录、提交种类、查询、提交、用量三个回调和"停下"信号;研究者另有能读的目录和写笔记,核验员另有能读的目录,实验员另有轻量检查;研究者交上一份被收下后会话不结束,实验员、核验员交上就结束。
- `RoleModel`、`ThinkingLevel`(类型):provider、模型名和思考档位,档位必须写明,名单与 config 的 `THINKING_LEVELS` 相同。
- `KindOf<R>`(类型):某个角色交的提交种类,取自 vocab 的 `SUBMISSION_KINDS`。
- `SubmitAttempt<K>`(类型):交给提交回调的一次提交:形状合格的内容和"替换哪一份"(只有研究者的提交能替换),或形状不合格的原样参数和原因(参数原文不是合法 JSON、必填字段交了 null、Pi 的形状校验不通过),后者回调只需记一条退回。
- `SubmitReceipt`(类型):提交回调的回答,收下和编号,或退回和原因,agents 原样回给模型;收下时另回显收到的字数和结尾。
- `SessionEnding`、`SessionOutcome`(类型):交了、没交(附原因,例如最后一条回复被输出上限截断)、出错(附原因)、被停下四种,都带墙钟时间(秒)和会话文件路径(Pi 没写出会话文件时为 null)。
### jev
下面是 `src/jev/index.ts` 导出的全部内容。九问的问法和阈值照参考项目,只改了说法和字段名;阈值没有在新项目的材料上校准过,等真实研究的数据(任务书 08 第三部分)。模型名由调用方从 config 的 `models.jev` 传入,密钥从环境变量 `TYPESAFE_API_KEY` 读进请求头。调不通时在发请求那一处有限次重试,仍不通就报错;回答读不懂报错并带请求编号。
- `judgeSameChange(model, a, b)`:③ 判重,两条提案的因子清单是不是同一个改法,一对一次请求,交回一条 `JevJudgment`。
- `checkProtocol(model, protocol)`:⑥ 查冻结条款,三问(改动了评测、自相矛盾、太含糊)各一次请求,交回 `JevCheck`。
- `checkHunk(model, hunk, protocol)`:⑨ 查一段代码改动,五问(不固定种子的随机数、没登记就读时钟、算登记指标的方式与定义不符、比登记的停止条件更早结束、测不到的量写成 0)同一份材料一次请求,交回 `JevCheck`。对照的指标取自冻结协议,编号是全名。
- `codeHunks(comparison)`:把 workspace 的比较结果(文件清单和差异原文)切成差异块;没有 `@@` 行的改动(二进制、只改可执行位、空文件增删)不出块,由程序检查。
- `DedupFactor`、`FrozenProtocol`、`ProtocolMetric`、`ProtocolRules`、`CodeHunk`(类型):三个函数收的带类型输入,取自 vocab 的字段定义;材料由 jev 自己拼。`DedupFactor` 是因子的名称、做法和关键设定:问题写着"设定不同就不算同一个改法",所以关键设定要给 Jev 看,判重的材料说明里也用一句话讲了 `keySettings` 是什么。
- `JevCheck`(类型):每一问的判断,以及有没有任何一问判"是"。
### submissions
下面是 `src/submissions/index.ts` 导出的全部内容。submissions 判一份提交收下还是退回:先做程序检查(vocab 字段定义管不到的跨字段规则,问题一次列全),有一项不过就退回、不问 Jev;全过了再问 Jev,几问问完、有一问判"是"就退回。它不读也不写账本,只依赖 jev 和 vocab;要 workspace、tasks、materials 算的东西由 flow 算好传进来。Jev 调不通、回答读不懂时 jev 报错,这里不接住,flow 按内部错误停下。
- `receive(incoming)`:判一份提交,交回 `Verdict`。回应、统一先拼好冻结协议主体,程序检查、问 Jev、交回都用这同一份;回应、统一问冻结条款三问,实现、修复对每个差异块问代码改动五问,其余种类不问 Jev。Jev 退回的原因只列判"是"的几问,用 jev 的英文问题名(用户 2026-10-03 定),差异块附原文。
- `protocolBody(from)`:拼冻结协议主体,只在这一处拼。回应:因子、保障、指标、正文四项,`changes` 写了哪项就整项换新,没写的用原提案的,实现条款取回应自己的;统一:直接用统一提交自己的字段。答复不进协议(用户 2026-10-03 定)。
- `verificationOutcome(v)`:一份已收下的核验通过没有:任何条款、因子、保障没落实,或任何没有归属的改动判为越界,就不通过;四类分开列(方法.md 4.3 ⑨)。
- `ablationOutcome(v, off, alsoOff)`:一份已收下的消融核验通过没有:要关的因子和实验员报的一起关的因子都写了"关着"、其余都写了"照留"才通过;分开列该关却照留的和该留却关了的(方法.md 4.3 ⑯)。
- `Incoming`(类型):交给 `receive` 的一份提交,按提交种类区分:内容、这次任务的对号清单、提交之后才知道的东西(`KnownOf`)、Jev 的模型名。
- `KnownOf<K>`(类型):提交之后才知道的东西,没有的种类为 null:提案是本人本阶段已收下几条,回应是作者那条原提案,实现和修复是 `ForwardCode`,消融实现是 `AblationCode`(交了"跑不起来"时为 null)。
- `ForwardCode`(类型):实现、修复的代码:存不成版本时带路径和原因;存成了带改了的不许改的文件、和本批基线比出的差异块、给 Jev 对照的协议条款。
- `AblationCode`(类型):消融实现的代码:存不成版本时带路径和原因;存成了只带改了的不许改的文件。
- `OriginalProposal`(类型):回应拼协议时叠在下面的原提案:因子、保障、指标、正文,作者自评不进协议。
- `ProtocolBody`(类型):冻结协议的主体,即方法.md 4.3 ⑥ 列的因子、保障、指标、正文、实现条款;flow 用 materials 的 `freezeProtocol` 补上轮次、基线、合并了哪些原提案,写成 `FrozenProtocol`。
- `Verdict`(类型):判的结论:收下(带用到的全部 Jev 判断,没问 Jev 为 null;回应、统一另带拼好的协议主体,其余为 null),或退回(带原因和用到的 Jev 判断)。
### flow
下面是 `src/flow/index.ts` 导出的全部内容,以后 plugin 也只用它。flow 只经各模块的 `index.ts` 用它们;正式代码里没有为检验写的参数、环境变量、回放模式或假实现(CLAUDE.md"测试")。这一节对应 flow 第 1–4 部分(任务书 12、14、16、17):骨架、研究开始、第零轮、第 r 轮从开轮到收尾、一轮接一轮直到余额用完;余额为 0 时走到 `export` 停下,原因"导出还没写"(导出还没写)。轮次按"第 r 轮"写,不写死轮数。
- `runStudy(input)`:推进一份研究,直到停下。输入三样,都必填:研究目录;配置(新研究交 `checkConfig` 检查过的配置,接着跑一份已有的研究写 null,以账本里冻结的那份为准;两者对不上就报错);"停下"信号。每次启动走同一条路:打开账本;新研究先冻结配置,本机已有 `inforsi-<研究编号>` 的镜像就说明研究编号用过了,报错停下,要换一个编号(编号重复的新研究会把旧研究遗留的同名容器当成自己的运行);已有研究的,核对冻结的配置、镜像还在、提示词哈希没变(开发入口开着时提示词可以变);给开始了没结束的模型调用补写结束(出错、"进程中断"、墙钟时间测不到);按容器名问 Docker 核对登记了没结果的正式运行(没启动的写"没启动"退还名额,启动了的补写确认启动、等它、判分、写结果,问不到的写"状态未知"事件并停下);然后进主循环:读账本 → 问"下一步做什么" → 做这一步 → 回到开头。主循环里 `docker run` 没启动的,写完"没启动"结果(退还名额、记下 Docker 报错原文)就停下,停下原因带上报错原文,本进程不重跑;下次启动时照旧起新编号重跑。恢复时补写的"没启动"不停下。
- `StudyInput`(类型):上面三样输入。
- `StudyEnding`(类型):两种结局之一:停下(附原因,同时写一条停下事件),或被用户停下(正在跑的模型会话立即停,正在跑的容器不管,下次启动由恢复入口接着等)。现在走得到 `export`:余额用完就停在那里,原因"导出还没写"。
研究目录里放:`ledger.sqlite` 账本;`repo.git` 代码版本的裸仓库;`runs/<运行编号>/` 一次正式运行(取出的代码、运行目录、日志、判分目录、校验器日志);`calls/<调用编号>/` 一次模型调用(工作目录、会话文件、能读的代码版本)。取出代码、建运行目录和判分目录时目标都是新建的空目录。容器名 `inforsi-<研究编号>-<运行编号>`,判分容器另加 `-judge`。同一份研究的全部运行用同一个种子:研究编号经 vocab 的 `hash` 取前 8 位十六进制转成整数,冻结进研究配置记录。
**每一步以哪条记录为准算做完**(任务书 12 第 2.2 节、任务书 14 第 2.2 节、任务书 16 第 2.3 节)。步骤的先后只写在 flow 的"下一步做什么"一个纯函数里;后续每加一步,这张表加一行。做完的轮次整轮跳过,第一个没做完的轮次里第一个没做完的步骤就是下一步。
| 步骤(config 的 `STUDY_STEPS`、`ROUND_STEPS`) | 看到哪条记录算做完 |
| --- | --- |
| `freezeConfig` 冻结配置 | 研究配置记录(`config`) |
| `baselineRun` 基线 | 基线代码记录(`code`,对象是 materials 的 `ORIGINAL_BASELINE`),而且最近一次基线运行的结果不是"没启动";"没启动"的下次启动时起新编号(`base-2`……)重跑,判为不算数的停下 |
| `entryDraft`、`discussion1`、`discussion2`、`claim` | 这个阶段的关闭记录(`stageClose`);阶段编号 `r0-<步骤>` |
| `assignEntries` 分配 | 第 0 轮的入口分配决定(`decision`,种类 `entries`) |
| `openRound`、`carryOverPool` | 不写记录:本轮名单和提案上限就是冻结的配置(方法.md 4.3 ①),留池候选由 materials 从记录算出(`Board.carried`) |
| `proposal`、`firstPrediction`、`critique`、`response`、`secondPrediction` | 这一轮这个阶段的关闭记录(`stageClose`);阶段编号 `r<轮>-<阶段>` |
| `dedupe` 判重合并 | 本轮的判重合并决定(`decision`,种类 `dedup`) |
| `freezeProtocol` 冻结协议 | 本轮每个新候选都有冻结协议记录(`protocol`);全部候选在同一个事务里写 |
| `selectBatch` 选批 | 本轮的选批决定(`decision`,种类 `batch`) |
| `implementation` | 这一轮实现阶段的关闭记录 |
| `verification`、`repair` | 每个入选候选最近一次核验通过,或修复次数用完且有“不跑”事件;中间每次核验、修复阶段各有开关记录 |
| `forwardRun` | 每个通过核验的候选最近一次正向运行有结果,而且不是“没启动” |
| `publish`、`informationAccount` | 不写记录:`forwardRun` 做完就算做完;公开靠本轮采纳决定,信息账从预测和结果现算 |
| `adopt` | 本轮的采纳决定(`decision`,种类 `adopt`),没有过线者也写,`adopted` 为 null |
| `listCells` | 不写记录:采纳了而且被采纳改法有 2 个以上因子才走 ⑬–⑰,格从采纳决定和冻结协议现算;否则这几步什么都不写,直接到收尾(方法.md 4.3 ⑫) |
| `explanation` | 这一轮解释阶段的关闭记录(阶段编号 `r<轮>-explanation`);本轮没有消融时不开这个阶段 |
| `selectCells` | 本轮的选格决定(`decision`,种类 `cells`);没选中的格不另写事件,原因和"含它的最好组合"都在这条决定里 |
| `ablationImplementation`、`ablationVerification` | 每个选中的格都已结束这一段:交了"跑不起来",或最近一次核验通过,或重做次数用完、有"不跑"事件。阶段编号 `r<轮>-ablationImplementation-1`、`r<轮>-ablationVerification-1`、`-2`……第几次从账本数,不另存 |
| `ablationRun` | 每个通过核验的格,最近一次消融运行有结果,而且不是"没启动" |
| `settle` | 本轮的结算记录(`settlement`,按轮次唯一),以及同一事务里的押的因子决定(`decision`,种类 `bet`) |
| `wrapUp` | 收尾阶段的关闭记录,以及本轮选中的每个候选都有解封记录(`unseal`) |
| 一轮接一轮 | 每轮开头用 `runBalance` 从运行记录现算余额;余额为 0 不再开新轮 |
| `export` | 现在不做:走到这里停下,原因"导出还没写" |
| 按配置跳过的步骤 | 一条"按配置跳过"事件(`skippedByConfig`),写明步骤(第 r 轮的步骤写 `r<轮>-<步骤>`)和 config 给的原因 |
研究者阶段没开就开,名单是全部研究者(本轮名单就是冻结的配置);每个任务都有被收下的提交之后才关。
**每个阶段谁做、对哪个对象**(任务书 14 第 2.3 节、任务书 16 第 2.2、2.4 节)。一个任务就是"一个人对一个对象",交一种提交。同一阶段里,一个人的几个任务按下表列出的先后在一个接一个的会话里跑,不同的人同时跑(任务书 12 第 2.6 节)。一个任务做完,以"这个人在这个阶段对这个对象有一份被收下的提交"为准。
| 阶段 | 任务 | 对象 |
| --- | --- | --- |
| 第零轮的写入口、两次讨论、认领 | 每位研究者一个任务 | 无 |
| 提案 | 每位研究者一个任务,一个会话里交 1 到 M 条 | 无 |
| 第一次预测 | 每位研究者 × 本轮每个新候选(含自己的;留池候选不做,方法.md 4.3 ④) | 候选 |
| 挑刺 | 每位研究者 × 本轮每条不是自己写的原提案(监督者定,照参考项目:方法没写谁挑谁) | 原提案 |
| 回应与统一 | 每个新候选一个任务:只有一条原提案的,由作者交"回应";几条合成的,由其中研究者编号最小的作者交"统一"(方法.md 4.3 ③⑥) | 候选 |
| 第二次预测 | 每位研究者 × 候选池里每个候选(新候选加留池候选,方法.md 4.3 ①⑦) | 候选 |
| 实现 | 每个入选候选一个实验员,从本批基线取工作目录,不同候选同时实现 | 候选 |
| 核验 | 每个待核验候选一个核验员;首次核验本批全部,之后只核验刚修复的候选 | 候选 |
| 修复 | 只给核验不通过的候选,交原实验员,从上次提交的代码接着改 | 候选 |
| 解释 | 每位研究者一个任务 | 无 |
| 消融实现 | 每个选中的格一个实验员;第一次从被采纳的完整代码取工作目录,重做时从上一次交的这一格代码接着改;不同格同时实现 | 消融格 |
| 消融核验 | 每个交了代码的格一个核验员;首次核验全部,之后只核验刚重做的格 | 消融格 |
| 收尾 | 每位研究者一个任务 | 无 |
没做完的任务派不派,按这个任务最近一次调用怎么结束的来定(任务书 12 第 2.3 节;第 2 部分从"一个人"细到"一个人对一个对象",任务书 14 第 2.3 节)。认是哪个任务,看"模型调用开始"里存的材料带的对象。没有调用、被用户停下、出错(含"进程中断")都派;没交的,开发入口关着就停下(交不出合格提交按内部错误,方法.md 4.3 ②),开着就再派。能派的先全派。一次派发里有任务没交上(没交或出错),不再开新的会话,已经在跑的不打断,等这次派发的会话全部结束后停下。
一个任务交上来之后才知道的东西(submissions 的 `known`,任务书 14 第 2.3 节):提案是本人在本阶段已经收下几条(替换时不算被替换的那份);回应是作者那条原提案;实现、修复交代码快照结果(见下文);其余种类没有。
提案和挑刺一个任务可以交几份,每份一个编号:提案用 materials 的 `proposalId`(轮次、作者、第几份),挑刺用 `critiqueId`(目标原提案、作者、第几份),收下时由 flow 按这个任务已收下几份往后分配,回给模型的提交编号就是它(任务书 14 第 2.3 节)。只交一份的种类,提交编号是"阶段.本人.种类.对象"。不写 `replaces` 是交新的一份,只交一份的种类已经交过就退回;写了 `replaces`,必须是这个任务已收下的某一份,整份替换它。
**第 r 轮里程序自己做的三步**(任务书 14 第 2.4 节,方法.md 4.3 ③⑥⑧)。每步写一条记录(冻结协议一个事务);写下之前被打断,下次启动从账本重新算一遍;写下之后恢复时照原样用,不重算。
- 判重合并(③):
- 本轮已公开的新提案两两问一次 Jev(jev 的 `judgeSameChange`,看两边的因子),每条新提案再和每个留池候选问一次(留池候选看它冻结协议里的因子)。按顺序发,全部问完才写决定;中途被打断就整轮重问(判重不扣运行次数)。
- 判为相同的连通成组:A 和 B 相同、B 和 C 相同,A、B、C 就是一个候选(监督者定,方法没写这种情况)。
- 一组里有留池候选,整组并进那个留池候选(方法.md 4.3 ③)。一组同时连到两个留池候选,按内部错误停下:真实运行里没出现过。
- 其余每组是一个新候选,按组里编号最小的原提案排序,编号用 materials 的 `candidateId`。
- 写一条决定:种类 `dedup`;输入是每一对的 Jev 判断,原样存;输出是 materials 的 `DedupOutput`(每个新候选合并了哪几条原提案,每个留池候选并进了哪几条,没有就是空数组);规则名 `jevSameChange`、版本 1,参数写 Jev 模型名,种子 `null`。
- 冻结协议(⑥ 之后):
- 只在回应阶段关闭之后写:materials 把冻结协议记录的出现当作"已公开",提前写会泄露(任务书 09 审阅时记下的"给 flow"条目)。
- 对每个新候选:取它已公开的最后一份回应或统一,用 submissions 的 `protocolBody` 拼出协议主体(回应另带作者那条原提案),用 materials 的 `freezeProtocol` 补上候选编号、轮次、本批基线(`Board.baselineOf`)、合并了哪些原提案。本轮全部新候选在同一个事务里写全。
- 留池候选沿用原来的冻结协议(方法.md 4.3 ①),不重写。
- 配置关掉回应一步时怎么冻结还没写(`protocolBody` 只收回应和统一),走到这一步就停下。
- 选批(⑧,方法.md 6.4):
- 输入照 rules 的 `BatchInput` 拼:候选池里每个候选、本轮名单上各人最后一份第二次预测的三个数;本批基线分数(`Board.baselineScore`);方向、ε 取自任务事实,λ(`flow.openReserve`)、每批上限取自冻结的配置;余额用 rules 的 `runBalance` 从运行记录算。
- 种子用 rules 的 `decisionSeed`,由研究编号、轮次、版本算。同一轮里要随机的决定各用一个版本,选批用 1,选格预留 2,两次抽取不会用同一串随机数(方法.md 第九部分;选格由第 4 部分接上)。
- 规则取步骤清单里 `selectBatch` 那一步的,调 `runBatchRule`。写一条决定:种类 `batch`,规则名、版本和参数取 `runBatchRule` 交回的。
**实现、核验、修复与采纳**(来源:任务书 16 第 2.2–2.4 节):
- 阶段编号:`r<轮>-implementation`、`r<轮>-verification-1`、`r<轮>-repair-1`、`r<轮>-verification-2`……第几次从账本数,不另存。实现阶段关后才核验,核验阶段关后才派修复。修复次数用完仍不通过,用 `forwardNotRunEvent` 写清原因,不登记运行、不扣次。
- 角色各取配置中的模型及自己的角色提示词。实验员工作目录先取版本,提交时用 workspace 存版本;相对本批基线比较、用 `codeHunks` 切块、查 `protectedChanges`,拼成 submissions 的 `known.code`。收下的提交和代码版本、位置表在同一事务写入;指标引用用 `protocolMetrics` 换成原登记定义。
- 核验及修复材料的 `changes` 都相对本批基线:修复看基线到上次提交,修后核验看基线到修复结果;不新增上次到此次的材料。核验通过与否用 `verificationOutcome` 判。
- 实验员的轻量检查接 `SessionTask.lightCheck`:tasks 给定义、runner 执行、tasks 读结果;每次模型调用分别按 `lightChecks.maxCount` 计次、按 `seconds` 限时,会话保留记录,不记入正式运行账本。
- 通过核验的候选用既有 `formalRun`,用途 `forward`;先登记占名额,确认启动才扣次,再独立判分记结果。**一批内一个接一个跑**,避免抢 CPU 改变按秒停下的程序成绩。没启动的处理同基线:本进程停下,下次启动起新编号。
- 本批全部有结果或“不跑”事件后,用 rules 的 `adopt`(内部用 `adoptionLine`),只传有效实测分数、本批基线分数和任务的方向、ε、已知最好;写种类 `adopt` 的决定,即使 `adopted` 为 null 也写。materials 据此公开本轮正向运行。
**消融、收尾与研究结束**(来源:任务书 17 第 2.2、2.3 节,方法.md 4.3 ⑬–⑱、4.4、第七部分):
- 采纳了而且被采纳改法有 2 个以上因子才走消融;否则 ⑬–⑰ 不写记录(从采纳决定和冻结协议算得出),直接到收尾。
- 选格:输入照 rules 的 `CellRuleInput` 拼,余额用 `runBalance` 从运行记录现算(选批同样用当时的余额),种子用 `decisionSeed` 版本 2(选批已用版本 1);规则取步骤清单里 `selectCells` 那一步的,调 `runCellRule`;写一条种类 `cells` 的决定。没选中的格不另写事件。
- 消融实现交了 `cannotRun`:`known.code` 传 null,这一格不核验、不运行、不扣次。实验员报了 `alsoOff` 时用 rules 的 `sharedSettings`:实际关掉的因子集合完全相同的格沿用第一格的结果,不重跑、不重复扣次。核验不通过退回原实验员重做,次数和正向同一个 `maxRepairs`;用完仍不通过用 `cellNotRunEvent` 写事件,结算记“证据不足”。
- 消融运行复用正向的同一个正式运行入口,用途 `ablation`、对象是格,一个接一个跑;恢复路径与正向相同,消融格的假设专用指标缺失一律只记缺失(方法.md 第五部分)。完整代码和基线的分数取本批已有结果,不重跑。
- 结算:选中的格全部有结果、“跑不起来”或“不跑”事件后,照 `SettleInput` 调 `settleAblation` 写结算记录,同一事务里用 `chooseBet` 写种类 `bet` 的决定。“消融中发现”在 `settleAblation` 的输出里,materials 的 `Board.baselineOf` 据此把那一格的代码当成下一轮的基线,flow 不另写采纳记录。
- 收尾阶段关闭之后,本轮选批选中的每个候选(跑过的、入选了没跑成的)在同一个事务里逐个写解封记录,取最后一版提案的封存;提前写会泄露(materials 把解封记录当作“已公开”)。
- 收尾做完后余额 ≥1 开下一轮;余额为 0 走到 `export` 停下,原因“导出还没写”。
- 检验时几份研究可以同时跑(任务书 17,用户 2026-10-04 同意),一份研究内的正式运行仍然一个接一个跑;同机并跑用每次运行的 CPU 时间与墙钟时间之比核对没有互相抢(任务书 16 的“启动区间互不重叠”要求只适用于那次检验)。
检验命令(`eval/`,真实运行,要 Docker;带 `*` 的会花模型或 Jev 的钱。输出都在仓库外 `/home/iomgaa/inforsi-eval/<日期-名字>/`,名字也当研究编号用,只能小写、不能重复):
| 命令 | 用途 |
| --- | --- |
| `npm run eval:study -- --config <文件> --dir <目录>`、`-- --resume --dir <目录>` * | 起一份研究或接着跑,Ctrl-C 发停下信号 |
| `npm run eval:r0:run -- <名字>` * | 用 `eval/round-zero/research.json` 真实跑一次第零轮 |
| `npm run eval:r0:quality -- <检验目录>` * | 对第零轮产出问六问(任务书 12 第 5.2 节),原始概率写进 `<检验目录>/quality.json` |
| `npm run eval:r0:extract` | 从参考项目 16-real-1 的账本重新提取第零轮提交,生成回放数据 |
| `npm run eval:r0:replay -- <名字>` | 回放 16-real-1 的第零轮,比对分配结果 |
| `npm run eval:r0:kill -- baseline\|drafting\|stageFull` | 在三处之一杀控制进程、重启跑完,逐条检查(用回放,不花钱) |
| `npm run eval:r0:concurrent` | 两份研究同时跑到基线结束,检查各扣各的、容器不撞名 |
| `npm run eval:r1:run -- <名字>` * | 用 `eval/round-zero/research.json` 从头真实跑一份研究,到第 1 轮选批后停下(任务书 14 第 3.1 节);另写 `timeline.jsonl`(每条记录第一次被看到的时刻)和 `outcome.json` |
| `npm run eval:r1:kill -- <名字>` * | 同上,在第 1 轮第一次预测阶段中途(已有人交、还有人在写)杀控制进程,用恢复入口跑完,查四条:被杀那次调用补了结束记录;交过的任务没被重派、没交的被重派;没有重复收下的提交;阶段照常关闭、走到选批后停下。结果写进 `kill-report.json` |
| `npm run eval:r1:export -- <检验名字> <来源>...` | 把几次运行的第 1 轮导出成同一种去掉来历的纯文本(任务书 14 第 3.2 节第 1 步);来源是我们的研究目录或参考项目的 `board.sqlite`(先复制出来再读) |
| `npm run eval:r1:blind -- <检验目录>` | 把导出的文本随机编成"运行 A、B……"放进 `blind/`,对照表另存在外面的 `blind-key.json` |
| `npm run eval:r1:jevcheck -- <检验目录>` * | 对每次导出的运行问 Jev 三类问题(任务书 14 第 3.2 节第 3 步):判重对不对、挑刺有没有被回应、提案对应研究路径的哪一步;原始概率写进 `jevcheck.json` |
| `npm run eval:r1:stats -- <检验目录>...` | 第 1 轮的数字和过程(任务书 14 第 3.2 节第 4 步),另查阶段开关成对、冻结协议写在回应阶段关闭之后、停下原因是"实现还没写";每个目录写一份 `stats.json` |
任务书 16 新增检验命令(来源:任务书 16 第三、四、五部分):
| 命令 | 用途 |
| --- | --- |
| `npm run eval:forward:run -- new <研究编号> <全新绝对输出目录>` * | 配置照 `eval/round-zero/research.json`,只换编号;一份研究跑到采纳后停下,记录进程起止、账本观察时间及机器快照;异常保留现场并停下 |
| `npm run eval:forward:run -- kill <研究编号> <全新绝对输出目录>` * | 同上;首个正向 `runStarted` 后、无结果且容器仍运行时杀控制进程,保存容器 ID 和启动时间,恢复同份一次,不另开研究 |
| `npm run eval:forward:run -- resume <研究编号> <原输出目录>` * | 查明服务错误后手动续跑同份,追加进程/观察记录;也可用 `eval:study -- --resume --dir <输出目录>/study` |
| `npm run eval:forward:export -- <研究目录> <全新绝对审阅目录>` | 候选协议全文、引用指标定义、基线/最终/各次代码、位置表及 concerns、每次核验/修复、退回与调用材料、会话位置 |
| `npm run eval:forward:stats -- <全新绝对统计目录> <参考副本根目录> <检验目录>...` | 五项表中的可计算字段:预测与实测、参考首轮成绩、核验/修复次数、账目、轻量检查、杀进程三项检查;不自动作质量判断 |
| `node eval/forward/acceptance.ts <统计输出目录>` | 在三份研究统计后单列总体程序验收:须为三份不同研究、各自走完并通过账目检查、至少一份杀进程恢复三项成立、启动区间完整且互不重叠;写 `acceptance.json`,不通过时退出码非零,不替代质量审阅 |
总体程序验收入口的来源:任务书 16 第 3.1、3.3、五部分;检验缺陷修复 `de8e13b` 把逐份统计与三份总体验收分开,避免单份通过冒充总体通过。恢复证据同时读取 `container-events.jsonl` 和 `container-events-window.json`,核对杀前到结果落账的容器生命周期;仅凭恢复前快照不能证明恢复后没有重跑。
三份研究须逐个启动,上一份结束并核查完再起下一份,同机不与别的研究并跑;进程起止见 `launches.jsonl`,机器观察见 `machine.jsonl`,杀进程前后见 `container-*.json`,扣次与后续结果见统计的 `kill`。快照只是观察证据,不声称追踪任意进程。结果放仓库外,不提交。独立审阅按候选分两步:先评实现忠实度,再给历史核验及其当时代码评误判和修复范围。路线分类、完整路线的实测减合并 q5、A–J 表由阅读后填写;复用 `eval/route/` 的导出、条目、思考、分位数核查及 Jev 四问,不另造问卷或判别器。参考项目五份账本用已有只读副本,保留无效数值和重复尝试,并注明参考提示词含题目做法。
任务书 17 新增检验命令(来源:任务书 17 第三、四部分;三份研究同时跑,不适用任务书 16 的“启动区间互不重叠”):
| 命令 | 用途 |
| --- | --- |
| `npm run eval:full:run -- <研究编号> <全新绝对输出目录> [--kill-after-first-ablation-start]` * | 配置照 `eval/round-zero/research.json` 只换编号;从头真实跑一份完整研究到余额用完、停在“导出还没写”;照任务书 16 惯例记 `launches.jsonl`、`machine.jsonl`、`timeline.jsonl`、`outcome.json`。带 kill 开关的,在第一个消融运行确认启动后、结果落账前杀控制进程,用恢复入口续跑到底,`kill-report.json` 写三条检查:同一容器接着等不重跑、只扣一次、结果照常写入且结算与后续走完 |
| `npm run eval:full:stats -- <全新绝对统计目录> <研究输出目录>...` | 每份研究出 stats.json/stats.txt:逐轮走向、程序账目十二项检查(阶段开关成对、扣次等于确认启动数、余额为 0、解释冻结早于首格启动、每轮最多一条结算、解封在收尾关闭之后且覆盖全部选中候选、跑不起来/不跑的格零扣次、第 2 轮起基线链、轻量检查计次、CPU/墙钟比等)、kill 三条检查;只算事实,不作质量判断 |
| `npm run eval:full:export -- <研究输出目录> <全新绝对审阅目录>` | 按轮导出提案、解释、选格决定、结算与押注、收尾(json+txt),每格一包(冻结协议、位置表、完整代码与格代码、diff、实现与核验往来及退回、调用材料),供全新上下文的子 Agent 评研究质量 |