@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/README.md +16 -9
- package/dist/src/server.js +876 -331
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +189 -220
- package/skills/legion-architect/SKILL.md +61 -38
- package/skills/legion-retro/SKILL.md +1 -1
- package/skills/legion-worker/SKILL.md +9 -8
- package/src/server.ts +74 -93
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -1,255 +1,224 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dispatch
|
|
3
|
-
description: Use when
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130
|
+
## What comes back
|
|
220
131
|
|
|
221
|
-
|
|
132
|
+
A write result's `details.topic` subscribes the host to the issue. Events render as:
|
|
222
133
|
|
|
223
|
-
|
|
134
|
+
```text
|
|
135
|
+
dispatch <KEY> · <type> · by <actor>
|
|
224
136
|
```
|
|
225
137
|
|
|
226
|
-
|
|
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
|
-
|
|
162
|
+
## Before / after
|
|
240
163
|
|
|
241
|
-
|
|
164
|
+
Before — a wall of text hides the decision and makes the choices unclickable:
|
|
242
165
|
|
|
243
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
+
Before — a status message loses the durable outcome:
|
|
204
|
+
|
|
205
|
+
```text
|
|
206
|
+
Done, PR merged.
|
|
248
207
|
```
|
|
249
208
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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 `
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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 `
|
|
38
|
-
child's role activity. Then spawn each child's
|
|
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
|
|
98
|
+
After release, spawn each relevant owner; for example:
|
|
85
99
|
|
|
86
100
|
```text
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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.
|
|
103
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
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.
|
|
126
|
-
implementer
|
|
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
|
-
|
|
130
|
-
|
|
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
|
|
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.
|
|
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
|
|
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:
|
|
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
|
|
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;
|
|
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
|
|
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.
|