fix(human-decision): give operator disagreement a closed wire channel - #15
Merged
Conversation
The attended vocabulary was closed at continue/skip/teach/escalate. An operator who looked at the live application and concluded the engine was right to stop had no way to say so: escalate PARKS the run for a colleague and reads as "I don't know", and continue asserts a repair that did not happen. Disagreement therefore had no channel, so the recorded answer distribution could not report how often a halt was correct -- the exact measurement needed before anyone tunes a halt rate. Add `reject` to `HumanDecisionAction` and the terminal `rejected` / `rejected_by_operator` pair to the receipt. Reject and escalate stay separate members because they do opposite things to the run: escalate leaves the durable pause resumable, reject ends it. Collapsing them is cheaper and destroys the signal. `allowed_actions` widens from four to five entries, which is the size of the vocabulary rather than a policy: a pause that is skippable, re-verifiable, rejectable, teachable and escalatable is legitimate, and a stale bound would have refused to issue that task at all. The cause of a rejection is a closed enum with one member. A reason taxonomy would be more informative, but there is no evidence yet for what its members should be -- the reject rate is the data that would design them -- and adding members later is additive, whereas a free-text reason could never be closed again.
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
The attended decision vocabulary is closed at
continue/skip/teach/escalate. When the engine is correct to halt — the record genuinely says Ineligible — the operator's only one-tap options are to assert they fixed something (verify_and_resume) or to hand it to a colleague (escalate). There is no way to say "you were right, stop."Consequences:
What
HumanDecisionAction.REJECT = "reject".HumanDecisionReceiptState.REJECTED+HumanDecisionReceiptReason.REJECTED_BY_OPERATOR, pinned as a pair inHUMAN_DECISION_RECEIPT_REASONS.allowed_actionsmax_length4 → 5.human-decision-task-v1.json/human-decision-receipt-v1.json.Design notes
rejectis not a relabelledescalate.escalateparks the run — the durable pause stays intact and a colleague can continue it.rejectterminates it. Collapsing them destroys the signal the action exists to obtain.rejectis scoped to one run. It asserts that this run must not proceed, not that the saved workflow is wrong. The latter changes future runs and belongs toteach, which carries the demonstration and requalification gate that authority requires.No free text anywhere. The cause is a closed enum with exactly one member today. A reason taxonomy would make the distribution far more informative, but there is no evidence yet for its members — the reject rate is the data that would design them — and a wrong taxonomy is worse than none. Adding members later is additive; a free-text reason could never be closed again.
max_length4 → 5 is the load-bearing part of the additive change. The bound is the size of the vocabulary, not a policy. A pause that is skippable, re-verifiable, rejectable, teachable and escalatable is legitimate, and a stale bound would have refused to issue that task at all — a refusal in exactly the case where the operator has the most choice. Covered by a new test.Release
fix:on purpose, notfeat:.major_on_zero = falsemeansfeat:would cut 0.7.0 and fall outsideopenadapt-flow'sopenadapt-types>=0.6.2,<0.7.0pin. This lands as 0.6.3, inside the pin. Flow's floor is raised to>=0.6.3in its own PR, because a floor that predates a new symbol fails at import, not at resolve.Precedent: #13 (
fix: close the attended terminal-outcome contract gap with HumanDecisionReceiptV1) added the entire receipt model underfix:.🤖 Generated with Claude Code
https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM