Files
InfoRSI/docs/结构.md
T
iomgaa 20fca73847 docs: 核对任务书 01 后修正结构.md 与方法.md
- 结构.md:vocab 上限 1100;依赖图重画,使同一行确实互不依赖(flow 依赖 config,原图把它们放在同一行)。
- 方法.md 第五部分:入口编号由程序分配;实现与消融实现不设代码字段。4.3 ⑨ 补回设计-16 原文:因子与保障同行时核验员只写一句提醒、不判越界。
用户同意执行者的第 1、3–7 条("同意 A,vocab 上限改为 1100 行"等,见 97a4359 说明)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:59:35 -04:00

247 lines
16 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 行。
- 依据是两次开发调研。一次是模块切分,参考了 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**(共用词汇),所有模块都可以引用,上限 1100 行(多出来的主要是发给模型的字段说明)。一样东西要有两个互相不能引用的模块都用到,才放进 vocab;只有一个模块用的,放在那个模块里。它放:
- 共用的类型和名单:角色、研究者阶段、提交种类、指标定义、运行定义与运行结果、模型用量;
- 每种提交的字段定义(用 TypeBox 写,同一份定义既是类型,也用来检查收到的数据,也是发给模型的提交工具参数);
- 只有两个函数:规范序列化和哈希,保证"内容相同就得到同一个指纹"只有一种算法。
依赖只能往下,图中同一行的模块之间不互相依赖。谁具体能依赖谁,以上面的模块表为准,`npm run check` 按那张表检查。
```
入口 plugin
组织 flow
export
materials submissions config
外部 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 | 上限 1100 |
---
## 八、各模块对外的函数与类型
写到哪个模块时,再把它对外的函数和类型补在这里。
### 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">`。