@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,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.
|