diff --git a/CLAUDE.md b/CLAUDE.md index 1e6512f..3fd8170 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -86,8 +86,13 @@ ### 测试 - 账本和数学规则的预期值,来自手算、独立脚本或参考项目的固定数据,不从实现里复制公式。 -- 每个阶段结束时核对几条总账,不符就停。比如:扣掉的运行次数等于已确认启动的正式运行数;每条证据只记一次账。 - 其余部分测到能跑通就够。真实模型链路要用真实运行来证明,假模型测试证明不了。 +- 流程(flow)的验收看研究做得好不好,不只看跑没跑通:用真模型、真 Jev、真 Docker 把同样的配置跑几次,用 Jev 当裁判,对照同类系统和参考项目高分运行的研究路径来判(`docs/结构.md` 第三部分"flow 分四部分写,各自验证")。 +- 正式代码和检验代码完全分开: + - `src/` 只放正式代码;`tests/` 放 `npm test` 跑的不花钱的测试;`eval/` 放真实运行的检验(起研究、回放记录、杀进程、查账本、评研究质量)。 + - `src/` 不引用 `tests/`、`eval/`;`eval/` 只经 `src/<模块>/index.ts` 用正式代码,像用户一样用:给配置、启动研究、读账本。 + - `src/` 里不出现为检验写的任何东西:参数、环境变量开关、"回放模式"、假实现、只给检验用的导出。检验要的假模型,用 Pi 自己的模型服务配置(`PI_CODING_AGENT_DIR`)接过去。 + - 检验跑出来的研究目录和报告放在仓库外,不提交。 - 修缺陷时,附一条能复现原问题的测试。 - 不为了方便测试而给产品代码加参数或分支。测试要造的情况(例如外部服务不应答),用测试自己的环境去造。 - 不靠删断言、改预期值,或只保留成功的运行来让测试通过。 diff --git a/WORK_LOG.md b/WORK_LOG.md index 809ed98..5eb19c3 100644 --- a/WORK_LOG.md +++ b/WORK_LOG.md @@ -58,6 +58,9 @@ - 提案、回应、统一的已占用指标编号加 `answer-path`、`safeguard-triggers` 两个保留键(用户选"materials 补进清单")。 - Jev 退回的原因只列判"是"的几问,只用 jev 的英文问题名(用户选"只列判是的+只写英文")。 - flow 拆成四部分、各自验证;验证一律用真 Jev、真 Docker,同样的输入真实跑几次;模型一侧回放真实记录和真模型两种都做(2026-10-03,细节见结构.md 第三部分)。用户原话:"我建议用一律用真 Jev、真 Docker。因为其实我们现在是在做科学研究,不是在开发程序……我们只需要重复运行多次";对拆分:"你那几个拆分我基本认同";对两种都做:"同意"。 +- flow 每部分的验收看研究质量,不只看跑通(2026-10-03)。第零轮问六问:入口里有没有关键想法、分到的入口分没分开、好方向分出去没有、是不是改代码的路线、有没有写答案、讨论有没有用;尺子是同类系统和参考项目高分运行的关键想法与死路清单(任务书 12 第五部分)。用户原话:"你要改掉你一直在做程序设计的逻辑。我们现在在做科研……我 care 的是……这个候选的方向是不是真的是正确的……那三个候选方向……是不是真的有分开……让 Jev 去判一下";只靠这部分答不了的问题不放进来:"有一部分是只有这份回答不了的那有啥好评判的"。 +- 正式代码和检验代码完全分开:`src/` 正式、`tests/` 不花钱的测试、`eval/` 真实运行的检验(CLAUDE.md"测试")。用户原话:"需要把正式代码和测试代码完全分开……我们现在是有一大堆的测试代码是需要监控我们正式代码"。 +- 基线每份研究都跑一次,不复用别的研究的结果;方法.md 第九部分"每个阶段结束时核对总账"删去,改为由记录方式保证(2026-10-03,监督者建议,用户说"基本可以,你可以写任务书了")。 ## 未完成 @@ -90,4 +93,4 @@ ## 下一步 -已完成模块收尾完毕,现在由用户和监督者一起过 flow。flow 分四部分写、各自验证,拆分、验证办法和对照用的记录见 `docs/结构.md` 第三部分"flow 分四部分写,各自验证"。用户还要求每部分也拿参考项目真实验收运行的记录(`/home/iomgaa/rb-acceptance/*/board.sqlite`)当对照,原话:"我们也可以把我们的记录作为一个标杆,然后去匹配"。下一步:监督者给用户讲部分 1,定下后写任务书 12。 +已完成模块收尾完毕,现在由用户和监督者一起过 flow。flow 分四部分写、各自验证,拆分、验证办法和对照用的记录见 `docs/结构.md` 第三部分"flow 分四部分写,各自验证"。用户还要求每部分也拿参考项目真实验收运行的记录(`/home/iomgaa/rb-acceptance/*/board.sqlite`)当对照,原话:"我们也可以把我们的记录作为一个标杆,然后去匹配"。部分 1 已写成任务书 12(`任务书/12-flow-1-骨架与第零轮.md`),等用户另开会话执行,之后监督者核对。 diff --git a/docs/方法.md b/docs/方法.md index 131b9f1..c85de7f 100644 --- a/docs/方法.md +++ b/docs/方法.md @@ -145,7 +145,7 @@ ### 4.1 研究开始 1. 程序把这份研究的配置冻结进账本,包括 N、M、因子数上限、每批上限、运行次数、各决策点用哪条规则、方法参数、提示词哈希。之后的流程只读这份冻结的配置。 -2. 已有的结果只有在实验条件完全相符时才能当基线;没有符合条件的结果,就安排一次基线运行,计入运行次数。 +2. 研究开始时跑一次基线,计入运行次数。不复用别的研究的结果。 ### 4.2 第零轮:各自找不同的入口 @@ -705,7 +705,7 @@ VOI(S) = E_{S 的结果}[ max_p E(收益_p | S 的结果) ] − max_p E(收益_p - **运行一旦启动,就按实测记账。** 跑完才发现实现有错,也不标为无效重跑。 - **不支持中途作废重做某一轮**,也不支持替换提示词模板、放弃某个阶段。想重来,就另开一份新研究。 - **开发入口**:研究因为模型交不出合格提交而停下后,开发者可以修好提示词,在同一阶段只重新发起那一个任务。这个入口由配置开关控制,配置里必须写明开还是关:正式研究写关,开发调试时才写开。〔自定〕打开过这个入口的研究,不能当验收证据。 -- **每个阶段结束时核对几条总账**,不符就停。例如:扣掉的次数等于已确认启动的正式运行数;每条证据只记一次账。 +- **总账由记录方式本身保证,不另设核对。** 扣掉的次数不单独存,每次从"确认启动"的记录数出来;同一轮的消融结算只能写一条,重复写入直接报错。 --- diff --git a/docs/结构.md b/docs/结构.md index 306ba42..455ad6b 100644 --- a/docs/结构.md +++ b/docs/结构.md @@ -115,7 +115,7 @@ | 部分 | 管哪些步骤 | 自己的验证目标 | 拿哪份真实记录对照 | |---|---|---|---| -| 1. 骨架、研究开始、第零轮 | 从账本算出现在到哪;"下一步做什么"的纯函数(步骤先后只写这一处);主循环;恢复入口;派一个模型任务、跑一次正式运行两个共用动作;冻结配置、建镜像、基线;第零轮 | 在各处写账本之后杀掉进程再启动,不重跑、不重扣、不漏写 | 16-real-1 第零轮回放,入口分配和参考项目相同 | +| 1. 骨架、研究开始、第零轮 | 从账本算出现在到哪;"下一步做什么"的纯函数(步骤先后只写这一处);主循环;恢复入口;派一个模型任务、跑一次正式运行两个共用动作;冻结配置、建镜像、基线;第零轮 | 第零轮的研究质量六问(任务书 12):入口里有没有关键想法;分到的入口分没分开;好方向分出去没有;入口是不是改代码的路线;入口有没有写答案;讨论有没有用。另杀进程再启动,不重跑、不重扣、不漏写 | 关键想法和死路清单取自同类系统与参考项目高分运行的研究路径;16-real-1 第零轮回放,分配和参考项目相同,并用同样六问打分当对照 | | 2. 开轮到冻结 | 4.3 ①–⑦ | 每次模型调用开始时存下的材料里,没有当时还没公开的东西 | 16-real-1 三轮提案到第二次预测回放;15-tight-2 抄错编号不再卡住 | | 3. 选批到采纳 | ⑧–⑫ | 扣掉的次数等于确认启动的运行数;修复用完不跑、不扣、记事件 | 选批记录的输入和 16-real-1、15-real-1 存下的比;15-tight-2 第 1 轮的修复 | | 4. 消融到收尾 | ⑬–⑱ 与研究结束 | 解释冻结早于任何一格启动;每格只结算一次 | 选格输入和 15-real-1、16-real-1 比;最后整份回放 16-real-1,当四部分合起来的验收 | diff --git a/任务书/12-flow-1-骨架与第零轮.md b/任务书/12-flow-1-骨架与第零轮.md new file mode 100644 index 0000000..43d5a34 --- /dev/null +++ b/任务书/12-flow-1-骨架与第零轮.md @@ -0,0 +1,432 @@ +# 任务书 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-`,和其他编号规则放在一起。这样正好和参考项目的写法一样,回放时不用改编号(参考项目入口编号 `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 条,写"做到了"或"没做到、为什么"。