Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/flow/docs/HARNESS2.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,41 @@ export still says which write hit the wrong patient.
In `readback` mode the same identity verdict also appears in the trajectory
Harness² proposers read, as an observation after each write.

## Repair search on a staging copy

Self-repair ([SELF_REPAIR.md](SELF_REPAIR.md)) probes a proposed fix up to
the last point before the save and stops there. That shows the fix reaches
the save on the right record. It cannot show what the save writes, and two
bridges can look the same up to the save and still write different records.

`openadapt_flow.repair.staging_search` closes that gap without touching
production. For each candidate bridge, on a resettable staging copy, it:

1. runs self-repair with that one bridge (halt, patch, probe, campaigns) and
stops at the approval request, which nobody answers;
2. runs the patched bundle end to end on a fresh copy, through the save;
3. reads the copy's system of record with the reward worker and scores it.

`StagingSearchAgent` hands production self-repair one proposal: the best
candidate the read-back verified, or none. From there the pipeline is
unchanged. Production probes it before the save and runs the campaigns, and
one person approves it before anything is saved (candidate, replay_passed,
fault_passed, approved, staged, canary, active). The search never opens a
production session.

Candidates come from `ScriptedBridges` (tests) or `ModelBridges`. A model
reads the halted step, the controls OCR finds on the live screen, and the
full traces of the halted run and the last verified runs, and answers with
control labels. The labels are mapped to points on the screen, so the model
never invents coordinates.

MockMed `?drift=menu2` is the test case. The save moved into a menu with two
save paths: Actions > Billing > Submit encounter (a Triage encounter, as
recorded) and Actions > Clinical > Submit as Consult. Both pass self-repair's
probe. On staging the Consult path writes a Consult record (wrong value,
-0.5) and the Billing path verifies (1.0), so production sees only the
Billing bridge.

## Install

Harness² needs Python 3.12 and is not on PyPI. Install it from its
Expand Down
13 changes: 13 additions & 0 deletions packages/flow/docs/REPAIR_LIFECYCLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,19 @@ it. Tests: `tests/test_agent_teach_branch.py`. A reference number in the
title makes the one-demonstration path abstain on identity too; the tests use
payer names.

## Repairs proposed during a run (self-repair)

`openadapt-flow run --self-repair` and
`openadapt_flow.repair.self_repair.SelfRepair` handle a run that halts before
any effect because a screen changed. A repair agent proposes a fix, and Flow
probes it up to the save without saving. A passing probe becomes a candidate
here: source `model_suggestion`, provenance `repaired_by: self_repair`. The
campaigns run on the evidence frames, or on live probes when the step's
identity is read from the DOM. One person's answer records the review and the
approval. The approved bundle is staged and enters its canary window, and its
first canary run is the resumed item. The agent can't review or approve the
candidate. See [SELF_REPAIR.md](SELF_REPAIR.md).

## Fleet drift patches

A repair a person approved can be contributed to the fleet hub, which
Expand Down
364 changes: 364 additions & 0 deletions packages/flow/docs/SELF_REPAIR.md

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions packages/flow/openadapt_flow/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3104,6 +3104,48 @@ def _cmd_run(args: argparse.Namespace) -> int:
# Admitted. A deployment run is not the drift-demo; force it off and delegate
# to the shared replay executor (which reads all deployment wiring itself).
args.drift = None
if getattr(args, "self_repair", False):
from openadapt_flow.cli_self_repair import cmd_run_self_repair

exit_code = cmd_run_self_repair(
args,
workflow=workflow,
bundle=bundle,
cfg=cfg,
backend_cfg=backend_cfg,
gate_kwargs={
"deployment": cfg,
"effect_verifier": effect_verifier,
"api_actuator": api_actuator,
"policy_source": policy_source,
"approval_available": bool(
getattr(args, "approve_unverified_writes", False)
),
"strict_templates": bool(getattr(args, "strict_templates", False)),
"require_encryption": not bool(
getattr(args, "allow_unencrypted", False)
),
"pinned_compiler_version": getattr(args, "pin_version", None),
"profile_contract": profile,
"effective_durable": effective_durable if profile is not None else None,
"effective_require_settled": (
effective_require_settled if profile is not None else None
),
},
params=runtime_params,
worklists=runtime_worklists,
effect_verifier=effect_verifier,
api_actuator=api_actuator,
durable=effective_durable,
allow_egress=bool(_egress),
run_replayer=_build_and_run_replayer,
approval_gate_for_run=_approval_gate_for_run,
finish=_finish_replay,
default_run_dir=_default_run_dir,
attest_pre_state=_maybe_attest_pre_state,
)
_attach_cloud_seal(args)
return exit_code
exit_code = _cmd_replay(args)
_attach_cloud_seal(args)
return exit_code
Expand Down Expand Up @@ -7657,6 +7699,9 @@ def build_parser() -> argparse.ArgumentParser:
"Max 1800."
),
)
from openadapt_flow.cli_self_repair import add_self_repair_flags

add_self_repair_flags(p)
_add_backend_flags(p)
_add_surface_flags(p)
_add_browser_session_flags(p)
Expand Down
Loading
Loading