@sjawhar/opencode-legion-envoy 8.31.0 → 9.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.
Files changed (43) hide show
  1. package/dist/dispatch.js +49 -30
  2. package/dist/src/server.js +16 -15
  3. package/package.json +1 -1
  4. package/skills/AGENTS.md +7 -6
  5. package/skills/dispatch/SKILL.md +54 -169
  6. package/skills/dispatch-first/SKILL.md +23 -17
  7. package/skills/dispatch-writing-specs/SKILL.md +76 -0
  8. package/skills/leading-a-work-stream/SKILL.md +96 -0
  9. package/skills/leading-a-work-stream/references/daily-plan.md +8 -0
  10. package/skills/leading-a-work-stream/references/decision-rights.md +6 -0
  11. package/skills/leading-a-work-stream/references/direction-and-coaching.md +13 -0
  12. package/skills/leading-a-work-stream/references/dispatch-upkeep.md +7 -0
  13. package/skills/leading-a-work-stream/references/sources.md +9 -0
  14. package/skills/leading-a-work-stream/references/speed-tick.md +44 -0
  15. package/skills/leading-a-work-stream/references/user-quotes.md +7 -0
  16. package/skills/legion-architect/SKILL.md +6 -3
  17. package/skills/legion-architect/references/acceptance.md +16 -6
  18. package/skills/legion-architect/references/decomposition.md +7 -3
  19. package/skills/legion-architect/references/design-gate.md +32 -17
  20. package/skills/legion-architect/references/wake-routing.md +4 -4
  21. package/skills/legion-controller/SKILL.md +30 -17
  22. package/skills/legion-controller/references/admission.md +2 -1
  23. package/skills/legion-controller/references/backlog-stewardship.md +17 -2
  24. package/skills/legion-controller/references/daily-report.md +7 -4
  25. package/skills/legion-controller/references/finding-asks.md +111 -14
  26. package/skills/legion-controller/references/slack-mentions.md +2 -2
  27. package/skills/legion-controller/references/triage.md +9 -5
  28. package/skills/legion-controller/references/wakes.md +8 -1
  29. package/skills/legion-worker/SKILL.md +5 -4
  30. package/skills/legion-worker/references/conflicts-and-rewrites.md +23 -16
  31. package/skills/legion-worker/references/findings.md +126 -31
  32. package/skills/legion-worker/references/merge-gate.md +48 -17
  33. package/skills/legion-worker/references/pr-body.md +9 -5
  34. package/skills/legion-worker/references/tools.md +22 -18
  35. package/skills/dispatch/references/api.md +0 -21
  36. package/skills/dispatch/references/asks.md +0 -43
  37. package/skills/dispatch/references/document-edits.md +0 -59
  38. package/skills/dispatch/references/documents.md +0 -79
  39. package/skills/dispatch/references/examples.md +0 -85
  40. package/skills/dispatch/references/issues.md +0 -66
  41. package/skills/dispatch/references/messages.md +0 -31
  42. package/skills/dispatch/references/reading.md +0 -32
  43. package/skills/dispatch-brainstorming/SKILL.md +0 -56
@@ -67,7 +67,7 @@ it, and any other change to the head voids it.
67
67
  Legion footer), and a `comments[]` array of `{path, line, side, body}`, one entry per
68
68
  finding — never one `pr review` call per finding (each submission fires a `pr-review` wake).
69
69
  A `COMMENT` decides nothing. After a
70
- conflict-forced rebase, compute the fingerprint (*The unchanged-diff check* in
70
+ forward merge, compute the fingerprint (*The unchanged-diff check* in
71
71
  `skill://legion-worker/references/conflicts-and-rewrites.md`) at the
72
72
  `commit_id` of your last submitted review and at the new head. Equal and that review was
73
73
  `APPROVE`: submit one more `APPROVE` naming the new head by SHA, its body naming both SHAs
@@ -75,24 +75,45 @@ it, and any other change to the head voids it.
75
75
  that review was `COMMENT` or `REQUEST_CHANGES`: continue that round against the new head;
76
76
  nothing restarts. Different, or either run printed no fingerprint: a new round — thermo again,
77
77
  one review. A return whose task's `Reason:` names a READY refusal is never a confirmation, even
78
- after a rebase, and a no-fix `REQUEST_CHANGES` that answered one is never continued: the round
78
+ after a forward merge, and a no-fix `REQUEST_CHANGES` that answered one is never continued: the round
79
79
  after it reviews the refused change in full from the last `APPROVE` (*Retro's commits do not
80
80
  void the reviewer's approval* below).
81
81
  - Answer every thread you opened, and every thread a bot opened that is none of Legion's role
82
82
  Apps, as `skill://legion-worker/references/review-threads.md` says; the same reference says
83
83
  when every thread is settled enough to approve, and resolving one never gates your approval.
84
+ - **Fix a prose-only finding yourself** — wording, a count, a tense, a citation — in your own
85
+ commit on the pull request, and list it in your review. Send the work back only for a finding
86
+ that touches code or behaviour. Push the fix **before** you submit the round's review, and
87
+ review the head that push made. A change pushed above the head you approved leaves the approval
88
+ standing for nothing: the round goes stuck and your architect is told (`ApprovalStands` in
89
+ `packages/daemon/internal/classify/decisions.go`, `stuckApprovedOtherCode` in
90
+ `packages/daemon/internal/workflow/review.go`, and the cases "the approval, then a code push"
91
+ and "a push since the approved head changed code, then an approval of the head" in
92
+ `packages/daemon/internal/workflow/review_test.go`). That push carries no `skip-checks: true`
93
+ line, so GitHub runs the branch's workflows on it. Wait for that head's required checks to be
94
+ green, and approve only then. The daemon sends nothing back for a red at a head the review App
95
+ pushed (`PlannedRed` in `classify/decisions.go`), so your own read is the only one. A red you
96
+ approve over reaches nobody: the tree runs on to the merge, where GitHub holds the pull request
97
+ under the base branch's required checks until someone reads them. A red there is a
98
+ `REQUEST_CHANGES` when the code is at fault, and your own commit to fix when your prose broke
99
+ it. The architect reads your prose commits at its acceptance (*The architect's acceptance*,
100
+ below), and that read is no new round. A round after a READY refusal is the one exception. Its
101
+ approval needs the tester's `E2E` line to name the head with only `.legion/` changed since. So
102
+ there you fix no prose yourself, and you name each prose finding in a `REQUEST_CHANGES`.
84
103
 
85
104
  A reviewer's phase ends with its completion, not with its review. Every round writes a handoff
86
105
  and takes this order: read CI as above; write, commit and push the handoff; submit the review of
87
106
  the head that push made, by its SHA; then complete. A red at the code is a `REQUEST_CHANGES`
88
107
  naming the check, never an approval. A request for changes does not wait, since it stands
89
- whatever CI says and the issue leaves reviewing with it. The approval waits on nothing at the
90
- handoff commit either: it ends the round whatever CI says there, since the round reads no CI
108
+ whatever CI says and the issue leaves reviewing with it. The approval waits on nothing at a
109
+ handoff-only commit either: it ends the round whatever CI says there, since the round reads no CI
91
110
  verdict, and a red the daemon reads later — at that commit or any above it — is the code's red
92
111
  and resumes the implementer from wherever the issue is (reviewing, retro or merging) without the
93
- reviewer. A review of a head the handoff push then replaces names a head the pull request no
94
- longer has. The daemon moves the issue once both are in — the decision GitHub reports and your
95
- completion, in either order — so a review posted without a completion leaves the issue in
112
+ reviewer. A head your own push changed beyond `.legion/` is the exception. The daemon marks it a
113
+ planned red and sends nothing back for it, so wait for its checks yourself (*Fix a prose-only
114
+ finding yourself*, above). A review of a head the handoff push then replaces names a head the pull
115
+ request no longer has. The daemon moves the issue once both are in — the decision GitHub reports
116
+ and your completion, in either order — so a review posted without a completion leaves the issue in
96
117
  reviewing until you finish.
97
118
 
98
119
  ## The architect's acceptance
@@ -103,9 +124,14 @@ reviewing until you finish.
103
124
  approval of the head by SHA, and for a parent that every child is done) and accepts with the
104
125
  `legion` tool's `sign_off`, which moves the issue to retro, or sends it back to `implementing`
105
126
  or `testing` with `request_backward_move` and a reason, which reaches the started worker's task
106
- as its `Reason:` line and counts a review round. The acceptance asks no `Production:` line of
107
- anyone: that line is written after the close (*The production check after the close*, below).
108
- CI red at the head in `accepting` returns the issue to `implementing` as it does in review.
127
+ as its `Reason:` line and counts a review round. The architect also reads the reviewer's own
128
+ prose commits here, with the rest of the evidence, and that read is no new round. It does not
129
+ accept while the issue holds a finding ask no controller has taken (`findings.md`, "When no
130
+ controller takes it"). The acceptance asks no `Production:` line of anyone: that line is written
131
+ after the close (*The production check after the close*, below).
132
+ CI red at the head in `accepting` returns the issue to `implementing` as it does in review. A
133
+ head the review App pushed beyond `.legion/` is the exception: the daemon marks it a planned red
134
+ and acts on it nowhere, and GitHub holds the merge under the base branch's own rules.
109
135
 
110
136
  ## Retro
111
137
 
@@ -246,11 +272,13 @@ reviewing until you finish.
246
272
  previous release, a daemon still running the previous commit, a deploy nobody has run): the
247
273
  summary names what is missing, and the `Production:` line stays at
248
274
  `pending deploy of <merge sha>`.
249
- - `fail`, for a defect: open a `dispatch ask` for it on the controller's report issue, as
250
- `skill://legion-worker/references/findings.md` says, publish its reference to the controller
251
- topic, and file no issue. It names this issue, the pull request, the merge commit, what you
252
- drove and what failed. Name the ask (`dispatch://<REPORT KEY>/ask/<id>`) in the `Production:`
253
- line and in the summary.
275
+ - `fail`, for a defect: file no issue, and take the path
276
+ `skill://legion-worker/references/findings.md` gives it. A defect with a clear fix goes to the
277
+ controller topic, with its evidence comment on the controller's report issue. A defect that
278
+ needs a decision is a `dispatch ask` on the report issue, and you publish its reference to the
279
+ controller topic. Either names this issue, the pull request, the merge commit, what you drove
280
+ and what failed. Name the message or the ask (`dispatch://<REPORT KEY>/ask/<id>`) in the
281
+ `Production:` line and in the summary.
254
282
  - **Nothing waits in the session for a deploy.** A `pending` ends the look. Where no wake is
255
283
  queued yet, the daemon suspends your claim with its session kept, and the issue's Sandbox goes
256
284
  with its pod and disk; where a wake arrived while the look was open, the next look starts at once
@@ -278,8 +306,11 @@ reviewing until you finish.
278
306
  run because production did not carry the merge within the bound.
279
307
  - **The record lives on the pull request alone.** Dispatch refuses a message, a comment or an ask
280
308
  on a closed issue (`ISSUE_CLOSED`), so the deployment instructions' "on the pull request and the
281
- issue" is the pull request for a closed issue, and a defect reaches Dispatch as the ask above.
282
- The architect accepted the issue before the merge and is asked nothing after it. `pass` or
309
+ issue" is the pull request for a closed issue. A defect reaches Dispatch by the two paths above:
310
+ a clear defect as the message to the controller, which gives it to the tree that owns that code,
311
+ and any other as the ask on the
312
+ controller's report issue. The architect accepted the issue before the merge and is asked
313
+ nothing after it. `pass` or
283
314
  `fail` closes the check: your claim is retired and the issue's Sandbox goes with its pod and
284
315
  disk, for a root as for a child. Neither ending retires or suspends anything of an issue back in
285
316
  the workflow (a root reopened inside its linger): its claim and pod are the new run's.
@@ -43,7 +43,7 @@ Verified the implementer's proof by <re-running its command | driving the same s
43
43
  **Production:** <what was checked in production, how, what was observed> — merge commit <sha>; woken by <event>.
44
44
  (written by the implementer from the production check after the merge closes the issue, naming what woke that look; `pending deploy of <sha>` until then)
45
45
 
46
- **Fast-follow:** <one named cleanup item and where it will land>, or "none".
46
+ **Fast-follow:** "none", or one named cleanup item in a file this pull request does not touch, and where it will land.
47
47
 
48
48
  **Chain:** stacked on <base bookmark> frozen at <sha> / not stacked.
49
49
  ```
@@ -124,13 +124,13 @@ and the tester's proof below are both this proof.
124
124
  tester names that missing surface as the blocker instead of passing the phase. Environment or
125
125
  secret-scrub evidence (e.g. "`LEGION_*`/`DISPATCH_*`/`ENVOY_*` unset") is recorded once, in
126
126
  `.legion/<issue>/test.json`, and only when the issue's acceptance criteria call for it — never
127
- re-pasted into the PR body each round. After a conflict-forced rebase, compute the
127
+ re-pasted into the PR body each round. After a forward merge, compute the
128
128
  fingerprint (*The unchanged-diff check* in
129
129
  `skill://legion-worker/references/conflicts-and-rewrites.md`) at the head your `E2E` line
130
130
  names and at the new head. Equal: re-run only the
131
131
  bare gates — the repository's CI green at the new head and its smoke check — and change the
132
132
  `E2E` line's head to the new SHA with
133
- `rebase re-check <old-sha> → <new-sha>: fingerprint unchanged, bare gates only`; the
133
+ `forward-merge re-check <old-sha> → <new-sha>: fingerprint unchanged, bare gates only`. The
134
134
  real-surface verification is not repeated. Different: a full test round.
135
135
  - **The implementer runs `skill://ce-simplify-code` once per pull request, in its first
136
136
  implementing round, after its own proof and before it completes that round, when the diff
@@ -144,8 +144,12 @@ and the tester's proof below are both this proof.
144
144
  That cost is why 0-applied is the expected outcome and a pass that applies is spent sparingly.
145
145
  The reviewer's pair runs at the head each round reviews. Record both in the `Thermo` line.
146
146
  - **No deferrals** is the body's rule (*PR body, review, and the merge gate* in
147
- `skill://legion-worker`): the `Fast-follow:` line holds naming, duplication, or wording cleanup
148
- only. A base frozen for others to stack on is never rewritten (*Rewriting pushed commits* in
147
+ `skill://legion-worker`): a wording nit, and any clear fix in a file this pull request touches,
148
+ is made in the running round, because a later round costs a CI pass and a re-read of the whole
149
+ change (the operator's answer on that case: fix it while you are there, and defer no fix to
150
+ another issue). The `Fast-follow:` line reads
151
+ `none`, or names only cleanup in files this pull request does not touch.
152
+ A base frozen for others to stack on is never rewritten (*Rewriting pushed commits* in
149
153
  `skill://legion-worker/references/conflicts-and-rewrites.md`); the `Chain` line records it.
150
154
  - **GitHub refuses a pull request body over 65,536 characters.** Each verification round links its
151
155
  evidence (the run, the comment) rather than inlining it once the body passes about 48,000
@@ -31,7 +31,7 @@ which you have; those need no runner.
31
31
 
32
32
  ## Why a sixth is an image change, not an install
33
33
 
34
- Your container is an unprivileged user with no writable system prefix: `/usr/local/bin` and `/opt`
34
+ Your role runs as an unprivileged user with no writable system prefix: `/usr/local/bin` and `/opt`
35
35
  are read-only, `/home` is read-only too, and `apt-get` cannot take dpkg's lock even to download.
36
36
  `$HOME` — `/home/legion`, the one writable directory under that read-only `/home` — is yours to
37
37
  write.
@@ -48,7 +48,7 @@ extraction prefix under `$HOME` with `LD_LIBRARY_PATH` set, and `psql` did not
48
48
  extraction prefix is on. What rules such an attempt out is not the unpacking: it is the next
49
49
  section's test ("The test: where it lands, and what it leaves resolving"), and an extraction under
50
50
  `$HOME` fails that test's first condition whatever you scope it to, because `/home/legion` is the
51
- container's own filesystem rather than the issue's volume — the placement `roleEnvironment`
51
+ pod's disk rather than the issue's volume — the placement `roleEnvironment`
52
52
  (`packages/daemon/internal/runtime/sandbox/manifest.go`) exists to move writes off. The sanctioned
53
53
  extraction is the harness's: onto the issue's volume, and exported around its own commands and no
54
54
  further (the Dispatch e2e stack, below, is the worked case). For everything the five do not cover,
@@ -68,7 +68,7 @@ three artefacts in this repository are what draw the line — not a claim about
68
68
  `uv` and `uvx`, `bun` with its `bunx` link, and the Go toolchain, and the `toolchain` capability
69
69
  row (`packages/daemon/internal/capabilities/capabilities.go`) grants them by name. A runner put
70
70
  in the image deliberately and granted by a capability row is not a route around a withheld one.
71
- - **What the pod configures for those runners to write.** A role container's environment relocates
71
+ - **What the pod configures for those runners to write.** A role's environment relocates
72
72
  `UV_CACHE_DIR` and `UV_PYTHON_INSTALL_DIR` (`podkind.go`) and `BUN_INSTALL_CACHE_DIR`, `GOCACHE`
73
73
  and `GOMODCACHE` (`roleEnvironment`, `packages/daemon/internal/runtime/sandbox/manifest.go`)
74
74
  onto the issue's volume, shared by the issue's roles. Legion provisions the caches a pinned run
@@ -91,8 +91,8 @@ whether through a variable the runtime sets for you there or under a path on tha
91
91
  harness names itself — but outside the issue's workspace (`$LEGION_WORKSPACE`, under
92
92
  `/legion/workspaces`) and the runtime's own directories (`/legion/repos`, `/legion/feed`); and
93
93
  after the command exits nothing else resolves differently. The workspace is excluded because its jj
94
- working copy tracks every new file under jj's `snapshot.max-new-file-size` (1 MiB in a role
95
- container), so a root written there is added to the change every role shares, and a larger new
94
+ working copy tracks every new file under jj's `snapshot.max-new-file-size` (1 MiB for a role), so
95
+ a root written there is added to the change every role shares, and a larger new
96
96
  file is left untracked, every jj command in the workspace then warning `Refused to snapshot some
97
97
  files`. The one write into it that is sanctioned is a repository's own dependency install into
98
98
  its ignored directories, below. The variables the runtime sets on the volume, where a tool's own
@@ -100,8 +100,9 @@ cache already goes, are
100
100
  `XDG_CACHE_HOME` (`/legion/scratch/cache`), `XDG_DATA_HOME`, `XDG_STATE_HOME`, `TMPDIR`, `GOCACHE`,
101
101
  `GOMODCACHE`, `BUN_INSTALL_CACHE_DIR`, `UV_CACHE_DIR` (`/legion/uv/cache`) and
102
102
  `UV_PYTHON_INSTALL_DIR` (`/legion/uv/python/<issue>`). (`XDG_CONFIG_HOME` is set too and is not
103
- one of them: it is the pod's shared in-memory volume.) `$HOME` and `/tmp` are not on the volume:
104
- both are the container's own filesystem.
103
+ one of them: it is your role's own config home, a copy of the pod's made for your role on the
104
+ pod's disk, which no other role reads.) `$HOME` and `/tmp` are not on the volume:
105
+ both are your role's own, on the pod's disk.
105
106
 
106
107
  It is the withheld capability when either condition fails — anything that writes into a
107
108
  system prefix, and anything that leaves a toolchain, shim or binary which a later launch or
@@ -158,22 +159,25 @@ home — worth reading before an install, for the two things that follow from sh
158
159
  `PLAYWRIGHT_BROWSERS_PATH` elsewhere is what costs you: that shared registry, and the lock that
159
160
  makes a second role installing at the same time wait rather than race. The condition is for a tool
160
161
  whose cache the runtime does not already place — Puppeteer is one, resolving its browser cache to
161
- `join(homedir(), '.cache', 'puppeteer')` and so to the container's own filesystem whatever
162
+ `join(homedir(), '.cache', 'puppeteer')` and so to your role's own home on the pod's disk whatever
162
163
  `XDG_CACHE_HOME` says. For that one, point the tool's own cache variable (`PUPPETEER_CACHE_DIR`
163
164
  here) at a directory under one of the paths above yourself, since an unset one leaves the download
164
165
  wherever the tool defaults, which is nowhere this runtime chose.
165
166
 
166
167
  Where it lands is what you pay. Under one of those variables it is on the issue's volume: it
167
- outlives a role's relaunch in its pod, and every role of the issue shares it — under every one of
168
- those variables but `XDG_STATE_HOME`, which is `/legion/scratch/state/<role>`, "the one directory
169
- of the issue's scratch that is the role's own rather than the issue's" (`roleEnvironment`),
170
- because Oh My Pi names its browser broker's lock from the state root and an abstract socket name
171
- is one name across every container of the pod — though not the pod itself: the issue's disk is
172
- the pod's own and is deleted with it, so a later pod of the issue starts without it. Under
173
- `$HOME` —
174
- `/home/legion`, which no volume is mounted on — it is the container's own filesystem: one copy per
175
- role, per pod, gone when the role relaunches, and charged to the container's ephemeral-storage
176
- limit, which is per role and smaller than the volume. Moving exactly these writes off that
168
+ outlives a role's relaunch in its pod, and every role of the issue is told the same path — under
169
+ every one of those variables but
170
+ `XDG_STATE_HOME`, which is `/legion/scratch/state/<role>`, a directory named for your role
171
+ (`roleEnvironment`) because Oh My Pi names its browser broker's lock from the state root and an
172
+ abstract socket name is one name across every role of the pod, which share its network. It is not
173
+ private: every role of the issue can write it, as it can every path above — though it does not
174
+ outlive the pod: the issue's disk is the pod's own and is deleted with it, so a later pod of the
175
+ issue starts without it. Under
176
+ `$HOME` — `/home/legion`, which no volume is mounted on — it is your role's own home, an overlay
177
+ over the image's whose writes land on the pod's disk (an `emptyDir` every role's home and `/tmp`
178
+ share): one copy per role, per pod, kept across the role's relaunches in that pod and gone when
179
+ the pod is replaced, and charged to the pod's ephemeral-storage limit, which every role of the pod
180
+ shares and which is smaller than the volume. Moving exactly these writes off that
177
181
  filesystem is what `roleEnvironment` (`packages/daemon/internal/runtime/sandbox/manifest.go`)
178
182
  exists for, after GKE Autopilot evicted issue pods that charged their caches to it — so an install
179
183
  under `$HOME` fails the first condition and is outside the sanctioned form, however little else it
@@ -1,21 +0,0 @@
1
- # Authentication and the HTTP API
2
-
3
- Read this to set up a Dispatch token, or to call a route no `dispatch` command covers.
4
-
5
- ## Agent authentication
6
-
7
- - Use a personal token. A human mints it in Dispatch **Settings → Agent tokens**. Supply it as `DISPATCH_TOKEN` or as `dispatch.token` in the session's `envoy.json`. The server records that human as the owner of your writes.
8
- - Which `envoy.json`: the session's own directory under an Oh My Pi profile other than `default`, a Claude Code `CLAUDE_CONFIG_DIR` or an OpenCode `OPENCODE_CONFIG_DIR`. Otherwise the file `ENVOY_CONFIG_FILE` names, else `~/.config/legion/envoy.json` (under `$XDG_CONFIG_HOME` when set).
9
- - The plugin passes its choice in `ENVOY_CONFIG_FILE` with `DISPATCH_HOST`. In a Claude Code Bash that lacks both, `CLAUDE_CODE_CHILD_SESSION=1` makes the command read `CLAUDE_CONFIG_DIR`'s file. A Legion pane reads the `DISPATCH_TOKEN_FILE` path the daemon sets.
10
- - A `dispatch.token` written `!<command>` is a command that prints the token.
11
- - A checkout's `.envoy/envoy.json` sets `dispatch.serverUrl` and `dispatch.token` only together. Its `envoy` section is not read. A file's token goes only to its own server: a `DISPATCH_URL` for another server needs `DISPATCH_TOKEN` or `DISPATCH_TOKEN_FILE`.
12
- - Never configure `DISPATCH_AGENT_TOKEN` (the shared devbox fallback) for one agent.
13
- - Never set the sign-in callback origin in an agent's `envoy.json`. It is `DISPATCH_SERVER_URL` in the deployment `compose/.env`: the exact URL humans type. `<DISPATCH_SERVER_URL>/auth/callback` is registered on the sign-in client (`DISPATCH_SIGNIN_CLIENT_ID`).
14
-
15
- ## Finding a route
16
-
17
- - `GET /api/v1` (no credential) lists every route as `{method, path, auth, description}`. `auth` is `public`, `any`, `human` (a bearer gets `403 HUMAN_ONLY`) or `bearer`.
18
- - `404 NOT_FOUND` with "GET /api/v1 lists every route" means a wrong path. Read the index, do not guess.
19
- - Branch on an error's `code`, never its text.
20
- - `GET /api/v1/issues` returns every match unpaged, or one page with `limit` (1–250) or `offset`: `{issues, total, limit, offset}`. Only the unpaged array is exact: a page walk can repeat or skip an issue that changes. `cursor` is `400 INVALID_QUERY`. `dispatch issues` pages for you.
21
- - Event logs page with `after` or `before` and `limit`. `GET /api/v1/search` takes `limit` up to 50 with no next page. Other routes do not page.
@@ -1,43 +0,0 @@
1
- # Asks after they open: who answers, editing, resolving, replying, and following
2
-
3
- Read this once an ask is open. Whether to ask, and how to write it, is "Before you ask" in `skill://dispatch`.
4
-
5
- ## Who answers an ask
6
-
7
- - An ask goes to the issue's assignee's Inbox: the `Assignee:` that `dispatch read` prints decides whose, not the project's default. On an unassigned issue it is in nobody's default Inbox or `Needs you` count. A project's default assignee fills in only when an issue is created with nobody named and nothing earlier in the chain below names someone; an issue created with an explicit nobody, set to nobody later, or created before the default was set stays unassigned.
8
- - `dispatch whoami` prints whom you act for: your token owner's email, or no owner (a service token shows as `<namespace>/<name>`, as your writes do). An issue you create goes to your owner, else inherits its parent's assignee, else goes to the project's default assignee when a person set one on the Settings page, else stays unassigned.
9
- - On an unassigned issue where the answer matters, assign it to your owner, or name in the question who should answer. Assign with `PATCH /api/v1/issues/{key}` and `{"assignee": "<email>"}`. An email nobody signed in with gets `ASSIGNEE_NOT_ALLOWED`.
10
- - Never reassign an issue a human holds to get a faster answer. That is the human's call.
11
-
12
- ## Handing a human a to-do, editing, resolving, and replying
13
-
14
- - A to-do follows the question-and-options contract of "Writing for the human". For a human-only action, what it changes and risks constrains the answer. No label is special. If an outcome needs a reason, say so in its description.
15
-
16
- ```bash
17
- dispatch ask --issue DSP-42 \
18
- --question 'The fix is merged but cannot run in production: the signing certificate it needs expired, and only its owner can renew it in the registrar console. Renewing replaces the certificate every client checks. How should we proceed? Recommendation: renew it now because the fix cannot run without it.' \
19
- --option 'Renew the certificate: Lets the fix run but replaces the certificate every client checks.' \
20
- --option 'Leave it expired: Changes nothing now but leaves the fix unable to run.'
21
- ```
22
-
23
- - Correct an open ask in place with `dispatch edit-ask`, not a second ask, while the same decision stays open. The human then reconfirms. You cannot edit an answered or resolved ask. If the decision of an open ask changed, retract it and open a new one. If the decision of an answered ask changed, open a new one.
24
- - `edit-ask` on a block ask writes the block too, so it mints a version and closes a spec's design gate until approved. Change anything else about the block with `dispatch doc-edit`. A retry with unchanged fields is safe.
25
- - A human can change an answer. A new `ask.answered` carries `previous_answer`. Act on the newest.
26
- - When the selected option and the typed text disagree, the text is the answer. Act on it. To clarify, open a new ask.
27
- - Retract a moot or superseded ask, or resolve one whose answer you found:
28
-
29
- ```bash
30
- dispatch resolve-ask --ask <id> --kind retracted --reason 'A newer ask supersedes this question.'
31
- ```
32
-
33
- - Use `retracted` for obsolete, `resolved` for found. Always give a reason: the question stays in its card. Resolution never records a human decision. An answered ask cannot be resolved.
34
- - `doc-edit` refuses to delete an open ask's block. A block deleted another way (an upload, the browser) closes its ask. Restoring it reopens the ask.
35
- - Reply under an ask with `dispatch comment --reply-to-ask <id>`. Close your review comment with `dispatch resolve-comment` ([Documents](skill://dispatch/references/documents.md#comments-and-suggestions)).
36
- - A human reply while the ask is open (`ask_state: open`) is a request for clarification, not an answer. Answer in the thread with `--reply-to-ask`, or reword with `edit-ask`. Do not open a second ask.
37
- - Each reply sets whose turn it is. Your default reply (`--turn human`) puts the ask in the human's `Waiting on you`. For any progress or working-on-it note, use `--turn agent`. A human reply hands the turn to you. Reads show it as `waiting_on`.
38
-
39
- ## Following
40
-
41
- - You follow an ask you opened or replied to, or one a human added you to. Its answer, edits, resolution and replies reach your agent topic directly.
42
- - Leave or rejoin: `dispatch follow --ask <id> --action unfollow` (or `follow`). `--ask` takes a full id, a `dispatch://KEY/ask/<id>`, or an 8+ hex prefix unique among your open asks. Removal arrives as `ask.follower_removed`, an add as `ask.follower_added`.
43
- - No write subscribes you to an owner. Replies to your comments and new comments on your documents also reach you. For everything else, subscribe: `envoy_subscribe notifications.dispatch.issue.<KEY>.>` or `envoy_subscribe notifications.dispatch.document.<PROJECT>.<SLUG>.>`. The topic carries every event. `notify` controls only wake and routed delivery. A human can unsubscribe you (`subscription.removed`).
@@ -1,59 +0,0 @@
1
- # Editing a document
2
-
3
- Edit every Dispatch document (an issue's spec, a project document) in place with `dispatch doc-edit`. Never upload it again. `dispatch doc-edit --help` has the full rules for each operation: markers, task boxes, hard breaks, anchors and preconditions.
4
-
5
- ```bash
6
- dispatch doc-edit --issue <KEY> --artifact spec --summary '<names the version>' --ops-json-file - <<'EOF'
7
- [{"op": "replace", "find": "old sentence", "with": "new sentence"}]
8
- EOF
9
- ```
10
-
11
- - Ops: `replace`, `delete`, `insert`, `retype`, `move`, `delete_row`, `delete_column`. Keys: `op`, `find`, `with`, `occurrence`, `markdown`, `after`, `before`, `block`, `index`, `type`, `attributes`. An unknown key is refused before sending. A misspelled `with` would turn the `replace` into a delete.
12
- - The output names the version, `unchanged_ops`, `lost_ops`, and a `Document token:`.
13
- - Use `--summary` to name a version that records a decision.
14
-
15
- ## Targeting text
16
-
17
- - Quote text as rendered: inline code without backticks, bold without asterisks, link text without syntax, a cell by its text, code without fences. One quote stays in one textblock. Split changes that span blocks.
18
- - A miss names the three nearest blocks. A `heading:` anchor must match the whole heading. Use zero-based `occurrence` for a repeat. Re-read before you retry a missing or ambiguous target.
19
- - `dispatch doc-read --blocks` lists each live block's id, type, token, anchor count and opening words, and each table row and cell index. Only live ids address an edit.
20
-
21
- ## Rewriting paragraphs
22
-
23
- - **One `replace` per paragraph, then a read-back.** A `with` of two paragraphs is refused. Each `replace` keeps the block id and the anchors outside the text you change. An anchor on changed text loses its quote but keeps its pin.
24
- - More new paragraphs than old: add the rest as **one** `insert` holding them all, `after` the last rewritten paragraph as it now reads. Two inserts at one anchor come out reversed. An insert lands after the top-level block that holds the quote.
25
- - Fewer: `delete` each leftover paragraph by its whole text or `block` id.
26
- - `replace` keeps a block's kind. When the new text adds a non-paragraph block (heading, list, table, quote, rule, fence or typed block) beside paragraphs, `insert` it beside the paragraph you replaced. Only when no paragraph of the new text stays, `delete` the old block, then `insert` the new one, anchored on the block before or after it.
27
- - An empty `with` deletes the matched text on purpose and leaves an empty block. Read the passage and its neighbours back with `dispatch doc-read`. A grep for your new words is not a check.
28
- - Dispatch refuses block HTML (`<div>`, `<!-- -->`, a lone `<br>` paragraph) in `with`. Inline HTML stays.
29
- - To add a rule, `insert` `***`. A `---` in `with` is stored as `\---`.
30
-
31
- ## Blocks and ids
32
-
33
- - A block loses its id only when deleted: by `delete`, by `delete_row` / `delete_column` (its cells), or by a `move` that empties its container. Its anchors go with it. `replace`, `insert`, `move` and `retype` keep ids.
34
- - `delete` of a block's whole text deletes the block. A partial match keeps it. A bullet with a nested list hoists the list. A bullet with other content needs `delete {block:"<item id>"}`.
35
- - Dispatch refuses a `delete` or `retype` that takes out an open ask's block, delete-then-insert included. Reword with `replace`, relocate with `move`, or use `dispatch edit-ask`. Moving an open block out of a container and deleting the container takes two calls.
36
- - `retype` turns a paragraph or typed block into a typed block in place, keeping its id. Use it when a paragraph is the question that should become a decision.
37
- - An ask block has two ids. The block id (`:::ask{#<id> …}`) goes bare in `block` for `move` / `delete` (`block:<id>` only in `before` / `after`). `dispatch read --ref dispatch://KEY/ask/<id>` takes either id, whole or as a unique prefix of 8 or more hex characters, and prints the ask id as `Ask id:`. `dispatch open-asks` lists ask ids. `edit-ask`, `resolve-ask`, `follow` and `--reply-to-ask` take only the ask id.
38
-
39
- ## Tables
40
-
41
- - `delete_row` / `delete_column` take the table `block` and a zero-based `index`. Row 0 is the header. The last body row and a row's last column cannot go. Open asks or unresolved comments on the cells block it.
42
- - Inserting bare body rows (no header or delimiter) at a cell quote extends that table. A wider row is `TABLE_WIDTH`. A full table with a header pasted there becomes a second table.
43
-
44
- ## Stale and concurrent edits
45
-
46
- - Guard with `--precondition-json`: `{"document": "<token>"}` or `{"blocks": [{"id": "<id>", "token": "<token>"}]}`. Use a document token for `insert` and `move`. A block guard must cover every block the batch changes.
47
- - Chain guarded edits by passing each output's token to the next. Read once, not once per edit.
48
- - `409 PRECONDITION_FAILED` or `409 EDIT_LOST_TO_CONCURRENT_CHANGE`: nothing written. Re-read and decide again. Non-empty `lost_ops` on success: a browser deleted that text after the write. Re-read before you build on it. A result that could not check survival: re-read.
49
- - A batch that changes nothing mints no version, even with `--summary`. `POST /api/v1/artifacts/<id>/versions` names the current state as a version on purpose.
50
-
51
- ## Edge cases
52
-
53
- - Inside a code block, `with` is the code's literal text. Trailing line breaks, and whitespace-only lines in a list item's code, do not survive a read.
54
- - A CR LF or lone CR in any text you write is stored as a line feed.
55
- - Replacing a non-paragraph block writes its marker as text. A backtick fence becomes an inline code span. A tilde fence stays literal.
56
- - An anchor that straddles the rewritten text keeps its mark on the words you left, with a shorter quote.
57
- - Emptying a task item's first paragraph while another block follows it is `INVALID_OP`.
58
- - A later op in one batch that names a block an earlier `delete {block}` took out fails as `INVALID_OP`. So does a move anchored inside the moved block, or a delete that leaves a typed block without its required body.
59
- - An op that only takes text out is never reported lost.
@@ -1,79 +0,0 @@
1
- # Documents: typed blocks, comments, suggestions, and artifacts
2
-
3
- Read this to write a typed block, comment, suggest, upload an artifact, request approval, or handle `DOC_SCHEMA`, `INVALID_ASK_BLOCK` or `DOC_SERVICE_UNAVAILABLE`. Text and table edits are [Editing a document](skill://dispatch/references/document-edits.md).
4
-
5
- ## Typed blocks
6
-
7
- - Use only the types, content rules, attributes and enum values from `GET /api/v1/schema/blocks`. Quote values: `:::callout{kind="warning" title="Risk"}`. Never set an attribute marked `server: true`.
8
- - Write the container form only: `:::name{#block-id key="value"}` on its own line, block children, and a closing line with the same number of colons. A block inside another, or one whose code holds a `:::` line, needs the outer fence one colon longer. Never write Pandoc `::: {.callout}`, leaf `::name` or text `:name`.
9
- - Dispatch refuses an unclosed block, and an opening indented four or more columns under a paragraph. An opening inside a line is stored as text.
10
- - For a new block, omit `#block-id`: Dispatch mints it. When you edit one, keep its id and every attribute. Never copy an id into new markdown. To rewrite a block whole, `delete` it, then `insert` it with its id, in one batch. You cannot do this to an open ask's block: use `replace`, `move` or `dispatch edit-ask`.
11
- - Questions about a document are `ask` blocks, never an "Open questions" section. The body is question paragraphs, then an optional bullet list of `Label: description` options. A code block, heading or quote, a paragraph after the options, or a second list is `INVALID_ASK_BLOCK`.
12
-
13
- ```md
14
- :::ask{urgency="high" multiple="false"}
15
- Today's release is blocked by a database migration. The maintenance window closes in two hours;
16
- whether production data needs an index rebuild is unknown. How should we complete the migration?
17
- Recommendation: rehearse on a production snapshot, then apply in the window, because it finds the
18
- unknown cost before production while keeping today's release possible.
19
-
20
- - Apply now: Meets today's release, but recovery may be slower if the index rebuild is needed.
21
- - Rehearse then apply: Costs rehearsal time, but exposes the rebuild and rollback cost before production.
22
- - Defer the release: Avoids migration risk today, but leaves the release and its fixes unavailable.
23
- :::
24
- ```
25
-
26
- - A human's answer lives on the ask. Never rewrite the question into its answer or blank its options. Resolve without a human with `dispatch resolve-ask`.
27
- - An answered or resolved ask's question is fixed. Dispatch writes its block back. To ask something new, write a new block. An open ask's block is its text.
28
- - `This spec holds no ask blocks …`: either no decision is needed, or you forgot the block. `… typed-block openings in this document are text, not blocks`: fix the markdown and upload again. Put an intended mention in code.
29
- - Neither check sees a spec wrapped in a code fence, an opening like `::ask{` or `:::ask {`, or any `doc-edit`. After an edit that writes an ask, `dispatch doc-read` the section and check that it renders `:::ask{#<id> …}` on its own line.
30
-
31
- ## Comments and suggestions
32
-
33
- ```bash
34
- dispatch comment --issue <KEY> --artifact spec --quote '<the passage>' --body '<the question or note>'
35
- dispatch suggest --issue <KEY> --artifact spec --quote '<exact text>' --replace-with '<replacement>'
36
- ```
37
-
38
- - An exact replacement is a suggestion, never a comment: a human accepts it with one click. A comment is for a question or note answered in words.
39
- - Omit `--quote` and `--artifact` for a floating comment. A reply (`--reply-to`, `--reply-to-ask`) takes no quote. Threads are flat. A reply reopens a resolved thread, except a decided suggestion's. `--turn` works only with `--reply-to-ask`.
40
- - `--ref` names the owner only. Dispatch refuses an ask, comment or message link there.
41
- - Resolve your own comment once addressed: `dispatch resolve-comment --comment <id>`. It takes no reason, so reply first if needed. Resolve only threads you opened or were asked to close. Reopening a resolved thread from the dashboard is human-only, but your reply to it reopens it, except a decided suggestion's.
42
- - Suggest only what the quoted block can hold. The accept checks it, and a refused accept stays open.
43
- - Error codes: `TARGET_AMBIGUOUS` (add `--occurrence`), `TARGET_NOT_FOUND` (re-read), `INVALID_ANCHOR` / `ANCHOR_MISSING` / `ANCHOR_ORPHANED` (bad or stale quote), `INVALID_MARKDOWN` / `DOC_SCHEMA`, `CAP_EXCEEDED`, `ISSUE_CLOSED`.
44
-
45
- ## Artifacts
46
-
47
- ```bash
48
- dispatch artifact --issue <KEY> --name diagram.png --path ./diagram.png
49
- dispatch artifact --issue <KEY> --name load-test-results.md --content-file - <<'EOF'
50
- # Load test
51
- EOF
52
- ```
53
-
54
- - Uploading the same `--name` makes its next version. Never upload `spec.md`: it replaces the human's spec. Edit with `dispatch doc-edit`.
55
- - Address an artifact by the slug the upload printed. A bare `--artifact` that names two documents (a slug and a filename) is refused with both ids. A `dispatch://` reference always uses the slug.
56
- - A copy of another document's `:::ask` block (same `#id` and text, same owner) opens no ask. It mirrors the original. Answer the original. Change the copy's text and it opens its own ask.
57
- - A picture from an Agents-page conversation is `dispatch://agent/<session id>/artifact/<slug>@vN`. `dispatch doc-read` reads it by that reference alone.
58
- - Show a picture inline with `--image` on `message`, `comment` or `ask`, not `dispatch artifact`. Each adds about 60 characters that count toward the cap. A plain `dispatch://KEY/artifact/<slug>` link to an image shows a thumbnail.
59
- - `dispatch read` and `dispatch doc-read` write pictures to files and print `- picture: <path>`. Open each with your file-read tool. A session sees each picture once. `dispatch doc-read` shows it again, after compaction too.
60
- - Documents are CommonMark. Put Slack mrkdwn or other non-Markdown in a fenced code block.
61
- - In a table cell, write every `|` as `\|`, inside code and links too. A row wider than its table is refused.
62
-
63
- ## Approval requests
64
-
65
- ```bash
66
- dispatch request-approval --issue <KEY> --summary '<what the human is approving>'
67
- ```
68
-
69
- - It opens an `Approve` / `Request changes` ask on the latest version. It is refused while a decision block is open. A block written in the last few seconds counts as open.
70
- - An answer or resolve reaches a version about two seconds later. Wait for that version, do not edit to force one. A waiver carries no record: write the decision into the text before you request.
71
- - A document holds one open request. A new version moves it and leaves it `waiting on agent`. Run the command again when "Approval of a spec" allows, to hand it back. Your own thread reply hands it back too, unless you reply with `--reply-to-ask` and `--turn agent`. A `--reply-to` reply takes no turn flag, so it takes the default, `human`. Exception: a new version moved the request since your last `dispatch request-approval`. Then no reply hands it back: run `dispatch request-approval` again. While it waits on the human, a different `--summary` is refused.
72
- - The answer arrives as `artifact.approved` or `artifact.changes_requested` with its version. `changes_requested` carries the reason: that is your next work. `stale` means approved, then changed.
73
- - A human's own answer to a block can carry their approval forward. That needs no change outside the blocks, and their answer on every new or answered block. Then `dispatch doc-read` shows `Approval: approved vN`. Anything else leaves it stale, and the card shows a diff: make the next `--summary` one sentence on what changed.
74
-
75
- ## A document that is reloading
76
-
77
- - `DOC_SERVICE_UNAVAILABLE` (503) means the document's live room is reloading. Your call wrote nothing. Nothing retries for you. Wait a few seconds and repeat the same call.
78
- - A second refusal in a row: tell the human, with the document's reference.
79
- - It comes from calls that write a document. Those are `doc-edit`, a quoted `ask` or `comment`, a reply in an anchored thread, and `suggest`. Also `resolve-comment` on an anchored comment, an `artifact` that replaces a document, and `edit-ask` / `resolve-ask` on a block ask. `doc-read` returns it only when the durable copy is unreadable.
@@ -1,85 +0,0 @@
1
- # Before and after: a decision block, a reply, a message, and a draft
2
-
3
- ## Before / after
4
-
5
- Before: a wall of text hides the decision, and the human never gets it.
6
-
7
- ```text
8
- The deploy branch has the migration and dashboard changes. Staging is fine, but release notes are
9
- not reviewed before tomorrow's customer demo. Review the notes, ship without them, or cut the
10
- dashboard from the release. Waiting is safest, but the list above may be stale.
11
- ```
12
-
13
- After: a decision block in the section it concerns, each real option a button.
14
-
15
- ```bash
16
- dispatch doc-edit --issue LEGION-815 --artifact spec --ops-json-file - <<'EOF'
17
- [{
18
- "op": "insert",
19
- "after": "Release requires reviewed operator instructions before deployment.",
20
- "markdown": ":::ask{#release-gate urgency=\"high\"}\nRelease 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.\n\n- Review notes, then ship: Delays release for review but keeps the release complete and reviewed.\n- Ship now: Meets the demo deadline but leaves the release notes unreviewed.\n:::"
21
- }]
22
- EOF
23
- ```
24
-
25
- Before: a progress note nobody needs.
26
-
27
- ```bash
28
- dispatch message --issue LEGION-815 --body 'Merged the release PR, moving to docs next.'
29
- ```
30
-
31
- After: nothing. The PR shows the merge. Post only when a human must act or can use a deliverable:
32
-
33
- ```bash
34
- dispatch message --issue LEGION-815 --body 'Release 1.4 is live on the devbox (dispatch://LEGION-815/artifact/release-notes). Nothing needed from you.'
35
- ```
36
-
37
- Before: the draft is a separate file, the spec only names it, and the ask does not link it.
38
-
39
- ```bash
40
- dispatch artifact --issue OPS-52 --name cu-update-2026-09-15.md --content 'Hi team, ...'
41
- dispatch doc-edit --issue OPS-52 --artifact spec \
42
- --ops-json '[{"op":"insert","after":"## Context","markdown":"## Draft (artifact cu-update-2026-09-15.md)"}]'
43
- ```
44
-
45
- After: the draft is a spec section, and the ask (a human-only send) anchors there. A real file is linked by its slug from both.
46
-
47
- ```bash
48
- dispatch doc-edit --issue OPS-52 --artifact spec \
49
- --ops-json '[{"op":"insert","after":"## Context","markdown":"## Draft\n\nHi team, ..."}]'
50
- dispatch ask --issue OPS-52 --anchor-json '{"artifact":"spec","quote":"Hi team,"}' \
51
- --question '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.' \
52
- --option 'Send the reviewed update: Delivers the update today but makes its text external.' \
53
- --option 'Hold the update: Avoids sending now but leaves customers without the update.'
54
- # or, for a real file:
55
- dispatch doc-edit --issue OPS-52 --artifact spec \
56
- --ops-json '[{"op":"insert","after":"## Context","markdown":"## Draft\n\nThe update to send: dispatch://OPS-52/artifact/cu-update-2026-09-15-md"}]'
57
- dispatch ask --issue OPS-52 --ref dispatch://OPS-52/artifact/cu-update-2026-09-15-md \
58
- --question '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.' \
59
- --option 'Send the reviewed update: Delivers the linked update today but makes its text external.' \
60
- --option 'Hold the update: Avoids sending now but leaves customers without the update.'
61
- ```
62
-
63
- ## Naming the mechanism
64
-
65
- Before: the reply paraphrases its tool, route and event away.
66
-
67
- ```text
68
- Today, when a spec is ready, the architect tells Legion so with a dedicated call on its Legion
69
- tool, and Legion then waits for your approval. This change removes that call, because Legion will
70
- notice your approval of the spec by itself.
71
- ```
72
-
73
- After: the reader's sentence first, then each mechanism by name, with how it works in a clause.
74
-
75
- ```text
76
- For you nothing changes: you still click Approve on the spec. What goes is a step the architect
77
- takes today: it calls `register_gate` on its `legion` tool, which posts to the daemon's
78
- `POST /legion/v1/gates/register` route and names the spec document and the version that gate the
79
- tree. The daemon opens the gate when Dispatch emits `artifact.approved` for that document at that
80
- version, which Dispatch does when you click Approve on the request the architect opened with
81
- `dispatch request-approval`. Without `register_gate`, the daemon reads the issue's primary
82
- document from Dispatch itself and waits for the same event.
83
- ```
84
-
85
- "A dedicated call", "tells" and "notice" each stood for something the reader can search, find in a log, or set.