Repository navigation
feat(flow): guard an agent you already run (CDP commit guard, run 2 as a taught tool) - #1357
Merged
Merged
Conversation
openadapt-agent serve --teach VERIFY_YAML adds teach_start, teach_observe, teach_act, teach_screen, teach_finish, teach_abort, teach_status, teach_confirm_checks and teach_admit. The connected agent is the inner agent of compile-agent's FirstRunCompiler (compile_agent.driven.DrivenRun): run 1 goes through the same harness and save gate, the record check reads the system of record, a person confirms the proposed check in the client's form, and teach_admit admits only as the registered agent the operator configured, under a person-signed policy (self-admission needs allow_self_admission). An admitted program joins list_workflows and run_workflow and gets a taught_<name> tool; the server sends tools/list_changed. FirstRunCompiler takes surface= (a deployment backend section) and uses a new SurfaceHarness off the web: same gating, and a person approves every gated action because the harness can't read which record a form shows. openadapt-flow compile-agent admit TASK_DIR is the person's terminal path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…9950e' into claude/mcp-teach-compile-a652e8
…the web Review fixes for the MCP teach tools: - An approval covers one run, one exact action and the app as it was (run id, action, actions performed so far). A changed screen, another run or another action in between refuses it; nothing is done. - Off the web, every click is gated unless it is on a ref'd text field, check box, tab or a plainly navigating control; clicks at x,y, Enter, typing outside a text field, single-letter keys and shortcuts are gated. Focus resets after Tab and other focus keys, so Space after Tab is gated. The gate uses the ref's control, not a coordinate match. - On the web, typing a space on a focused button is gated. - type with a ref refuses a ref that isn't a text field. - An action that times out ends run 1 instead of running later. - A run that fails to start stays tracked until its thread ends. - One app lock for run 1, canaries and taught runs. - The server stops an unfinished run 1 on exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ests) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n-2 handoff) openadapt-flow guard attaches to the agent's own Chromium over CDP, holds each save with the Fetch domain until the right-record check passes, blocks it and tells the agent why when it doesn't, and reads the save back with verify-run. --observe-only never blocks. A run that reads back VERIFIED becomes a JIT draft in a teach dir with a record check from scaffold-verifier; a person confirms and admits it, and openadapt-agent serve --teach serves it as a tool. compile-agent confirm_checks accepts extra no-new-row effects and keeps a check's surface route, so scaffold-verifier checks can be confirmed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…un 2 end to end A recorder refusal no longer ends the guard: the guard holds saves on its own CDP connection and the recorder runs on a separate thread. A verified save leaves no notice on the page, so run 1 is recorded as the app drew it. The scaffold-verifier check is found by and pinned to the record's identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 runs (15 scenarios x 3 arms x 2 trials) with a scripted Playwright agent, judged by the effect_e2e ground truth, plus run 2 as an MCP tool against the same faults and 5 same-load pairs. Load average is recorded on every row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uard results 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>
…roposed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps main's page-less CommitHint gating in the web harness next to the teach path's ref-aware commit_target. The teach unit tests no longer import the MCP SDK at collection (test_emit checks emission doesn't import it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… into claude/mcp-teach-compile-a652e8
Resolves the compile-agent conflicts with #1277 and #1305 as merged on main: Harness.act takes the teach path's positional ref and main's keyword hint; proposed() shows both the check's own reads and a code-as-action agent's reads. llms-full.txt regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_mcp_teach_surfaces and test_mcp_teach_cli imported mcp at module level (directly, or through pytest.importorskip). Collected into the same unit shard as test_emit, that loaded mcp before test_emit_mcp_server checks that emitting an MCP server never imports it (flow core unit shard 3/4). They now skip on a missing mcp with find_spec and import it inside the test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps both sides: the task ledger's tools and the teach tools in the MCP server (a taught workflow's own tool goes through the ledger like run_workflow), _attach_ledger on teach servers, both README sections (teach, then the ledger), both .gitleaksignore fingerprint sets, and the split command map with a compile-agent admit note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1335's files (agent/cli.py, agent/mcp.py, .gitleaksignore, the agent README) taken from its updated branch, which already merged this main; the guard section added back to the README. The guard command moved to docs/commands/11-guard-an-agent-you-already-run by scripts/migrate_branch_to_split_files.py, which also regenerated the command list, the verification docs and the path-only artifact inventory. llms-full.txt regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Takes #1335's harness docstring and its MCP teach tests (which no longer load mcp at collection); keeps the guard's CI tests, README section and capability rows. llms-full.txt regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
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.
Stacked on #1335. This branch contains #1335's current head (4999d26) and current
main. Until #1335 merges, the diff againstmainincludes #1335. This PR's own changes areopenadapt_flow/agent_guard/,benchmark/agent_guard/,docs/GUARD_AN_AGENT.md,docs/commands/11-guard-an-agent-you-already-run/, the guard tests, theconfirm_checks/proposed()change incompile_agent/pipeline.py, and the doc and capability rows.What it adds
openadapt-flow guard: a way in for developers who already run a browser agent (a Playwright script, Playwright MCP, browser-use, a computer-use agent with a Chromium). They keep the agent. The one change is a DevTools port on the browser it launches.Run 1, guarded (
openadapt_flow/agent_guard/):Fetchdomain.POST,PUT,PATCH,DELETE, unknown methods and GraphQL mutations to the app's origin are held as saves. Reads and GraphQL queries go on at once. Writes to other origins are blocked unlessguard.yamllists them undernot_saves.check_identity), and the baseline is read. The task is committed once, so a second save is blocked. Withapproval: each_item, a person approves first. After the save,verify-runreads it back.409with a JSON body, or a page for form posts, plusX-OpenAdapt-Guardand arole="alert"notice on the agent's page that names the fields that didn't match. A verified save leaves nothing on the page, so run 1 is recorded as the app drew it.--observe-onlynever blocks; it logs what it would have blocked and still reads the save back.record --browser-cdp-endpoint) records run 1 with network capture, on its own thread and connection. A recorder refusal can't stop the guard. An earlier version shared one connection, and a recorder refusal ended the guard and let a wrong-patient save through. The benchmark caught that, and it is fixed here.Run 2, compiled (
agent_guard/handoff.py): a run that reads back VERIFIED becomes a JIT draft in the teach dir, the same placeserve --teach(#1335) uses:compileproposes an API step when the save replays cleanly; the GUI steps are the fallback.check_run_record.scaffold-verifier(feat(flow): build a working record check from the app's own reads #1284), built from the app's reads,teach.db, or theverify.yamlread. It is pinned to the record's identity. If it misses a fault, it is kept for review and the before/after proposal is used instead.compile-agent admit.serve --teach --teach-dirthen serves it astaught_<name>.One change to shared code (
compile_agent/pipeline.py,confirm_checks): a check built from several reads may claim the new record once per read, routed to that read. Every claim must still claim exactly one record, pin everyexpectfield and read back every input. Extraexpected_count: 0"nothing else changed" checks are allowed. Each check keeps itssurface=route, and the proposal's ownverifier:section is used, with the reads shown inproposed(). A proposal with one plain claim, which is what the existing teach path writes, is handled exactly as before.Measured on MockMed (synthetic, no paid model calls)
The page is MockMed's SPA in front of the
effect_e2eSQLite store. The outside agent is a scripted Playwright agent with a 1 s pause before each of its 6 actions. There are 15 scenarios × 3 arms × 2 trials = 90 runs, one browser at a time, each judged by theeffect_e2eground truth. Both trials gave the same outcomes. Details:packages/flow/benchmark/agent_guard/RESULTS.md, raw rows inresults.json.guard.yaml(9 lines) andverify.yaml(16 lines) once per app, and a task file (12 lines) per task from whoever assigns the work.effect_e2efaults (8 of the 9 leave a wrong record;timeoutleaves the right one):collateral_unaudited: the REST read doesn't cover billing)timeoutreads back VERIFIED. When MockMed's page gives up after 1.2 s, the agent alone shows an error for a save that landed, and the guard reads it back VERIFIED.taught_triage_notetool over MCP took 4.39 s with 0 model calls. The guard held each save a median 7.4 ms and read it back in 41 ms; the agent's time with and without the guard was the same.What it doesn't do yet
fillwithout clicking the field (Playwright MCP does this) leaves no focus step. The compiled program then refuses at the canary, fail-closed. The scripted agent clicks first. Follow-up.screen_selector).bannertable as a read of the record. The runs reset the store between saves, so a false stop from it wouldn't show here.Tests
tests/test_agent_guard.py: 34 unit tests (request classification, guard decisions against MockMed's fault server through a fake page, fail-closed CDP adapter, config, handoff helpers). Passes locally.tests/e2e/test_agent_guard_e2e.py: a real Chromium. The guard blocks the wrong patient and verifies the right one, the run is handed over, a person admits it, and the tool catches 5 faults. Both files are added toflow-critical. Locally I ran the same steps through the harness withmcp2.3.0; the system Python'smcp1.6.0 is older than required.test_capabilities_map,test_record_sor,test_compile_agent, the agent README commands test; ruff and mypy on the new code. Not run locally: the full suite and the feat(agent): teach a workflow over MCP, then call it with no model calls #1335 teach e2e (CI runs them).🤖 Generated with Claude Code