@sjawhar/opencode-legion-envoy 0.15.0 → 0.17.0

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": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
@@ -1,255 +1,224 @@
1
1
  ---
2
2
  name: dispatch
3
- description: Use when you need a human decision from Sami or the user — asking a question, waiting on approval, saying "let me know", writing an end-of-message question block, picking between options with tradeoffs, or starting unattended/long-running work that will need input later. Raises the question as a durable GitHub-issue thread instead of a transcript question nobody will see.
3
+ description: "Use when asking Sami a question, updating the spec, commenting on a document, attaching an artifact, or calling a dispatch_* tool."
4
4
  ---
5
5
 
6
6
  # Dispatch
7
7
 
8
- `dispatch` turns a question into a durable, dashboard-visible GitHub-issue thread instead
9
- of a line buried in a transcript. A thread is **one decision, held as a conversation**: it
10
- opens with your question, the human replies, and if the reply changes the question you ask
11
- again on the same thread. It closes when the decision is settled, not when the first reply
12
- lands. Use it any time you would otherwise leave a question for a human to notice on their
13
- own.
14
-
15
- ## When to dispatch
16
-
17
- - Any question not answerable at the keyboard in seconds.
18
- - Anything decision-shaped: a fork in approach, a tradeoff only the human can weigh, an
19
- approval gate.
20
- - Anything you would otherwise write as an end-of-message question block and hope someone
21
- reads.
22
- - Before you park on a blocker. Never leave a session idle on an unasked question.
23
- - Unattended or long-running work (Legion roles, background agents) that will need input
24
- later — dispatch the question as soon as you know you'll need it, don't wait to be asked.
25
-
26
- ## When NOT to dispatch
27
-
28
- - A one-word clarification the human is actively typing with you right now, in the same
29
- turn — use the built-in `ask` tool instead. `dispatch` is for questions the reader has
30
- not seen your transcript for; `ask` is for questions inside a live conversation.
31
- - Anything you can resolve yourself from tools or repo context. Dispatch is for genuine
32
- human decisions, not a substitute for research.
33
-
34
- ## Writing for the reader
35
-
36
- The reader has not seen your transcript. They are looking at a card on a dashboard between
37
- other things, and they will answer the question you wrote, not the one you meant.
38
-
39
- - **No nouns you coined this session.** No "the list above", "those items", "lane B",
40
- "the eval-set thing". No internal identifiers — eval-set ids, lane names, hashes,
41
- session ids unless the question is about them. Expand every identifier on first use.
42
- GitHub references may be bare: `#482`, `acme-org/example-repo#17`, or a URL — the
43
- dashboard unfurls them into their titles.
44
- - **Structure over paragraphs.** `context` is at most three short paragraphs or a bullet
45
- list, one idea each: what you are doing, what you found, why you are stuck. `question`
46
- is a list: current state → desired state → your recommendation and why; the options
47
- themselves go in `ask`, each tradeoff as its `description`. Don't just describe the
48
- fork — say which branch you'd take and why.
49
- - **Options are buttons.** If you are offering choices, put them in `ask`; never enumerate
50
- them in prose. A human answers a button in one click and the answer arrives structured;
51
- a choice buried in a paragraph arrives as a sentence you have to interpret.
52
- - **Length caps.** `context` is at most 1200 characters, `question` at most 800. The
53
- service refuses a longer call before anything reaches GitHub, naming the field and the
54
- limit (`context is 1450 characters; the limit is 1200`); nothing is truncated. When you
55
- hit a cap: move the choices into `ask`, cut background the reader does not need to
56
- decide, and if there really are two decisions, open two threads.
57
-
58
- ### Before / after
59
-
60
- A real thread, as first written — one block, coined nouns, options in prose:
61
-
62
- ```
63
- subject: "e2e submitter"
64
- context: "So I've been going through the e2e harness like we discussed and the submitter
65
- identity thing from earlier is still broken because the fixture in lane B uses
66
- the shared bot account which means the assertions in eval-set 7f3a can't tell
67
- who submitted, and the thing I mentioned about the env var not being set in CI
68
- is also true so basically the list above doesn't apply until we pick one, and I
69
- looked at how the other repo does it and they hardcode it which is fine for them
70
- but not us, also the token minting issue is related but separate."
71
- question: "should I add E2E_SUBMITTER_EMAIL or use the bot or something else? I think
72
- probably the env var but not sure, or maybe the hardcode thing, or the third
73
- option where the harness reads it from git config. Let me know."
74
- ```
75
-
76
- The same decision, written for the reader:
77
-
78
- ```
79
- subject: "E2E harness: which identity submits test PRs?"
80
- context: "- The end-to-end test harness (acme-org/example-repo#17158) submits PRs as the
81
- shared bot account, so a run cannot tell test submissions apart from real ones.
82
- - CI sets no submitter identity today; the harness has no setting for one."
83
- question: "- Current: every test PR is authored by the shared bot.
84
- - Desired: each run's PRs carry an identity that marks them as test submissions.
85
- - Recommendation: a dedicated identity from one CI environment variable —
86
- smallest change, no per-machine setup."
87
- ask: [{ header: "Test submitter identity",
88
- question: "Which identity should the harness use?",
89
- options: [
90
- { label: "Env var E2E_SUBMITTER_EMAIL", description: "one CI variable; the harness reads it" },
91
- { label: "Shared bot (status quo)", description: "no change; runs stay indistinguishable" },
92
- { label: "Read from git config", description: "per-machine setup; drifts" } ] }]
93
- ```
94
-
95
- ## How
96
-
97
- ### Opening a thread
98
-
99
- ```
100
- dispatch({
101
- subject: "Postgres migration: run online or take a maintenance window?",
102
- context: "- Implementing the users-table index migration for #482.
103
- - The table has 40M rows; a plain CREATE INDEX locks writes for ~6 minutes
104
- in the staging timing test I just ran.",
105
- question: "- Current: no online-migration tooling in this repo's deploy pipeline.
106
- - Desired: index added without a customer-visible write outage.
107
- - Recommendation: the maintenance window — smaller blast radius, no
108
- migration-runner change, inside the existing SLA.",
109
- ask: [{ header: "Approach", question: "Which way?",
110
- options: [
111
- { label: "CREATE INDEX CONCURRENTLY", description: "no lock, ~25 min, needs a migration-runner change" },
112
- { label: "6-minute window at 03:00 UTC", description: "runner unchanged, brief write outage" } ] }],
113
- urgency: "med"
8
+ Dispatch is your issue's living spec, asks, comments, and artifacts. The transcript is your
9
+ scratch pad. Anything meant for a human goes through a `dispatch_*` tool.
10
+
11
+ The server enforces high signal: an ask question is at most 800 characters with at most eight
12
+ options; comment and message bodies are at most 2,000 characters; an artifact is at most 25 MiB.
13
+ It refuses over-limit input; it never truncates it. GitHub threads and markers no longer exist.
14
+
15
+ ## Your issue
16
+
17
+ Every session works on an issue. Legion pre-fills `issue` from `LEGION_ISSUE`: use a native issue key
18
+ such as `LEGION-3`, an external `owner/repo#n` reference, or a bare positive number (resolved
19
+ against the cwd repository). Otherwise pass the issue to every issue-scoped tool as its native key
20
+ or an external `owner/repo#n` reference. On first use, an external reference creates its native issue in the
21
+ project mapped by `DISPATCH_REPO_PROJECTS`.
22
+
23
+ Architects create newly tracked child work with:
24
+ ```ts
25
+ dispatch_issue({ project, title, parent?, external?, spec? })
26
+ ```
27
+ It returns `details` `{ issue, topic }`. Use `dispatch_issue` only to create an issue; never use
28
+ it to park a question.
29
+
30
+ ## Asking
31
+
32
+ Open a decision with:
33
+ ```ts
34
+ dispatch_ask({
35
+ issue,
36
+ question,
37
+ options?: { label, description? }[],
38
+ multiple?,
39
+ custom?,
40
+ urgency?,
41
+ anchor?: { artifact, quote, occurrence? },
114
42
  })
115
43
  ```
44
+ It returns `details` `{ issue, topic, ask }`. Options are buttons: never enumerate choices in
45
+ prose. Put the recommendation in `question`, and put each selectable choice in `options`.
46
+ Anchor a document question with `anchor: { artifact, quote, occurrence? }`; `occurrence` is
47
+ zero-based and selects a repeated quote. An anchor whose quote disappears becomes orphaned but
48
+ remains readable against its original document version. An ask stays open until a human answers;
49
+ you cannot withdraw it. Ask once and well. The host steers you when a human answers.
50
+ ## The spec is where narrative goes
51
+
52
+ Read the current document before changing it:
53
+
54
+ ```ts
55
+ dispatch_doc_read({ issue?, artifact?, version?, ref? })
56
+ ```
57
+ It returns live or versioned markdown with open marks and `details` `{ issue }`; omit `artifact`
58
+ with `issue` to read the primary document. Then write narrative with:
116
59
 
117
- - `subject`: one line, the decision — this is the issue title and the dashboard row.
118
- - `context` (required, ≤ 1200 chars): what you are doing, what you found, why you are stuck.
119
- - `question` (required, ≤ 800 chars): current → desired → recommendation (options live in
120
- `ask`).
121
- - `ask` (optional): a list of `{ question, header?, options: [{ label, description? }],
122
- multiple?, custom? }`. These render as buttons, always with an "Other (specify)"
123
- free-text field beside them (`custom` does not change this); a plain reply on the thread
124
- is also possible but arrives unstructured.
125
- - `urgency` (optional, default `med`): `low` — whenever convenient; `med` — needed today;
126
- `high` — something will stall soon; `blocking` — you cannot proceed at all right now.
127
- - `repo` (optional): only to target a **different** repo than your working directory's
128
- GitHub remote; the tool fills it from the cwd otherwise.
129
- - `parent` (optional): only when a real issue exists to attach to — `<n>` in the resolved
130
- repo or `owner/name#<n>` elsewhere; `owner/name#<n>#<commentId>` also appends a
131
- breadcrumb to that comment. For Legion roles, `parent` is the root issue you're working
132
- under. Omitting it creates a standalone thread; don't invent a parent.
133
-
134
- The tool returns `{"thread": N, "url": "..."}`; that URL is the durable record.
135
-
136
- ### Continuing a thread
137
-
138
- When the reply changes the question — the human challenged the premise, ruled out your
139
- recommendation, or asked for a variant you had not offered — re-ask **on the same thread**:
140
-
141
- ```
142
- dispatch({
143
- thread: "482", // or "owner/name#482"
144
- context: "- You ruled out the maintenance window: the 03:00 UTC slot is now used by
145
- the nightly export.
146
- - CONCURRENTLY needs the migration runner to run outside a transaction.",
147
- question: "- Current: the runner wraps every migration in a transaction.
148
- - Desired: this one index built online.
149
- - Recommendation: a one-off flag on the runner for non-transactional
150
- migrations, used only by this migration.",
151
- ask: [{ header: "Runner change", question: "Add the non-transactional flag?",
152
- options: [{ label: "Yes, one-off flag" }, { label: "No, hand-run the index" }] }]
153
- })
60
+ ```ts
61
+ dispatch_doc_edit({ issue, artifact, ops, summary? })
154
62
  ```
63
+ It returns `details` `{ issue, topic, applied, version? }`. `ops` is an array of this exact
64
+ `EditOp` shape:
65
+
66
+ ```ts
67
+ type EditOp = {
68
+ op: "replace" | "delete" | "insert";
69
+ find?: string;
70
+ with?: string;
71
+ occurrence?: number;
72
+ markdown?: string;
73
+ after?: string;
74
+ before?: string;
75
+ };
76
+ ```
77
+
78
+ Target edits by quote: `replace` requires `find` and `with`; `delete` requires `find`; `insert`
79
+ requires `markdown` and exactly one of `after` or `before`. An insert anchor is a quote,
80
+ `"start"`, `"end"`, or `"heading:Title"`. Use zero-based `occurrence` for a repeated `find`.
81
+ When a decision lands, pass `summary` to name the resulting version. Never paste progress into a
82
+ message.
83
+
84
+ ## Comments and suggestions
85
+
86
+ Add feedback with:
87
+
88
+ ```ts
89
+ dispatch_comment({ issue, artifact?, quote?, occurrence?, body, reply_to? })
90
+ ```
91
+
92
+ It returns `details` `{ issue, topic, comment }`. `quote` requires `artifact`; omit both for a
93
+ floating issue comment. Use `reply_to` to continue a comment.
155
94
 
156
- - `thread` replaces `subject`; `urgency`, `repo`, and `parent` are not accepted with it.
157
- `thread` names an open issue in the repo; a plain work issue is adopted as a thread on
158
- first use. The issue must be open and not a pull request, or the tool says so
159
- (`#N is not a dispatch thread`, `#N is closed; open a new thread`).
160
- - Same decision → same thread. A genuinely new decision → a new thread, even if it came
161
- up in the reply.
162
- - **A follow-up supersedes the thread's earlier unanswered questions.** The dashboard
163
- offers an answer only for the latest turn; earlier open questions read "superseded by a
164
- later follow-up". If an earlier question still stands, restate it in the follow-up's
165
- `ask` list. A follow-up with no `ask` list is one free-text question — the text under
166
- its `## Question` — and the human answers it in prose.
167
- - **Ask once.** An unanswered question is not a reason to post again. New facts that do
168
- not change the decision go in a plain `gh issue comment` on the thread, not a new
169
- `dispatch` call; a follow-up is for a changed question. A thread that fills with
170
- restated asks is noise for the human and hides the decision it is waiting on.
171
- - **Read a challenge as an answer.** "Why not the bot?" is not a request for more prose;
172
- it is the human declining your framing. Say what you now know, then ask the narrowed
173
- question with new options — unless the challenge itself decides ("Why not the bot? Just
174
- use the bot."), in which case act on it and re-ask nothing. A follow-up carries your
175
- session's identity, so the human sees which conversation is asking even after a handoff.
176
- - The tool returns `{"thread": N, "url": "...", "comment": "..."}`.
95
+ Propose an exact replacement instead of describing it:
177
96
 
178
- ## After dispatching
97
+ ```ts
98
+ dispatch_suggest({ issue, artifact, quote, replace_with, body?, occurrence? })
99
+ ```
100
+
101
+ It returns `details` `{ issue, topic, comment }`. A human accepts or rejects a suggestion. On
102
+ `TARGET_AMBIGUOUS`, add zero-based `occurrence`. On `TARGET_NOT_FOUND`, re-read the document
103
+ before retrying. `INVALID_OP` names a malformed edit; `CAP_EXCEEDED` never truncates;
104
+ `ISSUE_CLOSED` rejects a write. `ACTOR_KIND`, `ROUTE_INVALID`, and `PRIMARY_NOT_DOC` reject an
105
+ invalid actor, route, or primary artifact.
179
106
 
180
- The reply arrives back in your own session as a steer — you do not fetch it. Keep every
181
- other non-blocked lane of work moving while you wait; a dispatch is not a reason to go
182
- idle. Never poll GitHub for the reply and never set up a watcher, cron, or retry loop to
183
- check on it — the delivery mechanism is already push-based. Each answer names the question
184
- it answers, so a thread with two open questions delivers two steers.
107
+ ## Artifacts
185
108
 
186
- If the tool errors because it can't resolve a repo (cwd has no GitHub remote and no
187
- qualified `parent` or `thread`), pass `repo=owner/name` (opening) or
188
- `thread=owner/name#<n>` (continuing) and retry.
109
+ Attach an image, diagram, or file with:
189
110
 
190
- **Named risk:** a thread created in a repo where the Envoy GitHub App is not installed
191
- gets created successfully, but replies to it never route back to your session — you'll
192
- wait forever for a steer that can't arrive. The App is installed per account and per repo,
193
- not org-wide, and coverage changes; before relying on a reply from a repo you have not
194
- dispatched to before, check it: `gh api /repos/<owner>/<name>/installation` returns 200 when
195
- the App is installed there and 404 when it is not.
111
+ ```ts
112
+ dispatch_artifact({ issue, name, path, primary?, summary? })
113
+ ```
196
114
 
197
- ## Manual fallback: the marker format
115
+ It returns `details` `{ issue, topic, artifact, version }`. `path` is the local file to upload.
116
+ Uploading the same `name` creates its next version. Set `primary: true` only for a markdown file
117
+ to make it the issue's spec.
198
118
 
199
- If the tool itself is unavailable, the dashboard still understands a hand-written turn.
200
- Every dispatch marker is an HTML comment at the very start of the body — invisible on
201
- GitHub — followed by a blank line and the readable text.
119
+ ## Messages
202
120
 
203
- A follow-up question, posted with `gh issue comment <n> --body-file <file>`:
121
+ Use the escape valve only for a note that fits nowhere else:
204
122
 
123
+ ```ts
124
+ dispatch_message({ issue, body })
205
125
  ```
206
- <!-- dispatch:ask
207
- requestId: <16 lowercase hex chars, unique to this follow-up>
208
- ask:
209
- - askId: <the same 16 chars>
210
- question: <the question>
211
- header: <short header>
212
- options:
213
- - label: <label>
214
- description: <one line>
215
- -->
216
126
 
217
- ## Context
127
+ It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 characters. Your
128
+ message does not wake anyone. Do not use it for status, a decision, or document feedback.
218
129
 
219
- <context>
130
+ ## What comes back
220
131
 
221
- ## Question
132
+ A write result's `details.topic` subscribes the host to the issue. Events render as:
222
133
 
223
- <question>
134
+ ```text
135
+ dispatch <KEY> · <type> · by <actor>
224
136
  ```
225
137
 
226
- A new thread, posted with `gh issue create --label dispatch-thread --title "<subject>" --body-file <file>`:
138
+ Human-authored events reach you, except an unnamed artifact version; child-status events also
139
+ reach you. Session-authored events never wake sessions. After a restart, catch up with:
140
+
141
+ ```ts
142
+ dispatch_read({ issue?, ref? })
143
+ ```
144
+
145
+ With an issue ref, it returns the issue summary, open asks, and recent events with `details`
146
+ `{ issue }`. With an ask ref, it returns that ask's question, options, state, and answer. With a
147
+ comment ref, it returns that comment and its quoted reply chain. Use `dispatch_doc_read` for
148
+ document contents.
149
+
150
+ ## References
227
151
 
152
+ Use these in document, ask, comment, and message bodies; Dispatch unfurls them:
153
+
154
+ ```text
155
+ dispatch://KEY
156
+ dispatch://KEY/spec
157
+ dispatch://KEY/artifact/<slug>[@vN]
158
+ dispatch://KEY/ask/<id>
159
+ dispatch://KEY/comment/<id>
228
160
  ```
229
- <!-- dispatch:thread
230
- requestId: <16 lowercase hex chars>
231
- urgency: med
232
- ask:
233
- - askId: <the same 16 chars>
234
- question: <the question>
235
- options:
236
- - label: <label>
237
- -->
238
161
 
239
- **<subject>**
162
+ ## Before / after
240
163
 
241
- ## Context
164
+ Before — a wall of text hides the decision and makes the choices unclickable:
242
165
 
243
- <context>
166
+ ```text
167
+ We need to settle the release gate because the deploy branch has the migration and the
168
+ dashboard changes, I checked the staging result and it is fine except the release notes are
169
+ not reviewed, so should we ship today, wait for docs, or cut the dashboard from this release?
170
+ I think waiting is safest but the customer demo is tomorrow and the list above is probably stale.
171
+ ```
244
172
 
245
- ## Question
173
+ After — anchor the decision and make each option a button:
174
+
175
+ ```ts
176
+ dispatch_ask({
177
+ issue: "LEGION-815",
178
+ question:
179
+ "Choose the release gate. Recommendation: ship after release-note review because the tested deployment is otherwise ready.",
180
+ options: [
181
+ {
182
+ label: "Review notes, then ship",
183
+ description: "Keeps the tested release intact and publishes reviewed instructions.",
184
+ },
185
+ {
186
+ label: "Ship now",
187
+ description: "Meets the demo deadline; release notes follow separately.",
188
+ },
189
+ {
190
+ label: "Remove dashboard changes",
191
+ description: "Narrows the release but requires another deployment test.",
192
+ },
193
+ ],
194
+ custom: false,
195
+ urgency: "high",
196
+ anchor: {
197
+ artifact: "spec",
198
+ quote: "Release requires reviewed operator instructions before deployment.",
199
+ },
200
+ })
201
+ ```
246
202
 
247
- <question>
203
+ Before — a status message loses the durable outcome:
204
+
205
+ ```text
206
+ Done, PR merged.
248
207
  ```
249
208
 
250
- `ask` may be omitted when there are no buttons to offer. A second ask on the same turn has
251
- `askId: <requestId>.1`, a third `.2`. A hand-posted turn carries no `origin`, so the
252
- dashboard cannot say which session asked, and nothing subscribes your session to a
253
- hand-created thread — subscribe it yourself with `envoy_subscribe` to
254
- `notifications.github.<owner>.<name>.issue.<n>.>` or the reply never arrives. Prefer the
255
- tool.
209
+ After record the result in the spec and name its version:
210
+
211
+ ```ts
212
+ dispatch_doc_edit({
213
+ issue: "LEGION-815",
214
+ artifact: "spec",
215
+ ops: [
216
+ {
217
+ op: "replace",
218
+ find: "## Delivery\n\nRelease pending.",
219
+ with: "## Delivery\n\nRelease merged and ready for deployment.",
220
+ },
221
+ ],
222
+ summary: "Recorded merged release",
223
+ })
224
+ ```
@@ -14,12 +14,22 @@ separate coordinator to finish necessary work.
14
14
 
15
15
  - Use the `legion` tool for lifecycle writes. Its issue key format is
16
16
  `owner/repo#number`.
17
- - Use `task` for every Legion role spawn and `hub` to direct or revive a known phase
18
- worker. Phase workers escalate lifecycle, scope, and cross-phase matters inward to you
19
- through hub. Any role may use `dispatch` directly for a standalone human question;
20
- replies return to the asking session.
21
- - The runtime, not you, appends a machine `<legion-spawn>` block. Each Legion `task`
22
- text must start with `Legion-Issue: <owner/repo#n>` on its first line.
17
+ - Use `legion({ op: "spawn_worker", issue, role, task })` for every Legion role spawn.
18
+ Message a known phase worker with `envoy_publish` to `notifications.role.` followed by
19
+ its encoded role token (the token `spawn_worker` returned for it); re-assign it by
20
+ calling `spawn_worker` again on the same existing role, which resumes the same process
21
+ instead of starting a fresh one. Phase workers escalate lifecycle, scope, and
22
+ cross-phase matters the same way: `envoy_publish` to your own encoded token. Any role
23
+ may use `dispatch` directly for a standalone human question; replies return to the
24
+ asking session.
25
+ - The daemon spawns each role as its own process with the issue's context already in its
26
+ environment. Never hand-format a role token: the daemon encodes one as
27
+ `legion-<project>-<encoded-owner>__<encoded-repo>-<number>-<role>` (escaping `_`, `.`,
28
+ and `-` within the owner/repo names); for example, project `acme`, issue
29
+ `sjawhar/legion#41`, role `architect` encodes to `legion-acme-sjawhar__legion-41-architect`.
30
+ Reuse a token you already hold (your own, or one `spawn_worker` returned) or compute
31
+ another with the `roleToken` helper from `@legion/contracts` exactly the way the daemon
32
+ does.
23
33
  - Use only the live label vocabulary: `needs-approval`, `human-approved`,
24
34
  `legion-child`, and `legion-backlog`. Do not attempt to apply a label whose ownership
25
35
  belongs to the controller or Sami.
@@ -30,12 +40,16 @@ separate coordinator to finish necessary work.
30
40
  ## 1. Decompose or adopt
31
41
 
32
42
  Inspect the root issue, acceptance criteria, existing children, and current handoffs.
43
+ Decomposition is complete only when every child issue names the real surface its acceptance
44
+ criteria are proven on and the repository skill that drives it; if the repository cannot
45
+ exercise a criterion end to end, building that path is a child issue of this tree.
33
46
 
34
47
  - **Existing children:** adopt them. Do not replace or re-decompose human-created work.
35
48
  Put every adopted child into the initial wave. **You MUST call**
36
49
  `legion({ op: "wave_release", children: ["owner/repo#41", "owner/repo#42"] })`
37
- **before any `task` spawn for an adopted child.** Until release, the daemon holds that
38
- child's role activity. Then spawn each child's in-process `legion-architect` owner.
50
+ **before any `spawn_worker` call for an adopted child.** Until release, the daemon
51
+ holds that child's role activity. Then spawn each child's daemon-managed sub-architect
52
+ owner.
39
53
  - **No children:** choose a single-issue tree only when its acceptance criteria can be
40
54
  completed and integrated as one unit. Otherwise create complete child issues with:
41
55
 
@@ -81,32 +95,37 @@ explicit lifecycle write:
81
95
  legion({ op: "wave_release", children: ["owner/repo#41", "owner/repo#42"] })
82
96
  ```
83
97
 
84
- After release, spawn each relevant owner with an issue-prefixed task; for example:
98
+ After release, spawn each relevant owner; for example:
85
99
 
86
100
  ```text
87
- task({
88
- agent: "legion-architect",
89
- task: "Legion-Issue: owner/repo#41\nOwn this child through its lifecycle and report its evidence."
90
- })
101
+ legion({
102
+ op: "spawn_worker",
103
+ issue: "owner/repo#41",
104
+ role: "architect",
105
+ task: "Own this child through its lifecycle and report its evidence."
106
+ })
91
107
  ```
92
108
 
93
- Do not add a `<legion-spawn>` block. Keep the child agent IDs and session identifiers
94
- returned by `task`, because retro and adjustment use those live sessions. Park while
95
- children are in flight. On each child closure, re-scope open work, close obsolete work
96
- with a reason, and release the next wave only when it now makes sense. There is no
97
- inter-child dependency mechanism to encode.
109
+ The daemon spawns that sub-architect as its own process with the child's context already
110
+ in its environment; a resume of an existing role continues the same process instead of
111
+ starting a fresh one. Keep the returned session identifiers, because retro and adjustment
112
+ use those live sessions. Park while children are in flight. On each child closure,
113
+ re-scope open work, close obsolete work with a reason, and release the next wave only
114
+ when it now makes sense. There is no inter-child dependency mechanism to encode.
98
115
 
99
116
  ## 3. Children complete
100
117
 
101
118
  Treat `children-complete` as the edge into the end-game, not as a reason to close the
102
- parent. Launch one **fresh** `legion-tester` for the parent, scoped to the parent's own
103
- acceptance criteria and current `main` integration surface:
119
+ parent. Spawn the parent's `tester` role, scoped to the parent's own acceptance criteria
120
+ and current `main` integration surface:
104
121
 
105
122
  ```text
106
- task({
107
- agent: "legion-tester",
108
- task: "Legion-Issue: owner/repo#40\nFreshly verify this parent issue against its acceptance criteria on current main; return reproducible integration evidence."
109
- })
123
+ legion({
124
+ op: "spawn_worker",
125
+ issue: "owner/repo#40",
126
+ role: "tester",
127
+ task: "Verify this parent issue against its acceptance criteria on current main; return reproducible integration evidence."
128
+ })
110
129
  ```
111
130
 
112
131
  If that tester finds a failure, create and release a new corrective child wave, then
@@ -115,25 +134,25 @@ failure forward.
115
134
 
116
135
  ## 4. Integration verification
117
136
 
118
- Read the fresh tester's evidence, not merely a child PR's check status. The parent test
137
+ Read the tester's evidence, not merely a child PR's check status. The parent test
119
138
  is successful only when every parent acceptance criterion has evidence against current
120
139
  main. Route a failed criterion into a corrective child wave; route a passing result to
121
140
  review and the merge-gate sequence.
122
141
 
123
142
  ## 5. Retro
124
143
 
125
- Retro is mandatory for every issue that passed review, before merge. Revive the parked
126
- implementer that owns the reviewed work through `hub`, naming the skill in the message:
144
+ Retro is mandatory for every issue that passed review, before merge. Message the
145
+ implementer's live session (idle since it completed its phase; the daemon never tears
146
+ it down) with `envoy_publish` to its role token, naming the skill:
127
147
 
128
148
  ```text
129
- hub({
130
- op: "send",
131
- to: "<implementer agent identifier>",
149
+ envoy_publish({
150
+ topic: "notifications.role.<implementer's encoded token>",
132
151
  message: "Run the legion-retro skill now. Capture durable learnings and post the issue comment; do not create a .legion handoff file."
133
152
  })
134
153
  ```
135
154
 
136
- Wait for the revived implementer to report its durable retro result. Retro output is
155
+ Wait for the messaged implementer to report its durable retro result. Retro output is
137
156
  `docs/solutions/` plus an issue comment; it must not create a `.legion` file or change
138
157
  the reviewer-approved head after cleanup.
139
158
 
@@ -154,10 +173,11 @@ When the config-armed final merge gate applies, preserve this order exactly:
154
173
  approval already satisfies the gate and you immediately continue to the merger; do not
155
174
  wait for a new wake. If it returns `approved: false`, request or retain Sami approval
156
175
  and park for a later `pr-ready` wake. Do not poll or retry this check;
157
- 5. `legion-merger` verifies the approved head and squash-merges without pushing.
176
+ 5. The merger verifies the approved head and publishes `READY #<n> at <sha>` to
177
+ `notifications.role.pr-queue`; it never merges. The merge queue approves and merges under its own authority.
158
178
 
159
- If anything changes the approved head, return to review; do not ask the merger to merge
160
- an obsolete approval.
179
+ If anything changes the approved head, return to review; do not let the merger publish
180
+ `READY` for an obsolete approval.
161
181
 
162
182
  ## 7. Close
163
183
 
@@ -183,14 +203,17 @@ corresponding lifecycle procedure.
183
203
  | Wake | Procedure |
184
204
  | --- | --- |
185
205
  | `child-closed` | Read the child completion and remaining open children. Re-scope or close obsolete open work; release an appropriate next wave, or await `children-complete`. |
186
- | `children-complete` | Execute steps 3–4: fresh parent integration verification; failures become a new child wave, success advances to review and retro. |
206
+ | `children-complete` | Execute steps 3–4: parent integration verification; failures become a new child wave, success advances to review and retro. |
187
207
  | `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. |
208
+ | `phase-complete` | Payload `{type:"phase-complete", issue, role, summary}`. May arrive live or via `catchup-overseer`'s `phaseCompletions`. Read the committed handoff for that phase, then spawn the next phase's owner, or `spawn_worker` on the same role again to resume it with corrections if the handoff shows unresolved gaps. |
209
+ | `worker-queued` | Payload `{type:"worker-queued", issue, role}`. The deployment's worker cap is full; this role's spawn is queued. Do not respawn or retry — wait for `worker-started`. |
210
+ | `worker-started` | Payload `{type:"worker-started", issue, role}`. A previously queued role has been promoted and is now running. Treat it exactly as a normal spawn: resume tracking that role's live session. |
188
211
  | `pr-ready` | Verify the live PR head, green status, and review state. Continue the review/retro/Sami/merger order only for that current head. |
189
212
  | `pr-blocked` | Read the failed CI evidence and recovery attempts. Assign a focused implementer or corrective child, then return it through testing and review; do not treat the blocked PR as final. |
190
213
  | `pr-closed-unmerged` | Decide from current scope whether to reopen the work, send a fresh implementer, or cancel it with a reason. Delegate the repository action to the responsible phase worker and keep ownership. |
191
- | `issue-comment` | Interpret the comment in the issue's design context. Answer it, adjust the plan, or relay it through `hub` to the responsible worker; scope and product decisions remain with you. |
192
- | `catchup-overseer` | Verify its gates, child counts, and PR verdicts against current artifacts, then resume the applicable numbered lifecycle step. It is a current-state snapshot, not a raw-event replay. |
193
- | `revive-worker` | The extension has revived the backed worker. Do not create a duplicate; direct the restored worker through `hub` if action is needed and rely on its committed handoff over recollection. |
214
+ | `issue-comment` | Interpret the comment in the issue's design context. Answer it, adjust the plan, or relay it via `envoy_publish` to the responsible worker's role token; scope and product decisions remain with you. |
215
+ | `catchup-overseer` | Verify its gates, child counts, and PR verdicts against current artifacts, then resume the applicable numbered lifecycle step. It is a current-state snapshot, not a raw-event replay. For each entry in its `phaseCompletions` (`{issue, role, summary, at}`, phases that completed while you were not live), handle it exactly as a `phase-complete` wake. |
216
+ | `revive-worker` | The extension has revived the backed worker. Do not create a duplicate; message the restored worker via `envoy_publish` to its role token if action is needed and rely on its committed handoff over recollection. |
194
217
  | `reopened` | Reopen the root lifecycle: inspect the reason and current artifacts, reassess scope and children, and resume at the first applicable numbered step. |
195
218
 
196
219
  ## Escalation judgment
@@ -19,7 +19,7 @@ retrospective's durable output.
19
19
  3. Run this retro: commit durable learnings to `docs/solutions/` and post the issue comment.
20
20
  Retro writes **no `.legion` file**, so it never re-dirties the cleaned handoff tree.
21
21
  4. Sami approves the final reviewed head.
22
- 5. The merger squash-merges and pushes nothing.
22
+ 5. The merger verifies the approved head, publishes `READY`, and pushes nothing; the merge queue merges.
23
23
 
24
24
  Do not start retro before step 2, skip it because the change seems mechanical, or merge before
25
25
  steps 3 and 4. The design gate is not a substitute for this final merge gate.