@sjawhar/opencode-legion-envoy 3.19.8 → 4.0.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.
@@ -13894,6 +13894,8 @@ function dispatchToolSchema(spec, z2, opts) {
13894
13894
  }
13895
13895
  var ISSUE_REFERENCE = "An issue is a native KEY or external owner/repo#n reference. An external reference addresses an existing Dispatch issue, including one linked to that GitHub pull request; only dispatch_issue with external creates a native issue.";
13896
13896
  var OWNER_REFERENCE = "Exactly one of issue and project is required. An issue is a native KEY or external owner/repo#n reference; a project is a project key such as CORE and addresses an unlinked project document named by artifact.";
13897
+ var ASK_QUESTION_CONTRACT = "The question carries the problem the reader recognises and why it matters now, what constrains " + "the answer, and the recommendation with its reason. It asks how to solve the problem or which " + "outcome is wanted; never enumerate choices in the question.";
13898
+ var ASK_OPTIONS_CONTRACT = "Options carry the genuinely different approaches. Each option has a label, and its description " + "says what that approach costs.";
13897
13899
  function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false) {
13898
13900
  return {
13899
13901
  check: (value) => {
@@ -14041,18 +14043,31 @@ var dispatchToolSpecs = [
14041
14043
  },
14042
14044
  {
14043
14045
  name: "dispatch_ask",
14044
- example: { issue: "DSP-1", question: "Ship this?" },
14045
- description: "Open a durable, answerable decision on an issue or project document. Do not use it for a status update or discussion; " + "use dispatch_message instead. A to-do a human must complete is a question phrased as that to-do, with the options you want (for example Done / Can't). " + "Anything you are blocked on a human for, including a credential or grant to renew, an approval, or a decision, is an ask, never a message. " + "Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} ` + `characters and has at most 8 options. ${OWNER_REFERENCE}`,
14046
+ example: {
14047
+ issue: "DSP-1",
14048
+ question: "The release cannot pass its review gate because the revised plan is unreviewed. " + "How should we proceed? Recommendation: review the plan before release to keep the review gate.",
14049
+ options: [
14050
+ {
14051
+ label: "Review the revised plan",
14052
+ description: "Delays release for review but keeps the release gate."
14053
+ },
14054
+ {
14055
+ label: "Release without review",
14056
+ description: "Ships sooner but bypasses the review gate."
14057
+ }
14058
+ ]
14059
+ },
14060
+ description: "Open a durable, answerable decision on an issue or project document. Do not use it for a " + "status update or discussion; use dispatch_message instead. " + ASK_QUESTION_CONTRACT + " " + ASK_OPTIONS_CONTRACT + " For an action only a human can perform, state what it changes and risks as constraints. " + "Anything you are blocked on a human for, including a credential or grant to renew, an " + "approval, or a decision, is an ask, never a message. " + "Anchor a document question, thread reply_to/reply_to_ask, or cite a dispatch:// " + `reference \u2014 it must be answerable from its own text and anchor alone, never "see above". ` + `A quote anchor is pinned to its block. Question is at most ${ASK_QUESTION_MAX} characters ` + `and has at most 8 options. ${OWNER_REFERENCE}`,
14046
14061
  arguments: (z2) => ({
14047
14062
  issue: z2.string().describe(ISSUE_REFERENCE).optional(),
14048
14063
  project: z2.string().describe("Project key owning the document.").optional(),
14049
14064
  artifact: z2.string().describe("Project document artifact id, slug, or filename.").optional(),
14050
14065
  ref: z2.string().describe("Optional dispatch:// reference (issue, document, message, or ask); appended to the question and rendered as a link.").optional(),
14051
- question: z2.string({ max: ASK_QUESTION_MAX }).describe(`Decision question, at most ${ASK_QUESTION_MAX} characters.`),
14066
+ question: z2.string({ max: ASK_QUESTION_MAX }).describe(`${ASK_QUESTION_CONTRACT} At most ${ASK_QUESTION_MAX} characters.`),
14052
14067
  options: z2.array(z2.object({
14053
14068
  label: z2.string().describe("Selectable option label."),
14054
- description: z2.string().describe("Optional option context.").optional()
14055
- }), { max: 8 }).describe("Up to 8 choices, each an object { label, description? } (never a bare string).").optional(),
14069
+ description: z2.string().optional().describe("What this option costs.")
14070
+ }), { max: 8 }).optional().describe(`${ASK_OPTIONS_CONTRACT} Up to 8 objects { label, description? }.`),
14056
14071
  multiple: z2.boolean().describe("Whether multiple choices may be selected.").optional(),
14057
14072
  urgency: z2.enum(ASK_URGENCIES).describe("Optional decision urgency.").optional(),
14058
14073
  anchor: z2.object({
@@ -14067,16 +14082,26 @@ var dispatchToolSpecs = [
14067
14082
  name: "dispatch_edit_ask",
14068
14083
  example: {
14069
14084
  ask: "01234567-0000-4000-8000-000000000001",
14070
- question: "Ship the revised plan?"
14085
+ question: "The release cannot pass its review gate because the revised plan is unreviewed. " + "How should we proceed? Recommendation: review the plan before release to keep the review gate.",
14086
+ options: [
14087
+ {
14088
+ label: "Review the revised plan",
14089
+ description: "Delays release for review but keeps the release gate."
14090
+ },
14091
+ {
14092
+ label: "Release without review",
14093
+ description: "Ships sooner but bypasses the review gate."
14094
+ }
14095
+ ]
14071
14096
  },
14072
- description: "Edit an open question in place. Use it to correct or refine the same decision; retract the " + "old ask and open a new one when the decision itself changes. Previous text remains in the " + "event log. Only the asking session can edit it; answered or resolved asks cannot be edited. " + "An ask that lives as an `ask` block in a document is written in the document too, changing " + "only the fields you name - pass urgency alone and the question's wording, formatting, links " + "and comment anchors are untouched - so the edit writes a document version and closes a " + "spec's design gate until that version is " + "approved; text the block cannot carry back unchanged is refused, naming the field - an " + 'option label containing ": ", the separator between a label and its description, is one ' + "example.",
14097
+ description: "Edit an open question in place. Use it to correct or refine the same decision; retract the " + "old ask and open a new one when the decision itself changes. " + ASK_QUESTION_CONTRACT + " " + ASK_OPTIONS_CONTRACT + " Previous text remains in the event log. Only the asking session can edit it; answered or " + "resolved asks cannot be edited. An ask that lives as an `ask` block in a document is written " + "in the document too, changing only the fields you name - pass urgency alone and the " + "question's wording, formatting, links and comment anchors are untouched - so the edit " + "writes a document version and closes a spec's design gate until that version is approved; " + "text the block cannot carry back unchanged is refused, naming the field - an option label " + 'containing ": ", the separator between a label and its description, is one example.',
14073
14098
  arguments: (z2) => ({
14074
14099
  ask: z2.string().describe("Ask id (uuid); an 8+ hex prefix unique among this session's own open asks works too."),
14075
- question: z2.string({ max: ASK_QUESTION_MAX }).describe(`Replacement decision question, at most ${ASK_QUESTION_MAX} characters.`).optional(),
14100
+ question: z2.string({ max: ASK_QUESTION_MAX }).optional().describe(`${ASK_QUESTION_CONTRACT} Replaces the ask's question; at most ${ASK_QUESTION_MAX} characters.`),
14076
14101
  options: z2.array(z2.object({
14077
14102
  label: z2.string().describe("Selectable option label."),
14078
- description: z2.string().describe("Optional option context.").optional()
14079
- }), { max: 8 }).describe("Replacement choices, at most 8.").optional(),
14103
+ description: z2.string().optional().describe("What this option costs.")
14104
+ }), { max: 8 }).optional().describe(`${ASK_OPTIONS_CONTRACT} Replaces the ask's options; up to 8 objects { label, description? }.`),
14080
14105
  multiple: z2.boolean().describe("Whether multiple choices may be selected.").optional(),
14081
14106
  urgency: z2.enum(ASK_URGENCIES).describe("Replacement decision urgency.").optional()
14082
14107
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "3.19.8",
3
+ "version": "4.0.0",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
@@ -49,9 +49,11 @@ not share this session's vocabulary, and is often on a phone. Write for that per
49
49
  - Expand every identifier the first time it appears: an issue key gets its title, a PR number its
50
50
  title, a file what it is for, a session id who it is. Link a URL rather than pasting a bare id.
51
51
  - A question lives in the spec or discussion it came from, placed as
52
- [Decision blocks](#decision-blocks) says, never as a compressed standalone ask. Give the reader
53
- the options, what each costs, and your recommendation with its reason; do not prescribe yourself
54
- a form.
52
+ [Decision blocks](#decision-blocks) says, never as a compressed standalone ask. The question
53
+ carries the problem the reader recognises and why it matters now, what constrains the answer, and
54
+ the recommendation with its reason. It asks how to solve the problem or which outcome is wanted;
55
+ never enumerate choices in the question. The options carry the genuinely different approaches.
56
+ Each option has a label, and its description says what that approach costs.
55
57
  - Describe a change by what its reader stands to lose, not by what the system does. The
56
58
  engineering sentence names the change; the reader's sentence names who can do what today, what
57
59
  they will not be able to do after it, what still works, and what you cannot tell. It is a
@@ -84,7 +86,7 @@ a new version that keeps the human's own text, never a second "spec" artifact be
84
86
  versions keep the history.
85
87
  - **No placeholders.** No TBD, TODO, or hedging ("might", "could consider"): an open item is a
86
88
  decision block, a technical decision your lane makes and records in the text, or, for a
87
- contract between two lanes, a question for the platform PO (see
89
+ contract between two lanes, a question you settle with the other lane over Envoy (see
88
90
  [Before you ask](#before-you-ask) under Asking).
89
91
  - **No progress.** The spec records the design and its decisions, never status, timestamps, an
90
92
  "Update HH:MMZ" section, a pull-request list, or handoff notes. Progress is not a Dispatch
@@ -208,15 +210,15 @@ Every `dispatch_ask` passes four gates first:
208
210
  1. **Does it need his authority, taste, or risk appetite?** This is the bar for a decision
209
211
  written as an `:::ask` block in context ([Decision blocks](#decision-blocks)). Technical
210
212
  decisions inside your outcome do not: schema shapes, table layouts, field names, and migration
211
- internals are your lane's to decide and record in the spec. A contract between two lanes still
212
- goes to the platform PO over Envoy, and you open no ask for it. A halt condition (a change to
213
- IAM, deletion or exposure of production data, anything that reaches a customer) passes this
214
- gate: it is your own `dispatch_ask` to Sami on your own issue, never routed through the
215
- platform PO.
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.
216
217
  2. **Is there genuine uncertainty, and have you measured what you can?** If there is none, it is
217
218
  a plan you execute. The one legitimate ask without uncertainty is permission for an action
218
- only a human can authorise — a production write, an external send, a console action — and then
219
- the question is that action in one sentence, with options that name its outcomes (below).
219
+ only a human can authorise — a production write, an external send, a console action. Start it
220
+ with the problem and why the action is needed, then state what the action changes and risks;
221
+ its options name the outcomes.
220
222
  Measure before you write: how many are affected, whether anything reaches the path, what the
221
223
  current state already is. The measurement decides whether a human is needed at all, and when
222
224
  one is, it turns a research request he cannot answer into a decision he can. Put the
@@ -247,8 +249,8 @@ Every `dispatch_ask` passes four gates first:
247
249
  (further down) lists that the delivery waits on — including a conflict between what he asked
248
250
  for and another of his rules, which this gate would otherwise bury as settled.
249
251
 
250
- The platform PO audits open asks. One that fails a gate — or that points at another message in
251
- prose instead of carrying its content (below) — is retracted, with the PO's answer as the record.
252
+ Nobody audits or retracts another session's asks: passing every gate, and carrying the content
253
+ instead of pointing at another message in prose (below), is the asking session's own check.
252
254
 
253
255
  Open a decision with:
254
256
  ```ts
@@ -268,13 +270,11 @@ It returns `details` `{ issue, ask, follows: { ask } }` for an issue or `{ proje
268
270
 
269
271
  References belong in the question text; `ref` is sugar that appends its `dispatch://` value to the question as a rendered link.
270
272
 
271
- An ask is read on a phone by someone who has not read the code. Open with one or two plain
272
- sentences: what needs deciding and why it matters now. Each option is a button with a label and
273
- one sentence saying what happens if it is chosen; never enumerate choices in prose. Put the
274
- recommendation and its reason last, in `question`. Never put file paths, line numbers, sequence
275
- numbers, document versions, or role tokens in the question; if the human needs that detail, anchor
276
- the ask to the document passage instead. Apply the phone test from "Writing for the human" before
277
- posting. Anchor a document question with `anchor: { artifact, quote, occurrence? }`; `occurrence`
273
+ An ask is read on a phone by someone who has not read the code. Write its question and options as
274
+ [Writing for the human](#writing-for-the-human) says, and apply its phone test before posting.
275
+ Never put file paths, line numbers, sequence numbers, document versions, or role tokens in the
276
+ question; if the human needs that detail, anchor the ask to the document passage instead. Anchor a
277
+ document question with `anchor: { artifact, quote, occurrence? }`; `occurrence`
278
278
  is zero-based and selects a repeated quote. A quote anchor is pinned to its lowest complete
279
279
  containing block while retaining its quote as display text, so rewording the passage keeps it
280
280
  attached; a quote spanning top-level blocks, and existing anchors without a block, stay readable
@@ -289,11 +289,11 @@ message above", or "as attached".
289
289
  issue's comments. The rules:
290
290
 
291
291
  - An ask that names another message in prose — "my comment above", "the procedure I posted",
292
- "see the earlier message" — is retracted by the PO as failing the gates. Put the content IN the
293
- ask. If it does not fit the 800-character budget, the step is too big: split the step, never
294
- point elsewhere. The only pointers an ask may carry are a `dispatch://` reference or a document
295
- `anchor`, and they cite — the ask still says in one line what the reader will find there and can
296
- be answered without following them.
292
+ "see the earlier message" — fails the gates. Put the content IN the ask. If it does not fit the
293
+ 800-character budget, the step is too big: split the step, never point elsewhere. The only
294
+ pointers an ask may carry are a `dispatch://` reference or a document `anchor`, and they cite —
295
+ the ask still says in one line what the reader will find there and can be answered without
296
+ following them.
297
297
  - Expand every term the reader has not used first. A product name, an internal setting, an
298
298
  acronym, a value you coined this session — write what it is in the ask, in his words.
299
299
  - A runbook the human must execute is one ask per step, each self-contained: what to do, where,
@@ -308,19 +308,21 @@ they must read to decide belongs in the spec in the first place — see [Artifac
308
308
 
309
309
  ### When you need a human
310
310
 
311
- Before saying you are waiting for human input, call `dispatch_open_asks`. With no arguments it lists this session's active asks across open issues and project documents, including whether the human or agent owes the next reply. With `dispatch_open_asks({ project })` it lists every open ask in that project — on its issues and on its documents, whoever authored them — which is how you audit what a whole project is waiting on rather than just your own asks.
311
+ Before saying you are waiting for human input, call `dispatch_open_asks`. With no arguments it lists this session's active asks across open issues and project documents, including whether the human or agent owes the next reply. With `dispatch_open_asks({ project })` it lists every open ask in that project — on its issues and on its documents, whoever authored them — which is how you see what a whole project is waiting on rather than just your own asks.
312
312
 
313
- **Unsettled product shape needs a decision before implementation.** When a page, navigation entry, table key, customer-scoping rule, or persisted sidecar would set product shape that Sami has not already settled, send a one-line ask before the first implementation commit. A lane's schema decision or a platform-PO contract ruling does not settle product shape. This does not turn a user-specified decision or routine implementation into an approval request. A control or behaviour the human asked for in words is settled by those words, together with every choice inside it that his words do not make (where it sits, its defaults, its options): build it without an ask, as gate 4 of [Before you ask](#before-you-ask) says. This rule covers only product shape outside what he asked for, and its ask comes before the commit that sets that shape.
313
+ **Unsettled product shape needs a decision before implementation.** When a page, navigation entry, table key, customer-scoping rule, or persisted sidecar would set product shape that Sami has not already settled, send a one-line ask before the first implementation commit. A lane's schema decision or a contract two lanes agree does not settle product shape. This does not turn a user-specified decision or routine implementation into an approval request. A control or behaviour the human asked for in words is settled by those words, together with every choice inside it that his words do not make (where it sits, its defaults, its options): build it without an ask, as gate 4 of [Before you ask](#before-you-ask) says. This rule covers only product shape outside what he asked for, and its ask comes before the commit that sets that shape.
314
314
 
315
315
  **Anything you are blocked on a human for is an open ask.** An agent waits on a human only through
316
316
  an open ask. An approval, a credential or grant to renew, a setting only they can change, a review
317
317
  click, a decision, or a conflict between two of their own rules: open a `dispatch_ask` the moment
318
- you know, the action as the question. Never write it
319
- into a spec, a comment reply, a message, or a pull-request body: nothing in those paths reaches
320
- the human's Inbox, and a human who is not reading your document does not know they are the
321
- blocker. Before asking, try to remove the step: a value already on the machine, a permission you
322
- already hold, an API that replaces the click. One ask per item, `urgency: "high"` when work is
323
- stopped on it; while it is open, keep working on everything that is not.
318
+ you know. Start with the problem and why it matters, then the constraints, options and their
319
+ costs, and your recommendation. For an action only the human can perform, state what it changes
320
+ and risks; never make the action itself the question. Never write it into a spec, a comment reply,
321
+ a message, or a pull-request body: nothing in those paths reaches the human's Inbox, and a human
322
+ who is not reading your document does not know they are the blocker. Before asking, try to remove
323
+ the step: a value already on the machine, a permission you already hold, an API that replaces the
324
+ click. One ask per item, `urgency: "high"` when work is stopped on it; while it is open, keep
325
+ working on everything that is not.
324
326
 
325
327
  Once an ask is open (who answers it, handing a human a to-do, editing, retracting or resolving it,
326
328
  answering a clarification, whose turn a reply gives), see
@@ -15,14 +15,21 @@ It returns `details` `{ session, owner, service }`: `owner` is the lowercase log
15
15
 
16
16
  ## Handing a human a to-do, editing, resolving, and replying
17
17
 
18
- A to-do handed to a human is an ordinary question: phrase the to-do as the question and give it
19
- the options that name its outcomes, in the human's words - there is no fixed vocabulary and the
20
- server treats no label specially. If an outcome needs a reason, say so in that option's
21
- description, and the human's free-text answer carries it:
18
+ A to-do handed to a human still follows the question and options contract that `skill://dispatch`'s
19
+ "Writing for the human" states. For a human-only action, what it changes and risks constrains the
20
+ answer. There is no fixed vocabulary and the server treats no label specially.
21
+ If an outcome needs a reason, say so in that option's description, and the human's free-text answer
22
+ carries it:
22
23
  ```ts
23
- dispatch_ask({ issue: "DSP-42",
24
- question: "Run the production deploy for #19125?",
25
- options: [{ label: "Deployed" }, { label: "Blocked", description: "Say what is missing." }] })
24
+ dispatch_ask({
25
+ issue: "DSP-42",
26
+ question:
27
+ "The verified fix remains unavailable in production because deployment is pending. Deploying changes production and could expose a deployment problem. How should we proceed? Recommendation: deploy the verified fix now because it restores the intended behavior.",
28
+ options: [
29
+ { label: "Deploy the verified fix", description: "Restores the fix but can expose a deployment problem." },
30
+ { label: "Hold the deploy", description: "Avoids a production change now but leaves production without the fix." },
31
+ ],
32
+ })
26
33
  ```
27
34
 
28
35
  Correct or refine an open ask in place instead of opening a second question:
@@ -8,22 +8,21 @@ message that should not be sent, and a draft placed where the human reads it.
8
8
  Before — a wall of text hides the decision and makes the choices unclickable:
9
9
 
10
10
  ```text
11
- We need to settle the release gate because the deploy branch has the migration and the
12
- dashboard changes, I checked the staging result and it is fine except the release notes are
13
- not reviewed, so should we ship today, wait for docs, or cut the dashboard from this release?
14
- I think waiting is safest but the customer demo is tomorrow and the list above is probably stale.
11
+ The deploy branch has the migration and dashboard changes. Staging is fine, but release notes are
12
+ not reviewed before tomorrow's customer demo. Review the notes, ship without them, or cut the
13
+ dashboard from the release. Waiting is safest, but the list above may be stale.
15
14
  ```
16
15
 
17
- After — anchor the decision and make each option a button:
16
+ After — state the problem and make each genuinely different option a button:
18
17
 
19
18
  ```ts
20
19
  dispatch_ask({
21
20
  issue: "LEGION-815",
22
21
  question:
23
- "Choose the release gate. Recommendation: ship after release-note review, since the tested deployment is otherwise ready.",
22
+ "Release notes are unreviewed, and tomorrow's customer demo means the release cannot wait for a later review. How should we proceed? Recommendation: review the notes, then ship, to keep the release complete and reviewed.",
24
23
  options: [
25
- { label: "Review notes, then ship", description: "Keeps the release intact and reviewed." },
26
- { label: "Ship now", description: "Meets the demo deadline; release notes follow later." },
24
+ { label: "Review notes, then ship", description: "Delays release for review but keeps the release complete and reviewed." },
25
+ { label: "Ship now", description: "Meets the demo deadline but leaves the release notes unreviewed." },
27
26
  ],
28
27
  urgency: "high",
29
28
  anchor: { artifact: "spec", quote: "Release requires reviewed operator instructions before deployment." },
@@ -54,11 +53,19 @@ dispatch_artifact({ issue: "OPS-52", name: "cu-update-2026-09-15.md", content: "
54
53
  dispatch_doc_edit({ issue: "OPS-52", artifact: "spec", ops: [
55
54
  { op: "insert", after: "## Context", markdown: "## Draft (artifact cu-update-2026-09-15.md)" },
56
55
  ]})
57
- dispatch_ask({ issue: "OPS-52", question: "Send the customer update as drafted?", options: [...] })
56
+ dispatch_ask({
57
+ issue: "OPS-52",
58
+ question:
59
+ "Customers need an update today, but the draft is only in a separate file, so the reader cannot review it in context. How should we proceed? Recommendation: put the draft in the spec before sending it.",
60
+ options: [
61
+ { label: "Put the draft in the spec", description: "Adds a spec edit before sending but lets the reader review it in context." },
62
+ { label: "Keep the separate file", description: "Saves the spec edit but leaves the reader to find the draft." },
63
+ ],
64
+ })
58
65
  ```
59
66
 
60
67
  After — the draft is a section of the spec, and the ask anchors there. If it really must be a
61
- file (something to send as-is), the spec and the ask both link the slug from the upload result:
68
+ file, the spec and the ask both link the slug from the upload result:
62
69
 
63
70
  ```ts
64
71
  dispatch_doc_edit({ issue: "OPS-52", artifact: "spec", ops: [
@@ -66,17 +73,25 @@ dispatch_doc_edit({ issue: "OPS-52", artifact: "spec", ops: [
66
73
  ]})
67
74
  dispatch_ask({
68
75
  issue: "OPS-52",
69
- question: "Send the customer update as drafted?",
70
- options: [...],
76
+ question:
77
+ "Customers need an update today, and the reviewed text is ready in the spec. Sending it cannot be recalled. How should we proceed? Recommendation: send the reviewed update.",
78
+ options: [
79
+ { label: "Send the reviewed update", description: "Delivers the update today but makes its text external." },
80
+ { label: "Hold the update", description: "Avoids sending now but leaves customers without the update." },
81
+ ],
71
82
  anchor: { artifact: "spec", quote: "Hi team," },
72
83
  })
73
84
  // or, for a real file — the spec links it where the reader needs it, and so does the ask:
74
85
  dispatch_doc_edit({ issue: "OPS-52", artifact: "spec", ops: [
75
- { op: "insert", after: "## Context", markdown: "## Draft\n\nThe update to send as-is: dispatch://OPS-52/artifact/cu-update-2026-09-15-md" },
86
+ { op: "insert", after: "## Context", markdown: "## Draft\n\nThe update to send: dispatch://OPS-52/artifact/cu-update-2026-09-15-md" },
76
87
  ]})
77
88
  dispatch_ask({
78
89
  issue: "OPS-52",
79
- question: "Send this customer update as-is? dispatch://OPS-52/artifact/cu-update-2026-09-15-md",
80
- options: [...],
90
+ question:
91
+ "Customers need an update today, and its reviewed text is linked from the spec. Sending it cannot be recalled. How should we proceed? Recommendation: send the reviewed update.",
92
+ options: [
93
+ { label: "Send the reviewed update", description: "Delivers the linked update today but makes its text external." },
94
+ { label: "Hold the update", description: "Avoids sending now but leaves customers without the update." },
95
+ ],
81
96
  })
82
97
  ```
@@ -262,8 +262,9 @@ Preserve this order exactly:
262
262
  task. It drives the changed path in production through the user's own access path and records
263
263
  what it saw on the pull request and on this issue. Close only after the implementer's production
264
264
  report exists. A defect it finds is a corrective child issue of this tree, not a note on a
265
- closed one; a deploy the implementer cannot perform is its `dispatch_ask` naming that deploy,
266
- with options for its outcomes, and the issue waits for it.
265
+ closed one; if the implementer cannot perform the deploy, it opens a `dispatch_ask` that starts
266
+ with the production gap and why it matters, then names the required step, its risk, and
267
+ outcome-named options. The issue waits for that answer.
267
268
 
268
269
  What returns the tree to review: a changed diff — a commit above the approved head that
269
270
  touches anything outside `docs/solutions/`, or a conflict-resolution merge whose fingerprint
@@ -69,9 +69,8 @@ their own machine, and you are that foreground OMP session. The command fetched
69
69
  secret from the daemon with the operator's token, wrote it to a 0600 file under `LEGION_STATE_DIR`
70
70
  (`~/.local/state/legion/<project>-controller` by default) beside the `gh` shim and the `legion`
71
71
  launcher, and started you with `LEGION_CONTROLLER=1` and the same environment a tmux controller pane
72
- carries, so nothing changes in how you handle wakes. Under the TypeScript daemon the extension
73
- claims the role and calls `/controller/ready` exactly as under tmux; under the Go daemon
74
- (`LEGION_DAEMON_API=go` in your environment) it registers on `/legion/v1/claims/register` with the
72
+ carries, so nothing changes in how you handle wakes. The extension registers on
73
+ `/legion/v1/claims/register` with the
75
74
  secret, claims the role, then subscribes to `notifications.legion.<project>.controller`, where the
76
75
  Go daemon publishes the rows marked from the Go daemon in the wake routing table. The daemon records
77
76
  you as `controllerLocator: {runtime, external: true, sessionId, registeredAt}`, `runtime` being the
@@ -96,7 +96,8 @@ completion leaves the issue in reviewing until you finish.
96
96
  carrying the Legion footer, and a `dispatch_message` on the issue — the reviewer and merger
97
97
  read GitHub, the architect reads the issue. When the deploy that carries the merge has not
98
98
  happened (a shared profile still holding the previous plugin release, a daemon still running
99
- the previous commit, a slot nobody has run), open a `dispatch_ask` naming the exact install or
100
- restart step, with options for its outcomes, keep the `Production:` line at `pending <what is
101
- missing>`, and complete the check once the human answers that it is done. Never record a
102
- staging pass as the production check, and never let the architect sign off on a `pending` line.
99
+ the previous commit, a slot nobody has run), open a `dispatch_ask` that starts with the
100
+ production gap and why it matters, then names the required install or restart step, its risk,
101
+ and outcome-named options. Keep the `Production:` line at `pending <what is missing>`, and
102
+ complete the check once the human answers. Never record a staging pass as the production check,
103
+ and never let the architect sign off on a `pending` line.