@sjawhar/pi-envoy 0.0.0 → 8.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.
@@ -0,0 +1,498 @@
1
+ ---
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; before asking a design question or brainstorming a change; and when asking the human a question, updating the spec, commenting on a document, attaching an artifact, or calling a dispatch_* tool."
4
+ ---
5
+
6
+ # Dispatch
7
+
8
+ Dispatch is your issue's or project document's living spec, asks, comments, and artifacts — a high-signal record for the humans who
9
+ decide, never a log of your work. The transcript is your scratch pad; progress and status stay there. Anything meant for a human
10
+ goes through a `dispatch_*` tool.
11
+
12
+ The server enforces high signal: an ask question is at most 800 characters with at most eight options; comment and message bodies are at
13
+ most 2,000 characters; a markdown document is at most 1 MiB and any other file at most 25 MiB. It refuses over-limit input with the number
14
+ to trim (`question is 50 characters over the 800-character limit (850/800)`); it never truncates it. A tool call with several problems is
15
+ refused once, every problem listed (`<tool> was not called: N problems`), so one corrected call lands. GitHub threads and markers no
16
+ longer exist.
17
+
18
+ ## Where the detail lives
19
+
20
+ This file is the workflow. The detail for a step is in a reference file: open it when you reach
21
+ that step, not before. On a host with no `skill://` scheme (Claude Code, OpenCode), a link's path
22
+ after `skill://dispatch/` is relative to this skill's base directory.
23
+
24
+ | When you are about to | Read |
25
+ | --- | --- |
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
+ | 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
+ | 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
+ | 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
+ | catch up after a restart, trace what cites a node, or write a `dispatch://` reference | [Reading back](skill://dispatch/references/reading.md) |
31
+ | answer a BTW, Aside or Steer frame, or a message from the Agents page | [Targeted and direct messages](skill://dispatch/references/messages.md) |
32
+ | see a worked ask, a reply that names its mechanism, a message not to send, and where a draft goes | [Before and after](skill://dispatch/references/examples.md) |
33
+ | set up a token, or call a route the tools do not cover | [Authentication and the HTTP API](skill://dispatch/references/api.md) |
34
+
35
+ ## Design changes are brainstormed here
36
+
37
+ When a session has Dispatch, a design change needing the human's choices is brainstormed in its
38
+ issue's spec, not chat or a repository design document, and its plan is the issue's `plan.md`
39
+ document. `skill://dispatch-brainstorming` holds the process: read it before a spec's first
40
+ version, before each next turn, and before you write a plan.
41
+
42
+ ## Writing for the human
43
+
44
+ Every spec, ask, comment, message, and PR body is read by a person who has not read the code, does
45
+ not share this session's vocabulary, and is often on a phone. Write for that person.
46
+
47
+ - Plain English, full sentences, one idea per sentence. Never repo shorthand or nouns you coined:
48
+ not "fix 8c", "READY-target", "PR B", "spec@v3", "the pair", "the packet" — say what the thing is.
49
+ - A real name is not a coined noun: name a product, a tool and its operation
50
+ (`dispatch_request_approval`), an event type (`artifact.approved`), a route, a setting or a
51
+ status exactly, and say how it works in a clause ("the daemon opens the gate when Dispatch emits
52
+ `artifact.approved` for that document at that version"), never with a vague verb such as
53
+ "notice", "accept", "tell" or "pick up" ([before and after](skill://dispatch/references/examples.md#naming-the-mechanism)).
54
+ - Expand every identifier the first time it appears: an issue key gets its title, a PR number its
55
+ title, a file what it is for, a session id who it is. Link a URL rather than pasting a bare id.
56
+ - A question lives in the spec or discussion it came from, placed as
57
+ [Decision blocks](#decision-blocks) says, never as a compressed standalone ask. The question
58
+ carries the problem the reader recognises and why it matters now, what constrains the answer, and
59
+ the recommendation with its reason. It asks how to solve the problem or which outcome is wanted;
60
+ never enumerate choices in the question. The options carry the genuinely different approaches.
61
+ Each option has a label, and its description says what that approach costs.
62
+ - Describe a change first by what its reader stands to lose — who can do what today, what they
63
+ will not be able to do after it, what still works, and what you cannot tell — then by what the
64
+ system does, each mechanism named exactly. The reader's sentence is a different sentence, not a
65
+ shorter engineering one: shortening keeps the system as its subject. Test that the first sentence
66
+ has a human subject, even on a sentence you already simplified; a lead naming what a change does
67
+ inside a system leaves the reader nothing to act on. Where the judgment rule below applies, the
68
+ judgment leads and this rule shapes the sentence under it.
69
+ - Before posting, test it: could the human, reading only this text on their phone, know what they are being
70
+ told or asked? If not, rewrite it. Length is not the problem; density is.
71
+ - When an ask or message communicates a judgment, lead with that judgment in one sentence and put the mechanism underneath it. Do not make the reader ask a second time whether the result is a win. This shapes communication only when a judgment exists; it does not pre-decide an open question or remove its genuine options.
72
+ - When a Dispatch message states a root cause, include the reproducing command or test in that same message. Without it, label the diagnosis a hypothesis; a diagnosis still in progress may say so plainly. This boundary applies to causal claims, not to reporting that an investigation has started.
73
+
74
+ ## Writing a spec
75
+
76
+ A spec is the design conversation with the human, written down. It starts as the problem and the
77
+ evidence for it, in plain words: what goes wrong, for whom, and the counts or cases that show it.
78
+ It grows in place as the conversation goes. It is the issue's one primary document: extend it with
79
+ a new version that keeps the human's own text, never a second "spec" artifact beside it.
80
+
81
+ - **Each open question is a decision block**, shaped and placed as [Decision blocks](#decision-blocks)
82
+ says. Because it is an ask, it reaches the human's Inbox, and the answer lands next to its context.
83
+ - **A settled point records the person's own words and the date**, quoted, so no reader mistakes
84
+ it for your inference; an answer that is only a chosen option is recorded in the form
85
+ `<name> chose "Commit author" on the question below (<date>)`, naming them from `dispatch_whoami`
86
+ or the conversation, or "the person" when the token names no owner — never a name you were not
87
+ given. A point you inferred says so, with the reasoning — a security concern, a restriction, or
88
+ a cost nobody measured is exactly this, not a settled constraint; during a live brainstorming
89
+ conversation, `skill://dispatch-brainstorming` is stricter and keeps an inference out of the spec
90
+ until the human has agreed to it. One carried in from another document keeps its provenance: an
91
+ agent's inference there is marked one here, or stays out until the human raises it.
92
+ - **Sections follow the topic.** No heading is required and none has a fixed place; name each
93
+ section for what it discusses.
94
+ - **A changed point is rewritten, not appended to.** When an answer or a new fact changes the
95
+ design, rewrite the text it changes and fold the answered decision into it; the document's
96
+ versions keep the history.
97
+ - **No placeholders.** No TBD, TODO, or hedging ("might", "could consider"): an open item is a
98
+ decision block, a technical decision your lane makes where the work happens, outside the spec,
99
+ or, for a contract between two lanes, a question you settle with the other lane over Envoy (see
100
+ [Before you ask](#before-you-ask) under Asking).
101
+ - **No progress.** The spec records the design and its decisions, never status, timestamps, an
102
+ "Update HH:MMZ" section, a pull-request list, or handoff notes. Progress is not a Dispatch
103
+ object at all; it lives in your transcript and your pull request (see [Messages](#messages)).
104
+ - **No commentary.** The spec talks about the design, never about the spec or the conversation: no
105
+ sentence calls it a draft, a conversation, a version or a turn; says what a later version will
106
+ add; describes an earlier version or correction; or narrates the exchange that produced it.
107
+
108
+ Before a new version goes out, read it as the human will: no two passages conflict, each point has
109
+ one reading, and every decision block passes the phone test above. A worked example is
110
+ `dispatch://LEGION-386/artifact/spec@v5`: the problem and its counts, what the human settled in his
111
+ own words, a section for each part of the design, and its one open question as a decision block
112
+ after the section on models.
113
+
114
+ ## Decision blocks
115
+
116
+ A decision a human must make is an `:::ask` block at the end of the section that discusses it,
117
+ shaped as [Writing for the human](#writing-for-the-human) says for a question, with what
118
+ constrains the answer split into measured, known and unknown. It asks how to solve the problem,
119
+ never whether to apply a change already chosen. Never gather decisions into a list, at the top,
120
+ at the bottom or in an "open questions" section, and never ask one as a standalone `dispatch_ask`
121
+ that points at the spec.
122
+
123
+ The block is what reaches the human's Inbox. A question phrased as prose in the spec reaches
124
+ nobody. A spec with no ask blocks is fine only when the issue genuinely needs no human decision.
125
+ What `dispatch_issue` and `dispatch_artifact` answer about a spec's blocks, and how to check an edit
126
+ wrote one, is in [Typed blocks](skill://dispatch/references/documents.md#typed-blocks).
127
+
128
+ **Wrong:** a **Decisions needed** list at the top of the spec with three bullets.
129
+ **Right:** each decision an `:::ask{#slug}` block at the end of the design section that discusses
130
+ it, with 2–4 options, a recommendation, and surrounding prose that explains the trade-off.
131
+
132
+ A spec that already has the pile is repaired with `move`, not rewritten: `dispatch_doc_edit` with
133
+ `{ op: "move", block: "<block-uuid>", after: "<the last sentence of the section that discusses it>" }` relocates
134
+ the block and keeps its ask, its answer and its followers; the context paragraphs that were lifted
135
+ out of Design move the same way, and the emptied section is deleted. An ask block has two ids that
136
+ differ; [Editing a document](skill://dispatch/references/document-edits.md) says which.
137
+
138
+ See [Typed blocks](#typed-blocks) for the syntax and [Before you ask](#before-you-ask) under
139
+ [Asking](#asking) to decide whether the question is a real decision at all.
140
+
141
+ ## Your owner
142
+
143
+ Every session works on an issue or project document. Legion pre-fills `issue` from `LEGION_ISSUE`: use a native issue key such as
144
+ `LEGION-3`, an external `owner/repo#n` reference, or a bare positive number (resolved against the cwd repository). Otherwise pass
145
+ exactly one owner to every owner-scoped tool: `issue` for an issue, or `project` and `artifact` for an unlinked project document (see
146
+ [Reference forms](skill://dispatch/references/reading.md) for the resulting ref shape). An external issue reference addresses the existing Dispatch issue linked to
147
+ that GitHub issue or pull request. Only `dispatch_issue` with `external` creates a native issue; if no issue is linked, call
148
+ `dispatch_issue({ external: "owner/repo#n", project: "<project>", title: "<title>" })` before addressing it.
149
+
150
+ Create an issue for newly tracked work with:
151
+ ```ts
152
+ dispatch_issue({ project, title, parent?, external?, spec?, force?, labels?: string[], priority?: 0 | 1 | 2 | 3, assignee?: string })
153
+ ```
154
+ `labels` are optional initial labels: Dispatch trims them, preserves their case, and removes case-insensitive duplicates. `priority` is yours on creation too — see [Priority is yours to set](skill://dispatch/references/issues.md). Set `assignee` (the email of a person who has signed in to Dispatch) only when the human said who owns the work; otherwise the default above applies, so a child inherits its parent's assignee. It returns
155
+ `details` `{ issue }`; creating an issue does not subscribe you to it (see [Following](#following)). Use `dispatch_issue` only to create an issue; never use it to park a question. When `spec` is supplied,
156
+ follow [Writing a spec](#writing-a-spec).
157
+
158
+ ## Search first
159
+
160
+ `skill://dispatch-first`, which every session with Dispatch carries, says how to search before
161
+ you plan, start a design document, file an issue, ask, post a finding or start work, and what to
162
+ do with each hit. What it leaves out:
163
+ ```ts
164
+ dispatch_search({ query, project?, limit?, offset? })
165
+ ```
166
+ Websearch syntax applies: `"merge queue"`, `-daemon`, `OR`. Issues, documents, asks, comments, and messages rank separately; the
167
+ merged page orders each kind's best in turn — issue, document, ask, comment, message — and a bare issue key ranks first. Each
168
+ kind's list also merges a query embedding's meaning match with keyword hits (LEGION-549): a paraphrase with no shared word can
169
+ still match; without an embedder, it says so and ranks by keyword alone. A page holds `limit` hits (20 default, 50 max); the first
170
+ line gives the total (`showing 1-20 of 312`); see [Search paging](skill://dispatch/references/issues.md#search-paging) for more.
171
+ Queries over 1,000 characters are refused: use the few words `skill://dispatch-first` names, never a pasted passage. Issue hits
172
+ start with the issue key; document hits start with `dispatch://PROJECT/artifact/<slug>`, then the link. Cite the hit (`dispatch://KEY` or the doc ref) or say "no prior issue".
173
+
174
+ `dispatch_issue` refuses a title that near-duplicates an issue in the same project and returns the candidates (`POSSIBLE_DUPLICATE`). Read them; reference the existing issue, or repeat the call with `force: true` when it is genuinely new work.
175
+ The check compares title words only (shared stemmed terms), never meaning: "four tests that fail a
176
+ merge" pairs with "four CI gates that cannot fail a merge". So when you force past a candidate, give
177
+ the new issue a title that names what differs where you can, and open its spec with the
178
+ distinction from the named issue, citing it (`dispatch://KEY`), for whoever reads the next pairing.
179
+
180
+ ### Symptom versus cause
181
+
182
+ When a symptom and its cause sit on different issues, the work accrues to the cause's issue, and
183
+ the symptom's issue carries a pointer to it. Before posting a measurement or finding, search
184
+ Dispatch for the failing identity's or component's name, and post on the issue whose title names
185
+ the fix, not the one naming the symptom. A symptom issue gathering messages with no human response
186
+ is the tell.
187
+
188
+ ## Claim the issue before you work it
189
+
190
+ Before you start implementing an issue, claim it, and release the claim when you stop: finished,
191
+ handing over, or moving to something else. Closing the issue releases it for you.
192
+ ```ts
193
+ dispatch_claim({ issue: "LEGION-234" }) // I am implementing this
194
+ dispatch_claim({ issue: "LEGION-234", release: true }) // I have stopped; it is free
195
+ ```
196
+ A claim is intent to implement: reading, commenting, asking, or gating a pull request claims
197
+ nothing. `409 ISSUE_CLAIMED` means someone else holds the issue; never work it in parallel. What
198
+ each refusal means, and when a claim is yours to take, is in
199
+ [Working an issue](skill://dispatch/references/issues.md).
200
+
201
+ ## Issue status is yours to move
202
+
203
+ Claiming and moving the status are two actions, and you do both: when you start,
204
+ `dispatch_claim({ issue })` and `dispatch_issue_update({ issue, status: "in_progress" })`. Outside
205
+ Legion the session doing the work moves its issue and the children it owns: `in_progress` when
206
+ implementation starts, `testing` while the change is proven on a production-like surface,
207
+ `needs_review` when its pull request waits on the merge queue, and `done`, with a `reason`, once
208
+ the change has been driven in production. Never write the status of an issue that carries the
209
+ `legion` label, or of any issue under one: the Legion daemon writes those. Closing, clearing a
210
+ field, and choosing your next issue are in [Working an issue](skill://dispatch/references/issues.md).
211
+
212
+ ## Asking
213
+
214
+ Two kinds of question reach a human. A decision about the design a document records is a decision
215
+ block in that document ([Decision blocks](#decision-blocks)), whatever phase the work is in and
216
+ whether or not the document was approved: a block in an approved document makes the approval
217
+ stale, which is right. A Legion phase worker sends such a decision to its architect, which writes
218
+ the block (`skill://legion-worker`). A to-do or permission only a human can give, or a decision
219
+ with no document to live in, is a `dispatch_ask`. The gates below apply to both.
220
+
221
+ ### Before you ask
222
+
223
+ Every `dispatch_ask` passes four gates first:
224
+
225
+ 1. **Does it need his authority, taste, or risk appetite?** This is the bar for a decision
226
+ written as an `:::ask` block in context ([Decision blocks](#decision-blocks)). Technical
227
+ decisions inside your outcome do not: schema shapes, table layouts, field names, and migration
228
+ internals are your lane's to decide where the work happens, in the plan or the code, not in the
229
+ spec. A contract between two lanes is settled by those two lanes over Envoy, and you open no ask
230
+ for it. A halt condition (a change to IAM, deletion or exposure of production data, anything
231
+ that reaches a customer) passes this gate: it is your own `dispatch_ask` to the human on your own
232
+ issue.
233
+ 2. **Is there genuine uncertainty, and have you measured what you can?** If there is none, it is
234
+ a plan you execute. The one legitimate ask without uncertainty is permission for an action
235
+ only a human can authorise — a production write, an external send, a console action. Start it
236
+ with the problem and why the action is needed, then state what the action changes and risks;
237
+ its options name the outcomes.
238
+ Measure before you write: how many are affected, whether anything reaches the path, what the
239
+ current state already is. The measurement decides whether a human is needed at all, and when
240
+ one is, it turns a research request he cannot answer into a decision he can. Put the
241
+ measurement and the size of the affected population in the ask. When the measurement shows one
242
+ fix cannot repair most of that population and another fix can, drop the first, rather than
243
+ offering it cut down to the part it reaches. An option not to act, such as a permission ask's
244
+ Hold, is not a fix, and it stays.
245
+ Report what the measurement could **not** establish, with its own control: "I found no
246
+ evidence" and "there is no evidence to find" read alike and mean opposite things, and a
247
+ control that shares the query's blind spot proves neither. Before you say you are waiting on
248
+ him, run `dispatch_open_asks` (below) — and the test for a new ask is not whether you asked on
249
+ this issue before, but whether it asks him to re-report something he has already answered.
250
+ 3. **Can someone who has not read the code answer it on a phone?** Write it as
251
+ [Writing for the human](#writing-for-the-human) says — who can do what today and what changes
252
+ for them, then two options with what each costs and your recommendation — with no slice or
253
+ decision numbers, no coined nouns, and each real tool, event, route or setting named exactly with
254
+ what it does. If you cannot write it that way, you do not understand it well enough to ask.
255
+ 4. **Is it outside what he has already told you he wants?** If not, that want is settled, and so
256
+ is every choice inside it his words do not make: build it and ask nothing about it or beside it
257
+ until it is delivered, whatever the ask says about the build. Afterwards, ask only about a choice his own
258
+ message raises and leaves open, quoting his words and naming what you delivered. A cause you
259
+ diagnosed, or a behaviour next to the one he named, is not in his message: neither ask about
260
+ it nor build it — fixing the cause of the thing he named is delivering what he asked for, and
261
+ is not what this forbids; changing something else is. An ask that turns his complaint about
262
+ one control into a choice about another does not address what he asked, and changing that
263
+ other control is a change he never asked for. What is still an ask the moment you know it,
264
+ even before delivery, is anything "Anything you are blocked on a human for is visible in
265
+ Dispatch" (further down) lists that the delivery waits on — including a conflict between what
266
+ he asked for and another of his rules, which this gate would otherwise bury as settled.
267
+
268
+ Nobody audits or retracts another session's asks: passing every gate, and carrying the content
269
+ instead of pointing at another message in prose (below), is the asking session's own check.
270
+
271
+ Open a standalone ask with:
272
+ ```ts
273
+ dispatch_ask({
274
+ issue?,
275
+ project?,
276
+ artifact?,
277
+ ref?,
278
+ question,
279
+ options?: { label, description? }[],
280
+ multiple?,
281
+ urgency?,
282
+ anchor?: { artifact, quote, occurrence? },
283
+ })
284
+ ```
285
+ It returns `details` `{ issue, ask, follows: { ask } }` for an issue or `{ project, artifact, document, ask, follows: { ask } }` for a project document: you follow the ask you opened (see [Following](#following)).
286
+
287
+ References belong in the question text; `ref` is sugar that appends its `dispatch://` value to the question as a rendered link.
288
+
289
+ An ask is read on a phone by someone who has not read the code. Write its question and options as
290
+ [Writing for the human](#writing-for-the-human) says, and apply its phone test before posting.
291
+ Never lead a question with a file path, line number, sequence number, document version or role token: its own text explains the problem.
292
+ Evidence below it may cite one where the reader checks it, or anchor the ask to the passage with `anchor: { artifact, quote, occurrence? }`; `occurrence` is zero-based and selects a repeated quote.
293
+ A quote anchor is pinned to its lowest complete
294
+ containing block while retaining its quote as display text, so rewording the passage keeps it
295
+ attached; a quote spanning top-level blocks, and existing anchors without a block, stay readable
296
+ against their original document version if their quote disappears.
297
+
298
+ An ask must be answerable from its own text and its anchor alone. Anchor a to-do about a document
299
+ passage with `anchor`. Follow up on an ask or comment with `dispatch_comment`; cite anything else
300
+ with a `dispatch://` reference (see [References](#references)). Never write "see above", "the
301
+ message above", or "as attached".
302
+
303
+ **Pointing at another message is a defect, not a shortcut:** the ask view does not show the
304
+ issue's comments. The rules:
305
+
306
+ - An ask that names another message in prose — "my comment above", "the procedure I posted",
307
+ "see the earlier message" — fails the gates. Put the content IN the ask. If it does not fit the
308
+ 800-character budget, the step is too big: split the step, never point elsewhere. The only
309
+ pointers an ask may carry are a `dispatch://` reference or a document `anchor`, and they cite —
310
+ the ask still says in one line what the reader will find there and can be answered without
311
+ following them.
312
+ - A runbook the human must execute is one ask per step, each self-contained: what to do, where,
313
+ what result proves it, and options that name the step's outcomes. Each later step opens only
314
+ after the previous is answered and states that step's verified result in one line ("Step 1
315
+ done: the licence shows Cloud Identity Free on the admin console.") — never a pointer to the
316
+ earlier ask.
317
+
318
+ **A decision about an uploaded artifact links it.** If the human must read an artifact to answer,
319
+ the question carries `dispatch://KEY/artifact/<slug>` (or `ref`), never just its filename. Text
320
+ they must read to decide belongs in the spec in the first place — see [Artifacts](#artifacts).
321
+
322
+ ### When you need a human
323
+
324
+ 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.
325
+
326
+ **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 the human has not already settled, write a decision block in the document that records the work before the first implementation commit; in a Legion tree the architect writes it, and a phase worker sends the decision to its architect. A lane's schema decision or a contract two lanes agree does not settle product shape. This does not turn a user-specified decision or routine implementation into an approval request. A control or behaviour the human asked for in words is settled by those words, together with every choice inside it that his words do not make (where it sits, its defaults, …
327
+
328
+ **Anything you are blocked on a human for is visible in Dispatch.** An agent waits on a human only
329
+ through an open ask. A to-do, permission, credential or grant renewal, setting only they can
330
+ change, review click, or conflict between two of their own rules is a `dispatch_ask` the moment you
331
+ know. Start with the problem and why it matters, then the constraints, options and their costs, and
332
+ your recommendation. For an action only the human can perform, state what it changes and risks;
333
+ never make the action itself the question (a design decision is a decision block, as above). Never
334
+ write a human to-do only into a spec, a comment reply, a message, or a pull-request body: nothing
335
+ in those paths reaches the human's Inbox, and a human who is not reading the document does not
336
+ know they are the blocker. Before asking, try to remove the step: a value already on the machine, a
337
+ permission you already hold, an API that replaces the click. One ask per item, `urgency: "high"`
338
+ when work is stopped on it; while it is open, keep working on everything that is not.
339
+
340
+ Once an ask is open (who answers it, handing a human a to-do, editing, retracting or resolving it,
341
+ answering a clarification, whose turn a reply gives), see
342
+ [Asks after they open](skill://dispatch/references/asks.md).
343
+
344
+ ## Architecture components
345
+
346
+ Attach every architectural issue to the components it changes. Attach the root before decomposing it: children inherit the root's effective attachment unless they deliberately set their own, so one root attachment classifies the whole tree.
347
+
348
+ ```
349
+ dispatch_issue({ project: "CORE", title: "...", components: { mode: "explicit", ids: ["dispatch-server", "web"] } })
350
+ dispatch_issue_update({ issue: "CORE-12", components: { mode: "explicit", ids: ["web"] } }) // this issue's own set, replacing what it inherited
351
+ dispatch_issue_update({ issue: "CORE-13", components: { mode: "none", reason: "hiring, not code" } })
352
+ dispatch_issue_update({ issue: "CORE-14", components: { mode: "inherit" } }) // back to the parent chain's attachment
353
+ ```
354
+
355
+ No native Dispatch tool lists component ids. Read the configured model with `GET /api/v1/projects/{key}/architecture` and use each non-external `components[].id`; when its source repository is your checkout, those ids are the file names under `.dispatch/architecture/` (`web.md` → `web`). An id absent from the model, or an `external` component, is refused with `COMPONENTS_INPUT`.
356
+
357
+ Use `mode: "none"` with a concrete reason only when the work is genuinely non-architectural (for example hiring, process, or operations work). A closed issue can be classified without reopening. Change code and its architecture description (`.dispatch/architecture/<id>.md`) in the same review. When the model you read is behind your checkout, `dispatch_architecture_sync` imports it now ([Syncing a project's architecture model](skill://dispatch/references/issues.md)).
358
+
359
+ ## Close what you opened
360
+
361
+ An ask you opened is yours until it is answered or you resolve it. When the answer arrives some
362
+ other way — the human said it live, a later comment settled it, or the question became moot because the
363
+ design moved — resolve it yourself with `dispatch_resolve_ask` in the same turn you learn that.
364
+ Never leave it for the human to clear.
365
+
366
+ Every later write on the issue answers `You still have an open ask on …` and names it. Treat that
367
+ as the checklist: if it is still needed, leave it; if it was answered elsewhere, resolve it with
368
+ the resolving fact as the reason. Before posting a new ask, inspect your open ones. If the new ask
369
+ supersedes one, retract the old one with `dispatch_resolve_ask` and kind `retracted` in the same
370
+ turn.
371
+
372
+ See [Following](#following) for why you receive what happens to asks you open and
373
+ [What comes back](skill://dispatch/references/reading.md) for finding them again with `dispatch_open_asks`.
374
+
375
+ ## Approval of a spec
376
+
377
+ Approval is a property of a document, not a question you phrase: a human approves a specific
378
+ version, the way a pull-request review approves a commit, and any later version makes that
379
+ approval stale. A later version also moves an open request to that version and leaves it waiting
380
+ on you until you request again, which hands the same request back to the human. Answering a
381
+ decision block writes a new version, so request approval only when all three hold: every decision
382
+ block is settled, which means answered and folded into the text, or waived as the next paragraph
383
+ says; the human has agreed to every point in the spec; and the current version has not been
384
+ approved. A Legion root spec under an armed design gate always goes to approval once all three
385
+ hold (`skill://legion-architect`).
386
+
387
+ When all three hold, request it in the pass that finishes the spec: a design waiting with nothing in
388
+ the human's Inbox waits on nobody. When a human asks for approval while a block is open, do not
389
+ request it and do not hold it silently: name each open block and ask them to answer it or waive
390
+ it. For a waiver, close the block with `dispatch_resolve_ask` (`kind: "resolved"`, their words as
391
+ `reason`), then write their decision into the text in their words and request.
392
+ `dispatch_request_approval` refuses while any block is open.
393
+
394
+ An approval request carries nothing new: a point the human has not agreed to gets its own decision
395
+ block first, with your recommendation, or, when it is a choice you can make yourself
396
+ ([Before you ask](#before-you-ask), gate 1) and the human will not want a say in it, comes out of
397
+ the spec and is made where the work happens; an inference you cannot defend in a decision block
398
+ comes out of the spec. `summary` says in one to three sentences what the human is approving and
399
+ nothing else: no commentary on itself or on the conversation, and no open question. The Inbox
400
+ shows it after "Approve spec.md (version N)?". Never write "Approve" options into an ordinary
401
+ `dispatch_ask`; only humans approve. The call, its result and its answer:
402
+ [Approval requests](skill://dispatch/references/documents.md#approval-requests).
403
+
404
+ ## The Spec
405
+
406
+ The spec holds the design and the decisions that shaped it, written as [Writing a spec](#writing-a-spec)
407
+ says, which also lists what it never carries. It changes when the conversation changes it, and
408
+ every version that records a decision is named with `dispatch_doc_edit`'s `summary`.
409
+
410
+ Read the current document before changing it:
411
+
412
+ ```ts
413
+ dispatch_doc_read({ issue?, project?, artifact?, version?, ref? })
414
+ ```
415
+ It returns live or versioned markdown with open marks. A live read ends with a document token; `issue` with an omitted
416
+ `artifact` reads the issue specification; a project needs `artifact`; and a `dispatch://PROJECT/artifact/<document-ref>`
417
+ ref supplies both, where `document-ref` is the slug (an id or a filename resolves when no document has that slug). A file
418
+ `dispatch_artifact` uploaded reads its text at the latest or named version, or a description when it is not UTF-8 text.
419
+ A picture (PNG, JPEG, GIF or WebP under 3,750,000 bytes, 5 MB of base64) comes back as an image you see, with its name, type, size and version;
420
+ that includes `dispatch://agent/<session id>/artifact/<slug>@vN`, one a person sent you on the Agents page. A session is shown each picture once,
421
+ since the provider refuses a request over 32 MB: later reads and deliveries name it, and `dispatch_doc_read` shows it again (after compaction too).
422
+
423
+ Editing one is [Editing a document](skill://dispatch/references/document-edits.md): the shape of `dispatch_doc_edit`,
424
+ how to quote the text you mean, one `replace` per paragraph, preconditions against a stale edit, and
425
+ what each operation costs a block's id and its anchors.
426
+
427
+ ## Typed blocks
428
+
429
+ A typed block (an `:::ask`, a callout) has its own syntax, attributes and refusals: write one only
430
+ as [Documents](skill://dispatch/references/documents.md) says.
431
+
432
+ ## Artifacts
433
+
434
+ **Where a deliverable goes.** Text the human must read to decide — a draft message, a proposal,
435
+ a summary — goes in the spec as a section: the spec is the one document they open. A separate
436
+ artifact is for a real file: something sent as-is, a long report, a binary, a screenshot.
437
+
438
+ When you do upload one, the spec links it as `dispatch://KEY/artifact/<slug>` (the `slug` from the
439
+ upload result; it renders as a link) at the place the reader needs it, and the ask that needs the
440
+ decision carries the same reference. A heading or a sentence naming the filename is not a
441
+ reference.
442
+
443
+ Uploading one (`dispatch_artifact`, its slugs, versions and Markdown rules) is in
444
+ [Documents](skill://dispatch/references/documents.md).
445
+
446
+ ## Structure over stream
447
+
448
+ Dispatch is a structured workspace, never a message stream. The structure IS the product:
449
+
450
+ - **One issue per piece of work.** A new deliverable — an email to send, a document to review, a
451
+ decision with its own lifecycle — gets its own issue with the content as the issue's document
452
+ (spec artifact), not a message pile on an existing issue. If you are about to post a message
453
+ carrying a draft, a spec, or anything over a couple of paragraphs, stop: that is an issue with a
454
+ document, or an artifact on the issue it belongs to.
455
+ - **Content lives in documents; decisions live in asks; messages only announce.** A draft the
456
+ human must read goes in the issue's spec (see [Artifacts](#artifacts)), which the dashboard renders with versions and margins; the
457
+ ask that needs their word references it (`ref`, or the `dispatch://` link inline) instead of
458
+ restating it. A message never carries a body a human has to scroll.
459
+ - **Never split one deliverable across a message + an ask that points at it.** Ask the question
460
+ with the document reference in the question text; the reader lands on the content in one click.
461
+
462
+ The tool result answers your third consecutive message on an issue with no human reply with
463
+ `You've sent N messages …`. That is the ledger pattern being named; stop and either wait or ask
464
+ once.
465
+
466
+ ## Messages
467
+
468
+ Dispatch is a high-signal record for humans, not a log of what you are doing. A message is a reply to a human's message, or a
469
+ change a human must know about now: a deliverable landed. Nothing else — no progress updates, no
470
+ "starting X", no "still working", no restating the spec, no status on a timer. Your transcript is where work is narrated; the
471
+ pull request is where it is summarised. One message that a human reads beats ten that train them to skip you.
472
+
473
+ ```ts
474
+ dispatch_message({ issue, body })
475
+ ```
476
+
477
+ It returns `details` `{ issue, message }`. `body` is capped at 2,000 characters. A message is not a decision
478
+ (a decision block, or `dispatch_ask` for a human to-do) or document feedback (`dispatch_comment`), and it does not wake anyone unless the issue is routed.
479
+
480
+ **Pictures.** To show a screenshot inline, pass its path in `images` (`dispatch_message`, `dispatch_comment`, `dispatch_ask`);
481
+ `dispatch_read` hands you the pictures what it shows embeds. Both are in [Documents](skill://dispatch/references/documents.md), "Artifacts".
482
+
483
+ A BTW, Aside or Steer frame, or a message from the Agents page, is answered as
484
+ [Targeted and direct messages](skill://dispatch/references/messages.md) says.
485
+
486
+ ## Following
487
+
488
+ You follow every ask you open or reply to: its answer, edits, resolution and replies reach you
489
+ directly. Nothing else on an issue reaches you unless you subscribe to its topic. Leaving or
490
+ rejoining a thread, and the topic to subscribe to, are in
491
+ [Asks after they open](skill://dispatch/references/asks.md).
492
+
493
+ ## References
494
+
495
+ The `dispatch://` form for each kind of node, and what it resolves to, is under
496
+ [Reference forms](skill://dispatch/references/reading.md).
497
+
498
+ **Every reference is a link, never an unlinked mention.** If you name a thing that has an address, link it: another issue, ask, comment, spec, or message (the `dispatch://` forms in [Reading back](skill://dispatch/references/reading.md)), an artifact (`dispatch://KEY/artifact/<slug>`), an eval (its viewer URL), a Slack message (its permalink), a Drive file (its share link). Bare phrases like "see this eval", "his 09-04 run", "the comment above", or "per the spec" with no link are banned: they make the reader hunt for what you already had in hand, and nothing can be traversed from them. Linking every reference is what makes a body both consumable and navigable. If a thing genuinely has no linkable address, say so; otherwise the link is not optional.
@@ -0,0 +1,40 @@
1
+ # Authentication and the HTTP API
2
+
3
+ `skill://dispatch` sends you here when you set up a Dispatch token, or need a route the tools do
4
+ not cover.
5
+
6
+ ## Agent authentication
7
+
8
+ Use a personal Dispatch token: a human mints it in Dispatch **Settings → Agent tokens** and supplies
9
+ it to the agent through `dispatch.token` in `~/.config/opencode/envoy.json` or `DISPATCH_TOKEN`.
10
+ The server records the minting human as the owner of that session's writes. `DISPATCH_AGENT_TOKEN`
11
+ is the shared devbox fallback; do not configure it for an individual agent.
12
+
13
+ The deployed Dispatch server's browser origin is configured separately with
14
+ `DISPATCH_SERVER_URL` in the deployment `compose/.env`. Do not change an
15
+ agent's `envoy.json` to set the sign-in callback origin: the value must be the
16
+ exact URL humans type in their browser, and `<DISPATCH_SERVER_URL>/auth/callback`
17
+ is the callback registered on the sign-in pool's app client
18
+ (`DISPATCH_SIGNIN_CLIENT_ID`).
19
+
20
+ ### Finding a route
21
+
22
+ The tools cover the everyday surface. For anything else, ask the server: `GET /api/v1` (no
23
+ credential) returns every route as `{method, path, auth, description}` sorted by path — `auth`
24
+ is `public`, `any` (a human or a bearer), `human` (a bearer gets `403 HUMAN_ONLY`), or `bearer`.
25
+ A path Dispatch does not serve under `/api` or `/v1` answers
26
+ `404 {"code":"NOT_FOUND","error":"no route for GET /v1/issues","hint":"GET /api/v1 lists every
27
+ route"}`; when you see that, you typed the path wrong — read the index rather than guessing. Every
28
+ `/api/v1` error body carries a `code`; branch on the code, never on the text.
29
+
30
+ `GET /api/v1/issues` answers every matching issue as an array, or one page when you name `limit`
31
+ (1–250) or `offset` (alone it pages 50): `{issues, total, limit, offset}`, where `total` counts
32
+ every issue your filters match. Walking the pages with the next `offset` is exact only while the
33
+ listing does not change: an issue that enters or leaves what the filters match, or whose status or
34
+ rank changes, between two reads shifts rows across a page boundary, and one issue comes back twice
35
+ and another never, even when you stop at `total`. The unpaged array is the only exact set one read
36
+ gives. `cursor` is `400 INVALID_QUERY`. `dispatch_issues` pages for you.
37
+ The event logs page with `after` or `before` and `limit`, and `GET /api/v1/search` takes a `limit`
38
+ of at most 50 and has no next page. Every other route answers without paging and ignores a paging
39
+ parameter.
40
+