97a43591a3
- npm 包、TypeScript、node --test、dependency-cruiser;结构.md 第六部分的检查规则一次写全,临时违规实测均报错。 - vocab:基础类型与编号格式、规范序列化与哈希、角色/阶段/提交名单、指标与运行定义及结果、模型用量、16 种提交的 TypeBox 字段定义。 - 测试:规范序列化预期串手写,哈希预期值用 sha256sum 算出;每种提交一份合法样例。 - 结构.md 第八部分补 vocab 对外内容;WORK_LOG 记下用户决定。 用户决定(2026-10-01):"同意 A,vocab 上限改为 1100 行";"同意 A:解释的 cells 去掉 minItems,改为可选";"3、4、5、6:同意保留你的写法";答案路径与保障动手次数"tasks 只记读到的原始内容",对表由以后有位置表的模块现算。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
246 lines
16 KiB
Markdown
246 lines
16 KiB
Markdown
# InfoRSI 代码结构
|
||
|
||
这份文件说明代码分成哪些模块、模块之间怎么依赖、流程按什么步骤推进、账本记哪些记录。研究方法本身以 `docs/方法.md` 为准;这里只说代码怎么组织来实现它。
|
||
|
||
来源:
|
||
- 用户 2026-10-01 认可"按负责一件事来切"、下面这套模块与可替换点,以及步骤清单和账本记录种类。
|
||
- 用户 2026-10-01 同意:提交的字段定义放 vocab,submissions 只判收不收;vocab 可放规范序列化和哈希两个函数,上限调到 600 行。
|
||
- 依据是两次开发调研。一次是模块切分,参考了 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 的字段、默认值、校验;把配置翻译成步骤清单 | 无 |
|
||
| 组织 | materials(材料) | 给模型看什么、什么顺序、怎么写;回答研究者的查询 | ledger、rules |
|
||
| 组织 | submissions(收件) | 一份提交收下还是退回:先程序检查,再问 Jev(字段定义在 vocab) | jev |
|
||
| 外部 | ledger(账本) | 记录怎么存、怎么保证不可改、怎么从记录算出当前状态;谁在什么时候能看到哪些记录;花钱动作的登记 | 无 |
|
||
| 外部 | agents(角色会话) | Pi 的接口:开子会话、发工具和提示词、收提交、记用量 | 无 |
|
||
| 外部 | runner(运行器) | Docker 的用法:限时、限核、取消、记资源、取回产物 | 无 |
|
||
| 外部 | tasks(任务) | 一个任务怎么跑、怎么从原始产物独立算分(填圆的合法化也在这里) | 无 |
|
||
| 外部 | workspace(代码版本) | git 的用法:基线快照、实验员工作目录、冻结候选、代码差异 | 无 |
|
||
| 外部 | jev(Jev 判别) | 问法、阈值、校准 | 无 |
|
||
| 纯计算 | rules(研究规则) | 全部公式和常数;信念本、战绩、余额的计算 | 无 |
|
||
|
||
另有一个 **vocab**(共用词汇),所有模块都可以引用,上限 600 行。一样东西要有两个互相不能引用的模块都用到,才放进 vocab;只有一个模块用的,放在那个模块里。它放:
|
||
|
||
- 共用的类型和名单:角色、研究者阶段、提交种类、指标定义、运行定义与运行结果、模型用量;
|
||
- 每种提交的字段定义(用 TypeBox 写,同一份定义既是类型,也用来检查收到的数据,也是发给模型的提交工具参数);
|
||
- 只有两个函数:规范序列化和哈希,保证"内容相同就得到同一个指纹"只有一种算法。
|
||
|
||
依赖只能往下,同一层之间不互相依赖:
|
||
|
||
```
|
||
入口 plugin
|
||
组织 flow export config
|
||
materials submissions
|
||
外部 ledger agents runner tasks workspace jev
|
||
纯计算 rules vocab(谁都能用)
|
||
```
|
||
|
||
### 几条边界
|
||
|
||
- **能不能看归 ledger,这次给不给看归 materials。** ledger 只交出"截至某一刻、这位角色能看到的快照"。materials 和查询工具都只拿得到快照,拿不到数据库。
|
||
- **收不收一份提交,只在 submissions 一处判。** 跟状态有关的两条由 ledger 在写入时拒绝:这个阶段还开着吗;要替换的那一份是不是本人刚交的。
|
||
- **信念本**:数字由 rules 算,写成文字由 materials 负责,不单独存,每次现算。
|
||
- **runner 只管怎么跑,tasks 只管怎么判。** 两者互不认识,由 flow 串起来:先向 tasks 要运行定义,交给 runner 去跑,再把产物交回 tasks 算分。
|
||
- **"下一步做什么"只写在 flow 的一个纯函数里。** 每个步骤一个小文件。步骤文件里如果出现公式、字段名单或可见性判断,说明知识漏进来了,要挪回下层。
|
||
|
||
---
|
||
|
||
## 三、步骤清单
|
||
|
||
研究开始时,config 根据配置里的开关生成这份研究的步骤清单,连同每个决策点用哪条规则,一起冻结进账本。flow、导出、恢复都只读这份清单,不再读开关。被关掉的步骤不执行,账本里记一条"按配置跳过"。
|
||
|
||
**整份研究**
|
||
|
||
```
|
||
1. 冻结配置
|
||
2. 基线运行
|
||
3. 第零轮:写入口 → 讨论(1 或 2 次)→ 认领 → 分配 〔开关:第零轮〕
|
||
4. 一轮接一轮,直到余额用完
|
||
5. 导出
|
||
```
|
||
|
||
**每一轮**
|
||
|
||
```
|
||
1. 开轮:放信念本,处理留池候选
|
||
2. 提案
|
||
3. 判重合并
|
||
4. 第一次预测(看原始提案) 〔开关:第一次预测〕
|
||
5. 挑刺 〔开关:挑刺与回应〕
|
||
6. 回应与统一 → 协议冻结 〔开关:挑刺与回应〕
|
||
7. 第二次预测(看冻结协议)
|
||
8. 选批 〔规则:选批规则〕
|
||
9. 对每个入选候选:实现 → 核验 →(不通过:修复 → 再核验)→ 正式运行
|
||
10. 整批公开,记分数账和信息账
|
||
11. 采纳
|
||
12. 若采纳的改法有 2 个以上因子: 〔开关:消融〕
|
||
列格 → 解释 → 选格 → 对每个选中的格:消融实现 → 核验 → 运行 → 结算
|
||
〔规则:选格规则〕
|
||
13. 收尾,更新信念本
|
||
```
|
||
|
||
---
|
||
|
||
## 四、账本记录
|
||
|
||
账本只存发生过的事。信念本、战绩、余额、"现在进行到哪一步"都不存,每次从记录现算。
|
||
|
||
| # | 记录 | 内容 | 谁写入 | 禁止修改 |
|
||
|---|---|---|---|---|
|
||
| 1 | 研究配置 | 冻结的配置、步骤清单、每个决策点用哪条规则、方法参数、提示词哈希 | flow(内容由 config 生成) | 是 |
|
||
| 2 | 阶段 | 每个阶段的开和关、什么时候公开 | flow | 否 |
|
||
| 3 | 提交 | 各种提交的每一版:入口、讨论、志愿、提案、两次预测、挑刺、回应、解释、收尾、实现、核验。阶段关闭后以最后一版为准,此后不能再追加新版 | flow(经 submissions 收下后) | 替换时追加新版;阶段关闭后由 ledger 拒绝再写 |
|
||
| 4 | 冻结协议 | 每个候选的冻结版本:因子清单、保障清单、实现条款、指标登记 | flow | 是 |
|
||
| 5 | 作者自评 | 封存时的三个数;解封时追加一条解封记录 | flow | 是 |
|
||
| 6 | 冻结代码 | 每份候选代码和消融格代码的版本号,以及因子位置表 | flow(版本号由 workspace 给出) | 是 |
|
||
| 7 | 运行 | 每次正式运行三条:登记要跑;确认启动(扣次);结果(分数、合法化、指标、答案路径、资源) | flow(结果由 runner 与 tasks 给出) | 是 |
|
||
| 8 | 决定 | 程序做的每个决定:选批、采纳、选格、押的因子、入口分配、判重合并。每条存规则名、规则版本、方法参数、种子、全部输入、输出 | flow(由 rules 计算) | 是 |
|
||
| 9 | 结算 | 每格的结局、撑几成、配合关系标签、每条解释的确认或否定、世界比特、惊讶 | flow(由 rules 计算) | 是 |
|
||
| 10 | Jev 判别 | 每次问了什么、原始概率、结论 | 提问方(submissions 或 flow) | 否 |
|
||
| 11 | 用量 | 模型调用和整个环境的用量,只记录 | agents、runner | 否 |
|
||
| 12 | 事件 | 违规记录、"按配置跳过"、"没测"及原因、停下的原因 | flow | 否 |
|
||
|
||
**为什么这几种禁止修改**
|
||
- CLAUDE.md 定的核心表是冻结协议、预测、证据、运行与扣次,分别对应第 4 种、第 3 种里的两次预测(阶段关闭后不再接受新版本)、第 7 和第 9 种、第 7 种。
|
||
- 另外四种也禁止修改:研究配置、作者自评、冻结代码、决定。它们被改了以后,事后没人察觉得到:配置要在开始时冻结,作者自评要封存,代码冻结后才能启动运行,决定一经写入、恢复时就按原决定走。
|
||
|
||
禁止修改的意思是:数据库里加约束,只能追加,不能改也不能删;要更正,就追加一条新记录。
|
||
|
||
---
|
||
|
||
## 五、可替换点
|
||
|
||
论文实验要换掉的部件,按种类用最轻的办法换:
|
||
|
||
| 种类 | 例子 | 换法 |
|
||
|---|---|---|
|
||
| 外部服务 | Pi 会话、Docker 运行器、任务、Jev | 接口。Jev 另配一个"永远判不同"的空实现,用来关掉判重 |
|
||
| 决策规则 | 选批:批内最好期望、均匀随机。选格:信息价值减成本、全测、随机同格数、都不测。以后可能加合并方式 | 纯函数,登记在 rules 里的"名字 → 函数"表里,配置写名字 |
|
||
| 方法参数 | N、M、批量、运行次数、因子数上限、u、衰减、开放预留 | 配置值,不写成常数,也不用环境变量。分两层:用户配置(配置对话会问);方法参数(做实验时才改,配置对话不问) |
|
||
| 步骤开关 | 消融、挑刺与回应、第零轮、第一次预测 | 配置值,由 config 翻译进步骤清单 |
|
||
| 模型与提示词 | 每个角色、每位研究者用哪个模型;提示词目录 | 配置值;账本记下每份提示词文件的哈希 |
|
||
|
||
对照组"朴素循环"不另写流程,它就是一组开关的命名预设。
|
||
|
||
为了让换规则的实验做得对,结构上守住下面几条:
|
||
|
||
1. **开关只在一处被读取**:就是第三部分的步骤清单。互相冲突的组合在配置校验时报错,比如关掉了预测,选批却不是随机。
|
||
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 | 1500–2500 |
|
||
| rules | 1500–2500 |
|
||
| submissions | 600–1000 |
|
||
| jev | 300–500 |
|
||
| materials | 1000–1800 |
|
||
| agents | 800–1300 |
|
||
| workspace | 300–600 |
|
||
| runner | 500–900 |
|
||
| tasks | 契约 150–300;填圆 600–1000,另有 Python 文件 |
|
||
| export | 500–900 |
|
||
| vocab | 450–600 |
|
||
|
||
---
|
||
|
||
## 八、各模块对外的函数与类型
|
||
|
||
写到哪个模块时,再把它对外的函数和类型补在这里。
|
||
|
||
### 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`(字段定义):研究者在提案里登记的一条假设专用指标。
|
||
- `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">`。
|