Repository navigation
feat(improve): post-apply monitor and automatic rollback - #993
Merged
Merged
Conversation
checkApplied compares the first runs under an applied revision with the last runs before it, in up to three looks of window runs at a per-look z of 2.1, and rolls back a significant drop in the pass rate (or, for continuous scores, a corroborated trajectory regression) through rollbackHarness. It audits rollback, retire, guard-pass, guard-fail and unverified, writes a cooldown, never touches a hand-edited directory, and keeps a revision unverified after giveUpDays without enough runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…applies Review fixes: the monitor reads only the agent's own runs by id or name, orders them by end time with the baseline cut at the apply, keys its stored verdict by the apply's audit seq, runs under a lock, writes the cooldown before rolling back, settles a refused rollback as a guard failure, keeps other keys in state.json, and lastGoodRevision skips a retired revision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…failed rollback An empty agent or a name two agents share is refused, the exact-id run query honours the end-time filters, and any rollback failure settles as one guard failure instead of retrying every tick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Why
M4 ticket S10 (capability row E7, and Paper B's "retire on regression", B8 and B13). The gate and the canary each judge only a handful of cases. Real traffic is the final check, and this monitor is what makes auto-apply reversible without a person. The decisions taken in S0 apply here:
Replaces #992. That PR closed when its branch was deleted after a merge attempt that failed on a conflict. This PR has the same commits, rebased on the corpus guard (#989), which now holds the size-budget change.
Changes
New
src/improve/monitor.ts, exported fromlousho/improve:MonitorVerdict.stateis one ofidle,pending,kept,kept-unverified,regressedorrolled-back. It carriesreason,runs,baselineRuns,before,afterandto, wherebeforeandafterareWindowStats. It is an interface, not a union, so every field has a JSDoc and the docs ratchet stays at 1724.What it watches. It watches only when the newest
applyorrollbackaudit entry is anapplywhosetoequalscurrentRevision(dir). A directory a person edited by hand isidleand never touched. After a rollback there is nothing left to watch.What it compares. It reads only the given agent's runs. The name is resolved to exactly one
agent_id: an empty name or a name two agents share is refused. Revision ids are content hashes, so agents built from the same template share them. Runs are ordered by end time, so a late-finishing run can't move an earlier look's window:afteris the first runs under the applied revision that started after the apply;beforeis the last3 × windowruns of the parent revision that ended before the apply.With fewer than
max(minRuns, window)runs on either side, the verdict ispending.Serialisation and state.
.lousho/monitor.lock, so two concurrent checks audit a single decision.state.jsonis read, merged and written under.lousho/state.lockwith an atomic write, and other tickets' keys are kept.state.jsonthat is not a JSON object is refused rather than overwritten.seq, so a revision applied again is watched again.Statistics. The ticket's rule as written does not meet its own acceptance bars:
aftermore thanmaxDropbelowbefore, andafter's Wilson upper bound belowbefore's pass rate". It treatsbeforeas exact, and in a Monte Carlo run it rolls back 17% of stable 0.8 agents at 20 runs each.So the monitor uses a group-sequential design:
maxDropand Newcombe's interval (from Wilson bounds) for the difference excludes 0 at a per-look z of 2.1.maxDropthat the S9 trajectory gate (at the same z) reads as a regression also rolls back.Measured over 20,000 seeded simulations with this code:
Pocock's 2.004 gives 4.3%, which is too close to the bar for a 1,000-draw test, hence 2.1.
What each outcome writes. The verdict is stored in
.lousho/state.json(monitor: { revision, at, verdict }), so a decided revision is not judged or audited again.cooldownUntil(fromsettings.cooldownHours) written first, thenrollbackHarness(dir, parent, { actor: 'monitor', reason, expectedFrom: current }), which auditsrollback, then an auditretirewithevidence.score.lastGoodRevisionnow treats a retired revision as failedguard-failonce (beside the writer's own refusal) and settleregressed, so it is not retried every tickautoRollback: falseguard-failand reportregressed; files unchangedguard-pass, solastGoodRevisionsees itgiveUpDaysunverifiedaction once, and reportkept-unverified; never rolled backOther changes:
src/improve/audit.ts: a newunverifiedaction.src/improve/apply.ts:lastGoodRevisioncounts aretireas a failure for the retired revision.src/observe/store.ts:finishedRunsgainsendedBeforeandbyEndon the agent id-or-name path.src/observe/continuousScore.ts:pointOfis shared byscoreSeriesand the monitor.src/observe/continuousScore.ts:scoreSeries'sagentis optional (a revision is enough), andWindowStat's fields gain JSDoc.WindowStatis exported fromlousho/improve.R12: products WG.2 (rollback for product layers) reuses this through
rollbackHarness.Testing
src/improve/monitor.test.tsuses a real observe db seeded per revision and a temporary agent directory applied with the realapplyProposal:apply,rollback,retire, and the chain verifies);loadImproveSettingsdefaults;state.jsonkeeps foreign keys, andlastGoodRevisionskips the retired revision;state.jsonis refused and left as it was;Checks run:
typecheck(src, tests, examples),lint,build,api:update+api:check(api/improve.api.mdgainscheckApplied,MonitorVerdictandWindowStat),docs:llms+docs:llms:check,docs:verify-snippets -- --skip-build,docs:ratchet(1724), vitest oversrc/improve,src/observeand the improve, improveAdmin and observe CLI tests (978 passed), andnpx fallow audit --base origin/mainafter scoped coverage.Review: an independent review found two blockers, both fixed:
Nine should-fixes were also fixed:
state.jsonhandling could lose other tickets' keys;lastGoodRevisionstill returned the retired revision;cooldownHourswas ignored;A re-check found four more should-fixes, all fixed:
The CHANGELOG nit is out of scope, because the release job writes it.
Follow-up for S24: the generated Telegram bridge template (
src/cli/init/telegramTemplate.ts) also writes.lousho/state.json(relative to its cwd), without the lock or an atomic write.Docs: S27.
Notes for later tickets:
checkApplied(db, dir, settings, { agent })each tick, readscooldownUntilfrom.lousho/state.json, and should writestate.jsonunder.lousho/state.locktoo.state.monitor.verdict, and warns while it ispendingorkept-unverified.judgeAppliedfor its forward trial.🤖 Generated with Claude Code