@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 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 retract this request.`,
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 and request approval again. If they waive it, close the block with dispatch_resolve_ask (kind resolved, their words as the reason), write their decision into the text with dispatch_doc_edit, and request approval again."
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; an edit after approval makes it stale, so request again for the new version.`,
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 retract this request.`,
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 and request approval again. If they waive it, close the block with dispatch_resolve_ask (kind resolved, their words as the reason), write their decision into the text with dispatch_doc_edit, and request approval again."
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; an edit after approval makes it stale, so request again for the new version.`,
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.4",
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
- For a major design change the design conversation happens in Dispatch, in the spec: write it
38
- early, while it is still a draft with real alternatives, and let it grow as the human answers
39
- ([Writing a spec](#writing-a-spec)). A finished spec dropped after a chat-only design is not that
40
- conversation.
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) says.
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. A point you inferred says so, with the reasoning.
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 and records in the text, or, for a
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
- carrying the options, what each costs, and your recommendation. Never gather decisions into a
105
- list, at the top, at the bottom or in an "open questions" section, and never ask one as a
106
- standalone `dispatch_ask` that points at the spec.
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 and record in the spec. A contract between two lanes is
214
- settled by those two lanes over Envoy, and you open no ask for it. A halt condition (a change
215
- to IAM, deletion or exposure of production data, anything that reaches a customer) passes this
216
- gate: it is your own `dispatch_ask` to Sami on your own issue.
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. Answering a decision block writes a new version, so request approval only when
367
- both hold: every decision block is settled, which means answered and folded into the text, or
368
- waived as the next paragraph says; and it proposes something the human has not already settled.
369
- A Legion root spec under an armed design gate always goes to approval once its blocks are settled
370
- (`skill://legion-architect`).
371
-
372
- When both hold, request it in the pass that finishes the spec: a design waiting with nothing in
373
- the human's Inbox waits on nobody. A choice you can make yourself is not a decision block
374
- ([Before you ask](#before-you-ask), gate 1): write your call and its reason into the design and
375
- name it in `summary`; a human who disagrees answers `Request changes`. When a human asks for
376
- approval while a block is open, do not request it and do not hold it silently: name each open
377
- block and ask them to answer it or waive it. For a waiver, close the block with `dispatch_resolve_ask`
378
- (`kind: "resolved"`, their words as `reason`), then write their decision into the text in their
379
- words and request. `dispatch_request_approval` refuses while any block is open.
380
-
381
- `summary` names the proposals in this version the human has not agreed to, in one to three
382
- sentences, and never an open question: the Inbox shows it after "Approve spec.md (version N)?".
383
- Never write "Approve" options into an ordinary `dispatch_ask`; only humans approve. The call, its
384
- result and its answer: [Approval requests](skill://dispatch/references/documents.md#approval-requests).
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
- Should we ship the migration?
47
-
48
- - Ship: Release the verified change.
49
- - Hold: Wait for another review.
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) and then request. A block written in the last few seconds counts as open before Dispatch has
160
- opened its ask. A repeat at the same version returns the open request unchanged. A new version
161
- retracts an open request for an older one, and its `ask.resolved` reaches you: request again for
162
- the new version once its blocks are settled. The answer reaches you as `artifact.approved` or
163
- `artifact.changes_requested` with the pinned `version`; `changes_requested` carries the reason,
164
- which is your next piece of work. `dispatch_read` and `dispatch_doc_read` show the document's
165
- approval state; `stale` means it was approved and then edited.
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 you write or change a spec, read `skill://dispatch`,** including its "Writing a spec"
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 keeps the human's own text and grows the design (the adoption or decomposition and its
89
- waves, how each outcome is proven, and the integration test), each open question a
90
- [decision block](../dispatch/SKILL.md#decision-blocks). Never post a second "spec" artifact beside
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 tree will do that the human hasn't already agreed to>",
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. `summary` says in one to three sentences what the tree will do that the human
119
- hasn't already agreed to.
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 at the version an
131
- open request names returns that request unchanged, so it is safe to repeat; once the document has
132
- a newer version, that request is retracted (its `ask.resolved` reaches you) and the call opens a
133
- new one at the latest version. If its text instead reads "spec.md (document id <UUID>) is already
134
- approved at version <N>" — a human approved from the document header before you asked — still
135
- call `register_gate` with that id and version: the daemon reads the approval from Dispatch as it
136
- registers, opens the gate, and delivers `design-approved` at once. The same read covers a human
137
- who answers the question between your `dispatch_request_approval` and `register_gate` calls, so
138
- an approval is never lost to timing; you never approve anything yourself.
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), call `dispatch_request_approval` again with a `summary` of what the
143
- revision proposes — it opens the request at the new version — and stay parked. Approval is
144
- pinned to the spec version: editing the root spec after approval closes the gate again with no
145
- wake (you made the edit, or the `artifact.version` event on your issue tells you), so call
146
- `dispatch_request_approval` again with a `summary`, and release no new wave and spawn no new role
147
- until the next `design-approved` arrives — work already in flight continues. Later waves,
148
- re-scopes, and integration-failure children that leave the root spec untouched need no new
149
- approval, and a child issue's spec is never gated: the root approval covers the tree.
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, call `dispatch_request_approval` again with a `summary` of what the revision proposes, and stay parked; the gate is closed. |
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. Answer it, adjust the plan, or relay it via `envoy_publish` to the responsible worker's role token; scope and product decisions remain with you. |
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. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "6.0.4",
3
+ "version": "6.1.0",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [