feat(attended): give the bridge a brake, and say when a model pulled it - #22
Draft
abrichr wants to merge 2 commits into
Draft
feat(attended): give the bridge a brake, and say when a model pulled it#22abrichr wants to merge 2 commits into
abrichr wants to merge 2 commits into
Conversation
Two changes that are only sound together. 1. `reject_attention`. Flow gained `reject` -- an explicit "stop, this is wrong" that TERMINATES a run. This bridge could already answer `continue`, which resumes the run and can write to the system of record, but had no way to say stop. A configuration that can say "proceed" and cannot say "stop" is not the safer one: the only thing withholding the action removes is the brake. It is NOT gated on `live_actions_ready`. That gate exists because continue and skip need Flow's deployment-bound executor to re-read the application and act on it. Rejecting actuates nothing and resumes nothing, so it has nothing to gate on -- the same reason Flow's own `_allowed_actions` offers it at a pause carrying no resolvable action step at all. The tool description states the two distinctions a model would otherwise collapse: escalate PARKS the run for a colleague, teach changes FUTURE runs, reject ends THIS one and asserts nothing about the saved workflow. Its confirmation flag must be explicitly true; ending a run is not something to do by omission. The schema stays closed with no free-text property. 2. `decided_by="automation"` on EVERY decision this bridge submits, not only on reject. This is the half that makes the first half safe. `operator` is derived from the same `_local_operator_identity()` a person's own console uses, so a model's answer and a person's answer from one machine were indistinguishable in Flow's journal, and any agreement rate computed over it silently mixed the two populations. The bias this fixes was always mostly about `continue`, which has been model-callable all along -- attaching provenance only to the action added last would leave the population that actually matters unlabelled. Measurement integrity does not require withholding the action. It requires knowing who decided. Those are different problems, and only the second one is real here. The `openadapt-flow` floor rises to 1.26.0: `reject`, its `rejected_by_operator` disposition, and the `decided_by` keyword are named symbols that only exist there. A resolver satisfied by 1.25.x fails at CALL time with a TypeError, inside the attended decision path. One stale expectation is corrected: the capability's `allowed_actions` now contains `reject` because the ENGINE offers it, not because this bridge adds one. The bridge still relays Flow's signed set verbatim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM
The new reject/provenance cases introduced the only formatting drift in this file; main was clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM
Member
Author
|
CI diagnosis (2026-07-28): all four red jobs fail during dependency installation because this branch correctly requires openadapt-flow 1.26 or newer, while PyPI currently stops at 1.25.1. CodeQL, dependency review, and secret scan pass; the focused Agent tests pass against the local Flow decider branch. Do not lower the dependency floor: current Flow main does not yet expose the decided_by API, so that would turn an installation refusal into a runtime incompatibility. Exit condition: merge the reviewed Flow decider contract into the coordinated Flow 1.26 release, publish 1.26, rebase this PR once onto current Agent main, then run one authoritative matrix. |
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.
Two changes, and they are only sound together.
1. The bridge could say "proceed" but not "stop"
openadapt-agentexposes attended answers as model-callable MCP tools. It offerscontinue_attentionandskip_attentionwhenever a deployment-bound live executor is configured — andcontinueresumes the run, which can write to the system of record. It had no way to say stop.That asymmetry is the decisive fact. Withholding
rejectdid not make the configuration safer; the only thing it removed was the brake.reject_attentionis deliberately not gated onlive_actions_ready. That gate exists because continue and skip need the executor to re-read the application and act on it. Rejecting actuates nothing and resumes nothing, so it has nothing to gate on — the same reason Flow's own_allowed_actionsoffers it at a pause carrying no resolvable action step at all.The tool description states the two distinctions a model would otherwise collapse:
escalateparks the run for a colleague,teachchanges future runs,rejectends this one and asserts nothing about the saved workflow. Its confirmation flag must be explicitlytrue— ending a run is not something to do by omission — and the schema stays closed with no free-text property.2.
decided_by="automation"on every decision, not only rejectThis is the half that makes the first half safe, and it is the larger finding.
operatoris derived from the same_local_operator_identity()a person's own console uses. So a decision a model submitted and one a person submitted from the same machine carried an identical operator string in Flow's journal. Any agreement rate computed over it silently mixed the two populations, with no way to separate them afterwards.Measurement integrity does not require withholding the action. It requires knowing who decided. Those are different problems, and only the second one was real.
The bias was always mostly about
continue, which has been model-callable all along. Attaching provenance only to the action added last would have left the population that actually matters unlabelled — so every decision this bridge submits declares itself.Mutation proof
Each mutation is caught by the tests written for it.
decided_by="automation"→"human"reject_attentiongated behind / removed from the no-executor setNotes
allowed_actionsnow containsrejectbecause the engine offers it, not because this bridge adds one. The bridge still relays Flow's signed set verbatim and never adds, removes, or reorders an action.executegain the defaulted keyword. Two now record the value so a test can pin that the bridge declares itself; a fake that silently acceptedunknownwould hide a bridge that stopped declaring.rejected_by_operatoris a closed enum member, and the tool schema has no unconstrained string property.Local verification
tests/test_attended_bridge.py: 16 passed against an editableopenadapt-flowatmain.ruff check src tests: clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM