Repository navigation
feat(capture): bounded capture suites (captures.max_age / captures.max_cases) - #57
Open
babaliauskas wants to merge 26 commits into
Open
babaliauskas wants to merge 26 commits into
babaliauskas wants to merge 26 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te once Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… cap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…limits per spec Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stablished suite Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e room Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A huge max_age ("999999d", a plausible way to write "no limit") passed
validation, then overflowed the cutoff capture sync, doctor and compare
compute from it. The validator now caps it at 100 years and says to omit
max_age for no age limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fter it capture sync planned and deleted bounded-suite cases by their "name" field. A copied case file that kept its name redirected a retirement to the original (deleting a reviewed case), and a renamed file was never deleted while every sync still reported it retired. - iter_promoted_case_files returns each case with its path; sync plans by file stem and retire_promoted_case(path) deletes exactly that file. A deletion that fails is reported and not counted in the suite line. - Backfilled provenance is rewritten in the file it was read from. - A capture whose case file name is already taken is never planned; a planned write that fails is not counted as added, and sync re-plans around what was written so its stale twin stays. - Dedup seeds the content of every case that stays, so a replaceable case that shares content with a reviewed one cannot let a fresh copy in. - A suite synced only because its directory holds case files is rebuilt but wired only when it already has a suites: entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A case recorded on another model is replaced only with max_cases set; without it the finding says to set captures.max_cases. - An over-cap suite is trimmed only down to its reviewed cases; the finding says so when they alone exceed max_cases. - capture sync --help describes bounded suites instead of "promote every capture". - The doctor row comment names the real trigger (store without max_cases). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Captured suites take provenance: reviewed in the case file (.evalshift/suites/<suite>/<case>.json), not golden.jsonl, which sync regenerates; hand-written suites keep the golden-row guidance. - captures.max_age is at most 36500d; omit it for no age limit. - Retirement targets the case file; a failed delete is reported; a suite synced only from its directory is not wired back in; dedup keeps the claim of every case that stays. - Fetch window: a conversation straddling it loses earlier turns on a fresh checkout; pass the full messages list to capture.model_call. - Freshness messages quote the conditional wording. - CHANGELOG: the first sync writes captured_at / source_model even without limits, so everyone running capture commands upgrades together; a suite never shrinks because its cases went stale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…res: block A fresh init now scaffolds captures.max_age / max_cases, and the example's demo captures (recorded 2026-09-08 on a placeholder model) are all older than max_age, so following the README promoted nothing. The README now has the reader delete the captures: block init writes, says why, and shows what sync prints with it left in. The config and promoted case files are regenerated by replaying the walkthrough with this CLI (case files gain captured_at / source_model; the config picks up init's max_invariant_violations line it had drifted from). The drift test now also checks the config outside the managed region against init's output minus CAPTURES_SCAFFOLD. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Adds two optional settings under
captures:inevalshift.yaml,max_age(e.g.14d) andmax_cases(e.g.200). Together they keep each captured suite a capped, route-balanced sample of recent production traffic ondefaults.source_model.capture syncfilters. It promotes only captures recorded withinmax_ageand on the source model. Model ids match on bare names: provider prefix, case and snapshot date are ignored. A per-suite guardrail turns model filtering off, with a warning, when no case or capture was recorded ondefaults.source_model, so a typo or alias can never empty or freeze a suite.capture syncplans and retires. Withmax_casesset, the pureplan_suitefills free slots route-balanced, in sha256 order. Conversations enter whole. Stale cases (too old, or recorded on another model) leave only to make room, or when a fresh copy of the same question is added. Over-cap suites are trimmed from the largest route, oldest first. Cases markedprovenance: reviewed(in the case file) are never retired. A retirement deletes the exact file the case was read from. The same captures give the same suite on any machine, and a re-run with nothing new touches no file.max_agebounds what sync downloads fromcaptures.store.--sincestill overrides it.captured_at/source_model, and sync backfills them on existing cases whose capture is still local.doctorgains asuite freshnessrow per captured suite and acaptures limitsnudge (store set withoutmax_cases).compareprints the same findings above the verdict, including for the auto-selected suite, so a CI-only team sees its suite went stale.initand the example.initscaffoldsmax_age: 14d,max_cases: 200.examples/capture-firstwas regenerated with this CLI.defaults.source_modelalone changes nothing.Why
The SDK's 200-per-suite cap applies to local disk only, and the bucket keeps everything. A team that captures thousands of records a week, deletes its local mirror and runs
capture syncgot every record ever captured, old models included, and suites only grew. This bounds suite size and keeps the suites fresh.Design spec:
docs/superpowers/specs/2026-10-16-bounded-capture-suites-design.md. The plan's "Execution notes" section lists where the shipped behaviour deviates from the plan:docs/superpowers/plans/2026-10-16-bounded-capture-suites.md.Compatibility. Older CLIs reject the new config keys and the new case-file fields. The first
capture syncon this CLI writes the fields even without limits, so everyone runningcapturecommands, including a pinned CI action'sevalshift-version, should upgrade together. CI'scomparereads onlygolden.jsonl, whose format is unchanged. CHANGELOG has the details.Test plan
pytestpasses:make cishows 3267 passed, 1 skipped (the existing evalshift-server skip), coverage 95.15%.ruff check,ruff format --checkclean;pre-commit run --all-filesclean.mypy --strict src/evalshift_cliclean.## [Unreleased].tests/integration/test_capture_lifecycle.py: weeks of bucket captures stay one bounded, fresh suite; the fresh-checkout fetch is pinned; production model migration; the wrong-source-model guardrail on new and established suites.max_cases: 5: each suite trimmed to 5.doctor:ok/warnfreshness rows as expected.Screenshots
🤖 Generated with Claude Code