@sjawhar/opencode-legion-envoy 9.1.0 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,8 +16,9 @@ the issue header. Triage it as `skill://legion-controller/references/triage.md`
16
16
 
17
17
  ## `slot-free on <KEY>` (`{kind: "slot-free"}`)
18
18
 
19
- The daemon released `<KEY>`'s slot, because its tree finished or left the workflow, and no waiting
20
- root took it. Name the free slots; take nothing.
19
+ The daemon released `<KEY>`'s slot and no waiting root took it: its tree finished or left the
20
+ workflow, or the root returned the slot to wait on a person (`awaiting_person`, which also sends
21
+ its own `awaiting-person` wake). Name the free slots; take nothing.
21
22
 
22
23
  ## `todo on <KEY>` (`{kind: "todo"}`)
23
24
 
@@ -50,12 +51,25 @@ take nothing for them.
50
51
 
51
52
  ## `unslotted on <KEY>` (`{kind: "unslotted", reason}`)
52
53
 
53
- A root that waited for a slot was moved from `todo` to the status `reason` names, by a person or by
54
- another session, so the daemon took it out of the workflow (`phase: done`) and starts nothing for
55
- it. Re-admit nothing, because the move was someone else's; `dispatch read` shows who claims it now.
56
- Tell the person in your summary: "<KEY> left Legion's waiting line: it was set to <status> while it
57
- waited for a slot, so Legion let it go; `todo` with the `legion` label queues it again". Other
58
- admission states: `skill://legion-controller/references/admission.md`.
54
+ Either a root that waited for a slot was moved from `todo` to the status `reason` names, by a
55
+ person or by another session, or a root waiting on a person (`awaiting_person`) could not be
56
+ resumed — its `legion` label is gone, or a person or a session outside its tree took its Dispatch
57
+ claim. Either way the daemon took it out of the workflow (`phase: done`) and starts nothing for it;
58
+ `reason` says which. Re-admit nothing, because the move was someone else's; `dispatch read` shows
59
+ who claims it now. Tell the person in your summary: "<KEY> left Legion's workflow: <reason as the
60
+ wake gives it>; `todo` with the `legion` label queues it again". Other admission states:
61
+ `skill://legion-controller/references/admission.md`.
62
+
63
+ ## `awaiting-person on <KEY>` (`{kind: "awaiting-person", reason}`)
64
+
65
+ A root whose architect asked a person a question, or handed its spec to a person for approval, and
66
+ now waits only on them: the daemon returned
67
+ its slot and suspends the architect after its turn, keeping its session. Read
68
+ `issues.<KEY>.waitingOn`. Do not park it or start anything, and take nothing for the slot it
69
+ returned: it is no reason to label or admit an issue ([Free
70
+ slots](skill://legion-controller/SKILL.md#free-slots)). The person's answer puts it at the front of
71
+ `admission.waiting` by itself, and the next free slot resumes its architect. A lost label or a
72
+ foreign Dispatch claim sends the separate `unslotted` wake.
59
73
 
60
74
  ## `held on <KEY>` (`{kind: "held", phase, role?, reason?}`)
61
75
 
@@ -19,7 +19,7 @@ You work one phase of one issue in a jj workspace every role of the issue shares
19
19
 
20
20
  ## Stop rules
21
21
 
22
- - **A refused completion is information, not a retry loop.** Quote the answer verbatim to the architect, so "my work is lost" and "my work belongs to an ended run" read differently. `HANDOFF_STALE_GENERATION` (the issue is on a newer run, or your turn began before the daemon recorded your task, which it then sends again) or `HANDOFF_NO_RUN` (your claim holds no task): push nothing further and tell the architect; when a task then arrives whose work is already committed, call `handoff_complete` again, and never redo the work or write a second handoff. `HANDOFF_NOT_CURRENT_PHASE`: the issue left your phase; report, never complete again. `HANDOFF_ALREADY_RECORDED`: the first answer stands, an acceptance or one of the four refusals the daemon records (`HANDOFF_STALE_GENERATION`, `HANDOFF_NOT_CURRENT_PHASE`, `READY_REQUIRED`, `HANDOFF_NOT_NEW`); if you did not see it, tell the architect. A refusal given before any fact is applied (READY's checks of the head among them) records nothing, so the corrected call at the same commit is applied. A reminder is not a task: after an accepted completion, a second call is refused.
22
+ - **A refused completion is information, not a retry loop.** Quote the answer verbatim to the architect, so "my work is lost" and "my work belongs to an ended run" read differently. `HANDOFF_STALE_GENERATION` (the issue is on a newer run: your turn began before the daemon's task for that run reached you, and the answer names that task when it is queued) or `HANDOFF_NO_RUN` (your claim holds no task): push nothing further, end the turn without completing, and tell the architect; if the task then arrives (none may: the new run's phase can be another claim's) and its work is already committed, call `handoff_complete` again at the same commit in that task's turn, and never redo the work or write a second handoff. `HANDOFF_NOT_CURRENT_PHASE`: the issue left your phase; report, never complete again. `HANDOFF_ALREADY_RECORDED`: the first answer stands, an acceptance or one of the refusals the daemon records (`HANDOFF_NOT_CURRENT_PHASE`, `READY_REQUIRED`, `HANDOFF_NOT_NEW`, and `HANDOFF_STALE_GENERATION` only when the issue moved during the call); if you did not see it, tell the architect. A refusal given before any fact is applied (`HANDOFF_STALE_GENERATION`, `HANDOFF_NO_RUN` and READY's checks of the head among them) records nothing, so the corrected call at the same commit is applied. A reminder is not a task: after an accepted completion, a second call is refused.
23
23
  - **After your phase completes, the workspace is read-only for you**: no commit, no `jj new`, no edit to a tracked file, in any later turn until a new task names your phase. The active phase owns it, and you stay live only to answer questions.
24
24
  - **Never start another Legion role**: the daemon starts every phase worker.
25
25
  - **Never end your turn blocked on a decision someone else owns.** Send a product, scope, design, lifecycle or cross-phase decision to the owning architect as your role prompt says, with the issue, phase, verified observation, what you tried and the decision needed; a human-only to-do is a `dispatch ask`. Otherwise proceed: a phase that stops silently holds its issue until someone notices.
@@ -46,7 +46,7 @@ Read each by its `skill://` link: a read by path stops at 300 lines.
46
46
  ## Every role pushes its own commits
47
47
 
48
48
  - Before a push, read `jj -R "$LEGION_WORKSPACE" log -r 'ancestors(@, 5)'` (earlier phases' commits in it are expected) and run the identity check (`skill://legion-worker/references/default-branch-check.md`). Add no attribution trailer: the pane's jj adds `Omp-Session:`.
49
- - A push whose commits touch only `.legion/` ends its head's message with `skip-checks: true`, unless it is retro's push (the implementer's retro phase, before the merger's). Retro's push never carries the line, whatever paths it touches, because it is the last push before the merge and a base that requires a check will not merge a head whose checks never ran. No other push carries it. The daemon classifies a push by its paths, never by the trailer, so a wrong trailer can stall the issue but never makes it pass.
49
+ - A push whose commits touch only `.legion/<issue>/` ends its head's message with `skip-checks: true`, unless it is retro's push (the implementer's retro phase, before the merger's). Retro's push never carries the line, whatever paths it touches, because it is the last push before the merge and a base that requires a check will not merge a head whose checks never ran. No other push carries it. The daemon classifies a push by its paths, never by the trailer, so a wrong trailer can stall the issue but never makes it pass.
50
50
  - A planner whose issue moved back from implementing starts on the remote tip, `jj -R "$LEGION_WORKSPACE" new legion/<KEY>@origin`, so its handoff never sits on the implementer's unpushed commits.
51
51
 
52
52
  ## PR body, review, and the merge gate
@@ -23,10 +23,10 @@ it, and any other change to the head voids it.
23
23
  `task(agent="thermonuclear-code-quality")` once at that head — the head the round reviews —
24
24
  and records the verdict. The CI it reads is the checks that stand for the code under review:
25
25
  those GitHub ran for the newest push of the branch that started `pull_request` workflows, when
26
- every commit above that push's head changes only `.legion/` (GitHub starts no workflow run for a
27
- head whose commit message carries `skip-checks: true`, which every handoff-only push before
28
- retro does; retro's push carries no such line, so its head does get GitHub's workflow runs of
29
- its own). With `<o>/<r>`, `<n>`, `<base>`, `<headRefName>` and `<headRefOid>` from
26
+ every commit above that push's head changes only `.legion/<issue>/` (GitHub starts no workflow
27
+ run for a head whose commit message carries `skip-checks: true`, which every handoff-only push
28
+ before retro does; retro's push carries no such line, so its head does get GitHub's workflow runs
29
+ of its own). With `<o>/<r>`, `<n>`, `<base>`, `<headRefName>` and `<headRefOid>` from
30
30
  `gh pr view <n> --json baseRefName,headRefOid,headRefName`, four reads. The required set is the
31
31
  base branch's rulesets' required checks and its branch protection's, when it has any. Both
32
32
  branch endpoints take the base as a URL path, each segment escaped and each `/` kept (a raw `#`
@@ -46,8 +46,9 @@ it, and any other change to the head voids it.
46
46
  newest first). That commit's check-runs
47
47
  (`gh api repos/<o>/<r>/commits/<ci head>/check-runs --paginate --jq '.check_runs[] | [.name, .status, .conclusion] | @tsv'`):
48
48
  every required name present, `completed` and `success` (GitHub counts `neutral` and `skipped`
49
- as passing for a required check). And the compare proving only `.legion/` changed since
50
- (`gh api repos/<o>/<r>/compare/<ci head>...<headRefOid> --jq '{handoffsOnly: ([.files[].filename] | all(startswith(".legion/"))), files: (.files | length)}'`
49
+ as passing for a required check). And the compare proving only `.legion/<issue>/` changed since,
50
+ `<issue>` being your `LEGION_ISSUE` (another issue's `.legion/` is code to the daemon and READY)
51
+ (`gh api repos/<o>/<r>/compare/<ci head>...<headRefOid> --jq '{handoffsOnly: ([.files[] | .filename, (.previous_filename // empty)] | all(startswith(".legion/<issue>/"))), files: (.files | length)}'`
51
52
  prints `handoffsOnly` `true`; `false` means code was pushed since CI ran, so wait for the new
52
53
  push's runs and read the CI head again). GitHub's compare lists at most 300 files for the whole
53
54
  comparison, so `files` at 300 is a comparison GitHub cut at its cap and `handoffsOnly` decides
@@ -98,7 +99,7 @@ it, and any other change to the head voids it.
98
99
  `REQUEST_CHANGES` when the code is at fault, and your own commit to fix when your prose broke
99
100
  it. The architect reads your prose commits at its acceptance (*The architect's acceptance*,
100
101
  below), and that read is no new round. A round after a READY refusal is the one exception. Its
101
- approval needs the tester's `E2E` line to name the head with only `.legion/` changed since. So
102
+ approval needs the tester's `E2E` line to name the head with only `.legion/<issue>/` changed since. So
102
103
  there you fix no prose yourself, and you name each prose finding in a `REQUEST_CHANGES`.
103
104
 
104
105
  A reviewer's phase ends with its completion, not with its review. Every round writes a handoff
@@ -109,7 +110,7 @@ whatever CI says and the issue leaves reviewing with it. The approval waits on n
109
110
  handoff-only commit either: it ends the round whatever CI says there, since the round reads no CI
110
111
  verdict, and a red the daemon reads later — at that commit or any above it — is the code's red
111
112
  and resumes the implementer from wherever the issue is (reviewing, retro or merging) without the
112
- reviewer. A head your own push changed beyond `.legion/` is the exception. The daemon marks it a
113
+ reviewer. A head your own push changed beyond `.legion/<issue>/` is the exception. The daemon marks it a
113
114
  planned red and sends nothing back for it, so wait for its checks yourself (*Fix a prose-only
114
115
  finding yourself*, above). A review of a head the handoff push then replaces names a head the pull
115
116
  request no longer has. The daemon moves the issue once both are in — the decision GitHub reports
@@ -120,8 +121,8 @@ reviewing until you finish.
120
121
 
121
122
  - Between the reviewer's approval and retro the issue sits in `accepting` (Dispatch status
122
123
  `needs_review`), where no worker is started: the tree's architect reads the evidence (the
123
- tester's handoff, the PR body's `E2E (implementer)` and `E2E (tester)` lines, the reviewer's
124
- approval of the head by SHA, and for a parent that every child is done) and accepts with the
124
+ tester's handoff, the PR body's `E2E (implementer)` and `E2E (tester)` lines and the reviewer's
125
+ approval of the head by SHA; children still running do not hold it) and accepts with the
125
126
  `legion` tool's `sign_off`, which moves the issue to retro, or sends it back to `implementing`
126
127
  or `testing` with `request_backward_move` and a reason, which reaches the started worker's task
127
128
  as its `Reason:` line and counts a review round. The architect also reads the reviewer's own
@@ -130,7 +131,7 @@ reviewing until you finish.
130
131
  controller takes it"). The acceptance asks no `Production:` line of anyone: that line is written
131
132
  after the close (*The production check after the close*, below).
132
133
  CI red at the head in `accepting` returns the issue to `implementing` as it does in review. A
133
- head the review App pushed beyond `.legion/` is the exception: the daemon marks it a planned red
134
+ head the review App pushed beyond `.legion/<issue>/` is the exception: the daemon marks it a planned red
134
135
  and acts on it nowhere, and GitHub holds the merge under the base branch's own rules.
135
136
 
136
137
  ## Retro
@@ -8,7 +8,7 @@ sjawhar/legion.
8
8
 
9
9
  ## The shape GitHub honours
10
10
 
11
- A push whose commits touch only `.legion/` (a handoff-only push) ends its head's commit message
11
+ A push whose commits touch only `.legion/<issue>/` (a handoff-only push) ends its head's commit message
12
12
  with `skip-checks: true` as the last line, preceded by two empty lines, so GitHub starts no
13
13
  workflow run for it and the daemon carries the code head's verdict to it — except retro's push,
14
14
  which never carries the line whatever paths it touches, for the reason the next section gives.
@@ -14,8 +14,9 @@ which you have; those need no runner.
14
14
  - `psql` — the Dispatch browser suite (`bun run e2e`) resets its database for every spec through
15
15
  `packages/dispatch/e2e/psql.ts`, which `seed.ts` calls, and the local stack (`bun run
16
16
  dispatch:local`, `packages/dispatch/e2e/local-instance.ts`) refuses to start without `go`,
17
- `psql`, `flock` and `openssl` on `PATH`, and without `docker` too unless a `DATABASE_URL` is
18
- given. The image carries `go`, `flock` and `openssl`, a `DATABASE_URL` stands in for `docker`,
17
+ `psql`, `flock`, `openssl` and `python3` on `PATH`, and without `docker` too unless a
18
+ `DATABASE_URL` is given. The image carries `go`, `flock`, `openssl` and `python3`, a
19
+ `DATABASE_URL` stands in for `docker`,
19
20
  and `psql` was the one a role had no way to supply — so a role whose change owes that suite can
20
21
  run it.
21
22
  - `redis-cli` — no repository of the project speaks Redis today; it is carried anyway, at about a