@sjawhar/pi-legion 10.19.7 → 10.19.9
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/dist/legion.js +1 -1
- package/dist/skills/legion-architect/SKILL.md +12 -10
- package/dist/skills/legion-controller/SKILL.md +188 -198
- package/dist/skills/legion-controller/references/admission.md +1 -1
- package/dist/skills/legion-controller/references/backlog-stewardship.md +7 -4
- package/dist/skills/legion-controller/references/finding-asks.md +61 -0
- package/dist/skills/legion-controller/references/slack-mentions.md +8 -4
- package/dist/skills/legion-oracle/SKILL.md +5 -4
- package/dist/skills/legion-retro/SKILL.md +11 -8
- package/dist/skills/legion-worker/SKILL.md +11 -11
- package/dist/skills/legion-worker/references/findings.md +90 -0
- package/dist/skills/legion-worker/references/merge-gate.md +8 -7
- package/package.json +1 -1
package/dist/legion.js
CHANGED
|
@@ -16932,7 +16932,7 @@ async function bootLegionClaim(host2) {
|
|
|
16932
16932
|
// package.json
|
|
16933
16933
|
var package_default = {
|
|
16934
16934
|
name: "@sjawhar/pi-legion",
|
|
16935
|
-
version: "10.19.
|
|
16935
|
+
version: "10.19.9",
|
|
16936
16936
|
license: "Apache-2.0",
|
|
16937
16937
|
type: "module",
|
|
16938
16938
|
omp: {
|
|
@@ -35,9 +35,10 @@ separate coordinator to finish necessary work.
|
|
|
35
35
|
- There is no label vocabulary. Dispatch status replaces the board, and the design gate
|
|
36
36
|
is a human approving the root spec document at a version in Dispatch, requested with
|
|
37
37
|
`dispatch request-approval` — not a label and not an ask. Never attempt to apply a label.
|
|
38
|
-
- Deferring necessary work is failure. The sole valid deferral is a new child issue you
|
|
39
|
-
|
|
40
|
-
controller
|
|
38
|
+
- Deferring necessary work is failure. The sole valid deferral is a new child issue you create
|
|
39
|
+
and continue to own, or, on a root's split, a `dispatch ask` for the rest. A child that looks
|
|
40
|
+
independent goes to the controller (*Escalation judgment*); a problem outside the tree is a
|
|
41
|
+
`dispatch ask`, never an issue you file (`skill://legion-worker/references/findings.md`).
|
|
41
42
|
|
|
42
43
|
## Deployment instructions
|
|
43
44
|
|
|
@@ -369,8 +370,7 @@ The daemon keeps this order from its fixed table; you start none of its steps:
|
|
|
369
370
|
`retro`, so the implementer's retro removes it.
|
|
370
371
|
5. the merge lands under the repository's rules and closes the issue (section 7). The implementer's
|
|
371
372
|
production check follows the close, in its own session and on the pull request; you are asked
|
|
372
|
-
nothing for it, and a defect it finds is
|
|
373
|
-
child of the closed one.
|
|
373
|
+
nothing for it, and a defect it finds is a `dispatch ask`, never an issue.
|
|
374
374
|
|
|
375
375
|
What returns the tree to review: a changed diff — a commit above the approved head that
|
|
376
376
|
touches anything outside `docs/solutions/` and the issue's `.legion/<issue>/`, or a
|
|
@@ -420,7 +420,7 @@ is handed over (section 3), and the tree's close waits for a member's open check
|
|
|
420
420
|
|
|
421
421
|
Nothing of the check is yours: you post no sign-off after the merge and wait for no record. Its
|
|
422
422
|
record lives on the pull request alone, since Dispatch refuses a write on a closed issue; a defect
|
|
423
|
-
it finds is
|
|
423
|
+
it finds is a `dispatch ask`, never an issue; and
|
|
424
424
|
`read_record` shows a closed issue's open check (`check`: the pull request, the merge commit, the
|
|
425
425
|
looks, what woke the last one and when the next is due). A parent waits in `accepting` until
|
|
426
426
|
every child is `done` (section 3): accepted earlier it would merge before its children, the state
|
|
@@ -445,7 +445,7 @@ active phase worker.
|
|
|
445
445
|
| `design-approved` | Its `version` is the approved one. A human approved the root spec document at its current version; the gate is open. Proceed to section 2 — except on a root whose catch-up said `handed_over`: its workers already run their phases, so start nothing; the approval releases any READY the gate refused. One whose `version` is the version a human's own answer to a decision block wrote, after an earlier approval (Dispatch carried that approval to it, section 1), opens nothing new: carry on, and release no wave because of it. |
|
|
446
446
|
| `design-changes-requested` | Its `version` and `reason`. A human asked for changes to the root spec at `version`, for `reason`. Revise the spec as the reason asks and request approval again as section 1 says; stay parked; the gate is closed. |
|
|
447
447
|
| `phase-finished` | The daemon has already moved the issue to its next phase by its fixed table and started that phase's role; you start nothing. Read the committed handoff for the finishing phase; if it shows unresolved gaps, tell the role now working the issue (`envoy_publish` to its role topic). A `planner` notice that names a departure from the spec's design defers to section 1's full condition: when the approved Summary, Acceptance, scope and settled decisions still hold, the plan is the record; otherwise change the root spec and request approval again as section 1 says. A `reviewer` notice whose GitHub review is `CHANGES_REQUESTED` needs nothing from you: the daemon has returned the issue to `implementing` (Dispatch `in_progress`) and started the **implementer**, whose correction goes through the tester and the reviewer again, never straight to retro. A `reviewer` notice with an `APPROVED` review means the issue is in `accepting` and the acceptance is yours (section 6): read the evidence, then `sign_off` or send it back with `request_backward_move`. A `tester` notice with `verdict: "fail"` — its handoff carries `implementerProof.verdict: "rejected"`, or a failure naming the production-like proof — has gone back to the **implementer** by the daemon's table; never supply the proof from another role. A worker that reports no surface reaches the changed path sends that report instead of completing its phase, so the daemon starts nothing more on that issue and the worker stays idle in its session, not suspended: file a child issue in this tree to build the surface (infrastructure, tooling, or a skill), and when that child's `child-closed` arrives, tell the waiting worker to continue with `envoy_publish` to its role topic. That report is never a reason to advance the phase. |
|
|
448
|
-
| `pr-blocked` | Its `reason` names the pull request and the `max_fix_attempts` it reached, or reads `Issue reached review_round_cap=<N>.` (the end of this row). The count is of heads pushed onto a red verdict that changed something outside `.legion/` — handoff-only pushes (`.legion/` paths only) never count, a push by the review App (a planner's, tester's, reviewer's or architect's) never counts, and the head after a red the tester's red tests earned (a review-App push that changed a path outside `.legion/`, however many handoff-only pushes follow it) does not count either — so after the tester's handoff-only push onto the implementer's red, the implementer's next push does count; a push the daemon cannot classify (a listener without `changed_paths`, a list the listener stopped at 100 paths or 32,768 runes of text, a push listing no commits) does. Published once per exhausted count, not on every later red verdict for that count. Read the failed CI evidence and recovery attempts. The notice moves nothing, and no later CI red within the attempt that ran the count out (the counted head, and every head pushed on top of it until a push lands on a head CI judged green, the tester's red tests and handoff onto the held red included) sends the work back from `testing`, `reviewing`, `accepting`, `retro` or `merging` either: the issue stays in its phase until you move it, or a worker's own decision does (a tester's `fail`, a reviewer's request for changes). A head pushed onto green CI leaves that attempt, so its red still comes as a `checks-red`. In `implementing`, give the implementer the failing checks (`envoy_publish` to its role topic). In any later phase a worker holds, tell that worker (`envoy_publish` to its role topic) to move the issue back to `implementing` with `request_backward_move`, naming the failing checks, and the daemon starts the implementer; or file a corrective child. In `accepting`, where no worker holds a phase, the move is yours: `request_backward_move` with `issue`, naming the failing checks. In `awaiting_merge`, where no worker holds a phase and a backward move is refused, open a `dispatch ask` naming the failing checks for the human who merges, as section 6 does for a conflict there. Do not treat the blocked PR as final. A `review_round_cap` reason is the implementer's round count instead: every move back to an earlier phase counts one, and the notice comes again with each round past the cap. It moves nothing either, since the round that reached the cap has already started its worker. Tell the rounds apart by what announced each: a tester's fail by its `phase-finished` with `verdict: "fail"`; a request for changes by the reviewer's `phase-finished` over a `CHANGES_REQUESTED` review; the daemon's own move by a `checks-red` notice, whose reason names a conflict (`the head conflicts with <base>`), the required checks that failed (`CI is red at <head>`), or the merge queue's failed merge and its failing run (`the merge queue took pull request #<n> out at READY's head …`); a worker's `request_backward_move` by a `phase-finished` from the role that asked, whose reason reaches only the task of the worker the move started, so ask that worker; your own send-back from `accepting` by the reason you gave. Judge the cap by the rounds that are not conflicts. Conflict returns need nothing from you beyond the next round. Tester fails, requests for changes and CI-red returns (a `checks-red` notice is raised for CI only when a required check fails on code the branch pushed, or when the merge queue fails the merge) are the rounds the cap is for. Each `review_round_cap` notice is a decision you make in the turn it wakes, never one left for the next round to make: read what every counted round found, choose one of the three below, act on it in the same turn, and post it as a `dispatch comment` on the issue naming the rounds you read and the choice. **Narrow**: the change itself is sound but each round finds more around it; tell the implementer and the reviewer (`envoy_publish` to each one's role topic) which acceptance lines the next round answers; a correctness finding is still fixed in this pull request, and only naming, duplication or wording cleanup moves to the `Fast-follow:` line. **Split**: part of the work cannot settle in this pull request
|
|
448
|
+
| `pr-blocked` | Its `reason` names the pull request and the `max_fix_attempts` it reached, or reads `Issue reached review_round_cap=<N>.` (the end of this row). The count is of heads pushed onto a red verdict that changed something outside `.legion/` — handoff-only pushes (`.legion/` paths only) never count, a push by the review App (a planner's, tester's, reviewer's or architect's) never counts, and the head after a red the tester's red tests earned (a review-App push that changed a path outside `.legion/`, however many handoff-only pushes follow it) does not count either — so after the tester's handoff-only push onto the implementer's red, the implementer's next push does count; a push the daemon cannot classify (a listener without `changed_paths`, a list the listener stopped at 100 paths or 32,768 runes of text, a push listing no commits) does. Published once per exhausted count, not on every later red verdict for that count. Read the failed CI evidence and recovery attempts. The notice moves nothing, and no later CI red within the attempt that ran the count out (the counted head, and every head pushed on top of it until a push lands on a head CI judged green, the tester's red tests and handoff onto the held red included) sends the work back from `testing`, `reviewing`, `accepting`, `retro` or `merging` either: the issue stays in its phase until you move it, or a worker's own decision does (a tester's `fail`, a reviewer's request for changes). A head pushed onto green CI leaves that attempt, so its red still comes as a `checks-red`. In `implementing`, give the implementer the failing checks (`envoy_publish` to its role topic). In any later phase a worker holds, tell that worker (`envoy_publish` to its role topic) to move the issue back to `implementing` with `request_backward_move`, naming the failing checks, and the daemon starts the implementer; or file a corrective child. In `accepting`, where no worker holds a phase, the move is yours: `request_backward_move` with `issue`, naming the failing checks. In `awaiting_merge`, where no worker holds a phase and a backward move is refused, open a `dispatch ask` naming the failing checks for the human who merges, as section 6 does for a conflict there. Do not treat the blocked PR as final. A `review_round_cap` reason is the implementer's round count instead: every move back to an earlier phase counts one, and the notice comes again with each round past the cap. It moves nothing either, since the round that reached the cap has already started its worker. Tell the rounds apart by what announced each: a tester's fail by its `phase-finished` with `verdict: "fail"`; a request for changes by the reviewer's `phase-finished` over a `CHANGES_REQUESTED` review; the daemon's own move by a `checks-red` notice, whose reason names a conflict (`the head conflicts with <base>`), the required checks that failed (`CI is red at <head>`), or the merge queue's failed merge and its failing run (`the merge queue took pull request #<n> out at READY's head …`); a worker's `request_backward_move` by a `phase-finished` from the role that asked, whose reason reaches only the task of the worker the move started, so ask that worker; your own send-back from `accepting` by the reason you gave. Judge the cap by the rounds that are not conflicts. Conflict returns need nothing from you beyond the next round. Tester fails, requests for changes and CI-red returns (a `checks-red` notice is raised for CI only when a required check fails on code the branch pushed, or when the merge queue fails the merge) are the rounds the cap is for. Each `review_round_cap` notice is a decision you make in the turn it wakes, never one left for the next round to make: read what every counted round found, choose one of the three below, act on it in the same turn, and post it as a `dispatch comment` on the issue naming the rounds you read and the choice. **Narrow**: the change itself is sound but each round finds more around it; tell the implementer and the reviewer (`envoy_publish` to each one's role topic) which acceptance lines the next round answers; a correctness finding is still fixed in this pull request, and only naming, duplication or wording cleanup moves to the `Fast-follow:` line. **Split**: part of the work cannot settle in this pull request. On a child, file that part as a child of this issue's parent, never of this one (a parent waits in `accepting` for every child, and the split part usually builds on this pull request's unmerged code): `dispatch issue --project <project> --parent <parent> --title '<outcome>' --blocked-by <this issue> --spec-file -`, released with `release_children` once this issue closes. On the root, narrow it to what settles here and open a `dispatch ask` for the rest (`skill://legion-worker/references/findings.md`): a person decides whether that becomes an issue. Tell the implementer and the reviewer that this pull request no longer carries it. **Accept the rounds**: each round found less than the one before it, and the last found no defect in the change itself (a stale line, a comment that states an old default); let the loop run and say why in the comment. A later notice is a new decision: accept again only when its round found less than the round you last accepted, and otherwise narrow or split. A finding that changes what the spec accepts is a decision block in the root spec (section 1), and `dispatch ask` is only for a decision that needs a human's authority; neither replaces the choice. Judge a worker's backward move by the reason that worker gives. A move you ordered for a conflict found before `awaiting_merge` (section 6) is a conflict return. The merger's move back to `reviewing` for a READY refusal (`READY_UNREVIEWED_CHANGE`, or `READY_NOT_APPROVED` when its task names no approved head), and the reviewer's no-fix request for changes that follows it so the tester runs the head, are not the work going in circles either. A move that sends back code found wrong counts with the tester fails. |
|
|
449
449
|
| `pr-merged` | Its `reason` names the pull request. The PR merged, under the repository's rules, before the issue reached `awaiting_merge`. The workflow runs on and asks no one to merge it: the merge closes the issue the moment it reaches `awaiting_merge` (section 7), and you start nothing; the production check after the close is the implementer's, on the pull request, and asks nothing of you. Until then the phases run as they would, your acceptance included. |
|
|
450
450
|
| `pr-closed-unmerged` | Decide from current scope whether the work is reopened, started over (`park_child` then `rerun_child`, for a child), or ended with a reason. Delegate the repository action to the responsible phase worker and keep ownership. |
|
|
451
451
|
| `merge-refused` | Its `reason` names the pull request, READY's head, what the daemon asked GitHub to do (merge, enqueue, or enable auto-merge for it), and GitHub's refusal, which a retry would meet again (squash merges disabled, a permission the implement App lacks). The daemon has stopped submitting that merge; the issue stays in `awaiting_merge`. The cause is a repository setting or an App permission, a person's to change: ask one with `dispatch ask`, naming the refusal and the pull request at READY's head, which that person merges under the repository's rules once it is fixed. The merge closes the issue. Never bypass a rule. |
|
|
@@ -458,9 +458,11 @@ active phase worker.
|
|
|
458
458
|
|
|
459
459
|
## Escalation judgment
|
|
460
460
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
461
|
+
Three matters go to the controller, with `envoy_publish` to the controller topic your `Legion
|
|
462
|
+
addressing` line names: a child that looks independent of the tree (the controller asks a person
|
|
463
|
+
whether to re-file it as a root of its own, and does so only on their yes), capacity, and a
|
|
464
|
+
cross-tree conflict. A problem outside the tree is a `dispatch ask` you open, never an issue you
|
|
465
|
+
file (`skill://legion-worker/references/findings.md`). Handle everything else in the
|
|
464
466
|
tree. A product, scope, or design decision that needs the human, yours or one a worker escalated,
|
|
465
467
|
is a decision block you write (section 1 says what one does to the root spec's gate). A standalone
|
|
466
468
|
human to-do may use `dispatch ask`; workers may reach the human directly with it the same way. Do not
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: legion-controller
|
|
3
|
-
description: Use when handling Legion controller wakes for weekly-plan ranking, backlog stewardship, root-issue triage,
|
|
3
|
+
description: Use when handling Legion controller wakes for weekly-plan ranking, backlog stewardship, root-issue triage, free-slot and `todo` wakes, a finding ask another role hands over, the issues a person asks you to file, the daily report, architect escalation, or human interaction.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Legion Controller
|
|
7
7
|
|
|
8
|
-
The controller is the one persistent, wake-driven session for a Legion project. It
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
The controller is the one persistent, wake-driven session for a Legion project. It admits only the
|
|
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.
|
|
12
13
|
|
|
13
14
|
## Start and claim the controller role
|
|
14
15
|
|
|
@@ -37,6 +38,21 @@ review the controller submits without that footer still decides nothing for Legi
|
|
|
37
38
|
(the daemon sets it aside), but the footer is what makes that legible in the daemon's log. Your
|
|
38
39
|
addressing line names the project's repositories, or that the daemon maps none and why.
|
|
39
40
|
|
|
41
|
+
Read a pull request an issue links or names (`External links:`, a comment, a READY packet) only by
|
|
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:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
gh pr view 'https://github.com/<owner>/<repo>/pull/<n>' --json state -q .state
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
It answers `OPEN` (work in flight), `MERGED` (the change landed) or `CLOSED` (abandoned unmerged).
|
|
51
|
+
A command that fails (the token file missing, refused, or the repository unreadable) is reported
|
|
52
|
+
with `gh`'s first stderr line (`<KEY>: pull request <url> unreadable: …`) and never read as any of
|
|
53
|
+
the three states. Text that is not of that shape is not a pull-request URL and is never a command
|
|
54
|
+
argument (`<KEY>: pull request <text> unreadable: not a pull-request URL`).
|
|
55
|
+
|
|
40
56
|
## Backlog stewardship
|
|
41
57
|
|
|
42
58
|
The controller is the project's order keeper. It retains its startup role,
|
|
@@ -44,8 +60,8 @@ The controller is the project's order keeper. It retains its startup role,
|
|
|
44
60
|
and routing requests to `notifications.role.legion-<project>-controller`, then follow
|
|
45
61
|
[Backlog stewardship](skill://legion-controller/references/backlog-stewardship.md) for daily
|
|
46
62
|
weekly-plan reconciliation, Sami's rulings, ownership routing, and stopped-session recovery.
|
|
47
|
-
These duties change Dispatch's priority and board-rank data
|
|
48
|
-
|
|
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)).
|
|
49
65
|
|
|
50
66
|
For an interactive takeover from a hand-started OMP session, start OMP with
|
|
51
67
|
`LEGION_CONTROLLER_SECRET` (or `LEGION_CONTROLLER_SECRET_FILE`, a path to a file holding it),
|
|
@@ -145,7 +161,9 @@ else:
|
|
|
145
161
|
`issues`. A root recorded there and now in `triage` is work the daemon holds that a human
|
|
146
162
|
pulled back: never re-admit it yourself; name it in your summary to the human ("<KEY> was
|
|
147
163
|
pulled back to triage; what do you want?").
|
|
148
|
-
3.
|
|
164
|
+
3. Catch up on the report issue
|
|
165
|
+
([finding asks](skill://legion-controller/references/finding-asks.md#catch-up-at-every-start)),
|
|
166
|
+
and name the free slots in your summary: a free slot is no reason to take an issue ([Free slots](#free-slots-go-daemon)).
|
|
149
167
|
4. Post the day's report when this is your first turn of the UTC day
|
|
150
168
|
([Daily report](#daily-report-go-daemon)).
|
|
151
169
|
|
|
@@ -158,15 +176,31 @@ since its project may be shared with humans and other agents. It never admits a
|
|
|
158
176
|
without the label, and wakes you for a root in `triage` only while the root carries it and is
|
|
159
177
|
unrecorded: on its creation with the label, and on each change to it after that while it stays
|
|
160
178
|
in triage, the change that adds the label included (the dashboard creates an issue without
|
|
161
|
-
labels, so a human adds it from the issue header).
|
|
162
|
-
the label from the issue header,
|
|
163
|
-
|
|
164
|
-
|
|
179
|
+
labels, so a human adds it from the issue header). Work reaches Legion only by a person's decision:
|
|
180
|
+
a person sets the label from the issue header, or asks you to (a direct message, a Slack request
|
|
181
|
+
that asks Legion to do the work, their answer to a finding ask, or a ruling of theirs the weekly
|
|
182
|
+
plan records with its link, [Backlog stewardship](#backlog-stewardship)), and you set it for them.
|
|
183
|
+
You label an issue only then, so the label means a person handed Legion the issue, or did once. A
|
|
184
|
+
person's label is their decision: never take it off. A child needs no label: it runs under its
|
|
165
185
|
tree's architect once its root is admitted. `set_status` to `todo` admits a root, or a child
|
|
166
186
|
outside a live tree (admitted as a root of its own), only while it carries the label, so an issue
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
187
|
+
a person hands over carries it first. Taking the label off a waiting root drops it from the
|
|
188
|
+
waiting line; taking it off an admitted tree does not stop it
|
|
189
|
+
([reading admission](skill://legion-controller/references/admission.md)).
|
|
190
|
+
|
|
191
|
+
To label an existing issue for a person, keep the labels it has, which `Labels:` lists (`none` is
|
|
192
|
+
no labels): the `--label` flags replace the whole set, so a label you leave out is removed. Pass
|
|
193
|
+
each label single-quoted, exactly as `Labels:` shows it. A label holding a `'` or a line break
|
|
194
|
+
cannot be quoted and never goes in a command: tell the person (`<KEY>: label <label> cannot be
|
|
195
|
+
quoted`), so they hand it over from the issue header.
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
dispatch issue-update --issue <KEY> --label '<each current label>' --label legion
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
In `todo` the label admits it. In `triage`, triage it in the same turn, from step 3 of [New issue
|
|
202
|
+
triage](#new-issue-triage): the person's request is its handover. Elsewhere, `set_status` it to
|
|
203
|
+
`todo` only when the person asked Legion to start it now.
|
|
170
204
|
|
|
171
205
|
## Trees waiting on a root claim (Go daemon)
|
|
172
206
|
|
|
@@ -179,139 +213,77 @@ tell that tree's architect to claim again with `envoy_publish` to `notifications
|
|
|
179
213
|
claim token, `issues.<KEY>.architect.locator.claim` in `read_state`. A claim that is its architect's
|
|
180
214
|
own, or one that still holds, needs nothing.
|
|
181
215
|
|
|
182
|
-
##
|
|
216
|
+
## Free slots (Go daemon)
|
|
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:
|
|
183
225
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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)).
|
|
187
232
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
unsent:
|
|
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:
|
|
191
235
|
|
|
192
236
|
- `slot-free on <KEY>`: the daemon released `<KEY>`'s slot, because its tree finished or left the
|
|
193
|
-
workflow, and
|
|
237
|
+
workflow, and no waiting root took it.
|
|
194
238
|
- `todo on <KEY>`: `<KEY>`, an issue nobody handed to Legion, changed while in `todo` and a slot
|
|
195
|
-
was free
|
|
196
|
-
|
|
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.
|
|
197
241
|
- `tick on <PROJECT>`: the daemon's periodic wake, a minute after it starts and then every
|
|
198
|
-
`controller_wake_interval_seconds` (an hour by default), whatever the slots.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
also `daemon.project` in `read_state`: the project key exactly as `legion.yaml` writes it.
|
|
244
|
-
A row that shows `claimed by …` and does not end its claim with `· not running` (the route, when
|
|
245
|
-
the row shows one, comes after the claim) is claimed, as the table below says: skip it without
|
|
246
|
-
reading it.
|
|
247
|
-
|
|
248
|
-
**Walk.** Take the remaining rows in that order until the free slots are filled. Read each one with
|
|
249
|
-
`dispatch read --issue <KEY>` and skip it when any of these holds. `dispatch read` shows only
|
|
250
|
-
the issue's last 10 events, so the rows that read `Events:` are best-effort: the pull-request row
|
|
251
|
-
leans on `External links:`, and the label row is the one that never depends on history.
|
|
252
|
-
|
|
253
|
-
| Skip when | How you check it |
|
|
254
|
-
|---|---|
|
|
255
|
-
| It carries the `legion` label | `Labels:` lists `legion`, in any case. Legion has the issue or had it, as **Candidates** above says. |
|
|
256
|
-
| It has any child | `dispatch read --ref dispatch://<KEY>/children` lists any child, open or `done`. It is an umbrella, and a finished umbrella is still no leaf. That also skips an issue whose only child is done, which is accepted. Its open children are candidates themselves, each in its own place in the order. |
|
|
257
|
-
| An ancestor is Legion's | Follow `Links:` up through each `child_of` parent, reading each one, and skip when any ancestor carries the `legion` label or is recorded under `issues` in `read_state`, whatever its status: a Legion tree, running or parked, owns its children. An ancestor's claim or route does not skip the issue: a coordinator holding an umbrella files `todo` leaves for others to pick up, and the claim on the issue itself is what keeps two sessions off the same work. A `child_of` under `Referenced by:` is a child of this issue, not its parent. |
|
|
258
|
-
| Someone is designing it | `Open asks:` lists any ask, a `Spec approval: awaiting …` line shows the spec waits on a human, or `Events:` show an `artifact.version` or an `ask.opened` from the last seven days: a session or a person is shaping it even when nobody claims or routes it. |
|
|
259
|
-
| Legion ran it without the label now on it | `read_state` records it under `issues`, whatever its status, or `Events:` show a status write by `session legion-daemon:<PROJECT>`, the daemon's actor on every status it writes (your `set_status` included) and on its own `in_progress` at admission. That covers a root a person took the label off, and one that ran before the daemon required the label and never had it. Name each one you skip for this in your summary. The walk never sends a root Legion already ran back into Legion: a person does that with the label and `todo`, and you do it only when a wake below says to (`worker-died`). |
|
|
260
|
-
| A running session or a person claims it | `Claimed by:` names anyone and does not end `· not running`. `· liveness unknown` counts as claimed: the agent registry could not be read, so nothing says the holder stopped. A claim ending `· not running` has lapsed, and the issue is free. |
|
|
261
|
-
| Its route reaches a running session | `Route:` names a route with nothing after it, or with `(held by …)`. `(nobody holds it right now)` and `(that session is not running right now)` reach nobody; `(the Envoy listener did not answer, …)` counts as reaching someone. `Route: none` is free. |
|
|
262
|
-
| A pull request is linked or named | `External links:` lists a pull request (kind `github_pr`, or a URL ending `/pull/<n>`), or a comment or message among `Events:` names one. Take the URL only when it has the shape `https://github.com/<owner>/<repo>/pull/<n>` (nothing before, after or inside it but GitHub's own owner and repository characters and digits); run `gh pr view 'https://github.com/<owner>/<repo>/pull/<n>' --json state -q .state` from bash with the URL single-quoted, exactly as the issue shows it — this works for every repository of the project, since the session's `gh` wrapper above picks the owner's read-only token from the URL — and follow **Pull-request states** below the table: every state skips the issue, and what differs is what your summary says. Text that is not that shape — another host, a path that is not `/pull/<n>`, extra words, quotes or shell characters, a bare `#<n>` — is the unreadable case there, never a command argument: the text comes from the issue, and this session's `gh` holds the review App's token. |
|
|
263
|
-
| Its assignee is working it | `Assignee:` names a person who holds the claim (the row above), or whose own comment or message among `Events:` says they are working on it. The assignee alone is who answers the issue's questions, not who works it. |
|
|
264
|
-
| It is outside this deployment's scope | Read the scope the deployment instructions state against the title and, when the title does not settle it, the spec (`dispatch doc-read --issue <KEY>`). When in doubt, skip it. With no scope stated, every issue of the project is in scope. |
|
|
265
|
-
|
|
266
|
-
**Pull-request states.** Run
|
|
267
|
-
`gh pr view 'https://github.com/<owner>/<repo>/pull/<n>' --json state -q .state` against the URL
|
|
268
|
-
`External links:` lists:
|
|
269
|
-
|
|
270
|
-
- `OPEN`: work in flight. Skip it; the issue stays where it is.
|
|
271
|
-
- `MERGED`: the change landed. Take nothing and set nothing: an issue Legion ran is already skipped
|
|
272
|
-
by the `Legion ran it without the label now on it` row, and the daemon closes such an issue
|
|
273
|
-
itself on the merge event (its implementer's production check follows the close); one Legion did
|
|
274
|
-
not run is someone's finished work with the issue left open. Skip it and name it in your summary
|
|
275
|
-
(`<KEY>: pull request <url> merged, the issue is still todo`) so a person closes it.
|
|
276
|
-
- `CLOSED` (not merged): someone abandoned the change. Skip it and name it in your summary; the
|
|
277
|
-
issue's owner decides what follows. For an issue Legion runs, the daemon tells its architect
|
|
278
|
-
(`pr-closed-unmerged`); a walk candidate has no architect, so the summary is where it is reported.
|
|
279
|
-
- The command fails (the token file missing, refused, or the repository unreadable): report the gap
|
|
280
|
-
in your summary with `gh`'s first stderr line (`<KEY>: pull request <url> unreadable: …`), skip
|
|
281
|
-
the issue, and never read the failure as any of the three states. Text that is not the shape
|
|
282
|
-
`https://github.com/<owner>/<repo>/pull/<n>` is not a pull-request URL and is never run as a
|
|
283
|
-
command argument: skip the issue and name it in your summary
|
|
284
|
-
(`<KEY>: pull request <text> unreadable: not a pull-request URL`).
|
|
285
|
-
|
|
286
|
-
A person who wants Legion on it anyway hands it over themselves: the label, then `todo`.
|
|
287
|
-
|
|
288
|
-
**Take.** For each candidate that passes, in order:
|
|
289
|
-
|
|
290
|
-
1. Add the label and keep the labels it has, which `Labels:` lists (`none` is no labels). The
|
|
291
|
-
`--label` flags replace the whole set, so a label you leave out is removed. Pass each label
|
|
292
|
-
single-quoted, exactly as `Labels:` shows it; a label holding a `'` or a line break cannot be
|
|
293
|
-
single-quoted and is never put in a command: skip the issue and name it in your summary
|
|
294
|
-
(`<KEY>: label <label> cannot be quoted`) so a person hands it over themselves.
|
|
295
|
-
|
|
296
|
-
```text
|
|
297
|
-
dispatch issue-update --issue <KEY> --label '<each current label>' --label legion
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
2. It is already in `todo`, so the label admits it: the daemon records it and gives it the free
|
|
301
|
-
slot, or queues it in `admission.waiting`. It needs no status write.
|
|
302
|
-
3. Post one short comment that says Legion took it, who is asked at its design gate, and how to
|
|
303
|
-
undo the take, naming the assignee with the sentence [New issue triage](#new-issue-triage)
|
|
304
|
-
step 4 gives (which follows the design gate policy):
|
|
305
|
-
|
|
306
|
-
```text
|
|
307
|
-
dispatch comment --issue <KEY> --body-file - <<'EOF'
|
|
308
|
-
Legion took this issue: it was the highest-priority open issue nobody else was working on. Assigned to <login>, who will get this tree's questions and its design approval. To stop Legion, move the issue to backlog. To keep Legion off it for good, also take the legion label off; taking the label off alone does not stop a tree that has started.
|
|
309
|
-
EOF
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
The daemon admits each root when Dispatch's event reaches it; the next `read_state`
|
|
313
|
-
shows it in `admission.active`. When the candidates run out with slots still free, leave those
|
|
314
|
-
slots free and say so in your summary.
|
|
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)).
|
|
257
|
+
|
|
258
|
+
## Findings, and the issues you file
|
|
259
|
+
|
|
260
|
+
You file an issue only when a person asks you to: in a direct message, in a Slack request
|
|
261
|
+
([Slack mentions](skill://legion-controller/references/slack-mentions.md)), or in their answer to a
|
|
262
|
+
finding ask. The one issue you file unasked is the report issue. Anything else is a finding: a
|
|
263
|
+
problem you find, and one a session or role sends you as text. For each, open a `dispatch ask` on
|
|
264
|
+
your report issue ([Daily report](#daily-report-go-daemon) finds it, creates it when there is none,
|
|
265
|
+
and reopens it when it is closed), as `skill://legion-worker/references/findings.md` says. Then
|
|
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:
|
|
274
|
+
|
|
275
|
+
- an answer to an ask: `dispatch://<REPORT KEY>/ask/<id>`, for the person who answered it;
|
|
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`.
|
|
281
|
+
|
|
282
|
+
Each `curl` in this skill and its references reads `DISPATCH_URL` and `DISPATCH_TOKEN_FILE`, which
|
|
283
|
+
`legion controller start` sets only when `controller.yaml` names `dispatch_url`. A read that fails
|
|
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 it unassigned (`400 ASSIGNEE_NOT_ALLOWED` names an email
|
|
286
|
+
nobody has signed in to Dispatch with). Name either, with its first line, in your summary.
|
|
315
287
|
|
|
316
288
|
## Daily report (Go daemon)
|
|
317
289
|
|
|
@@ -321,22 +293,35 @@ change and why, and what is running.
|
|
|
321
293
|
**Where.** On the issue the deployment instructions name for Legion's reports. With none named,
|
|
322
294
|
on the project's `Legion daily report` issue:
|
|
323
295
|
`dispatch search --query '"Legion daily report"' --project <PROJECT>` finds it. When it
|
|
324
|
-
does not exist, create it once and park it in `icebox`, so nobody
|
|
325
|
-
carries the `legion` label
|
|
296
|
+
does not exist, create it once (the one issue you file unasked) and park it in `icebox`, so nobody
|
|
297
|
+
takes it as work; it never carries the `legion` label. It holds every finding ask, so assign it to
|
|
298
|
+
the person who answers for the project: the one the deployment instructions name, else the owner
|
|
299
|
+
`dispatch whoami` names:
|
|
326
300
|
|
|
327
301
|
```text
|
|
328
|
-
dispatch issue --project <PROJECT> --title 'Legion daily report' --spec-file - <<'EOF'
|
|
302
|
+
dispatch issue --project <PROJECT> --title 'Legion daily report' --assignee '<email>' --spec-file - <<'EOF'
|
|
329
303
|
## Summary
|
|
330
304
|
|
|
331
|
-
Legion's controller posts one message here each day: what Legion finished, what it closed without a change and why, and what is running. This issue is not work, so it carries no `legion` label and stays in icebox.
|
|
305
|
+
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.
|
|
332
306
|
EOF
|
|
333
307
|
legion({ op: "set_status", issue: "<report KEY>", status: "icebox" })
|
|
334
308
|
```
|
|
335
309
|
|
|
310
|
+
With no person known, leave out `--assignee`. While the issue's `Assignee:` says `unassigned`,
|
|
311
|
+
each report opens with one sentence asking a person to take the issue from its header, since its
|
|
312
|
+
asks reach only the Inbox's `Unassigned` filter.
|
|
313
|
+
|
|
314
|
+
Each time you read it, keep it answerable. `Status: done` means someone closed it: Dispatch
|
|
315
|
+
refuses an ask or a message there (`409 ISSUE_CLOSED`) and hides its open asks, so reopen it with
|
|
316
|
+
`set_status` to `icebox` and say so in the day's report. `Assignee: unassigned`, with a person
|
|
317
|
+
known, means assign it, naming your session (`dispatch whoami` names it):
|
|
318
|
+
`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>"`.
|
|
319
|
+
|
|
336
320
|
**When.** On your first turn of each UTC day, whatever it is: your start, or a wake of any kind.
|
|
337
321
|
While you are registered, the daemon's `tick` gives you a turn at least every
|
|
338
322
|
`controller_wake_interval_seconds`. After the turn's own work, read the report issue with
|
|
339
|
-
`dispatch read`, and post when its `Events:` show no
|
|
323
|
+
`dispatch read`, keep it answerable as above, and post when its `Events:` show no
|
|
324
|
+
`message.created` from today.
|
|
340
325
|
|
|
341
326
|
**What.** One `dispatch message --issue <report KEY> --body-file -` of at most 2,000 characters,
|
|
342
327
|
since it is a report rather than a log of the day, written as `skill://dispatch`'s "Writing for
|
|
@@ -354,9 +339,8 @@ the human" says: every issue by its key and title, every pull request by its URL
|
|
|
354
339
|
on that holder: its architect started nothing and asked them to release it or take the issue
|
|
355
340
|
back. Nothing in `read_state` records that, so read the tree's issue (its `Claimed by:` line
|
|
356
341
|
and the ask or message the architect opened) before you name it.
|
|
357
|
-
- **
|
|
358
|
-
|
|
359
|
-
reader can see why a free slot stays empty.
|
|
342
|
+
- **Free slots.** The free slots, as [Free slots](#free-slots-go-daemon) counts them: each stays
|
|
343
|
+
free until a person hands Legion an issue.
|
|
360
344
|
- **Open capabilities.** Each row of `capabilities` in `read_state` whose `status` is
|
|
361
345
|
`open` (the `tick` payload's `openCapabilities`): its name, its `detail`, and, on a deployment
|
|
362
346
|
row, its `configLine`, the `legion.yaml` line that records a decision. A live row a running
|
|
@@ -372,9 +356,10 @@ and the highest-priority issues.
|
|
|
372
356
|
|
|
373
357
|
Deployment instructions, when present, are the operator's standing rules for this project's
|
|
374
358
|
repositories — required checks, deploy/smoke commands, code-owner expectations, and standing roles
|
|
375
|
-
you may consult. They override this skill's defaults where they conflict
|
|
376
|
-
|
|
377
|
-
|
|
359
|
+
you may consult. They override this skill's defaults where they conflict and may narrow what you
|
|
360
|
+
label for Legion (a request outside the scope they state gets no label), but never hand Legion work
|
|
361
|
+
themselves: an issue reaches Legion only when a person hands it over
|
|
362
|
+
([Issues handed to Legion](#issues-handed-to-legion-go-daemon)).
|
|
378
363
|
|
|
379
364
|
## Turn discipline
|
|
380
365
|
|
|
@@ -402,15 +387,17 @@ highest priority first, then board rank ([Keeping the slots full](#keeping-the-s
|
|
|
402
387
|
|
|
403
388
|
| Wake | Content | Controller action |
|
|
404
389
|
|---|---|---|
|
|
405
|
-
| 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: `set_status` to `todo`
|
|
406
|
-
| `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
|
|
407
|
-
| `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 |
|
|
390
|
+
| 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)) |
|
|
391
|
+
| `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)) |
|
|
392
|
+
| `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)) |
|
|
408
393
|
| `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") |
|
|
409
|
-
| `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
|
|
410
|
-
| Architect escalation (
|
|
394
|
+
| `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) |
|
|
395
|
+
| 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 |
|
|
396
|
+
| 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)) |
|
|
397
|
+
| 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)) |
|
|
411
398
|
| Mention | GitHub PR @mention text | Answer, or route to the owning issue's architect role |
|
|
412
399
|
| 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)) |
|
|
413
|
-
| `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`
|
|
400
|
+
| `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 |
|
|
414
401
|
| `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 |
|
|
415
402
|
| Direct user message | — | Always first |
|
|
416
403
|
|
|
@@ -422,25 +409,32 @@ highest priority first, then board rank ([Keeping the slots full](#keeping-the-s
|
|
|
422
409
|
Note the `Assignee:` line: that human answers the tree's asks, and their Inbox opens on
|
|
423
410
|
the issues they hold. Never reassign during triage — who holds an issue is the humans'
|
|
424
411
|
decision, made from the issue header.
|
|
425
|
-
2.
|
|
412
|
+
2. Read who handed it over. `dispatch read` does not show who set a label, so list the issue's
|
|
413
|
+
events: `curl -sS -H "Authorization: Bearer $(cat "$DISPATCH_TOKEN_FILE")" "$DISPATCH_URL/api/v1/issues/<issue>/events?limit=200"`.
|
|
414
|
+
The first `issue.created` or `issue.updated` event whose `payload.labels` holds `legion` (in
|
|
415
|
+
any case) set it, and its `actor` is who. A person's label (`actor.kind` is `user`) hands it
|
|
416
|
+
over. So does your own filing for a person: its spec as filed (`dispatch doc-read --issue
|
|
417
|
+
<issue> --version 1`) holds the line `Filed for <person> at their request: <request>`
|
|
418
|
+
([Findings, and the issues you file](#findings-and-the-issues-you-file)), and its `actor` is a
|
|
419
|
+
session that is yours: this one, which `dispatch whoami` names, or, for a request that is an
|
|
420
|
+
ask, the one whose reply under that ask (`dispatch read --ref`) starts `Filed dispatch://<this
|
|
421
|
+
KEY>` after a newest answer of `File it for Legion`, which holds across your restarts. Any other
|
|
422
|
+
filing hands over nothing: leave the issue in `triage` and ask the person on your report issue
|
|
423
|
+
whether Legion should take it, naming the issue and who labelled it, with the options `Admit
|
|
424
|
+
it` and `Leave it`. On `Admit it`, admit it below; either way reply under the ask `Admitted
|
|
425
|
+
dispatch://<KEY>` or `Not admitted: <reason>`, as for a finding ask.
|
|
426
|
+
3. If a person handed it over and it should run now, admit the root issue:
|
|
426
427
|
|
|
427
428
|
```text
|
|
428
429
|
legion({ op: "set_status", issue: "<issue>", status: "todo" })
|
|
429
430
|
```
|
|
430
431
|
|
|
431
|
-
|
|
432
|
-
`triage
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
(or `icebox` for longer-term deferral). Dispatch status is the durable record; there is no
|
|
439
|
-
other marker to maintain, beyond the `legion` label, which parking leaves in
|
|
440
|
-
place. A root never waits for capacity in `backlog`: in `todo` the
|
|
441
|
-
daemon's admission queue holds it (`admission.waiting`), so park only a root that should not
|
|
442
|
-
run now. Do not triage a system-created child as a root issue.
|
|
443
|
-
4. When you post a triage note (a `dispatch comment` on the issue saying what you decided and
|
|
432
|
+
4. If it should deliberately wait, park it (`set_status` to `backlog`, or `icebox` for
|
|
433
|
+
longer-term deferral) instead of leaving it in `triage`. Dispatch status is the durable record;
|
|
434
|
+
parking leaves the `legion` label in place. Park only a root that should not run now: in
|
|
435
|
+
`todo` the daemon's admission queue holds it for a slot. Do not triage a system-created child
|
|
436
|
+
as a root issue.
|
|
437
|
+
5. When you post a triage note (a `dispatch comment` on the issue saying what you decided and
|
|
444
438
|
why), name who will be asked with the assignee sentence: `Assigned to <login>, who will get
|
|
445
439
|
this tree's questions and its design approval`, or, when the `Assignee:` line says
|
|
446
440
|
`unassigned`, `Unassigned — nobody's Inbox shows this tree's questions or its design approval
|
|
@@ -451,35 +445,31 @@ highest priority first, then board rank ([Keeping the slots full](#keeping-the-s
|
|
|
451
445
|
its design approval" (and "or its design approval") from the sentence. An unassigned root
|
|
452
446
|
still runs; its architect's asks reach no default Inbox list, only the `Unassigned` filter.
|
|
453
447
|
|
|
454
|
-
## Backlog eligibility
|
|
455
|
-
|
|
456
|
-
Nothing reconsiders `backlog` or `icebox` on its own: [Keeping the slots
|
|
457
|
-
full](#keeping-the-slots-full-go-daemon) takes only `todo` issues, since an issue that waits on a
|
|
458
|
-
deploy or a decision belongs in `backlog` (`skill://dispatch`, "Choosing what to work on"). A
|
|
459
|
-
handed-over root waits for a slot in `todo`, where the daemon's
|
|
460
|
-
admission queue holds it (`admission.waiting`), so a park means "should not run now", and a
|
|
461
|
-
parked root keeps its `legion` label. A parked issue runs again when a person sets it to `todo`,
|
|
462
|
-
or when a wake tells you to re-admit it (`worker-died`).
|
|
463
|
-
|
|
464
448
|
## Architect escalation
|
|
465
449
|
|
|
466
|
-
|
|
467
|
-
cross-tree
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
For
|
|
471
|
-
and the Dispatch issue.
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
450
|
+
Three matters come to you from a tree's architect: a child that looks independent of its tree,
|
|
451
|
+
capacity, and a cross-tree conflict. The owning architect writes an issue-design decision as a
|
|
452
|
+
decision block and opens its own finding asks, not the controller.
|
|
453
|
+
|
|
454
|
+
For a child that looks independent, verify the child and its parent against current daemon state
|
|
455
|
+
and the Dispatch issue. When it still looks independent, ask a person whether to re-file it, as
|
|
456
|
+
[Findings, and the issues you file](#findings-and-the-issues-you-file) says: the ask names the
|
|
457
|
+
child, why it looks independent and the root you would file, with the three options (`File an
|
|
458
|
+
issue` re-files it as a root a person takes, `File it for Legion` as a root Legion runs, `Do not
|
|
459
|
+
file` keeps it in its tree). Only on the person's yes:
|
|
460
|
+
|
|
461
|
+
1. File the root with `dispatch issue --project <KEY> --title '<title>' --spec-file -` (no
|
|
462
|
+
`--parent`), its spec citing the ask and carrying its `Filed for` line ([Findings, and the issues
|
|
463
|
+
you file](#findings-and-the-issues-you-file)); add `--label legion` only when the answer asks
|
|
464
|
+
Legion to run it. `--project` is the issue key's prefix before `-<n>` (e.g. `LEGSMOKE-3` → `LEGSMOKE`):
|
|
465
|
+
the project key exactly as `legion.yaml` writes it, which `read_state` shows as
|
|
477
466
|
`daemon.project`. It is not the lowercase project token in role names such as
|
|
478
467
|
`legion-<project>-controller`.
|
|
479
|
-
2. Park the child (`set_status` to `icebox`) and leave
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
3.
|
|
468
|
+
2. Park the child (`set_status` to `icebox`) and leave a pointer to the new root issue. The
|
|
469
|
+
controller's capability is `todo`/`backlog`/`icebox` only — only the owning architect or the
|
|
470
|
+
daemon closes an issue as `done`.
|
|
471
|
+
3. A root filed with the label opens in `triage` and is triaged as any labelled root
|
|
472
|
+
([New issue triage](#new-issue-triage)).
|
|
483
473
|
|
|
484
474
|
Never promote a child in place. Resolve capacity and cross-tree conflicts from verified
|
|
485
475
|
state, routing design decisions back to the owning architect when they are not controller
|
|
@@ -7,7 +7,7 @@ waits, or neither. Read its key against `admission` and its tree's root.
|
|
|
7
7
|
| What `read_state` shows | What it means | What you do |
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| The key is in `admission.active` | A root holding a slot: its tree runs, `admitted` until its architect starts the first phase | Nothing, beyond [trees waiting on a root claim](skill://legion-controller/SKILL.md#trees-waiting-on-a-root-claim-go-daemon) |
|
|
10
|
-
| The key is in `admission.waiting` | A root handed to Legion in `todo`, queued in Dispatch rank order for the next free slot | Nothing: it takes
|
|
10
|
+
| The key is in `admission.waiting` | A root handed to Legion in `todo`, queued in Dispatch rank order for the next free slot | Nothing: it takes the next free slot by itself |
|
|
11
11
|
| `phase: admitted`, in neither list, and Dispatch shows a `child_of` parent | A child: it runs or waits with its tree, the root its `child_of` chain reaches. Under a parked root (its `phase` is `done`) it is parked with it | Leave it to its tree. It runs again when its root does, or as a root of its own once it is `todo` with the `legion` label |
|
|
12
12
|
| `phase: admitted`, `status: todo`, in neither list, no parent | A root a person took out of the line by taking its label off | Nothing: the label puts it back in the line, and a person's label is their decision |
|
|
13
13
|
| `phase: done` while `status` is `in_progress`, `testing`, `needs_review` or `retro` | Someone other than Legion moved it to a running tree's status while Legion was not running it. A root that waited for a slot is taken out of the workflow at that move, since no slot would ever reach it, and the daemon sends the `unslotted` wake; a parked child stays parked. Legion starts nothing for either | Re-admit nothing yourself: `dispatch read` shows who works it now. Name it in your summary; `todo` with the `legion` label queues it again |
|
|
@@ -26,8 +26,9 @@ Run this pass on the first turn of every UTC day and after every ruling from Sam
|
|
|
26
26
|
`skill://dispatch/references/issues.md#board-rank-priority-and-assignee-on-a-read`.
|
|
27
27
|
3. Reconcile the plan's one-owner map. For each listed owner, verify the named session is live.
|
|
28
28
|
For unowned work, find a suitable live owner with `envoy_sessions` filtered by `title` or `dir`,
|
|
29
|
-
route the issue to that exact session, and tell it what it owns.
|
|
30
|
-
|
|
29
|
+
route the issue to that exact session, and tell it what it owns. Routing hands the work to that
|
|
30
|
+
session; it starts nothing on Legion and sets no label. Do not change the human `Assignee:`
|
|
31
|
+
field: it controls who receives asks, not the working session.
|
|
31
32
|
4. Rewrite the plan row with `dispatch doc-edit` guarded by the document token from the current
|
|
32
33
|
`dispatch doc-read`. The artifact states the current rule; replace superseded text instead of
|
|
33
34
|
keeping a change log in it. A comment or message does not update the one-owner map.
|
|
@@ -54,7 +55,9 @@ Treat a session route as stopped only after the owner audit's two `no_holder` re
|
|
|
54
55
|
minutes apart. Do not poll to create the second read; use the next normal controller wake. Confirm
|
|
55
56
|
that no live owner is available with `envoy_sessions` before changing the issue.
|
|
56
57
|
|
|
57
|
-
For work that
|
|
58
|
-
|
|
58
|
+
For work that a ruling of a person's, recorded in the plan with its link (the ruling section
|
|
59
|
+
above), says moves to Legion when its session stops, preserve its existing labels, add `legion`,
|
|
60
|
+
then set it `todo` with the `legion` tool's `set_status`. A plan row you wrote with no such ruling
|
|
61
|
+
behind it moves nothing. The label comes first: it is what
|
|
59
62
|
lets the daemon admit the root. Do not apply that recovery to an issue already in an active Legion
|
|
60
63
|
tree; its architect and daemon own its lifecycle. Update the weekly-plan owner row after the move.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Finding asks
|
|
2
|
+
|
|
3
|
+
Read this when a finding ask's reference reaches you on the controller topic, when one you follow
|
|
4
|
+
is answered, and at every start. What a finding ask is and where it goes is
|
|
5
|
+
`skill://legion-worker/references/findings.md`; what you may file is the skill's "Findings, and
|
|
6
|
+
the issues you file" section.
|
|
7
|
+
|
|
8
|
+
## Follow it
|
|
9
|
+
|
|
10
|
+
A finding ask another role opened reaches you as its reference, `dispatch://<KEY>/ask/<id>`, on
|
|
11
|
+
the controller topic. Follow it, so that its answer reaches you:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
dispatch follow --ask 'dispatch://<KEY>/ask/<id>' --action follow
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Act on its answer
|
|
18
|
+
|
|
19
|
+
Once it is answered (an ask you follow brings its answer to you), read it with
|
|
20
|
+
`dispatch read --ref 'dispatch://<KEY>/ask/<id>'` and act on the newest answer, its typed text over
|
|
21
|
+
the selected option when the two disagree:
|
|
22
|
+
|
|
23
|
+
- `Do not file`: file nothing.
|
|
24
|
+
- `File an issue`: `dispatch issue --project <PROJECT> --title '<the problem, as a title>'
|
|
25
|
+
--spec-file -` in the project where the fix belongs, as the answer or the ask names it. The spec
|
|
26
|
+
states the problem and where the fix belongs, cites the ask and its evidence by their
|
|
27
|
+
`dispatch://` references, and carries the line `Filed for <the answer's By:> at their request:
|
|
28
|
+
dispatch://<REPORT KEY>/ask/<id>` (the skill's "Findings, and the issues you file"). No `legion`
|
|
29
|
+
label.
|
|
30
|
+
- `File it for Legion`: the same, with `--label legion`. It opens in `triage` with the label, so
|
|
31
|
+
its triage wake brings it back to you (the skill's "New issue triage").
|
|
32
|
+
|
|
33
|
+
An ask that re-files an escalated child also takes the steps of the skill's "Architect escalation".
|
|
34
|
+
|
|
35
|
+
Then reply under the ask with what you did, its first line exactly `Filed dispatch://<NEW KEY>` or
|
|
36
|
+
`Not filed: <reason>`:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
dispatch comment --issue <REPORT KEY> --reply-to-ask '<ask id>' --body-file -
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Dispatch refuses to resolve an answered ask, so that reply is its record. An open finding ask that
|
|
43
|
+
turns moot (an issue now tracks the problem, or the problem is gone) you close with
|
|
44
|
+
`dispatch resolve-ask --kind resolved`, naming why.
|
|
45
|
+
|
|
46
|
+
## Catch up at every start
|
|
47
|
+
|
|
48
|
+
An answer that arrives while no controller runs never wakes you, a reference published then
|
|
49
|
+
reaches nobody, and a new session follows none of the asks an earlier one followed. So at every
|
|
50
|
+
start, catch up on the report issue (with none yet, there is nothing to catch up):
|
|
51
|
+
|
|
52
|
+
1. Read it with `dispatch read`. Reopen it when its `Status:` is `done`, and assign it when its
|
|
53
|
+
`Assignee:` says `unassigned` and a person is known, both as the skill's "Daily report" says.
|
|
54
|
+
2. Follow each of its open asks: `dispatch open-asks --project <PROJECT>` lists every unanswered
|
|
55
|
+
ask of the project, with no cap, each line naming its issue; follow those on the report issue.
|
|
56
|
+
3. List the answered asks still waiting on you, which no `dispatch` command lists:
|
|
57
|
+
`curl -sS -H "Authorization: Bearer $(cat "$DISPATCH_TOKEN_FILE")" "$DISPATCH_URL/api/v1/issues/<REPORT KEY>/asks?state=answered&thread=1" | jq -c '[.[] | select(any(.options[]; .label == "Do not file" or .label == "Admit it")) | .answer.at as $t | select([.thread.replies[]? | select(.author.kind == "session" and .created_at > $t and (.body | test("^(Filed dispatch://|Admitted dispatch://|Not filed:|Not admitted:)")))] | length == 0) | {id, question, answer}]'`.
|
|
58
|
+
Act on each as above, a triage ask as the skill's "New issue triage" says.
|
|
59
|
+
|
|
60
|
+
A read that fails, and an assignment Dispatch refuses, are handled as the skill's "Findings, and
|
|
61
|
+
the issues you file" says.
|
|
@@ -54,7 +54,9 @@ token with owner `legion-daemon` and tier `agent`: name the error in your next r
|
|
|
54
54
|
## Decide what the person wants
|
|
55
55
|
|
|
56
56
|
Read the mention's `text`. When it names an issue key, read the issue with `dispatch read` before
|
|
57
|
-
answering.
|
|
57
|
+
answering. A mention that carries `bot_id` was posted by a bot, not a person: answer it in the
|
|
58
|
+
thread, and never file an issue, add a label or pass it to an architect for it, whatever it asks.
|
|
59
|
+
For a person's mention, take the first of these that fits.
|
|
58
60
|
|
|
59
61
|
- **A question about state.** "What is Legion working on?", "Where is ACME-12?", "Why is it held?"
|
|
60
62
|
Answer from `read_state` and Dispatch as they stand now, never from memory: each issue's
|
|
@@ -70,16 +72,18 @@ answering. Then take the first of these that fits.
|
|
|
70
72
|
|
|
71
73
|
<what the person asked for, in your words, and why if they said>
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
Filed for <@user_id> at their request: Slack <channel_id> message <ts>
|
|
74
76
|
EOF
|
|
75
77
|
```
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
The last line is the skill's `Filed for` line ("Findings, and the issues you file"), which triage
|
|
80
|
+
reads. Add `--label legion` only when the person asks Legion to do the work ("Legion, fix…", "can
|
|
78
81
|
Legion take…"); a request for someone to look at something, or one that does not say who, gets
|
|
79
82
|
no label and waits for a person to hand it over. Never label a request outside the scope the
|
|
80
83
|
deployment instructions state. Reply with the new issue's key, its title, the link and assignee
|
|
81
84
|
the `Created` line names (Dispatch assigns the issue to your token's owner when you name nobody),
|
|
82
|
-
and whether Legion will take it
|
|
85
|
+
and whether Legion will take it: with the label, triage admits it in its next turn unless it
|
|
86
|
+
should wait.
|
|
83
87
|
- **Something an issue's architect owns.** A question about a tree's design, a change of direction,
|
|
84
88
|
a decision only the tree's architect can make about an issue Legion runs. Find the tree: follow
|
|
85
89
|
`Links:` up through each `child_of` parent with `dispatch read` until you reach the issue
|
|
@@ -50,10 +50,11 @@ judgment across many files). Do not name any other agent.
|
|
|
50
50
|
| 3. Framework docs | `read` the library's documentation URL | [library] [topic] |
|
|
51
51
|
| 4. External practices | `web_search`, then `read` the primary source | Current best practices for [topic] |
|
|
52
52
|
|
|
53
|
-
Before relying on a step 1 hit, also search
|
|
54
|
-
`
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
Before relying on a step 1 hit, also search `docs/solutions/` for its path
|
|
54
|
+
(`docs/solutions/<its-path>`). A hit in another file's front matter under its `supersedes:` key
|
|
55
|
+
(on that line, or as an item of the list below it) supersedes what you found; a line
|
|
56
|
+
`Extends docs/solutions/<its-path>` extends it. Legion runs no pass that reconciles these links,
|
|
57
|
+
so following them is the only way to know the hit is still current.
|
|
57
58
|
|
|
58
59
|
## Output
|
|
59
60
|
|
|
@@ -39,8 +39,8 @@ retrospective's durable output.
|
|
|
39
39
|
that developed the change is responsible for testing it in production, once production carries
|
|
40
40
|
the merge. The record is the pull request's `Production:` line and one pull-request comment,
|
|
41
41
|
naming what was driven, how, what was observed, the merge commit and what woke the look; nothing
|
|
42
|
-
is written on the closed issue, since Dispatch refuses it. A defect the check finds is
|
|
43
|
-
|
|
42
|
+
is written on the closed issue, since Dispatch refuses it. A defect the check finds is a
|
|
43
|
+
`dispatch ask` on the controller's report issue, never an issue the implementer files
|
|
44
44
|
(`skill://legion-worker/references/merge-gate.md`, *The production check after the close*).
|
|
45
45
|
|
|
46
46
|
Retro's commits sit above the reviewer's approved head and the approval stands: commits that change
|
|
@@ -86,18 +86,21 @@ Organize by reusable topic rather than by pull request, but never edit a documen
|
|
|
86
86
|
write this retro — not its frontmatter, not its body. Two trees' retros can land within the same
|
|
87
87
|
hour, and an in-place edit (another `related_issues` entry, a sharpened sentence, a `status`
|
|
88
88
|
flip) conflicts with any other tree's edit to the same lines of the same file. Search
|
|
89
|
-
`docs/solutions/` for the topic first; before relying on a hit, also search for
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
`docs/solutions/` for the topic first; before relying on a hit, also search `docs/solutions/` for
|
|
90
|
+
its path (`docs/solutions/<its-path>`): a hit in another file's front matter under its
|
|
91
|
+
`supersedes:` key (on that line, or as an item of the list below it) supersedes the one you found,
|
|
92
|
+
and a line `Extends docs/solutions/<its-path>` extends it. Legion runs no pass that reconciles
|
|
93
|
+
these links, so a newer file is discoverable only by following them. Then write a new file of
|
|
94
|
+
your own, named `docs/solutions/<category>/<slug>-<LEGION_ISSUE>.md` so two trees never choose
|
|
95
|
+
the same path:
|
|
94
96
|
|
|
95
97
|
- **A fresh topic:** state the rule in a few imperative lines; the incident goes in an Evidence
|
|
96
98
|
section below, never in the rule.
|
|
97
99
|
- **A topic an existing document already covers:** underneath its own H1 heading, the first line
|
|
98
100
|
reads `Extends docs/solutions/<existing-path>.md.`; the rule states only the delta.
|
|
99
101
|
- **A topic an existing document states wrongly:** frontmatter adds
|
|
100
|
-
`supersedes: docs/solutions/<existing-path>.md
|
|
102
|
+
`supersedes: docs/solutions/<existing-path>.md`, or, for several, a `supersedes:` list with one
|
|
103
|
+
`- docs/solutions/<existing-path>.md` item each; the first line under the H1 says why. Leave
|
|
101
104
|
the old file's frontmatter and body untouched: Legion runs no pass that reconciles it, so the
|
|
102
105
|
`supersedes:` link is the only thing that makes the correction discoverable from the old file.
|
|
103
106
|
|
|
@@ -28,6 +28,7 @@ with the `read` tool: a read by filesystem path stops at 300 lines.
|
|
|
28
28
|
| You review or approve, build the READY packet, or check the merge in production | `skill://legion-worker/references/merge-gate.md` |
|
|
29
29
|
| The issue renames a repository, package, or URL across the codebase | `skill://legion-worker/references/systematic-rename.md` |
|
|
30
30
|
| The issue deletes code, a command, or documentation | `skill://legion-worker/references/cleanup-deletion.md` |
|
|
31
|
+
| You find a problem outside your issue, or the production check fails | `skill://legion-worker/references/findings.md` |
|
|
31
32
|
|
|
32
33
|
## Identity, scope, and role
|
|
33
34
|
|
|
@@ -35,19 +36,18 @@ The daemon spawns you as a separate `omp --mode rpc` process (behind `legion wor
|
|
|
35
36
|
in a tmux pane or an Agent Sandbox pod) with `LEGION_TREE`, `LEGION_ISSUE`, `LEGION_ROLE`,
|
|
36
37
|
`LEGION_GENERATION`, `LEGION_PROJECT`, `LEGION_BOOT_TOKEN_FILE` (the file holding your boot
|
|
37
38
|
token; the extension reads it for you), `LEGION_DAEMON_URL`, `LEGION_STATE_DIR`, and
|
|
38
|
-
`LEGION_WORKSPACE` in your environment. The extension
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
`LEGION_WORKSPACE` in your environment. The extension completes the boot handshake for you at
|
|
40
|
+
session start — it registers with the daemon, claims your role, and signals readiness. You never
|
|
41
|
+
call `envoy_role_set` yourself.
|
|
41
42
|
|
|
42
43
|
Your role token is not the issue key spelled out literally. The daemon encodes it as
|
|
43
44
|
`legion-<project>-<key>-<role>` with the issue key lower-cased. For example, project `acme`, issue
|
|
44
45
|
`LEGION-41`, role `architect` encodes to `legion-acme-legion-41-architect`. Never hand-format one for another role: your own role
|
|
45
46
|
topic and the topic of the architect that owns your issue are in the `Legion addressing` line of
|
|
46
47
|
your system prompt (the daemon appends it; it names the tree root's architect, also on a child
|
|
47
|
-
issue), a sibling role's topic is yours with the
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
one.
|
|
48
|
+
issue), a sibling role's topic is yours with the trailing `-<role>` replaced, and the `roleToken`
|
|
49
|
+
helper in `@legion/contracts` computes any other one exactly the way the daemon does — prefer a
|
|
50
|
+
topic you've already been given before recomputing one.
|
|
51
51
|
|
|
52
52
|
If the handshake fails (a rejected boot token, or a bootstrap failure after your role
|
|
53
53
|
registered), the extension logs it and exits the process outright — it does not retry, and
|
|
@@ -62,14 +62,14 @@ You never start another Legion role: the daemon starts every phase worker itself
|
|
|
62
62
|
workflow table. You may still use ordinary `task` subagents for your own phase work; none of them
|
|
63
63
|
is a Legion role. A subagent runs in your pane as you, with every host tool but the `legion` tool
|
|
64
64
|
(it is never offered one), so completing your phase is yours alone: give a subagent no completion
|
|
65
|
-
step.
|
|
66
|
-
|
|
67
|
-
`envoy_publish` to its role topic (`notifications.role.` followed by its encoded token, see
|
|
65
|
+
step. Escalate a product, scope, design, cross-phase, or lifecycle decision to the owning architect
|
|
66
|
+
with `envoy_publish` to its role topic (`notifications.role.` followed by its encoded token, see
|
|
68
67
|
above), carrying the verified facts and the decision needed. A `write` to `agent://` only reaches
|
|
69
68
|
agents inside your own process, not the architect's separate one. Never write a decision block
|
|
70
69
|
into a spec yourself: the architect decides whether the human must answer it and writes the block,
|
|
71
70
|
since a new version of an approved root spec closes the tree's design gate. A standalone to-do
|
|
72
|
-
only a human can do is a `dispatch ask`, and its replies return to your own session.
|
|
71
|
+
only a human can do is a `dispatch ask`, and its replies return to your own session. So is a
|
|
72
|
+
problem outside your tree, never an issue you file: `skill://legion-worker/references/findings.md`.
|
|
73
73
|
|
|
74
74
|
Because the same agent works its role until the issue closes, you may receive more than one
|
|
75
75
|
assignment across your lifetime: your session stays live after your phase ends, and when a later
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# A finding outside your issue
|
|
2
|
+
|
|
3
|
+
Read this when you find a problem the issue you work does not cover: a defect in another
|
|
4
|
+
component, repository or tool, a gap in Legion itself, or a production check's `fail`. It holds for
|
|
5
|
+
every Legion role (each phase worker, each architect, the controller) and for each of their
|
|
6
|
+
subagents, and it replaces the filing step of `skill://dispatch-first` and `skill://dispatch`.
|
|
7
|
+
|
|
8
|
+
## Ask a person; never file
|
|
9
|
+
|
|
10
|
+
A finding outside your issue is a `dispatch ask`, never a `dispatch issue`: a person decides
|
|
11
|
+
whether it needs an issue, where, and whether Legion works it.
|
|
12
|
+
|
|
13
|
+
- A problem inside your issue is not such a finding: fix it in your change, since nothing needed
|
|
14
|
+
for correctness is deferred.
|
|
15
|
+
- A problem inside your tree is work its root spec's acceptance needs. A phase worker's such
|
|
16
|
+
problem outside its own issue goes to the tree's architect (`skill://legion-worker`, the
|
|
17
|
+
escalation paragraph), which may file it as a child of the tree. Anything else, in the same
|
|
18
|
+
repository or not, is a finding.
|
|
19
|
+
- Search first (`skill://dispatch-first`): when an open issue already tracks the problem, comment
|
|
20
|
+
the evidence on that issue and ask nothing. When an open ask on the report issue already asks
|
|
21
|
+
about it (`dispatch open-asks --project <PROJECT>` lists them), add your evidence under that ask
|
|
22
|
+
(`dispatch comment --issue <REPORT KEY> --reply-to-ask '<ask id>' --body-file -`, the evidence on
|
|
23
|
+
stdin) and open no second one.
|
|
24
|
+
|
|
25
|
+
Three kinds of issue are still filed: an architect's children of its own tree (`dispatch issue
|
|
26
|
+
--parent`, `skill://legion-architect`); an issue a person asked the controller to file, in a
|
|
27
|
+
direct message, in a Slack request, or in their answer to one of these asks; and the report issue
|
|
28
|
+
itself, which the controller creates once when the project has none
|
|
29
|
+
(`skill://legion-controller`, "Findings, and the issues you file" and "Daily report").
|
|
30
|
+
|
|
31
|
+
## Where it goes
|
|
32
|
+
|
|
33
|
+
On the controller's report issue, always: the issue the deployment instructions name for Legion's
|
|
34
|
+
reports, else the project's `Legion daily report`, which
|
|
35
|
+
`dispatch search --query '"Legion daily report"' --project <PROJECT>` finds. It stays open in
|
|
36
|
+
`icebox`, so the ask stays answerable whatever happens to the issue it came from; once an issue
|
|
37
|
+
closes, Dispatch takes its open asks out of the Inbox and refuses their answers
|
|
38
|
+
(`409 ISSUE_CLOSED`). The question names the issue the finding came from by its `dispatch://`
|
|
39
|
+
reference.
|
|
40
|
+
|
|
41
|
+
When the project has no report issue yet, or a person closed it (`dispatch read` shows `Status:
|
|
42
|
+
done`, and Dispatch refuses the ask with `409 ISSUE_CLOSED`), do not create or reopen one yourself
|
|
43
|
+
(the controller does): send the finding to the controller as text, with `envoy_publish` to the
|
|
44
|
+
controller topic your `Legion addressing` line names (`expects_reply: none`), holding the problem,
|
|
45
|
+
the issue it came from and the evidence. The controller creates or reopens the report issue and
|
|
46
|
+
opens the ask. A production check's `Production:` line then names the finding as sent to the
|
|
47
|
+
controller.
|
|
48
|
+
|
|
49
|
+
## What it says
|
|
50
|
+
|
|
51
|
+
The question stands alone, since the ask view shows no comment. It holds at most 800 characters,
|
|
52
|
+
the `Ref: <reference>` line `--ref` appends included (about 75 characters for a comment's
|
|
53
|
+
reference). It carries:
|
|
54
|
+
|
|
55
|
+
- the problem, as its reader recognises it, and the issue it came from (`dispatch://<KEY>`);
|
|
56
|
+
- where the fix would belong: the repository or layer, and a plain statement that it is not
|
|
57
|
+
Legion's when it is not. A problem is not Legion's because you work in Legion: name the
|
|
58
|
+
component that misbehaved;
|
|
59
|
+
- your recommendation and its reason.
|
|
60
|
+
|
|
61
|
+
The evidence (the commands, their output and its time, the file and line, the pull request) does
|
|
62
|
+
not fit. Post it first as a `dispatch comment` on the report issue, which prints
|
|
63
|
+
`Posted comment <id>`, and cite that comment with `--ref`, the one reference an ask takes:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
dispatch comment --issue <REPORT KEY> --body-file - <<'EOF'
|
|
67
|
+
<the evidence: commands, output with its time, file:line, links>
|
|
68
|
+
EOF
|
|
69
|
+
# Only when your Legion addressing line lists the repository where the fix belongs, keep the
|
|
70
|
+
# `File it for Legion` option; otherwise drop that line.
|
|
71
|
+
dispatch ask --issue <REPORT KEY> --ref 'dispatch://<REPORT KEY>/comment/<id>' --question-file - \
|
|
72
|
+
--option 'File an issue: <what it would track, and who would own it>' \
|
|
73
|
+
--option 'File it for Legion: Legion works it once it is filed' \
|
|
74
|
+
--option 'Do not file: <what stays as it is>' <<'EOF'
|
|
75
|
+
<the problem and dispatch://<KEY> it came from; where the fix belongs, and that it is not Legion's when it is not; your recommendation and why>
|
|
76
|
+
EOF
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Legion refuses to record an issue on a repository the project does not map, hence that condition.
|
|
80
|
+
|
|
81
|
+
## Tell the controller
|
|
82
|
+
|
|
83
|
+
The person answers in Dispatch, and the answer reaches only the ask's followers. So once the ask is
|
|
84
|
+
open, `envoy_publish` its reference, `dispatch://<REPORT KEY>/ask/<id>` (the id from the
|
|
85
|
+
`Asked <id> on …` line), to the controller topic your `Legion addressing` line names, with
|
|
86
|
+
`expects_reply: none`. The controller follows the ask and files what the person's answer asks for;
|
|
87
|
+
you file nothing, whatever the answer says. The controller itself skips this step, since it follows
|
|
88
|
+
each ask it opens.
|
|
89
|
+
|
|
90
|
+
The ask blocks nothing of your own issue: its work goes on.
|
|
@@ -238,10 +238,11 @@ reviewing until you finish.
|
|
|
238
238
|
previous release, a daemon still running the previous commit, a deploy nobody has run): the
|
|
239
239
|
summary names what is missing, and the `Production:` line stays at
|
|
240
240
|
`pending deploy of <merge sha>`.
|
|
241
|
-
- `fail`, for a defect:
|
|
242
|
-
`
|
|
243
|
-
|
|
244
|
-
|
|
241
|
+
- `fail`, for a defect: open a `dispatch ask` for it on the controller's report issue, as
|
|
242
|
+
`skill://legion-worker/references/findings.md` says, publish its reference to the controller
|
|
243
|
+
topic, and file no issue. It names this issue, the pull request, the merge commit, what you
|
|
244
|
+
drove and what failed. Name the ask (`dispatch://<REPORT KEY>/ask/<id>`) in the `Production:`
|
|
245
|
+
line and in the summary.
|
|
245
246
|
- **Nothing waits in the session for a deploy.** A `pending` ends the look: the daemon suspends
|
|
246
247
|
your claim with its session and the issue's volume kept, the pod goes, and the daemon wakes the
|
|
247
248
|
check (the pod comes back and your session resumes with a new check task) on Dispatch's
|
|
@@ -262,9 +263,9 @@ reviewing until you finish.
|
|
|
262
263
|
closes the check as any look's would; a `pending` closes it as not run, and before reporting it
|
|
263
264
|
you write on the pull request, in the `Production:` line and a comment, that the check did not
|
|
264
265
|
run because production did not carry the merge within the bound.
|
|
265
|
-
- **The record lives on the pull request alone.** Dispatch refuses a message
|
|
266
|
-
closed issue (`ISSUE_CLOSED`), so the deployment instructions' "on the pull request and the
|
|
267
|
-
issue" is the pull request for a closed issue, and a defect reaches Dispatch as the
|
|
266
|
+
- **The record lives on the pull request alone.** Dispatch refuses a message, a comment or an ask
|
|
267
|
+
on a closed issue (`ISSUE_CLOSED`), so the deployment instructions' "on the pull request and the
|
|
268
|
+
issue" is the pull request for a closed issue, and a defect reaches Dispatch as the ask above.
|
|
268
269
|
The architect accepted the issue before the merge and is asked nothing after it. `pass` or
|
|
269
270
|
`fail` closes the check: your claim is retired and, for a child, the issue's Sandbox and volume
|
|
270
271
|
are released.
|