Repository navigation
feat(flow): repair every stop that wrote nothing by default, and measure it - #1360
Merged
Merged
Conversation
…vironment The cache key was the task id and parameter schema only, so a changed record check or a new app version at the same origin silently reused the old program. The admission manifest now pins the record check's digest (contracts of every saving step, anchors left out so a repair keeps it) and the canary's environment (origin plus an optional environment_id the app reports). A run refuses a mismatch. A caller may also pin both in TaskSignature, which puts them in the key. Keys without pins are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a repair agent is configured (deploy.yaml repair section or --repair-agent), run hands every HALTED_BEFORE_EFFECT stop to it: the agent gets the stop, the screen evidence and the record check (literals masked, identity as digests), proposes a re-anchored step, a bridge or a standard change, Flow probes it and runs the replay and fault cases, asks one person unless a signed policy covers it, and resumes. RECONCILIATION_REQUIRED stops never go to repair. --no-auto-repair or repair.auto: false turns it off; a run that can't be repaired runs plainly instead of being refused. Every stop appends one row to <store>/auto-repair/ledger.jsonl, and auto_repair.summarize reports the funnel from stops to resumed items. WorklistQueue writes the same rows for its items and test-record repairs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MockMed, all 22 drift modes, 3 trials, a real Claude repair agent and a scripted approver: 30 stops, 21 reached repair, 6 resumed and verified (relink and redesign 3 of 3 each), 0 wrong saves in 66 runs. Not repaired: a save moved into a closed menu, a new interstitial, a new required choice, and font/zoom false stops of a pixel postcondition. The worklist run (vendor change after the pre-flight): 10 stopped items, 2 repairs on the test record, 2 person decisions, 10 of 10 resumed and verified. Routed offline: the 8 recorded OpenEMR stops behind #1347 and the 3 openIMIS relabel stops of #1331 (before #1337) were all RECONCILIATION_REQUIRED, so none reached repair. Docs: SELF_REPAIR (repair is the default, Measured), REPAIR_LIFECYCLE, LIMITS, JIT (reuse binding), CAPABILITIES; llms-full.txt and the public artifact inventory regenerated with their scripts. 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>
…enIMIS live The live openIMIS run of the relabelled Save (#1331, with #1337 merged locally and a scripted risk review) found two self-repair bugs: - a re-targeted save that carries a submit_binding got the DOM locator under the new point, and a step may not carry both, so the patched bundle failed to load even though the agent found the right control. The patch now keeps such a step visual and keeps its binding. - the halted session stays open while a probe opens another, and two Playwright drivers can't share a thread. Tests hid it behind their fixture's shared browser. run now shares one browser for every session of a repair. Measured: with the review, the stop reaches repair 3 of 3 (0 of 3 before); the agent found SAVE CLAIM 2 of 3; no fix passed the probe, because the replay sees the Save disabled where the template was cropped from the enabled button. Nothing was written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An agent that gave up returned None, so the call that decision took never reached the ledger: the MockMed run's ledger shows 18 calls and $0.24 where the agent's own log has 30 and $0.43. An agent can now give up with a proposal that has no action and still report its calls. The benchmark's agent does, and the README gives the numbers from the agent's log. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…protocol Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fault # Conflicts: # llms-full.txt
…page zoom Silent-saves 1.1.0 (#1380) stopped the compiled MockMed program on all 28 items of three UI-change blocks, and self-repair fixed none. Two causes are fixed here: - relink: a repaired copy of an admitted agent-compiled (JIT) program was refused by the run gate (DraftNotAdmitted), because its admission pins the digest every patch changes. A repair keeps the record check, so the admission now carries over: lifecycle.admit_repair writes a repair_probe admission (runs only behind a gate that stops before the save) and, after the person's approval or a signed standard change, a repair admission that embeds and re-verifies the prior one. A copy that changes the record check or parameters is refused. - zoom: at 125% a correct screen failed its region_stable postcondition (the content is 25% larger and further from the origin). The check now also looks at the page scale this step's own click showed, only when both axes agree, within one percent, at the same thresholds. A covered region fails. Measured on the same program and items: 18 of 28 now end correct (zoom 9 of 9 with no stop, relink 9 of 9 after one repair), 0 wrong saves. The What's New block (notice) still stops: its fix clicks "Continue to tasks", which the fail-closed write classifier reads as a possible save; the same classification causes #1377. Both need a person's classification. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CAPABILITIES.md # llms-full.txt # packages/flow/public-artifacts.json
Member
Author
abrichr
added a commit
that referenced
this pull request
Oct 20, 2026
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.
This work was built stacked on #1325 (the bridge); #1325 has since merged, so this diff is against main alone.
Compiled programs stop rather than guess when the app changes. Before this PR, a stop waited for a person to start self-repair. Now repair is the default response to a stop, and its success rate is measured, failures included.
What changes
Repair by default (
openadapt_flow.repair.auto_repair). When a deployment names a repair agent (a newrepair:section indeploy.yaml, or--repair-agent),openadapt-flow runhands every stop that wrote nothing to it:RepairRequest: each save's effect contracts with literal values masked, and its record identity as digests.WorklistQueue.How each stop is routed:
HALTED_BEFORE_EFFECTRECONCILIATION_REQUIREDundo list. Never repair.How to turn it off, and what happens when a run can't be repaired:
--no-auto-repair, orrepair.auto: false, turns it off.--self-repairstill means "require repair", and then such a run is refused.Every stop is one row in
<store>/auto-repair/ledger.jsonl, andsummarize()reports the funnel from stop to resumed item.Program reuse bound to intent (
jit/signature.py,jit/compile_agent.py). The admission manifest now pins two things:environment_idsuch as the app version, or a callable that reads it.A run refuses a mismatch. A new app version at the same origin is refused instead of silently reusing the old program.
TaskSignature(record_check=, environment=)puts both in the key. Keys without pins are unchanged, so existing caches keep working.Measured (
packages/flow/benchmark/auto_repair/)The agent was a real Claude agent (
claude-sonnet-5-5, one call per proposal). The approver was scripted and approves everything that reaches it, so a bad fix that passed the probe would have been saved. Total model spend was $0.99 in the committed runs (from the agent's own call logs), plus about $0.15 in uncommitted diagnostic probes.What these numbers show:
Repaired: relink 3/3 and redesign 3/3. Each took one call ($0.013–0.015) and one tap.
Not repaired, nothing written:
menu: the agent can't see inside the closed menu.notice: the fix needs two clicks, and "Continue" reads as a possible save to the fail-closed classifier.provider: the agent declined to choose a provider.fontandzoom: false stops from a pixelregion_stablepostcondition. No click fixes these.The first three need a look at more than one screen, which is feat(flow): search repair bridges on a staging copy, scored by read-back #1302's staging search.
OpenEMR: all 8 recorded stops are
RECONCILIATION_REQUIRED, because "Create New Patient" (step_039) counts as a save and had already acted. Repair can't help these; OpenEMR: identity check at 'Confirm Create New Patient' reads a background label behind the modal and refuses every 9-17 items #1347 needs its cause fixed.openIMIS: with fix(flow): halt a relabelled Save on the Save, not the scroll before it #1337 and the risk review, the relabelled Save halts on the Save before any effect and reaches repair, 3 of 3 (0 of 3 before). The agent found "SAVE CLAIM" in 2 of 3. No fix passed the probe: in the replay the Save is still disabled (grey) while the scroll looks for it, and the new template was cropped from the enabled button. Nothing was written.
Person time: not timed (the approver was scripted). The measure is the count of decisions: one per repaired stop, or one per worklist group.
Silent-saves 1.1.0: the 28 items arms 3 and 3r always stopped on (#1380)
Re-run on the same admitted MockMed program, items and store judge (
benchmark/auto_repair/measure_silent_saves.py, which needs #1380'sbenchmark/silent_saves):region_stablenow also matches at the page scale its step's own click showed (both axes must agree; ±1%; same thresholds; a covered region still fails).relink: the agent had proposed the right control in bench: silent-saves 1.1.0, repeated runs under UI change, four arms (stacked on #1352) #1380, but a repaired copy of an admitted agent-compiled (JIT) program could never run. Its admission pins the digest, which every patch changes, so the probe failed with
DraftNotAdmitted. A repair keeps the record check, so the admission now carries over (jit.lifecycle.admit_repair):repair_probe, which runs only behind a gate that stops before the save;repair, after the person's approval or a signed standard change, embedding the prior admission and verifying it again.A copy that changes the record check or the parameters is refused.
zoom: a false stop on a correct screen at 125%. bench: silent-saves 1.1.0, repeated runs under UI change, four arms (stacked on #1352) #1380's older engine also declined the halt as not repairable.
notice / [arm 2, items 46-55] What's New dismiss click gets record_written from effect mining, so the canary refuses #1377: the fix is two clicks, "Continue to tasks" then "Open". The fail-closed write classifier reads "Continue" as a possible save, so the patch is refused, and clicking it on a real record to find out is exactly what the probe avoids. The same classification is [arm 2, items 46-55] What's New dismiss click gets record_written from effect mining, so the canary refuses #1377: arm 2 attaches a placeholder
record_writtento that click, so the canary refuses. Both need a person's one-time classification of that control (asqualify set-riskdoes for openIMIS); neither is loosened here.Spend: 21 agent calls, $0.31. 10 of them were on
notice, one per item: a single run repairs only its own stop, andWorklistQueuegrouping would ask once.Bugs the live openIMIS run found (fixed here)
Patch failed to load. A re-targeted save that carries a
submit_binding(openIMIS's icon-only Save, recorded on the visual path) got the DOM locator under the new point, and a step may not carry both. The patched bundle failed to load even though the agent found the right control. The patch now keeps such a step visual and keeps its binding.Probe couldn't open a session. The halted session stays open while a probe opens another, and two Playwright drivers can't share a thread. The tests hid this behind their fixture's shared browser.
runnow shares one browser for every session of a repair. A new test runs the CLI without the fixture's browser; it fails without the fix.Give-ups weren't counted. An agent that gave up returned
None, so the call that decision took never reached the ledger. The MockMed ledger shows 18 calls and $0.24; the agent's own log has 30 calls and $0.43. An agent can now give up with an action-less proposal that still reports its calls.Not done
test_reviewed_risk_makes_the_halt_repairablemust expect(step, "replace", result); the bridge changed_repairable's return value.Tests
tests/test_auto_repair.py: routing, ledger and funnel.tests/test_auto_repair_cli.py: live MockMed. It covers on-by-default fromdeploy.yaml,--no-auto-repair, and the plain-run fallback.tests/test_jit_reuse_binding.py: 16 tests.WorklistQueuetest intests/test_standard_change.py.🤖 Generated with Claude Code