@sjawhar/opencode-legion-envoy 5.1.0 → 5.2.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/dist/src/server.js +5 -4
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +30 -30
- package/skills/dispatch/references/documents.md +9 -0
- package/skills/dispatch/references/examples.md +23 -17
- package/skills/legion-architect/SKILL.md +52 -32
- package/skills/legion-controller/SKILL.md +3 -3
- package/skills/legion-worker/SKILL.md +22 -15
package/dist/src/server.js
CHANGED
|
@@ -13896,6 +13896,7 @@ var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n referen
|
|
|
13896
13896
|
var OWNER_REFERENCE = "Exactly one of issue and project is required. An issue is a native KEY or external owner/repo#n reference; a project is a project key such as CORE and addresses an unlinked project document named by artifact.";
|
|
13897
13897
|
var ASK_QUESTION_CONTRACT = "The question carries the problem the reader recognises and why it matters now, what constrains " + "the answer, and the recommendation with its reason. It asks how to solve the problem or which " + "outcome is wanted; never enumerate choices in the question.";
|
|
13898
13898
|
var ASK_OPTIONS_CONTRACT = "Options carry the genuinely different approaches. Each option has a label, and its description " + "says what that approach costs.";
|
|
13899
|
+
var HUMAN_AGREED_TO_DOCUMENT = "the human has agreed to every point in the document";
|
|
13899
13900
|
function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false) {
|
|
13900
13901
|
return {
|
|
13901
13902
|
check: (value) => {
|
|
@@ -14057,7 +14058,7 @@ var dispatchToolSpecs = [
|
|
|
14057
14058
|
}
|
|
14058
14059
|
]
|
|
14059
14060
|
},
|
|
14060
|
-
description: "Open a
|
|
14061
|
+
description: "Open a to-do or permission only a human can give, or a decision that has no document to " + "live in. A question about the design an issue's document records is not this tool: write " + "it into that document as a decision block (dispatch_doc_edit inserting an ask block at the " + "end of the section it concerns), at every phase, approved spec or not; the block reaches " + "the Inbox and its answer lands next to its context. Never give an ask an Approve option: a " + "document is approved through dispatch_request_approval. Do not use this tool for a status " + "update or discussion; use dispatch_message instead. " + ASK_QUESTION_CONTRACT + " " + ASK_OPTIONS_CONTRACT + " For an action only a human can perform, state what it changes and risks as constraints. " + "Anything that requires a human to do, including a credential or grant renewal, is an ask, " + "never a message. " + "Anchor a to-do about a document passage, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". ` + `A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} characters ` + `and has at most 8 options. ${OWNER_REFERENCE}`,
|
|
14061
14062
|
arguments: (z2) => ({
|
|
14062
14063
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14063
14064
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -14188,7 +14189,7 @@ var dispatchToolSpecs = [
|
|
|
14188
14189
|
{
|
|
14189
14190
|
name: "dispatch_message",
|
|
14190
14191
|
example: { issue: "DSP-1", body: "Implementation started." },
|
|
14191
|
-
description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A
|
|
14192
|
+
description: "Post a note humans must read now: a reply to a human's message or a deliverable that landed. A to-do only a human can " + "complete is an ask (dispatch_ask), so it reaches their inbox. Never progress or status updates - Dispatch is a high-signal " + "record, not a log. Not a design decision (write it as a decision block in the document) or document feedback (dispatch_comment). " + "To answer a human's direct message to this session - one sent from the Agents page, which names no issue - pass that message's bare id as " + "in_reply_to and no issue; the reply lands in that conversation. Another call with the same in_reply_to and new text posts a follow-up, " + "threaded under this session's first reply; the same text again posts nothing. dispatch_read({message}) reads " + "that conversation back. Every other message names its issue. " + `Body is at most 2,000 characters. ${ISSUE_REFERENCE}`,
|
|
14192
14193
|
arguments: (z2) => ({
|
|
14193
14194
|
issue: z2.string().describe(`${ISSUE_REFERENCE} Omit it only when in_reply_to answers a human's direct message to this session.`).optional(),
|
|
14194
14195
|
body: z2.string({ max: 2000 }).describe("Update text, at most 2,000 characters."),
|
|
@@ -14250,12 +14251,12 @@ var dispatchToolSpecs = [
|
|
|
14250
14251
|
{
|
|
14251
14252
|
name: "dispatch_request_approval",
|
|
14252
14253
|
example: { issue: "DSP-1", summary: "A live sync replaces the nightly export." },
|
|
14253
|
-
description: "Ask a human to approve a document at its current version. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested.
|
|
14254
|
+
description: "Ask a human to approve a document at its current version. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested. An open request follows the document: a " + "later version moves it to that version and leaves it waiting on you, as a human's reply in " + "its thread does. Only the first move since the request was opened or handed back sends an " + "event, and never to the session whose version made it; dispatch_doc_read shows whom it " + "waits on. Once the revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ", call this again to hand that same Inbox row back. The request carries nothing new. A " + "call while it already waits on the human hands nothing back: the same summary changes " + "nothing, and a different one is refused, since it would rewrite the card the human is " + "reading. Approve and Request changes each close the request, so the next call opens a new " + "one. Call it once per revision, when the revision is complete, never after each edit. An " + "approval goes stale when the document changes after it: request approval again once that " + "revision is complete and " + HUMAN_AGREED_TO_DOCUMENT + ". A new version of a Legion root spec closes its armed design gate until a human approves " + "it. " + "Refused, with nothing sent, while the document holds an open decision block, even when a " + "human asked for approval: the refusal names each block; ask the human to answer or waive " + "it first. " + OWNER_REFERENCE,
|
|
14254
14255
|
arguments: (z2) => ({
|
|
14255
14256
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14256
14257
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
14257
14258
|
artifact: z2.string().describe("Project document artifact id, slug, or filename; primary document by default for an issue.").optional(),
|
|
14258
|
-
summary: z2.string({ min: 1 }).describe(
|
|
14259
|
+
summary: z2.string({ min: 1 }).describe(`What the human is approving, in one to three sentences, and nothing else: no commentary on itself or the conversation, and no question. Request approval only once ${HUMAN_AGREED_TO_DOCUMENT}.`)
|
|
14259
14260
|
}),
|
|
14260
14261
|
validation: documentOwnerValidation(true)
|
|
14261
14262
|
},
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -127,14 +127,8 @@ that points at the spec.
|
|
|
127
127
|
|
|
128
128
|
The block is what reaches the human's Inbox. A question phrased as prose in the spec reaches
|
|
129
129
|
nobody. A spec with no ask blocks is fine only when the issue genuinely needs no human decision.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
it answers `… typed-block openings in this document are text, not blocks`, the quoted openings are
|
|
133
|
-
blocks stored as prose (inside a line, or a paste with something before every line): fix the markdown
|
|
134
|
-
and upload again; a mention on purpose belongs in code. Neither answer sees a spec wrapped whole in a
|
|
135
|
-
code fence (take the fence off), a malformed opening inside a line (`::ask{`, `:::ask {`: an ask opens
|
|
136
|
-
only as `:::ask{…}` on a line of its own), or any `dispatch_doc_edit`: after an edit that writes an
|
|
137
|
-
ask, `dispatch_doc_read` the section and check it renders as `:::ask{#<id> …}` on its own line.
|
|
130
|
+
What `dispatch_issue` and `dispatch_artifact` answer about a spec's blocks, and how to check an edit
|
|
131
|
+
wrote one, is in [Typed blocks](skill://dispatch/references/documents.md#typed-blocks).
|
|
138
132
|
|
|
139
133
|
**Wrong:** a **Decisions needed** list at the top of the spec with three bullets.
|
|
140
134
|
**Right:** each decision an `:::ask{#slug}` block at the end of the design section that discusses
|
|
@@ -223,6 +217,13 @@ field, and choosing your next issue are in [Working an issue](skill://dispatch/r
|
|
|
223
217
|
|
|
224
218
|
## Asking
|
|
225
219
|
|
|
220
|
+
Two kinds of question reach a human. A decision about the design a document records is a decision
|
|
221
|
+
block in that document ([Decision blocks](#decision-blocks)), whatever phase the work is in and
|
|
222
|
+
whether or not the document was approved: a block in an approved document makes the approval
|
|
223
|
+
stale, which is right. A Legion phase worker sends such a decision to its architect, which writes
|
|
224
|
+
the block (`skill://legion-worker`). A to-do or permission only a human can give, or a decision
|
|
225
|
+
with no document to live in, is a `dispatch_ask`. The gates below apply to both.
|
|
226
|
+
|
|
226
227
|
### Before you ask
|
|
227
228
|
|
|
228
229
|
Every `dispatch_ask` passes four gates first:
|
|
@@ -266,14 +267,14 @@ Every `dispatch_ask` passes four gates first:
|
|
|
266
267
|
is not what this forbids; changing something else is. An ask that turns his complaint about
|
|
267
268
|
one control into a choice about another does not address what he asked, and changing that
|
|
268
269
|
other control is a change he never asked for. What is still an ask the moment you know it,
|
|
269
|
-
even before delivery, is anything "Anything you are blocked on a human for is
|
|
270
|
-
(further down) lists that the delivery waits on — including a conflict between what
|
|
271
|
-
for and another of his rules, which this gate would otherwise bury as settled.
|
|
270
|
+
even before delivery, is anything "Anything you are blocked on a human for is visible in
|
|
271
|
+
Dispatch" (further down) lists that the delivery waits on — including a conflict between what
|
|
272
|
+
he asked for and another of his rules, which this gate would otherwise bury as settled.
|
|
272
273
|
|
|
273
274
|
Nobody audits or retracts another session's asks: passing every gate, and carrying the content
|
|
274
275
|
instead of pointing at another message in prose (below), is the asking session's own check.
|
|
275
276
|
|
|
276
|
-
Open a
|
|
277
|
+
Open a standalone ask with:
|
|
277
278
|
```ts
|
|
278
279
|
dispatch_ask({
|
|
279
280
|
issue?,
|
|
@@ -293,15 +294,14 @@ References belong in the question text; `ref` is sugar that appends its `dispatc
|
|
|
293
294
|
|
|
294
295
|
An ask is read on a phone by someone who has not read the code. Write its question and options as
|
|
295
296
|
[Writing for the human](#writing-for-the-human) says, and apply its phone test before posting.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
zero-based and selects a repeated quote. A quote anchor is pinned to its lowest complete
|
|
297
|
+
Never lead a question with a file path, line number, sequence number, document version or role token: its own text explains the problem.
|
|
298
|
+
Evidence below it may cite one where the reader checks it, or anchor the ask to the passage with `anchor: { artifact, quote, occurrence? }`; `occurrence` is zero-based and selects a repeated quote.
|
|
299
|
+
A quote anchor is pinned to its lowest complete
|
|
300
300
|
containing block while retaining its quote as display text, so rewording the passage keeps it
|
|
301
301
|
attached; a quote spanning top-level blocks, and existing anchors without a block, stay readable
|
|
302
302
|
against their original document version if their quote disappears.
|
|
303
303
|
|
|
304
|
-
An ask must be answerable from its own text and its anchor alone. Anchor a
|
|
304
|
+
An ask must be answerable from its own text and its anchor alone. Anchor a to-do about a document
|
|
305
305
|
passage with `anchor`. Follow up on an ask or comment with `dispatch_comment`; cite anything else
|
|
306
306
|
with a `dispatch://` reference (see [References](#references)). Never write "see above", "the
|
|
307
307
|
message above", or "as attached".
|
|
@@ -329,19 +329,19 @@ they must read to decide belongs in the spec in the first place — see [Artifac
|
|
|
329
329
|
|
|
330
330
|
Before saying you are waiting for human input, call `dispatch_open_asks`. With no arguments it lists this session's active asks across open issues and project documents, including whether the human or agent owes the next reply. With `dispatch_open_asks({ project })` it lists every open ask in that project — on its issues and on its documents, whoever authored them — which is how you see what a whole project is waiting on rather than just your own asks.
|
|
331
331
|
|
|
332
|
-
**Unsettled product shape needs a decision before implementation.** When a page, navigation entry, table key, customer-scoping rule, or persisted sidecar would set product shape that Sami has not already settled,
|
|
332
|
+
**Unsettled product shape needs a decision before implementation.** When a page, navigation entry, table key, customer-scoping rule, or persisted sidecar would set product shape that Sami has not already settled, write a decision block in the document that records the work before the first implementation commit; in a Legion tree the architect writes it, and a phase worker sends the decision to its architect. A lane's schema decision or a contract two lanes agree does not settle product shape. This does not turn a user-specified decision or routine implementation into an approval request. A control or behaviour the human asked for in words is settled by those words, together with every choice inside it that his words do not make (where it sits, its defaults, its options): build it without a block, as gate 4 of [Before you ask](#before-you-ask) says. This rule covers only product shape outside what he asked for, and its block comes before the commit that sets that shape.
|
|
333
333
|
|
|
334
|
-
**Anything you are blocked on a human for is
|
|
335
|
-
an open ask.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
a
|
|
341
|
-
|
|
342
|
-
the step: a value already on the machine, a
|
|
343
|
-
click. One ask per item, `urgency: "high"`
|
|
344
|
-
working on everything that is not.
|
|
334
|
+
**Anything you are blocked on a human for is visible in Dispatch.** An agent waits on a human only
|
|
335
|
+
through an open ask. A to-do, permission, credential or grant renewal, setting only they can
|
|
336
|
+
change, review click, or conflict between two of their own rules is a `dispatch_ask` the moment you
|
|
337
|
+
know. Start with the problem and why it matters, then the constraints, options and their costs, and
|
|
338
|
+
your recommendation. For an action only the human can perform, state what it changes and risks;
|
|
339
|
+
never make the action itself the question (a design decision is a decision block, as above). Never
|
|
340
|
+
write a human to-do only into a spec, a comment reply, a message, or a pull-request body: nothing
|
|
341
|
+
in those paths reaches the human's Inbox, and a human who is not reading the document does not
|
|
342
|
+
know they are the blocker. Before asking, try to remove the step: a value already on the machine, a
|
|
343
|
+
permission you already hold, an API that replaces the click. One ask per item, `urgency: "high"`
|
|
344
|
+
when work is stopped on it; while it is open, keep working on everything that is not.
|
|
345
345
|
|
|
346
346
|
Once an ask is open (who answers it, handing a human a to-do, editing, retracting or resolving it,
|
|
347
347
|
answering a clarification, whose turn a reply gives), see
|
|
@@ -478,7 +478,7 @@ dispatch_message({ issue, body })
|
|
|
478
478
|
```
|
|
479
479
|
|
|
480
480
|
It returns `details` `{ issue, message }`. `body` is capped at 2,000 characters. A message is not a decision
|
|
481
|
-
(`dispatch_ask`) or document feedback (`dispatch_comment`), and it does not wake anyone unless the issue is routed.
|
|
481
|
+
(a decision block, or `dispatch_ask` for a human to-do) or document feedback (`dispatch_comment`), and it does not wake anyone unless the issue is routed.
|
|
482
482
|
|
|
483
483
|
A BTW, Aside or Steer frame, or a message from the Agents page, is answered as
|
|
484
484
|
[Targeted and direct messages](skill://dispatch/references/messages.md) says.
|
|
@@ -59,6 +59,15 @@ When a human answers a decision written as an ask block, the answer lives on tha
|
|
|
59
59
|
human's answer; never rewrite the question into its answer or blank its options. An edit that leaves
|
|
60
60
|
an ask block without a question or with a blank option is rejected with `INVALID_ASK_BLOCK`.
|
|
61
61
|
|
|
62
|
+
When `dispatch_issue` or `dispatch_artifact` answers `This spec holds no ask blocks …`, read it as a
|
|
63
|
+
question: either no decision is needed and you say nothing, or you forgot to make it a block. When
|
|
64
|
+
it answers `… typed-block openings in this document are text, not blocks`, the quoted openings are
|
|
65
|
+
blocks stored as prose (inside a line, or a paste with something before every line): fix the markdown
|
|
66
|
+
and upload again; a mention on purpose belongs in code. Neither answer sees a spec wrapped whole in a
|
|
67
|
+
code fence (take the fence off), a malformed opening inside a line (`::ask{`, `:::ask {`: an ask opens
|
|
68
|
+
only as `:::ask{…}` on a line of its own), or any `dispatch_doc_edit`: after an edit that writes an
|
|
69
|
+
ask, `dispatch_doc_read` the section and check it renders as `:::ask{#<id> …}` on its own line.
|
|
70
|
+
|
|
62
71
|
## Comments and suggestions
|
|
63
72
|
|
|
64
73
|
Add feedback with:
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# Before and after:
|
|
1
|
+
# Before and after: a decision block, a reply, a message, and a draft
|
|
2
2
|
|
|
3
|
-
`skill://dispatch` sends you here for worked examples: a decision
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
`skill://dispatch` sends you here for worked examples: a design decision in its document, a reply
|
|
4
|
+
that names its mechanism, a message that should not be sent, and a draft placed where the human
|
|
5
|
+
reads it.
|
|
6
6
|
|
|
7
7
|
## Before / after
|
|
8
8
|
|
|
9
|
-
Before — a wall of text hides the decision and
|
|
9
|
+
Before — a wall of text hides the decision and the human never receives it:
|
|
10
10
|
|
|
11
11
|
```text
|
|
12
12
|
The deploy branch has the migration and dashboard changes. Staging is fine, but release notes are
|
|
@@ -14,19 +14,23 @@ not reviewed before tomorrow's customer demo. Review the notes, ship without the
|
|
|
14
14
|
dashboard from the release. Waiting is safest, but the list above may be stale.
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
After — state the problem and make each
|
|
17
|
+
After — write the decision into the document section it concerns: state the problem, and make each
|
|
18
|
+
genuinely different option a button, so the answer stays with the release design:
|
|
18
19
|
|
|
19
20
|
```ts
|
|
20
|
-
|
|
21
|
+
dispatch_doc_edit({
|
|
21
22
|
issue: "LEGION-815",
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
artifact: "spec",
|
|
24
|
+
ops: [{
|
|
25
|
+
op: "insert",
|
|
26
|
+
after: "Release requires reviewed operator instructions before deployment.",
|
|
27
|
+
markdown: `:::ask{#release-gate urgency="high"}
|
|
28
|
+
Release notes are unreviewed, and tomorrow's customer demo means the release cannot wait for a later review. How should we proceed? Recommendation: review the notes, then ship, to keep the release complete and reviewed.
|
|
29
|
+
|
|
30
|
+
- Review notes, then ship: Delays release for review but keeps the release complete and reviewed.
|
|
31
|
+
- Ship now: Meets the demo deadline but leaves the release notes unreviewed.
|
|
32
|
+
:::`,
|
|
33
|
+
}],
|
|
30
34
|
})
|
|
31
35
|
```
|
|
32
36
|
|
|
@@ -65,8 +69,9 @@ dispatch_ask({
|
|
|
65
69
|
})
|
|
66
70
|
```
|
|
67
71
|
|
|
68
|
-
After — the draft is a section of the spec, and the ask anchors there.
|
|
69
|
-
|
|
72
|
+
After — the draft is a section of the spec, and the ask anchors there. Sending it is a to-do only a
|
|
73
|
+
human can complete, so it stays a standalone ask. If it really must be a file, the spec and the ask
|
|
74
|
+
both link the slug from the upload result:
|
|
70
75
|
|
|
71
76
|
```ts
|
|
72
77
|
dispatch_doc_edit({ issue: "OPS-52", artifact: "spec", ops: [
|
|
@@ -94,6 +99,7 @@ dispatch_ask({
|
|
|
94
99
|
{ label: "Send the reviewed update", description: "Delivers the linked update today but makes its text external." },
|
|
95
100
|
{ label: "Hold the update", description: "Avoids sending now but leaves customers without the update." },
|
|
96
101
|
],
|
|
102
|
+
ref: "dispatch://OPS-52/artifact/cu-update-2026-09-15-md",
|
|
97
103
|
})
|
|
98
104
|
```
|
|
99
105
|
|
|
@@ -22,10 +22,11 @@ separate coordinator to finish necessary work.
|
|
|
22
22
|
is the issue's active phase at a time, and calling `spawn_worker` for a different role
|
|
23
23
|
while a phase is active supersedes that phase: the superseded worker's
|
|
24
24
|
`handoff_complete` is then refused, so finish (or deliberately abandon) one role
|
|
25
|
-
before assigning the next. Phase workers escalate lifecycle, scope, and
|
|
26
|
-
cross-phase
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
before assigning the next. Phase workers escalate lifecycle, product, scope, design, and
|
|
26
|
+
cross-phase decisions the same way: `envoy_publish` to your own encoded token. You decide whether
|
|
27
|
+
one needs the human and write its decision block yourself (section 1 says what one does to an
|
|
28
|
+
approved root spec); a worker never writes one. Any role may use `dispatch_ask` directly for a
|
|
29
|
+
standalone to-do only a human can complete, and replies return to the asking session.
|
|
29
30
|
- The daemon spawns each role as its own process with the issue's context already in its
|
|
30
31
|
environment. Never hand-format a role token: the daemon encodes one as
|
|
31
32
|
`legion-<project>-<key>-<role>` with the issue key lower-cased; for example, project `acme`,
|
|
@@ -81,8 +82,8 @@ exercise a criterion end to end, building that path is a child issue of this tre
|
|
|
81
82
|
|
|
82
83
|
Specifications written into Dispatch follow `skill://dispatch`'s [Writing a spec](../dispatch/SKILL.md#writing-a-spec).
|
|
83
84
|
Wave releases, child closures, and your own status are visible from the issue tree and the
|
|
84
|
-
handoffs; do not narrate them into the spec or a `dispatch_message`. A
|
|
85
|
-
|
|
85
|
+
handoffs; do not narrate them into the spec or a `dispatch_message`. A to-do only Sami can clear
|
|
86
|
+
is a `dispatch_ask`.
|
|
86
87
|
|
|
87
88
|
The issue's primary document **is** the root specification. Extend it in place: a new version
|
|
88
89
|
that adds only the evidence each decision needs and what the human decides, each as a
|
|
@@ -145,14 +146,32 @@ yourself.
|
|
|
145
146
|
|
|
146
147
|
Then park. Do not release a wave or spawn a Legion role until a later delivered wake shows
|
|
147
148
|
`design-approved` on the root. On `design-changes-requested`, revise the spec (a new version of
|
|
148
|
-
the primary document), request approval again as above (the answer
|
|
149
|
-
this opens a new one), and stay parked.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
149
|
+
the primary document) as the human's reason asks, request approval again as above (the answer
|
|
150
|
+
closed the last request, so this opens a new one), and stay parked.
|
|
151
|
+
|
|
152
|
+
**After approval, the root spec changes only when what the tree delivers, or a decision a human
|
|
153
|
+
settled, changes.** Approval is pinned to the spec version: any new version of the root spec closes
|
|
154
|
+
the gate again with no wake (you made the edit, or the `artifact.version` event on your issue tells
|
|
155
|
+
you). So edit an approved root spec only when its Summary, its Acceptance, the tree's scope, or a
|
|
156
|
+
decision a human settled in one of its decision blocks changes. Such a change is a point the human
|
|
157
|
+
has not agreed to: put the problem behind it to them as its own decision block, with its evidence,
|
|
158
|
+
at the end of the section it changes, and request approval again as above once they have answered
|
|
159
|
+
it. Release no new wave and spawn no new role until the next `design-approved` arrives — work
|
|
160
|
+
already in flight continues. Every merger's `READY` in the tree is refused until a human approves
|
|
161
|
+
the latest version. A settled decision is the human's. A plan that would overturn one goes back to
|
|
162
|
+
the planner with the decision kept, which asks the human nothing, unless the planner brings
|
|
163
|
+
evidence the human did not weigh that would change the decision, such as a measurement showing the
|
|
164
|
+
settled choice cannot meet the Acceptance; then that decision block names the decision and that
|
|
165
|
+
evidence. A plan never overturns a settled decision on its own. A design change that leaves all
|
|
166
|
+
four intact, such as a planner's measurement that finds a better way to build the same outcome,
|
|
167
|
+
goes in the plan (the issue's `plan.md` document and `.legion/plan.json`), never into the approved
|
|
168
|
+
spec, even where the spec's text describes the older design; the reviewer reads the plan beside the
|
|
169
|
+
spec. When a planner's phase-finished notice names a departure from the spec's design, defer to
|
|
170
|
+
this section's full condition: only when the approved Summary, Acceptance, scope and settled
|
|
171
|
+
decisions all hold is the plan the record and the tree carries on. Otherwise change the root spec
|
|
172
|
+
and request approval again as this section says. Later waves, re-scoping open children toward the
|
|
173
|
+
same Acceptance, and integration-failure children need no spec edit and no new approval, and a
|
|
174
|
+
child issue's spec is never gated: the root approval covers the tree.
|
|
156
175
|
|
|
157
176
|
## 2. Children in flight
|
|
158
177
|
|
|
@@ -252,7 +271,7 @@ Preserve this order exactly:
|
|
|
252
271
|
2. on a clean review, `spawn_worker` the implementer once more to push only the `.legion/`
|
|
253
272
|
deletion, then the reviewer approves that head. The deletion must land before that approval, which is head-pinned. An implementer
|
|
254
273
|
completion advances the status only from `in_progress` to `testing`; this push, like retro
|
|
255
|
-
later, leaves the status where it is, so you set nothing by hand — on its `phase-
|
|
274
|
+
later, leaves the status where it is, so you set nothing by hand — on its `phase-finished`
|
|
256
275
|
wake, `spawn_worker` the reviewer to approve that head (a finished reviewer may already be
|
|
257
276
|
retired; `spawn_worker` resumes it);
|
|
258
277
|
3. retro commits its learnings under `docs/solutions/` on top of the approved head; that
|
|
@@ -285,7 +304,7 @@ the reviewer confirms and approves the new head by SHA (or continues its round i
|
|
|
285
304
|
approved); the merger republishes READY. Retro does not re-run. This merge happens only when
|
|
286
305
|
GitHub reports `CONFLICTING`
|
|
287
306
|
(`legion gh -- pr view <n> --json mergeable,mergeStateStatus`); read that on every end-game
|
|
288
|
-
wake — `pr-ready`, `pr-review`, `phase-
|
|
307
|
+
wake — `pr-ready`, `pr-review`, `phase-finished`, `catchup-overseer` — because a `CONFLICTING`
|
|
289
308
|
PR gets no CI and no wake announces it, and send the implementer to resolve it the moment you see
|
|
290
309
|
it. Do not let the merger publish `READY` for an obsolete approval.
|
|
291
310
|
|
|
@@ -314,7 +333,7 @@ entire end-game sequence has completed.
|
|
|
314
333
|
Handle one delivered wake by verifying the relevant live artifact and then performing the
|
|
315
334
|
corresponding lifecycle procedure. Architect-addressed wakes always reach the architect that owns
|
|
316
335
|
the payload issue: a claimed child sub-architect, otherwise the nearest claimed ancestor, then the
|
|
317
|
-
root. A wake about an architect role's own launch reaches the architect above it. `phase-
|
|
336
|
+
root. A wake about an architect role's own launch reaches the architect above it. `phase-finished`,
|
|
318
337
|
worker lifecycle, child lifecycle, and design-gate wakes are architect-only and never go to an
|
|
319
338
|
active phase worker.
|
|
320
339
|
|
|
@@ -326,33 +345,34 @@ active phase worker.
|
|
|
326
345
|
| `children-complete` | Execute steps 3–4: parent integration verification; failures become a new child wave, success advances to review and retro. |
|
|
327
346
|
| `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. |
|
|
328
347
|
| `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. |
|
|
329
|
-
| `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 and request approval again as section 1 says; stay parked; the gate is closed. |
|
|
330
|
-
| `phase-
|
|
348
|
+
| `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. |
|
|
349
|
+
| `phase-finished` | Read the committed handoff for the finishing 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. A `planner` notice that names a departure from the spec's design defers to section 1's full condition: the plan is the record and the next phase starts only when the approved Summary, Acceptance, scope and settled decisions still hold. A `reviewer` notice whose GitHub review is `CHANGES_REQUESTED` (the daemon returns the issue's Dispatch status to `in_progress` for this, on the reviewer's completion and again when you spawn the corrective implementer unless the daemon already knows the issue is `in_progress`) means `spawn_worker` the **implementer** again with the review findings — thread URLs and blocking items — as its task, then route back through tester and reviewer in order; never `spawn_worker` the reviewer directly off this wake and never proceed to retro on this verdict. A reviewer notice with an `APPROVED` review proceeds to retro (step 5). A `reviewer` notice after a conflict-forced rebase whose review body names an unchanged fingerprint is a confirmation, not a round: if retro already completed, `spawn_worker` the merger; otherwise resume the step you were on. An `implementer` notice that follows the merge is its production report: read the record on the pull request and the issue, then run step 7 — the issue is already at `retro`, the daemon writes no status for this completion, and you set `done` yourself. A `tester` notice whose handoff carries `implementerProof.verdict: "rejected"`, or a failure naming the production-like proof, goes back to the **implementer** with that finding — never forward to the reviewer, and never by supplying the proof from another role. A worker that reports no surface reaches the changed path gets a child issue in this tree (infrastructure, tooling, or a skill) and a resume once it lands; that report is never a reason to advance the phase. |
|
|
331
350
|
| `worker-queued` | Payload `{type:"worker-queued", issue, role}`. This role's task is queued for promotion — either the deployment's worker cap is full, or the live worker acknowledged the task without starting a turn and the daemon is retrying it (counted; the worker is replaced after three such failures, still with the same task). Do not respawn or retry — wait for `worker-started`. `legion state` shows the queue (`workerAdmission.queue`: role token, issue, role, kind, and the time the task was first queued — never the task text); read it before re-sending. A `spawn_worker` identical to the queued task changes nothing and is not announced again. Different text replaces the queued task silently in the same FIFO slot and retains its original queue time. A `spawn_worker` that fails with "got no response in 3 attempts" was already retried by the plugin under one request id and may still have reached the daemon: read the queue and the role's claim in `legion state` before sending it again. |
|
|
332
351
|
| `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. |
|
|
333
352
|
| `worker-recovered` | Payload `{type:"worker-recovered", issue, role, fromRef, delivery?}`. The worker's tree volume was lost and the daemon replaced it from the committed handoff on `fromRef`. `delivery: "spawned"` means the current assignment was preserved on the new worker; do not resend it. `delivery: "queued"` means that preserved assignment awaits capacity; wait for `worker-started`. Without `delivery`, inspect `.legion/` and the active phase before deciding whether work needs a new assignment. |
|
|
334
353
|
| `pr-ready` | Verify the live PR head, green status, and review state. Continue the review/retro/merger order only for that current head. |
|
|
335
|
-
| `pr-review` | Payload `{type:"pr-review", state, author, body}`. Delivered to whichever role is currently active for the issue, falling back to you when no worker phase is active. Follows the same verdict rule as a reviewer's `phase-
|
|
354
|
+
| `pr-review` | Payload `{type:"pr-review", state, author, body}`. Delivered to whichever role is currently active for the issue, falling back to you when no worker phase is active. Follows the same verdict rule as a reviewer's `phase-finished`: `state: "changes_requested"` sends the implementer back in with the review findings, then tester, then reviewer — never the reviewer again and never retro; that `spawn_worker` returns the issue to `in_progress` on its own (the daemon writes it for a corrective implementer whenever the PR's latest recorded review is changes requested, a human's after approval included), so you set nothing by hand; `state: "approved"` proceeds toward retro (step 5) once the step 6 integration/merge-gate conditions are met. `state: "approved"` on a rebased head whose body names an unchanged fingerprint is that confirmation: proceed to retro if it has not run, otherwise to the merger — never to a second retro or test round. |
|
|
336
355
|
| `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. Assign a focused implementer or corrective child, then return it through testing and review; do not treat the blocked PR as final. |
|
|
337
|
-
| `pr-merged` | Payload `{type:"pr-merged", pr, mergeCommitSha}`. The PR merged because a human merged it under the repository's rules. `spawn_worker` the **implementer** with the production-check task naming that merge commit (it resumes the same agent; a retired role has no live holder, so never `envoy_publish` for this). Its `phase-
|
|
356
|
+
| `pr-merged` | Payload `{type:"pr-merged", pr, mergeCommitSha}`. The PR merged because a human merged it under the repository's rules. `spawn_worker` the **implementer** with the production-check task naming that merge commit (it resumes the same agent; a retired role has no live holder, so never `envoy_publish` for this). Its `phase-finished` is what brings you to step 7: verify the record on the pull request and this issue first, then sign off naming it and set the issue `done`. A merge is not the close. |
|
|
338
357
|
| `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. |
|
|
339
358
|
| `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. |
|
|
340
|
-
| `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-
|
|
359
|
+
| `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. |
|
|
341
360
|
| `worker-died` | Payload `{type:"worker-died", issue, role}`. Two causes, one verdict: the daemon retried this role's boot through `MAX_LAUNCH_FAILURES` attempts and could not confirm it, or the worker booted and acknowledged every prompt without ever starting a turn through `MAX_PROMPT_RETIRES` retire-and-relaunch cycles (LEGION-93) — never a raw-event replay or a silent revive. Your next `spawn_worker` for the role is the retry (one cold launch, three prompts, and `worker-died` again if the agent is still broken). Reassess the work and `spawn_worker` again for the role (it resumes the same agent via `--resume` if a session file survived) or reassign it if the failure looks environmental, not agent-specific. |
|
|
342
361
|
| `reopened` | Reopen the root lifecycle: inspect the reason and current artifacts, reassess scope and children, and resume at the first applicable numbered step. |
|
|
343
362
|
|
|
344
363
|
## Escalation judgment
|
|
345
364
|
|
|
346
|
-
Controller-actionable matters are exactly re-filing a genuinely independent child,
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
365
|
+
Controller-actionable matters are exactly re-filing a genuinely independent child, capacity, and
|
|
366
|
+
cross-tree conflict. Use the Legion escalation operation for those. Handle everything else in the
|
|
367
|
+
tree. A product, scope, or design decision that needs the human, yours or one a worker escalated,
|
|
368
|
+
is a decision block you write (section 1 says what one does to the root spec's gate). A standalone
|
|
369
|
+
human to-do may use `dispatch_ask`; workers may reach Sami directly with it the same way. Do not
|
|
370
|
+
create a wait loop for any wake source.
|
|
371
|
+
|
|
372
|
+
Never yield while waiting on a human. A human is waiting on you only where an open ask sits in
|
|
373
|
+
their inbox, so open it before you stop: a decision block in the spec, `dispatch_ask` for a
|
|
374
|
+
standalone human to-do, or `dispatch_request_approval` for the spec gate. Otherwise proceed:
|
|
375
|
+
proceeding is the default, and a stop that waits on nobody stalls the tree until someone notices.
|
|
356
376
|
|
|
357
377
|
## Architecture components
|
|
358
378
|
|
|
@@ -323,7 +323,7 @@ priority first, then board rank ([Keeping the slots full](#keeping-the-slots-ful
|
|
|
323
323
|
| `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
324
|
| `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
325
|
| `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
|
-
| Architect escalation (controller-actionable only: re-file a child as a root issue, capacity, cross-tree conflicts) | request + context | Judge and act; issue-
|
|
326
|
+
| 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
327
|
| Resync report | artifact-driven anomaly list (zero-owner trees, untriaged-open, launch-failed, admission-drift) | Verify against fresh state, then heal |
|
|
328
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
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` |
|
|
@@ -390,8 +390,8 @@ controller decision, not a no-op.
|
|
|
390
390
|
## Architect escalation
|
|
391
391
|
|
|
392
392
|
Only decide controller-actionable escalations: re-filing independent work, capacity, and
|
|
393
|
-
cross-tree conflicts.
|
|
394
|
-
|
|
393
|
+
cross-tree conflicts. The owning architect writes an issue-design decision as a decision block and
|
|
394
|
+
uses `dispatch_ask` only for a human to-do, not the controller.
|
|
395
395
|
|
|
396
396
|
For an independence judgment, verify the child and its parent against current daemon state
|
|
397
397
|
and the Dispatch issue. If the work belongs in an independent root:
|
|
@@ -60,12 +60,13 @@ only on this phase's artifact.
|
|
|
60
60
|
You never spawn another Legion role: spawning a worker
|
|
61
61
|
(`legion({op: "spawn_worker", ... })`) is architect-only. You may still use ordinary `task`
|
|
62
62
|
subagents for your own phase work; none of them is a Legion role.
|
|
63
|
-
Escalate a product, scope, cross-phase, or lifecycle decision to the owning architect with
|
|
63
|
+
Escalate a product, scope, design, cross-phase, or lifecycle decision to the owning architect with
|
|
64
64
|
`envoy_publish` to its role topic (`notifications.role.` followed by its encoded token, see
|
|
65
65
|
above), carrying the verified facts and the decision needed. `hub` only reaches subagents
|
|
66
|
-
inside your own process, not the architect's separate one.
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
inside your own process, not the architect's separate one. Never write a decision block into a
|
|
67
|
+
spec yourself: the architect decides whether the human must answer it and writes the block, since
|
|
68
|
+
a new version of an approved root spec closes the tree's design gate. A standalone to-do only a
|
|
69
|
+
human can do is a `dispatch_ask`, and its replies return to your own session.
|
|
69
70
|
|
|
70
71
|
Because the same agent is always resumed for its phase, you may receive more than one
|
|
71
72
|
assignment across your lifetime: after you complete and go idle, a later event (a review
|
|
@@ -165,7 +166,7 @@ other tree paused.
|
|
|
165
166
|
|
|
166
167
|
## Phase work
|
|
167
168
|
|
|
168
|
-
Specifications written into Dispatch follow `skill://dispatch`'s [Writing a spec](../dispatch/SKILL.md#writing-a-spec).
|
|
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
170
|
|
|
170
171
|
Follow the repository's normal engineering workflow and the assigned issue's acceptance
|
|
171
172
|
criteria. Your phase's own charter and the predecessor handoffs you read define the phase
|
|
@@ -260,13 +261,20 @@ legion gh -- pr comment <pr-number> \
|
|
|
260
261
|
|
|
261
262
|
## Planner artifact
|
|
262
263
|
|
|
263
|
-
The plan lives in `.legion/plan.json` and the
|
|
264
|
+
The plan lives in `.legion/plan.json` and the issue's `plan.md` document, never in the issue's
|
|
265
|
+
primary document, which is its spec; never commit a plan or spec file to the repository.
|
|
264
266
|
No `docs/plans/*`, `docs/superpowers/plans/*`, or spec markdown goes into the pull request: plan
|
|
265
267
|
and spec content goes into the issue, never into a PR (the root `AGENTS.md`
|
|
266
268
|
calls its own `docs/plans/` human-authored design history, not a Legion artifact). A skill step that says "save the plan
|
|
267
269
|
to a file" is satisfied by the handoff write in the completion gate below; the planner's only
|
|
268
270
|
commit is `plan: record handoff`.
|
|
269
271
|
|
|
272
|
+
A plan that departs from the spec's design records the departure in `plan.md` and in the required
|
|
273
|
+
`.legion/plan.json` `specDepartures`: `[]` means no departure; otherwise each bounded record names
|
|
274
|
+
the spec, plan, evidence and outcome. The planner's role prompt defines that record. The planner
|
|
275
|
+
never edits the spec. Whether the spec changes is the architect's decision
|
|
276
|
+
(`skill://legion-architect`, section 1), and the reviewer reads the plan beside the spec.
|
|
277
|
+
|
|
270
278
|
## Implementer push and pull request
|
|
271
279
|
|
|
272
280
|
The implementer opens the pull request. After its implementation commit and verification, it
|
|
@@ -474,13 +482,12 @@ committed handoff as needed, without mutating anything (see Workspace and handof
|
|
|
474
482
|
precedence above). You will also be the one resumed, with a new prompt in this same
|
|
475
483
|
session, if this phase's work needs to run again.
|
|
476
484
|
|
|
477
|
-
When blocked on
|
|
478
|
-
architect a concise message: issue, phase, verified observation, what you tried, and the
|
|
479
|
-
decision required.
|
|
480
|
-
outside that coordination.
|
|
485
|
+
When blocked on a product, scope, design, lifecycle, or cross-phase decision, `envoy_publish` the
|
|
486
|
+
owning architect a concise message: issue, phase, verified observation, what you tried, and the
|
|
487
|
+
decision required.
|
|
481
488
|
|
|
482
|
-
Never yield while blocked on a decision someone else owns. Before you stop, make the block
|
|
483
|
-
|
|
484
|
-
owning architect as above, and a standalone human
|
|
485
|
-
proceed: proceeding is the default, and a phase that stops silently holds its issue until
|
|
486
|
-
|
|
489
|
+
Never yield while blocked on a decision someone else owns. Before you stop, make the block visible
|
|
490
|
+
where its owner will see it: a product, scope, design, lifecycle, or cross-phase decision goes to
|
|
491
|
+
the owning architect as above, and a standalone human to-do goes in `dispatch_ask`. Otherwise
|
|
492
|
+
proceed: proceeding is the default, and a phase that stops silently holds its issue until someone
|
|
493
|
+
notices.
|