From daa6f145ce6ab7945387f3e3884c6de32ea1691d Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Tue, 14 Jul 2026 10:36:21 +0100 Subject: [PATCH 1/2] feat(autonomy): human-authorized corrective-PR exception for Heart RED (#112) Heart RED forbids commit/push/PR-open at every autonomy level, yet Heart cannot clear a RED until the fixing source reaches main, wheels rebuild, and release-integration validation passes -- so a source fix that repairs the exact defect named by the RED reason cannot be shipped. A deadlock. Add a narrow, auditable, human-authorized exception to AUTONOMY.md: - Trigger: Heart RED + a source fix that directly repairs a named RED reason. - Authorization: explicit, contemporaneous human authorization quoting the exact RED reason and approving the specific corrective issue. - Permitted, nothing else: commit, push, one pending-release feature PR whose issue/plan/diff map to the named reason. - Forbidden: automatic merge, issue close, release, release rehearsal, unrelated scope. Merge stays a separate human act; every release stays blocked while Heart is RED. - Human-only: never fires under --auto; the invariant "Heart YELLOW/RED is never acknowledged autonomously" stands verbatim. - Records in four sinks (issue, PR body, active.md - corrective-red: block, autonomy_log.md corrective row); names exactly one reason when RED has several; parks without shipping on mixed-scope/stale-reason/missing-evidence/ not-causal; recovery = human merge -> fresh wheels + release-integration validation -> new Heart verdict. Ship skills (ship_library/ship_workspace/WORKFLOW) link the exception at the RED-handling step -- no policy duplication. New deterministic contract test pins the guardrails and the no-duplication seam. Co-Authored-By: Claude Opus 4.8 --- AUTONOMY.md | 81 +++++++++++++++- skills/WORKFLOW.md | 5 +- skills/ship_library/ship_library.md | 5 +- skills/ship_workspace/ship_workspace.md | 6 +- tests/test_corrective_red_exception.py | 124 ++++++++++++++++++++++++ 5 files changed, 213 insertions(+), 8 deletions(-) create mode 100644 tests/test_corrective_red_exception.py diff --git a/AUTONOMY.md b/AUTONOMY.md index 408cf84..08881eb 100644 --- a/AUTONOMY.md +++ b/AUTONOMY.md @@ -31,7 +31,7 @@ Where the dev workflow stops for a human today: | Plan approval | write plan to the issue, proceed | write plan to the issue, proceed | present + wait | | Ship PR sign-off | proceed through the autonomous-ship gate; end at PR-open | park (`awaiting-input`), question to the issue, continue elsewhere | present + wait | | Heart YELLOW | park, unless the reason set was human-acknowledged at launch (see the autonomous-ship gate) | same as `safe` | present + wait | -| Heart RED | stop, report | stop, report | stop, report | +| Heart RED | stop, report | stop, report | stop, report — a human may separately invoke the corrective-PR exception (below), which is not an autonomy level | | Merge / close | human, always | human, always | human, always | | Version ask | n/a — release stays `human-required` (sole exception: the scheduled-nightly standing grant, dated below) | n/a | ask | | Cleanup | proceed + log | proceed + log | confirm | @@ -175,6 +175,69 @@ applicability rule so "n/a" is a stated fact, never an assumption: A failed leg downgrades the run to a human checkpoint: state written to the issue, nothing force-shipped, never modify code to make a leg pass. +## Corrective-PR exception for Heart RED (human-authorized) + +Heart RED forbids commit, push and PR-open at every autonomy level (the levels +table). But Heart cannot clear a RED until the fixing source reaches `main`, +fresh wheels are built, and release-integration validation passes — so a source +fix that directly repairs the exact defect named by the RED reason cannot be +shipped, and recovery is impossible without violating policy. This section is +the **one** authorized way through that deadlock, and it is a **human act**, not +an autonomy level: it never fires under `--auto` (the hard invariant "Heart +YELLOW/RED is never acknowledged autonomously" stands verbatim — an unattended +`--auto` run on RED still stops and reports). A human invokes it, live, per +incident. + +- **Trigger** — Heart is RED, and a source fix directly repairs a defect named + by a RED reason. +- **Authorization** — explicit, contemporaneous human authorization that + (a) quotes the exact RED reason string and (b) approves the specific + corrective issue. It is recorded as a human comment on that issue; a stored, + reused or "standing" authorization does not count — it must be for this RED, + now. +- **Permitted, and nothing else** — commit, push, and opening **one** + pending-release feature PR whose issue, plan and diff all map to the named + reason. +- **Forbidden** — automatic merge, issue close, release, release rehearsal, and + any unrelated scope. Merge stays a separate human act; **every release stays + blocked while Heart is RED**. +- **Recorded in four sinks** — the authorization, the exact RED reason, the + causal mapping (reason → issue → plan → diff), the tests, and the validation + plan are written to: the **GitHub issue**; the **PR body**; the corrective + task's **`PyAutoMind/active.md`** entry (a `- corrective-red:` block naming + the reason and pointing at the authorization comment); and a + **`autonomy_log.md`** row whose outcome is tagged `corrective` (it is not an + `--auto` run, but the calibration log still records that the exception was + used). +- **Multiple RED reasons** — the authorization names exactly one. The PR body + states which reason the diff clears and that any sibling RED reasons remain, + so Heart stays RED and release stays blocked until every reason is cleared by + its own corrective PR. A corrective PR never claims to clear a reason it does + not address. + +**Failure behaviour — park without shipping** (set the `active.md` entry to +`status: blocked` or `awaiting-input`, write why on the issue, open no PR): + +- **Mixed-scope diff** — the diff touches anything beyond the named reason's + fix. The narrow permission covers only the causal fix; bundle nothing with it. +- **Stale or changed RED reason** — re-read Heart's verdict at ship time; if the + named reason string has changed, split, or cleared, the authorization no + longer matches the world. Park and re-authorize against the current verdict. +- **Missing evidence** — no causal mapping, no tests, or no validation plan. +- **Review finds the patch is not causal** — the review faculty (or a human + reviewer) judges the diff does not actually repair the named reason. + +**Recovery sequence** — the exception opens a PR; it does not resume release +work. That resumes only along this path: + +1. A **human merges** the corrective PR (a separate human act — the exception + never merges). +2. **Fresh post-merge wheels are built** and **release-integration validation** + is re-run on `main`. +3. Heart emits a **new verdict** over that fresh evidence. +4. Release work resumes only on that new verdict, and release stays + `human-required` throughout (the release cap is untouched by this section). + ## Calibration log `PyAutoMind/autonomy_log.md` — append-only. Every `--auto` run appends a row @@ -184,9 +247,11 @@ at PR-open (or on parking): | date | task | effective level | gates (tests/smoke/review/heart) | outcome | ``` -Outcome ∈ `merged-unchanged` / `amended` / `rejected` / `parked`. This is the -evidence base for raising or lowering caps — autonomy grows by demonstrated -calibration, not by optimism. +Outcome ∈ `merged-unchanged` / `amended` / `rejected` / `parked` / `corrective` +(the last records a use of the human-authorized corrective-PR exception above — +not an `--auto` run, but logged so the exception's use is auditable alongside the +autonomy rows). This is the evidence base for raising or lowering caps — autonomy +grows by demonstrated calibration, not by optimism. ### Calibration review — 2026-07-09 @@ -223,6 +288,11 @@ tier), never by weakening leg 4. - **Heart YELLOW/RED is never acknowledged autonomously.** A launch-time human acknowledgement of a named reason set is a human acknowledgement — it binds to that exact set, for that launch, and never extends to new reasons. +- **The corrective-PR exception for Heart RED is a contemporaneous human act** + (the section above), never reachable under `--auto`. It permits only commit, + push and opening one pending-release PR that repairs the named RED reason — + never merge, close, release or unrelated scope; every release stays blocked + while Heart is RED. - **Never rewrite history** (`AGENTS.md` rules apply verbatim to autonomous runs). - The `Autonomy:` header is a model's own estimate. The caps, the explicit @@ -234,7 +304,8 @@ tier), never by weakening leg 4. - `start_dev` — `--auto` usage, effective-level computation, plan-to-issue for `safe`, launch-acknowledgement recording (its "--auto mode" section). - `ship_library` / `ship_workspace` — the four-leg gate at step 4, stop at - PR-open, validation checklist, calibration append. + PR-open, validation checklist, calibration append; the RED-handling step + points here for the human-authorized corrective-PR exception. - `run_queue` — the generic queue loop: launching it is the batch's `--auto` activation; per-entry effective-level dispatch, `PARKED` checkpointing, RED stops the run. diff --git a/skills/WORKFLOW.md b/skills/WORKFLOW.md index bae88b5..618150e 100644 --- a/skills/WORKFLOW.md +++ b/skills/WORKFLOW.md @@ -153,7 +153,10 @@ pyauto-heart readiness --json # authoritative GREEN / YELLOW / RED verdict - **YELLOW** → surface the warnings; proceed only with explicit user acknowledgement (a human checkpoint at **every** autonomy level — [`../AUTONOMY.md`](../AUTONOMY.md)). -- **RED** → stop; report what failed. Do not ship. +- **RED** → stop; report what failed. Do not ship — **unless** a human + authorizes the narrow corrective-PR exception naming the exact RED reason + ([`../AUTONOMY.md`](../AUTONOMY.md) "Corrective-PR exception for Heart RED"): + commit/push/PR-open of one reason-scoped fix only, never merge or release. Tests/smoke runs that feed the verdict are Heart's domain — invoke them through the vitals faculty rather than re-deriving pass/fail criteria in the skill. diff --git a/skills/ship_library/ship_library.md b/skills/ship_library/ship_library.md index a737bff..c1c0792 100644 --- a/skills/ship_library/ship_library.md +++ b/skills/ship_library/ship_library.md @@ -53,7 +53,10 @@ pyauto-heart readiness --json # authoritative GREEN / YELLOW / RED The library test suites are part of Heart's verdict — they run as the gate, not as an ad-hoc step the skill re-judges. **GREEN** → proceed to step 4. **YELLOW** → surface warnings, proceed only on explicit user acknowledgement. -**RED** → stop and report; do not ship. If `pyauto-brain`/`pyauto-heart` are +**RED** → stop and report; do not ship — **unless** a human authorizes the +narrow corrective-PR exception naming the exact RED reason (`AUTONOMY.md` +"Corrective-PR exception for Heart RED"), which permits commit/push/PR-open of +one reason-scoped fix only, never merge or release. If `pyauto-brain`/`pyauto-heart` are unavailable, run the per-repo `pytest / -x` inside the worktree as the gate and treat any failure as RED (WORKFLOW.md). Under `--auto`, this step is the four-leg **autonomous-ship gate** — tests (+ downstream dependents on diff --git a/skills/ship_workspace/ship_workspace.md b/skills/ship_workspace/ship_workspace.md index 9a56454..fbae93d 100644 --- a/skills/ship_workspace/ship_workspace.md +++ b/skills/ship_workspace/ship_workspace.md @@ -50,7 +50,11 @@ pyauto-heart readiness --json # GREEN / YELLOW / RED ``` Workspace **smoke tests** are part of Heart's verdict. **GREEN** → execute. -**YELLOW** → proceed only on explicit acknowledgement. **RED** → stop. If the +**YELLOW** → proceed only on explicit acknowledgement. **RED** → stop — +**unless** a human authorizes the narrow corrective-PR exception naming the +exact RED reason (`AUTONOMY.md` "Corrective-PR exception for Heart RED"), which +permits commit/push/PR-open of one reason-scoped fix only, never merge or +release. If the organism CLIs are unavailable, run `/smoke_test` (with `activate.sh` sourced) as the gate and treat any failure as RED. Under `--auto`, this step is the four-leg **autonomous-ship gate** (`AUTONOMY.md` "The autonomous-ship gate"); diff --git a/tests/test_corrective_red_exception.py b/tests/test_corrective_red_exception.py new file mode 100644 index 0000000..eb2b31f --- /dev/null +++ b/tests/test_corrective_red_exception.py @@ -0,0 +1,124 @@ +"""Doc-contract tests for the human-authorized corrective-PR exception to +Heart RED (PyAutoBrain#112). + +The exception is doctrine, not code, so its guardrails are pinned against the +canonical text of AUTONOMY.md — a weakening edit (dropping a forbidden action, +a record sink, or the human-only clause) fails loudly here rather than silently +widening the one authorized path through a RED gate. The seam tests also pin +the "link, don't duplicate policy" contract: the ship skills point at +AUTONOMY.md and do not carry a second copy of the permitted/forbidden lists. +""" + +from pathlib import Path + +BRAIN_HOME = Path(__file__).resolve().parents[1] +AUTONOMY = (BRAIN_HOME / "AUTONOMY.md").read_text() +SHIP_LIBRARY = (BRAIN_HOME / "skills" / "ship_library" / "ship_library.md").read_text() +SHIP_WORKSPACE = (BRAIN_HOME / "skills" / "ship_workspace" / "ship_workspace.md").read_text() +WORKFLOW = (BRAIN_HOME / "skills" / "WORKFLOW.md").read_text() + +SECTION_HEADER = "## Corrective-PR exception for Heart RED (human-authorized)" + + +def _norm(text): + """Lowercase, strip markdown emphasis, and collapse whitespace so assertions + ignore ``**``/``` ` ``` and line wrapping.""" + return " ".join(text.replace("*", "").replace("`", "").lower().split()) + + +def _section(): + """The corrective-PR exception section body (header to the next ``## ``).""" + start = AUTONOMY.index(SECTION_HEADER) + rest = AUTONOMY[start + len(SECTION_HEADER):] + end = rest.find("\n## ") + return _norm(rest if end == -1 else rest[:end]) + + +def test_section_exists(): + assert SECTION_HEADER in AUTONOMY + + +def test_permitted_set_is_commit_push_pr_open_only(): + sec = _section() + for verb in ("commit", "push", "opening one"): + assert verb in sec, verb + # the permission is explicitly bounded + assert "nothing else" in sec + + +def test_forbidden_set_named(): + sec = _section() + for forbidden in ("merge", "issue close", "release", "release rehearsal", + "unrelated scope"): + assert forbidden in sec, forbidden + assert "every release stays blocked while heart is red" in sec + + +def test_four_record_sinks_named(): + sec = _section() + for sink in ("github issue", "pr body", "pyautomind/active.md", + "autonomy_log.md", "- corrective-red:"): + assert _norm(sink) in sec, sink + + +def test_park_without_shipping_cases_named(): + sec = _section() + assert "park without shipping" in sec + for case in ("mixed-scope diff", "stale or changed red reason", + "missing evidence", "not causal"): + assert case in sec, case + + +def test_multiple_reasons_names_exactly_one(): + sec = _section() + assert "multiple red reasons" in sec + assert "names exactly one" in sec + + +def test_recovery_sequence_is_merge_then_fresh_validation_then_new_verdict(): + sec = _section() + assert "recovery sequence" in sec + assert "human merges" in sec + assert "post-merge wheels" in sec + assert "release-integration validation" in sec + assert "new verdict" in sec + assert "human-required" in sec # release cap untouched + + +def test_exception_is_human_only_never_under_auto(): + sec = _section() + assert "human act" in sec + assert "never fires under --auto" in sec + + +def test_never_acknowledged_autonomously_invariant_survives_verbatim(): + # The exception must not weaken this hard invariant; it stays word-for-word. + assert ( + "**Heart YELLOW/RED is never acknowledged autonomously.**" in AUTONOMY + ) + + +def test_hard_invariant_bullet_scopes_the_exception(): + inv = _norm(AUTONOMY) + assert "corrective-pr exception for heart red is a contemporaneous human act" in inv + + +def test_ship_skills_link_the_exception(): + # Each ship gate points at the canonical section (link, not restatement). + marker = "Corrective-PR exception for Heart RED" + assert marker in SHIP_LIBRARY + assert marker in SHIP_WORKSPACE + assert marker in WORKFLOW + + +def test_ship_skills_do_not_duplicate_policy(): + # The exception's permitted/forbidden lists, record sinks and failure cases + # live only in AUTONOMY.md; the ship skills reference the section by name and + # must not carry a second copy that could drift. (Tokens checked here are + # distinctive to the exception — not incidental words like "autonomy_log.md", + # which ship_library legitimately uses for the calibration append.) + for doc in (SHIP_LIBRARY, SHIP_WORKSPACE, WORKFLOW): + norm = _norm(doc) + assert "- corrective-red:" not in norm # the active.md block form + assert "park without shipping" not in norm # the failure-case list + assert "recovery sequence" not in norm # the resume-release steps From 23ac90136c8dae0cdfe9fbb233929d1c06e897be Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Tue, 14 Jul 2026 10:41:55 +0100 Subject: [PATCH 2/2] feat(autonomy): agent surfaces the exact RED reason for the human to quote The exception requires the human to quote the exact Heart RED reason string, but a human can only quote what they are shown. Make the agent responsible for providing the quote: when the corrective-PR circumstance arises it surfaces the exact RED reason string(s) verbatim from `pyauto-heart readiness`, together with the specific corrective request, so the human authorizes what the agent put in front of them rather than reconstructing the wording from memory. Updates the AUTONOMY.md Authorization clause and the RED-handling step of ship_library / ship_workspace / WORKFLOW; adds a contract test. Co-Authored-By: Claude Opus 4.8 --- AUTONOMY.md | 7 ++++++- skills/WORKFLOW.md | 3 +++ skills/ship_library/ship_library.md | 5 ++++- skills/ship_workspace/ship_workspace.md | 5 ++++- tests/test_corrective_red_exception.py | 11 +++++++++++ 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/AUTONOMY.md b/AUTONOMY.md index 08881eb..e486b83 100644 --- a/AUTONOMY.md +++ b/AUTONOMY.md @@ -194,7 +194,12 @@ incident. (a) quotes the exact RED reason string and (b) approves the specific corrective issue. It is recorded as a human comment on that issue; a stored, reused or "standing" authorization does not count — it must be for this RED, - now. + now. **The agent provides the quote.** When the circumstance arises, the agent + surfaces the exact RED reason string(s) **verbatim from Heart's current + verdict** (`pyauto-heart readiness`) together with the specific corrective + request it is asking the human to approve — so the human authorizes what the + agent put in front of them, never a string reconstructed from memory. It is the + human's judgement, on the agent-surfaced reason, that authorizes. - **Permitted, and nothing else** — commit, push, and opening **one** pending-release feature PR whose issue, plan and diff all map to the named reason. diff --git a/skills/WORKFLOW.md b/skills/WORKFLOW.md index 618150e..159bcf6 100644 --- a/skills/WORKFLOW.md +++ b/skills/WORKFLOW.md @@ -157,6 +157,9 @@ pyauto-heart readiness --json # authoritative GREEN / YELLOW / RED verdict authorizes the narrow corrective-PR exception naming the exact RED reason ([`../AUTONOMY.md`](../AUTONOMY.md) "Corrective-PR exception for Heart RED"): commit/push/PR-open of one reason-scoped fix only, never merge or release. + When that exception is in play, surface the exact RED reason string(s) + verbatim from `pyauto-heart readiness` so the human authorizes the quote the + agent provided rather than hunting for the wording. Tests/smoke runs that feed the verdict are Heart's domain — invoke them through the vitals faculty rather than re-deriving pass/fail criteria in the skill. diff --git a/skills/ship_library/ship_library.md b/skills/ship_library/ship_library.md index c1c0792..9777e5a 100644 --- a/skills/ship_library/ship_library.md +++ b/skills/ship_library/ship_library.md @@ -56,7 +56,10 @@ as an ad-hoc step the skill re-judges. **GREEN** → proceed to step 4. **RED** → stop and report; do not ship — **unless** a human authorizes the narrow corrective-PR exception naming the exact RED reason (`AUTONOMY.md` "Corrective-PR exception for Heart RED"), which permits commit/push/PR-open of -one reason-scoped fix only, never merge or release. If `pyauto-brain`/`pyauto-heart` are +one reason-scoped fix only, never merge or release. When that exception is in +play, **surface the exact RED reason string(s) verbatim from `pyauto-heart +readiness`** and the specific corrective request, so the human authorizes the +quote the agent provided rather than hunting for the wording. If `pyauto-brain`/`pyauto-heart` are unavailable, run the per-repo `pytest / -x` inside the worktree as the gate and treat any failure as RED (WORKFLOW.md). Under `--auto`, this step is the four-leg **autonomous-ship gate** — tests (+ downstream dependents on diff --git a/skills/ship_workspace/ship_workspace.md b/skills/ship_workspace/ship_workspace.md index fbae93d..13bbe29 100644 --- a/skills/ship_workspace/ship_workspace.md +++ b/skills/ship_workspace/ship_workspace.md @@ -54,7 +54,10 @@ Workspace **smoke tests** are part of Heart's verdict. **GREEN** → execute. **unless** a human authorizes the narrow corrective-PR exception naming the exact RED reason (`AUTONOMY.md` "Corrective-PR exception for Heart RED"), which permits commit/push/PR-open of one reason-scoped fix only, never merge or -release. If the +release. When that exception is in play, **surface the exact RED reason +string(s) verbatim from `pyauto-heart readiness`** and the specific corrective +request, so the human authorizes the quote the agent provided rather than +hunting for the wording. If the organism CLIs are unavailable, run `/smoke_test` (with `activate.sh` sourced) as the gate and treat any failure as RED. Under `--auto`, this step is the four-leg **autonomous-ship gate** (`AUTONOMY.md` "The autonomous-ship gate"); diff --git a/tests/test_corrective_red_exception.py b/tests/test_corrective_red_exception.py index eb2b31f..c7510a4 100644 --- a/tests/test_corrective_red_exception.py +++ b/tests/test_corrective_red_exception.py @@ -85,6 +85,17 @@ def test_recovery_sequence_is_merge_then_fresh_validation_then_new_verdict(): assert "human-required" in sec # release cap untouched +def test_agent_provides_the_quote(): + # The human quotes the exact RED reason, so the agent must surface it + # verbatim from Heart's verdict — the human never reconstructs the wording. + sec = _section() + assert "the agent provides the quote" in sec + assert "verbatim from heart's current verdict" in sec + # and the ship gates instruct the agent to surface it at the RED step + for doc in (SHIP_LIBRARY, SHIP_WORKSPACE, WORKFLOW): + assert "verbatim from" in _norm(doc) + + def test_exception_is_human_only_never_under_auto(): sec = _section() assert "human act" in sec