@sjawhar/opencode-legion-envoy 5.6.1 → 5.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "5.6.1",
3
+ "version": "5.6.2",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "dist/src/server.js",
package/skills/AGENTS.md CHANGED
@@ -6,7 +6,7 @@ event intake, process lifecycle, credentials, and role delivery.
6
6
 
7
7
  | Skill | Who reads it | What it owns |
8
8
  | --- | --- | --- |
9
- | `ce-simplify-code/` | the implementer, once per pull request | the behaviour-preserving simplify pass before the reviewer's final pass (Legion's copy of the MIT-licensed Compound Engineering skill; `LICENSE` beside it) |
9
+ | `ce-simplify-code/` | the implementer, once per pull request | the behaviour-preserving simplify pass, in the implementer's first implementing round (Legion's copy of the MIT-licensed Compound Engineering skill; `LICENSE` beside it) |
10
10
  | `dispatch/` | every role, and any session writing to Dispatch | specs, asks, comments, artifacts, and messages on native Dispatch |
11
11
  | `dispatch-brainstorming/` | any session with Dispatch that starts a design conversation or writes a plan, as `dispatch-first` directs | the design conversation in the issue's spec, turn by turn to one approval, and the plan as the issue's `plan.md`; in a session with Dispatch it replaces superpowers' `brainstorming` and `writing-plans` |
12
12
  | `dispatch-first/` | every session with Dispatch configured, injected by each host plugin on every request (Oh My Pi), on every `SessionStart` (startup, resume, clear, compact, fork) and `SubagentStart` (Claude Code), or as an instruction file (OpenCode) | searching Dispatch before acting, extending the existing issue, citing decisions, closing duplicates; kept under 60 lines and 6,000 characters |
@@ -77,24 +77,24 @@ exercise a criterion end to end, building that path is a child issue of this tre
77
77
  answers the tree's asks); under a personal token it goes to that token's owner, whom
78
78
  `dispatch_whoami` names. Set it only when a human told you a specific person owns that child.
79
79
 
80
- Specifications written into Dispatch follow `skill://dispatch`'s [Writing a spec](../dispatch/SKILL.md#writing-a-spec).
80
+ Specifications written into Dispatch follow `skill://dispatch`'s "Writing a spec" section.
81
81
  Wave releases, child closures, and your own status are visible from the issue tree and the
82
- handoffs; do not narrate them into the spec or a `dispatch_message`. A to-do only a human can clear
83
- is a `dispatch_ask`.
82
+ handoffs; do not narrate them into the spec or a `dispatch_message`. A to-do only a human can
83
+ clear is a `dispatch_ask`.
84
84
 
85
85
  The issue's primary document **is** the root specification. Extend it in place: a new version
86
86
  that adds only the evidence each decision needs and what the human decides, each as a
87
- [decision block](../dispatch/SKILL.md#decision-blocks). The decomposition and its waves, how each
87
+ decision block (`skill://dispatch`, "Decision blocks"). The decomposition and its waves, how each
88
88
  outcome is proven, and the integration test are your own calls: they go in the child issues and
89
89
  the planner's `.legion/plan.json`, not the root spec. Never post a second "spec" artifact beside
90
90
  it (`dispatch_artifact` with the primary document's name replaces the human's document; do not do
91
91
  that).
92
- The design gate runs only when the "Design gate policy" line at the end of your system prompt
93
- says `gates.design: root-issues`. When it says `gates.design: off`, write the spec and continue
94
- to section 2 with no approval step at all: do not request approval, do not register a gate, and
95
- do not wait for `design-approved`. A sub-architect on a child issue has no policy line and never
96
- runs the gate either: the root approval covers the tree. When the gate is armed, run this exact
97
- sequence **before the tree's work starts**:
92
+ The design gate's approval step runs only when the `Design gate policy` sentence of your
93
+ `Legion addressing` line says `gates.design: root-issues`. When it says `gates.design: off`, write
94
+ the spec and register it with `register_gate` at its current version, with no approval step: do
95
+ not request approval and do not wait for `design-approved`; the gate opens at registration. A
96
+ sub-architect on a child issue has no policy line and never runs the gate: the root approval
97
+ covers the tree. When the gate is armed, run this exact sequence **before the tree's work starts**:
98
98
 
99
99
  ```text
100
100
  dispatch_doc_edit({ issue: "<root issue>", ... }) // extend the primary document in place
@@ -112,7 +112,7 @@ legion({
112
112
  ```
113
113
 
114
114
  The decision blocks come first: settle every one as
115
- [Approval of a spec](../dispatch/SKILL.md#approval-of-a-spec) says before you request approval;
115
+ "Approval of a spec" (`skill://dispatch`) says before you request approval;
116
116
  `dispatch_request_approval` refuses while one is open. Each answer reaches you, since you follow
117
117
  every ask you open. An approval request carries nothing new: request it only once the human has
118
118
  agreed to every point in the spec, so a point they have not agreed to gets its own decision block
@@ -127,8 +127,8 @@ ordinary question is not a gate and the daemon ignores its answer. Copy `artifac
127
127
  spec.md (document id <UUID>) at version <N> (ask <id>)", followed by the question the human's
128
128
  Inbox shows, and its `details.artifact` / `details.version` carry the same two values. The
129
129
  document id is never the slug or file name you passed in (`spec`, `spec.md`): the daemon
130
- recognizes the document's approval events by that id, and both the `legion` tool and the daemon
131
- refuse a value that is not a UUID. Calling `dispatch_request_approval` again while that request
130
+ recognizes the document's approval events by that id and takes no other value; the `legion` tool
131
+ looks a slug or file name up in Dispatch and hands the daemon the id. Calling `dispatch_request_approval` again while that request
132
132
  waits on the human, with the same `summary`, changes nothing and returns it (its text says
133
133
  it "already waits on the human"), so it is safe to repeat; a different `summary` is refused then.
134
134
  A newer version of the spec moves the open request to that version and leaves it waiting on you,
@@ -181,8 +181,9 @@ legion({ op: "release_children", issues: ["LEGION-41", "LEGION-42"] })
181
181
  The daemon moves each released child to `todo` and, while the root's design gate is open, starts
182
182
  its phases itself from its fixed workflow table, each phase worker as its own process with the
183
183
  child's context already in its environment; it starts no sub-architect, and you start no worker.
184
- Park while children are in flight. On each child closure, re-scope open work, close obsolete work
185
- with a reason, and release the next wave only when it now makes sense. There is no inter-child
184
+ Park while children are in flight. On each child closure, re-scope open work, take obsolete work
185
+ out of the workflow with `park_child` (saying why on the issue with `dispatch_comment`), and
186
+ release the next wave only when it now makes sense. There is no inter-child
186
187
  dependency mechanism to encode.
187
188
 
188
189
  Release admits nothing. A child never takes an admission slot or becomes a root tree of its own
@@ -221,7 +222,7 @@ worker's role topic to start retro: a suspended role is not running to receive i
221
222
 
222
223
  Wait for the implementer to report its durable retro result. Retro output is
223
224
  `docs/solutions/` plus one `dispatch_message` on the issue; it must not create a `.legion`
224
- file or rewrite the reviewer-approved head after cleanup.
225
+ file or rewrite the reviewer-approved head.
225
226
 
226
227
  ## 6. Architect sign-off and merge
227
228
 
@@ -235,16 +236,15 @@ The daemon keeps this order from its fixed table; you start none of its steps:
235
236
 
236
237
  1. tester green and review cycles complete;
237
238
  2. on a clean review, the reviewer approves the head by SHA, and the daemon moves the issue to
238
- `retro`. Under this daemon no role pushes the `.legion/` deletion: the approved head still
239
- carries `.legion/`, and the operator removes it from the default branch in a follow-up pull
240
- request after the merge;
239
+ `retro`. No role pushes a `.legion/` deletion: the approved head still carries `.legion/`, and
240
+ the operator removes it from the default branch in a follow-up pull request after the merge;
241
241
  3. retro commits its learnings under `docs/solutions/` on top of the approved head; that
242
242
  commit does not void the approval and never returns the tree to the tester or reviewer;
243
243
  4. the merger verifies the current head is the reviewer-approved head plus only commits that
244
244
  change `docs/solutions/` (`jj diff --from <approved-sha> --to <tip-sha> --summary`, quoted in
245
- READY) and posts `READY #<n> at <current sha> (approved at <approved sha>) for <KEY> (<pr url>)`
246
- on the Dispatch issue. When its `Legion addressing` line names the project's merge queue, it
247
- publishes the same packet there too. Legion never merges; a human merges under the repository's
245
+ READY) and sends the READY packet with its completion; the daemon posts
246
+ `READY #<n> at <current sha> (approved at <approved sha>) for <KEY> (<pr url>)` on the Dispatch
247
+ issue and publishes it to the project's merge queue role when one is set. Legion never merges; a human merges under the repository's
248
248
  GitHub branch-protection and CODEOWNERS rules. If the merger reports a failed verification,
249
249
  treat it like `pr-blocked`: the merger holds the phase, so tell it to move the issue back with
250
250
  `request_backward_move`, naming what failed; never bypass.
@@ -268,19 +268,19 @@ never a rebase, since a rebase rewrites every descendant of the chain's fork poi
268
268
  another tree's branch stacked on it), pushes it with the ordinary push procedure (a genuine
269
269
  fast-forward), and posts the before/after fingerprints; the tester re-runs the bare gates only;
270
270
  the reviewer confirms and approves the new head by SHA (or continues its round if it had not
271
- approved); the daemon carries the issue on through retro to the merger, which republishes READY.
271
+ approved); the daemon carries the issue on through retro to the merger, whose new READY packet the daemon posts.
272
272
  This merge happens only when GitHub reports `CONFLICTING`
273
273
  (`legion gh -- pr view <n> --json mergeable,mergeStateStatus`); read that on every end-game
274
- wake — `pr-ready`, `phase-finished`, `catchup-overseer` — because a `CONFLICTING`
274
+ wake — `phase-finished`, `catch-up`, `checks-red`, `review-stuck` — because a `CONFLICTING`
275
275
  PR gets no CI and no wake announces it. The moment you see it, tell the worker holding the issue's
276
276
  phase (`envoy_publish` to its role topic) to move the issue back to `implementing` with
277
277
  `request_backward_move`; in `awaiting_merge`, where no worker holds a phase, open a `dispatch_ask`
278
- naming the conflict for the human who merges. Do not let the merger publish `READY` for an
278
+ naming the conflict for the human who merges. Do not let the merger send a READY packet for an
279
279
  obsolete approval.
280
280
 
281
281
  If a worker reports that `legion threads resolve` exited 1 naming a review thread GitHub refused
282
282
  to resolve, open a `dispatch_ask` that names the thread's URL and GitHub's message for a human to
283
- resolve it by hand, with options for resolved / could not; the merger does not publish while it
283
+ resolve it by hand, with options for resolved / could not; the merger does not complete while it
284
284
  is open. That is the one review-thread step a human takes: the review App cannot resolve a thread
285
285
  on a pull request the implementer opened, and the implementer's and merger's runs of the command
286
286
  close every accepted one.
@@ -317,19 +317,17 @@ active phase worker.
317
317
  | Wake | Procedure |
318
318
  | --- | --- |
319
319
  | `child-status` | A child of your tree left the workflow or re-entered it; the notice's reason names the child and its new status. `todo` (a human's move, your `release_children`, or your `rerun_child`) means the child runs again under your tree from planning, and the daemon starts it; you start nothing. `backlog`, `icebox` or `triage` (a human's move, or your `park_child`) means the daemon has suspended the child's workers, and it advances no further until it is set back to `todo`. What the rest of the tree does is your decision. |
320
- | `child-closed` | Read the child completion and remaining open children. Re-scope or close obsolete open work; release an appropriate next wave with `release_children`. When a worker waits on this child for a missing surface (see `phase-finished`), tell it to continue with `envoy_publish` to its role topic. The last child's close is not the end-game (section 3). |
321
- | `child-reopened` | Treat the completion edge as reset. Reassess the reopened child and return the tree to children-in-flight; do not continue an already-started end-game. |
322
- | `design-approved` | Payload `{type:"design-approved"}`. A human approved the root spec document at its current version; the gate is open. Proceed to section 2. |
323
- | `design-changes-requested` | Payload `{type:"design-changes-requested", version, reason, author?}`. A human asked for changes to the root spec at `version`, for `reason`. Revise the spec as the reason asks and request approval again as section 1 says; stay parked; the gate is closed. |
320
+ | `child-closed` | Read the child completion and remaining open children. Re-scope open work, park obsolete work with `park_child`, and release an appropriate next wave with `release_children`. When a worker waits on this child for a missing surface (see `phase-finished`), tell it to continue with `envoy_publish` to its role topic. The last child's close is not the end-game (section 3). |
321
+ | `design-approved` | Its `version` is the approved one. A human approved the root spec document at its current version; the gate is open. Proceed to section 2. |
322
+ | `design-changes-requested` | Its `version` and `reason`. A human asked for changes to the root spec at `version`, for `reason`. Revise the spec as the reason asks and request approval again as section 1 says; stay parked; the gate is closed. |
324
323
  | `phase-finished` | The daemon has already moved the issue to its next phase by its fixed table and started that phase's role; you start nothing. Read the committed handoff for the finishing phase; if it shows unresolved gaps, tell the role now working the issue (`envoy_publish` to its role topic). A `planner` notice that names a departure from the spec's design defers to section 1's full condition: when the approved Summary, Acceptance, scope and settled decisions still hold, the plan is the record; otherwise change the root spec and request approval again as section 1 says. A `reviewer` notice whose GitHub review is `CHANGES_REQUESTED` needs nothing from you: the daemon has returned the issue to `implementing` (Dispatch `in_progress`) and started the **implementer**, whose correction goes through the tester and the reviewer again, never straight to retro. A `reviewer` notice with an `APPROVED` review means the daemon has started retro (step 5). An `implementer` notice for `production_check` is its production report: read the record on the pull request and the issue, then run step 7. A `tester` notice with `verdict: "fail"` — its handoff carries `implementerProof.verdict: "rejected"`, or a failure naming the production-like proof — has gone back to the **implementer** by the daemon's table; never supply the proof from another role. A worker that reports no surface reaches the changed path sends that report instead of completing its phase, so the daemon starts nothing more on that issue and the worker stays idle in its session, not suspended: file a child issue in this tree to build the surface (infrastructure, tooling, or a skill), and when that child's `child-closed` arrives, tell the waiting worker to continue with `envoy_publish` to its role topic. That report is never a reason to advance the phase. |
325
- | `pr-ready` | Verify the live PR head, green status, and review state. Continue the review/retro/merger order only for that current head. |
326
- | `pr-blocked` | Payload `{type:"pr-blocked", pr, attempts}`. `attempts` counts heads pushed onto a red verdict that changed something outside `.legion/` — handoff-only pushes (`.legion/` paths only) never count, a push by the review App (a planner's, tester's, reviewer's or architect's) never counts, and the head after a red the tester's red tests earned (a review-App push that changed a path outside `.legion/`, however many handoff-only pushes follow it) does not count either — so after the tester's handoff-only push onto the implementer's red, the implementer's next push does count; a push the daemon cannot classify (a listener without `changed_paths`, a list the listener stopped at 100 paths or 32,768 runes of text, a push listing no commits) does. Published once per exhausted count, not on every later red verdict for that count. Read the failed CI evidence and recovery attempts. The notice moves nothing: the issue stays in its phase, and only the worker holding that phase is running; an earlier phase's worker is suspended. In `implementing`, give the implementer the failing checks (`envoy_publish` to its role topic). In any later phase a worker holds, tell that worker (`envoy_publish` to its role topic) to move the issue back to `implementing` with `request_backward_move`, naming the failing checks, and the daemon starts the implementer; or file a corrective child. In `awaiting_merge`, where no worker holds a phase and a backward move is refused, open a `dispatch_ask` naming the failing checks for the human who merges, as section 6 does for a conflict there. Do not treat the blocked PR as final. |
327
- | `pr-merged` | Payload `{kind:"pr-merged", reason}`. The PR merged, under the repository's rules, before the issue reached `awaiting_merge`. The workflow runs on and asks no one to merge it: the daemon starts the **implementer** on the production check once the issue gets there, and you start nothing. Its `phase-finished` for `production_check` is what brings you to step 7: verify the record on the pull request and this issue first, then sign off naming it with `sign_off`. A merge is not the close. |
324
+ | `pr-blocked` | Its `reason` names the pull request and the `max_fix_attempts` it reached. The count is of heads pushed onto a red verdict that changed something outside `.legion/` — handoff-only pushes (`.legion/` paths only) never count, a push by the review App (a planner's, tester's, reviewer's or architect's) never counts, and the head after a red the tester's red tests earned (a review-App push that changed a path outside `.legion/`, however many handoff-only pushes follow it) does not count either — so after the tester's handoff-only push onto the implementer's red, the implementer's next push does count; a push the daemon cannot classify (a listener without `changed_paths`, a list the listener stopped at 100 paths or 32,768 runes of text, a push listing no commits) does. Published once per exhausted count, not on every later red verdict for that count. Read the failed CI evidence and recovery attempts. The notice moves nothing: the issue stays in its phase, and only the worker holding that phase is running; an earlier phase's worker is suspended. In `implementing`, give the implementer the failing checks (`envoy_publish` to its role topic). In any later phase a worker holds, tell that worker (`envoy_publish` to its role topic) to move the issue back to `implementing` with `request_backward_move`, naming the failing checks, and the daemon starts the implementer; or file a corrective child. In `awaiting_merge`, where no worker holds a phase and a backward move is refused, open a `dispatch_ask` naming the failing checks for the human who merges, as section 6 does for a conflict there. Do not treat the blocked PR as final. |
325
+ | `pr-merged` | Its `reason` names the pull request. The PR merged, under the repository's rules, before the issue reached `awaiting_merge`. The workflow runs on and asks no one to merge it: the daemon starts the **implementer** on the production check once the issue gets there, and you start nothing. Its `phase-finished` for `production_check` is what brings you to step 7: verify the record on the pull request and this issue first, then sign off naming it with `sign_off`. A merge is not the close. |
328
326
  | `pr-closed-unmerged` | Decide from current scope whether the work is reopened, started over (`park_child` then `rerun_child`, for a child), or ended with a reason. Delegate the repository action to the responsible phase worker and keep ownership. |
329
- | `issue-comment` | Interpret the comment in the issue's design context. Reply in its thread (`dispatch_comment` with `reply_to`; under an open ask whose next move is yours, such as the approval request you must revise or hand back, `reply_to_ask` with `turn: "agent"`, since a default-turn reply hands that request back to the human and a corrected `summary` is then refused), then adjust the plan or relay it via `envoy_publish` to the responsible worker's role token; scope and product decisions remain with you. |
330
- | `catchup-overseer` | Verify its child counts and PR verdicts against current artifacts, then resume the applicable lifecycle step. This is a current-state snapshot, not a raw-event replay. A root architect uses `gates[LEGION_TREE].open`: `true` means the root spec is approved and section 2 may continue; `false`, or no `open` key, means section 1 still applies. A resumed sub-architect receives `overseerCatchup(state, LEGION_ISSUE)` for its own subtree: its `gates` intentionally omits the root gate because a child spec is never gated. Do not request or register a gate; resume at section 2. Handle each `phaseCompletions` entry exactly as a `phase-finished` wake, then compare `childCounts[LEGION_ISSUE].open` with `legion state` and Dispatch before deciding the next action. |
331
- | `worker-died` | Payload `{kind:"worker-died", role, phase}`. That role's claim failed: its launches or prompts ran out. For a phase worker the daemon holds the issue (phase `held`) and starts nothing more on it. Reassess the work, then decide with `retry_or_escalate`: `retry` when the failure looks agent-specific or transient, `escalate` to hand the held issue to the controller when it looks environmental. |
332
- | `reopened` | Reopen the root lifecycle: inspect the reason and current artifacts, reassess scope and children, and resume at the first applicable numbered step. |
327
+ | A reply on an ask you follow | Interpret the reply in the issue's design context. Answer in its thread (`dispatch_comment` with `reply_to`; under an open ask whose next move is yours, such as the approval request you must revise or hand back, `reply_to_ask` with `turn: "agent"`, since a default-turn reply hands that request back to the human and a corrected `summary` is then refused), then adjust the plan or relay it via `envoy_publish` to the responsible worker's role token; scope and product decisions remain with you. |
328
+ | `worker-died` | Its `role` and `phase`. That role's claim failed: its launches or prompts ran out. For a phase worker the daemon holds the issue (phase `held`) and starts nothing more on it. Reassess the work, then decide with `retry_or_escalate`: `retry` when the failure looks agent-specific or transient, `escalate` to hand the held issue to the controller when it looks environmental. |
329
+ | `held` | Its `role` and `phase` (the phase the issue left), or `phase` with `reason: "escalated"`. Without `reason`, that phase's worker ran out of launches or prompts: the issue is held (phase `held`), the `worker-died` that comes with it is yours to answer with `retry_or_escalate` as that row says, and the controller hears of the hold too. With `reason: "escalated"`, it records your own `escalate`: the controller has the issue now, and you start nothing for it. |
330
+ | `ready-refused` | Its `version` and `reason`. The merger's READY was refused. A `reason` of `READY refused: approve design version <N> before requesting READY.` (or, with `version` 0, `no design version is approved`) means the design gate is closed: get that version approved (section 1), and the daemon advances the merge and posts the packet the merger sent once it is; nothing else is needed. A `reason` starting `READY_PACKET_MISSING` names a READY refused before the daemon kept packets: that READY is void and the issue stays in merging, so start it over (`park_child` then `rerun_child`, for a child) or end it. |
333
331
 
334
332
  ## Escalation judgment
335
333
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: legion-controller
3
- description: Use when handling Legion controller wakes for root-issue triage, keeping the admission slots full from `todo` issues, the daily report, architect escalation, resync healing, or human interaction.
3
+ description: Use when handling Legion controller wakes for root-issue triage, keeping the admission slots full from `todo` issues, the daily report, architect escalation, or human interaction.
4
4
  ---
5
5
 
6
6
  # Legion Controller
@@ -12,73 +12,63 @@ architect.
12
12
 
13
13
  ## Start and claim the controller role
14
14
 
15
- The Legion extension claims `legion-<project>-controller` and registers controller readiness
16
- with the daemon during session startup. Do not handle a wake unless that startup succeeded.
15
+ The Legion extension registers this session with the daemon as the controller and claims
16
+ `legion-<project>-controller` during session startup. Do not handle a wake unless that startup
17
+ succeeded.
17
18
 
18
- The daemon runs the controller as an interactive OMP terminal session in its private tmux
19
- server (the pane runs plain `omp`, not `--mode rpc`, and no `legion worker-shim`; the operator reaches
20
- it with `tmux -L legion-<project> select-window -t <window id> \; attach -t legion-<project>`,
21
- the window id being `controllerLocator.tmuxWindowId` in `legion state --json` — every window
22
- opens detached, so a bare `attach` lands on whichever window is current). The operator may attach and
23
- type into this session at any time. The pane carries no GitHub credential: its GitHub token
24
- variables are emptied, and both `legion gh -- <args>` and `legion threads resolve` are refused.
25
- The controller reads Dispatch and applies its controller capability with `legion status <KEY>
26
- <status>`; it never reads GitHub or merges a pull request.
19
+ The session carries no GitHub credential: its GitHub token variables are emptied, and both
20
+ `legion gh -- <args>` and `legion threads resolve` are refused. The controller reads Dispatch and
21
+ applies its controller capability with `legion status <KEY> <status>`; it never reads GitHub or
22
+ merges a pull request.
27
23
 
28
24
  For an interactive takeover from a hand-started OMP session, start OMP with
29
25
  `LEGION_CONTROLLER_SECRET` (or `LEGION_CONTROLLER_SECRET_FILE`, a path to a file holding it),
30
- `LEGION_DAEMON_URL`, `LEGION_STATE_DIR` (the daemon's state directory), and `LEGION_GRANT_FILE`
26
+ `LEGION_DAEMON_URL`, `LEGION_PROJECT` (the daemon's project), `LEGION_STATE_DIR`, and `LEGION_GRANT_FILE`
31
27
  (an absolute path to a file only you can read, under a 0700 directory; the extension writes
32
28
  each command's grant there and every `bash` call is blocked without it) in its environment. Do
33
- not set `LEGION_CONTROLLER=1` — that marker is the daemon pane's own, and a session carrying it
34
- claims at startup and reports its transcript as the pane's. Then run:
29
+ not set `LEGION_CONTROLLER=1` — that marker is `legion controller start`'s own, and a session
30
+ carrying it claims at startup. Then run:
35
31
 
36
32
  ```text
37
33
  /legion-claim-controller
38
34
  ```
39
35
 
40
- The command resolves the project from daemon state, claims the Envoy role for the current
41
- session, and posts readiness before controller commands can act. From then on this session's
36
+ The command checks that `LEGION_PROJECT` is the daemon's project, registers this session with the
37
+ daemon, and claims the Envoy role for it before controller commands can act. From then on this session's
42
38
  shell commands are wrapped with a controller grant, but the grant holds no GitHub credential;
43
39
  `legion status <KEY> <status>` works through the controller secret in this session's environment
44
40
  (`LEGION_CONTROLLER_SECRET` or its `_FILE`), not the grant — if it fails, that is the variable to check.
45
- The takeover moves the role
46
- and the daemon's recorded session id to this session; it never replaces the transcript the
47
- daemon recorded for its own pane, so a later respawn of that pane resumes the pane's own
48
- conversation, not yours. Never pass a secret as a command argument or copy it into a transcript.
49
- The claim is kept alive automatically afterwards: the Envoy registration heartbeat re-asserts it
50
- and re-posts readiness whenever the listener loses sight of this session, so
51
- `/legion-claim-controller` is the manual override, not a routine step after a listener restart.
52
-
53
- Two limits of a takeover session. It caches the controller secret it started with: after the
54
- daemon respawns its own pane the secret rotates, every `bash` call in the takeover session then
55
- fails with a 403 from the grant mint, and the fix is to start a fresh OMP with the new secret,
56
- not to retry. And the role does not follow `/new` or `/fork` in a takeover session — without
41
+ The takeover moves the role and the daemon's recorded session id to this session. Never pass a
42
+ secret as a command argument or copy it into a transcript. The Envoy registration heartbeat keeps
43
+ the role afterwards, so `/legion-claim-controller` is the manual override, not a routine step after
44
+ a listener restart.
45
+
46
+ Two limits of a takeover session. It caches the controller secret it started with: the next
47
+ `legion controller start` mints a new secret, every `bash` call in the takeover session then fails
48
+ with a 403 from the grant mint, and the fix is to start a fresh OMP with the new secret, not to
49
+ retry. And the role does not follow `/new` or `/fork` in a takeover session — without
57
50
  `LEGION_CONTROLLER=1` the new session is not a Legion session to the extension — so after either
58
51
  command run `/legion-claim-controller` again.
59
52
 
60
- This handshake lets the daemon redeliver held controller work. It does not turn the controller
61
- into a state holder: daemon state and the Dispatch project remain authoritative.
53
+ The daemon holds nothing for a controller: daemon state and the Dispatch project remain
54
+ authoritative, and the start procedure below reads what happened while no controller ran.
62
55
 
63
56
  ### Started by the operator
64
57
 
65
- When the daemon cannot open a terminal for you — the Go daemon, under either runtime, since it
66
- launches no controller — nobody launched your
67
- pane: the operator ran `legion controller start --config controller.yaml [--daemon-url <url>]` on
58
+ The daemon launches no controller, under either runtime: the operator ran
59
+ `legion controller start --config controller.yaml [--daemon-url <url>]` on
68
60
  their own machine, and you are that foreground OMP session. The command fetched a fresh controller
69
61
  secret from the daemon with the operator's token, wrote it to a 0600 file under `LEGION_STATE_DIR`
70
62
  (`~/.local/state/legion/<project>-controller` by default) beside the `gh` shim and the `legion`
71
- launcher, and started you with `LEGION_CONTROLLER=1` and the same environment a tmux controller pane
72
- carries, so nothing changes in how you handle wakes. The extension registers on
73
- `/legion/v1/claims/register` with the
63
+ launcher, and started you with `LEGION_CONTROLLER=1` and the controller's environment. The
64
+ extension registers on `/legion/v1/claims/register` with the
74
65
  secret, claims the role, then subscribes to `notifications.legion.<project>.controller`, where the
75
- Go daemon publishes the rows marked from the Go daemon in the wake routing table. The daemon records
66
+ daemon publishes the rows marked from the Go daemon in the wake routing table. The daemon records
76
67
  you as `controllerLocator: {runtime, external: true, sessionId, registeredAt}`, `runtime` being the
77
- daemon's own (`kubernetes`, or `tmux` under the Go daemon). The TypeScript daemon reads your
78
- liveness from the Envoy role registry (the holder of
79
- `legion-<project>-controller` and its `last_seen`), not from a pane: keep the session running.
68
+ daemon's own (`kubernetes` or `tmux`). The daemon reads your liveness from the Envoy role registry
69
+ (the holder of `legion-<project>-controller` and its `last_seen`): keep the session running.
80
70
  Exiting it leaves the project without a controller until the operator runs the command again —
81
- the TypeScript daemon logs `controller not registered; run legion controller start` once per
71
+ the daemon logs `controller not registered; run legion controller start` once per
82
72
  boot-timeout interval and launches nothing itself. `legion state` and `legion status <KEY>
83
73
  <status>` work here over `LEGION_DAEMON_URL`. A second `legion controller start` replaces you: it
84
74
  mints a new secret, so your grants stop working and the role moves to the new session.
@@ -214,7 +204,7 @@ leans on `External links:`, and the label row is the one that never depends on h
214
204
  | It has any child | `dispatch_read({ ref: "dispatch://<KEY>/children" })` lists any child, open or `done`. It is an umbrella, and a finished umbrella is still no leaf. That also skips an issue whose only child is done, which is accepted. Its open children are candidates themselves, each in its own place in the order. |
215
205
  | An ancestor is Legion's | Follow `Links:` up through each `child_of` parent, reading each one, and skip when any ancestor carries the `legion` label or is recorded under `issues` in `legion state --json`, whatever its status: a Legion tree, running or parked, owns its children. An ancestor's claim or route does not skip the issue: a coordinator holding an umbrella files `todo` leaves for others to pick up, and the claim on the issue itself is what keeps two sessions off the same work. A `child_of` under `Referenced by:` is a child of this issue, not its parent. |
216
206
  | Someone is designing it | `Open asks:` lists any ask, a `Spec approval: awaiting …` line shows the spec waits on a human, or `Events:` show an `artifact.version` or an `ask.opened` from the last seven days: a session or a person is shaping it even when nobody claims or routes it. |
217
- | Legion ran it without the label now on it | `legion state --json` records it under `issues`, whatever its status, or `Events:` show a status write by `session legion-daemon:<PROJECT>`, the daemon's actor on every `legion status` (yours included) and on its own `in_progress` at admission. That covers a root a person took the label off, and one that ran before the daemon required the label and never had it. Name each one you skip for this in your summary. The walk never sends a root Legion already ran back into Legion: a person does that with the label and `todo`, and you do it only when a wake below says to (`worker-died`, closed-tree activity). |
207
+ | Legion ran it without the label now on it | `legion state --json` records it under `issues`, whatever its status, or `Events:` show a status write by `session legion-daemon:<PROJECT>`, the daemon's actor on every `legion status` (yours included) and on its own `in_progress` at admission. That covers a root a person took the label off, and one that ran before the daemon required the label and never had it. Name each one you skip for this in your summary. The walk never sends a root Legion already ran back into Legion: a person does that with the label and `todo`, and you do it only when a wake below says to (`worker-died`). |
218
208
  | A running session or a person claims it | `Claimed by:` names anyone and does not end `· not running`. `· liveness unknown` counts as claimed: the agent registry could not be read, so nothing says the holder stopped. A claim ending `· not running` has lapsed, and the issue is free. |
219
209
  | Its route reaches a running session | `Route:` names a route with nothing after it, or with `(held by …)`. `(nobody holds it right now)` and `(that session is not running right now)` reach nobody; `(the Envoy listener did not answer, …)` counts as reaching someone. `Route: none` is free. |
220
210
  | A pull request is linked or named | `External links:` lists a pull request (kind `github_pr`, or a URL ending `/pull/<n>`), or a comment or message among `Events:` names one. You cannot read GitHub, so an open, merged, or closed pull request all count. A person who wants Legion on it anyway hands it over themselves: the label, then `todo`. |
@@ -302,7 +292,7 @@ priority first, then board rank ([Keeping the slots full](#keeping-the-slots-ful
302
292
  it before handling every other wake.
303
293
  - **One wake = one turn.** Handle exactly the wake's implication, then end the turn. Never
304
294
  poll, idle-loop, or wait for another event. Two additions, after any direct user message: every
305
- turn under the Go daemon first rechecks the [trees waiting on a root
295
+ turn first rechecks the [trees waiting on a root
306
296
  claim](#trees-waiting-on-a-root-claim-go-daemon), and your first turn of each UTC day, whatever
307
297
  woke you, also posts the day's report ([Daily report](#daily-report-go-daemon)).
308
298
  - **Wakes are advisory.** Before any side effect, verify the current daemon state and the
@@ -318,21 +308,14 @@ priority first, then board rank ([Keeping the slots full](#keeping-the-slots-ful
318
308
 
319
309
  | Wake | Content | Controller action |
320
310
  |---|---|---|
321
- | New issue created in the Dispatch project (`issue.created`, status `triage`; under the TypeScript daemon resync heals misses, under the Go daemon the boot step above does). From the Go daemon: `triage on <KEY>` (payload `{kind: "triage"}`) on the controller topic, for an unrecorded root carrying the `legion` label only ("Issues handed to Legion" above) | issue key + triage context (incl. pre-existing children) | Triage: `legion status <KEY> todo` to admit, or set `backlog`/`icebox` to park |
322
- | Backlog eligibility (TypeScript daemon) | slot freed / priority change | Reconsider parked items and move the eligible root to `todo` |
311
+ | New issue created in the Dispatch project, status `triage`: `triage on <KEY>` (payload `{kind: "triage"}`) on the controller topic, for an unrecorded root carrying the `legion` label only ("Issues handed to Legion" above); the start procedure above catches one sent while no controller ran | issue key + triage context (incl. pre-existing children) | Triage: `legion status <KEY> todo` to admit, or set `backlog`/`icebox` to park |
323
312
  | `slot-free on <KEY>` from the Go daemon (payload `{kind: "slot-free"}`) | the root whose slot the daemon released with no waiting root to take it | Verify a free slot in `legion state --json`, then fill it ([Keeping the slots full](#keeping-the-slots-full-go-daemon)) |
324
313
  | `todo on <KEY>` from the Go daemon (payload `{kind: "todo"}`) | an issue not handed to Legion that changed while in `todo` and a slot stood free, sent half a minute later | Verify a free slot, then walk the whole `todo` list ([Keeping the slots full](#keeping-the-slots-full-go-daemon)) |
325
314
  | `tick on <PROJECT>` from the Go daemon (payload `{kind: "tick"}`) | the project key; the daemon's periodic wake, whatever the slots | Recheck the trees waiting on a claim, then walk if a slot is free; post the day's report if this is the day's first turn |
326
315
  | Architect escalation (controller-actionable only: re-file a child as a root issue, capacity, cross-tree conflicts) | request + context | Judge and act; the owning architect writes an issue-design decision as a decision block and opens `dispatch_ask` only for a human to-do |
327
- | Resync report | artifact-driven anomaly list (zero-owner trees, untriaged-open, launch-failed, admission-drift) | Verify against fresh state, then heal |
328
- | Resync report: `admission-drift` entry | issue key + whether the daemon added it to, or removed it from, its admission list (the detail says which) | No action: the daemon already repaired it in the same run. An issue that reappears in consecutive reports is a live leak — file a LEGION issue on Dispatch with both reports pasted as evidence (never a GitHub issue) |
329
- | `child-status` | child key + status transition | Not controller-actionable by default; if the daemon could not route it to the parent's architect role, verify the transition and forward it with `envoy_publish` |
330
316
  | Mention | Slack/GitHub PR @mention text | Answer, or route to the owning issue's architect role |
331
- | READY packet seen on a Dispatch issue (via issue subscription) | READY line + gate facts | No action: a human merges; the merger has already notified the queue role if the project has one |
332
- | `worker-recovered` (role `architect`) from the daemon | issue, fromRef | A root architect's tree volume was lost; it restarted as a new session. Verify the tree is active in `legion state` and that the architect posts its next step on the issue within one resync interval; otherwise treat it as an anomaly. |
333
317
  | `held on <KEY>` from the Go daemon (payload `{kind: "held", phase, role?, reason?}`) | the held issue, the phase it left, and the role whose claim failed, or `reason: "escalated"` | Verify the hold in `legion state` (the issue's phase is `held`). Without `reason`, a phase worker's launches or prompts ran out and the tree's architect decides retry or escalate: no action. With `reason: "escalated"` (on the record, `issues.<KEY>.holdReason` is `escalated`), the architect sent it to you: handle it as an architect escalation below. Parking the tree is `legion status <root> backlog`; setting the root back to `todo` later re-admits it as a new generation, which starts again from its architect |
334
318
  | `worker-died on <KEY>` from the Go daemon with `role: "architect"` | the tree root whose architect's claim failed, and the phase the root was in | The tree's architect ran out of launches or prompts and the daemon relaunches nothing; every other notice of the tree goes to that architect, so nobody inside the tree can act. Verify in `legion state` (`issues.<KEY>.architect.state` is `failed`); if the root's phase is `done`, the tree is already parked: no action. Otherwise re-admit the tree (`legion status <root> backlog`, then `todo`: a new generation, whose architect starts again with fresh budgets) or leave it parked and say why on the issue |
335
- | Closed-tree activity (comment, review, CI on a closed tree) | issue, root, event summary | Read the artifact; if work should resume, `legion status <root> todo`; otherwise no action — the event is not held or redelivered |
336
319
  | Direct user message | — | Always first |
337
320
 
338
321
  ## New issue triage
@@ -357,8 +340,8 @@ priority first, then board rank ([Keeping the slots full](#keeping-the-slots-ful
357
340
  ```
358
341
 
359
342
  (or `icebox` for longer-term deferral). Dispatch status is the durable record; there is no
360
- other marker to maintain, beyond the Go daemon's `legion` label, which parking leaves in
361
- place. Under the Go daemon a root never waits for capacity in `backlog`: in `todo` the
343
+ other marker to maintain, beyond the `legion` label, which parking leaves in
344
+ place. A root never waits for capacity in `backlog`: in `todo` the
362
345
  daemon's admission queue holds it (`admission.waiting`), so park only a root that should not
363
346
  run now. Do not triage a system-created child as a root issue.
364
347
  4. When you post a triage note (a `dispatch_comment` on the issue saying what you decided and
@@ -374,18 +357,13 @@ priority first, then board rank ([Keeping the slots full](#keeping-the-slots-ful
374
357
 
375
358
  ## Backlog eligibility
376
359
 
377
- Under the Go daemon nothing reconsiders `backlog` or `icebox` on its own: [Keeping the slots
360
+ Nothing reconsiders `backlog` or `icebox` on its own: [Keeping the slots
378
361
  full](#keeping-the-slots-full-go-daemon) takes only `todo` issues, since an issue that waits on a
379
362
  deploy or a decision belongs in `backlog` (`skill://dispatch`, "Choosing what to work on"). A
380
363
  handed-over root waits for a slot in `todo`, where the daemon's
381
364
  admission queue holds it (`admission.waiting`), so a park means "should not run now", and a
382
365
  parked root keeps its `legion` label. A parked issue runs again when a person sets it to `todo`,
383
- or when a wake tells you to re-admit it (`worker-died`, closed-tree activity).
384
-
385
- Under the TypeScript daemon, when a slot frees or priority changes, use `legion state --json` and
386
- the current Dispatch issue to reconsider parked roots. Admit the selected root with
387
- `legion status <KEY> todo`. Moving an item to or from `backlog`/`icebox` is a deliberate
388
- controller decision, not a no-op.
366
+ or when a wake tells you to re-admit it (`worker-died`).
389
367
 
390
368
  ## Architect escalation
391
369
 
@@ -397,7 +375,7 @@ For an independence judgment, verify the child and its parent against current da
397
375
  and the Dispatch issue. If the work belongs in an independent root:
398
376
 
399
377
  1. File a **fresh root issue** with `dispatch_issue({ project, title, spec })` (no `parent`;
400
- under the Go daemon add `labels: ["legion"]`, without which it is never admitted).
378
+ add `labels: ["legion"]`, without which it is never admitted).
401
379
  `project` is the issue key's prefix before `-<n>` (e.g. `LEGSMOKE-3` → `LEGSMOKE`): the
402
380
  project key exactly as `legion.yaml` writes it, which `legion state --json` shows as
403
381
  `daemon.project`. It is not the lowercase project token in role names such as
@@ -411,17 +389,6 @@ Never promote a child in place. Resolve capacity and cross-tree conflicts from v
411
389
  state, routing design decisions back to the owning architect when they are not controller
412
390
  judgments.
413
391
 
414
- ## Resync report
415
-
416
- Treat a resync report as an anomaly list, not an instruction. For every zero-owner tree,
417
- untriaged-open, or launch-failed issue it names, verify `legion state --json` and the
418
- current Dispatch issue first. Then heal the verified condition: admit an eligible root, move
419
- an issue back to its intended status, or use the applicable daemon control path. Do not act
420
- on stale entries until their source artifact explains the anomaly. An `admission-drift` entry
421
- needs no healing — the daemon added the tree back to (or removed it from) its admission list in
422
- the same run; verify only that the same issue does not recur in the next report, and file a
423
- LEGION issue with both reports if it does.
424
-
425
392
  ## Mentions
426
393
 
427
394
  Read the mention and its artifact. Answer it when it asks the controller for triage or
@@ -5,9 +5,9 @@ description: Use when an issue has passed review and its parked implementer is r
5
5
 
6
6
  # Legion Retro
7
7
 
8
- Retro is mandatory for every issue that passed review. The architect revives the parked
9
- implementer so the person with implementation context performs the retrospective, and the
10
- skill obtains a separate fresh-eyes perspective. Retro runs before merge.
8
+ Retro is mandatory for every issue that passed review. The daemon starts the implementer on it
9
+ once the reviewer approves, so the person with implementation context performs the retrospective,
10
+ and the skill obtains a separate fresh-eyes perspective. Retro runs before merge.
11
11
 
12
12
  Every path this skill cites (`packages/...`, `docs/...`) is in sjawhar/legion, the Legion
13
13
  repository, which need not be the repository you are working in.
@@ -18,16 +18,17 @@ Follow this ordering exactly. It keeps the reviewed branch clean while preservin
18
18
  retrospective's durable output.
19
19
 
20
20
  1. Tester green and all code-review cycles finish.
21
- 2. The implementer pushes the `.legion/` deletion at the reviewer's direction, and the reviewer
22
- approves that head.
21
+ 2. The reviewer approves the head. It still carries `.legion/`: no role removes it before the
22
+ merge, and the operator removes it from the default branch after the merge.
23
23
  3. Run this retro: commit durable learnings to `docs/solutions/` and post the retro message on
24
24
  the Dispatch issue.
25
- Retro writes **no `.legion` file**, so it never re-dirties the cleaned handoff tree.
25
+ Retro writes **no `.legion` file**, so it never changes the approved head's handoffs.
26
26
  4. The merger verifies the tip is the approved head plus commits that change only
27
27
  `docs/solutions/` — `jj diff --from <approved-sha> --to <tip-sha> --summary`, quoted in READY —
28
- posts `READY` on the Dispatch issue, and publishes the same packet to the project's merge-queue
29
- role when configured. A human merges under the repository's GitHub branch-protection and
30
- CODEOWNERS requirements; GitHub's merge queue participates only when the repository enables it.
28
+ and sends the READY packet with its completion; the daemon posts it on the Dispatch issue and
29
+ publishes it to the project's merge-queue role when one is configured. A human merges under the
30
+ repository's GitHub branch-protection and CODEOWNERS requirements; GitHub's merge queue
31
+ participates only when the repository enables it.
31
32
  5. After that merge, the implementer — not the reviewer or merger — verifies the change in production
32
33
  and records it on the PR and the issue: the agent that developed it is responsible for testing
33
34
  in production. The architect's sign-off waits for that record.
@@ -41,14 +42,14 @@ changes only `docs/solutions/` does not void it, and the tree goes from retro to
41
42
  never back to the tester or reviewer. A conflict-forced rebase after retro moves these documents
42
43
  with the branch; retro does not re-run.
43
44
 
44
- Do not start retro before step 2, skip it because the change seems mechanical, or publish `READY`
45
- before step 3. The design gate is not a substitute for review and retro.
45
+ Do not start retro before step 2 or skip it because the change seems mechanical; the merger's
46
+ `READY` comes only after step 3. The design gate is not a substitute for review and retro.
46
47
 
47
48
  ## Two perspectives
48
49
 
49
50
  1. Re-read the issue, its acceptance criteria, the PR, test evidence, and review evidence.
50
51
  Confirm the PR carries both proofs: the implementer's own `E2E (implementer)` line and the tester's `E2E (tester)` line,
51
- each naming a production-like surface (the daemon's test harness (`packages/daemon/src/daemon/__tests__/`) and real-process fixtures; a live check at the operator's next daemon restart, recorded on the PR; a sandbox repository, a devN
52
+ each naming a production-like surface (the daemon's real-process tests and the live proofs under `scripts/e2e/`; a live check at the operator's next daemon restart, recorded on the PR; a sandbox repository, a devN
52
53
  stack, staging, or a local stack with real migrations), a command or run id, an observation, a head
53
54
  SHA, and a negative control. If either is missing, or links only a unit suite, the retro's first
54
55
  durable learning is that gap and the issue goes back — to the implementer for its own proof, to the
@@ -87,8 +88,8 @@ related_issues:
87
88
  ---
88
89
  ```
89
90
 
90
- Commit the documentation on the existing issue branch, advance its existing bookmark, and push
91
- that branch. Do not create a replacement branch or bookmark. Then post one Dispatch message on
91
+ Commit the documentation on the existing issue branch and push it with `legion push`. Do not
92
+ create a replacement branch or bookmark. Then post one Dispatch message on
92
93
  the issue — `issue` is your `LEGION_ISSUE`; Legion issues live on Dispatch, never on a GitHub
93
94
  issue, and the `gh` shim refuses every GitHub-issue write — naming the documents, the
94
95
  one-to-three most useful takeaways, the two proofs you read, and the production check that
@@ -115,16 +116,16 @@ dispatch_message({
115
116
  ```
116
117
 
117
118
  The Dispatch message and the `docs/solutions/` commit are the only retro outputs. Never write a
118
- handoff, phase artifact, local feedback log, or completion label; `.legion/` was deleted before
119
- retro and nothing recreates it. Report completion with the `legion` tool's `handoff_complete` alone (its
119
+ handoff, phase artifact, local feedback log, or completion label, and add or change nothing under
120
+ `.legion/`. Report completion with the `legion` tool's `handoff_complete` alone (its
120
121
  summary: two sentences for the architect) — no `handoff_write`.
121
122
 
122
123
  ## Completion check
123
124
 
124
125
  Before returning, verify all of the following:
125
126
 
126
- - The reviewer cleanup commit remains below the retro documentation commit, and the reviewer's
127
- approval of that cleanup head stands: the merger accepts the approved head plus this commit.
127
+ - The reviewer-approved head remains below the retro documentation commit, and the reviewer's
128
+ approval of that head stands: the merger accepts the approved head plus this commit.
128
129
  - The learning documents and the Dispatch message both exist (never a GitHub issue comment).
129
130
  - Both proofs were read, and any gap in either is recorded as a learning.
130
131
  - No `.legion` file was created or modified by retro.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: legion-worker
3
- description: Use when dispatched as a per-process Legion phase worker — architect (on a child issue), planner, implementer, tester, reviewer, or merger — booted from the daemon's LEGION_* environment.
3
+ description: Use when dispatched as a per-process Legion phase worker — planner, implementer, tester, reviewer, or merger — booted from the daemon's LEGION_* environment.
4
4
  ---
5
5
 
6
6
  # Legion Phase Worker
@@ -24,26 +24,26 @@ with the `read` tool: a read by filesystem path stops at 300 lines.
24
24
  | You write or edit the PR body, record a proof (an `E2E` line or a handoff `proof` array), verify another phase's proof, or run the simplify pass | `skill://legion-worker/references/pr-body.md` |
25
25
  | You reply to, accept, or resolve a review thread, or run `legion threads resolve` | `skill://legion-worker/references/review-threads.md` |
26
26
  | GitHub reports a conflict, the pull request is retargeted, you compare heads after a conflict merge, or you would rewrite a pushed commit | `skill://legion-worker/references/conflicts-and-rewrites.md` |
27
- | You review or approve, push the `.legion/` deletion, publish READY, or check the merge in production | `skill://legion-worker/references/merge-gate.md` |
27
+ | You review or approve, build the READY packet, or check the merge in production | `skill://legion-worker/references/merge-gate.md` |
28
28
  | The issue renames a repository, package, or URL across the codebase | `skill://legion-worker/references/systematic-rename.md` |
29
29
  | The issue deletes code, a command, or documentation | `skill://legion-worker/references/cleanup-deletion.md` |
30
30
 
31
31
  ## Identity, scope, and role
32
32
 
33
33
  The daemon spawns you as a separate `omp --mode rpc` process (behind `legion worker-shim`,
34
- in a tmux pane) with `LEGION_TREE`, `LEGION_ISSUE`, `LEGION_ROLE`, `LEGION_GENERATION`,
35
- `LEGION_BOOT_TOKEN_FILE` (a 0600 file under `$LEGION_STATE_DIR/secrets` holding your boot token;
36
- the extension reads it for you), `LEGION_DAEMON_URL`, `LEGION_STATE_DIR`, and `LEGION_WORKSPACE`
37
- in your environment (`LEGION_PROJECT` is also supplied, but nothing reads it). The extension
34
+ in a tmux pane or an Agent Sandbox pod) with `LEGION_TREE`, `LEGION_ISSUE`, `LEGION_ROLE`,
35
+ `LEGION_GENERATION`, `LEGION_PROJECT`, `LEGION_BOOT_TOKEN_FILE` (the file holding your boot
36
+ token; the extension reads it for you), `LEGION_DAEMON_URL`, `LEGION_STATE_DIR`, and
37
+ `LEGION_WORKSPACE` in your environment. The extension
38
38
  completes the boot handshake for you at session start — it registers with the daemon, claims
39
39
  your role, and signals readiness. You never call `envoy_role_set` yourself.
40
40
 
41
41
  Your role token is not the issue key spelled out literally. The daemon encodes it as
42
42
  `legion-<project>-<key>-<role>` with the issue key lower-cased. For example, project `acme`, issue
43
43
  `LEGION-41`, role `architect` encodes to `legion-acme-legion-41-architect`. Never hand-format one for another role: your own role
44
- topic and the topic of the architect that owns your issue are stated at the end of your system
45
- prompt (a "Legion addressing" line the daemon appends: on a child issue that is the child's
46
- sub-architect when one is claimed, else the root's), a sibling role's topic is yours with the
44
+ topic and the topic of the architect that owns your issue are in the `Legion addressing` line of
45
+ your system prompt (the daemon appends it; it names the tree root's architect, also on a child
46
+ issue), a sibling role's topic is yours with the
47
47
  trailing `-<role>` replaced, and the `roleToken` helper in `@legion/contracts` computes any other
48
48
  one exactly the way the daemon does — prefer a topic you've already been given before recomputing
49
49
  one.
@@ -166,7 +166,7 @@ other tree paused.
166
166
 
167
167
  ## Phase work
168
168
 
169
- Specifications written into Dispatch follow `skill://dispatch`'s [Writing a spec](../dispatch/SKILL.md#writing-a-spec), except that a phase worker writes no decision block: it sends an open product, scope or design decision to its architect, which writes the block.
169
+ Specifications written into Dispatch follow `skill://dispatch`'s "Writing a spec" section, except that a phase worker writes no decision block: it sends an open product, scope or design decision to its architect, which writes the block.
170
170
 
171
171
  Follow the repository's normal engineering workflow and the assigned issue's acceptance
172
172
  criteria. Your phase's own charter and the predecessor handoffs you read define the phase
@@ -214,7 +214,7 @@ legion gh -- <gh args…>
214
214
  ```
215
215
 
216
216
  Four facts about `gh` in a worker pane. The `gh` on your `PATH` is a shim
217
- (`<state_dir>/worker-bin/gh`, installed by the daemon at startup — `packages/daemon/src/daemon/worker-bin.ts`)
217
+ (`<state_dir>/worker-bin/gh`, installed by the daemon at startup — `packages/daemon/internal/runtime/workerbin/workerbin.go`)
218
218
  that execs `legion gh -- "$@"`, so `gh …` and `legion gh -- …` are the same call, and each call
219
219
  redeems a fresh token from your session's grant — identity is supplied per call, never stored.
220
220
  Never run `gh auth login` or `gh auth setup-git`; there is no login state to create. The shim
@@ -282,8 +282,8 @@ pushes the issue branch under this exact name with the one push procedure every
282
282
  (*Every role pushes its own commits*, below).
283
283
 
284
284
  The provisioned issue workspace configures `credential.helper` with the daemon's absolute
285
- credential command, so `jj -R "$LEGION_WORKSPACE" git push` authenticates transparently
286
- through the same session capability. Never handle a token.
285
+ credential command, so `legion push` authenticates transparently through the same session
286
+ capability. Never handle a token.
287
287
 
288
288
  Then open the pull request with `legion gh -- pr create`. The PR body **must** contain the
289
289
  line `Dispatch: <KEY>` — the daemon's fallback link from a PR to its Dispatch issue when the
@@ -314,9 +314,9 @@ line), the full definition of a proof, what the tester verifies, and the simplif
314
314
  - **A conflict or a retarget** is the only reason to bring the base into the branch, always as a
315
315
  forward merge and never `jj rebase`; the unchanged-diff fingerprint each role compares
316
316
  afterwards is in the same reference: `skill://legion-worker/references/conflicts-and-rewrites.md`.
317
- - **The merge gate**, in order: the tester's evidence green → the implementer's `.legion/`
318
- deletion push → the reviewer's approval of that head → retro → the merger's READY → the human
319
- merge → the implementer's production check. Legion never merges. The reviewer's submissions,
317
+ - **The merge gate**, in order: the tester's evidence green → the reviewer's approval of the head
318
+ → retro → the merger's READY → the human merge → the implementer's production check. Legion
319
+ never merges. The reviewer's submissions,
320
320
  retro's commit, the merger's READY, and the production check follow
321
321
  `skill://legion-worker/references/merge-gate.md`.
322
322
  - **No surface reaches the changed path** is a report to the architect, never a reason to
@@ -359,58 +359,25 @@ cd -- "$LEGION_WORKSPACE" && \
359
359
  ```
360
360
 
361
361
  **Every role pushes its own commits.** After the handoff commit — and, for the tester, the red
362
- tests it wrote — advance the issue bookmark and push it with the provisioned credential helper,
363
- which authenticates as your role's App (`appRoleForLegionRole` in
364
- `packages/daemon/src/daemon/github-apps.ts`).
365
-
366
- **Under the Go daemon, every push is `legion push`,** run from bash in your workspace in place of
367
- the commands below. It runs this same procedure on `@-`: the ancestry check, against the remote
368
- branch or the tip you recorded before rewriting pushed commits (below), then the bookmark and the
369
- push. It also decides whether the push skips CI. A push skips CI only when none of its commits
370
- touches anything but handoffs whose phase guarantees a later push: the planner's
371
- `.legion/plan.json`, the tester's `.legion/test.json`, and a reviewer's `.legion/review.json` whose
372
- `verdict` is `"changes_requested"`. Its head then ends with GitHub's `skip-checks: true` trailer,
373
- and the Go daemon carries the code head's verdict to it. Every other push runs CI in full. Never
374
- add or remove that trailer yourself, never write one of GitHub's bracket keywords (`[skip ci]`,
375
- `[ci skip]`, `[no ci]`, `[skip actions]`, `[actions skip]`) into a commit message, and never push
376
- the issue branch with the commands below under the Go daemon: a hand-run push of a handoff that
377
- could skip CI runs it in full, and a hand-added trailer or keyword on any other push skips CI on a
378
- head a human may merge. `legion push` refuses a head whose message carries a keyword and pushes
379
- nothing until you take it out. Under the TypeScript daemon, whose `legion` has no `push` command,
380
- run the commands below yourself.
381
-
382
- `-r @-` puts the bookmark on the commit you just
383
- split off: the working copy left above it has no description, and `jj git push` refuses a
384
- commit without one. `--allow-backwards` is for that local step alone: after a split the bookmark
385
- can sit on the undescribed working copy above `@-`. `--bookmark` also publishes the locally
386
- provisioned bookmark on its first push — a bookmark not yet tracking a remote one is tracked
387
- automatically. This is the one push procedure, run as `legion push` under the Go daemon and by
388
- hand under the TypeScript daemon; every push of the issue branch uses it:
362
+ tests it wrote — push the issue branch with `legion push`, run from bash in your workspace:
389
363
 
390
364
  ```bash
391
- cd -- "$LEGION_WORKSPACE" && \
392
- tip_file="${TMPDIR:-/tmp}/legion-<KEY>-$LEGION_ROLE-rewritten-tip" && \
393
- old=$(cat -- "$tip_file" 2>/dev/null || true) && \
394
- behind=$(jj -R "$LEGION_WORKSPACE" log --no-graph -T 'commit_id.short() ++ "\n"' \
395
- -r "remote_bookmarks(exact:\"legion/<KEY>\", exact:\"origin\") ~ (::@-${old:+ | $old})") && \
396
- { [ -z "$behind" ] || { echo "legion/<KEY>@origin is at $behind, which @- does not descend from" >&2; false; }; } && \
397
- jj -R "$LEGION_WORKSPACE" bookmark set legion/<KEY> -r @- --allow-backwards && \
398
- jj -R "$LEGION_WORKSPACE" git push --bookmark legion/<KEY> && \
399
- rm -f -- "$tip_file"
365
+ cd -- "$LEGION_WORKSPACE" && legion push
400
366
  ```
401
367
 
402
- The `behind` check refuses unless `@-` descends from `legion/<KEY>@origin` (or the branch is not
403
- on GitHub yet). Every issue workspace shares one clone, so another role's push moves
404
- `legion/<KEY>@origin` here at once. With the flag and no check, `jj git push` then moves the
405
- remote branch sideways onto your commit and drops theirs (jj 0.45.1:
406
- `bookmark: legion/K [move sideways from <theirs> to <yours>]`). A clone that has not seen the other
407
- push is refused by jj itself (`unexpectedly moved on the remote`).
368
+ It pushes `@-` through the provisioned credential helper, which authenticates as your role's App
369
+ (`appauth.AppRoleFor` in `packages/daemon/internal/appauth/identity.go`). Your system prompt says
370
+ which pushes skip CI and which commit-message keywords it refuses. Its ancestry check refuses
371
+ unless `@-` descends from `legion/<KEY>@origin` (or the branch is not on GitHub yet): every issue
372
+ workspace shares one clone, so another role's push moves `legion/<KEY>@origin` here at once, and
373
+ a push that did not descend from it would move the remote branch sideways onto your commit and
374
+ drop theirs.
408
375
 
409
376
  Before any rewrite of a commit you already pushed — a `jj squash --into` one, or any other
410
377
  rewrite — read *Rewriting pushed commits* in
411
378
  `skill://legion-worker/references/conflicts-and-rewrites.md`: it checks that no other tree's work
412
- is built on that commit and records the pushed tip `$tip_file` above reads, the only case in which
413
- the push above lets the remote branch move off its current tip.
379
+ is built on that commit and records the pushed tip `legion push` reads, the only case in which a
380
+ push lets the remote branch move off its current tip.
414
381
 
415
382
  Before the push, check ancestry and identity as above: the chain carries every earlier phase's
416
383
  commits, and pushing them with yours is expected. A refusal, and a push the remote rejects, is a
@@ -418,14 +385,10 @@ report to the architect with the output, never a force-push. The merger makes no
418
385
  pushes nothing.
419
386
 
420
387
  Do not report phase completion until the write, existence check, handoff commit, and push
421
- succeed. This is the committed copy the next phase
422
- reads after revival. It is removed once, at the end of a clean review: the implementer pushes
423
- that deletion at the reviewer's direction. No other phase removes it — and once it is gone
424
- (`jj -R "$LEGION_WORKSPACE" file list -r @- .legion` prints nothing on stdout; jj warns on
425
- stderr), this gate no longer applies: a later rebase, bare-gate re-check, confirmation, retro, or
426
- the post-merge production check writes no `.legion/<phase>.json`, commits no handoff, and reports
427
- with `handoff_complete` alone (below). Recreating `.legion/` after its deletion changes the
428
- approved head and restarts the review loop this rule exists to end.
388
+ succeed. This is the committed copy the next phase reads after revival. No phase removes
389
+ `.legion/`: the reviewer approves a head that carries it, and the operator removes it from the
390
+ default branch after the merge. Retro and the post-merge production check write no
391
+ `.legion/<phase>.json`, commit no handoff, and report with `handoff_complete` alone (below).
429
392
 
430
393
  ## Completion: report to the architect, then stay
431
394
 
@@ -1,35 +1,32 @@
1
1
  # The merge gate: review, retro, READY, and the production check
2
2
 
3
3
  Part of `skill://legion-worker`. Read it when you are the reviewer submitting a review or an
4
- approval, the implementer pushing the `.legion/` deletion or recording the production check, or
5
- the merger publishing READY. Every path it cites is in sjawhar/legion.
4
+ approval, the implementer recording the production check, or the merger building READY. Every
5
+ path it cites is in sjawhar/legion.
6
6
 
7
- The order, in full: the tester's evidence green → the implementer's `.legion/` deletion push →
8
- the reviewer's approval of that head → retro → the merger's READY → the human merge → the
9
- implementer's production check. After the approval, only retro's `docs/solutions/` commit leaves
10
- it standing on its own (*Retro*, below). A conflict-forced merge goes back to the reviewer for a
11
- confirmation or a new round, as the fingerprint decides (*The reviewer*, below, and
12
- `skill://legion-worker/references/conflicts-and-rewrites.md`), and any other change voids it.
7
+ The order, in full: the tester's evidence green → the reviewer's approval of the head →
8
+ retro → the merger's READY → the human merge → the implementer's production check. No role
9
+ removes `.legion/` before the merge: the approved head carries it, and the operator removes it
10
+ from the default branch after the merge. After the approval, only retro's `docs/solutions/`
11
+ commit leaves it standing on its own (*Retro*, below). A conflict-forced merge goes back to the
12
+ reviewer for a confirmation or a new round, as the fingerprint decides (*The reviewer*, below,
13
+ and `skill://legion-worker/references/conflicts-and-rewrites.md`), and any other change voids it.
13
14
 
14
15
  ## The reviewer
15
16
 
16
17
  - The reviewer verifies the `CI`, `Threads`, and `E2E` facts against GitHub directly —
17
18
  never from a handoff — then runs `task(agent="thermonuclear-deep-review")` and
18
- `task(agent="thermonuclear-code-quality")` once at that head — the head the implementer's
19
- simplify pass left final — and records the verdict.
19
+ `task(agent="thermonuclear-code-quality")` once at that head — the head the round reviews —
20
+ and records the verdict.
20
21
  Approval is refused while either `E2E (implementer)` or `E2E (tester)` is missing: `REQUEST_CHANGES` naming the missing line.
21
22
  Skip the `Thermo` line entirely on a docs-only PR. Submit **one review per round** —
22
- `REQUEST_CHANGES` when any correctness finding stands, otherwise `COMMENT` while the head
23
- still carries `.legion/`; `APPROVE` only for a head that carries no `.legion/` — the head
24
- that differs from the reviewed one by the `.legion/` deletion alone, or, after a
25
- conflict-forced rebase, the new head whose fingerprint equals the approved head's — always
26
- named by SHA — carrying every inline comment in that single
23
+ `REQUEST_CHANGES` when any correctness finding stands, otherwise `APPROVE` of the head you
24
+ reviewed — always named by SHA — carrying every inline comment in that single
27
25
  call: `legion gh -- api --method POST repos/{owner}/{repo}/pulls/{number}/reviews --input body.json`
28
- with `commit_id`, `event` (`REQUEST_CHANGES`, `COMMENT`, or `APPROVE`), `body` (with the
26
+ with `commit_id`, `event` (`REQUEST_CHANGES` or `APPROVE`), `body` (with the
29
27
  Legion footer), and a `comments[]` array of `{path, line, side, body}`, one entry per
30
28
  finding — never one `pr review` call per finding (each submission fires a `pr-review` wake).
31
- Then return the issue to the architect; when clean, have the architect send the implementer
32
- back to push the `.legion/` deletion, then review **that** head and approve it by name. After a
29
+ A `COMMENT` decides nothing. After a
33
30
  conflict-forced rebase, compute the fingerprint (*The unchanged-diff check* in
34
31
  `skill://legion-worker/references/conflicts-and-rewrites.md`) at the
35
32
  `commit_id` of your last submitted review and at the new head. Equal and that review was
@@ -41,8 +38,8 @@ confirmation or a new round, as the fingerprint decides (*The reviewer*, below,
41
38
  Apps, as `skill://legion-worker/references/review-threads.md` says; the same reference says
42
39
  when every thread is settled enough to approve, and resolving one never gates your approval.
43
40
 
44
- A reviewer's phase ends with its completion, not with its review. A round that writes a handoff
45
- takes this order: write, commit and push the handoff; submit the review of the head that push
41
+ A reviewer's phase ends with its completion, not with its review. Every round writes a handoff
42
+ and takes this order: write, commit and push the handoff; submit the review of the head that push
46
43
  made, by its SHA; then complete. An approval waits for the CI verdict to settle green at that head
47
44
  before you submit it, since an approval stands only on green checks and GitHub can dismiss one
48
45
  once the head moves, and a verdict that settles red there makes the round's decision a request for
@@ -57,40 +54,39 @@ require. A required check that was cancelled, or that the head's checks
57
54
  settled without, leaves no verdict until a later settlement decides it, since a run can be
58
55
  cancelled or not yet queued when the head settles; a required workflow's run on the head that is
59
56
  still going or has not happened leaves none either. A review of a head the handoff push
60
- then replaces names a head the pull request no longer has. A round that writes none (the final
61
- approval of the `.legion/` deletion head) reviews the head as it is. The daemon moves the issue
57
+ then replaces names a head the pull request no longer has. The daemon moves the issue
62
58
  once both are in — the decision GitHub reports and your completion, in either order — so a
63
59
  review posted without a completion leaves the issue in reviewing until you finish.
64
60
 
65
61
  ## Retro
66
62
 
67
63
  - **Retro's commit does not void the reviewer's approval.** After the reviewer approves the
68
- cleaned head, retro commits its learnings under `docs/solutions/` on top of it; that commit
64
+ head, retro commits its learnings under `docs/solutions/` on top of it; that commit
69
65
  stays, the approval stands, and the tree goes to the merger — never back to the tester or
70
66
  reviewer. Anything else above the approved head does void it, and the merger tells the
71
- architect the head must return to review instead of publishing. A conflict-forced rebase
67
+ architect the head must return to review instead of completing. A conflict-forced rebase
72
68
  after retro moves those documents with the branch; retro never re-runs.
73
69
 
74
70
  ## The merger
75
71
 
76
72
  - The merger runs `legion threads resolve --pr <n> --repo <owner>/<repo>` (it acts as the same
77
73
  code-writing App as the implementer; resolving a thread changes no commit, so this run never
78
- invalidates the approval), does not publish while any `left open` line remains or the command
74
+ invalidates the approval), does not complete while any `left open` line remains or the command
79
75
  exits 1 (report the thread to the architect instead), then proves that rule with two commands.
80
76
  First `cd -- "$LEGION_WORKSPACE" && jj -R "$LEGION_WORKSPACE" git fetch && jj -R
81
77
  "$LEGION_WORKSPACE" diff --from <approved-sha> --to <tip-sha> --summary`, whose output is quoted
82
78
  in READY (an empty output is quoted as `no file changes above the approved head`); then the same
83
- with `'~docs/solutions'` appended, which must print nothing. *The READY packet*: the merger
84
- always posts `READY #<n> at <current sha> (approved at <approved sha>) for <KEY> (<pr url>)`
79
+ with `'~docs/solutions'` appended, which must print nothing. *The READY packet* is
80
+ `READY #<n> at <current sha> (approved at <approved sha>) for <KEY> (<pr url>)`
85
81
  (the shape `packages/daemon/internal/prompts/roles/merger.md` defines), then the PR body's
86
82
  `Outcome:` line and its `Not proven / risk:` value — every bullet under that label joined with
87
83
  `; ` on the one READY line, or `none` — quoted from the `## For the reviewer` block at that same
88
- head (or one line saying the body carries no brief — the packet still publishes), then the
89
- `--summary` output and the PR body's gate facts, as a `dispatch_message` on the issue. When the
90
- `Legion addressing` line names a merge queue, it also publishes the same packet there with
91
- `envoy_publish`; a 404 means the Dispatch message remains the durable notice and the merger
92
- stays idle. The READY packet names both the implementer's and tester's `E2E` lines; a missing
93
- one is reported to the architect instead of published. Legion never merges.
84
+ head (or one line saying the body carries no brief — the packet still goes out), then the
85
+ `--summary` output and the PR body's gate facts. The merger sends it as the `summary` of its
86
+ `handoff_complete` with `ready: true`; the daemon posts it as a `dispatch_message` on the issue,
87
+ publishes it to the project's merge queue role when one is set, and says on the issue when that
88
+ role has no live holder. The READY packet names both the implementer's and tester's `E2E` lines;
89
+ a missing one is reported to the architect instead of completing. Legion never merges.
94
90
 
95
91
  ## After the human merge
96
92
 
@@ -36,7 +36,7 @@ left open <thread URL> — newest reply by <login> is not an acceptance
36
36
  left open <thread URL> — newest reply by <login> is an unsubmitted draft in a pending review
37
37
  left open <thread URL> — newest reply by <login> is not its opener's or the Legion reviewer's acceptance
38
38
 
39
- **Thermo:** `ce-simplify-code` once at <head-sha>: <0 applied | applied → new head <sha>>; thermonuclear pair at the final head <sha>:
39
+ **Thermo:** `ce-simplify-code` once at <head-sha>: <0 applied | applied → new head <sha>>; thermonuclear pair at the reviewed head <sha>:
40
40
  <verdict>. (omitted entirely on a docs-only PR — there is no code for either pass, so neither runs)
41
41
 
42
42
  **E2E (implementer):** <surface> — ran `<command or run id>`, observed <result>, at head <sha>.
@@ -66,7 +66,7 @@ repos/{owner}/{repo}/pulls/{number} --jq .body`, edit, then `--method PATCH ...
66
66
  `Not proven / risk` copies every claim recorded as unproven before READY — the tester's
67
67
  `failures`, the reviewer's own review, any proof-check comment already on the pull request —
68
68
  word for word, and `none` is a finding while one stands. The merger quotes `Outcome` and
69
- `Not proven / risk` from the body at the published head in the READY packet
69
+ `Not proven / risk` from the body at the head its READY packet names
70
70
  (*The READY packet* in `skill://legion-worker/references/merge-gate.md`); a stale `Outcome` that no
71
71
  longer describes the diff is a finding against the implementer, not a line the merger rewrites.
72
72
 
@@ -95,8 +95,8 @@ and the tester's proof below are both this proof.
95
95
  the implementer's command or drives the same surface independently, and records the verdict in
96
96
  `.legion/test.json` as `implementerProof` (`{verdict, how}`).
97
97
  A test handoff whose predecessor carried no proof is a test failure, not a gap for the tester to fill:
98
- record it in `failures` with `implementerProof.verdict: "rejected"`, complete the phase, and let
99
- the architect return the issue to the implementer — the agent that developed the change owns
98
+ record it in `failures` with `implementerProof.verdict: "rejected"`, complete the phase with
99
+ `verdict: "fail"`, and the daemon returns the issue to the implementer — the agent that developed the change owns
100
100
  proving it (`handoff_write` for phase `test` refuses a rejected verdict, or `failed > 0`,
101
101
  with no recorded failure). Otherwise, add your own proof before completing — a proof as defined
102
102
  above — as the `E2E (tester)` line and the `proof` array `handoff_write` for
@@ -114,18 +114,17 @@ and the tester's proof below are both this proof.
114
114
  `E2E` line's head to the new SHA with
115
115
  `rebase re-check <old-sha> → <new-sha>: fingerprint unchanged, bare gates only`; the
116
116
  real-surface verification is not repeated. Different: a full test round.
117
- - **The implementer runs `skill://ce-simplify-code` once per pull request, after the last review round
118
- closes and before the reviewer's final pass, when the diff touches runtime code; a docs-only
119
- diff gets none.** It is scoped to the pull request's own diff, at the head where the last review
120
- round closed: nothing applied leaves that head final; applied → the applied head is the final
121
- head: CI runs on it, the pair runs once on it, and the E2E proof re-runs on it for the surface
122
- the simplify diff touched, since a refactor that "preserves behaviour" is a claim until it is
123
- executed. That cost is
124
- why 0-applied is the expected outcome and a pass that applies is spent sparingly. At the applied
125
- head the implementer re-cites the `CI` line and re-runs its own proof into `E2E (implementer)`,
126
- and the tester re-runs its proof for the touched surface into `E2E (tester)`, before the
127
- reviewer's final pass. Simplify is the last code change; the pair is the last review. Record it
128
- in the `Thermo` line.
117
+ - **The implementer runs `skill://ce-simplify-code` once per pull request, in its first
118
+ implementing round, after its own proof and before it completes that round, when the diff
119
+ touches runtime code; a docs-only diff gets none.** The daemon starts no implementing round
120
+ between a clean review and the approval, so this is the one slot before the tester and the
121
+ reviewer read the head; a later round answering a review runs no second pass. It is scoped to
122
+ the pull request's own diff at that head: nothing applied leaves the head as it is; applied →
123
+ the implementer re-runs the repository's checks and its own proof on the applied head for the
124
+ surface the simplify diff touched, since a refactor that "preserves behaviour" is a claim until
125
+ it is executed, and re-cites the `CI` line and `E2E (implementer)` there before it completes.
126
+ That cost is why 0-applied is the expected outcome and a pass that applies is spent sparingly.
127
+ The reviewer's pair runs at the head each round reviews. Record both in the `Thermo` line.
129
128
  - **No deferrals** is the body's rule (*PR body, review, and the merge gate* in
130
129
  `skill://legion-worker`): the `Fast-follow:` line holds naming, duplication, or wording cleanup
131
130
  only. A base frozen for others to stack on is never rewritten (*Rewriting pushed commits* in
@@ -35,8 +35,7 @@ Every path it cites is in sjawhar/legion.
35
35
  your own `gh`; where no `legion` command is installed, use `gh api graphql` with the session's
36
36
  GitHub credential and the fallback below.
37
37
  In a Legion pane, the **implementer** runs the command after every push that answers a review
38
- (the corrective push, and the final `.legion/` deletion push where the daemon has one) and
39
- before its `handoff_complete`, and pastes its output, stamped with the head it just pushed, into
38
+ and before its `handoff_complete`, and pastes its output, stamped with the head it just pushed, into
40
39
  the `Threads` section. The output is then recorded against the head the reviewer will read, and
41
40
  nothing reads thread state before the implementer's completion. The command resolves each
42
41
  unresolved thread whose newest submitted comment is the opener's own `Accepted:` reply. On a
@@ -80,8 +79,8 @@ Every path it cites is in sjawhar/legion.
80
79
 
81
80
  Re-read `reviewThreads` and confirm that thread's `isResolved` is true. In either route, report
82
81
  a refused resolution to the architect, which opens an ask for a human to resolve the thread by
83
- hand — never skip it silently. The merger runs the command once more before publishing READY
84
- and does not publish while any `left open` line remains. That run is where every accepted
82
+ hand — never skip it silently. The merger runs the command once more before its READY
83
+ completion and does not complete while any `left open` line remains. That run is where every accepted
85
84
  thread's resolution is guaranteed, since the merge queue's gate counts the unresolved threads at
86
85
  the head. Acceptances posted after the implementer's last run are resolved here.
87
86
 
@@ -99,4 +98,4 @@ Every path it cites is in sjawhar/legion.
99
98
  workflow passes on a re-run only once its threads are resolved, so you run `legion threads
100
99
  resolve` (the daemon resolves the bot threads you accepted) and re-run the failed run before you
101
100
  approve, as your role prompt says.
102
- The merger resolves accepted threads that remain open before publishing READY.
101
+ The merger resolves accepted threads that remain open before its READY completion.