Skip to content

Commit 1b306ea

Browse files
tzhouamclaude
andcommitted
Add proposal publication, Tier 2 proposals, the engine's self-declaration and the meta-benchmark adapter
Meta-improvement engine P4 (design §9.2, §11.1, §11.2). improve/publish.py: the engine holds no GitHub token, so a proposal leaves it as an action file (open / update / close) in an outbox directory that the maintainer routine's `propose` command picks up and answers with an ack; the routine also observes each proposal issue (human comments, `maintainer: hold`, referencing pull requests, closure) into an inbox the engine reads back. Rule 4: evidence is cited only by record id and blob hash; every excerpt is drawn verbatim from the redacted blob (<= 20 lines, long lines cut to a prefix) so a reader can recover it from the hash, and lint_proposal refuses anything else (unresolvable ids, a Tier 1 proposal with fewer than three records, a Tier 2 proposal without a stage S1-S9 or a loss, an excerpt that is not a line of its blob, a credential pattern left in the body); a refused proposal is recorded and never published. The issue body follows the fixed template (claim, stage or lint, loss, evidence, suggested pre-registration with the register command, ledger link, marker) and carries improve:<workflow> / improve:tier<n> / improve:<state> labels. plan/publish write each action once (a pending action waits for its ack for seven days), skip workflows on hold (rule 5: lints and experiments continue) and record every write. sync applies acks (issue url, published state; a dry-run ack from a routine still in shadow publishes nothing) and observations: a human comment is a channel touch, a hold comment sets a channel hold the next observation without it releases, a merged referencing pull request lands the proposal, a closure on GitHub closes it. A landed Tier 1 proposal's issue closes only once a later cycle shows the lint's defect rate below the rate at opening. Steps: improve.mode publishes the run mode and item key; improve.sync (read) reads the outbox back; improve.publish is the cycle's only outward write, declared risk="push" and gated like pr.post_review — without the task's post intent it only previews, with it but ALLOW_POST=0 it is a dry run, with both it writes the outbox (blocked loudly when no outbox is configured); the executor's shadow refusal stops it before it runs. The forensics step now opens one Tier 2 proposal per punch-list stage with a loss (never S0/S10), each with a suggested pre-registration (metric, the items with gold, the item count the historical sd requires, what the arm's fingerprint should cover for that stage); descriptive-only workflows get proxy proposals without a suggestion. Tier 1 proposals record the rate at opening. Settings: improve_outbox_dir, improve_proposal_repo, improve_meta_dir. Self-enrolment (§11.2): improve/workflows/workflow-improve.yaml declares the engine's own forensics role (`workflow-improve.improve.forensics`, item `{improve_item}`) with a fingerprint over the forensics, lint, statistics and adapter sources and the resolved models. With `--task-param meta_case=<case>` the playbook runs only that step over one frozen meta case (every cycle step is gated off) and writes a `meta_eval` outcome: the engine's stage labels beside the human ones, agreement, Cohen's kappa and the Tier 1 catalogue's recall on the injected lint samples. adapters/meta_bench.py scores it: hit when the label agrees, disputed when the families disagreed, unlabeled for S0; no judge is involved (human_labelled), so the cross-family rule holds by construction. experiments.run stages a self-experiment item by copying the case and the lint samples into the shadow directory (nothing narrative: no doc/, no judgments), runs the copilot child on it through IMPROVE_META_DIR, needs no judge for a human-labelled adapter and picks the unit enrolled in the experiment's workflow rather than the child's last unit. CLI: improve publish [--dry-run], improve sync, improve meta bench. Records that forensics writes while attributing keep the caller's unit item when one is bound (the investigated unit and item are named in the result), so the engine's own unit over a meta case is keyed by the case. Review round 1 (gpt-6.1-sol-high) fixes: the self-experiment child names its repo (`--task-param repo=meta`, the one alias its REPO_PATHS exposes) so run_playbook accepts it instead of falling back to default_repo, and shadow_env now writes REPO_PATHS in the JSON form Settings parses (the `alias=path` form failed every real shadow child at Settings load); a `maintainer: hold` is remembered per proposal and the workflow's channel hold is aggregated over its proposals, so observing an unheld proposal never releases a hold another proposal's issue still carries (an operator's own hold is never touched by the channel); the Tier 1 rate check skips cycles whose units were all quarantined, so a cycle that measured nothing cannot verify a landed proposal. Review round 2 fixes: a private-key block is redacted whole (header to END, or to the end of the text when cut), never just its header; a publication (plan, action files, pending notes) runs under one lock per outbox, so concurrent publishers cannot emit two actions for one proposal; an ack is applied only when its action id is the proposal's pending action — a delayed answer to a superseded action is consumed as stale and may only add the issue url it created when none is known, never clear the newer pending action or rewrite the published state. Review round 3 fix: sync applies acks under the publisher's lock, so the pending-action check and its clearing cannot interleave with a publisher retrying that action (a stale ack could otherwise clear the newer pending id and let a further publish duplicate it). Tests: test_improve_p4.py — citation excerpts (verbatim, redacted, recoverable, refused when tampered), the linter's refusals and the template, the outbox round trip (once per action, acks, dry-run acks, failures, touches, holds and their release, landing, the Tier 1 rate check, refuted -> closed), the double-gated publish step and the shadow refusal, Tier 2 proposals from the punch list, the meta case run and adapter, a self-experiment adjudicated without a judge, the meta-mode playbook run as the engine's own unit, the real CLI entry for a shadow child (repo alias accepted, plan printed), a hold that survives another proposal's silence, a fully quarantined cycle, a whole-key redaction, four concurrent publishers, a stale ack after a retry, sync blocked behind a publisher's lock, the CLI. Full suite green except test_mcp.py (host mcp 2.x). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 834d554 commit 1b306ea

18 files changed

Lines changed: 2023 additions & 52 deletions

File tree

‎doc/architecture/SPEC/improve.md‎

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<!-- verified-against: 2026-09-30 -->
44

5-
`设计:/data/zhoutaichang/copilot/meta-improvement-engine-design.md v1(GPT-6 sol 批准 2026-09-29) · refactor-status: building (P0–P3 已落地)`
5+
`设计:/data/zhoutaichang/copilot/meta-improvement-engine-design.md v1(GPT-6 sol 批准 2026-09-29) · refactor-status: building (P0–P4 已落地)`
66

77
## 职责
88
面向任意 trace/1 工作流的"取证—实验—提案"循环。**只提案**:引擎的写权限限于追加自己的 trace/1 记录、
@@ -29,20 +29,27 @@
2929
| `meta.py` | P2 | 冻结元基准:`eval/dataset/meta/cases`(trace + 人工阶段标签)与 `meta/lints`(注入缺陷样本)的导出与加载 |
3030
| `budget.py` | P3 | 周包络(美元 + 判官次数,按 ISO 周持久化、跨进程加锁):每次模型调用发出前按最坏情况预留(输入 = 请求字节数 ≥ token 数、输出 = `max_tokens`、无价格即拒绝)、返回后结算;`结算 > 预留` 记 `budget_breach` 并中止;`governed()`/`current_governor()` 供 `LLM.create` 与 `run_judge` 使用 |
3131
| `experiments.py` | P3 | 预注册(Tier 2 且非 descriptive-only、API 后端、指纹 diff 非空且不触及元基准、每个 item 有金标、按历史 sd 算 `n_required`、成本预留)→ 影子运行(stage → 每 item×replicate×臂 一个子进程、`PR_SNAPSHOT_FILE` 交接、指纹核对、L13/预算/崩溃隔离)→ 配对判定(`n_retained` 重算功效、五种标签、`experiment_verdict` 记录、提案状态推进) |
32-
| `cli.py` | P0–P3 | `improve migrate-index|rebuild-index|rollback-index|compare-index|verify-index|cycle|ledger|lints|gold draft/check|meta lint-check|budget|experiment register/run/list` |
32+
| `publish.py` | P4 | 提案发布:引用只以记录 id + blob 哈希(`excerpt_for` 逐字摘录 ≤20 行、脱敏、`verify_excerpt` 可由哈希复原);`lint_proposal` 拒绝任何不可解析引用(Tier 1 ≥3 条记录,Tier 2 S1–S9 + 损失量);固定 issue 模板(主张/阶段/损失/证据/建议预注册/账本/marker);`ProposalOutbox` 文件协议 `improve-outbox/1`(`actions/` 引擎写、`acks/` 与 `inbox/` maintainer routine 写);`plan`/`publish`(open/update/close,hold 暂停发布、待 ack 不重发);`sync`(ack → issue 与已发布状态;inbox → 人类触碰、`maintainer: hold`、合并 PR → landed、GitHub 关闭 → closed;Tier 1 landed 在后一周期 lint 率低于开单时才 close) |
33+
| `adapters/meta_bench.py` | P4 | 引擎自身的结果适配器(§11.2):金标 = 元基准 case 的人工阶段标签;outcome = `meta_eval` 记录;hit = 标签一致、disputed = 双家族不一致、S0 = unlabeled;`review_scores` = κ、lint 召回;`human_labelled`(无判官) |
34+
| `workflows/workflow-improve.yaml` | P4 | 引擎自登记:`workflow-improve.improve.forensics`,item `{improve_item}`(周期为 `cycle`、自实验为 `meta:<case>`),指纹覆盖取证/lint/统计/适配器源码与模型 |
35+
| `cli.py` | P0–P4 | `improve migrate-index|rebuild-index|rollback-index|compare-index|verify-index|cycle|ledger|lints|gold draft/check|meta lint-check/bench|budget|experiment register/run/list|publish [--dry-run]|sync` |
3336

3437
## 接入点
3538
- 执行器:`settings.trace_store_root` 非空时绑定 store 并为每步绑定单元上下文;`settings.improve_shadow` 下拒绝非 read/report 步骤。
3639
- `tools.dispatch` / `LLM.create` / `HarnessLLM.create` / `run_harness_step` / MCP bridge:全保真采集。
3740
- `kb serve` 调度器:每 tick 调 `_improve_cycle()`,周度槽位到达即跑一次周期(与知识服务同一租约、异常隔离)。
38-
- 任务种类 `workflow_improve`(L2,READ_ONLY_KINDS);playbook `workflow-improve`(preflight → lint → experiments → forensics → ledger → report)。
41+
- 任务种类 `workflow_improve`(L2,READ_ONLY_KINDS);playbook `workflow-improve`(mode → preflight → sync → lint → experiments → forensics → ledger → publish → report);`improve.publish` 是唯一 `risk=push` 步骤,与 `pr.post_review` 同样过"TaskSpec `post` 且 ALLOW_POST=1"双门,否则 dry-run 打印将写的 outbox 动作。
3942
- `LLM.create`:绑定了 governor 时先预留再发请求,失败释放、成功结算;`improve.experiments` 与 `improve.forensics` 都在 `governed()` 内运行。
40-
- `improve.forensics` 步骤:对每个有结果适配器的工作流建覆盖矩阵、用两个模型家族归因每个 miss、产出整改清单;单元数低于 `tier2_min_items` 或无 LLM 时跳过并说明。
43+
- `improve.forensics` 步骤:对每个有结果适配器的工作流建覆盖矩阵、用两个模型家族归因每个 miss、产出整改清单并按阶段各开一条 Tier 2 提案(`cycle.open_tier2_proposals`,带建议预注册;descriptive-only 工作流的提案标 proxy、无建议);单元数低于 `tier2_min_items` 或无 LLM 时跳过并说明。`--task-param meta_case=<case>` 时(`improve.mode` 发布 `improve_meta`)只跑取证步骤:对一个冻结 case 归因并写 `meta_eval` outcome——这就是自实验的影子单元(`experiments.meta_run_unit` / `_meta_stage` 只把 case 与 lint 样本复制进影子目录,不带任何叙述材料)。
44+
- `improve.sync`(read)读回 acks/inbox;`improve.publish`(**`risk="push"`**,唯一外向写):无 `post` 意图只预览、有意图但 `ALLOW_POST=0` 为 dry-run、二者俱备才写 `improve_outbox_dir` 下的动作文件(`improve_proposal_repo` 必填);影子运行被执行器在步骤前拒绝。
45+
- 自实验:`experiments.run` 对 `human_labelled` 适配器不要求判官;`_find_unit` 优先取声明工作流的单元。
4146

4247
## 不变量
43-
- P1 周期永远 dry-run:提案只在账本与 trace 里,不上 GitHub(P4)。
48+
- 周期内唯一外向写是 `improve.publish` 写入 outbox 的提案动作;引擎不持有 GitHub 令牌,issue 的开/改/关由 maintainer routine 执行并以 ack 回报。
49+
- 未通过 `lint_proposal` 的提案永不发布(记 `proposal_lint_failed`);hold 只暂停发布,lint 与实验照常。
50+
- 元基准只读:任何触及 `eval/dataset/meta` 的指纹 diff 或 `IMPROVE_*` 覆盖在注册时被拒。
4451
- 引擎自身的记录(`playbook=workflow-improve`)是下一周期的 Tier 1 单元(自登记)。
4552
- lint 只读 trace,不调模型;无证据形态的 lint 不猜。
4653

4754
## 测试
48-
`test_improve_p0.py`、`test_improve_p0b.py`、`test_improve_p1.py`、`test_improve_p2.py`、`test_improve_p3.py`。
55+
`test_improve_p0.py`、`test_improve_p0b.py`、`test_improve_p1.py`、`test_improve_p2.py`、`test_improve_p3.py`、`test_improve_p4.py`。

‎eval/dataset/meta/README.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,12 @@ The historical campaigns (T3, wave-2, wave-3) left narrative reports, not
1616
per-cell labels; the cases here are labelled fresh against the traces (design
1717
§6.1, P2). Labels are the benchmark, never the report text, so an investigator
1818
cannot recite them.
19+
20+
The engine measures itself here (design §11.2): `infermatrix-copilot improve
21+
meta bench [--case <case>]` runs the forensics role over every case and
22+
reports agreement and Cohen's kappa against the human labels plus the Tier 1
23+
catalogue's recall on the lint samples; a shadow self-experiment
24+
(`improve experiment register --workflow workflow-improve.improve.forensics
25+
--items meta:<case>,...`) stages a copy of one case per item and scores it
26+
with `improve.adapters.meta_bench`. No judge is involved: the labels are the
27+
gold.

‎playbooks/workflow-improve.yaml‎

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
# The meta-improvement engine's weekly cycle (design §4). A vetted reuse
22
# playbook, never generated. Every step is read-only toward the target
3-
# repositories and the trace store (the engine only appends its own records);
4-
# publication (P4) is a separate `risk: push` step behind the post gate.
3+
# repositories and the trace store (the engine only appends its own records)
4+
# except `improve.publish`: `risk: push`, the cycle's only outward write
5+
# (proposal actions into omni-maintainer's outbox), behind the explicit
6+
# `post` intent AND ALLOW_POST=1 like `pr.post_review`.
7+
#
8+
# With `--task-param meta_case=<case>` the same playbook runs the engine's
9+
# own forensics role against ONE frozen meta-benchmark case (the shadow
10+
# self-experiment unit, §11.2): only `improve.forensics` runs.
511
name: workflow-improve
6-
version: 1
12+
version: 2
713
status: active
814
provenance:
915
created_by: human
10-
source: meta-improvement-engine-design.md v1 (sol-approved 2026-09-29), P1 slice
16+
source: meta-improvement-engine-design.md v1 (sol-approved 2026-09-29), P1-P4
1117
task_kinds: [workflow_improve]
1218
repos: []
1319
params: {}
1420
steps:
15-
- {id: preflight, step: improve.preflight}
16-
- {id: lint, step: improve.lint}
17-
- {id: experiments, step: improve.experiments}
21+
- {id: mode, step: improve.mode}
22+
- {id: preflight, step: improve.preflight, when: not improve_meta}
23+
- {id: sync, step: improve.sync, when: not improve_meta}
24+
- {id: lint, step: improve.lint, when: not improve_meta}
25+
- {id: experiments, step: improve.experiments, when: not improve_meta}
1826
- {id: forensics, step: improve.forensics}
19-
- {id: ledger, step: improve.ledger}
27+
- {id: ledger, step: improve.ledger, when: not improve_meta}
28+
- {id: publish, step: improve.publish, when: not improve_meta}
2029
- {id: report, step: report.final_summary}
2130
success: one cycle recorded (decision type=cycle), the per-workflow ledgers
22-
updated, the cycle report written; proposals stay in the ledger until P4
31+
updated, the cycle report written, publishable proposals handed to the
32+
outbox when the post gates are open

‎src/infermatrix_copilot/config.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,16 @@ def _parse_mixture(cls, v):
586586
# the review bot's GitHub login, so its own replies never count as the
587587
# author accepting or disputing a finding
588588
improve_rb_bot_login: str = ""
589+
# Publication (design §9.2): the engine holds no GitHub token; proposal
590+
# actions are files in this outbox that the maintainer routine's `propose`
591+
# routine picks up. Unset = the publish step is a no-op even when both
592+
# post gates are open. `improve_proposal_repo` names the repository the
593+
# proposal issues go to ("owner/name"); required for publication.
594+
improve_outbox_dir: str = ""
595+
improve_proposal_repo: str = ""
596+
# The frozen meta-benchmark the engine measures itself against (§11.2);
597+
# a shadow self-experiment gets a staged copy through IMPROVE_META_DIR.
598+
improve_meta_dir: str = "eval/dataset/meta"
589599

590600
# Metrics (eval/METRICS_RESEARCH.md) — per-run metrics.json: CATQ = Q·S/C.
591601
# Reference budgets are EXPLICIT deployment assumptions (RQS3e precedent):

0 commit comments

Comments
 (0)