@sjawhar/pi-envoy 0.0.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,132 @@
1
+ # Before and after: a decision block, a reply, a message, and a draft
2
+
3
+ `skill://dispatch` sends you here for worked examples: a design decision in its document, a reply
4
+ that names its mechanism, a message that should not be sent, and a draft placed where the human
5
+ reads it.
6
+
7
+ ## Before / after
8
+
9
+ Before — a wall of text hides the decision and the human never receives it:
10
+
11
+ ```text
12
+ The deploy branch has the migration and dashboard changes. Staging is fine, but release notes are
13
+ not reviewed before tomorrow's customer demo. Review the notes, ship without them, or cut the
14
+ dashboard from the release. Waiting is safest, but the list above may be stale.
15
+ ```
16
+
17
+ After — write the decision into the document section it concerns: state the problem, and make each
18
+ genuinely different option a button, so the answer stays with the release design:
19
+
20
+ ```ts
21
+ dispatch_doc_edit({
22
+ issue: "LEGION-815",
23
+ artifact: "spec",
24
+ ops: [{
25
+ op: "insert",
26
+ after: "Release requires reviewed operator instructions before deployment.",
27
+ markdown: `:::ask{#release-gate urgency="high"}
28
+ 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.
29
+
30
+ - Review notes, then ship: Delays release for review but keeps the release complete and reviewed.
31
+ - Ship now: Meets the demo deadline but leaves the release notes unreviewed.
32
+ :::`,
33
+ }],
34
+ })
35
+ ```
36
+
37
+ Before — a progress note that nobody needs, posted where humans look for decisions:
38
+
39
+ ```ts
40
+ dispatch_message({ issue: "LEGION-815", body: "Merged the release PR, moving to docs next." })
41
+ ```
42
+
43
+ After — nothing. The merge is visible on the pull request; the docs work shows up as its own deliverable. Post a message only when
44
+ a human must act or a deliverable is theirs to use:
45
+
46
+ ```ts
47
+ dispatch_message({
48
+ issue: "LEGION-815",
49
+ body: "Release 1.4 is live on the devbox (dispatch://LEGION-815/artifact/release-notes). Nothing needed from you.",
50
+ })
51
+ ```
52
+
53
+ Before — a draft the human must read is uploaded as a separate file, the spec only names it, and
54
+ the ask does not point at it, so the reader has to go looking:
55
+
56
+ ```ts
57
+ dispatch_artifact({ issue: "OPS-52", name: "cu-update-2026-09-15.md", content: "Hi team, ..." })
58
+ dispatch_doc_edit({ issue: "OPS-52", artifact: "spec", ops: [
59
+ { op: "insert", after: "## Context", markdown: "## Draft (artifact cu-update-2026-09-15.md)" },
60
+ ]})
61
+ dispatch_ask({
62
+ issue: "OPS-52",
63
+ question:
64
+ "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.",
65
+ options: [
66
+ { label: "Put the draft in the spec", description: "Adds a spec edit before sending but lets the reader review it in context." },
67
+ { label: "Keep the separate file", description: "Saves the spec edit but leaves the reader to find the draft." },
68
+ ],
69
+ })
70
+ ```
71
+
72
+ After — the draft is a section of the spec, and the ask anchors there. Sending it is a to-do only a
73
+ human can complete, so it stays a standalone ask. If it really must be a file, the spec and the ask
74
+ both link the slug from the upload result:
75
+
76
+ ```ts
77
+ dispatch_doc_edit({ issue: "OPS-52", artifact: "spec", ops: [
78
+ { op: "insert", after: "## Context", markdown: "## Draft\n\nHi team, ..." },
79
+ ]})
80
+ dispatch_ask({
81
+ issue: "OPS-52",
82
+ question:
83
+ "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.",
84
+ options: [
85
+ { label: "Send the reviewed update", description: "Delivers the update today but makes its text external." },
86
+ { label: "Hold the update", description: "Avoids sending now but leaves customers without the update." },
87
+ ],
88
+ anchor: { artifact: "spec", quote: "Hi team," },
89
+ })
90
+ // or, for a real file — the spec links it where the reader needs it, and so does the ask:
91
+ dispatch_doc_edit({ issue: "OPS-52", artifact: "spec", ops: [
92
+ { op: "insert", after: "## Context", markdown: "## Draft\n\nThe update to send: dispatch://OPS-52/artifact/cu-update-2026-09-15-md" },
93
+ ]})
94
+ dispatch_ask({
95
+ issue: "OPS-52",
96
+ question:
97
+ "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.",
98
+ options: [
99
+ { label: "Send the reviewed update", description: "Delivers the linked update today but makes its text external." },
100
+ { label: "Hold the update", description: "Avoids sending now but leaves customers without the update." },
101
+ ],
102
+ ref: "dispatch://OPS-52/artifact/cu-update-2026-09-15-md",
103
+ })
104
+ ```
105
+
106
+ ## Naming the mechanism
107
+
108
+ Before — a reply explaining a design paraphrases its tool, its route and its event away, so the
109
+ reader has to ask which tool, and how Legion "notices" anything:
110
+
111
+ ```text
112
+ Today, when a spec is ready, the architect tells Legion so with a dedicated call on its Legion
113
+ tool, and Legion then waits for your approval. This change removes that call, because Legion will
114
+ notice your approval of the spec by itself.
115
+ ```
116
+
117
+ After — the reader's sentence first, then each mechanism by its real name, with how it works in a
118
+ clause:
119
+
120
+ ```text
121
+ For you nothing changes: you still click Approve on the spec. What goes is a step the architect
122
+ takes today: it calls `register_gate` on its `legion` tool, which posts to the daemon's
123
+ `POST /legion/v1/gates/register` route and names the spec document and the version that gate the
124
+ tree. The daemon opens the gate when Dispatch emits `artifact.approved` for that document at that
125
+ version, which Dispatch does when you click Approve on the request the architect opened with
126
+ `dispatch_request_approval`. Without `register_gate`, the daemon reads the issue's primary
127
+ document from Dispatch itself and waits for the same event.
128
+ ```
129
+
130
+ "A dedicated call", "tells" and "notice" each stood for something the reader can search for, find
131
+ in a log, or set. None is a noun anyone coined, so each is named; the clause after each name is
132
+ what keeps the reply readable on a phone.
@@ -0,0 +1,213 @@
1
+ # Working an issue: choosing, claiming, status, priority, and the backlog
2
+
3
+ `skill://dispatch` sends you here when you pick your next issue, claim or release one, get
4
+ `409 ISSUE_CLAIMED`, move an issue's status or priority, reorder a board, reassign an issue, sync a
5
+ project's architecture model, or list or audit a project's backlog.
6
+
7
+ ## Choosing what to work on
8
+
9
+ When you finish an issue, or are told to work on the next thing, take the top ready issue of the
10
+ whole backlog, across every project: status `todo`, highest priority first, then board rank. There
11
+ are no areas: a standing role, a product owner and a lane each take the top issue like everyone
12
+ else. `todo` means ready: specced, unblocked, and waiting on neither a deploy nor a
13
+ decision. An issue that waits on one belongs in `backlog`, with what it waits on said on the issue.
14
+
15
+ Hold at most three issues in flight (`in_progress`, `testing`, `needs_review` or `retro`), of any
16
+ kind. The limit is per agent and has nothing to do with the week's priorities: the priorities
17
+ decide only what you pull next. Past three:
18
+ push any unfinished work, say where in one comment on the issue, move it to `backlog` and clear
19
+ its route. Each issue counts on its own; a child does not ride under its parent's slot.
20
+ In-flight issues with no owner at all go
21
+ back to `backlog` as well: no claim or route held by a live session, no Dispatch activity in the
22
+ last day, and no pull request moving on GitHub (an owner working there leaves no Dispatch trace).
23
+ The order keeper sweeps those. Never write the status of an issue that carries the `legion`
24
+ label, or of any issue under one: the Legion daemon writes those statuses. A status your session
25
+ writes on the root of a tree Legion is running is set back and the tree's architect told who wrote
26
+ it; only a person in the dashboard, or `legion status`, stops that tree. On an issue under one, a
27
+ status that takes it out of the flow parks that issue and stops its workers.
28
+
29
+ One agent keeps the backlog's order against those priorities. Setting an
30
+ issue's priority stays yours ([Priority is yours to set](#priority-is-yours-to-set)); reordering
31
+ the board does not.
32
+ When the top of the backlog looks wrong, or a priority's next step is not yet a ready issue,
33
+ publish it to `notifications.role.backlog-order`, which the order keeper holds, instead of
34
+ reordering the board yourself.
35
+
36
+ ## Claim the issue before you work it
37
+
38
+ Before you start implementing an issue, claim it:
39
+
40
+ ```ts
41
+ dispatch_claim({ issue: "LEGION-234" }) // I am implementing this
42
+ dispatch_claim({ issue: "LEGION-234", release: true }) // I have stopped; it is free
43
+ ```
44
+
45
+ A claim records **your** session — the one making the call, never another — and shows on every
46
+ read of the issue: the dashboard header, the issue list and board, `dispatch_read` (a
47
+ `Claimed by:` line) and `dispatch_issues` (a claim on the row). A session's claim there ends
48
+ `· not running` when the live agent registry does not list that session, as the dashboard's chip
49
+ does — that claim is free to take — and `· liveness unknown` when the registry could not be read,
50
+ which says nothing either way. `dispatch_issues` plus the dashboard's **Unclaimed** filter is how
51
+ you find work nobody is on.
52
+
53
+ - **`409 ISSUE_CLAIMED` means someone else holds this issue.** When it is another session, the
54
+ refusal names it and says it is still running: do not work the issue in parallel — message
55
+ that session (its id is in the message; `envoy_send` reaches it) or pick up something else,
56
+ and tell the human if you believe the work should be yours. When a **human** holds it, the
57
+ refusal names the person and says nothing about a session running, because there is none to
58
+ message: ask them with `dispatch_ask` instead, so the open ask appears in their Inbox, and
59
+ never assume their claim has lapsed — only a human releases or forces a human's claim.
60
+ - **`409 CLAIM_CONTENDED` means the issue changed hands twice while your call ran**, so nothing
61
+ was applied and nobody's liveness was checked. Read the issue and decide again; it is not a
62
+ refusal by a live holder.
63
+ - **A claim whose session has ended is yours to take.** If the Envoy listener no longer lists
64
+ the holder, your claim simply succeeds; the takeover is recorded on the issue and the session
65
+ that lost it is told.
66
+ - **Release it when you stop** — finished, handing over, or moving to something else. A claim is
67
+ released by its holder or any human — and by any agent once the holder's session is no longer
68
+ running, the same rule that lets you take it. Closing the issue releases it for you.
69
+ - A claim is intent to implement, not contact: reading the issue, commenting, asking, or gating
70
+ its pull request claims nothing, so a coordinator never collides with an implementer.
71
+
72
+ **Claiming and moving the status are two separate actions, and you do both.** A claim says which
73
+ session is on the work; the status says where the work has got to, and humans use it to track
74
+ that too. So when you start: `dispatch_claim({ issue })` **and**
75
+ `dispatch_issue_update({ issue, status: "in_progress" })`.
76
+
77
+ ## Issue status is yours to move
78
+
79
+ The issue's status is how a human sees delivery without asking a session. Outside Legion (where
80
+ the daemon writes it), the session doing the work moves it, the way a person moves a card:
81
+ `in_progress` when implementation starts, `testing` when the change is being proven on a
82
+ production-like surface, `needs_review` when its pull request is open and waiting on the merge
83
+ queue, `done` when the change has been driven in production (a merge is not `done`). Move child
84
+ issues you own as well as the root. An issue left at `triage` while work is underway is a defect.
85
+ Waiting for the deploy lane is not a status and is never announced.
86
+
87
+ ```ts
88
+ // PATCH /api/v1/issues/{key} — status, title, labels, priority, external_links (merged by URL), route, parent
89
+ dispatch_issue_update({ issue: "PROJ-175", status: "testing" })
90
+ dispatch_issue_update({ issue: "PROJ-175", status: "done", reason: "Shipped in owner/repo#7; verified on the production dashboard." })
91
+ dispatch_issue_update({ issue: "PROJ-175", priority: 1 }) // 0–3; see Priority is yours to set
92
+ dispatch_issue_update({ issue: "PROJ-175", external_links: ["https://github.com/owner/repo/pull/7"] })
93
+ dispatch_issue_update({ issue: "PROJ-175", parent: "PROJ-170" }) // same-project key; "" clears the parent
94
+ ```
95
+
96
+ Closing takes a `reason`, and the tool refuses `status: "done"` without one: it posts the reason on
97
+ the issue as a message, then closes it, because a closed issue refuses messages, comments, and
98
+ artifacts, so a reason left for later has nowhere to go. When the close fails after the post, the
99
+ error names the posted message; after a timeout or a server error it also says the close may have
100
+ landed, so read the issue's status first. A retry points its reason at the posted message rather
101
+ than repeating it.
102
+
103
+ The two clears differ: `priority` clears with `null`, while `parent` and `route` clear with `""`.
104
+ Guessing the other one is a refusal either way.
105
+
106
+ Link the pull request that delivers the issue in `external_links` when you open it; the issue page
107
+ renders its state and checks from that link, and `dispatch_read` lists it under `External links:`.
108
+ The call is authenticated with the same bearer as every
109
+ other `dispatch_*` tool: a Legion pane reads it from the `DISPATCH_TOKEN_FILE` path the daemon sets on
110
+ the pane; an OMP session outside Legion reads `dispatch.token` from `~/.config/opencode/envoy.json`.
111
+
112
+ A write to an issue still in `triage` answers once with `… is still in triage …`; move the status
113
+ when work has started.
114
+
115
+ ## Priority is yours to set
116
+
117
+ Priority is the coarse bucket a backlog is read by: `0` is P0, the highest, through `3`, P3, the
118
+ lowest, and `null` clears it. Agents set it (`dispatch://LEGION/artifact/issue-status-conventions-md`)
119
+ — on creation, and on a grooming pass over issues that have none — and say what you set and why;
120
+ the human overrides anything they disagree with from the dashboard. A closed
121
+ issue takes only `rank`, `components`, and a reopening `status` (any status but `done`);
122
+ everything else, `priority` included, waits for the reopen (`409 ISSUE_CLOSED`). So reopen it
123
+ first, then set the priority — the two cannot go in one call. `rank` itself is not a tool field:
124
+ reorder it the way [Issue reads](#board-rank-priority-and-assignee-on-a-read) describes, through `PATCH /api/v1/issues/{key}`.
125
+
126
+ ## Board rank, priority, and assignee on a read
127
+
128
+ Issue reads include `rank`, the server-owned ordering key used by project boards; reorder through `PATCH /api/v1/issues/{key}` with `{"rank": {"before": "<key>", "after": "<key>"}}`, either neighbor optional and both in the issue's project. A bearer caller also names its own session in that body, `"actor": {"kind": "session", "id": "<your session id>"}`, or the server refuses with `ACTOR_KIND`. They also include nullable coarse priority (`P0` highest through `P3` lowest) and `assignee`: the lowercase email of the person who answers the issue's asks, or `null` when nobody holds it. `dispatch_read` of an issue prints it as `Assignee: <email>` or `Assignee: unassigned`.
129
+
130
+ ## Reading a project's backlog
131
+
132
+ To see the shape of a project rather than find a phrase, list its issues:
133
+ ```ts
134
+ dispatch_issues({ project, status?, parent?, label?, priority?, route_status?, updated_since?, limit?, offset? })
135
+ ```
136
+ Each row carries the issue key, title, status, priority, parent, labels, its open-ask count, and
137
+ when it last changed — a roadmap or backlog pass without opening every issue. Filter with `status`
138
+ (a lifecycle status), `parent` (one issue's children), `label`, `priority` (a list of `0`–`3`, with
139
+ `null` for an issue with no priority: `[0, 1]` is every P0 and P1), `route_status` (below), or
140
+ `updated_since` (an RFC3339 timestamp, for "what moved this week"). `limit` is the page size, 50 by
141
+ default and 250 at most, and `offset` is where the page starts; the answer says how many issues
142
+ match. Repeating with the next offset walks every matching issue only while the list does not
143
+ change: an issue that enters or leaves what your filters match, or whose status or rank changes,
144
+ between two pages shifts rows across a page boundary, and the walk then shows one issue twice and
145
+ misses another.
146
+
147
+ This is not search: it matches no text. Use `dispatch_search` for a keyword or phrase, and
148
+ `dispatch_issues` when you want every issue in a project and its current state.
149
+
150
+ ### The owner audit
151
+
152
+ As the owner of a surface, list the project's P0 and P1 issues and staff or close each one nobody
153
+ has started:
154
+ ```ts
155
+ dispatch_issues({ project, priority: [0, 1], limit: 250 })
156
+ ```
157
+ Every unclaimed row in `triage`, `icebox`, `backlog` or `todo` is a decision: someone takes it and
158
+ builds it, or it closes. A row in `in_progress`, `testing`, `needs_review` or `retro`, or one that
159
+ carries a claim, is work under way ([Issue status is yours to move](#issue-status-is-yours-to-move))
160
+ and is not re-staffed. A todo with a finished spec reads as queued work that nobody is doing.
161
+
162
+ A close that says the defect cannot happen cites the code that makes it impossible. An issue
163
+ closed because a rewrite forecloses it names the file and line in the rewrite that does so; a
164
+ close that cannot name one is not foreclosed, it is unread. The cheapest way for a rewrite to reach
165
+ parity is to port the code, defect included.
166
+
167
+ The audit finds four shapes:
168
+
169
+ - **Unstaffed work.** A plan or measurement exists, and no one is building it.
170
+ - **Unrecorded delivery.** An issue not yet in `testing` or `done`, claimed or not, has a merged PR
171
+ naming it. Check the change live, then move the issue.
172
+ - **Unrecorded practice.** Someone does the issue's work by hand, more than once, while the issue
173
+ sits in backlog. It leaves no plan and no PR to find; the tell is your own messages. Doing
174
+ something by hand more than once means an issue is wearing the wrong status.
175
+ - **Unreachable route.** An open issue whose route names a role nobody holds, or a session that is
176
+ not running, reaches nobody, whatever its priority, and the priority filter above never finds
177
+ it. List it on its own:
178
+ ```ts
179
+ dispatch_issues({ project, route_status: "no_holder", limit: 250 })
180
+ ```
181
+ Each row reads `route role:sre (nobody holds it right now)` or `route session:<id> (that session
182
+ is not running right now)`. That is one read of the listener, and one read cannot tell a
183
+ restart gap from a vacancy: an agent box that restarts or resumes keeps the session id, but the
184
+ session is absent from the listener for minutes, and its role with it.
185
+ So a route is unowned only when it is `no_holder` on two reads at least ten minutes apart: list
186
+ again after ten minutes and act on the issues both lists name. Confirm with the second
187
+ `dispatch_issues` read, not `envoy_role_get`: a role lookup releases the claim of a holder whose
188
+ session is absent from the registry as it answers. Then staff the role, re-route the
189
+ issue to a live holder, or clear the route and assign it. `route_status: "unknown"` means the
190
+ listener did not answer, so a route could not be judged; a `no_holder` filter refuses rather
191
+ than answer an empty list then.
192
+
193
+ Run the audit as a step of a coordinator's loop, at each checkpoint, not as a habit: these shapes
194
+ are found by running the check, not by noticing them.
195
+
196
+ ## Search paging
197
+
198
+ `skill://dispatch` sends you here when a `dispatch_search` page is not enough: more hits remain,
199
+ or a kind runs out before the page does.
200
+
201
+ A page holds `limit` hits (20 by default, 50 at most); the first line names how many match
202
+ (`showing 1-20 of 312`), and, while more can be reached, the last line names the next `offset`.
203
+ Each kind lists at most its best 100, so when the result says the rest cannot be paged to, narrow
204
+ the query or name a `project`.
205
+
206
+ ## Syncing a project's architecture model
207
+
208
+ Import a project's architecture model from its configured source repository now (a human configures the source in Settings):
209
+ ```ts
210
+ dispatch_architecture_sync({ project: "CORE" })
211
+ ```
212
+ It returns the imported commit, or the recorded error when the model was rejected — the previous model stays up. Without a configured source it answers 404 `SOURCE_NOT_FOUND`.
213
+
@@ -0,0 +1,74 @@
1
+ # Answering targeted and direct messages
2
+
3
+ `skill://dispatch` sends you here when a Dispatch frame targets you as BTW, Aside or Steer, or a
4
+ human messages you directly from the Agents page.
5
+
6
+ ## Targeted agent messages
7
+
8
+ A human — or any bearer caller over HTTP, such as a test rig — can target the issue message at a
9
+ live Envoy session or role as **BTW**, **Aside**, or **Steer** (the dashboard calls a steer
10
+ **Send**). The incoming Dispatch frame names
11
+ the issue and includes a `reply_with` hint (`{ tool, args }`, ready to issue on any host); reply on the same open issue with the existing
12
+ tool, never a new targeted send:
13
+
14
+ ```ts
15
+ dispatch_message({
16
+ issue: "CORE-1",
17
+ in_reply_to: "<targeted-message-id>",
18
+ body: "The requested answer.",
19
+ })
20
+ ```
21
+
22
+ `in_reply_to` correlates the answer under the asker's message in its Conversation card. A BTW
23
+ delivery can post its answer automatically; use this call when the frame asks the primary agent to
24
+ reply. A human may reply to your message in turn — the follow-up arrives as a targeted frame whose
25
+ `in_reply_to` names your message and whose `reply_body` quotes it; answer it the same way,
26
+ `dispatch_message({ issue, in_reply_to: "<their reply id>", body })`, so the exchange reads as one
27
+ thread. `dispatch_message` itself never carries `target` or `delivery`: agent-to-agent traffic goes
28
+ through Envoy or Oh My Pi's `agent://` messages. A bearer that targets over HTTP names its own
29
+ session in `actor` (`{kind: "session", id}`), and the card shows that session as the author.
30
+ `GET /api/v1/agents` (any authenticated caller) lists live sessions with their capabilities
31
+ (`aside`, `btw`, `steer`); target only a session that advertises the mode you want. Sending to a
32
+ session with no issue (`POST /api/v1/agents/{session_id}/messages`) stays human-only.
33
+
34
+ ## Answering a direct message
35
+
36
+ A human can also message you directly from the **Agents** page, with no issue at all. On Oh My
37
+ Pi, a person's **Send** or **Aside** arrives as their own user message, exactly as if they had
38
+ typed it at your terminal: your Envoy plugin takes it only once Dispatch accepts it as a person's
39
+ own fresh message to you, and injects the text Dispatch stored. Answer it in the conversation as
40
+ you would anything typed, with no `dispatch_message`; the Agents page shows your conversation
41
+ live, so they read your answer there.
42
+
43
+ Everything else still arrives as a Dispatch frame: a **BTW**, a broadcast, a direct message on a
44
+ host that takes no user turn from its plugin (Claude Code), and one Dispatch did not accept.
45
+ That frame names no issue and its `reply_with` hint carries none either; answer it with the
46
+ message's bare id in `in_reply_to`, alone:
47
+
48
+ ```ts
49
+ dispatch_message({
50
+ in_reply_to: "<the direct message's id>",
51
+ body: "The requested answer.",
52
+ })
53
+ ```
54
+
55
+ Leave `issue` out — there is no issue to post into, and naming one would file your answer on
56
+ unrelated work. Dispatch threads the reply under their message in the same conversation, and the
57
+ human sees it in your conversation on the Agents page, where it shows as an unread reply until
58
+ they read it. Every other message still names its issue, so keep the `issue`
59
+ the frame gave you whenever it gave you one; a `dispatch://KEY/message/<id>` reference names the
60
+ issue its message lives on, so that form is a reply on that issue, not a direct message.
61
+
62
+ Have more to say after you answered? Call it again with the same `in_reply_to` and the new text:
63
+ Dispatch threads that follow-up under your first reply, and the tool result names the reply it
64
+ follows. The same text again posts nothing, so a retry is safe. Your host may already have
65
+ answered a **BTW** automatically before you got here; a second call is then your follow-up to
66
+ that answer, so read the result before writing again.
67
+
68
+ Read the whole conversation back — their message and every reply, yours included — with the
69
+ message id alone; it has no issue:
70
+
71
+ ```ts
72
+ dispatch_read({ message: "<the direct message's id, or any reply's>" })
73
+ ```
74
+
@@ -0,0 +1,64 @@
1
+ # Reading Dispatch back, and the reference forms
2
+
3
+ `skill://dispatch` sends you here when you catch up after a restart, read an issue, ask, comment,
4
+ message or document, trace what cites a node, or need the exact `dispatch://` form for a reference.
5
+
6
+ ## What comes back
7
+
8
+ After a restart, catch up with:
9
+
10
+ ```ts
11
+ dispatch_read({ issue?, project?, artifact?, ref? })
12
+ ```
13
+
14
+ With an issue ref, it returns the issue summary, open asks, references, and recent events with `details` `{ issue }`. With a project
15
+ document owner or ref, it returns a document summary with `details` `{ project, document }`. With an ask ref, it returns that ask's
16
+ question, options, state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted reply chain. With a
17
+ message ref, it returns that message and its reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
18
+
19
+ An anchored comment or ask also prints `Position:`, where its quote's block stands — `table[3] › row 5 (Red-teamer loop), column Due`
20
+ is the fourth top-level block, a table, its row 5 (row 0 is the header; the index `delete_row` takes), labelled by the row's cells
21
+ before the anchored one, in the column headed Due; outside a table it is the path of types and child indexes down to the block. The
22
+ same facts are `anchor_block` on `GET /api/v1/comments/{id}` and `GET /api/v1/asks/{id}`, and
23
+ `GET /api/v1/artifacts/{id}/blocks/{block_id}` answers them for any block id a document holds. When Dispatch could not read the
24
+ document, the read still answers and prints `Position: unavailable (DOC_SERVICE_UNAVAILABLE)` (try again shortly),
25
+ `Position: unavailable (DOC_SCHEMA)` (the document needs repair) or `Position: unavailable (INTERNAL)`; the API carries that
26
+ code as `anchor_block_error`.
27
+
28
+ Every read ends with two sections from the reference graph. `Referenced by:` lists what points at the node — every document, ask,
29
+ comment, or message that cites it, plus its structure: child issues, attached documents, anchored and owned asks and comments, replies,
30
+ followers — and `Links:` lists what it cites. Each row is `- <edge kind> <node kind> dispatch://… (<excerpt> · <when>)`; for a
31
+ document source the excerpt is the start of the block holding the mention, and a whole list is one block, so every issue named in
32
+ one list previews the list's first item. When a document references many issues and each backlink should read right, give each
33
+ issue its own paragraph (or block), not an item of one list. Cross-project, always: a message on another project's issue that
34
+ cites an ask shows up under that ask. So "what led to this decision" is one `dispatch_read` on the ask, and "who relies on this
35
+ document" one read on the document. Cite with `dispatch://` references (below) whenever you name a node in a body — a bare id or
36
+ title is invisible to the graph.
37
+
38
+ ## Reference forms
39
+
40
+ Use these in document, ask, comment, and message bodies. In the dashboard, a reference renders
41
+ as an inline link whose text is the target's title (an issue's title, an ask's question, a
42
+ comment's first line, a document's name) once it resolves; a body that is only a bare reference
43
+ still gets an unfurl card instead. Every `ref` argument below (and `issue`/`project`) accepts
44
+ either form — an issue key or a project key is never ambiguous, since a project key never
45
+ contains a dash:
46
+
47
+ ```text
48
+ dispatch://KEY
49
+ dispatch://KEY/spec
50
+ dispatch://KEY/artifact/<slug>[@vN]
51
+ dispatch://KEY/ask/<id>
52
+ dispatch://KEY/comment/<id>
53
+ dispatch://KEY/message/<id>
54
+ dispatch://PROJECT/artifact/<document-ref>[@vN]
55
+ dispatch://PROJECT/artifact/<document-ref>/ask/<id>
56
+ dispatch://PROJECT/artifact/<document-ref>/comment/<id>
57
+ ```
58
+
59
+ A bare UUID or `KEY#seq` is not a reference; the `dispatch://` form is what Dispatch links and records. `dispatch_read` also
60
+ accepts the dashboard URL of an issue, spec, artifact, ask, comment, or project document on the configured server (it maps to the
61
+ `dispatch://` form above), and an ask or comment id may be a unique prefix of at least 8 hex characters; a message id is always the
62
+ full uuid. A non-uuid id on `GET /asks/{id}`, `/comments/{id}`, or `/issues/{key}/messages/{id}` is a 400 `ASK_ID_INPUT` /
63
+ `COMMENT_ID_INPUT` / `MESSAGE_ID_INPUT`, never a 500.
64
+
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: dispatch-brainstorming
3
+ description: "Use in a session with Dispatch tools whenever a design conversation with a person starts: brainstorming or /brainstorming, \"let's design\", writing or changing a spec, writing an implementation plan, or a feature request or change that needs a person's choices. In a session with Dispatch it replaces superpowers' brainstorming and writing-plans; a Legion architect's own spec follows skill://legion-architect instead."
4
+ ---
5
+
6
+ # Brainstorming in Dispatch
7
+
8
+ This skill governs a design conversation with a person, never a Legion architect's own issue
9
+ spec: an architect follows `skill://legion-architect` for that, even for a child issue.
10
+
11
+ In a session with Dispatch, such a conversation happens in its issue's spec, and the plan that
12
+ follows is a document on the same issue. This skill replaces superpowers' `brainstorming` and
13
+ `writing-plans` there, even when the user invokes one of them by name:
14
+
15
+ - No design question goes to chat. Every question is a decision block in the spec, and your reply
16
+ in chat links the issue and asks nothing.
17
+ - There is no limit of one question per message. Every question that is ready goes out at once.
18
+ - There is no approval after each section, only the one at the end.
19
+ - A small change gets a short spec, never a design in chat: its size shortens the spec and changes
20
+ nothing about where the conversation happens.
21
+ - No spec or plan file goes in the repository (`docs/superpowers/specs/`,
22
+ `docs/superpowers/plans/`, `docs/plans/`): the spec is the issue's primary document and the plan
23
+ is its `plan.md` document.
24
+
25
+ The brainstorming stages still shape what you ask: clarifying questions, then two or three
26
+ approaches with a recommendation, then the design section by section. A question that depends on
27
+ no open answer goes out at once, whatever its stage.
28
+
29
+ Read `skill://dispatch` before the first version: its "Writing a spec", "Decision blocks",
30
+ "Writing for the human" and "Approval of a spec" sections define the spec's shape, each block's
31
+ shape, and when approval is requested. A Legion phase worker never writes the spec; it sends a
32
+ decision to its architect (`skill://legion-worker`).
33
+
34
+ ## Where the spec lives
35
+
36
+ Search Dispatch first, as `skill://dispatch-first` says. When an issue already tracks the work, its
37
+ primary document is the spec: extend it in place with `dispatch_doc_edit`, keeping the human's own
38
+ text. Otherwise create the issue with its first version as `spec`, in the project a search hit
39
+ already named or, when none does, the project the human names:
40
+
41
+ ```ts
42
+ dispatch_issue({ project, title, spec })
43
+ ```
44
+
45
+ No tool lists every project, so when nothing you found names one, ask the human in chat which
46
+ project the issue belongs in before you create it — a filing question with nowhere else to land
47
+ yet, not a design question, so it does not change "your reply in chat links the issue and asks
48
+ nothing" for the design itself. Never read the project from the environment, a configuration file,
49
+ or the Dispatch HTTP API: every read and write goes through a `dispatch_*` tool.
50
+
51
+ Before the first version, read the code and documents the design touches, so the facts you write
52
+ are ones you checked.
53
+
54
+ ## Each turn
55
+
56
+ 1. **Start with what is established.** The first version holds only what the conversation has
57
+ established: the problem and its evidence, what the human has said in their own words, the
58
+ facts the next questions need, and each question that is ready, as a decision block at the end
59
+ of the section that sets it up. Write nothing past those questions: no design, no defaults you
60
+ chose, no "what we will build", and no recommendation outside a decision block's own text. A
61
+ point the human has not stated, however obvious it seems (what a word in the request means, how
62
+ an edge case behaves, what a change does to an existing command), goes inside a decision block
63
+ or stays out. This is stricter than `skill://dispatch`'s general rule for an inferred point
64
+ elsewhere in a spec ("a point you inferred says so, with the reasoning"): in a live conversation
65
+ nothing is settled until the human has seen it, so even a flagged inference waits for its own
66
+ decision block.
67
+ 2. **The human answers or comments.** Reply to each human comment in its thread
68
+ (`dispatch_comment` with `reply_to`), then rewrite the passage the answer or the comment changes.
69
+ Under an open ask whose next move is yours, such as the approval request you must revise or
70
+ hand back, reply with `reply_to_ask` and `turn: "agent"` instead: a `reply_to` reply takes the
71
+ default turn, which hands the request back to the human unchanged, and a call with a corrected
72
+ `summary` is refused while it waits on them.
73
+ 3. **Each next version folds the answers in and adds what they open.** Keep each answered
74
+ decision block where it is, fold its answer into the surrounding text in the human's words with
75
+ the date, recording the person as `skill://dispatch`'s "Writing a spec" says for a settled
76
+ point. Then add the next sections, each with its question. Every question that is ready goes out
77
+ at once, each as a decision block at the end of the section that sets it up; a question waits
78
+ only when it depends on an answer still open.
79
+ 4. **A comment that answers a question settles it** as surely as the block does. Fold it into the
80
+ text at once, and close the block with `dispatch_resolve_ask` if the human has not.
81
+ 5. **Request approval at the end, not after each section.** Once nothing in the spec is new to the
82
+ human, request it exactly when "Approval of a spec" in `skill://dispatch` says: every decision
83
+ block settled and folded in, the human has agreed to every point in the spec, and the current
84
+ version is not yet approved (or waived, as that section's next paragraph covers). The request
85
+ carries nothing new, as that section also says.
86
+
87
+ ## Coming to terms
88
+
89
+ The conversation comes to terms in both directions. Explain what the code does today, plainly
90
+ enough for the human to react to and with each tool, event and route it uses named exactly (as
91
+ "Writing for the human" in `skill://dispatch` says), and ask; the human's model comes out of those
92
+ reactions, and so do corrections to it. Neither your model nor theirs is the starting truth.
93
+
94
+ ## The plan
95
+
96
+ Once the spec is approved — or, when the issue needed no spec at all (a bug fix with no design
97
+ decision), once you have enough to plan from — the implementation plan is the issue's `plan.md`
98
+ document, never its spec and never a repository file:
99
+
100
+ ```ts
101
+ dispatch_artifact({ issue, name: "plan.md", content })
102
+ ```
103
+
104
+ Uploading `plan.md` again writes its next version. `writing-plans`'s content rules still apply in
105
+ full — file structure, task right-sizing, bite-sized steps, no placeholders, the header, and its
106
+ self-review — only where the plan lives changes: the issue's `plan.md`, never a repository file.
107
+ Technical choices inside the approved design are made in the plan and need no question. A plan
108
+ never changes a decision the human made in the spec: a change to one goes back to the spec as a
109
+ decision block, with the evidence for it.
110
+
111
+ Execution reads the plan the same way: `dispatch_doc_read({ issue, artifact: "plan.md" })` in place
112
+ of opening a plan file, then follows `executing-plans` or `subagent-driven-development` as it would
113
+ for a file on disk.