Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 96 additions & 3 deletions AUTONOMY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<slot>.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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
6 changes: 5 additions & 1 deletion agents/conductors/batch/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<slot>` 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:`. |

Expand Down Expand Up @@ -137,7 +139,9 @@ terms written into `PyAutoMind/batches/<date>-<am|pm>.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

Expand Down
35 changes: 31 additions & 4 deletions skills/batch/batch.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:

Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
54 changes: 54 additions & 0 deletions tests/test_autonomy_doctrine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading