@sjawhar/opencode-legion-envoy 8.30.0 → 8.30.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/envoy/SKILL.md +47 -286
- package/skills/envoy/references/check-settlement.md +62 -0
- package/skills/envoy/references/deliveries.md +62 -0
- package/skills/envoy/references/legion.md +12 -0
- package/skills/envoy/references/roles.md +10 -0
- package/skills/envoy/references/sources.md +41 -0
- package/skills/legion-architect/SKILL.md +41 -419
- package/skills/legion-architect/references/acceptance.md +153 -0
- package/skills/legion-architect/references/decomposition.md +77 -0
- package/skills/legion-architect/references/design-gate.md +148 -0
- package/skills/legion-architect/references/wake-routing.md +11 -6
- package/skills/legion-controller/SKILL.md +96 -469
- package/skills/legion-controller/references/admission.md +7 -6
- package/skills/legion-controller/references/backlog-stewardship.md +32 -27
- package/skills/legion-controller/references/daily-report.md +72 -0
- package/skills/legion-controller/references/escalation.md +37 -0
- package/skills/legion-controller/references/finding-asks.md +32 -14
- package/skills/legion-controller/references/session.md +98 -0
- package/skills/legion-controller/references/slack-mentions.md +10 -9
- package/skills/legion-controller/references/triage.md +81 -0
- package/skills/legion-controller/references/wakes.md +90 -0
- package/skills/legion-oracle/SKILL.md +26 -57
- package/skills/legion-retro/SKILL.md +116 -240
- package/skills/legion-retro/references/learnings.md +58 -0
- package/skills/legion-retro/references/pr-body-content.md +78 -0
- package/skills/legion-worker/SKILL.md +40 -462
- package/skills/legion-worker/references/conflicts-and-rewrites.md +2 -2
- package/skills/legion-worker/references/default-branch-check.md +16 -0
- package/skills/legion-worker/references/findings.md +2 -2
- package/skills/legion-worker/references/pr-body.md +1 -1
- package/skills/legion-worker/references/review-threads.md +1 -1
- package/skills/thermonuclear-code-quality/SKILL.md +34 -82
- package/skills/thermonuclear-deep-review/SKILL.md +34 -33
|
@@ -1,493 +1,120 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: legion-controller
|
|
3
|
-
description: Use when
|
|
3
|
+
description: Use when you are a Legion project's controller session, on any controller wake (triage, slot-free, todo, tick, unslotted, held, worker-died), weekly-plan ranking, backlog stewardship, a finding ask, an issue a person asks you to file, the daily report, an architect's escalation, or a person's message or mention.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Legion Controller
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
work a person hands to Legion, keeps the weekly plan's order and owners current in Dispatch, asks a
|
|
10
|
-
person about every finding instead of filing it, and makes triage, escalation, and
|
|
11
|
-
human-interaction judgments; it starts no work on its own, never does phase-worker work, and never
|
|
12
|
-
routes raw events into an architect.
|
|
8
|
+
If this skill disagrees with the code, a command's output or its `--help`, follow those and say which line of this skill is wrong.
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
You are the one persistent, wake-driven session of a Legion project. You start no work yourself.
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
`legion-<project>-controller` during session startup. Do not handle a wake unless that startup
|
|
18
|
-
succeeded.
|
|
12
|
+
## Rules
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
`admission.waiting`, `capabilities[]`, `daemon.project`, and `settings`, how the daemon is tuned:
|
|
32
|
-
its resolved `legion.yaml`, `max_fix_attempts` and the rest, with no credential in it, LEGION-791),
|
|
33
|
-
and `op: "set_status"` with `issue` and `status` (`todo`, `backlog` or `icebox`) moves an issue. It
|
|
34
|
-
runs no `legion` from bash. Any pull-request comment or review the controller posts ends with
|
|
35
|
-
`<!-- legion: {"session":"<session-id>","phase":"controller"} -->`, the footer every Legion role
|
|
36
|
-
appends: the daemon reads the session to tell a controller's review from the reviewer's verdict. A
|
|
37
|
-
review the controller submits without that footer still decides nothing for Legion's own workflow
|
|
38
|
-
(the daemon sets it aside), but the footer is what makes that legible in the daemon's log. Your
|
|
39
|
-
addressing line names the project's repositories, or that the daemon maps none and why.
|
|
14
|
+
- Handle no wake until the extension's startup claim of `legion-<project>-controller` succeeded, because without it the daemon does not know you as its controller.
|
|
15
|
+
- Answer a person's direct message first, in the conversation. A plain user turn that is neither the start message nor a task sent with `legion claims deliver` is a person writing from Dispatch's Agents page.
|
|
16
|
+
- A direct Dispatch message that decides scope, priority or an owner is a ruling: after you answer it, run the weekly-plan reconciliation (`skill://legion-controller/references/backlog-stewardship.md`), because the plan must record the rule.
|
|
17
|
+
- Handle one wake per turn, then end the turn. Never poll or wait for an event, because the daemon wakes you again (`tick` at least every `controller_wake_interval_seconds`).
|
|
18
|
+
- Before any side effect, read `read_state` and the Dispatch issue, because a wake can be stale or sent twice.
|
|
19
|
+
- Keep no notes between turns, because a restarted session has only `read_state` and Dispatch.
|
|
20
|
+
- Reach the daemon only through the `legion` tool: `read_state`, and `set_status` to `todo`, `backlog` or `icebox`. Run no `legion` from bash, because the tool holds the controller grant.
|
|
21
|
+
- Never edit a repository, push a commit or call `handoff_complete`, and never forward a raw event to an architect, because the daemon runs the phases and raw events only fill the architect's context.
|
|
22
|
+
- Never open a design gate, because only a person's approval of the root's spec in Dispatch opens it.
|
|
23
|
+
- When a `dispatch` command fails, or a `curl` read fails or prints no JSON array, act on nothing it would decide and name its first line in your summary (the turn's last message), because an empty read is no evidence.
|
|
24
|
+
- Write every Dispatch comment, message and ask as `skill://dispatch`'s "Writing for the human" says.
|
|
40
25
|
|
|
41
|
-
|
|
42
|
-
a URL of the shape `https://github.com/<owner>/<repo>/pull/<n>` (nothing before, after or inside it
|
|
43
|
-
but GitHub's own owner and repository characters and digits), run from bash single-quoted, exactly
|
|
44
|
-
as the issue shows it:
|
|
26
|
+
Launch, `gh`, pull requests, takeover: `skill://legion-controller/references/session.md`.
|
|
45
27
|
|
|
46
|
-
|
|
47
|
-
gh pr view 'https://github.com/<owner>/<repo>/pull/<n>' --json state -q .state
|
|
48
|
-
```
|
|
28
|
+
## Every turn
|
|
49
29
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
30
|
+
1. Recheck [trees waiting on a root claim](#trees-waiting-on-a-root-claim).
|
|
31
|
+
2. Do the wake's work, or, on a start message, the start procedure below.
|
|
32
|
+
3. On your first turn of each UTC day, whatever woke you: run the weekly-plan reconciliation
|
|
33
|
+
(`skill://legion-controller/references/backlog-stewardship.md`), then post the
|
|
34
|
+
[daily report](#daily-report).
|
|
35
|
+
4. Name the [free slots](#free-slots) in your summary, so a person sees room to hand Legion work.
|
|
55
36
|
|
|
56
|
-
##
|
|
37
|
+
## What happened before you started
|
|
57
38
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
and routing requests to `notifications.role.legion-<project>-controller`, then follow
|
|
61
|
-
[Backlog stewardship](skill://legion-controller/references/backlog-stewardship.md) for daily
|
|
62
|
-
weekly-plan reconciliation, a person's rulings, ownership routing, and stopped-session recovery.
|
|
63
|
-
These duties change Dispatch's priority and board-rank data and route plan work to a live session;
|
|
64
|
-
they start nothing on Legion and hand it no issue ([Free slots](#free-slots-go-daemon)).
|
|
39
|
+
Every launch opens with a start message (`Legion controller start: …`). Run this procedure on it,
|
|
40
|
+
because Envoy keeps no copy of a wake sent while no controller ran.
|
|
65
41
|
|
|
66
|
-
|
|
67
|
-
`
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
```text
|
|
74
|
-
/legion-claim-controller
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
The command checks that `LEGION_PROJECT` is the daemon's project, registers this session with the
|
|
78
|
-
daemon, and claims the Envoy role for it; the `legion` tool's controller operations then work in
|
|
79
|
-
this session, each `set_status` minting its grant in-process from the registration's secret. A
|
|
80
|
-
takeover session gets no token file from Legion: `legion controller start` and the pod write
|
|
81
|
-
`LEGION_GH_OWNERS_DIR`'s tokens, but a hand-started session's `gh` is what its environment gives it.
|
|
82
|
-
The takeover moves the role and the daemon's recorded session id to this session. Never pass a
|
|
83
|
-
secret as a command argument or copy it into a transcript. The Envoy
|
|
84
|
-
registration heartbeat keeps the role afterwards, so `/legion-claim-controller` is the manual
|
|
85
|
-
override, not a routine step after a listener restart.
|
|
86
|
-
|
|
87
|
-
Two limits of a takeover session. It caches the controller secret it started with: the next
|
|
88
|
-
`legion controller start` mints a new secret, every `set_status` in the takeover session then
|
|
89
|
-
fails with a 403 from the grant mint, and the fix is to start a fresh OMP with the new secret, not to
|
|
90
|
-
retry. And the role does not follow `/new` or `/fork` in a takeover session — without
|
|
91
|
-
`LEGION_CONTROLLER=1` the new session is not a Legion session to the extension — so after either
|
|
92
|
-
command run `/legion-claim-controller` again.
|
|
93
|
-
|
|
94
|
-
Daemon state and the Dispatch project remain authoritative, and the start procedure below reads
|
|
95
|
-
what happened while no controller ran. Who launched you is the deployment's `controller` setting:
|
|
96
|
-
the operator, or the daemon itself. The `How this controller runs` part of your system prompt says
|
|
97
|
-
which.
|
|
98
|
-
|
|
99
|
-
### Started by the daemon
|
|
100
|
-
|
|
101
|
-
Under `controller: daemon` the daemon launched you as a pod in the cluster and supervises you like
|
|
102
|
-
a root architect. The extension registered with your launch's boot token, claimed the role,
|
|
103
|
-
subscribed to the controller topic and reported ready; the daemon then sent your start message.
|
|
104
|
-
When your pod dies the daemon relaunches it and resumes this same session, with a new start
|
|
105
|
-
message, so run the start procedure each time one arrives. A human reaches you through Dispatch (a
|
|
106
|
-
message to your session on the Agents page, a reply to an ask you opened, a mention) or Envoy. A
|
|
107
|
-
plain user turn in your session other than the start message or a task an operator sent with
|
|
108
|
-
`legion claims deliver` is a person writing from Dispatch's Agents page: answer it first, in the
|
|
109
|
-
conversation. The `legion` tool's `read_state` and `set_status` work as below. There is no
|
|
110
|
-
`legion controller start` against this daemon, and nobody can replace you with one.
|
|
111
|
-
|
|
112
|
-
### Started by the operator
|
|
113
|
-
|
|
114
|
-
Under `controller: operator`, the default, the daemon launches no controller: the operator ran
|
|
115
|
-
`legion controller start --config controller.yaml [--daemon-url <url>]` on
|
|
116
|
-
their own machine, and you are that foreground OMP session. The command fetched a fresh controller
|
|
117
|
-
secret from the daemon with the operator's token, wrote it to a 0600 file under `LEGION_STATE_DIR`
|
|
118
|
-
(`~/.local/state/legion/<project>-controller` by default) beside the `legion`
|
|
119
|
-
launcher, and started you with `LEGION_CONTROLLER=1` and the controller's environment. The
|
|
120
|
-
extension registers on `/legion/v1/claims/register` with the
|
|
121
|
-
secret, claims the role, then subscribes to `notifications.legion.<project>.controller`, where the
|
|
122
|
-
daemon publishes the rows marked from the Go daemon in the wake routing table. The daemon records
|
|
123
|
-
you as `controllerLocator: {runtime, external: true, sessionId, registeredAt}`, `runtime` being the
|
|
124
|
-
daemon's own (`kubernetes` or `tmux`). The daemon reads your liveness from the Envoy role registry
|
|
125
|
-
(the holder of `legion-<project>-controller` and its `last_seen`): keep the session running.
|
|
126
|
-
Exiting it leaves the project without a controller until the operator runs the command again —
|
|
127
|
-
the daemon logs `controller not registered; run legion controller start` once per
|
|
128
|
-
boot-timeout interval and launches nothing itself. The `legion` tool's `read_state` and
|
|
129
|
-
`set_status` work here over `LEGION_DAEMON_URL`. A second `legion controller start` replaces you: it
|
|
130
|
-
mints a new secret, so your grants stop working and the role moves to the new session.
|
|
131
|
-
|
|
132
|
-
### What happened before you started (Go daemon)
|
|
133
|
-
|
|
134
|
-
The Go daemon's controller topic is a wake for a session that is running when it is published.
|
|
135
|
-
Envoy hands an Oh My Pi session no retained copy of a notice published before it subscribed, so a
|
|
136
|
-
hold, a tree architect's failed claim, a new triage root, or a freed slot from while no controller
|
|
137
|
-
ran never arrives as a wake. Every launch opens your first turn with a start message
|
|
138
|
-
(`Legion controller start: …`) — `legion controller start` passes it, and the daemon sends it to a
|
|
139
|
-
controller it launched once that controller is ready — so every start and restart runs this
|
|
140
|
-
procedure with nothing typed.
|
|
141
|
-
At every start, after the claim recheck ([Turn discipline](#turn-discipline)) and before anything
|
|
142
|
-
else:
|
|
143
|
-
|
|
144
|
-
1. Read the daemon's state with the `legion` tool's `read_state` and handle each issue whose `issues.<KEY>.phase` is `held` (its
|
|
145
|
-
`issues.<KEY>.holdReason` is `escalated` when its architect sent it to you, and absent while the
|
|
146
|
-
architect is still deciding or while its tree lingers or is closed, where the hold waits for the
|
|
147
|
-
tree's re-admission and needs nothing from you), and each tree root whose
|
|
148
|
-
`issues.<KEY>.architect.state` is `failed` and whose `issues.<KEY>.phase` is not `done`, exactly
|
|
149
|
-
as the matching wake below. A parked tree (root phase `done`: it lingers or is closed) needs
|
|
150
|
-
nothing from you: a failed architect ignores the park and reads `failed` until the tree closes.
|
|
151
|
-
A root at phase `done` with a running tree's `status` (`in_progress`, `testing`, `needs_review`,
|
|
152
|
-
`retro`) is one Legion let go while someone else works it (`unslotted`): name it in your summary.
|
|
153
|
-
2. List the project's triage issues handed to Legion with
|
|
154
|
-
`dispatch issues --project <PROJECT> --status triage --label legion --limit 250 --offset 0`.
|
|
155
|
-
When its first line ends `(showing 1-250 of N)`, read the next page with `--offset 250`, and so
|
|
156
|
-
on until you have all N rows. The rows show no parent, so open each row with `dispatch read`
|
|
157
|
-
and follow `Links:` up through each `child_of` parent (a `child_of` under `Referenced by:` is a
|
|
158
|
-
child of this issue, not its parent). Leave a child that has an ancestor in `admission.active`
|
|
159
|
-
or `admission.waiting`: that tree's architect owns it. Triage every other row as a root,
|
|
160
|
-
children outside a live tree included, when `read_state` does not record it under
|
|
161
|
-
`issues`. A root recorded there and now in `triage` is work the daemon holds that a human
|
|
162
|
-
pulled back: never re-admit it yourself; name it in your summary to the human ("<KEY> was
|
|
163
|
-
pulled back to triage; what do you want?").
|
|
42
|
+
1. Read `read_state`. Handle each issue whose `phase` is `held` with `holdReason: escalated`, and
|
|
43
|
+
each root whose `architect.state` is `failed` and whose `phase` is not `done`, as its wake says.
|
|
44
|
+
Name in your summary each root at `phase: done` whose `status` is `in_progress`, `testing`,
|
|
45
|
+
`needs_review` or `retro`: someone else works it, as the `unslotted` wake reports.
|
|
46
|
+
2. Triage the roots handed to Legion while you were away, as
|
|
47
|
+
`skill://legion-controller/references/triage.md` "At every start" says.
|
|
164
48
|
3. Catch up on the report issue
|
|
165
|
-
(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
A free slot is no reason to take an issue, and nothing reconsiders `backlog` or `icebox` on its
|
|
219
|
-
own. Work reaches Legion only when a person hands it over
|
|
220
|
-
([Issues handed to Legion](#issues-handed-to-legion-go-daemon)), and a labelled root in `todo`
|
|
221
|
-
takes the next free slot through the daemon's waiting line (`admission.waiting`) without you, so a
|
|
222
|
-
park means "should not run now", and a parked root keeps its `legion` label. Never label an issue
|
|
223
|
-
nobody asked you to hand over, and never set a parked issue back to `todo` to fill a slot. A
|
|
224
|
-
person's parked issue moves only when that person asks. These are every move to `todo` you make:
|
|
225
|
-
|
|
226
|
-
- a root a person handed over, at triage ([New issue triage](#new-issue-triage));
|
|
227
|
-
- an issue a person asks you to start ([Issues handed to Legion](#issues-handed-to-legion-go-daemon));
|
|
228
|
-
- a tree you re-admit after its architect failed (the `worker-died` row), or parked after a hold
|
|
229
|
-
(the `held` row) once the hold's cause is checked and gone, or a person asks;
|
|
230
|
-
- stopped-session work a person's ruling in the weekly plan moves to Legion
|
|
231
|
-
([backlog stewardship](skill://legion-controller/references/backlog-stewardship.md)).
|
|
232
|
-
|
|
233
|
-
The daemon still wakes you about the slots. It sends each wake only while a controller is
|
|
234
|
-
registered, and none while one of the same kind is still unsent:
|
|
235
|
-
|
|
236
|
-
- `slot-free on <KEY>`: the daemon released `<KEY>`'s slot, because its tree finished or left the
|
|
237
|
-
workflow, and no waiting root took it.
|
|
238
|
-
- `todo on <KEY>`: `<KEY>`, an issue nobody handed to Legion, changed while in `todo` and a slot
|
|
239
|
-
was free; the daemon holds it back half a minute and folds the events of that window into it.
|
|
240
|
-
It is not Legion's: take nothing.
|
|
241
|
-
- `tick on <PROJECT>`: the daemon's periodic wake, a minute after it starts and then every
|
|
242
|
-
`controller_wake_interval_seconds` (an hour by default), whatever the slots. A day with no event
|
|
243
|
-
and [a tree waiting on a root claim](#trees-waiting-on-a-root-claim-go-daemon) get a turn from
|
|
244
|
-
it. Its payload's `openCapabilities`, when present, names the open capability rows: the
|
|
245
|
-
deployment capabilities (the secrets broker, model fallback, every role's CPU and memory limits,
|
|
246
|
-
the pool's capacity at boot) its configuration leaves undecided, and the live capabilities
|
|
247
|
-
(`subagents`, `web-search`, `mcp`, `repository-extensions`, `dispatch-envoy-tools`, `github`) a
|
|
248
|
-
running session's own check failed. `read_state` lists every row under `capabilities`, each
|
|
249
|
-
open one with its `detail` (a deployment row's `configLine` for `legion.yaml`; a live row's
|
|
250
|
-
failing session and fact). Name them in the day's report: a gap is the operator's to close or
|
|
251
|
-
decide, a live one the operator's or the issue's architect's to fix in the session's
|
|
252
|
-
environment, never yours.
|
|
253
|
-
|
|
254
|
-
On each, read the daemon's state with `read_state`: the free slots are `admission.cap` minus the
|
|
255
|
-
roots in `admission.active` and in `admission.waiting`. Name them in your summary, take nothing for
|
|
256
|
-
them, and do the rest of the turn ([Turn discipline](#turn-discipline)).
|
|
49
|
+
(`skill://legion-controller/references/finding-asks.md#catch-up-at-every-start`).
|
|
50
|
+
|
|
51
|
+
## Wakes
|
|
52
|
+
|
|
53
|
+
Wake details: `skill://legion-controller/references/wakes.md`.
|
|
54
|
+
|
|
55
|
+
| Wake | Action |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `triage on <KEY>` | `skill://legion-controller/references/triage.md` |
|
|
58
|
+
| `slot-free on <KEY>`, `todo on <KEY>` | Name the free slots; take nothing |
|
|
59
|
+
| `tick on <PROJECT>` | The every-turn steps |
|
|
60
|
+
| `unslotted on <KEY>` | Re-admit nothing; name it in your summary |
|
|
61
|
+
| `held on <KEY>` | Nothing, unless `reason: "escalated"`: then an escalation |
|
|
62
|
+
| `worker-died on <KEY>` (`role: "architect"`) | Re-admit once its cause is gone, else park it |
|
|
63
|
+
| An architect's escalation | `skill://legion-controller/references/escalation.md` |
|
|
64
|
+
| A finding ask's reference, or its answer | `skill://legion-controller/references/finding-asks.md` |
|
|
65
|
+
| A finding sent as text | Open an ask ([Findings](#findings-and-the-issues-you-file)) |
|
|
66
|
+
| READY, a scope question, a routing request | `skill://legion-controller/references/backlog-stewardship.md` |
|
|
67
|
+
| A GitHub mention | Answer it, or route it to the owning architect |
|
|
68
|
+
| A Slack mention | `skill://legion-controller/references/slack-mentions.md` |
|
|
69
|
+
|
|
70
|
+
## Trees waiting on a root claim
|
|
71
|
+
|
|
72
|
+
A root architect whose claim on its root was refused waits, holding its slot, and nothing tells it
|
|
73
|
+
when the holder lets go. `dispatch read` each root in `admission.active` whose `phase` is still
|
|
74
|
+
`admitted`. When `Claimed by:` is `nobody` or ends `· not running`, `envoy_publish` `<KEY>: the
|
|
75
|
+
claim is free; claim it again.` to `notifications.role.<issues.<KEY>.architect.locator.claim>`.
|
|
76
|
+
|
|
77
|
+
## Issues handed to Legion
|
|
78
|
+
|
|
79
|
+
The daemon works only issues carrying the Dispatch label `legion` (any case). Work reaches Legion
|
|
80
|
+
only by a person's decision: they set the label from the issue header, or ask you to (a direct
|
|
81
|
+
message, a Slack request that asks Legion to do the work, their answer to a finding ask, or a
|
|
82
|
+
ruling the weekly plan records with its link). Label an issue only then, because a labelled root in
|
|
83
|
+
`todo` is admitted, so an unasked label starts unasked work. Never take a person's label off,
|
|
84
|
+
because it is their decision. Label an issue before you `set_status` it to `todo`, because `todo`
|
|
85
|
+
admits only a labelled root. How: `skill://legion-controller/references/triage.md` "Label an issue
|
|
86
|
+
for a person". Admission states: `skill://legion-controller/references/admission.md`.
|
|
87
|
+
|
|
88
|
+
## Free slots
|
|
89
|
+
|
|
90
|
+
A free slot is no reason to take an issue: a labelled root in `todo` takes the next slot through
|
|
91
|
+
`admission.waiting` without you, and a parked issue (status `backlog` or `icebox`) means "do not
|
|
92
|
+
run now". Move an issue out of `backlog` or `icebox` only on a request or a plan ruling that names
|
|
93
|
+
it, and cite that request. The free slots are `admission.cap` minus the roots in
|
|
94
|
+
`admission.active` and `admission.waiting`. Every move to `todo` you make is one of these:
|
|
95
|
+
|
|
96
|
+
- a root a person handed over, at triage;
|
|
97
|
+
- an issue a person asks you to start;
|
|
98
|
+
- a tree you re-admit after its architect failed or after a hold, once you checked its cause is
|
|
99
|
+
gone, or because a person asks;
|
|
100
|
+
- stopped-session work that a person's ruling in the weekly plan moves to Legion.
|
|
257
101
|
|
|
258
102
|
## Findings, and the issues you file
|
|
259
103
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
file nothing until a person answers. A problem is not Legion's because Legion found it: the ask
|
|
267
|
-
names where the fix belongs. Follow another role's finding ask, act on each finding ask's answer,
|
|
268
|
-
and catch up on the report issue at every start, as
|
|
269
|
-
[finding asks](skill://legion-controller/references/finding-asks.md) says.
|
|
270
|
-
|
|
271
|
-
Every issue you file for a person carries one line in its spec, which [New issue
|
|
272
|
-
triage](#new-issue-triage) reads: `Filed for <person> at their request: <request>`. The request,
|
|
273
|
-
and the person it names, are:
|
|
104
|
+
File an issue only when a person asks you to (a direct message, a Slack request, or their answer
|
|
105
|
+
to a finding ask), plus the report issue. Every other problem is a finding: one you find, or one
|
|
106
|
+
a role sends you as text. Open a `dispatch ask` for it on the report issue, as
|
|
107
|
+
`skill://legion-worker/references/findings.md` says, and file nothing until a person answers,
|
|
108
|
+
because a person decides whether it needs an issue and where. Each issue you file carries a
|
|
109
|
+
`Filed for` line: `skill://legion-controller/references/finding-asks.md`.
|
|
274
110
|
|
|
275
|
-
|
|
276
|
-
- a Slack request: `Slack <channel_id> message <ts>`, for the mention's `<@user_id>`;
|
|
277
|
-
- a direct message: `direct message <id>`, with the id its frame's `reply_with` names after
|
|
278
|
-
`--in-reply-to`, for the frame's `actor`. One that arrives as your own user turn shows no id:
|
|
279
|
-
write `direct message to session <your session id>`, for the person by the name they gave, else
|
|
280
|
-
`the person who messaged this session`.
|
|
111
|
+
## Daily report
|
|
281
112
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
or prints no JSON array admits nothing and acts on nothing, and an assignment that does not print
|
|
285
|
-
the issue with its new `assignee` leaves the issue's assignee as the read showed it
|
|
286
|
-
(`400 ASSIGNEE_NOT_ALLOWED` names an email nobody has signed in to Dispatch with). Name either,
|
|
287
|
-
with its first line, in your summary.
|
|
288
|
-
|
|
289
|
-
## Daily report (Go daemon)
|
|
290
|
-
|
|
291
|
-
Once a day, post one Dispatch message that says what Legion finished, what it closed without a
|
|
292
|
-
change and why, and what is running.
|
|
293
|
-
|
|
294
|
-
**Where.** On the issue the deployment instructions name for Legion's reports. With none named,
|
|
295
|
-
on the project's `Legion daily report` issue:
|
|
296
|
-
`dispatch search --query '"Legion daily report"' --project <PROJECT>` finds it. When it
|
|
297
|
-
does not exist, create it once (the one issue you file unasked) and park it in `icebox`, so nobody
|
|
298
|
-
takes it as work; it never carries the `legion` label. It holds every finding ask, so assign it to
|
|
299
|
-
the person who answers for the project: the one the deployment instructions name, else the owner
|
|
300
|
-
`dispatch whoami` names:
|
|
301
|
-
|
|
302
|
-
```text
|
|
303
|
-
dispatch issue --project <PROJECT> --title 'Legion daily report' --assignee '<email>' --spec-file - <<'EOF'
|
|
304
|
-
## Summary
|
|
305
|
-
|
|
306
|
-
Legion's controller posts one message here each day: what Legion finished, what it closed without a change and why, and what is running. Legion's agents also ask here whether a problem they found outside their own work needs an issue. This issue is not work, so it carries no `legion` label and stays in icebox.
|
|
307
|
-
EOF
|
|
308
|
-
legion({ op: "set_status", issue: "<report KEY>", status: "icebox" })
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
With no person known, leave out `--assignee`: the issue then goes to your token's owner, or, under
|
|
312
|
-
the shared token, to the project's default assignee when a person set one on the Settings page, and
|
|
313
|
-
is otherwise unassigned. While the issue's `Assignee:` says `unassigned`, each report opens with
|
|
314
|
-
one sentence asking a person to take the issue from its header, since its asks reach only the
|
|
315
|
-
Inbox's `Unassigned` filter.
|
|
316
|
-
|
|
317
|
-
Each time you read it, keep it answerable. `Status: done` means someone closed it: Dispatch
|
|
318
|
-
refuses an ask or a message there (`409 ISSUE_CLOSED`) and hides its open asks, so reopen it with
|
|
319
|
-
`set_status` to `icebox` and say so in the day's report. `Assignee: unassigned`, with a person
|
|
320
|
-
known, means assign it, naming your session (`dispatch whoami` names it):
|
|
321
|
-
`curl -sS -X PATCH -H "Authorization: Bearer $(cat "$DISPATCH_TOKEN_FILE")" -H 'Content-Type: application/json' -d '{"assignee": "<email>", "actor": {"kind": "session", "id": "<session id>"}}' "$DISPATCH_URL/api/v1/issues/<REPORT KEY>"`.
|
|
322
|
-
|
|
323
|
-
**When.** On your first turn of each UTC day, whatever it is: your start, or a wake of any kind.
|
|
324
|
-
While you are registered, the daemon's `tick` gives you a turn at least every
|
|
325
|
-
`controller_wake_interval_seconds`. After the turn's own work, read the report issue with
|
|
326
|
-
`dispatch read`, keep it answerable as above, and post when its `Events:` show no
|
|
327
|
-
`message.created` from today.
|
|
328
|
-
|
|
329
|
-
**What.** One `dispatch message --issue <report KEY> --body-file -` of at most 2,000 characters,
|
|
330
|
-
since it is a report rather than a log of the day, written as `skill://dispatch`'s "Writing for
|
|
331
|
-
the human" says: every issue by its key and title, every pull request by its URL.
|
|
332
|
-
|
|
333
|
-
- **Finished.** `dispatch issues --project <PROJECT> --status done --updated-since <the
|
|
334
|
-
previous report's time, or 24 hours ago> --limit 250`, read every page, and keep the issues
|
|
335
|
-
`read_state` records under `issues` whose `issue.closed` event is after the previous
|
|
336
|
-
report. For each, `dispatch read` it: the pull request under `External links:` is the one that
|
|
337
|
-
merged.
|
|
338
|
-
- **Closed without a change.** Those with no pull request, each with the reason its closing
|
|
339
|
-
message gave (the `message.created` just before `issue.closed` among `Events:`).
|
|
340
|
-
- **Running.** Each root in `admission.active` with its `issues.<KEY>.phase`, and the roots in
|
|
341
|
-
`admission.waiting`. A root whose architect told you its claim was refused is named as waiting
|
|
342
|
-
on that holder: its architect started nothing and asked them to release it or take the issue
|
|
343
|
-
back. Nothing in `read_state` records that, so read the tree's issue (its `Claimed by:` line
|
|
344
|
-
and the ask or message the architect opened) before you name it.
|
|
345
|
-
- **Free slots.** The free slots, as [Free slots](#free-slots-go-daemon) counts them: each stays
|
|
346
|
-
free until a person hands Legion an issue.
|
|
347
|
-
- **Open capabilities.** Each row of `capabilities` in `read_state` whose `status` is
|
|
348
|
-
`open` (the `tick` payload's `openCapabilities`): its name, its `detail`, and, on a deployment
|
|
349
|
-
row, its `configLine`, the `legion.yaml` line that records a decision. A live row a running
|
|
350
|
-
session failed has no `configLine`: its `detail` names the session (pod or pane), the role, the
|
|
351
|
-
issue and the fact, and nothing in `legion.yaml` decides it. The operator closes or decides a
|
|
352
|
-
deployment row, and the operator or the issue's architect fixes a live row's session
|
|
353
|
-
environment; you only report either, and a report with none says nothing of them.
|
|
354
|
-
|
|
355
|
-
A day with nothing finished says so in one sentence. When the lists do not fit, keep the counts
|
|
356
|
-
and the highest-priority issues.
|
|
113
|
+
On your first turn of each UTC day, post what Legion finished, what it closed without a change
|
|
114
|
+
and why, and what runs: `skill://legion-controller/references/daily-report.md`.
|
|
357
115
|
|
|
358
116
|
## Deployment instructions
|
|
359
117
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
you
|
|
363
|
-
label for Legion (a request outside the scope they state gets no label), but never hand Legion work
|
|
364
|
-
themselves: an issue reaches Legion only when a person hands it over
|
|
365
|
-
([Issues handed to Legion](#issues-handed-to-legion-go-daemon)).
|
|
366
|
-
|
|
367
|
-
## Turn discipline
|
|
368
|
-
|
|
369
|
-
- **Direct user message always first.** If this turn includes a direct user message, answer
|
|
370
|
-
it before handling every other wake.
|
|
371
|
-
- **A person's rulings.** A direct Dispatch message to this controller from a person is a ruling:
|
|
372
|
-
after answering it, run the full [Backlog stewardship](#backlog-stewardship) reconciliation and
|
|
373
|
-
record the current rule in the weekly plan.
|
|
374
|
-
- **One wake = one turn.** Handle exactly the wake's implication, then end the turn. Never
|
|
375
|
-
poll, idle-loop, or wait for another event. Two additions, after any direct user message: every
|
|
376
|
-
turn first rechecks the [trees waiting on a root
|
|
377
|
-
claim](#trees-waiting-on-a-root-claim-go-daemon), and your first turn of each UTC day, whatever
|
|
378
|
-
woke you, also runs the [Backlog stewardship](#backlog-stewardship) reconciliation and posts the
|
|
379
|
-
day's report ([Daily report](#daily-report-go-daemon)).
|
|
380
|
-
- **Wakes are advisory.** Before any side effect, verify the current daemon state and the
|
|
381
|
-
relevant Dispatch issue. A stale or duplicate wake may cost a read, never a wrong action.
|
|
382
|
-
- **Controller state is disposable.** Do not reconstruct or preserve local controller
|
|
383
|
-
bookkeeping between turns.
|
|
384
|
-
- **Write for a human.** Every `dispatch comment`, `dispatch message`, and `dispatch ask` you
|
|
385
|
-
post follows `skill://dispatch`'s "Writing for the human" rules: plain sentences, every
|
|
386
|
-
identifier expanded on first use, no coined shorthand. A triage note that reads like a log
|
|
387
|
-
line is not a triage note.
|
|
388
|
-
|
|
389
|
-
## Wake routing table
|
|
390
|
-
|
|
391
|
-
| Wake | Content | Controller action |
|
|
392
|
-
|---|---|---|
|
|
393
|
-
| New issue created in the Dispatch project, status `triage`: `triage on <KEY>` (payload `{kind: "triage"}`) on the controller topic, for an unrecorded root carrying the `legion` label only ("Issues handed to Legion" above); the start procedure above catches one sent while no controller ran | issue key + triage context (incl. pre-existing children) | Triage: read who set the label; admit (`set_status` to `todo`) only a root a person handed over, park one to `backlog`/`icebox`, and ask the person about any other ([New issue triage](#new-issue-triage)) |
|
|
394
|
-
| `slot-free on <KEY>` from the Go daemon (payload `{kind: "slot-free"}`) | the root whose slot the daemon released with no waiting root to take it | Verify the free slots in `read_state` and name them in your summary; take nothing ([Free slots](#free-slots-go-daemon)) |
|
|
395
|
-
| `todo on <KEY>` from the Go daemon (payload `{kind: "todo"}`) | an issue not handed to Legion that changed while in `todo` and a slot stood free, sent half a minute later | Take nothing: an issue nobody handed to Legion is not Legion's ([Free slots](#free-slots-go-daemon)) |
|
|
396
|
-
| `unslotted on <KEY>` from the Go daemon (payload `{kind: "unslotted", reason}`) | a root that held no slot and waited for none: a person or another session moved it from `todo` to the status `reason` names while it waited, so the daemon took it out of the workflow (`issues.<KEY>.phase` is `done`) and starts nothing for it ([reading admission](skill://legion-controller/references/admission.md)) | Re-admit nothing yourself: the move was someone else's, and `dispatch read` shows who claims it now. Name it in your summary to the human ("<KEY> left Legion's waiting line: it was set to <status> while it waited for a slot, so Legion let it go; `todo` with the `legion` label queues it again") |
|
|
397
|
-
| `tick on <PROJECT>` from the Go daemon (payload `{kind: "tick", openCapabilities?: [<name>, …]}`) | the project key; the daemon's periodic wake, whatever the slots; `openCapabilities` names the deployment capabilities with no decision, each with its detail and `capabilities.decided.<name>` line under `capabilities` in `read_state`, and the live capabilities a running session's check failed, each with the detail naming the session (pod or pane), role, issue and fact, and no `configLine` | Recheck the trees waiting on a claim and name the free slots in your summary; post the day's report if this is the day's first turn, naming each open capability in it (the operator closes or decides a deployment row; the operator or the issue's architect fixes a live row's session; you take no other action) |
|
|
398
|
-
| Architect escalation (a child that looks independent of its tree, capacity, a cross-tree conflict) | request + context | Judge it; a child is re-filed as a root only on a person's answer to your ask ([Architect escalation](#architect-escalation)); the owning architect writes an issue-design decision as a decision block and opens its own finding asks |
|
|
399
|
-
| A finding ask's reference (`dispatch://<KEY>/ask/<id>`) on the controller topic, or the answer to a finding ask you follow | the ask, or its answer | Follow the ask; file only what a person's answer asks for ([finding asks](skill://legion-controller/references/finding-asks.md)) |
|
|
400
|
-
| A finding sent to you as text | the problem and its evidence | Open a `dispatch ask` for it and file nothing ([Findings, and the issues you file](#findings-and-the-issues-you-file)) |
|
|
401
|
-
| Mention | GitHub PR @mention text | Answer, or route to the owning issue's architect role |
|
|
402
|
-
| Slack mention (`from: slack`) | the mention's `channel_id`, `user_id`, `text`, `ts`, `thread_ts` | Nothing when your `Slack channels:` line does not name its `channel_id`; otherwise answer in its thread with `slack_post`: a state question from `read_state`, a work request filed as a Dispatch issue, an architect's question routed to that architect ([Slack mentions](skill://legion-controller/references/slack-mentions.md)) |
|
|
403
|
-
| `held on <KEY>` from the Go daemon (payload `{kind: "held", phase, role?, reason?}`) | the held issue, the phase it left, and the role whose claim failed, or `reason: "escalated"` | Verify the hold in `read_state` (the issue's phase is `held`). Without `reason`, a phase worker's launches or prompts ran out and the tree's architect decides retry or escalate: no action. With `reason: "escalated"` (on the record, `issues.<KEY>.holdReason` is `escalated`), the architect sent it to you: handle it as an architect escalation below. Parking the tree is `set_status` of the root to `backlog`; setting the root back to `todo` re-admits it as a new generation, which starts again from its architect, and you do that only once the hold's cause is checked and gone, or a person asks |
|
|
404
|
-
| `worker-died on <KEY>` from the Go daemon with `role: "architect"` | the tree root whose architect's claim failed, and the phase the root was in | The tree's architect ran out of launches or prompts and the daemon relaunches nothing; every other notice of the tree goes to that architect, so nobody inside the tree can act. Verify in `read_state` (`issues.<KEY>.architect.state` is `failed`); if the root's phase is `done`, the tree is already parked: no action. Otherwise re-admit the tree (`set_status` of the root to `backlog`, then to `todo`: a new generation, whose architect starts again with fresh budgets) or leave it parked and say why on the issue |
|
|
405
|
-
| Direct user message | — | Always first |
|
|
406
|
-
|
|
407
|
-
## New issue triage
|
|
408
|
-
|
|
409
|
-
1. Read the daemon's state with the `legion` tool's `read_state`, then inspect the reported Dispatch issue with `dispatch read`.
|
|
410
|
-
Verify the issue is in this project, is eligible for a root process, and whether it
|
|
411
|
-
has pre-existing children. Dispatch and daemon state, not the wake text, decide triage.
|
|
412
|
-
Note the `Assignee:` line: that human answers the tree's asks, and their Inbox opens on
|
|
413
|
-
the issues they hold. Never reassign during triage — who holds an issue is the humans'
|
|
414
|
-
decision, made from the issue header.
|
|
415
|
-
2. Read who handed it over. `dispatch read` does not show who set a label, so list the issue's
|
|
416
|
-
events: `curl -sS -H "Authorization: Bearer $(cat "$DISPATCH_TOKEN_FILE")" "$DISPATCH_URL/api/v1/issues/<issue>/events?limit=200"`.
|
|
417
|
-
The first `issue.created` or `issue.updated` event whose `payload.labels` holds `legion` (in
|
|
418
|
-
any case) set it, and its `actor` is who. A person's label (`actor.kind` is `user`) hands it
|
|
419
|
-
over. So does your own filing for a person: its spec as filed (`dispatch doc-read --issue
|
|
420
|
-
<issue> --version 1`) holds the line `Filed for <person> at their request: <request>`
|
|
421
|
-
([Findings, and the issues you file](#findings-and-the-issues-you-file)), and its `actor` is a
|
|
422
|
-
session that is yours: this one, which `dispatch whoami` names, or, for a request that is an
|
|
423
|
-
ask, the one whose reply under that ask (`dispatch read --ref`) starts `Filed dispatch://<this
|
|
424
|
-
KEY>` after a newest answer of `File it for Legion`, which holds across your restarts. Any other
|
|
425
|
-
filing hands over nothing: leave the issue in `triage` and ask the person on your report issue
|
|
426
|
-
whether Legion should take it, naming the issue and who labelled it, with the options `Admit
|
|
427
|
-
it` and `Leave it`. On `Admit it`, admit it below; either way reply under the ask `Admitted
|
|
428
|
-
dispatch://<KEY>` or `Not admitted: <reason>`, as for a finding ask.
|
|
429
|
-
3. If a person handed it over and it should run now, admit the root issue:
|
|
430
|
-
|
|
431
|
-
```text
|
|
432
|
-
legion({ op: "set_status", issue: "<issue>", status: "todo" })
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
4. If it should deliberately wait, park it (`set_status` to `backlog`, or `icebox` for
|
|
436
|
-
longer-term deferral) instead of leaving it in `triage`. Dispatch status is the durable record;
|
|
437
|
-
parking leaves the `legion` label in place. Park only a root that should not run now: in
|
|
438
|
-
`todo` the daemon's admission queue holds it for a slot. Do not triage a system-created child
|
|
439
|
-
as a root issue.
|
|
440
|
-
5. When you post a triage note (a `dispatch comment` on the issue saying what you decided and
|
|
441
|
-
why), name who will be asked with the assignee sentence: `Assigned to <login>, who will get
|
|
442
|
-
this tree's questions and its design approval`, or, when the `Assignee:` line says
|
|
443
|
-
`unassigned`, `Unassigned — nobody's Inbox shows this tree's questions or its design approval
|
|
444
|
-
until someone takes it from the issue header (Assignee, beside Priority)`. The design approval
|
|
445
|
-
is promised only when the `Design gate policy:` line of your system prompt, which
|
|
446
|
-
whoever launched you writes from the daemon's own configuration, says
|
|
447
|
-
`gates.design: root-issues`. Under `gates.design: off` nobody approves a design, so drop "and
|
|
448
|
-
its design approval" (and "or its design approval") from the sentence. Take the line from the
|
|
449
|
-
read, never from how the issue was created: an issue filed with no assignee still lands on the
|
|
450
|
-
project's default assignee where a person set one. An unassigned root still runs; its
|
|
451
|
-
architect's asks reach no default Inbox list, only the `Unassigned` filter.
|
|
452
|
-
|
|
453
|
-
## Architect escalation
|
|
454
|
-
|
|
455
|
-
Three matters come to you from a tree's architect: a child that looks independent of its tree,
|
|
456
|
-
capacity, and a cross-tree conflict. The owning architect writes an issue-design decision as a
|
|
457
|
-
decision block and opens its own finding asks, not the controller.
|
|
458
|
-
|
|
459
|
-
For a child that looks independent, verify the child and its parent against current daemon state
|
|
460
|
-
and the Dispatch issue. When it still looks independent, ask a person whether to re-file it, as
|
|
461
|
-
[Findings, and the issues you file](#findings-and-the-issues-you-file) says: the ask names the
|
|
462
|
-
child, why it looks independent and the root you would file, with the three options (`File an
|
|
463
|
-
issue` re-files it as a root a person takes, `File it for Legion` as a root Legion runs, `Do not
|
|
464
|
-
file` keeps it in its tree). Only on the person's yes:
|
|
465
|
-
|
|
466
|
-
1. File the root with `dispatch issue --project <KEY> --title '<title>' --spec-file -` (no
|
|
467
|
-
`--parent`), its spec citing the ask and carrying its `Filed for` line ([Findings, and the issues
|
|
468
|
-
you file](#findings-and-the-issues-you-file)); add `--label legion` only when the answer asks
|
|
469
|
-
Legion to run it. `--project` is the issue key's prefix before `-<n>` (e.g. `LEGSMOKE-3` → `LEGSMOKE`):
|
|
470
|
-
the project key exactly as `legion.yaml` writes it, which `read_state` shows as
|
|
471
|
-
`daemon.project`. It is not the lowercase project token in role names such as
|
|
472
|
-
`legion-<project>-controller`.
|
|
473
|
-
2. Park the child (`set_status` to `icebox`) and leave a pointer to the new root issue. The
|
|
474
|
-
controller's capability is `todo`/`backlog`/`icebox` only — only the owning architect or the
|
|
475
|
-
daemon closes an issue as `done`.
|
|
476
|
-
3. A root filed with the label opens in `triage` and is triaged as any labelled root
|
|
477
|
-
([New issue triage](#new-issue-triage)).
|
|
478
|
-
|
|
479
|
-
Never promote a child in place. Resolve capacity and cross-tree conflicts from verified
|
|
480
|
-
state, routing design decisions back to the owning architect when they are not controller
|
|
481
|
-
judgments.
|
|
482
|
-
|
|
483
|
-
## Mentions
|
|
484
|
-
|
|
485
|
-
Read the mention and its artifact; a Slack mention follows [Slack
|
|
486
|
-
mentions](skill://legion-controller/references/slack-mentions.md). Answer it when it asks the
|
|
487
|
-
controller for triage or human-facing information. A human asking how to let a root proceed past
|
|
488
|
-
its design gate approves the root issue's spec document in Dispatch — the `Approve` control in the
|
|
489
|
-
document's header, or the approval question the architect's request opened in the Inbox. The
|
|
490
|
-
controller never opens a gate and there is no operator command for it; a project that does not
|
|
491
|
-
want the gate at all runs `gates.design: off` in its `legion.yaml`. Otherwise resolve the
|
|
492
|
-
authoritative owning architect role and route the verified context with `envoy_publish`. Do not
|
|
493
|
-
route raw event traffic or invent a role token from a partial issue reference.
|
|
118
|
+
The operator's deployment instructions in your system prompt (required checks, deploy commands,
|
|
119
|
+
code owners, roles you may consult) override this skill where the two conflict, and may narrow
|
|
120
|
+
what you label for Legion. They never hand Legion work, because only a person does.
|