@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.
- package/README.md +160 -0
- package/dist/THIRD_PARTY_NOTICES +513 -0
- package/dist/envoy.js +37511 -0
- package/dist/skills/dispatch/SKILL.md +498 -0
- package/dist/skills/dispatch/references/api.md +40 -0
- package/dist/skills/dispatch/references/asks.md +116 -0
- package/dist/skills/dispatch/references/document-edits.md +202 -0
- package/dist/skills/dispatch/references/documents.md +230 -0
- package/dist/skills/dispatch/references/examples.md +132 -0
- package/dist/skills/dispatch/references/issues.md +213 -0
- package/dist/skills/dispatch/references/messages.md +74 -0
- package/dist/skills/dispatch/references/reading.md +64 -0
- package/dist/skills/dispatch-brainstorming/SKILL.md +113 -0
- package/dist/skills/dispatch-first/SKILL.md +58 -0
- package/dist/skills/envoy/SKILL.md +275 -0
- package/package.json +42 -1
|
@@ -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
|
+
|