Repository navigation
feat(sleep): add opt-in leave-one-out edit ablation for accepted gate trials - #390
Intelligence Research Project (sunghunkwag) wants to merge 2 commits into
Conversation
… trials The gate accepts or rejects a candidate as a whole, so when a multi-edit candidate passes, the report cannot say which of its edits the held-out score depends on. microsoft#174 left this per-edit attribution as follow-up work because it needs ablation runs. With `gate_edit_ablation` on, each skill or memory candidate that passes the gate is scored again on the same validation tasks once per edit, each time without that one edit. The result is recorded on the trial as an `edit_ablation` row per edit: the gate score with and without the edit, an effect of positive / none / negative / unmeasured, and the validation tasks whose score differs. The rows are report-only. No gate decision reads them, so accepted edits and documents are identical with the option on or off. The default (off) adds no replay; its only visible change is a `gate_edit_ablation: false` field in diagnostics.json and in the logged cycle config. Cost and edge cases: - A single-edit candidate needs no extra replay: the trial already compared the document with and without the edit. - An N-edit candidate costs up to N validation replays. They are skipped, and listed once in `budget_skipped`, when the token budget is spent. - An edit another edit of the same trial overrides is reported without a replay, since the document is the same without it. - Edits that only matched through the removed edit are listed as `co_dropped`, so a row is not read as the effect of one edit. - A missing or non-finite ablation score is `unmeasured`, never an effect in either direction. - With `gate_mode` off there is no validation replay, so the option has no effect. Rows flow through the existing `gate_trials` path, so they appear in `report.md`, `report.json`, `diagnostics.json`, the CLI `--json` output and the evidence log with the existing redaction. In multi-skill fan-out each group's rows are written to the evidence log. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015s4y5QVk9hfpeGNR7dzrHb
|
@microsoft-github-policy-service agree |
|
Thanks, this is a careful and well-tested change. It is off by default, the default report is byte-identical, single-edit and overridden-edit cases avoid extra replays, the redaction path is covered, and I could not find token double-counting. One blocking issue, plus a few suggestions:
|
Addresses the review of microsoft#390. Ablation could change decisions when the nightly token budget was binding: its replays ran inside the gate trial and spent budget that the memory step, the final validation replay, the held-out test replay and later fan-out groups check afterwards. - Ablation replays now run only after the final decision of an accepted epoch. The nightly cycle queues them and runs them after the held-out test replay and every fan-out group, so they can use leftover budget but cannot change what a budget-dependent step decides. `consolidate_groups` defers them past its last group in the same way. - The budget is checked before each ablation replay instead of once per trial. A skip still adds a single `edit ablation` entry, and it no longer marks a fan-out group as cut short on budget. - `effect` is renamed to `observed_change`, with `higher_with_edit`, `no_change`, `lower_with_edit` and `unmeasured`. The report caption and the docs say that a row is an observed change on one replay, not a causal attribution, and that the "with" score is the replay selected by the gate. - The docs state that ablation spend is part of `tokens used` and counts toward `max_tokens_per_night`. - Rows carry the edit's `source_task_ids` and `source_session_ids`. - A candidate that the final decision rejects is not ablated. - `consolidate` reads the backend's diagnostics before ablation runs, so `call_error` and `reflect_raw` are the same with the option on or off. Regression tests: a token-based budget with `evolve_memory=True` at every budget size, `consolidate_groups` under a shared budget, and fan-out and held-out-test nights with a token-counting mock backend.
|
Thanks for the careful review and for the two repros. You were right about the blocking issue: my claim that the option leaves decisions unchanged only held when the budget was not binding. I pushed
|
What this adds
When a multi-edit candidate passes the Sleep gate, the report shows which validation tasks moved, but not which edit moved them. The maintainers' reply in #174 left that open: "true per-rule causal attribution would require separate ablation runs and can be explored as a follow-up." This PR is that follow-up.
With
gate_edit_ablation: true(defaultfalse), each skill or memory candidate the night accepts is replayed on the same validation tasks once per edit, each time without that one edit. Every edit gets a row on its trial: the gate score with and without the edit, anobserved_changeofhigher_with_edit/no_change/lower_with_edit/unmeasured, the validation tasks whose score differs, and the edit'ssource_task_ids/source_session_ids.A row is an observed change on one replay, not a causal attribution. The rows are report-only, and ablation runs after every gate decision of the night, so accepted edits and documents are the same with the option on or off, including when the token budget binds.
Example
The #174 shape: one edit lifts a task, another lowers a different task, and the slice mean still rises (0.400 → 0.850). This is what the report renderer produces for that case (the scenario in
test_ablation_names_the_edit_behind_a_hidden_regression), shown as source:The task table already shows that
routerregressed. The ablation table adds which edit that replay points to, and that the candidate scored 0.900 without it.Behavior and cost
report.md,report.jsonand the CLI--jsonoutput are unchanged. The only default-visible change is agate_edit_ablation: falsefield indiagnostics.jsonand in the logged cycle config.run_sleep_cyclequeues them and runs them after the held-out test replay and every fan-out group, andconsolidate_groupsdefers them past its last group. They can use leftover budget but cannot change what a budget-dependent step decides.tokens usedand count towardmax_tokens_per_night, which is checked before each replay. Replays that do not start leave their rowsunmeasuredand add oneedit ablationentry to the budget note; a skip never marks a fan-out group as cut short. In a fan-out night with a token-counting mock, spend rose from 11,280 to 15,600 tokens (+38%).gate_ablation:<target>in the evidence log.gate_no_regression) nor an epoch whose final decision rejects.no_changewithout a replay, because the document is the same without it.replaceanchored on a line that anaddin the same trial created) are listed asco_dropped, so the row is not read as an observation about one edit.unmeasured, with no score and no per-task direction.gate_mode: offhas no validation replay, so the option has no effect there.gate_trialspath, so they reachreport.md,report.json,diagnostics.json, the CLI--jsonoutput and the evidence log with the existing redaction. In multi-skill fan-out each group's rows are in the evidence log; surfacing them inreport.mdis left for a follow-up.Limits
higher_with_edit, and a nondeterministic backend or judge can move any row by noise.no_change.Tests
tests/test_gate_edit_ablation.py, 61 test cases. Beyond the first version: a token-based budget withevolve_memory=Trueat every budget from 0 to 10 replays,consolidate_groupsunder a shared budget from 0 to 12 replays, fan-out and held-out-test nights with a token-counting mock undermax_tokens_per_night, the per-replay budget check, the deferral queue, and a rejected final decision.python -m pytest -q: 2276 passed, 13 skipped, 4 xfailed on the branch merged with currentmain(Python 3.13, project dependencies plus theparallelextra).ruff checkon the changed files: clean.python -m mkdocs build --strict: passes.Docs:
docs/sleep/README.md(the option and the nightly token budget section), the advanced-config key list inplugins/README.md, andCHANGELOG.md. I left the per-pluginSKILL.mdkey lists alone.🤖 Generated with Claude Code
https://claude.ai/code/session_015s4y5QVk9hfpeGNR7dzrHb