@sjawhar/pi-legion-envoy 6.0.4 → 6.1.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/envoy.js +5 -5
- package/dist/legion.js +6 -6
- package/dist/skills/dispatch/SKILL.md +59 -36
- package/dist/skills/dispatch/references/brainstorming.md +52 -0
- package/dist/skills/dispatch/references/documents.md +23 -11
- package/dist/skills/dispatch-first/SKILL.md +3 -2
- package/dist/skills/legion-architect/SKILL.md +31 -25
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -33716,7 +33716,7 @@ function approvalLine(artifact) {
|
|
|
33716
33716
|
case "approved":
|
|
33717
33717
|
return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}`;
|
|
33718
33718
|
case "stale":
|
|
33719
|
-
return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}, edited since (now v${approval.latest_version}) - request approval again`;
|
|
33719
|
+
return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}, edited since (now v${approval.latest_version}) - request approval again once the human has agreed to every point in this version`;
|
|
33720
33720
|
case "changes_requested":
|
|
33721
33721
|
return `Approval: changes requested on v${approval.version} by ${approval.by?.id ?? "unknown"}: ${approval.reason ?? ""}`;
|
|
33722
33722
|
}
|
|
@@ -34079,9 +34079,9 @@ async function refuseOpenDecisionBlocks(client, tool, resolved) {
|
|
|
34079
34079
|
return;
|
|
34080
34080
|
const count = open.length === 1 ? "1 open decision block" : `${open.length} open decision blocks`;
|
|
34081
34081
|
throw new Error([
|
|
34082
|
-
`${tool} was not called: ${artifact.name} (version ${latest}) has ${count}. Answering one writes a new version, which would
|
|
34082
|
+
`${tool} was not called: ${artifact.name} (version ${latest}) has ${count}. Answering one writes a new version, which would move this request to that version and leave it waiting on you.`,
|
|
34083
34083
|
...open.map((line) => `- ${line}`),
|
|
34084
|
-
"Do not request approval over an open block, even when a human asked for it. Tell the human which block is open and ask them to answer it or to waive it. Once it is answered, fold the answer into the text with dispatch_doc_edit
|
|
34084
|
+
"Do not request approval over an open block, even when a human asked for it. Tell the human which block is open and ask them to answer it or to waive it. Once it is answered, fold the answer into the text with dispatch_doc_edit. If they waive it, close the block with dispatch_resolve_ask (kind resolved, their words as the reason) and write their decision into the text with dispatch_doc_edit. Then request approval again once the human has agreed to every point in the new version: the call opens the request, or hands an open one back to the human."
|
|
34085
34085
|
].join(`
|
|
34086
34086
|
`));
|
|
34087
34087
|
}
|
|
@@ -34775,7 +34775,7 @@ ${trailer.join(`
|
|
|
34775
34775
|
});
|
|
34776
34776
|
if (result.ask === null) {
|
|
34777
34777
|
return {
|
|
34778
|
-
text: `${resolved.artifact.name} (document id ${resolved.artifact.id}) is already approved at version ${result.version} by ${result.approval.by?.id ?? "unknown"}; no new request was opened. An edit after approval makes it stale, so request again only for a new version.`,
|
|
34778
|
+
text: `${resolved.artifact.name} (document id ${resolved.artifact.id}) is already approved at version ${result.version} by ${result.approval.by?.id ?? "unknown"}; no new request was opened. An edit after approval makes it stale, so request again only for a new version, once the human has agreed to every point in it.`,
|
|
34779
34779
|
details: {
|
|
34780
34780
|
...resolved.owner.kind === "project" ? documentResultDetails(resolved.artifact) : { issue: resolved.issue?.key },
|
|
34781
34781
|
artifact: resolved.artifact.id,
|
|
@@ -34786,7 +34786,7 @@ ${trailer.join(`
|
|
|
34786
34786
|
const details = await followedAskDetails(client, result.ask, resolved.artifact);
|
|
34787
34787
|
const outcome = result.recorded ? `Approval requested for ${resolved.artifact.name} (document id ${resolved.artifact.id}) at version ${result.version} (ask ${result.ask.id}).` : `The approval request for ${resolved.artifact.name} (document id ${resolved.artifact.id}) at version ${result.version} (ask ${result.ask.id}) already waits on the human, so this call changed nothing: nothing since it last reached the human (a newer version, a human's reply in its thread, or your progress note) left it waiting on you.`;
|
|
34788
34788
|
return {
|
|
34789
|
-
text: `${outcome} The human's Inbox asks: ${JSON.stringify(result.ask.question)}. The answer arrives as artifact.approved or artifact.changes_requested
|
|
34789
|
+
text: `${outcome} The human's Inbox asks: ${JSON.stringify(result.ask.question)}. The answer arrives as artifact.approved or artifact.changes_requested. An edit before the answer moves this request to the new version and leaves it waiting on you, and an edit after approval makes the approval stale: either way, request again for the new version once the human has agreed to every point in it, which hands this request back or opens a new one.`,
|
|
34790
34790
|
details: { ...details, artifact: resolved.artifact.id, version: result.version }
|
|
34791
34791
|
};
|
|
34792
34792
|
}
|
package/dist/legion.js
CHANGED
|
@@ -16501,7 +16501,7 @@ function approvalLine(artifact) {
|
|
|
16501
16501
|
case "approved":
|
|
16502
16502
|
return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}`;
|
|
16503
16503
|
case "stale":
|
|
16504
|
-
return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}, edited since (now v${approval.latest_version}) - request approval again`;
|
|
16504
|
+
return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}, edited since (now v${approval.latest_version}) - request approval again once the human has agreed to every point in this version`;
|
|
16505
16505
|
case "changes_requested":
|
|
16506
16506
|
return `Approval: changes requested on v${approval.version} by ${approval.by?.id ?? "unknown"}: ${approval.reason ?? ""}`;
|
|
16507
16507
|
}
|
|
@@ -16864,9 +16864,9 @@ async function refuseOpenDecisionBlocks(client, tool, resolved) {
|
|
|
16864
16864
|
return;
|
|
16865
16865
|
const count = open.length === 1 ? "1 open decision block" : `${open.length} open decision blocks`;
|
|
16866
16866
|
throw new Error([
|
|
16867
|
-
`${tool} was not called: ${artifact.name} (version ${latest}) has ${count}. Answering one writes a new version, which would
|
|
16867
|
+
`${tool} was not called: ${artifact.name} (version ${latest}) has ${count}. Answering one writes a new version, which would move this request to that version and leave it waiting on you.`,
|
|
16868
16868
|
...open.map((line) => `- ${line}`),
|
|
16869
|
-
"Do not request approval over an open block, even when a human asked for it. Tell the human which block is open and ask them to answer it or to waive it. Once it is answered, fold the answer into the text with dispatch_doc_edit
|
|
16869
|
+
"Do not request approval over an open block, even when a human asked for it. Tell the human which block is open and ask them to answer it or to waive it. Once it is answered, fold the answer into the text with dispatch_doc_edit. If they waive it, close the block with dispatch_resolve_ask (kind resolved, their words as the reason) and write their decision into the text with dispatch_doc_edit. Then request approval again once the human has agreed to every point in the new version: the call opens the request, or hands an open one back to the human."
|
|
16870
16870
|
].join(`
|
|
16871
16871
|
`));
|
|
16872
16872
|
}
|
|
@@ -17560,7 +17560,7 @@ ${trailer.join(`
|
|
|
17560
17560
|
});
|
|
17561
17561
|
if (result.ask === null) {
|
|
17562
17562
|
return {
|
|
17563
|
-
text: `${resolved.artifact.name} (document id ${resolved.artifact.id}) is already approved at version ${result.version} by ${result.approval.by?.id ?? "unknown"}; no new request was opened. An edit after approval makes it stale, so request again only for a new version.`,
|
|
17563
|
+
text: `${resolved.artifact.name} (document id ${resolved.artifact.id}) is already approved at version ${result.version} by ${result.approval.by?.id ?? "unknown"}; no new request was opened. An edit after approval makes it stale, so request again only for a new version, once the human has agreed to every point in it.`,
|
|
17564
17564
|
details: {
|
|
17565
17565
|
...resolved.owner.kind === "project" ? documentResultDetails(resolved.artifact) : { issue: resolved.issue?.key },
|
|
17566
17566
|
artifact: resolved.artifact.id,
|
|
@@ -17571,7 +17571,7 @@ ${trailer.join(`
|
|
|
17571
17571
|
const details = await followedAskDetails(client, result.ask, resolved.artifact);
|
|
17572
17572
|
const outcome = result.recorded ? `Approval requested for ${resolved.artifact.name} (document id ${resolved.artifact.id}) at version ${result.version} (ask ${result.ask.id}).` : `The approval request for ${resolved.artifact.name} (document id ${resolved.artifact.id}) at version ${result.version} (ask ${result.ask.id}) already waits on the human, so this call changed nothing: nothing since it last reached the human (a newer version, a human's reply in its thread, or your progress note) left it waiting on you.`;
|
|
17573
17573
|
return {
|
|
17574
|
-
text: `${outcome} The human's Inbox asks: ${JSON.stringify(result.ask.question)}. The answer arrives as artifact.approved or artifact.changes_requested
|
|
17574
|
+
text: `${outcome} The human's Inbox asks: ${JSON.stringify(result.ask.question)}. The answer arrives as artifact.approved or artifact.changes_requested. An edit before the answer moves this request to the new version and leaves it waiting on you, and an edit after approval makes the approval stale: either way, request again for the new version once the human has agreed to every point in it, which hands this request back or opens a new one.`,
|
|
17575
17575
|
details: { ...details, artifact: resolved.artifact.id, version: result.version }
|
|
17576
17576
|
};
|
|
17577
17577
|
}
|
|
@@ -17843,7 +17843,7 @@ function endInjectedUserTurns(sessionID) {
|
|
|
17843
17843
|
// package.json
|
|
17844
17844
|
var package_default = {
|
|
17845
17845
|
name: "@sjawhar/pi-legion-envoy",
|
|
17846
|
-
version: "6.0
|
|
17846
|
+
version: "6.1.0",
|
|
17847
17847
|
type: "module",
|
|
17848
17848
|
omp: {
|
|
17849
17849
|
extensions: [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dispatch
|
|
3
|
-
description: "Use before posting a message, a status update, or a periodic status update; before asking a question that references another message, artifact, or eval; and when asking Sami a question, updating the spec, commenting on a document, attaching an artifact, or calling a dispatch_* tool."
|
|
3
|
+
description: "Use before posting a message, a status update, or a periodic status update; before asking a question that references another message, artifact, or eval; before asking a design question or brainstorming a change; and 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
|
|
@@ -26,6 +26,7 @@ after `skill://dispatch/` is relative to this skill's base directory.
|
|
|
26
26
|
| call `dispatch_doc_edit`: rewrite a paragraph, insert or move a block, change a table's cells, rows or columns | [Editing a document](skill://dispatch/references/document-edits.md) |
|
|
27
27
|
| write a typed block (an `:::ask`, a callout), comment on or suggest a change to a document, upload an artifact, or retry after `DOC_SERVICE_UNAVAILABLE` | [Documents](skill://dispatch/references/documents.md) |
|
|
28
28
|
| choose your next issue, claim one, move its status or priority, reorder a board, or audit a project's backlog | [Working an issue](skill://dispatch/references/issues.md) |
|
|
29
|
+
| start or answer a design conversation in a spec: each turn, a comment that settles a question, coming to terms, a worked example | [Brainstorming in the spec](skill://dispatch/references/brainstorming.md) |
|
|
29
30
|
| find who answers an ask, edit, retract or resolve one, reply with the turn, or follow a thread | [Asks after they open](skill://dispatch/references/asks.md) |
|
|
30
31
|
| catch up after a restart, trace what cites a node, or write a `dispatch://` reference | [Reading back](skill://dispatch/references/reading.md) |
|
|
31
32
|
| answer a BTW, Aside or Steer frame, or a message from the Agents page | [Targeted and direct messages](skill://dispatch/references/messages.md) |
|
|
@@ -34,10 +35,18 @@ after `skill://dispatch/` is relative to this skill's base directory.
|
|
|
34
35
|
|
|
35
36
|
## Design changes are brainstormed here
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
When a session has Dispatch, a design change needing the human's choices is brainstormed in its
|
|
39
|
+
issue's spec, not chat or a repository design document. The first version holds only established
|
|
40
|
+
facts and every ready question, each a decision block at the end of the section that sets it up; a
|
|
41
|
+
question waits only when it depends on an answer still open. Each next version replies in each
|
|
42
|
+
human comment's thread (`dispatch_comment` with `reply_to`; under an open ask whose next move is
|
|
43
|
+
yours, such as an approval request you must revise or hand back, `reply_to_ask` with
|
|
44
|
+
`turn: "agent"`, since a default-turn reply hands it back to the human), folds the answers into the
|
|
45
|
+
surrounding text while their decision blocks stay, in the human's words (or the option they chose)
|
|
46
|
+
with the date, and adds the questions they open. Approval is requested at the end, not after each
|
|
47
|
+
section, when nothing in the spec is new to the human. Before you write a spec's first version, and
|
|
48
|
+
again before each next turn, read [Brainstorming in the spec](skill://dispatch/references/brainstorming.md):
|
|
49
|
+
each step, and a worked example.
|
|
41
50
|
|
|
42
51
|
## Writing for the human
|
|
43
52
|
|
|
@@ -75,22 +84,28 @@ evidence for it, in plain words: what goes wrong, for whom, and the counts or ca
|
|
|
75
84
|
It grows in place as the conversation goes. It is the issue's one primary document: extend it with
|
|
76
85
|
a new version that keeps the human's own text, never a second "spec" artifact beside it.
|
|
77
86
|
|
|
78
|
-
- **Each open question is a decision block**, placed as [Decision blocks](#decision-blocks)
|
|
79
|
-
Because it is an ask, it reaches the human's Inbox, and the answer lands next to its context.
|
|
87
|
+
- **Each open question is a decision block**, shaped and placed as [Decision blocks](#decision-blocks)
|
|
88
|
+
says. Because it is an ask, it reaches the human's Inbox, and the answer lands next to its context.
|
|
80
89
|
- **A settled point records the human's own words and the date**, quoted, so no reader mistakes
|
|
81
|
-
it for your inference
|
|
90
|
+
it for your inference; an answer that is only a chosen option is recorded in the form
|
|
91
|
+
`Sami chose "Commit author" on the question below (2026-10-02)`. A point you inferred says so,
|
|
92
|
+
with the reasoning. One carried in from another document keeps its provenance: an agent's
|
|
93
|
+
inference there is marked one here, or stays out until the human raises it.
|
|
82
94
|
- **Sections follow the topic.** No heading is required and none has a fixed place; name each
|
|
83
95
|
section for what it discusses.
|
|
84
96
|
- **A changed point is rewritten, not appended to.** When an answer or a new fact changes the
|
|
85
97
|
design, rewrite the text it changes and fold the answered decision into it; the document's
|
|
86
98
|
versions keep the history.
|
|
87
99
|
- **No placeholders.** No TBD, TODO, or hedging ("might", "could consider"): an open item is a
|
|
88
|
-
decision block, a technical decision your lane makes
|
|
89
|
-
contract between two lanes, a question you settle with the other lane over Envoy (see
|
|
100
|
+
decision block, a technical decision your lane makes where the work happens, outside the spec,
|
|
101
|
+
or, for a contract between two lanes, a question you settle with the other lane over Envoy (see
|
|
90
102
|
[Before you ask](#before-you-ask) under Asking).
|
|
91
103
|
- **No progress.** The spec records the design and its decisions, never status, timestamps, an
|
|
92
104
|
"Update HH:MMZ" section, a pull-request list, or handoff notes. Progress is not a Dispatch
|
|
93
105
|
object at all; it lives in your transcript and your pull request (see [Messages](#messages)).
|
|
106
|
+
- **No commentary.** The spec talks about the design, never about the spec or the conversation: no
|
|
107
|
+
sentence calls it a draft, a conversation, a version or a turn; says what a later version will
|
|
108
|
+
add; describes an earlier version or correction; or narrates the exchange that produced it.
|
|
94
109
|
|
|
95
110
|
Before a new version goes out, read it as the human will: no two passages conflict, each point has
|
|
96
111
|
one reading, and every decision block passes the phone test above. A worked example is
|
|
@@ -101,9 +116,11 @@ after the section on models.
|
|
|
101
116
|
## Decision blocks
|
|
102
117
|
|
|
103
118
|
A decision a human must make is an `:::ask` block at the end of the section that discusses it,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
119
|
+
shaped as [Writing for the human](#writing-for-the-human) says for a question, with what
|
|
120
|
+
constrains the answer split into measured, known and unknown. It asks how to solve the problem,
|
|
121
|
+
never whether to apply a change already chosen. Never gather decisions into a list, at the top,
|
|
122
|
+
at the bottom or in an "open questions" section, and never ask one as a standalone `dispatch_ask`
|
|
123
|
+
that points at the spec.
|
|
107
124
|
|
|
108
125
|
The block is what reaches the human's Inbox. A question phrased as prose in the spec reaches
|
|
109
126
|
nobody. A spec with no ask blocks is fine only when the issue genuinely needs no human decision.
|
|
@@ -210,10 +227,11 @@ Every `dispatch_ask` passes four gates first:
|
|
|
210
227
|
1. **Does it need his authority, taste, or risk appetite?** This is the bar for a decision
|
|
211
228
|
written as an `:::ask` block in context ([Decision blocks](#decision-blocks)). Technical
|
|
212
229
|
decisions inside your outcome do not: schema shapes, table layouts, field names, and migration
|
|
213
|
-
internals are your lane's to decide
|
|
214
|
-
settled by those two lanes over Envoy, and you open no ask
|
|
215
|
-
to IAM, deletion or exposure of production data, anything
|
|
216
|
-
gate: it is your own `dispatch_ask` to Sami on your own
|
|
230
|
+
internals are your lane's to decide where the work happens, in the plan or the code, not in the
|
|
231
|
+
spec. A contract between two lanes is settled by those two lanes over Envoy, and you open no ask
|
|
232
|
+
for it. A halt condition (a change to IAM, deletion or exposure of production data, anything
|
|
233
|
+
that reaches a customer) passes this gate: it is your own `dispatch_ask` to Sami on your own
|
|
234
|
+
issue.
|
|
217
235
|
2. **Is there genuine uncertainty, and have you measured what you can?** If there is none, it is
|
|
218
236
|
a plan you execute. The one legitimate ask without uncertainty is permission for an action
|
|
219
237
|
only a human can authorise — a production write, an external send, a console action. Start it
|
|
@@ -363,25 +381,30 @@ See [Following](#following) for why you receive what happens to asks you open an
|
|
|
363
381
|
|
|
364
382
|
Approval is a property of a document, not a question you phrase: a human approves a specific
|
|
365
383
|
version, the way a pull-request review approves a commit, and any later version makes that
|
|
366
|
-
approval stale.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
384
|
+
approval stale. A later version also moves an open request to that version and leaves it waiting
|
|
385
|
+
on you until you request again, which hands the same request back to the human. Answering a
|
|
386
|
+
decision block writes a new version, so request approval only when all three hold: every decision
|
|
387
|
+
block is settled, which means answered and folded into the text, or waived as the next paragraph
|
|
388
|
+
says; the human has agreed to every point in the spec; and the current version has not been
|
|
389
|
+
approved. A Legion root spec under an armed design gate always goes to approval once all three
|
|
390
|
+
hold (`skill://legion-architect`).
|
|
391
|
+
|
|
392
|
+
When all three hold, request it in the pass that finishes the spec: a design waiting with nothing in
|
|
393
|
+
the human's Inbox waits on nobody. When a human asks for approval while a block is open, do not
|
|
394
|
+
request it and do not hold it silently: name each open block and ask them to answer it or waive
|
|
395
|
+
it. For a waiver, close the block with `dispatch_resolve_ask` (`kind: "resolved"`, their words as
|
|
396
|
+
`reason`), then write their decision into the text in their words and request.
|
|
397
|
+
`dispatch_request_approval` refuses while any block is open.
|
|
398
|
+
|
|
399
|
+
An approval request carries nothing new: a point the human has not agreed to gets its own decision
|
|
400
|
+
block first, with your recommendation, or, when it is a choice you can make yourself
|
|
401
|
+
([Before you ask](#before-you-ask), gate 1) and the human will not want a say in it, comes out of
|
|
402
|
+
the spec and is made where the work happens; an inference you cannot defend in a decision block
|
|
403
|
+
comes out of the spec. `summary` says in one to three sentences what the human is approving and
|
|
404
|
+
nothing else: no commentary on itself or on the conversation, and no open question. The Inbox
|
|
405
|
+
shows it after "Approve spec.md (version N)?". Never write "Approve" options into an ordinary
|
|
406
|
+
`dispatch_ask`; only humans approve. The call, its result and its answer:
|
|
407
|
+
[Approval requests](skill://dispatch/references/documents.md#approval-requests).
|
|
385
408
|
|
|
386
409
|
## The Spec
|
|
387
410
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Brainstorming in the spec: each turn, from the first version to approval
|
|
2
|
+
|
|
3
|
+
This overrides the brainstorming skill's pace: no design question goes to chat, there is no limit
|
|
4
|
+
of one question per message, and there is no approval after each section, only the one at the end.
|
|
5
|
+
Its stages still shape what you ask (clarifying questions, then two or three approaches with a
|
|
6
|
+
recommendation, then the design section by section), but a question that depends on no open answer
|
|
7
|
+
goes out at once, whatever its stage.
|
|
8
|
+
|
|
9
|
+
## Each turn
|
|
10
|
+
|
|
11
|
+
1. **Start with what is established.** The first version holds only what the conversation has
|
|
12
|
+
established: the problem and its evidence, what the human has said in their own words, the
|
|
13
|
+
facts the next questions need, and each question that is ready, as a decision block at the end
|
|
14
|
+
of the section that sets it up, shaped as "Decision blocks" in `skill://dispatch` says. Write
|
|
15
|
+
nothing past those questions.
|
|
16
|
+
2. **The human answers or comments.** Reply to each human comment in its thread
|
|
17
|
+
(`dispatch_comment` with `reply_to`), then rewrite the passage the answer or the comment changes.
|
|
18
|
+
Under an open ask whose next move is yours, such as the approval request you must revise or
|
|
19
|
+
hand back, reply with `reply_to_ask` and `turn: "agent"` instead: a `reply_to` reply takes the
|
|
20
|
+
default turn, which hands the request back to the human unchanged, and a call with a corrected
|
|
21
|
+
`summary` is refused while it waits on them.
|
|
22
|
+
3. **Each next version folds the answers in and adds what they open.** Keep each answered
|
|
23
|
+
decision block where it is, fold its answer into the surrounding text in the human's words with
|
|
24
|
+
the date (an answer that is only a chosen option as
|
|
25
|
+
`Sami chose "Commit author" on the question below (2026-10-02)`), then add the next sections,
|
|
26
|
+
each with its question. Every question that is ready goes out at once, each as a decision block
|
|
27
|
+
at the end of the section that sets it up; a question waits only when it depends on an answer
|
|
28
|
+
still open.
|
|
29
|
+
4. **A comment that answers a question settles it** as surely as the block does. Fold it into the
|
|
30
|
+
text at once, and close the block with `dispatch_resolve_ask` if the human has not.
|
|
31
|
+
5. **Request approval at the end, not after each section, when nothing in the spec is new to the
|
|
32
|
+
human:** every block settled, every comment answered, and every point they have not agreed to,
|
|
33
|
+
however small, either put to them first as its own decision block or, when it is yours to
|
|
34
|
+
decide, taken out of the spec and made where the work happens. An inference you cannot defend in
|
|
35
|
+
a decision block comes out of the spec. The request carries nothing new ("Approval of a spec" in
|
|
36
|
+
`skill://dispatch`).
|
|
37
|
+
|
|
38
|
+
## Coming to terms
|
|
39
|
+
|
|
40
|
+
The conversation comes to terms in both directions. Explain what the code does today, plainly
|
|
41
|
+
enough for the human to react to, and ask; the human's model comes out of those reactions, and so
|
|
42
|
+
do corrections to it. Neither your model nor theirs is the starting truth.
|
|
43
|
+
|
|
44
|
+
## A worked example
|
|
45
|
+
|
|
46
|
+
`dispatch://AGENTC-1563/artifact/spec` is the secrets broker's identity model. Its first version
|
|
47
|
+
held the problem, the human's words, what exists today, and the one question that was ready then;
|
|
48
|
+
it also called itself "a conversation", which the human struck as commentary. Each later version
|
|
49
|
+
folds the answers in with the human's words and date and adds the questions they open. Its first
|
|
50
|
+
approval request, on `dispatch://AGENTC-1563/artifact/spec@v35`, named four inferences the human
|
|
51
|
+
had never discussed; the human rejected it, and each of the four was then either put to the human
|
|
52
|
+
as its own decision block or taken out of the spec.
|
|
@@ -43,10 +43,14 @@ unreadable in the browser refuses nothing it is carried through unchanged by. Fo
|
|
|
43
43
|
|
|
44
44
|
```md
|
|
45
45
|
:::ask{urgency="high" multiple="false"}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
Today's release is blocked by a database migration. The maintenance window closes in two hours;
|
|
47
|
+
whether production data needs an index rebuild is unknown. How should we complete the migration?
|
|
48
|
+
Recommendation: rehearse on a production snapshot, then apply in the window, because it finds the
|
|
49
|
+
unknown cost before production while keeping today's release possible.
|
|
50
|
+
|
|
51
|
+
- Apply now: Meets today's release, but recovery may be slower if the index rebuild is needed.
|
|
52
|
+
- Rehearse then apply: Costs rehearsal time, but exposes the rebuild and rollback cost before production.
|
|
53
|
+
- Defer the release: Avoids migration risk today, but leaves the release and its fixes unavailable.
|
|
50
54
|
:::
|
|
51
55
|
```
|
|
52
56
|
|
|
@@ -156,13 +160,21 @@ is refused, with nothing sent, while that version holds a decision block open, a
|
|
|
156
160
|
names each block and its ask. An answer or a `dispatch_resolve_ask` closes the ask at once but
|
|
157
161
|
reaches a version only when the document settles, about two seconds later, or with your next
|
|
158
162
|
`dispatch_doc_edit`: fold the answer into the text (or, for a waiver, write the human's decision
|
|
159
|
-
in)
|
|
160
|
-
opened its ask. A
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
`
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
in) before you request. A block written in the last few seconds counts as open before Dispatch has
|
|
164
|
+
opened its ask. A document holds one open request. A new version moves it to that version, keeping
|
|
165
|
+
its thread and summary, and leaves it waiting on you, as a human's reply in its thread does; a move
|
|
166
|
+
your own edit made sends you no event, and `dispatch_read` and `dispatch_doc_read` show it as
|
|
167
|
+
`Approval: awaiting, waiting on agent`. Call again when "Approval of a spec" in `skill://dispatch`
|
|
168
|
+
allows: that hands the same request back to the human, reworded first when `summary` is new. Your
|
|
169
|
+
own reply in its thread hands it back too, with no call and the summary it already holds, unless
|
|
170
|
+
you post it with `reply_to_ask` and `turn: "agent"` (`reply_to` carries no turn, so its reply takes
|
|
171
|
+
the default, `human`) or a new version has moved the request since your last
|
|
172
|
+
`dispatch_request_approval`. While it waits on the human, a call with the same `summary` changes
|
|
173
|
+
nothing, and one with a different `summary` is refused, since it would rewrite the card they are
|
|
174
|
+
reading. The answer reaches you as `artifact.approved` or `artifact.changes_requested` with the
|
|
175
|
+
pinned `version` and closes the request, so the next call opens a new one; `changes_requested`
|
|
176
|
+
carries the reason, which is your next piece of work. Those reads show the document's approval
|
|
177
|
+
state; `stale` means it was approved and then edited.
|
|
166
178
|
|
|
167
179
|
## A document that is reloading
|
|
168
180
|
|
|
@@ -43,7 +43,8 @@ parent's children and the issue's `Components:` line show where the rest of that
|
|
|
43
43
|
|
|
44
44
|
## Load the full skill before you write
|
|
45
45
|
|
|
46
|
-
- **Before
|
|
47
|
-
section
|
|
46
|
+
- **Before your first design question on a change, and before you write or change a spec, read
|
|
47
|
+
`skill://dispatch`,** including its "Writing a spec" section: a design change is brainstormed in
|
|
48
|
+
its issue's spec, not in chat, even when the brainstorming skill says otherwise.
|
|
48
49
|
- Before any other write to Dispatch (an ask, a message, a comment, a document edit, a status
|
|
49
50
|
change, a claim), load `skill://dispatch` unless you already have in this session.
|
|
@@ -85,9 +85,10 @@ handoffs; do not narrate them into the spec or a `dispatch_message`. A blocker o
|
|
|
85
85
|
clear is a `dispatch_ask`.
|
|
86
86
|
|
|
87
87
|
The issue's primary document **is** the root specification. Extend it in place: a new version
|
|
88
|
-
that
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
that adds only the evidence each decision needs and what the human decides, each as a
|
|
89
|
+
[decision block](../dispatch/SKILL.md#decision-blocks). The decomposition and its waves, how each
|
|
90
|
+
outcome is proven, and the integration test are your own calls: they go in the child issues and
|
|
91
|
+
the planner's `.legion/plan.json`, not the root spec. Never post a second "spec" artifact beside
|
|
91
92
|
it (`dispatch_artifact` with the primary document's name replaces the human's document; do not do
|
|
92
93
|
that).
|
|
93
94
|
The design gate runs only when the "Design gate policy" line at the end of your system prompt
|
|
@@ -102,7 +103,7 @@ dispatch_doc_edit({ issue: "<root issue>", ... }) // extend the primary docume
|
|
|
102
103
|
// then settle its decision blocks (below)
|
|
103
104
|
result = dispatch_request_approval({
|
|
104
105
|
issue: "<root issue>", // the primary document by default
|
|
105
|
-
summary: "<what the
|
|
106
|
+
summary: "<what the human is approving>",
|
|
106
107
|
})
|
|
107
108
|
legion({
|
|
108
109
|
op: "register_gate",
|
|
@@ -115,8 +116,10 @@ legion({
|
|
|
115
116
|
The decision blocks come first: settle every one as
|
|
116
117
|
[Approval of a spec](../dispatch/SKILL.md#approval-of-a-spec) says before you request approval;
|
|
117
118
|
`dispatch_request_approval` refuses while one is open. Each answer reaches you, since you follow
|
|
118
|
-
every ask you open.
|
|
119
|
-
|
|
119
|
+
every ask you open. An approval request carries nothing new: request it only once the human has
|
|
120
|
+
agreed to every point in the spec, so a point they have not agreed to gets its own decision block
|
|
121
|
+
first, or comes out of the spec. `summary` says in one to three sentences what the human is
|
|
122
|
+
approving and nothing else: no commentary and no open question.
|
|
120
123
|
|
|
121
124
|
`dispatch_request_approval` opens a system question on the document with the fixed options
|
|
122
125
|
`Approve` and `Request changes`; a human answers it from the Inbox or approves from the
|
|
@@ -127,26 +130,29 @@ spec.md (document id <UUID>) at version <N> (ask <id>)", followed by the questio
|
|
|
127
130
|
Inbox shows, and its `details.artifact` / `details.version` carry the same two values. The
|
|
128
131
|
document id is never the slug or file name you passed in (`spec`, `spec.md`): the daemon
|
|
129
132
|
recognizes the document's approval events by that id, and both the `legion` tool and the daemon
|
|
130
|
-
refuse a value that is not a UUID. Calling `dispatch_request_approval` again
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
refuse a value that is not a UUID. Calling `dispatch_request_approval` again while that request
|
|
134
|
+
waits on the human, with the same `summary`, changes nothing and returns it (its text says
|
|
135
|
+
it "already waits on the human"), so it is safe to repeat; a different `summary` is refused then.
|
|
136
|
+
A newer version of the spec moves the open request to that version and leaves it waiting on you,
|
|
137
|
+
with no wake when the edit was yours: once the human has agreed to every point in it, call again
|
|
138
|
+
to hand the same request back at the latest version. If its text instead reads "spec.md (document
|
|
139
|
+
id <UUID>) is already approved at version <N>" — a human approved from the document header before
|
|
140
|
+
you asked — still call `register_gate` with that id and version: the daemon reads the approval
|
|
141
|
+
from Dispatch as it registers, opens the gate, and delivers `design-approved` at once. The same
|
|
142
|
+
read covers a human who answers the question between your `dispatch_request_approval` and
|
|
143
|
+
`register_gate` calls, so an approval is never lost to timing; you never approve anything
|
|
144
|
+
yourself.
|
|
139
145
|
|
|
140
146
|
Then park. Do not release a wave or spawn a Legion role until a later delivered wake shows
|
|
141
147
|
`design-approved` on the root. On `design-changes-requested`, revise the spec (a new version of
|
|
142
|
-
the primary document),
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
the primary document), request approval again as above (the answer closed the last request, so
|
|
149
|
+
this opens a new one), and stay parked. Approval is pinned to the spec version: editing the root
|
|
150
|
+
spec after approval closes the gate again with no wake (you made the edit, or the
|
|
151
|
+
`artifact.version` event on your issue tells you). Request approval again as above, and release
|
|
152
|
+
no new wave and spawn no new role until the next `design-approved` arrives — work already in
|
|
153
|
+
flight continues. Later waves, re-scopes, and integration-failure children that leave the root
|
|
154
|
+
spec untouched need no new approval, and a child issue's spec is never gated: the root approval
|
|
155
|
+
covers the tree.
|
|
150
156
|
|
|
151
157
|
## 2. Children in flight
|
|
152
158
|
|
|
@@ -320,7 +326,7 @@ active phase worker.
|
|
|
320
326
|
| `children-complete` | Execute steps 3–4: parent integration verification; failures become a new child wave, success advances to review and retro. |
|
|
321
327
|
| `child-reopened` | Treat the completion edge as reset. Reassess the reopened child and return the tree to children-in-flight; do not continue an already-started end-game. |
|
|
322
328
|
| `design-approved` | Payload `{type:"design-approved"}`. A human approved the root spec document at its current version; the gate is open. Proceed to section 2. |
|
|
323
|
-
| `design-changes-requested` | Payload `{type:"design-changes-requested", version, reason, author?}`. A human asked for changes to the root spec at `version`, for `reason`. Revise the spec
|
|
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. |
|
|
324
330
|
| `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. A `reviewer` completion 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 completion with an `APPROVED` review proceeds to retro (step 5). A `reviewer` completion 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` completion 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` completion 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. |
|
|
325
331
|
| `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. |
|
|
326
332
|
| `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. |
|
|
@@ -330,7 +336,7 @@ active phase worker.
|
|
|
330
336
|
| `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. |
|
|
331
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-complete` 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. |
|
|
332
338
|
| `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. |
|
|
333
|
-
| `issue-comment` | Interpret the comment in the issue's design context.
|
|
339
|
+
| `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. |
|
|
334
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-complete` wake, then compare `childCounts[LEGION_ISSUE].open` with `legion state` and Dispatch before deciding the next action. |
|
|
335
341
|
| `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. |
|
|
336
342
|
| `reopened` | Reopen the root lifecycle: inspect the reason and current artifacts, reassess scope and children, and resume at the first applicable numbered step. |
|