From a91b42c4ca8211b205ae4eb92526c3a606caf301 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Tue, 3 Nov 2026 13:10:52 +0000 Subject: [PATCH] docs: AUTONOMY.md shift briefs and unattended glance (2026-11-03); batch brief rule Autonomous orchestrators phase 1 (#551): five dated sub-rules each with a revert condition, Consumers + hard-invariant entries, the batch skill and conductor brief rule, and doctrine tests pinning the new section. Co-Authored-By: Claude Opus 5.5 --- AUTONOMY.md | 99 ++++++++++++++++++++++++++++++- agents/conductors/batch/AGENTS.md | 6 +- skills/batch/batch.md | 35 +++++++++-- tests/test_autonomy_doctrine.py | 54 +++++++++++++++++ 4 files changed, 186 insertions(+), 8 deletions(-) diff --git a/AUTONOMY.md b/AUTONOMY.md index 1e9a0ec0..71cc038d 100644 --- a/AUTONOMY.md +++ b/AUTONOMY.md @@ -526,6 +526,94 @@ It is not adopted here; it needs its own doctrine edit. auto-merge found to have bypassed one of the never-auto-merge rules retires this section and returns every merge to `/prm`. +## Shift briefs and unattended `glance` — 2026-11-03 + +Approved by the human 2026-11-03 as the rulings of the autonomous-orchestrators +epic (`PyAutoMind/draft/feature/pyautobrain/autonomous_orchestrators_epic.md`, +"Human decisions"). This section is that dated doctrine edit. It widens two +earlier sections — "What a batch launch is" and "Merge authority follows +Consequence" — and narrows nothing else. The record format is +`PyAutoMind/batches/AGENTS.md` "Shift brief"; the one process allowed to keep +time is `PyAutoMind/policy/shift_watcher.md`. + +### Shift brief = launch — 2026-11-03 + +A human-written **shift brief** — a `brief:` block with `review-at:` in +`PyAutoMind/batches/.md` — **is the launch act** "What a batch launch is" +requires. The human performs the dispatch by writing it. The grant covers the +brief's named tasks, then its backlog-dip policy (`none` | `fill-idle` | +`scoped:` plus `exclude:`), and nothing else, until `review-at:`. Inside that +grant only, it replaces "never dispatch on a schedule": the deterministic +watcher may start sessions for work the brief covers. A watcher tick with **no +open brief dispatches nothing**, and a brief whose `review-at:` has passed is +not open. + +*Revert condition:* one dispatch found outside a brief's scope returns batching +to per-task `--auto` until the cause is found. + +### Unattended `glance` merges — 2026-11-03 + +This extends the 2026-10-02 table. For a declared `glance` task under an open +brief, **the in-turn wait may be replaced by a watcher-started close-out after +CI lands**, only when every one of these holds: + +1. an **independent session** ran the `Witness:` — not the author; a different + model where one is available, as leg 5 already requires of the adversary; +2. Heart is **not RED**, and any YELLOW reason not in the brief's `heart-ack:` + matches one of its `expected-effects:` lines (leg 4 under a batch launch, + applied per shift); +3. the brief's **`merge-cap:`** is not yet reached this shift; +4. the `glance` **Shadow window row is appended** at close-out, as today; +5. the task is `glance` — **never `judge`**, never a `decision-taken` PR, never + a release, and never anything the "Never auto-merged" list above excludes. + +Every `/prm` guard applies to that close-out verbatim. + +*Revert condition:* a `reverted` row demotes the tier exactly as the 2026-10-02 +section says; one merge past a brief's cap retires this sub-rule. + +### One Mind writer per shift — 2026-11-03 + +During a shift, **only the orchestrator commits to PyAutoMind**, from its own +temporary worktree (cherry-pick onto `origin/main`, never the canonical +checkout). Workers never touch Mind: they write their ledger lines — the +`active.md` claim, the close-out — as files in the lease directory, and the +orchestrator commits them. The reason is three losses on the shared checkout: +an unconditional `reset --hard` that wiped another session's edit +(2026-09-30), `pull --rebase --autostash` committing conflict markers into +`state.json` (2026-10-07), and `prompt_sync_push` sweeping another session's +dirty files into its own commit. + +*Revert condition:* one lost Mind edit traced to a worker commit during a shift +returns that orchestrator to human-dispatched batches until the path is closed. + +### Goal decomposition files drafts only — 2026-11-03 + +When the orchestrator breaks a brief's goal into tasks, each task is **filed as +a draft** with a proposed `Witness:`, `Consequence:` and `Lane:`, and approved +in bulk at check-in. Only already-formalised prompts run; a draft it filed this +shift never runs this shift. + +*Revert condition:* never loosened before the `glance` Shadow window is +confirmed and the pilot's overrule rate says so — loosening is its own dated +edit. + +### The push-notification test — 2026-11-03 + +A push to the human's phone is sent only when **waiting until `review-at:` +would cause harm or waste the shift**. Four classes qualify: + +- data loss, or a destructive action at risk; +- a breach of the RAL rules, or runaway usage; +- the whole shift blocked on one human decision; +- a credential or security problem. + +Everything else goes in the digest. The budget is **about one push per shift**, +and a second push in one shift is itself a digest finding. + +*Revert condition:* two pushes in one shift that the human judged unnecessary +retire push for that orchestrator. + ## Human override for Heart RED (development only) Heart RED still parks every autonomous run and blocks every release. After the @@ -735,9 +823,10 @@ Codex hook list. ## Hard invariants (every level, no exceptions) - **Merge and issue-close are human acts, except as "Merge authority follows - Consequence" (2026-10-02) grants them** — a declared `notify` or witnessed - `glance` task, authorized at plan or launch approval, merged in-turn on green - CI. Nothing else extends autonomy to merge; GitHub native auto-merge is not + Consequence" (2026-10-02) and "Shift briefs and unattended `glance`" + (2026-11-03) grant them** — a declared `notify` or witnessed `glance` task, + authorized at plan or launch approval, merged in-turn on green CI, or a + `glance` task merged by a watcher-started close-out under an open shift brief. Nothing else extends autonomy to merge; GitHub native auto-merge is not adopted and must not be assumed. - **Releases are human acts, with one dated exception.** The scheduled-nightly standing grant (2026-07-09, above) is the only path that ships a release @@ -800,5 +889,9 @@ Codex hook list. PR-open (or the tiered in-turn merge), validation checklist, calibration append; the RED-handling step points here for both the development-only human override and the narrower corrective-PR exception. +- `batch`, `start_dev`, `prm` — "Shift briefs and unattended `glance`" + (2026-11-03): the brief as the launch act, the backlog-dip scope, the + unattended `glance` close-out guards and the merge cap, one Mind writer per + shift, drafts-only decomposition, the push test. Skills must link here rather than copying the tables. diff --git a/agents/conductors/batch/AGENTS.md b/agents/conductors/batch/AGENTS.md index 1d35ad1d..936e2421 100644 --- a/agents/conductors/batch/AGENTS.md +++ b/agents/conductors/batch/AGENTS.md @@ -52,6 +52,8 @@ consulting a conductor. If a rule here starts needing to *judge* rather than | **`Unattended: ready` only** | `needs-slicing` goes to the decomposition pass; `never` never enters a batch | | **Lane match** | a session detects its own lane and plans only that one | | **Backpressure** | see below | +| **Brief scope** | under a shift brief, only its named tasks and the backlog its dip policy covers run; anything else is a dispatch outside the launch, which "What a batch launch is" forbids (`AUTONOMY.md`, 2026-11-03) | +| **Merge cap** | unattended `glance` merges stop at the brief's `merge-cap:`; each merge moves `main` and stales the next member's evidence, and the cap bounds what one unread shift can change | | **One integration root per slot, and it is local** | `integration/` is cut fresh from `origin/main` on every run and never pushed, so a re-run is a re-preview rather than a rewrite; a dirty integration worktree is skipped, never reset. The one-member-per-library-repo rule above is what keeps within-repo conflicts rare — the common case is one member with organ PRs across five repos. | | **Publishing that root is a separate act, typed by the human** | `--push` is the only remote write the batch makes, and no record can ask for it — `- integration: yes` requests the *local* preview. What it writes is a throwaway `integration/*` review ref and nothing else: never a pull request, never a base for one, never forced. A refresh that is not a fast-forward publishes `-2` rather than moving what is already there, and the ref expires at the record's `sweep-after:`. | @@ -137,7 +139,9 @@ terms written into `PyAutoMind/batches/-.md`, and the human performing the dispatch. A scheduler may build the review packet and wake a session; the act that starts work on the approved list is the human's. That is the line that keeps "never ambient" true — the schedule carries the *timing*, -never the *authority*. +never the *authority*. A shift brief written with `review-at:` is that act in +writing (2026-11-03): the human dispatches by writing it, and its scope is the +whole grant (`AUTONOMY.md`, "Shift briefs and unattended `glance`"). ## Running diff --git a/skills/batch/batch.md b/skills/batch/batch.md index d57c6acc..f1dabb69 100644 --- a/skills/batch/batch.md +++ b/skills/batch/batch.md @@ -26,8 +26,12 @@ in the backlog costs zero review-minutes. Say which it is. or a description ("something on the numba path", "the next euclid slice"). Resolve what they gave you to prompt paths, then run the planner's constraints -over exactly that set — do not silently substitute your own picks. Report, per -task they named: +over exactly that set. **Under a shift brief** (`AUTONOMY.md`, "Shift briefs +and unattended `glance`" — 2026-11-03) the brief *is* the human's composition: +its named tasks go first, then the backlog dip its policy allows (`none` | +`fill-idle` | `scoped:` plus `exclude:`). Backlog the brief does not cover is +never dipped, and every dipped pick carries a one-line reason in the digest. +Report, per task they named: - **taken** — with its tier and review-minutes; - **rejected, and why** — in the planner's own words. The reasons that come up @@ -49,7 +53,10 @@ open, and `Unattended: never`. Those are not preferences. (`AUTONOMY.md`, "What a batch launch is"): membership fixed at approval, the grant expiring with the shift — the interval from dispatch to the `review-at:` they declare here. Never dispatch on your own initiative, on a schedule, or -because a batch looks ready. +because a batch looks ready. **Writing a shift brief with `review-at:` is the +dispatch** (2026-11-03): inside that brief's scope, and only until its +`review-at:`, the watcher may start sessions without a second "go" +(`PyAutoMind/policy/shift_watcher.md`). In order: @@ -84,6 +91,11 @@ In order: **The member-session contract (2026-08-31, both measured that night):** + - *Spawned from the orchestrator's session template, never bare* + (2026-11-03). Under a brief, every worker starts from the same template + the orchestrator uses — sources, bootstrap, the brief's path — so the two + points below are met by construction rather than remembered. + - *Spawned cloud sessions need sources and a bootstrap preamble.* A session created bare (environment inherited, no source repos) has no clones, no skills and no GitHub scope — the whole first wave died on @@ -109,6 +121,12 @@ In order: collect fills it in. 5. Confirm what was dispatched, and stop. Do not follow the runs. +**Planned verbs — not code yet.** `batch brief --check` (validate a brief +against the `batches/AGENTS.md` schema) arrives in phase 2 of the +autonomous-orchestrators epic; `batch dispatch` and `batch digest`, with the +watcher, in phase 3. Until then a brief is written and checked by hand, and the +steps above are the dispatch. + ## When they come back — collect, review, close (2026-08-31) **Collect first, before the human reads anything.** It is one command: @@ -195,6 +213,14 @@ review file's path) and **`review-minutes-actual:`**. That last number is the only calibration the review estimate will ever get, so ask for it if the human does not volunteer it. +**Under a brief, the digest is the review surface** (2026-11-03). Collect +appends a `## Digest` to the record — brief work, then backlog picks with a +one-line reason each, one proof packet per task, then the shift counters — and +posts it once as a comment on the shift's `issue:`. The record plus that comment +is what the human reads; the HTML packet is written only when the brief says +`packet: yes`. Formats: `PyAutoMind/batches/AGENTS.md`, "Digest" and "Proof +packet". + **`delivered:` is not "green".** A cloud session's green status means it exited without an infrastructure error — not that the task succeeded. A member counts as delivered only with a PR that has a non-empty diff and checks that ran green. @@ -203,7 +229,8 @@ the top. ## Never -- Dispatch without an explicit go from the human in this conversation. +- Dispatch without an explicit go from the human in this conversation, or an + open shift brief whose scope covers the task. - Acknowledge a Heart YELLOW or RED on their behalf, or reuse a previous shift's reason set. - Add a member the planner rejected without saying which rejection you are diff --git a/tests/test_autonomy_doctrine.py b/tests/test_autonomy_doctrine.py index 19b4a42c..66325c62 100644 --- a/tests/test_autonomy_doctrine.py +++ b/tests/test_autonomy_doctrine.py @@ -85,3 +85,57 @@ def test_revert_condition_returns_the_ship_checkpoint_to_park_and_ask(): assert "park-and-ask" in sec # the second, 2026-09-07 condition: one unwanted PR retires the extension assert "would have wanted stopped before it existed" in sec + + +# --- Shift briefs and unattended glance (2026-11-03, PyAutoBrain#551) ------- + +SHIFT_HEADER = "## Shift briefs and unattended `glance` — 2026-11-03" +SHIFT_SUBRULES = ( + "### Shift brief = launch — 2026-11-03", + "### Unattended `glance` merges — 2026-11-03", + "### One Mind writer per shift — 2026-11-03", + "### Goal decomposition files drafts only — 2026-11-03", + "### The push-notification test — 2026-11-03", +) + + +def _shift_section(): + """The shift-brief H2 body, raw (header line to the next ``## ``).""" + assert AUTONOMY.count(SHIFT_HEADER) == 1, "shift-brief heading missing or duplicated" + rest = AUTONOMY[AUTONOMY.index(SHIFT_HEADER) + len(SHIFT_HEADER):] + return rest[:rest.find("\n## ")] + + +def test_shift_section_sits_after_merge_authority(): + # It extends the 2026-10-02 section, so it is read after it. + assert AUTONOMY.index("## Merge authority follows Consequence — 2026-10-02") \ + < AUTONOMY.index(SHIFT_HEADER) + + +def test_shift_section_has_five_dated_subrules_each_with_a_revert_condition(): + sec = _shift_section() + for head in SHIFT_SUBRULES: + assert head in sec, head + assert _norm(sec).count("revert condition:") == len(SHIFT_SUBRULES) + + +def test_unattended_glance_never_reaches_judge_or_decision_taken(): + sec = _norm(_shift_section()) + assert "never judge" in sec + assert "never a decision-taken pr" in sec + assert "never a release" in sec + assert "merge-cap:" in sec + assert "independent session" in sec + + +def test_brief_is_the_launch_and_no_brief_dispatches_nothing(): + sec = _norm(_shift_section()) + assert "is the launch act" in sec + assert "no open brief dispatches nothing" in sec + + +def test_consumers_and_invariant_name_the_shift_section(): + consumers = AUTONOMY[AUTONOMY.index("## Consumers"):] + assert "Shift briefs and unattended `glance`" in consumers + invariants = _norm(AUTONOMY[AUTONOMY.index("## Hard invariants"):]) + assert "\"shift briefs and unattended glance\" (2026-11-03) grant them" in invariants