@sjawhar/opencode-legion-envoy 0.30.0 → 0.31.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.
@@ -13874,8 +13874,17 @@ var legionRole = _enum2(LEGION_ROLES);
13874
13874
  var requiredUnknown = unknown().refine((value) => value !== undefined, {
13875
13875
  message: "Required"
13876
13876
  });
13877
- var ARCHITECT_MUTABLE_LABELS = ["needs-approval"];
13878
- var architectMutableLabel = _enum2(ARCHITECT_MUTABLE_LABELS);
13877
+ var LIFECYCLE_STATUSES = [
13878
+ "triage",
13879
+ "icebox",
13880
+ "backlog",
13881
+ "todo",
13882
+ "in_progress",
13883
+ "testing",
13884
+ "needs_review",
13885
+ "retro",
13886
+ "done"
13887
+ ];
13879
13888
  var architectCapability = strictObject({
13880
13889
  tree: nonEmptyString,
13881
13890
  sessionId: nonEmptyString,
@@ -13928,33 +13937,10 @@ var LegionDaemonApi = {
13928
13937
  request: architectCapability.extend({ generation: number2().int() }),
13929
13938
  response: object({})
13930
13939
  },
13931
- IssueCreate: {
13932
- request: architectCapability.extend({
13933
- title: nonEmptyString,
13934
- body: nonEmptyString,
13935
- labels: array(architectMutableLabel).optional()
13936
- }),
13937
- response: object({ issue: nonEmptyString, url: nonEmptyString })
13938
- },
13939
13940
  WaveRelease: {
13940
- request: architectCapability.extend({ children: array(nonEmptyString).optional() }),
13941
+ request: architectCapability.extend({ issues: array(nonEmptyString).optional() }),
13941
13942
  response: object({ released: array(nonEmptyString) })
13942
13943
  },
13943
- Comment: {
13944
- request: architectCapability.extend({ issue: nonEmptyString, body: nonEmptyString }),
13945
- response: object({ commentId: number2().int(), url: nonEmptyString })
13946
- },
13947
- PostBody: {
13948
- request: architectCapability.extend({ issue: nonEmptyString, body: nonEmptyString }),
13949
- response: object({})
13950
- },
13951
- Labels: {
13952
- request: architectCapability.extend({
13953
- issue: nonEmptyString,
13954
- add: array(architectMutableLabel).optional()
13955
- }),
13956
- response: object({ labels: array(nonEmptyString) })
13957
- },
13958
13944
  Escalate: {
13959
13945
  request: architectCapability.extend({
13960
13946
  kind: _enum2(["re-file", "capacity", "cross-tree"]),
@@ -13962,10 +13948,6 @@ var LegionDaemonApi = {
13962
13948
  }),
13963
13949
  response: object({})
13964
13950
  },
13965
- IssueClose: {
13966
- request: architectCapability.extend({ issue: nonEmptyString, comment: string2().optional() }),
13967
- response: object({})
13968
- },
13969
13951
  ProvisioningCredential: {
13970
13952
  request: architectCapability.extend({ issue: nonEmptyString }),
13971
13953
  response: object({ token: nonEmptyString })
@@ -14028,16 +14010,16 @@ var LegionDaemonApi = {
14028
14010
  secret: nonEmptyString
14029
14011
  })
14030
14012
  },
14031
- GatesApprove: {
14032
- request: controllerIssue,
14013
+ IssueStatus: {
14014
+ request: controllerIssue.extend({
14015
+ status: _enum2(LIFECYCLE_STATUSES),
14016
+ tree: nonEmptyString.optional(),
14017
+ sessionId: nonEmptyString.optional()
14018
+ }),
14033
14019
  response: object({})
14034
14020
  },
14035
- Admission: {
14036
- request: controllerIssue,
14037
- response: object({ result: _enum2(["spawned", "queued"]) })
14038
- },
14039
- Backlog: {
14040
- request: controllerIssue.extend({ marker: nonEmptyString }),
14021
+ GatesRegister: {
14022
+ request: architectCapability.extend({ issue: nonEmptyString, askId: nonEmptyString }),
14041
14023
  response: object({})
14042
14024
  },
14043
14025
  Grant: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/opencode-legion-envoy",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "type": "module",
5
5
  "main": "dist/src/server.js",
6
6
  "exports": {
@@ -104,7 +104,7 @@ gh api graphql -f query='query {
104
104
  Add a label to an issue (additive — does not remove existing labels):
105
105
 
106
106
  ```bash
107
- gh issue edit $ISSUE_NUMBER --add-label "needs-approval" -R $OWNER/$REPO
107
+ gh issue edit $ISSUE_NUMBER --add-label "bug" -R $OWNER/$REPO
108
108
  ```
109
109
 
110
110
  **Parameters:**
@@ -114,7 +114,7 @@ gh issue edit $ISSUE_NUMBER --add-label "needs-approval" -R $OWNER/$REPO
114
114
 
115
115
  **Example:**
116
116
  ```bash
117
- gh issue edit 123 --add-label "needs-approval" -R acme/backend
117
+ gh issue edit 123 --add-label "bug" -R acme/backend
118
118
  ```
119
119
 
120
120
  ### Remove Label
@@ -122,7 +122,7 @@ gh issue edit 123 --add-label "needs-approval" -R acme/backend
122
122
  Remove a label from an issue:
123
123
 
124
124
  ```bash
125
- gh issue edit $ISSUE_NUMBER --remove-label "needs-approval" -R $OWNER/$REPO
125
+ gh issue edit $ISSUE_NUMBER --remove-label "wontfix" -R $OWNER/$REPO
126
126
  ```
127
127
 
128
128
  **Parameters:**
@@ -132,7 +132,7 @@ gh issue edit $ISSUE_NUMBER --remove-label "needs-approval" -R $OWNER/$REPO
132
132
 
133
133
  **Example:**
134
134
  ```bash
135
- gh issue edit 123 --remove-label "legion-backlog" -R acme/backend
135
+ gh issue edit 123 --remove-label "wontfix" -R acme/backend
136
136
  ```
137
137
 
138
138
  ### Comment on Issue
@@ -10,29 +10,31 @@ with human-created children; either way you own its complete outcome. Work from
10
10
  wakes and current artifacts. Do not perform code work yourself and do not rely on a
11
11
  separate coordinator to finish necessary work.
12
12
 
13
+ This skill documents the target Dispatch-native contract. The `legion` tool's
14
+ `register_gate`/`release_wave`/`set_status` ops, the Dispatch key format, and the
15
+ `dispatch_*` tool family land with PR B (#TBD); until that PR merges, this skill's contract
16
+ is not yet runnable on `main`.
17
+
13
18
  ## Tool and ownership boundaries
14
19
 
15
- - Use the `legion` tool for lifecycle writes. Its issue key format is
16
- `owner/repo#number`.
20
+ - Use the `legion` tool for lifecycle writes. Its issue key is the Dispatch key
21
+ (pattern `^[A-Z][A-Z0-9]*-[0-9]+$`, e.g. `LEGION-41`).
17
22
  - Use `legion({ op: "spawn_worker", issue, role, task })` for every Legion role spawn.
18
23
  Message a known phase worker with `envoy_publish` to `notifications.role.` followed by
19
24
  its encoded role token (the token `spawn_worker` returned for it); re-assign it by
20
25
  calling `spawn_worker` again on the same existing role, which resumes the same process
21
26
  instead of starting a fresh one. Phase workers escalate lifecycle, scope, and
22
27
  cross-phase matters the same way: `envoy_publish` to your own encoded token. Any role
23
- may use `dispatch` directly for a standalone human question; replies return to the
28
+ may use `dispatch_ask` directly for a standalone human question; replies return to the
24
29
  asking session.
25
30
  - The daemon spawns each role as its own process with the issue's context already in its
26
31
  environment. Never hand-format a role token: the daemon encodes one as
27
- `legion-<project>-<encoded-owner>__<encoded-repo>-<number>-<role>` (escaping `_`, `.`,
28
- and `-` within the owner/repo names); for example, project `acme`, issue
29
- `sjawhar/legion#41`, role `architect` encodes to `legion-acme-sjawhar__legion-41-architect`.
30
- Reuse a token you already hold (your own, or one `spawn_worker` returned) or compute
31
- another with the `roleToken` helper from `@legion/contracts` exactly the way the daemon
32
- does.
33
- - Use only the live label vocabulary: `needs-approval`, `human-approved`,
34
- `legion-child`, and `legion-backlog`. Do not attempt to apply a label whose ownership
35
- belongs to the controller or Sami.
32
+ `legion-<project>-<KEY>-<role>`; for example, project `acme`, issue `LEGION-41`, role
33
+ `architect` encodes to `legion-acme-LEGION-41-architect`. Reuse a token you already
34
+ hold (your own, or one `spawn_worker` returned) or compute another with the
35
+ `roleToken` helper from `@legion/contracts` exactly the way the daemon does.
36
+ - There is no label vocabulary. Dispatch status replaces the board, and the design gate
37
+ is a `dispatch_ask` answered `Approve`, not a label. Never attempt to apply a label.
36
38
  - Deferring necessary work is failure. The sole valid deferral is a new child issue you
37
39
  create and continue to own. Re-file a genuinely independent child through the
38
40
  controller rather than treating it as an abandoned dependency.
@@ -46,7 +48,7 @@ exercise a criterion end to end, building that path is a child issue of this tre
46
48
 
47
49
  - **Existing children:** adopt them. Do not replace or re-decompose human-created work.
48
50
  Put every adopted child into the initial wave. **You MUST call**
49
- `legion({ op: "wave_release", children: ["owner/repo#41", "owner/repo#42"] })`
51
+ `legion({ op: "release_wave", issues: ["LEGION-41", "LEGION-42"] })`
50
52
  **before any `spawn_worker` call for an adopted child.** Until release, the daemon
51
53
  holds that child's role activity. Then spawn each child's daemon-managed sub-architect
52
54
  owner.
@@ -54,16 +56,19 @@ exercise a criterion end to end, building that path is a child issue of this tre
54
56
  completed and integrated as one unit. Otherwise create complete child issues with:
55
57
 
56
58
  ```text
57
- legion({
58
- op: "issue_create",
59
+ dispatch_issue({
60
+ project: "<project>",
61
+ parent: "<root issue>",
59
62
  title: "<child outcome>",
60
- body: "<acceptance criteria, scope, and context>",
61
- labels: []
63
+ spec: "<acceptance criteria, scope, and context>"
62
64
  })
63
65
  ```
64
66
 
65
- The daemon establishes the sub-issue relationship and the `legion-child` label. Keep
66
- the returned issue keys in ordered waves; a child is inert until released.
67
+ `project` is the issue key's prefix before `-<n>` (e.g. `LEGSMOKE-3` `LEGSMOKE`) — not
68
+ the role-token `<project>` (the daemon's own project, e.g. `acme`), a different string. A
69
+ root session has `LEGION_TREE == LEGION_ISSUE`. The daemon establishes the sub-issue
70
+ relationship from `parent`. Keep the returned issue keys in ordered waves; a child is
71
+ inert until released.
67
72
 
68
73
  Write one root specification containing the accepted scope, adoption/decomposition,
69
74
  waves, acceptance criteria, and integration test. When the config-armed root design gate
@@ -71,20 +76,18 @@ applies, run this exact sequence **before any Legion-role spawn**, including a
71
76
  sub-architect:
72
77
 
73
78
  ```text
74
- legion({ op: "post_spec", issue: "<root issue>", body: "<root specification>" })
75
- legion({ op: "label_add", issue: "<root issue>", label: "needs-approval" })
76
- dispatch({
77
- parent: "<root issue>",
78
- subject: "Legion design approval requested",
79
- context: "<what the tree is, what triggered the gate>",
80
- question: "<specification summary and the decision requested>"
79
+ dispatch_artifact({ issue: "<root issue>", name: "spec.md", content: "<root specification>", primary: true, summary: "<one-line summary>" })
80
+ askId = dispatch_ask({
81
+ issue: "<root issue>",
82
+ question: "<specification summary and the decision requested>",
83
+ options: [{ label: "Approve" }, ...]
81
84
  })
85
+ legion({ op: "register_gate", issue: "<root issue>", askId })
82
86
  ```
83
87
 
84
88
  Then park. Do not release a wave or spawn a Legion role until a later delivered wake
85
- shows `human-approved` on the root. You never add that label yourself. Approval covers
86
- the entire tree: later waves, re-scopes, and integration-failure children do not repeat
87
- this sequence.
89
+ shows `design-approved` on the root. Approval covers the entire tree: later waves,
90
+ re-scopes, and integration-failure children do not repeat this sequence.
88
91
 
89
92
  ## 2. Children in flight
90
93
 
@@ -92,7 +95,7 @@ Release only the next useful wave, then give its owners their work. A release is
92
95
  explicit lifecycle write:
93
96
 
94
97
  ```text
95
- legion({ op: "wave_release", children: ["owner/repo#41", "owner/repo#42"] })
98
+ legion({ op: "release_wave", issues: ["LEGION-41", "LEGION-42"] })
96
99
  ```
97
100
 
98
101
  After release, spawn each relevant owner; for example:
@@ -100,7 +103,7 @@ After release, spawn each relevant owner; for example:
100
103
  ```text
101
104
  legion({
102
105
  op: "spawn_worker",
103
- issue: "owner/repo#41",
106
+ issue: "LEGION-41",
104
107
  role: "architect",
105
108
  task: "Own this child through its lifecycle and report its evidence."
106
109
  })
@@ -122,7 +125,7 @@ and current `main` integration surface:
122
125
  ```text
123
126
  legion({
124
127
  op: "spawn_worker",
125
- issue: "owner/repo#40",
128
+ issue: "LEGION-40",
126
129
  role: "tester",
127
130
  task: "Verify this parent issue against its acceptance criteria on current main; return reproducible integration evidence."
128
131
  })
@@ -181,15 +184,12 @@ If anything changes the approved head, return to review; do not let the merger p
181
184
 
182
185
  ## 7. Close
183
186
 
184
- After the merge result and sign-off are recorded, close this issue through the Legion
185
- write surface and include the sign-off comment:
187
+ After the merge result and sign-off are recorded, post the sign-off and close this issue
188
+ through the Legion write surface:
186
189
 
187
190
  ```text
188
- legion({
189
- op: "issue_close",
190
- issue: "owner/repo#40",
191
- comment: "<sign-off: scope, integration evidence, review, retro, Sami approval, and merge>"
192
- })
191
+ dispatch_comment({ issue: "LEGION-40", body: "<sign-off: scope, integration evidence, review, retro, Sami approval, and merge>" })
192
+ legion({ op: "set_status", issue: "LEGION-40", status: "done" })
193
193
  ```
194
194
 
195
195
  Closing a child supplies the closure event to its parent. Do not close a parent until the
@@ -220,6 +220,6 @@ corresponding lifecycle procedure.
220
220
 
221
221
  Controller-actionable matters are exactly re-filing a genuinely independent child,
222
222
  capacity, and cross-tree conflict. Use the Legion escalation operation for those. Handle
223
- everything else in the tree, or use `dispatch` for a human question; workers may reach
224
- Sami directly with `dispatch` the same way. Do not create a wait loop for any wake
223
+ everything else in the tree, or use `dispatch_ask` for a human question; workers may reach
224
+ Sami directly with `dispatch_ask` the same way. Do not create a wait loop for any wake
225
225
  source.
@@ -9,6 +9,10 @@ The controller is the one persistent, wake-driven session for a Legion project.
9
9
  triage, escalation, and human-interaction judgments; it never does phase-worker work or
10
10
  routes raw events into an architect.
11
11
 
12
+ This skill documents the target Dispatch-native contract. The `legion` tool's `set_status`
13
+ op, `dispatch_read`/`dispatch_issue`, and the Dispatch key format land with PR B (#TBD);
14
+ until that PR merges, this skill's contract is not yet runnable on `main`.
15
+
12
16
  ## Start and claim the controller role
13
17
 
14
18
  The Legion extension claims `legion-<project>-controller` and registers controller readiness
@@ -23,11 +27,11 @@ For an interactive takeover, start OMP with `LEGION_CONTROLLER_SECRET` and
23
27
 
24
28
  The command resolves the project from daemon state, claims the Envoy role for the current
25
29
  session, and posts readiness before controller commands can act. It retains the environment
26
- capability for `legion admit` and `legion backlog`. Never pass a secret as a command argument
30
+ capability for `legion({ op: "set_status", issue, status })`. Never pass a secret as a command argument
27
31
  or copy it into a transcript.
28
32
 
29
33
  This handshake lets the daemon redeliver held controller work. It does not turn the controller
30
- into a state holder: daemon state and GitHub artifacts remain authoritative.
34
+ into a state holder: daemon state and the Dispatch project remain authoritative.
31
35
 
32
36
  ## Turn discipline
33
37
 
@@ -36,7 +40,7 @@ into a state holder: daemon state and GitHub artifacts remain authoritative.
36
40
  - **One wake = one turn.** Handle exactly the wake's implication, then end the turn. Never
37
41
  poll, idle-loop, or wait for another event.
38
42
  - **Wakes are advisory.** Before any side effect, verify the current daemon state and the
39
- relevant GitHub artifact. A stale or duplicate wake may cost a read, never a wrong action.
43
+ relevant Dispatch issue. A stale or duplicate wake may cost a read, never a wrong action.
40
44
  - **Controller state is disposable.** Do not reconstruct or preserve local controller
41
45
  bookkeeping between turns.
42
46
 
@@ -44,52 +48,59 @@ into a state holder: daemon state and GitHub artifacts remain authoritative.
44
48
 
45
49
  | Wake | Content | Controller action |
46
50
  |---|---|---|
47
- | New issue added to the project board (webhook: issue opened / project item added; resync heals misses) | issue ref + triage context (incl. pre-existing children) | Triage: spawn root process via daemon admission, or park in the daemon-state backlog |
48
- | Backlog eligibility | slot freed / priority change | Reconsider parked items; deliberately-backlogged issues carry a marker so resync doesn't re-flag them |
49
- | Architect escalation (controller-actionable only: re-file a child as a root issue, capacity, cross-tree conflicts) | request + context | Judge and act; issue-scoped human Q&A goes through `dispatch` from the owning architect, not here |
50
- | Resync report | artifact-driven anomaly list (zero-owner trees, erroring issues) | Verify against fresh state, then dispatch/heal |
51
- | Mention | Slack/GitHub @mention text | Answer, or route to the owning issue's architect role |
52
- | Closed-tree activity (comment, review, CI on a closed tree) | issue, root, event summary | Read the artifact; if work should resume, `legion admit <root>`; otherwise no action — the event is not held or redelivered |
51
+ | New issue created in the Dispatch project (`issue.created`, status `triage`; resync heals misses) | issue key + triage context (incl. pre-existing children) | Triage: `legion({ op: "set_status", issue, status: "todo" })` to admit, or set `backlog`/`icebox` to park |
52
+ | Backlog eligibility | slot freed / priority change | Reconsider parked items and move the eligible root to `todo` |
53
+ | Architect escalation (controller-actionable only: re-file a child as a root issue, capacity, cross-tree conflicts) | request + context | Judge and act; issue-scoped human Q&A goes through `dispatch_ask` from the owning architect, not here |
54
+ | Resync report | artifact-driven anomaly list (zero-owner trees, untriaged-open, launch-failed) | Verify against fresh state, then heal |
55
+ | `child-status` | child key + status transition | Not controller-actionable by default; if the daemon could not route it to the parent's architect role, verify the transition and forward it with `envoy_publish` |
56
+ | Mention | Slack/GitHub PR @mention text | Answer, or route to the owning issue's architect role |
57
+ | Closed-tree activity (comment, review, CI on a closed tree) | issue, root, event summary | Read the artifact; if work should resume, `legion({ op: "set_status", issue: root, status: "todo" })`; otherwise no action — the event is not held or redelivered |
53
58
  | Direct user message | — | Always first |
54
59
 
55
60
  ## New issue triage
56
61
 
57
- 1. Read `legion state --json`, then inspect the reported GitHub issue with `gh issue view`.
58
- Verify the issue is on this project board, is eligible for a root process, and whether it
59
- has pre-existing children. GitHub and daemon state, not the wake text, decide triage.
62
+ 1. Read `legion state --json`, then inspect the reported Dispatch issue with `dispatch_read`.
63
+ Verify the issue is in this project, is eligible for a root process, and whether it
64
+ has pre-existing children. Dispatch and daemon state, not the wake text, decide triage.
60
65
  2. If it should run now, admit the root issue:
61
66
 
62
- ```bash
63
- legion admit <issue>
67
+ ```text
68
+ legion({ op: "set_status", issue: "<issue>", status: "todo" })
64
69
  ```
65
70
 
66
- 3. If it should deliberately wait, record a durable reason instead of leaving it unowned:
71
+ 3. If it should deliberately wait, move it to a parked status instead of leaving it in
72
+ `triage`:
67
73
 
68
- ```bash
69
- legion backlog <issue> --marker <reason>
74
+ ```text
75
+ legion({ op: "set_status", issue: "<issue>", status: "backlog" })
70
76
  ```
71
77
 
72
- The marker is required: it distinguishes intentional backlog from a missed wake during
73
- resync. Do not triage a system-created child as a root issue.
78
+ (or `status: "icebox"` for longer-term deferral). Dispatch status is the durable record;
79
+ there is no separate marker to maintain. Do not triage a system-created child as a root
80
+ issue.
74
81
 
75
82
  ## Backlog eligibility
76
83
 
77
- When a slot frees or priority changes, use `legion state --json` and the current issue
78
- artifact to reconsider marked backlog entries. Admit the selected root with `legion admit
79
- <issue>`. Keep an item backlogged only with a current, explicit marker; changing the marker
80
- is a deliberate controller decision, not a no-op.
84
+ When a slot frees or priority changes, use `legion state --json` and the current Dispatch
85
+ issue to reconsider parked roots. Admit the selected root with
86
+ `legion({ op: "set_status", issue, status: "todo" })`. Moving an item to or from `backlog`/
87
+ `icebox` is a deliberate controller decision, not a no-op.
81
88
 
82
89
  ## Architect escalation
83
90
 
84
91
  Only decide controller-actionable escalations: re-filing independent work, capacity, and
85
- cross-tree conflicts. Issue-scoped human Q&A goes through `dispatch` from the owning
92
+ cross-tree conflicts. Issue-scoped human Q&A goes through `dispatch_ask` from the owning
86
93
  architect, not the controller.
87
94
 
88
- For an independence judgment, verify the child and its parent against GitHub and current
89
- daemon state. If the work belongs in an independent root:
95
+ For an independence judgment, verify the child and its parent against current daemon state
96
+ and the Dispatch issue. If the work belongs in an independent root:
90
97
 
91
- 1. File a **fresh root issue** with `gh`, carrying the necessary context.
92
- 2. Close the child and leave a pointer to the new root issue.
98
+ 1. File a **fresh root issue** with `dispatch_issue({ project, title, spec })` (no `parent`).
99
+ `project` is the issue key's prefix before `-<n>` (e.g. `LEGSMOKE-3` `LEGSMOKE`) — not
100
+ the role-token `<project>` (the daemon's own project, e.g. `acme`), a different string.
101
+ 2. Park the child (`legion({ op: "set_status", issue: child, status: "icebox" })`) and leave
102
+ a pointer to the new root issue. The controller's capability is `todo`/`backlog`/`icebox`
103
+ only — only the owning architect or the daemon closes an issue as `done`.
93
104
  3. Admit or deliberately backlog the new root through the normal triage procedure.
94
105
 
95
106
  Never promote a child in place. Resolve capacity and cross-tree conflicts from verified
@@ -98,11 +109,11 @@ judgments.
98
109
 
99
110
  ## Resync report
100
111
 
101
- Treat a resync report as an anomaly list, not an instruction. For every zero-owner tree or
102
- erroring issue it names, verify `legion state --json` and the current GitHub artifact first.
103
- Then heal the verified condition: admit an eligible root, restore a deliberately backlogged
104
- marker, or use the applicable daemon control path. Do not act on erroring or stale entries
105
- until their source artifact explains the anomaly.
112
+ Treat a resync report as an anomaly list, not an instruction. For every zero-owner tree,
113
+ untriaged-open, or launch-failed issue it names, verify `legion state --json` and the
114
+ current Dispatch issue first. Then heal the verified condition: admit an eligible root, move
115
+ an issue back to its intended status, or use the applicable daemon control path. Do not act
116
+ on stale entries until their source artifact explains the anomaly.
106
117
 
107
118
  ## Mentions
108
119
 
@@ -111,13 +122,3 @@ human-facing information. Otherwise resolve the authoritative owning architect r
111
122
  route the verified context with `envoy_publish`. Do not route raw event traffic or invent a
112
123
  role token from a partial issue reference.
113
124
 
114
- ## Label vocabulary
115
-
116
- Use only the project labels below, with their stated ownership:
117
-
118
- | Label | Applied by | Removed by | Meaning |
119
- |---|---|---|---|
120
- | `needs-approval` | architect | controller/Sami when applying `human-approved` | design gate armed, awaiting Sami |
121
- | `human-approved` | Sami or controller | Sami | design gate open |
122
- | `legion-child` | daemon | never | system-created child |
123
- | `legion-backlog` | controller | controller | deliberately unowned root |
@@ -11,6 +11,10 @@ phase gets its own long-lived process against the same jj workspace, run in turn
11
11
  the phase assigned to you, report its completion to the architect, and leave the durable
12
12
  copy the next phase can trust.
13
13
 
14
+ This skill documents the target Dispatch-native contract. `LEGION_ISSUE` as a Dispatch key,
15
+ the `dispatch_ask` tool, and the `Dispatch: <KEY>` PR-body linkage land with PR B (#TBD);
16
+ until that PR merges, this skill's contract is not yet runnable on `main`.
17
+
14
18
  ## Identity, scope, and role
15
19
 
16
20
  The daemon spawns you as a separate `omp --mode rpc` process (behind `legion worker-shim`,
@@ -21,13 +25,11 @@ completes the boot handshake for you at session start — it registers with the
21
25
  your role, and signals readiness. You never call `envoy_role_set` yourself.
22
26
 
23
27
  Your role token is not the issue key spelled out literally. The daemon encodes it as
24
- `legion-<project>-<encoded-owner>__<encoded-repo>-<number>-<role>`, escaping `_`, `.`, and
25
- `-` within the owner and repo names (`_u`, `_d`, `_h`) so `__` is always the one safe
26
- separator. For example, project `acme`, issue `sjawhar/legion#41`, role `architect` encodes
27
- to `legion-acme-sjawhar__legion-41-architect`. Never hand-format one for another role: your
28
- own role topic and your tree's architect's topic are stated at the end of your system
29
- prompt (a "Legion addressing" line the daemon appends), a sibling role's topic is yours
30
- with the trailing `-<role>` replaced, and the `roleToken` helper in `@legion/contracts`
28
+ `legion-<project>-<KEY>-<role>`. For example, project `acme`, issue `LEGION-41`, role
29
+ `architect` encodes to `legion-acme-LEGION-41-architect`. Never hand-format one for another
30
+ role: your own role topic and your tree's architect's topic are stated at the end of your
31
+ system prompt (a "Legion addressing" line the daemon appends), a sibling role's topic is
32
+ yours with the trailing `-<role>` replaced, and the `roleToken` helper in `@legion/contracts`
31
33
  computes any other one exactly the way the daemon does — prefer a topic you've already
32
34
  been given before recomputing one.
33
35
 
@@ -47,7 +49,7 @@ Escalate a product, scope, cross-phase, or lifecycle decision to the owning arch
47
49
  `envoy_publish` to its role topic (`notifications.role.` followed by its encoded token, see
48
50
  above), carrying the verified facts and the decision needed. `hub` only reaches subagents
49
51
  inside your own process, not the architect's separate one. For a durable question that needs
50
- Sami directly, you may use the raw `dispatch` MCP tool yourself; replies return to your own
52
+ Sami directly, you may use `dispatch_ask` yourself; replies return to your own
51
53
  session.
52
54
 
53
55
  Because the same agent is always resumed for its phase, you may receive more than one
@@ -133,11 +135,13 @@ capability it needs; invoke GitHub through the credential helper:
133
135
  legion gh -- <gh args…>
134
136
  ```
135
137
 
136
- ## GitHub comment attribution
138
+ ## GitHub PR comment attribution
137
139
 
138
- Append this exact structured footer to **every** GitHub issue comment, pull-request
139
- comment, and review that this phase posts. It preserves session provenance on the artifact
140
- itself so work stays attributable to the session that produced it:
140
+ Append this exact structured footer to **every** pull-request comment and review that this
141
+ phase posts on GitHub. It preserves session provenance on the artifact itself so work stays
142
+ attributable to the session that produced it. Dispatch comments carry session provenance
143
+ natively through their own `actor`/`origin` fields; this footer is only for GitHub PR
144
+ artifacts:
141
145
 
142
146
  ```html
143
147
  <!-- legion: {"session":"<session-id>","phase":"<phase>"} -->
@@ -146,7 +150,7 @@ itself so work stays attributable to the session that produced it:
146
150
  For example:
147
151
 
148
152
  ```bash
149
- legion gh -- issue comment <issue-number> \
153
+ legion gh -- pr comment <pr-number> \
150
154
  --body $'Verification complete.\n\n<!-- legion: {"session":"<session-id>","phase":"<phase>"} -->' \
151
155
  --repo <owner>/<repo>
152
156
  ```
@@ -159,17 +163,19 @@ procedure:
159
163
 
160
164
  ```bash
161
165
  cd -- "$LEGION_WORKSPACE" && \
162
- jj -R "$LEGION_WORKSPACE" bookmark set legion/issue-<n> && \
163
- jj -R "$LEGION_WORKSPACE" git push --bookmark legion/issue-<n> --allow-new
166
+ jj -R "$LEGION_WORKSPACE" bookmark set legion/<KEY> && \
167
+ jj -R "$LEGION_WORKSPACE" git push --bookmark legion/<KEY>
164
168
  ```
165
169
 
166
170
  The provisioned issue workspace configures `credential.helper` with the daemon's absolute
167
171
  credential command, so `jj -R "$LEGION_WORKSPACE" git push` authenticates transparently
168
172
  through the same session capability. Never handle a token.
169
173
 
170
- Then open the pull request with `legion gh -- pr create`. The credential helper and
171
- `legion gh` provide the GitHub identity; never export, fetch, or replace a token. Other
172
- phases advance the existing branch rather than creating a replacement bookmark or PR.
174
+ Then open the pull request with `legion gh -- pr create`. The PR body **must** contain the
175
+ line `Dispatch: <KEY>` the daemon's fallback link from a PR to its Dispatch issue when the
176
+ branch name alone is ambiguous. The credential helper and `legion gh` provide the GitHub
177
+ identity; never export, fetch, or replace a token. Other phases advance the existing branch
178
+ rather than creating a replacement bookmark or PR.
173
179
 
174
180
  ## PR body and merge-queue discipline
175
181
 
@@ -243,13 +249,13 @@ cd -- "$LEGION_WORKSPACE" && \
243
249
  ```
244
250
 
245
251
  If the issue bookmark exists locally, advance it and push it with the provisioned
246
- credential helper. `--allow-new` also publishes the locally provisioned bookmark on its
247
- first push:
252
+ credential helper. `--bookmark` also publishes the locally provisioned bookmark on its
253
+ first push — a bookmark not yet tracking a remote one is tracked automatically:
248
254
 
249
255
  ```bash
250
256
  cd -- "$LEGION_WORKSPACE" && \
251
- jj -R "$LEGION_WORKSPACE" bookmark set legion/issue-<n> && \
252
- jj -R "$LEGION_WORKSPACE" git push --bookmark legion/issue-<n> --allow-new
257
+ jj -R "$LEGION_WORKSPACE" bookmark set legion/<KEY> && \
258
+ jj -R "$LEGION_WORKSPACE" git push --bookmark legion/<KEY>
253
259
  ```
254
260
 
255
261
  Do not report phase completion until the write, existence check, and handoff commit
@@ -279,5 +285,5 @@ session, if this phase's work needs to run again.
279
285
 
280
286
  When blocked on lifecycle, scope, or cross-phase matters, `envoy_publish` the owning
281
287
  architect a concise message: issue, phase, verified observation, what you tried, and the
282
- decision required. Reach for `dispatch` yourself only for a standalone human question
288
+ decision required. Reach for `dispatch_ask` yourself only for a standalone human question
283
289
  outside that coordination.
@@ -39,7 +39,7 @@ Returns: title, description, status, labels, comments, attachments.
39
39
  linear_linear(action="update", id="ABC-123", state="Done")
40
40
  linear_linear(action="update", id="ABC-123", priority=1)
41
41
  linear_linear(action="update", id="ABC-123", assignee="me")
42
- linear_linear(action="update", id="ABC-123", labels=["legion-backlog", "existing-label"])
42
+ linear_linear(action="update", id="ABC-123", labels=["bug", "existing-label"])
43
43
  ```
44
44
 
45
45
  **Labels array replaces all labels.** Fetch current labels first, then append.