group-em · diff
git:20260901.f2cc548 to git:20260901.92b5ae8
364 added, 397 removed. Audit A to A.
---
name: group-em
description: "PM-GATED. Monitor peer sessions in this repo, never plan or author on their behalf."
allowed-tools: ["Read", "Bash", "Glob", "Grep", "Agent", "SendMessage", "CronCreate", "CronList", "CronDelete", "Monitor", "TaskStop"]
argument-hint: "[no arguments — invoke to start monitoring this repo's peer sessions]"
---
# Group EM — Peer-Session Monitoring and Wave Coordination
- Invoking `/group-em` switches this session into monitoring the other sessions running in the same
- repo and moving them along. It is a **mode a session enters, not an operation on a target** — the
- same shape as `/autonomous`'s toggle-a-disposition naming, borrowed for the name only (see
- Anti-Scope below; the sentinel-file mechanism does not transfer).
+ `/group-em` switches this session into monitoring the other sessions in the same repo and moving
+ them along. A **mode a session enters, not an operation on a target**.
- **PM-GATED. Only invoke when the PM explicitly asks for it, never EM-initiated on its own
- judgment.** This is a description-prefix convention, matching `staff-session`, `spinoff`, and
- `roadmap-planning` — there is no hook or lib that enforces PM-gating anywhere in this repo
- (`grep -rln "PM-GATED|pm_gated" coordinator/hooks/scripts coordinator/lib` returns nothing). The
- gate is honoured by disposition, not by a guard, until something else supplies enforcement.
+ **PM-GATED — only on an explicit PM ask, never EM-initiated.** A description-prefix convention
+ (as `staff-session`, `spinoff`, `roadmap-planning`); no hook enforces it. Honoured by disposition.
- **Dispatch authorization — invoking this skill IS the request, for the approvability-judge
- dispatch specifically.** The approvability-judge dispatch in § Delegated approve-for-execution
- procedure below is a constitutive step of that procedure, not a separate thing to get cleared:
- invoking a skill requests the actions that skill performs. This is a narrow grant, not a general
- dispatch grant for this skill — it covers spawning the named Opus persona under this session's own
- authority for that one procedure, nothing else. It does not touch the peer-session send/nudge
- mechanism (`gem-14`, § Send pass (gem-14), gated separately) — that remains outside this
- paragraph's scope; see the Anti-scope carve-out below for how the two are distinguished. Tripwire:
- `UNATTRIBUTED-HARNESS-LINE-IS-NOT-PM`.
+ **Dispatch authorization — invoking this skill IS the request, for three named dispatches only:**
+ the approvability judge (§ Delegated approve-for-execution, step 4), `coordinator:fleet-watch` and
+ `coordinator:group-em-assistant` at entry, and § Inbox-blitz delegation's assistants. Each covers
+ spawning that named agent under this session's own authority and nothing else. **None of them
+ touches the peer-session send mechanism (§ Send pass, `gem-14`), which stays gated per send.**
+ Tripwire: `UNATTRIBUTED-HARNESS-LINE-IS-NOT-PM`.
## Entry — already done by the time you read this
**Typing `/group-em` fires entry.** `hooks/scripts/group-em-autofire.py` runs the entry op ahead of
- your turn and injects the crown verdict, roster, digest and baseline as context. There is no
- command to run and no step to assemble: the passes below document what the entry op composes and
- what governs acting on its output, not a sequence to perform.
-
- **If that context is absent, entry did not happen.** The hook fails open — any failure degrades to
- silence rather than blocking your prompt — so its absence is a fact, never a reason to assume it
- worked. Run `<plugin-root>/bin/group-em-enter.py --repo <root>` yourself and find out why before
- acting as this repo's Group EM.
- (Every `<plugin-root>/bin/` CLI on this page is plugin-local and has no settings-home launcher —
- resolve per `snippets/resolve-coordinator-bin.md` § CLIs with no launcher; never cwd-relative.)
+ your turn and injects standing, roster, digest and baseline. Nothing below is a sequence to
+ perform; it documents what the op composed and what governs acting on it.
- The op is a shim over the engine's `groupem.enter`, which reads the harness registry in-process. An
- unreachable engine **refuses** (exit 7); it never quietly assembles in-tree instead. The two paths
- differ in authz classification — `groupem.enter` is MUTATING in the engine registry, the in-tree
- passes are unclassified script calls — so a silent fallback would route around a classification,
- not merely a latency budget. `--local` selects in-tree assembly explicitly when you mean it, and prints whether this tree's
- copies still match the engine's — a divergent ladder answers differently rather than failing, and
- `--local` is chosen during an outage, when that is hardest to notice. `DRIFT UNKNOWN` is an
- unknown, never a match.
+ **If that context is absent, entry did not happen.** The hook fails open, so its silence is a fact,
+ never a reason to assume success. Run `<plugin-root>/bin/group-em-enter.py --repo <root>
+ --session-id <your sid>` and find out why first. **`--session-id` is not optional** — it defaults to
+ `$CLAUDE_SESSION_ID`, unset in many shells; without it the op refuses (exit 2), and a refusal read
+ as a quiet result means you never entered. (Every `<plugin-root>/bin/` CLI here is plugin-local with
+ no settings-home launcher — resolve per `snippets/resolve-coordinator-bin.md`, never cwd-relative.)
- Exit 6 means the engine returned a digest under a refused crown: that engine composes its legs
- independently and has already armed cooldowns for peers this session has no standing to offer.
- The payload is refused whole. Re-run once the engine mirror carries the crown-gate fix.
+ The op shims the engine's `groupem.enter`. An unreachable engine **refuses** (exit 7) rather than
+ quietly assembling in-tree: the two paths differ in authz classification, so a silent fallback would
+ route around a classification, not a latency budget. `--local` picks in-tree assembly explicitly and
+ prints whether this tree's copies match the engine's; `DRIFT UNKNOWN` is an unknown, never a match.
+ Exit 6 means a digest under a refused standing — that engine armed cooldowns for peers you have no
+ standing to offer. Refuse the payload whole; re-run once the mirror carries the standing-gate fix.
- Do not import `read_pass` or `send_pass` directly. They are the entry op's collaborators; reaching
- past it means supplying your own session id and re-deriving state the op already holds.
+ Do not import `read_pass` / `send_pass` directly. They are the op's collaborators.
- **Crown first, and last writer wins.** Whoever invokes `/group-em` most recently holds the role.
- Entry never refuses over an incumbent and there is no override flag to reach for — a nomination
- record outlives the session that earned it, so "someone is listed" is not "someone is
- coordinating," and the overwhelmingly common incumbent is a session that has simply exited.
- Re-entry by the holder itself is a refresh, displacing nobody.
+ **Standing first, last writer wins.** Whoever invokes most recently holds the role; entry never
+ refuses over an incumbent and there is no override flag. A nomination record outlives its session,
+ so "someone is listed" is not "someone is coordinating" — the common incumbent has simply exited.
+ Re-entry by the holder is a refresh.
- **A displaced holder that is still running is OWED a message, this turn.** `displaced_holder` and
- `displaced_holder_live` arrive in the entry context. Live means that session believes it is this
- repo's Group EM and will act on it; that is the one send this mode owes rather than offers, and
- it does not wait on the § Send pass (gem-14) gates — those weigh an interrupt against its cost to
- the receiver, and a peer acting under a role it has already lost is not a case where the answer
- is in doubt. Not live means nobody to tell. A refusal that still reaches you after entry vacated the
- record is not a crown-ownership problem; do not treat it as one.
+ **A displaced holder that is still running is OWED a message this turn.** `displaced_holder` and
+ `displaced_holder_live` arrive in the entry context. Live means that session still believes it
+ holds the role and will act on it: the one send this mode owes rather than offers, exempt from
+ § Send pass's gates. Not live means nobody to tell.
## What this skill does and does not do
- **Does NOT plan — never plan or author on anyone's behalf.** Reading peer state and nudging a
- stuck session is never authoring roadmap or plan content for the sessions it watches. A
- `/group-em` session never writes plan bodies, stub content, or roadmap decisions on their behalf —
- that stays each session's own.
+ **Does NOT plan or author on anyone's behalf** — never plan bodies, stub content, or roadmap
+ decisions for the sessions it watches. **DOES coordinate execution waves and cross-session
+ sequencing** — a stalled wave, a peer blocked on a dependency that has cleared, two sessions about
+ to collide on one file.
- **DOES coordinate execution waves and cross-session sequencing.** The affirmative half of the same
- boundary: noticing a wave is stalled, a session is blocked on a dependency that has since cleared,
- or two sessions are about to collide on the same file, and nudging accordingly.
+ ## Entry dispatches both standing teammates, then arms what is left — all three mandatory
**ENTRY DISPATCHES BOTH STANDING TEAMMATES, THEN ARMS WHAT IS LEFT. ALL THREE ARE MANDATORY.**
- Entry assembles once. Nothing re-runs it, no hook fires it again, and a roster read is stale within
- a minute — so a mode entered and never re-derived reports the room as it was, not as it is. In this
- order, as your first act:
+ **"Entry" here means you, on entering the mode — not the entry op.** `group-em-enter.py` assembles
+ and dispatches nobody. The autofire hook is silent either way, so its silence is not a report that
+ these were done. Entry assembles once; nothing re-runs it, and a roster read is stale within a
+ minute. In this order, as your first act:
- 1. **`coordinator:fleet-watch`** — dispatch it. It holds the `Monitor` poller over the session
- registry and pokes stopped sessions along, off your context. **The engine ships the runnable; it
- does not hand-roll one:** `python -m coordinator_core.group_em.watch --repo-root <root>
- --crown-session-id <your sid>`, `persistent: true`. **`--crown-session-id` is passed explicitly,
- never defaulted** — it is a separate id from `--caller-session-id` on purpose (the caller is
- whatever process polls; the crown is whose offer log suppresses an already-answered peer), and
- they diverge the moment a teammate holds the watch. That form needs the engine importable; there
- is no settings-home `group-em-watch` launcher yet. It emits one line per peer entering a parked
- state, derives parked from `read_pass.classify_peer` rather than a registry `status` field, and
- stays silent while that peer's send-pass offer cooldown is armed. It never disarms, so there is
- nothing to re-arm.
- 2. **`coordinator:group-em-assistant`** — dispatch it. Your standing reader: transcript tails,
- baton claimants, what landed on a path since a SHA. Warm across the session, woken by
- `SendMessage`.
+ 1. **`coordinator:fleet-watch`** — dispatch it. It starts and owns the watch SUBPROCESS and answers
+ adjudication asks off your context. **The engine ships the runnable; do not hand-roll
+ one:** `python -m coordinator_core.group_em.watch --repo-root <root> --group-em-session-id
+ <your sid>`, `persistent: true`. **`--group-em-session-id` is passed explicitly, never
+ defaulted** — it is a separate id from `--caller-session-id` (the caller is whatever process
+ polls; the Group EM is whose offer log suppresses an already-answered peer), and they diverge
+ the moment a teammate holds the watch. It emits one line per peer entering a parked state,
+ derives parked from `read_pass.classify_peer` rather than a registry `status` field, stays
+ silent while that peer's cooldown is armed, and never disarms.
+ **That form needs the engine importable**, so it starts only from a repo whose interpreter can
+ already import it; from anywhere else it is a `ModuleNotFoundError` at start-up, and a watcher
+ whose subprocess never started presents as `idle` — indistinguishable from a quiet fleet, no
+ error anywhere. A `group-em-watch` trampoline over that module exists in engine source (with
+ `--once` and `--status`) but is **not in the published engine mirror**, so it is unreachable
+ from a repo resolving the engine from the mirror. Check the mirror before reaching for it; until
+ it publishes, arm from a repo that can import the engine and confirm the subprocess started
+ rather than trusting its silence.
+ 2. **`coordinator:group-em-assistant`** — dispatch it. Your standing reader: transcript tails, baton claimants,
+ what landed on a path since a SHA. Warm across the session, woken by `SendMessage`.
3. **A `CronCreate` tick**, ~23 minutes, off the :00/:30 marks — the only clock that stays yours.
- It audits the watch rather than performing it: *is nudging actually happening, what is the state
- of the batons and the live plans, is anything stuck that nobody has poked.*
+ It audits the watch rather than performing it: *is nudging happening, what is the state of the
+ batons and live plans, is anything stuck nobody has poked.*
**Dispatch them yourself, from this session.** A teammate belongs to whoever dispatched it, so one
- dispatched by any other session relieves that session instead and leaves you watching while
- believing you were relieved. What transfers to `fleet-watch` is the `Monitor` poller; the
- `notify_when_idle` one-shot stays with you either way, because the harness accepts that parameter
- only from a main conversation and a teammate cannot hold it.
+ dispatched by another session relieves that session and leaves you watching while believing you
+ were relieved.
- **A crown holding neither teammate is what this sequence exists to prevent, and it is not
- hypothetical** — until entry named them, nothing did, and both agent bodies claim to be "kept for
- the life of their session" while no step created one. Worked case:
- `docs/wiki/group-em-crown-entry-teammates.md`.
+ **THE `Monitor` STAYS WITH YOU. It cannot be delegated, and believing otherwise is the failure this
+ step exists to prevent.** A subagent goes idle between turns, so `fleet-watch` can START the
+ subprocess and cannot hold a live wire from it to a decision-maker. Delegate the `Monitor` and you
+ get a healthy sensor writing park events that reach nobody — every event you see arrives because you
+ asked on a tick, never because anything pushed, and the arrangement looks correct from every surface:
+ teammate alive, subprocess alive, log filling. Measured here: ~50 minutes of exactly that.
+ So `fleet-watch` owns the subprocess; **you** arm the `Monitor` over its output from your own
+ session, `persistent: true`, filtering `PARKED|ESCALATE|OUT-OF-WORK|GROUP-EM-MOVED|UNKNOWN` plus
+ failure signatures. Give it a liveness arm that emits when the watch process leaves the process
+ table — a sensor that dies silently is indistinguishable from a quiet fleet. The `notify_when_idle`
+ one-shot likewise stays with you; the harness accepts it only from a main conversation.
- **Dispatch authorization — invoking this skill IS the request, for these two dispatches.** Steps 1
- and 2 — `coordinator:fleet-watch` and `coordinator:group-em-assistant` — are constitutive steps of
- entry, not a separate thing to get cleared, on the same basis as the approvability-judge dispatch
- above and § Inbox-blitz delegation's. Narrow, as those are: it covers spawning those two named
- agents under this session's own authority at entry and nothing else, and it does not touch the
- peer-session send/nudge mechanism (§ Send pass (gem-14)), which stays gated per-send.
+ **Verify each clock's holder, never assume it.** A subagent that spawned a sensor and went idle
+ renders identically to one holding a live wire. `CronList` shows your tick; the `Monitor` task must
+ appear in your own task list; the subprocess must be in the process table. Three clocks, three
+ holders, three separate checks.
- **Before arming either party, read `holder_session_id` out of `state/group-em-watch.json`.** The
- watch stamps that file every tick, so a fresh holder that is not you means someone is already
- watching this repo — arming beside them is the half-handover, and nothing refuses it for you. A
- stale or absent holder is the clear case. This is a check you perform; the engine detects the
- condition but does not yet prevent it.
+ **A Group EM holding neither teammate is what this sequence exists to prevent, and it is not
+ hypothetical.** Worked case:
+ `docs/wiki/group-em-entry-teammates.md`.
- **A fresh holder may be a prober, not a watcher.** Running the arm command at all — including
- `--max-iterations 2` to check that it works — stamps the record and names the probing session as
- holder. So ask whether that session is plausibly watching this repo before standing down: a
- holder from another repo, or one carrying `subscribed_peers: 1`, ran the command once. The record
- is untracked runtime state and ages out on its own; never repair it by hand.
+ **Before arming either party, read `holder_session_id` from `state/group-em-watch.json`.** A fresh holder
+ that is not you means someone is already watching; arming beside them is the half-handover and
+ nothing refuses it for you. But **a fresh holder may be a prober, not a watcher** — running the arm
+ command at all, `--max-iterations 2` included, stamps the record. Ask whether that session
+ plausibly watches this repo before standing down: a holder from another repo, or one carrying
+ `subscribed_peers: 1`, ran the command once. The record is untracked runtime state, ages out on its
+ own, and is never repaired by hand.
- **Both clocks, deliberately: belt-and-suspenders, PM-ruled.** A poll and an event watch fail
- differently, and whichever survives a given interval catches what the other missed. Shipping one is
- the elegant choice and the wrong one. Entry covers both in one pass — the poller via `fleet-watch`,
- the cron yours — and never arms the same clock twice.
+ **Both clocks, deliberately: PM-ruled belt-and-suspenders.** A poll and an event watch fail
+ differently. Entry covers both — poller via `fleet-watch`, cron yours — and never arms one twice.
- **That is a rule about entry, and it is NOT a bar on re-arming a clock that has been spent.**
- Prefer the clock that cannot be spent. A one-shot subscription — `notify_when_idle` is one —
- fires once for a peer and leaves that peer unwatched, so a watch armed only at entry decays
- during the session to covering just the peers that have not yet stopped, and an exhausted watch
- and a quiet repo emit identically. A poller has no such state and is the better default for that
- reason. Where a one-shot is used at all, re-arming it per peer per tick is required, not
- duplication. Reported by `claude-klabauter-ad` from a live case: a handoff recording eight watched
- peers, last re-armed at 00:26, whose watch covered nobody by the time the role was picked up.
+ **That is a rule about entry, NOT a bar on re-arming a spent clock.** Prefer the clock that cannot
+ be spent. A one-shot (`notify_when_idle`) fires once per peer and leaves it unwatched, so a watch
+ armed only at entry decays to covering the peers that have not yet stopped — and an exhausted watch
+ and a quiet repo emit identically. Where a one-shot is used, re-arming it per peer per tick is
+ required, not duplication.
**Each tick records a DECLINATION for every roster entry it does not message** — which gate failed
- and why. A tick that closes on "nothing sent" with no declination is indistinguishable from a tick
- that never looked, which is the failure this whole mechanism exists to end.
+ and why. A tick closing on "nothing sent" with no declination is indistinguishable from one that
+ never looked.
- **And each tick STAMPS those declinations to disk**, via
- `watch_heartbeat.stamp(repo_root, session_id, name, source, declinations, subscribed_peers=…)` —
- `source` is `cron` or `monitor` for a clock tick; entry stamps its own. `subscribed_peers` is the
- count of peers your `Monitor` arm is *still* subscribed to right now, not the count you armed:
- `notify_when_idle` is one-shot and unwatches per peer as it fires, so a watch that armed eleven
- peers and has since decayed to zero is not a watch, and only a live count says so. A declination
- recorded in your own context and nowhere else dies with you.
+ **And each tick STAMPS them to disk**, via `watch_heartbeat.stamp(repo_root, holder_session_id,
+ declinations, interval_seconds, subscribed_peers=…, tick_source=…)`. Copy that order: `declinations`
+ is the THIRD positional and `interval_seconds` the fourth and required, so a call shaped
+ `(repo_root, session_id, name, source, declinations)` raises rather than stamping — and a tick that
+ raises here is exactly the tick that cannot tell "looked, nothing to do" from "did not look".
+ `tick_source` is `cron` or `monitor` and is a KEYWORD, never positional; entry stamps its own.
+ `declinations` is THIS tick's rows only — each `{session_id, name, gate, reason}`, never an
+ accumulating history; a tick that declined nothing passes `[]`. `subscribed_peers` is the count your
+ `Monitor` arm is *still* subscribed to right now, not the count you armed. A declination held only
+ in your context dies with you.
+ **The engine must be importable, which it is not from the repo you are Group EM for.** `stamp`,
+ `idle_report` and the watch runnable all live in `coordinator_core`; invoked from a doctrine repo's
+ root they raise `ModuleNotFoundError` before doing anything. Run them from an engine-rooted cwd, or
+ via `<settings-home>/bin/group-em-watch`. A tick that assumes the import worked reports a stamp it
+ never wrote.
+
**The clock's absence is silent, which is why it is named at entry every time.** A tick is
- session-only: nothing on disk creates it, nothing records that it existed, and it dies with the
- session that armed it. A fleet with no watcher and a fleet with a healthy one look identical from
- every artifact — no error, no gap, no stale marker. So whoever holds the crown re-arms it
- themselves, and a session that inherits the role inherits nothing.
+ session-only: nothing on disk creates it or records that it existed. A fleet with no watcher and
+ one with a healthy watcher look identical from every artifact. A session inheriting the role
+ inherits nothing.
- **A watching session goes idle exactly like the sessions it watches.** That is what the clock is
- for. A Group EM who looks only when the PM asks has made the PM the watcher, which is the whole
- thing the role exists to stop.
+ **A watching session goes idle exactly like the sessions it watches** — that is what the clock is
+ for. A Group EM who looks only when the PM asks has made the PM the watcher.
- **What the watch is FOR.** Sessions asking permission for EM-autonomous acts they have already
+ **What the watch is FOR:** sessions asking permission for EM-autonomous acts they already
recommended; break-class defects handed up as "worth your eye"; a session idling on a peer repo
- while holding a repo-agnostic fallback it identified itself. **Not** finding sessions something to
- do.
+ while holding a repo-agnostic fallback it identified itself. **Not** finding sessions something to do.
- **Idle is not the same as done, and "don't invent scope" is not "don't act."** A session holding a
- claimed baton over a FINISHED remit needs closing out, and closing out is its existing remit rather
- than new scope. Over-applying the no-manufactured-work rule turns it into a reason not to finish
- work that already exists — the failure that reads as restraint and is abdication.
+ **Idle is not done, and "don't invent scope" is not "don't act."** A session holding a claimed baton
+ over a FINISHED remit needs closing out, and that is its existing remit, not new scope.
**Run the altitude test on the way OUT of a turn, not only when deciding to intervene.** The
- watcher's most durable failure is escalating to look deferential: a peer can see a bad push and
- refuse it, and can never see a bad escalation that goes over its head. *"This session has been idle
- 35 minutes, do you want it doing something?"* is the canonical shape, and it is the EM's call.
+ watcher's durable failure is escalating to look deferential: a peer can refuse a bad push and can
+ never see a bad escalation that goes over its head. *"This session has been idle 35 minutes, do you
+ want it doing something?"* is the canonical shape, and it is the EM's call.
+ ## Liveness instruments — two sanctioned, one banned, and you read the banned one constantly
+
+ **`ListAgents`' `busy`/`idle` IS the harness registry's `status`, banned as an input to any
+ liveness, reachability, or claim verdict.** Ratified and test-enforced in the engine plane; this
+ clause is its crown-facing half, because nothing warns you at the call site.
+
+ The trap is that `busy` is *accurate* — 80 of 80 as a positive — and the ban stands anyway, because
+ status AGE is unbounded (p90 5.2h, max 6.9h): a 6.9h-old `busy` is exactly the shape of a session
+ that has since stopped. `idle` means unknown, never quiet. The named failure is a reader who learns
+ "busy is a trustworthy positive" and narrows the ban on that basis.
+
+ **Sanctioned, exhaustively: the oracle's verdict, and `last_tick_at` age in
+ `state/group-em-watch.json`.**
+
+ **When a PM reports the watcher is dead, do not confirm it from the session list.** `idle` is
+ `fleet-watch`'s normal state between polls, so a live watcher and a dead one render identically on
+ the only surface a human has. Answer from `last_tick_at` age and name the instrument, so the PM has
+ one they can re-run.
+
+ Tripwire: `LISTAGENTS-BUSY-IS-A-BANNED-LIVENESS-INPUT`.
+
+ ## The drive loop — what you are driving peers TOWARD
+
+ Nudging is the mechanism; this is the goal. The Group EM owns four of these five steps — **step 4's
+ clear is the PM's act**, the one exception.
+
+ 1. **Drive to execute.** A reviewed plan sitting on execution authorization is yours to release —
+ § "Do I execute?" for the light call, § Delegated approve-for-execution where the formal stamp
+ is wanted. Holding one because *"ready for execution authorization"* reads like a gate re-asks a
+ question the PM answered when they cleared the fleet to act.
+ 2. **Drive to workstream-complete.** Toward the close, not merely away from idleness. A peer that
+ stopped short of its own close is the case; a peer mid-execution and moving is not.
+ 3. **Troubleshoot alongside them until the primary exit criterion is met.** A **different act from
+ nudging** — a nudge says *keep going*; troubleshooting finds out why they can't. Stays inside
+ the no-authoring boundary: clear what blocks them, never write their plan or code.
+ 4. **When the ceremony completes, the PM clears them.** **Only a successfully completed ceremony
+ establishes out-of-work — a peer's own "I'm done" does not.** `workstream-complete` or
+ `quick-wrap` establishes it; a self-report is a claim about a ceremony, not the ceremony.
+ **The clear itself is not yours to perform**: `/clear` is a human act in the terminal — you
+ cannot issue it to a peer and a peer cannot issue it to itself. A session at this point is not
+ stuck and not your failure; it is **done and awaiting clear**, and *"N sessions awaiting clear"*
+ is a normal OUTPUT of a healthy tick, never an escalation. Yours: establishing the ceremony ran,
+ and **saying plainly that the clear is expected rather than a loss** — finished sessions read it
+ as one and hesitate.
+ 5. **Assign something new from the daily priority set.** PM-owned and per-day, so it does not live
+ here. What lives here: it **exists, step 5 draws from it, ask for it if you do not have one.**
+ Without it a Group EM improvises from a 34-item inbox and calls it prioritisation. A real one is
+ usually one line, and it makes routing trivial.
+
+ **The step-4 rule has two worked cases, both expensive.** A Group EM handed a peer its next baton
+ on a self-reported close — and that session had, that hour, proved an advisory in
+ `close_out_and_stamp.py` never fired for anyone because a literal `0x08` byte sat where a word
+ boundary belonged. The session best placed to know a close that ran from one that looked like it
+ was still taken at its word. Conversely, a crown held three peers to the ceremony over mild
+ objection; one, running the close it would have skipped, surfaced a P1 — the sanctioned
+ scoped-commit route dropping an explicitly-named path while returning `committed: true` with a real
+ SHA. A peer's *"nothing will come of it"* is a prediction about a ceremony it has not run.
+ `A-SELF-REPORTED-CLOSE-IS-NOT-A-COMPLETED-CEREMONY`.
+
+ **No step here is discharged by an artifact, and your own report cannot show the gap — because you
+ are what is failing.** A Group EM that skips the loop and reports diligently on peer idleness looks,
+ in its own transcript, exactly like one doing the job. Measured: across one Group EM's day, every
+ drive-loop failure was caught by the PM and none by a mechanism — four for four. **Do not read any
+ step as self-detecting**; re-read this section deliberately rather than expecting to be told.
+
## No registration ceremony, no persistence
- Nothing is written to disk on invoke. Nothing is cleaned up on exit. No roster, address, or
- reachability fact for any peer session is persisted anywhere — not in this repo's state
- directories, not in a scratch file, not in a sentinel. Every fact this skill acts on is re-derived
- live, every time, from the tools available at invocation (`Read`, `Bash`, `Glob`, `Grep` against
- this repo's own working state).
+ Nothing is written on invoke, nothing cleaned up on exit. No roster, address, or reachability fact
+ for any peer is persisted anywhere. Every fact is re-derived live, every time.
- **One carve-out, by name: the send log.** `build_send_digest` appends to
+ **One carve-out: the send log.** `build_send_digest` appends to
`state/subagent-share/<this-session-id>/group-em-send-log.jsonl` — a record of **this session's own
- offers**, which is what the per-peer cooldown throttles against. It is not a peer fact: no peer
- state, address, or reachability is written, and nothing about a peer is read back out of it. It
- follows the existing per-session bookkeeping convention beside
- `advisory-fire-counts.jsonl`, and it is session-scoped, so a new Group EM starts with an empty
- cooldown — the same lifetime the DACI ruling above gives the Driver role.
+ offers**, which the per-peer cooldown throttles against. No peer state is written or read back out.
+ Session-scoped, so a new Group EM starts with an empty cooldown.
## DACI is a frame, not a registry
- A `/group-em` session invoked by the PM in a repo **is** that repo's Driver for the sessions it
- coordinates, for exactly as long as it keeps running. That role is instantiated by invocation —
- never declared in advance, never persisted. When the session exits, the Driver role ends with it;
- nothing records that it ever held it. Do not build a Driver registry, roster file, or any standing
- record of who is coordinating whom.
+ A `/group-em` session **is** that repo's Driver for as long as it runs — instantiated by
+ invocation, never declared in advance, never persisted. Do not build a Driver registry or roster.
## Stale-read discipline
- Peer state must be re-read immediately before acting on it, never from a snapshot taken earlier in
- the same turn — by re-entering the mode, which re-derives live, and never by calling the entry op a
- second time inside one tick (§ Send pass step 1: the second call eats the first one's offers). A peer session's status can change between one tool call and the next; treating an
- earlier read as still current is exactly the failure mode this clause exists to prevent. Every
- nudge or wave decision re-reads first.
+ Peer state is re-read immediately before acting on it, never from a snapshot taken earlier in the
+ turn — by re-entering the mode, which re-derives live, and **never** by calling the entry op a
+ second time inside one tick (§ Send pass step 1). A peer's state can change between one tool call
+ and the next.
## Collision check — discharged, cited not re-run
- The platform-vocabulary collision check on the name `group-em` (plan skill scaffold checklist item
- 6, `coordinator/skills/plan/residue/plan-corpus.md:53`) is **already discharged clean** — no
- `claude` CLI subcommand or flag, no bundled-skill shadow-set collision, no PascalCase hook token,
- no existing coordinator skill/command/agent, and no hit anywhere under
- `~/.claude/plugins|skills|commands|workflows` (`state/roadmap/gem-2026-08-14/research-corpus/group-em-skill-scaffold.md`
- § "The outstanding collision check"). This citation is the record of that discharge; nothing above
- re-runs it.
-
- ## Gating granularity — entry AND per send, resolved by `gem-14`
+ The platform-vocabulary collision check on `group-em` is **already discharged clean** — no `claude`
+ subcommand or flag, no bundled-skill shadow, no PascalCase hook token, no existing coordinator
+ skill/command/agent, no hit under `~/.claude/plugins|skills|commands|workflows`
+ (`state/roadmap/gem-2026-08-14/research-corpus/group-em-skill-scaffold.md`). Nothing above re-runs it.
- Both, at different strengths, and the send gate is the load-bearing one.
+ ## Gating granularity — entry AND per send
- **Entry stays PM-gated** by this file's prefix convention, unchanged — that gate is about who may
- put a session into this mode at all.
+ **Entry stays PM-gated** by this file's prefix convention: who may enter the mode at all.
- **The send is gated separately, per send, and entry-gating never satisfies it.** A read-only pass
- is harmless whatever the entry gate says; the send is where the
- `ask-before-external-action`-shaped question actually lives, and it is where the PM's own bar
- applies: *is this worth tapping the busy engineer on the shoulder and saying "stop what you're
- doing and listen to me"* — an interrupt is justified by its **cost to the receiver**, never by the
- sender's convenience or the topic's importance. Because that bar is receiver-relative it must be
- re-asked for every message; one clearance at invocation cannot stand in for it, and receiver state
- goes stale on the minute scale in any case.
+ **The send is gated separately, per send, and entry-gating never satisfies it.** A read-only pass is
+ harmless whatever the entry gate says; the send is where the `ask-before-external-action` question
+ lives, at the PM's own bar: *is this worth tapping the busy engineer on the shoulder and saying
+ "stop what you're doing and listen to me"* — justified by **cost to the receiver**, never the
+ sender's convenience or the topic's importance. Receiver-relative, so it is re-asked every message.
## Read pass (gem-13)
- The enumerate-and-classify ladder referenced in the Anti-scope entry below lives in
- `read_pass.py`, beside this file — not inlined here, and invoked through § Entry's op rather than
- imported. It composes `gem-11`'s
- `coordinator/lib/receiver_state_reader.py` reader over `receiver-state.json` with a
- `claude agents --json` fallback into a bounded, read-only candidate roster:
- `build_candidate_roster(repo_root)`. It excludes the caller's
- own session, never writes or sends anything, and never adjudicates whether a paused peer
- "shouldn't" be paused — see that file's module docstring for the full ladder and the measured
- reader/fallback split. The send/nudge mechanism that plugs into this roster is `gem-14`,
- gated separately — see § Send pass (gem-14) below.
+ The enumerate-and-classify ladder lives in `read_pass.py` beside this file, invoked through § Entry's
+ op rather than imported. It composes `coordinator/lib/receiver_state_reader.py` over
+ `receiver-state.json` with a `claude agents --json` fallback into a bounded, read-only roster:
+ `build_roster(repo_root)` for the classified population, `build_candidate_roster(repo_root)` for the
+ paused-only shortlist. It excludes the caller, never writes or sends, and never adjudicates whether
+ a paused peer "shouldn't" be paused. Full ladder: that file's module docstring.
## Send pass (gem-14)
- `send_pass.py` turns `read_pass.build_candidate_roster`'s output into **one digest per invocation**
- (`build_send_digest`). It selects and throttles; **it does not send.** Emitting an entry arms that
- peer's cooldown itself and there is no per-peer entry point, so the per-peer-per-tick firehose is
- unreachable from the API rather than left to the sender's memory. Rationale and measurements:
+ `send_pass.py` turns `build_roster`'s **full** population — not the shortlist — into **one digest
+ per invocation** (`build_send_digest`). It selects and throttles; **it does not send.** Emitting an
+ entry arms that peer's cooldown, and there is no per-peer entry point, so the per-peer-per-tick
+ firehose is unreachable from the API. Rationale:
`docs/decisions/DR-group-em-send-narrows-on-the-obligation-ledger.md`.
The roster is the population; a human adjudicates it. The obligation ledger ranks and annotates,
- never admits — `undischarged_obligations: None` means no ledger exists at all, a producer coverage
- gap rather than evidence the peer owes nothing.
+ never admits — `undischarged_obligations: None` means no ledger exists, a producer coverage gap
+ rather than evidence the peer owes nothing.
- **Ledger rows can arrive from the engine plane.** DoE is the ledger's sole writer; the engine
+ **The roster carries the trap that has actually fired.** A digest of `0 entry(ies)` reads as a quiet
+ fleet, and says that identically whether every peer was weighed and held or none was ever
+ enumerated. Check the roster count against the room: a roster visibly smaller than the sessions you
+ know are running is `unknown`, never quiet, and the missing peers were never in the population, so
+ nothing in `suppressed` mentions them. Two settling reads: `claude agents --json` counts the room
+ without this ladder, and `python -m coordinator_core.group_em.idle_report --repo-root <root>
+ --group-em-session-id <your sid>` derives the population independently (both flags required; exits 2
+ bare). Three instruments disagreeing on how many peers exist is a defect to report, not a number to
+ average.
+
+ **Ledger rows can arrive from the engine plane.** This plane is the ledger's sole writer; the engine
appends to `state/subagent-share/<sid>/obligations-inbound.jsonl` and entry folds every session's
intake before the digest ranks. Contract: `coordinator/docs/wiki/obligations-inbound-intake.md`.
- A quarantined row is a producer bug and entry says so; it is not a peer fact.
+ A quarantined row is a producer bug, not a peer fact.
Tripwire: `A-SECOND-WRITER-TO-A-REWRITTEN-FILE-LOSES-ROWS-SILENTLY`.
**Procedure, per invocation:**
- 1. § Entry's op has already built both, plus a `baseline` peer-set delta. **Act on that payload —
- do not re-run the entry op to refresh it.** `build_send_digest` arms each emitted peer's
- cooldown as it emits, so a second entry call consumes the offers the first one made and renders
- those same peers back as `suppressed: cooldown` — identical to a peer genuinely offered an hour
- ago. Re-reading to satisfy the stale-read discipline is the move that hides the peer you were
- about to act on. The discipline is satisfied by the entry the tick already fired: it re-derives
- live and caches nothing.
- 2. Present it. `suppressed` says why each roster peer was held. `truncated` means those peers are
- not in this digest and are re-evaluated next tick — they are not queued. Anything in `unrecorded`
- had its cooldown write fail: that peer's throttle is not armed.
- 3. **Per entry you intend to message, declare both gates in prose before sending.** Entries arrive
- with `gate1`/`gate2` unset and no code resolves them:
+ 1. § Entry's op already built both plus a `baseline` delta. **Act on that payload — do not re-run
+ the entry op to refresh it.** `build_send_digest` arms cooldowns as it emits, so a second call
+ consumes the first's offers and renders those peers back as `suppressed: cooldown`, identical to
+ a peer genuinely offered an hour ago. The stale-read discipline is satisfied by the entry this
+ tick already fired.
+ 2. Present it. `suppressed` says why each peer was held. `truncated` means those peers are
+ re-evaluated next tick — not queued. `unrecorded` means the cooldown write failed: that peer's
+ throttle is not armed.
+ 3. **Per entry you intend to message, declare both gates in prose before sending.** They arrive
+ unset and no code resolves them:
- **GATE 1 (message).** Is the shared contract itself the unknown, needing round-trips — or is
- this a settled ask? Converging on the shape of an ask is coupled; MAKING the ask is not. A
- settled ask is a memo.
- - **GATE 2 (receiver).** Is this cheaper to them now than later — blocked and waiting, about to
- ship something this would change, or an agreed synchronous window? **GATE 2 has no
- instrument**: `peer_roster.status` is negative-spec'd, and the obligation ledger answers a
- different question. It rests on what you already know about that peer, never on anything you
- read this turn.
+ this a settled ask? Converging on an ask's shape is coupled; MAKING it is not. A settled ask
+ is a memo.
+ - **GATE 2 (receiver).** Cheaper to them now than later — blocked and waiting, about to ship
+ something this would change, or an agreed synchronous window? **GATE 2 has no instrument**:
+ `peer_roster.status` is negative-spec'd and the ledger answers a different question. It rests
+ on what you already know about that peer, never anything you read this turn.
- **Either gate unclear → the memo channel.** Not a degraded mode. The named anti-pattern is
sending live *because the channel is open*.
-
- 4. **Resolve the addressee immediately before you send, and treat a refusal as a refusal.**
+ 4. **Resolve the addressee immediately before sending, and treat a refusal as a refusal.**
`read_pass.resolve_addressee(name, expected_session_id, repo_root=...)` re-enumerates live and
- returns `ok: False` with a named reason unless the name still answers for exactly the session
- the roster saw. A peer name is not a stable address — it re-points with no event and no visible
- difference at the call site. **`ok: False` means do not send.** There is no fallback to the
- session id you already had; that fallback is precisely how a nudge lands on a session nobody
- examined, about work it never had, while the sender believes the address held. The refusals are
+ returns `ok: False` unless the name still answers for exactly the session the roster saw — a peer
+ name re-points with no event and no visible difference at the call site. **`ok: False` means do
+ not send.** There is no fallback to the session id you already had; that fallback is how a nudge
+ lands on a session nobody examined, about work it never had. Refusals:
`name-resolves-to-no-live-session`, `name-resolves-to-more-than-one-live-session`,
`name-now-points-at-a-different-session`, `name-resolves-outside-this-repo`, `no-name`.
Tripwire: `A-PEER-NAME-IS-NOT-A-STABLE-ADDRESS`.
- **Never loop over `entries` sending.** The per-send gate separates "may be offered" from
- "message", and nothing in code enforces it — `SendMessage` is in `allowed-tools` and the gate is
- this paragraph. The DR records that as a known limit.
+ **Never loop over `entries` sending.** Nothing in code enforces the per-send gate — `SendMessage` is
+ in `allowed-tools` and the gate is this paragraph.
**A `PAUSED:away` peer is never offered**, reported as `never-send-reason` even where a bookkeeping
- cause would also have excluded it. The digest never claims a peer is stuck or "shouldn't" be
- paused — that population is unmeasurable by PM ruling.
+ cause would also have excluded it. The digest never claims a peer is stuck or "shouldn't" be paused.
Tripwire: `A-PAUSED-ROSTER-IS-NOT-A-NUDGE-LIST`.
## The escalation screen — a claimed PM item is presumed yours until it survives
- **When a session says it has something for the PM, it is wrong about 19 times in 20.** That is the
- measured prior the PM states, and it is the number this screen exists to act on. Treat "this needs
- the PM" as a claim to be tested, never as routing already done.
-
- The three that arrive most often, and what each actually is:
+ **When a session says it has something for the PM, it is wrong about 19 times in 20** — the PM's own
+ measured prior. Treat "this needs the PM" as a claim to test, never routing already done.
- - **"Next: review."** A statement of the next step in a procedure the session already owns. There
- is no decision in it. Push it.
- - **"Variable `x` or `xy`?"** Naming, shape, structure — engineering, decided by whoever is
- holding the file. Push it, and do not answer it either: answering teaches the session to ask you
- next time instead of the PM, which relocates the problem rather than closing it.
- - **"Do I execute?"** The one with a real question inside it, answerable by you — see below.
+ - **"Next: review."** The next step in a procedure the session owns. No decision in it. Push it.
+ - **"Variable `x` or `xy`?"** Engineering, decided by whoever holds the file. Push it — and do not
+ answer it either: answering teaches the session to ask you instead of the PM.
+ - **"Do I execute?"** The one with a real question, answerable by you — below.
- **This is a model disposition, not a peer failing.** Sessions hedge, defer, and wait for a human
- because that is what they are trained toward, not because they lack judgment or standing. Say so
- when you push, and push anyway. A session that is told its instinct is systematic rather than
- personal stops re-deriving the same hesitation next time; one that is told it was wrong just
- hesitates more quietly.
+ **This is a model disposition, not a peer failing.** Sessions hedge because that is what they are
+ trained toward. Say so when you push, and push anyway: a session told its instinct is systematic
+ rather than personal stops re-deriving it; one told it was wrong just hesitates more quietly.
### Push, do not nudge
**"Do you need anything?" is a failure, not a gentler success.** It hands the decision back to the
- session that already froze on it, and its most likely answer is another hedge. Same for "want me
- to...?", "let me know if...", and "happy to help when you are ready" — every one of them is an
- invitation to keep waiting, phrased politely.
+ session that already froze on it. Same for "want me to...?", "let me know if...", "happy to help
+ when you are ready".
- A push **names the act, names its owner, and closes**: *"That is yours. Do X."* No question mark,
- no offer, no conditional. Where you are wrong the session tells you, which is cheap; where you are
- right it moves, which is the point. Give the reason in one clause so the correction generalises —
- *"that is an engineering call and it is yours"* — then stop.
+ A push **names the act, names its owner, and closes**: *"That is yours. Do X."* No question mark, no
+ offer, no conditional. Where you are wrong the session tells you, which is cheap. Give the reason in
+ one clause so the correction generalises — *"that is an engineering call and it is yours"* — then stop.
- You are not pushing anyone past a gate their own skill names (§ Send pass, and the standing rule
- that a peer's refusal is a safety mechanism). Those are different things: a gate is a considered
- refusal with a named reason, and hesitation is the absence of one. Push the second, never the
- first — and where you cannot tell which you are looking at, ask which it is, because that question
- has a real answer.
+ You are not pushing anyone past a gate their own skill names. A gate is a considered refusal with a
+ named reason; hesitation is the absence of one. Push the second, never the first — and where you
+ cannot tell, ask which it is.
### "Do I execute?" — yours to answer, with a viability read
The PM has delegated this call. **Do not read the plan into your own context to make it** — that
- spends the context this role exists to protect, and you are a worse reviewer of it than the agent
- built for the job. Dispatch `coordinator:staff-eng` for a viability read: is this plan likely to
- succeed as written, and what would stop it. Answer the session on the strength of that.
+ spends the context this role protects, and you are a worse reviewer than the agent built for the
+ job. Dispatch `coordinator:staff-eng` for a viability read: is this plan likely to succeed as
+ written, and what would stop it.
- **This does NOT route around the rubric.** Where a plan needs the formal
- `execution_authorized_by` stamp, § Delegated approve-for-execution procedure below is the only
- path to it, unchanged — reversibility gate, blinded baseline, blinded judge, 8 of 8. This lighter
- call answers *"should I get on with it"* for work that does not need that stamp, and it never
- produces one. If you find yourself reaching for the viability read on a plan that wants the stamp,
- you are on the wrong path; take the other one.
+ **This does NOT route around the rubric.** Where a plan needs the formal `execution_authorized_by`
+ stamp, § Delegated approve-for-execution is the only path — reversibility gate, blinded baseline,
+ blinded judge, 8 of 8. This lighter call answers *"should I get on with it"* for work that does not
+ need that stamp, and never produces one.
## Delegated approve-for-execution procedure
- Ordered procedure for the Group EM's rubric-gated execution-authorization stamp: the Group EM may
- take the PM's execute-plan turn on one named condition — the plan is reversibility-eligible and a
- rubric-scored judgment against the plan's own prime exit criterion clears a locked threshold
- (8 of 8, no dimension scoring 0). This is a *dispatch* path (step 4 below
- spawns the judge under this session's own authority) — distinct from the `gem-14` peer-session
- *send* mechanism named in § Send pass (gem-14) above, which is gated separately.
- Do not read the judge dispatch below as a send capability against a peer session.
+ The Group EM may take the PM's execute-plan turn on one condition: the plan is
+ reversibility-eligible and a rubric-scored judgment against its own prime exit criterion clears a
+ locked threshold (8 of 8, no dimension scoring 0).
- 1. Confirm this session holds the nomination (`<plugin-root>/bin/group-em-nomination.py who
- --repo <root> --json`). § Entry's op claims it, so this is a check, not the claim.
- 2. Run `<plugin-root>/bin/plan-reversibility-eligibility.py <plan-path> --json` (C9). Ineligible
- (`eligible: false`) → escalate per the threshold page's escalate shape, stop, do not dispatch.
- This CLI is the single source of truth for eligibility — do not restate the six D4 rules in
- prose as a substitute for running it.
- 3. Confirm the plan's recorded falsifier baseline shows the prime exit criterion false **and that
- a blinded `coordinator:exit-criterion-falsifier` authored it**. Absent, already true, or
- authored by the plan's own author → escalate, stop. Content and provenance are separate checks
- and only one of them is visible in the output: a self-authored baseline satisfies "shows the
- criterion false" perfectly and voids the guarantee, because the instrument is worth having only
- while its author has not seen the ACs, the chunk bodies, or the spine. If it is missing,
- dispatch the falsifier — that dispatch is a constitutive step of this procedure, and the
- hardcoded `AgentTool` system-prompt constant is a conditional default this skill's invocation
- satisfies, never a bar (`harness-directive-conflicts.md`). Reading it as a bar converts the
- blinded falsifier into dead letter and leaves the self-authored baseline standing.
- **Where provenance is readable, and what to do when it is not:** `plan.schema.json` admits only
- `how` / `baseline_output` / `baseline_ref` / `expected_when_true` on `falsifier`, deliberately —
- the instrument is what must be re-runnable, and authorship is a review-trail fact. So provenance
- lives in the plan BODY, in a prior `state/review-trail/approvability/` record, or in this
- session's own dispatch of the falsifier. If none of the three establishes it, treat that
- **exactly as a missing baseline and dispatch** — never as a pass. An unestablishable author is
- the self-authored case as far as the guarantee is concerned, and assuming otherwise is how the
- check passes while attesting nothing. Tripwires:
- **A FALSE baseline proves the criterion false, never the instrument sound — that is a third
- check, not a restatement of the first two.** An instrument can be correctly written, blinded,
- and honestly FALSE while being unable to print TRUE at all, because its fixture describes a
- world that cannot exist. Content and provenance both pass on such a baseline. Arming it the
- usual way — proving it returns the other answer — is not payable here: the other answer needs
- an implementation that does not exist at plan time, which is why this step goes missing without
- leaving a trace. So ask the cheap question instead, which IS answerable now: **what would have
- to be true for this to print TRUE, and is any of it reachable in the world the fixture builds?**
- Unreachable → escalate with that reason named, exactly as for a missing baseline.
- Consequence for later: when a correct implementation fails this instrument, suspect the
- instrument. Tripwires:
- `A-SELF-AUTHORED-FALSIFIER-SATISFIES-THE-CHECK-AND-VOIDS-IT`,
+ 1. Confirm this session holds the nomination (`<plugin-root>/bin/group-em-nomination.py who --repo
+ <root> --json`). Entry claims it; this is a check.
+ 2. Run `<plugin-root>/bin/plan-reversibility-eligibility.py <plan-path> --json` (C9). `eligible:
+ false` → escalate per the threshold page's shape, stop, do not dispatch. This CLI is the single
+ source of truth — do not restate the six D4 rules in prose as a substitute for running it.
+ 3. Confirm the plan's falsifier baseline shows the prime exit criterion false **and that a blinded
+ `coordinator:exit-criterion-falsifier` authored it**. Three separate checks, and only the first
+ is visible in the output:
+ - **Content.** Absent or already true → escalate, stop.
+ - **Provenance.** A self-authored baseline satisfies "shows the criterion false" perfectly and
+ voids the guarantee — the instrument is worth having only while its author has not seen the
+ ACs, chunk bodies, or spine. `plan.schema.json` admits only `how`/`baseline_output`/
+ `baseline_ref`/`expected_when_true` on `falsifier`, deliberately, so provenance lives in the
+ plan BODY, a prior `state/review-trail/approvability/` record, or this session's own dispatch
+ of the falsifier. **If none of the three establishes it, treat it exactly as a missing baseline
+ and dispatch.** An unestablishable author IS the self-authored case. That dispatch is a
+ constitutive step of this procedure, and the hardcoded `AgentTool` system-prompt constant is a
+ conditional default this skill's invocation
+ satisfies, never a bar (`harness-directive-conflicts.md`). Reading it as a bar converts the
+ blinded falsifier into dead letter.
+ - **Instrument soundness.** A FALSE baseline proves the criterion false, never the instrument
+ sound. An instrument can be correctly written, blinded, and honestly FALSE while unable to
+ print TRUE at all, because its fixture describes a world that cannot exist — content and
+ provenance both pass on it. Arming it the usual way is not payable at plan time, so ask the
+ question that is: **what would have to be true for this to print TRUE, and is any of it
+ reachable in the world the fixture builds?** Unreachable → escalate with that reason named.
+ Consequence for later: when a correct implementation fails this instrument, suspect the
+ instrument.
+ Tripwires: `A-SELF-AUTHORED-FALSIFIER-SATISFIES-THE-CHECK-AND-VOIDS-IT`,
`A-FALSE-BASELINE-PROVES-THE-CRITERION-FALSE-NOT-THE-INSTRUMENT-SOUND`,
`UNATTRIBUTED-HARNESS-LINE-IS-NOT-PM`.
4. Dispatch the named Opus persona (the PM's chosen reviewer, at the PM's chosen effort) with the
- plan path and the rubric (`coordinator/schemas/plan-approvability-rubric.json`) and nothing
- else. Authorship blinding is a hard constraint on the dispatch brief — the judge never receives
- the authoring session's context or identity.
- 5. Write the judgment record (`state/review-trail/approvability/<YYYY-MM-DD>-<plan-slug>.json`);
- validate it with C9's `--validate-record` mode (verbatim-substring check on every non-zero
- dimension's evidence) before treating it as final.
+ plan path and the rubric (`coordinator/schemas/plan-approvability-rubric.json`) and nothing else.
+ Authorship blinding is a hard constraint: the judge never receives the authoring session's
+ context or identity.
+ 5. Write the judgment record (`state/review-trail/approvability/<YYYY-MM-DD>-<plan-slug>.json`) and
+ validate it with C9's `--validate-record` before treating it as final.
6. On `approve`: first refuse if `execution_authorized_by` is already present with any value — a
- plan the PM already authorized needs no delegated approval, and `--append-note` would otherwise
- leave the PM's own note appended under a Group-EM attribution. Otherwise stamp with
- `review-exec-auth-stamp stamp <plan-path> --by "GROUP-EM:<session-id>" --append-note
- "<record-path>"`. On `escalate`, emit the decision to the PM per the threshold page's escalate
- shape — do not stamp.
+ plan the PM already authorized needs no delegated approval, and `--append-note` would leave the
+ PM's own note under a Group-EM attribution. Otherwise `review-exec-auth-stamp stamp <plan-path>
+ --by "GROUP-EM:<session-id>" --append-note "<record-path>"`. On `escalate`, emit per the
+ threshold page's shape — do not stamp.
7. **The stamp carries a standing offer: you produce criterion evidence the plan's author should
- not.** Blinding the falsifier buys an instrument its author did not shape, and buys nothing
- about who RUNS it and writes down what came out — the author of a criterion is the worst
- available producer of the evidence that it came true. This is not suspicion of them; it is that
- nobody can tell an honest self-produced record from a staged one by reading the artifact
- afterwards, which is the property the blinding existed to buy in the first place.
- So when a plan's terminal evidence is a live act rather than a test run — a real tick, a real
- publish, a real install on this box — offer to perform it as the crown holder and let the author
- verify instead. You are usually the available non-author, and where that is true the separation
- is free. Where it is not free, say so and let it go: an author-produced record with its
- provenance stated beats a delayed one. Tripwire:
- `THE-AUTHOR-OF-A-CRITERION-IS-THE-WORST-PRODUCER-OF-ITS-EVIDENCE`.
+ not.** Blinding the falsifier buys an instrument its author did not shape and buys nothing about
+ who RUNS it. Nobody can tell an honest self-produced record from a staged one by reading it
+ afterwards — the property the blinding bought. So when a plan's terminal evidence is a live act
+ rather than a test run (a real tick, publish, or install on this box), offer to perform it as the
+ Group EM and let the author verify. Where the separation is not free, say so and let it go: an
+ author-produced record with its provenance stated beats a delayed one.
+ Tripwire: `THE-AUTHOR-OF-A-CRITERION-IS-THE-WORST-PRODUCER-OF-ITS-EVIDENCE`.
## Inbox-blitz delegation
The Group EM may execute `/workday-start` Step 1.45a's inbox blitz on this repo's behalf.
- 1. **Nomination gate.** Resolve `<plugin-root>/bin/group-em-nomination.py who --repo <root>
- --json` and confirm it names this session — the same step 1 the § Delegated
- approve-for-execution procedure above opens with.
- 2. **The dispatch itself, in one sentence.** Execute `/workday-start` Step 1.45a's
- inbox-blitz procedure as written, dispatching `coordinator:group-em-assistant` for each
- `dispatches[]` entry. 1.45a already owns the assembler invocation, the tri-valued `state`
- handling (`skipped` = no dispatch), the ~30-memo shard grain, verbatim `brief`/`memos[]`
- passing, verify-rides-with-triage, the two EM-added verify checks, the manifest-race
- caveat, and wave pacing — this procedure does not restate any of it.
- 3. **Dispatch authorization — scoped to this clause only.** Invoking this clause IS the
- request for the `coordinator:group-em-assistant` dispatches Step 1.45a performs, on the
- same constitutive-step basis as the approvability-judge dispatch (§ Dispatch authorization,
- top of this file, and § Delegated approve-for-execution procedure above) — it covers
- spawning those assistants under this session's own authority for this procedure only,
- nothing else. It does not touch the peer-session send/nudge mechanism (`gem-14`, § Send
- pass (gem-14)), which stays gated separately; see the matching Anti-scope carve-out below.
+ 1. **Nomination gate.** `<plugin-root>/bin/group-em-nomination.py who --repo <root> --json` must
+ name this session.
+ 2. Execute Step 1.45a's procedure as written, dispatching `coordinator:group-em-assistant` per
+ `dispatches[]` entry. 1.45a owns the assembler invocation, tri-valued `state` handling
+ (`skipped` = no dispatch), ~30-memo shard grain, verbatim `brief`/`memos[]` passing,
+ verify-rides-with-triage, the two EM-added verify checks, the manifest-race caveat, and wave
+ pacing. None of it is restated here.
## Anti-scope
- - This skill does not **auto-send** to a peer session. `send_pass.py` selects and throttles a
- nudge population; it holds no transport and messages nobody. Every send is an explicit per-send
- act under § Send pass (gem-14), with both gates declared. Building an unattended sender, a loop
- that messages each digest entry without a per-send gate declaration, or any `Stop`-registered
- trigger, is out of scope and re-derives the stood-down watcher.
- - **Mandating that the watcher EXISTS is not mandating that it sends unattended, and the entry
- sequence above is not a hole in the bullet above.** Stated rather than left to inference, because
- the two sit close enough in this file to be read together. `fleet-watch` nudging on an observed
- registry transition is the "concrete observed signal" shape the ban preserves; a tick that
- messages everyone it can see is the shape the ban forbids. The discriminator is the signal, never
- the fact that a teammate is holding it — a teammate given a timing predicate re-derives
- `runtime-tripwire-stop-watcher.py` (681 fires / 26 days / ~99.4% wrong) one level down, where the
- crown loses sight of it. A mechanism wrong that often trains its reader to ignore
- it, which is worse than not existing.
+ - **No auto-send.** `send_pass.py` selects and throttles; it holds no transport. Every send is an
+ explicit per-send act with both gates declared. An unattended sender, a loop that messages each
+ digest entry, or any `Stop`-registered trigger is out of scope and re-derives the stood-down
+ watcher.
+ - **Mandating that the watcher EXISTS is not mandating that it sends unattended.** `fleet-watch`
+ nudging on an observed registry transition is the "concrete observed signal" shape the ban
+ preserves; a tick that messages everyone it can see is the shape it forbids. The discriminator is
+ the signal, never that a teammate holds it — a teammate given a timing predicate re-derives
+ `runtime-tripwire-stop-watcher.py` (681 fires / 26 days / ~99.4% wrong) one level down, where you
+ lose sight of it.
- **That ban is about AUTOMATION, never attentiveness.** A timing predicate cannot tell a stuck
- session from a working one, so wiring one to `Stop` fires near-continuously and is wrong almost
- every time. A crown-holder re-deriving the roster each turn and judging it is the opposite
- mechanism, and is what the ban exists to preserve. Reading this bullet as a reason not to look is
- the abdication failure above — which costs more than over-pushing, because nothing else can see
- it.
- - This skill does not implement the read-pass ladder or receiver-state consumption logic that
- supplies the peer-state facts this body's stale-read discipline governs — that is supplied by a
- separate stub and integrated here by reference, not merged into this file.
- - **Carve-out, stated by name, not a reversal of the rule below:** the general anti-scope clause's
- stated reason is that `/group-em` messages **peer sessions** in their own windows, an
- `ask-before-external-action` question that clause does not dissolve. Spawning the
- approvability-judge persona under this session's own authority (§ Delegated approve-for-execution
- procedure, step 4) is the *contrasted* case the clause itself names in the bullet below, not the
- prohibited one — so it is carved out here by name rather than left as a silent exception. This
- clarifies the existing ruling; it does not overturn it.
- - This skill does not otherwise carry a dispatch-authorization paragraph ("invoking this skill IS
- the request") of the general kind used by skills that dispatch subagents (`plan`, `execute-plan`,
- `review`, `shape`, `sizing`, `workstream-complete`) — the three dispatch-authorization paragraphs
- it does carry, the approvability-judge one above, § Inbox-blitz delegation's
- `group-em-assistant` one, and the entry-sequence one covering `coordinator:fleet-watch` and
- `coordinator:group-em-assistant`, are each scoped to their own named dispatch only, not a general
- grant. The entry-sequence one is the only one that fires unconditionally; that is a statement
- about when it applies, not a widening of what it covers. Those skills spawn
- subagents under the invoking session's own authority; `/group-em` otherwise messages **peer
- sessions** in their own windows, which is an `ask-before-external-action` question neither
- paragraph dissolves.
- - **Carve-out, stated by name, not a reversal of the rule above:** spawning
- `coordinator:group-em-assistant` under this session's own authority (§ Inbox-blitz delegation,
- clause 3) is the *contrasted* case the bullet above names, not the prohibited one — it dispatches
- a subagent under this session's own authority, the same shape as the approvability-judge dispatch,
- never a message into a peer session's own window. This clarifies the existing ruling; it does not
- overturn it.
- - `/autonomous` supplies the mode-shaped naming precedent only. Its `/tmp` sentinel mechanism is
- durable state and is not borrowed — the no-ceremony ruling above forbids durable state.
+ session from a working one. A holder re-deriving the roster each turn and judging it is the
+ opposite mechanism and is what the ban preserves. Reading this as a reason not to look is the
+ abdication failure above, which costs more than over-pushing because nothing else can see it.
+ - This skill does not implement the read-pass ladder or receiver-state consumption logic — supplied
+ separately and integrated by reference.
+ - **The three dispatch grants at the top of this file are each scoped to their own named dispatch.**
+ Spawning the approvability judge, `fleet-watch`, and `group-em-assistant` under this session's own
+ authority is the same shape as `plan`/`execute-plan`/`review` spawning subagents. What stays
+ gated is that `/group-em` otherwise messages **peer sessions in their own windows** — an
+ `ask-before-external-action` question no dispatch grant dissolves. The entry-sequence grant is the
+ only one that fires unconditionally; that is when it applies, not what it covers.
+ - `/autonomous` supplies the mode-shaped naming precedent only. Its `/tmp` sentinel is durable state
+ and is not borrowed.