@sjawhar/pi-envoy 8.4.0 → 8.6.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.
@@ -1,16 +1,19 @@
1
1
  # Editing a document
2
2
 
3
- Every document a Dispatch tool writes — an issue's spec, a project document — is edited in place
4
- with `dispatch_doc_edit`, never re-uploaded. "The Spec" in `skill://dispatch` sends you here for
5
- the tool's shape, how to target the text you mean, what each operation costs a block, and how to
3
+ Every document Dispatch holds — an issue's spec, a project document — is edited in place
4
+ with `dispatch doc-edit`, never re-uploaded. "The Spec" in `skill://dispatch` sends you here for
5
+ the command's shape, how to target the text you mean, what each operation costs a block, and how to
6
6
  reject a stale edit.
7
7
 
8
- ```ts
9
- dispatch_doc_edit({ issue?, project?, artifact, ops, precondition?, summary? })
8
+ ```bash
9
+ dispatch doc-edit --issue <KEY> --artifact spec --summary '<names the version>' --ops-json-file - <<'EOF'
10
+ [{"op": "replace", "find": "old sentence", "with": "new sentence"}]
11
+ EOF
10
12
  ```
11
- It returns issue or project-document owner details plus `applied`, optional `version`, `changed`,
12
- `unchanged_ops`, `lost_ops`, and the document token this edit produced, rendered as a
13
- `Document token: <token>` line. `ops` is an array of this exact `EditOp` shape:
13
+ `--project` replaces `--issue` for a project document, and `--precondition-json` guards against a
14
+ stale edit (below). It prints whether the edit applied, the version it wrote, which operations
15
+ changed nothing (`unchanged_ops`) or were lost to a concurrent change (`lost_ops`), and the document
16
+ token this edit produced, as a `Document token: <token>` line. The ops are a JSON array of this exact `EditOp` shape:
14
17
 
15
18
  ```ts
16
19
  type EditOp = {
@@ -28,8 +31,8 @@ type EditOp = {
28
31
  };
29
32
  ```
30
33
 
31
- An operation takes only these keys. A key it does not declare is refused before the call leaves
32
- your process, naming the operation and its keys, because every key but `op` is optional: a
34
+ An operation takes only these keys. A key it does not declare is refused before the command sends
35
+ anything, naming the operation and its keys, because every key but `op` is optional: a
33
36
  misspelled `with` would otherwise be dropped and the `replace` would delete the text you meant to
34
37
  rewrite.
35
38
 
@@ -93,7 +96,7 @@ paragraph is stored `\---`, not a rule, so to add a rule, `insert` it beside the
93
96
  `***`). A CR LF or a lone carriage return in any text you write - an insert's markdown, a `with` in text or in code,
94
97
  a suggestion, an upload or a spec - is stored as a line feed, as the browser editor reads both. Use zero-based
95
98
  `occurrence` for a
96
- repeated target; re-read a missing or ambiguous target before retrying. Pass `summary` to name the version when recording a decision.
99
+ repeated target; re-read a missing or ambiguous target before retrying. Pass `--summary` to name the version when recording a decision.
97
100
 
98
101
  **Rewriting several paragraphs is one `replace` per paragraph, then a read-back.** `replace` is inline:
99
102
  each `with` is the new text of one paragraph, and outside a code block a `with` that forms two paragraphs is
@@ -131,17 +134,17 @@ the old block's place is it a `delete` of the old block and then an `insert` of
131
134
  block before or after it. The delete is what costs the id (below); a typed block keeps its id when the insert
132
135
  carries it, which works only in that order, because an insert carrying an id the document still holds is refused.
133
136
  The tools refuse a `delete` or `retype` that would take out an `ask` block whose ask is still open, delete-then-insert included:
134
- reword it with `replace`, relocate it with `move`, or change its question, options, urgency or `multiple` with `dispatch_edit_ask` if you asked it. A single
137
+ reword it with `replace`, relocate it with `move`, or change its question, options, urgency or `multiple` with `dispatch edit-ask` if you asked it. A single
135
138
  batch that moves an open block out of a container and then deletes the container is refused; make them two calls (the check reads
136
139
  the document as it stood before the batch).
137
140
  Then read the document back with
138
- `dispatch_doc_read` and read the passage and its neighbours, not a grep for the words you added: an empty
141
+ `dispatch doc-read` and read the passage and its neighbours, not a grep for the words you added: an empty
139
142
  `with` deletes the matched text on purpose, so a `replace` whose `with` you meant to fill
140
143
  empties that paragraph — the block and its id stay, holding nothing — and only a read shows what the document now says.
141
144
 
142
145
  A batch that leaves the document's semantic identity unchanged — including its inline anchor marks, so an edit that only orphans a
143
146
  comment or ask anchor still mints its version — mints no version, named or not: the response carries
144
- `changed: false` with `unchanged_ops` naming each operation that did nothing, and the tool result says nothing changed. A `summary`
147
+ `changed: false` with `unchanged_ops` naming each operation that did nothing, and the output says nothing changed. A `--summary`
145
148
  does not force a version for such a batch; `POST /api/v1/artifacts/<id>/versions`, which names the current state on purpose, still does.
146
149
 
147
150
  A `delete` whose `find` is a block's entire text removes the block itself — the bullet, paragraph, or heading, not just its words — and
@@ -165,11 +168,11 @@ whose anchor lies inside the moved block, or a delete that would leave a typed b
165
168
  `INVALID_OP` naming the field and the rule.
166
169
 
167
170
  `GET /api/v1/artifacts/<artifact UUID>/blocks` includes a full-state `token` on every block, including
168
- inline marks. To reject a stale edit, pass `precondition` with exactly one of
169
- `{ document: "<document token>" }` or
170
- `{ blocks: [{ id: "<block id>", token: "<block token>" }] }`. A document token comes from
171
- `dispatch_doc_read` or from the previous `dispatch_doc_edit`, whose result carries the token its
172
- own edit produced: a run of guarded edits passes each result's token to the next one and reads the
171
+ inline marks. To reject a stale edit, pass `--precondition-json` with exactly one of
172
+ `{"document": "<document token>"}` or
173
+ `{"blocks": [{"id": "<block id>", "token": "<block token>"}]}`. A document token comes from
174
+ `dispatch doc-read` or from the previous `dispatch doc-edit`, whose output carries the token its
175
+ own edit produced: a run of guarded edits passes each output's token to the next one and reads the
173
176
  document once, not once per edit. The server resolves the whole batch before
174
177
  mutation: a block guard must cover every content block it changes, or Dispatch returns
175
178
  `400 INVALID_PRECONDITION` without applying anything. Use a document token for insert and move because they
@@ -197,6 +200,6 @@ an existing paragraph is the question that should become a decision.
197
200
 
198
201
  An ask block has two ids: the block id, shown as `:::ask{#<id> …}` in the rendered document and
199
202
  taken bare by `move`/`delete` in `block` (the `block:<id>` form is only for `before`/`after`
200
- anchors), and the ask id, which `dispatch_open_asks`, the dashboard's ask links (`/asks/<id>`,
201
- `?ask=`), `dispatch_read` and `dispatch_comment({ reply_to_ask })` use. They differ;
203
+ anchors), and the ask id, which `dispatch open-asks`, the dashboard's ask links (`/asks/<id>`,
204
+ `?ask=`), `dispatch read` and `dispatch comment --reply-to-ask` use. They differ;
202
205
  `dispatch://KEY/ask/<block-id>` answers `not found`.
@@ -15,7 +15,7 @@ Dispatch writes its fences that way. An unclosed typed block at document level i
15
15
  opening line that continues a paragraph instead of standing on its own (indented four or more
16
16
  columns under the paragraph's text) is refused, naming the line's number and text - `INVALID_OP` on an edit's `markdown` or `with`, `INVALID_MARKDOWN` on any other write - since it
17
17
  would be stored as the paragraph's text; start the block on its own line. An opening written inside
18
- a line is stored as text, and `dispatch_issue` and `dispatch_artifact` quote it back as a
18
+ a line is stored as text, and `dispatch issue` and `dispatch artifact` quote it back as a
19
19
  typed-block opening that is text, not a block. For
20
20
  a new typed block, omit `#block-id`; Dispatch mints it. When editing an existing typed block, retain
21
21
  its id and every rendered attribute. Never copy an existing block's id into new markdown: an id
@@ -24,9 +24,9 @@ outside the text it replaces is refused naming the id: `INVALID_OP` for an inser
24
24
  `INVALID_MARKDOWN` for any other write. To rewrite such a block whole, `delete` it and then
25
25
  `insert` the new one carrying its id, anchored on the block before or after it, in that order and
26
26
  in one batch: an insert carrying an id the document still holds is refused. An `ask` block whose ask
27
- is still open cannot be rewritten that way: the tools refuse the `delete`, so reword it with
27
+ is still open cannot be rewritten that way: `dispatch doc-edit` refuses the `delete`, so reword it with
28
28
  `replace`, relocate it with `move`, or change its question, options, urgency or `multiple` with
29
- `dispatch_edit_ask` if you asked it ([Editing a document](skill://dispatch/references/document-edits.md)).
29
+ `dispatch edit-ask` if you asked it ([Editing a document](skill://dispatch/references/document-edits.md)).
30
30
 
31
31
  Use only the type names, content rule, attributes, and enum values returned by the schema. Values are
32
32
  quoted: `:::callout{kind="warning" title="Risk"}`. Do not write Pandoc-style `::: {.callout}`, leaf
@@ -55,67 +55,67 @@ unknown cost before production while keeping today's release possible.
55
55
  ```
56
56
 
57
57
  When a human answers a decision written as an ask block, the answer lives on that ask. Use
58
- `dispatch_resolve_ask` when the decision is resolved without a human response, or preserve the
58
+ `dispatch resolve-ask` when the decision is resolved without a human response, or preserve the
59
59
  human's answer; never rewrite the question into its answer or blank its options. An edit that leaves
60
60
  an ask block without a question or with a blank option is rejected with `INVALID_ASK_BLOCK`.
61
61
 
62
- When `dispatch_issue` or `dispatch_artifact` answers `This spec holds no ask blocks …`, read it as a
62
+ When `dispatch issue` or `dispatch artifact` answers `This spec holds no ask blocks …`, read it as a
63
63
  question: either no decision is needed and you say nothing, or you forgot to make it a block. When
64
64
  it answers `… typed-block openings in this document are text, not blocks`, the quoted openings are
65
65
  blocks stored as prose (inside a line, or a paste with something before every line): fix the markdown
66
66
  and upload again; a mention on purpose belongs in code. Neither answer sees a spec wrapped whole in a
67
67
  code fence (take the fence off), a malformed opening inside a line (`::ask{`, `:::ask {`: an ask opens
68
- only as `:::ask{…}` on a line of its own), or any `dispatch_doc_edit`: after an edit that writes an
69
- ask, `dispatch_doc_read` the section and check it renders as `:::ask{#<id> …}` on its own line.
68
+ only as `:::ask{…}` on a line of its own), or any `dispatch doc-edit`: after an edit that writes an
69
+ ask, `dispatch doc-read` the section and check it renders as `:::ask{#<id> …}` on its own line.
70
70
 
71
71
  ## Comments and suggestions
72
72
 
73
- Add feedback with:
73
+ Add feedback with `dispatch comment` (`dispatch comment --help` lists its flags):
74
74
 
75
- ```ts
76
- dispatch_comment({ issue?, project?, artifact?, ref?, quote?, occurrence?, body, reply_to?, reply_to_ask?, turn? })
75
+ ```bash
76
+ dispatch comment --issue <KEY> --artifact spec --quote '<the passage>' --body '<the question or note>'
77
77
  ```
78
78
 
79
- It returns issue or project-document owner details plus `comment`; a `reply_to_ask` reply also returns `ask` and `follows: { ask }`,
80
- because replying to an ask makes you one of its followers (see [Following](skill://dispatch/references/asks.md)).
81
- `ref` names the owner (an issue or project-document reference) in place of `issue`/`project`.
82
- `quote` requires `artifact`; its anchor is pinned to the containing block while retaining the quote
83
- for display. Omit both for a floating issue comment. A reply (`reply_to`/`reply_to_ask`) takes no
84
- `quote`; it belongs to its parent's anchor. Reply to any comment in a thread; the server keeps
79
+ It prints the comment's id; a `--reply-to-ask` reply also makes you one of the ask's followers
80
+ (see [Following](skill://dispatch/references/asks.md)).
81
+ `--ref` names the owner (an issue or project-document reference) in place of `--issue`/`--project`.
82
+ `--quote` requires `--artifact`; its anchor is pinned to the containing block while retaining the quote
83
+ for display. Omit both for a floating issue comment. A reply (`--reply-to`/`--reply-to-ask`) takes no
84
+ `--quote`; it belongs to its parent's anchor. Reply to any comment in a thread; the server keeps
85
85
  threads flat. A reply to a resolved thread reopens it, except a decided suggestion's: a reply joins
86
- that thread and the suggestion stays accepted or rejected. Use `reply_to_ask` to reply directly under a
87
- question asked with `dispatch_ask`; `turn` (only with `reply_to_ask`) says who holds the turn after
86
+ that thread and the suggestion stays accepted or rejected. Use `--reply-to-ask` to reply directly under a
87
+ question asked with `dispatch ask`; `--turn` (only with `--reply-to-ask`) says who holds the turn after
88
88
  the reply — `agent` for a progress note that keeps the ask waiting on you, `human` (the default) when
89
89
  the human needs to act; see "Asking" in `skill://dispatch`. Comments are edited only by their author from the
90
90
  dashboard. A delivered `comment.created` event carries the comment `id`; reply to it with
91
- `dispatch_comment({ reply_to: <id> })`.
91
+ `dispatch comment --reply-to <id>`.
92
92
 
93
93
  Resolve your own review comment once you have addressed it:
94
94
 
95
- ```ts
96
- dispatch_resolve_comment({ comment })
95
+ ```bash
96
+ dispatch resolve-comment --comment <id>
97
97
  ```
98
98
 
99
- `comment` is the comment id, or a `dispatch://KEY/comment/<id>` or `dispatch://PROJECT/artifact/<slug>/comment/<id>` reference (an
100
- 8+ character id prefix is resolved against the owner's comments). It returns the owner details plus `comment`, and takes no reason —
101
- say what you did in a `reply_to` first if the thread needs it. The server lets any session or human resolve any open comment, so
99
+ `--comment` is the comment id, or a `dispatch://KEY/comment/<id>` or `dispatch://PROJECT/artifact/<slug>/comment/<id>` reference (an
100
+ 8+ character id prefix is resolved against the owner's comments). It takes no reason —
101
+ say what you did in a `--reply-to` first if the thread needs it. The server lets any session or human resolve any open comment, so
102
102
  resolve only threads you opened or were asked to close; reopening a resolved thread is human-only (from the dashboard), though your
103
103
  reply to it reopens it - except a decided suggestion's, which your reply joins and leaves accepted or rejected. A reply to a pending
104
- suggestion whose text an edit removed lands as well. Asks are closed with `dispatch_resolve_ask` instead.
104
+ suggestion whose text an edit removed lands as well. Asks are closed with `dispatch resolve-ask` instead.
105
105
 
106
- An exact replacement for document text is a suggestion (`dispatch_suggest`), never a comment; a
106
+ An exact replacement for document text is a suggestion (`dispatch suggest`), never a comment; a
107
107
  comment is for a question or a note the human answers in words. A human accepts a suggestion with
108
108
  one click and cannot accept a comment, so propose the replacement instead of describing it:
109
109
 
110
- ```ts
111
- dispatch_suggest({ issue?, project?, artifact, ref?, quote, replace_with, body?, occurrence? })
110
+ ```bash
111
+ dispatch suggest --issue <KEY> --artifact spec --quote '<exact text>' --replace-with '<replacement>'
112
112
  ```
113
113
 
114
- It returns issue or project-document owner details plus `comment`. A human accepts or rejects a suggestion.
115
- Errors: `TARGET_AMBIGUOUS` (add `occurrence`), `TARGET_NOT_FOUND` (re-read first), `INVALID_ANCHOR`/`ANCHOR_MISSING`/`ANCHOR_ORPHANED`
114
+ It prints the suggestion's id; `--body` adds a note and `--occurrence` picks a repeated quote. A human accepts or rejects a suggestion.
115
+ Errors: `TARGET_AMBIGUOUS` (add `--occurrence`), `TARGET_NOT_FOUND` (re-read first), `INVALID_ANCHOR`/`ANCHOR_MISSING`/`ANCHOR_ORPHANED`
116
116
  (bad, unwritten, or stale quote), `INVALID_MARKDOWN`/`DOC_SCHEMA` (malformed content), `CAP_EXCEEDED`, `ISSUE_CLOSED`.
117
117
  Suggest only what the quoted block can hold. The accept, not the suggestion, checks that: an
118
- accept whose `replace_with` would break an ask block that was readable before it is refused with
118
+ accept whose replacement would break an ask block that was readable before it is refused with
119
119
  `INVALID_ASK_BLOCK` - a question given a code block, text after an ask's options, a second option
120
120
  list, or an emptied question; one that names an id the document holds outside the text it
121
121
  replaces, an ask under a held id included, with `INVALID_MARKDOWN`; and one no part of the
@@ -127,51 +127,54 @@ stays open until someone rejects or replaces it.
127
127
 
128
128
  Attach an image, diagram, or local file with:
129
129
 
130
- ```ts
131
- dispatch_artifact({ issue?, project?, name, path, summary? })
130
+ ```bash
131
+ dispatch artifact --issue <KEY> --name diagram.png --path ./diagram.png
132
132
  ```
133
133
 
134
- Or, when the text is already in the call, post a Markdown document directly:
134
+ Or, when the text is already at hand, post a Markdown document directly:
135
135
 
136
- ```ts
137
- dispatch_artifact({ issue?, project?, name: "load-test-results.md", content: "# Load test\n..." })
136
+ ```bash
137
+ dispatch artifact --issue <KEY> --name load-test-results.md --content-file - <<'EOF'
138
+ # Load test
139
+ ...
140
+ EOF
138
141
  ```
139
142
 
140
- Exactly one of `issue` and `project` is required. A project upload creates an unlinked project document; it must not include `artifact`.
141
- Exactly one of `path` and `content` is required. It returns issue or project-document owner details plus `artifact` and `version`.
142
- Uploading the same `name` creates its next version — so uploading `spec.md` **replaces the issue's own specification**
143
- with your text. Never do that: the spec is edited in place with `dispatch_doc_edit` (see [Editing a document](skill://dispatch/references/document-edits.md)). Address an existing
144
- artifact by the slug shown in the upload result or by its filename, and a project document by its artifact id, slug, or filename; the
143
+ Exactly one of `--issue` and `--project` is required. A project upload creates an unlinked project document; it takes no `--artifact`.
144
+ Exactly one of `--path` and `--content` is required. It prints the document's slug and version.
145
+ Uploading the same `--name` creates its next version — so uploading `spec.md` **replaces the issue's own specification**
146
+ with your text. Never do that: the spec is edited in place with `dispatch doc-edit` (see [Editing a document](skill://dispatch/references/document-edits.md)). Address an existing
147
+ artifact by the slug the upload printed or by its filename, and a project document by its artifact id, slug, or filename; the
145
148
  slug also arrives on `artifact.created` events. Dispatch suffixes a slug two documents would share, so one document's
146
149
  filename can be another's slug (`plan v2` takes `plan-v2`, then a document named `plan-v2` takes `plan-v2-2`): a bare
147
- `artifact` that names both is refused with each one's id, while a `dispatch://` reference's document part is always the slug.
150
+ `--artifact` that names both is refused with each one's id, while a `dispatch://` reference's document part is always the slug.
148
151
 
149
- A picture meant to be seen inline in a message, comment or ask is not uploaded with `dispatch_artifact`: pass its local
150
- path in that tool's `images` (PNG, JPEG, GIF or WebP by its bytes, at most 25 MiB each). Each is uploaded to the issue (a
152
+ A picture meant to be seen inline in a message, comment or ask is not uploaded with `dispatch artifact`: pass its local
153
+ path with that command's `--image`, once per picture (PNG, JPEG, GIF or WebP by its bytes, at most 25 MiB each). Each is uploaded to the issue (a
151
154
  project document's project; for a reply to a direct message, your own conversation) and appended after a blank line as
152
155
  one `![<file name>](dispatch://<owner>/artifact/<slug>@vN)` line, about 60 characters that count toward the body or
153
- question cap; a call the lines would carry over the cap is refused with the count before anything uploads, and again,
156
+ question cap; a command the lines would carry over the cap is refused with the count before anything uploads, and again,
154
157
  with nothing posted, in the rare case where Dispatch gives an upload a longer address than that (a later version of a
155
158
  name the owner already holds, or a slug another file took first). The `@vN` pins the version the upload made (the same
156
159
  file name again makes the next version). A plain `dispatch://KEY/artifact/<slug>` reference to an image stays a link
157
160
  with a thumbnail.
158
161
 
159
- `dispatch_read` hands you the pictures the messages, asks and comments it shows embed, newest first, at most 8 and
160
- 10 MiB per read, and its `Pictures:` section names each one shown, in order, and the rest by reference for
161
- `dispatch_doc_read`, which shows one picture (at most 3,750,000 bytes, the 5 MB of base64 a model provider takes; a
162
- larger one or an SVG is described). A picture in a
162
+ `dispatch read` writes the pictures the messages, asks and comments it shows embed to files, newest first, at most 8 and
163
+ 10 MiB per read, prints a `- picture: <path>` line for each, and its `Pictures:` section names each one shown, in order, and the rest by reference for
164
+ `dispatch doc-read`, which shows one picture (at most 3,750,000 bytes, the 5 MB of base64 a model provider takes; a
165
+ larger one or an SVG is described). Open each written file with your file-read tool. A picture in a
163
166
  conversation on the Agents page belongs to that conversation, `dispatch://agent/<session id>/artifact/<slug>@vN`, and
164
- `dispatch_doc_read` reads it by that reference alone. On Oh My Pi an Inbox delivery carries its pictures the same way; in
167
+ `dispatch doc-read` reads it by that reference alone. On Oh My Pi an Inbox delivery carries its pictures as images; in
165
168
  Claude Code it stays text with the reference.
166
169
 
167
170
  A session is shown each picture once. A later read or delivery that embeds a picture you were already shown names it in
168
171
  `Pictures:` as `shown earlier this session` and sends no bytes, since every request carries the session's whole history
169
- and a model provider refuses one over 32 MB; `dispatch_doc_read` always shows the picture, so ask it for one you need
172
+ and a model provider refuses one over 32 MB; `dispatch doc-read` always shows the picture, so ask it for one you need
170
173
  again, after compaction too.
171
174
 
172
175
  Documents are CommonMark. A bare `<https://example.com|text>` is a CommonMark autolink and is normalised: the angle brackets are
173
176
  dropped and the URL keeps `|text`. A backslash-escaped `\<https://example.com|text>` displays as `<https://example.com|text>` in the
174
- document but comes back re-escaped (`\<`) from `dispatch_doc_read`. A Slack mrkdwn draft, or any other payload that is not Markdown,
177
+ document but comes back re-escaped (`\<`) from `dispatch doc-read`. A Slack mrkdwn draft, or any other payload that is not Markdown,
175
178
  still belongs inside a fenced code block, where it survives verbatim both ways.
176
179
 
177
180
  A table cell ends at every `|` not written `\|`, inside inline code and links too, so write
@@ -184,27 +187,27 @@ of bare table rows `TABLE_WIDTH`, which names the cell counts only.
184
187
 
185
188
  ## Approval requests
186
189
 
187
- ```
188
- dispatch_request_approval({ issue?, project?, artifact?, summary })
190
+ ```bash
191
+ dispatch request-approval --issue <KEY> --summary '<what the human is approving>'
189
192
  ```
190
193
 
191
- The call opens an approval ask with the options `Approve` and `Request changes`, its question
192
- "Approve spec.md (version N)?" followed by `summary`, where N is the document's latest version. It
194
+ The command opens an approval ask with the options `Approve` and `Request changes`, its question
195
+ "Approve spec.md (version N)?" followed by the summary, where N is the document's latest version. It
193
196
  is refused, with nothing sent, while that version holds a decision block open, and the refusal
194
- names each block and its ask. An answer or a `dispatch_resolve_ask` closes the ask at once but
197
+ names each block and its ask. An answer or a `dispatch resolve-ask` closes the ask at once but
195
198
  reaches a version only when the document settles, about two seconds later, or with your next
196
- `dispatch_doc_edit`: fold the answer into the text (or, for a waiver, write the human's decision
199
+ `dispatch doc-edit`: fold the answer into the text (or, for a waiver, write the human's decision
197
200
  in) before you request. A block written in the last few seconds counts as open before Dispatch has
198
201
  opened its ask. A document holds one open request. A new version moves it to that version, keeping
199
202
  its thread and summary, and leaves it waiting on you, as a human's reply in its thread does; a move
200
- your own edit made sends you no event, and `dispatch_read` and `dispatch_doc_read` show it as
201
- `Approval: awaiting, waiting on agent`. Call again when "Approval of a spec" in `skill://dispatch`
202
- allows: that hands the same request back to the human, reworded first when `summary` is new. Your
203
- own reply in its thread hands it back too, with no call and the summary it already holds, unless
204
- you post it with `reply_to_ask` and `turn: "agent"` (`reply_to` carries no turn, so its reply takes
203
+ your own edit made sends you no event, and `dispatch read` and `dispatch doc-read` show it as
204
+ `Approval: awaiting, waiting on agent`. Run it again when "Approval of a spec" in `skill://dispatch`
205
+ allows: that hands the same request back to the human, reworded first when `--summary` is new. Your
206
+ own reply in its thread hands it back too, with no command and the summary it already holds, unless
207
+ you post it with `--reply-to-ask` and `--turn agent` (`--reply-to` carries no turn, so its reply takes
205
208
  the default, `human`) or a new version has moved the request since your last
206
- `dispatch_request_approval`. While it waits on the human, a call with the same `summary` changes
207
- nothing, and one with a different `summary` is refused, since it would rewrite the card they are
209
+ `dispatch request-approval`. While it waits on the human, the same `--summary` changes
210
+ nothing, and a different `--summary` is refused, since it would rewrite the card they are
208
211
  reading. The answer reaches you as `artifact.approved` or `artifact.changes_requested` with the
209
212
  pinned `version` and closes the request, so the next call opens a new one; `changes_requested`
210
213
  carries the reason, which is your next piece of work. Those reads show the document's approval
@@ -213,18 +216,18 @@ state; `stale` means it was approved and then edited.
213
216
  ## A document that is reloading
214
217
 
215
218
  These calls can answer `DOC_SERVICE_UNAVAILABLE` (HTTP 503), because each writes a document inside its
216
- transaction: `dispatch_doc_edit`; `dispatch_ask` and `dispatch_comment` on a quote; a `dispatch_comment` reply
217
- in a thread whose first comment is anchored; `dispatch_suggest`; `dispatch_resolve_comment` on an anchored
218
- comment; `dispatch_artifact` replacing a document that already exists; and, on an ask that lives in a `:::ask`
219
- block, `dispatch_edit_ask` and `dispatch_resolve_ask`, which write that block. Creating an issue with a spec,
220
- uploading a new document, `dispatch_request_approval` and `dispatch_message` never answer it, and neither do
221
- `dispatch_edit_ask` and `dispatch_resolve_ask` on an ask that has no block. It means that document's live room
219
+ transaction: `dispatch doc-edit`; `dispatch ask` and `dispatch comment` on a quote; a `dispatch comment` reply
220
+ in a thread whose first comment is anchored; `dispatch suggest`; `dispatch resolve-comment` on an anchored
221
+ comment; `dispatch artifact` replacing a document that already exists; and, on an ask that lives in a `:::ask`
222
+ block, `dispatch edit-ask` and `dispatch resolve-ask`, which write that block. Creating an issue with a spec,
223
+ uploading a new document, `dispatch request-approval` and `dispatch message` never answer it, and neither do
224
+ `dispatch edit-ask` and `dispatch resolve-ask` on an ask that has no block. It means that document's live room
222
225
  failed and is reloading from its durable copy, so the server refused rather than wait for it; your call wrote
223
226
  nothing and the document is intact. Nothing retries it for you: the Dispatch client hands a 503 straight back.
224
227
  Wait a few seconds and make the same call again. A second refusal in a row is worth telling your human about,
225
228
  with the document's reference.
226
229
 
227
- `dispatch_doc_read` can answer it too, though it writes nothing: a read never opens a live room, and waits out
230
+ `dispatch doc-read` can answer it too, though it writes nothing: a read never opens a live room, and waits out
228
231
  a room that is reloading, so it is refused only when the document's durable copy cannot be read or decoded.
229
232
  Retry it the same way.
230
233
 
@@ -17,90 +17,60 @@ dashboard from the release. Waiting is safest, but the list above may be stale.
17
17
  After — write the decision into the document section it concerns: state the problem, and make each
18
18
  genuinely different option a button, so the answer stays with the release design:
19
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
- })
20
+ ```bash
21
+ dispatch doc-edit --issue LEGION-815 --artifact spec --ops-json-file - <<'EOF'
22
+ [{
23
+ "op": "insert",
24
+ "after": "Release requires reviewed operator instructions before deployment.",
25
+ "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:::"
26
+ }]
27
+ EOF
35
28
  ```
36
29
 
37
30
  Before — a progress note that nobody needs, posted where humans look for decisions:
38
31
 
39
- ```ts
40
- dispatch_message({ issue: "LEGION-815", body: "Merged the release PR, moving to docs next." })
32
+ ```bash
33
+ dispatch message --issue LEGION-815 --body 'Merged the release PR, moving to docs next.'
41
34
  ```
42
35
 
43
36
  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
37
  a human must act or a deliverable is theirs to use:
45
38
 
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
- })
39
+ ```bash
40
+ dispatch message --issue LEGION-815 --body 'Release 1.4 is live on the devbox (dispatch://LEGION-815/artifact/release-notes). Nothing needed from you.'
51
41
  ```
52
42
 
53
43
  Before — a draft the human must read is uploaded as a separate file, the spec only names it, and
54
44
  the ask does not point at it, so the reader has to go looking:
55
45
 
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
- })
46
+ ```bash
47
+ dispatch artifact --issue OPS-52 --name cu-update-2026-09-15.md --content 'Hi team, ...'
48
+ dispatch doc-edit --issue OPS-52 --artifact spec \
49
+ --ops-json '[{"op":"insert","after":"## Context","markdown":"## Draft (artifact cu-update-2026-09-15.md)"}]'
50
+ dispatch ask --issue OPS-52 \
51
+ --question '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.' \
52
+ --option 'Put the draft in the spec: Adds a spec edit before sending but lets the reader review it in context.' \
53
+ --option 'Keep the separate file: Saves the spec edit but leaves the reader to find the draft.'
70
54
  ```
71
55
 
72
56
  After — the draft is a section of the spec, and the ask anchors there. Sending it is a to-do only a
73
57
  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
- })
58
+ both link the slug the upload printed:
59
+
60
+ ```bash
61
+ dispatch doc-edit --issue OPS-52 --artifact spec \
62
+ --ops-json '[{"op":"insert","after":"## Context","markdown":"## Draft\n\nHi team, ..."}]'
63
+ dispatch ask --issue OPS-52 --anchor-json '{"artifact":"spec","quote":"Hi team,"}' \
64
+ --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.' \
65
+ --option 'Send the reviewed update: Delivers the update today but makes its text external.' \
66
+ --option 'Hold the update: Avoids sending now but leaves customers without the update.'
67
+ # or, for a real file — the spec links it where the reader needs it, and so does the ask:
68
+ dispatch doc-edit --issue OPS-52 --artifact spec \
69
+ --ops-json '[{"op":"insert","after":"## Context","markdown":"## Draft\n\nThe update to send: dispatch://OPS-52/artifact/cu-update-2026-09-15-md"}]'
70
+ dispatch ask --issue OPS-52 --ref dispatch://OPS-52/artifact/cu-update-2026-09-15-md \
71
+ --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.' \
72
+ --option 'Send the reviewed update: Delivers the linked update today but makes its text external.' \
73
+ --option 'Hold the update: Avoids sending now but leaves customers without the update.'
104
74
  ```
105
75
 
106
76
  ## Naming the mechanism
@@ -123,7 +93,7 @@ takes today: it calls `register_gate` on its `legion` tool, which posts to the d
123
93
  `POST /legion/v1/gates/register` route and names the spec document and the version that gate the
124
94
  tree. The daemon opens the gate when Dispatch emits `artifact.approved` for that document at that
125
95
  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
96
+ `dispatch request-approval`. Without `register_gate`, the daemon reads the issue's primary
127
97
  document from Dispatch itself and waits for the same event.
128
98
  ```
129
99