@sjawhar/claude-envoy 0.0.0 → 1.0.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.
Files changed (113) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/.mcp.json +11 -0
  3. package/.omp-plugin/plugin.json +6 -0
  4. package/AGENTS.md +343 -0
  5. package/README.md +639 -0
  6. package/bin/dispatch +6 -0
  7. package/bin/envoy-channel.ts +9 -0
  8. package/bin/envoy-send.ts +33 -0
  9. package/dist/THIRD_PARTY_NOTICES +816 -0
  10. package/dist/dispatch.js +27707 -0
  11. package/dist/envoy-channel.js +57695 -0
  12. package/dist/session-hook.js +26328 -0
  13. package/dist/session-title-hook.js +116 -0
  14. package/hooks/hooks.json +60 -0
  15. package/hooks/session-hook.ts +512 -0
  16. package/hooks/session-title-hook.ts +46 -0
  17. package/legion/.claude-plugin/plugin.json +4 -0
  18. package/legion/agents/deep-worker.md +68 -0
  19. package/legion/agents/oracle.md +35 -0
  20. package/legion/agents/plan-gap-analyst.md +65 -0
  21. package/legion/agents/plan-reviewer.md +104 -0
  22. package/legion/agents/thermonuclear-code-quality.md +30 -0
  23. package/legion/agents/thermonuclear-deep-review.md +30 -0
  24. package/legion/hooks/hooks.json +89 -0
  25. package/legion/skills/ce-simplify-code/LICENSE +21 -0
  26. package/legion/skills/ce-simplify-code/SKILL.md +66 -0
  27. package/legion/skills/ce-simplify-code/references/personas/code-quality-reviewer.md +17 -0
  28. package/legion/skills/ce-simplify-code/references/personas/code-reuse-reviewer.md +7 -0
  29. package/legion/skills/ce-simplify-code/references/personas/efficiency-reviewer.md +11 -0
  30. package/legion/skills/leading-a-work-stream/SKILL.md +96 -0
  31. package/legion/skills/leading-a-work-stream/references/daily-plan.md +8 -0
  32. package/legion/skills/leading-a-work-stream/references/decision-rights.md +6 -0
  33. package/legion/skills/leading-a-work-stream/references/direction-and-coaching.md +13 -0
  34. package/legion/skills/leading-a-work-stream/references/dispatch-upkeep.md +8 -0
  35. package/legion/skills/leading-a-work-stream/references/sources.md +9 -0
  36. package/legion/skills/leading-a-work-stream/references/speed-tick.md +44 -0
  37. package/legion/skills/leading-a-work-stream/references/user-quotes.md +7 -0
  38. package/legion/skills/legion-architect/SKILL.md +86 -0
  39. package/legion/skills/legion-architect/references/acceptance.md +208 -0
  40. package/legion/skills/legion-architect/references/decomposition.md +129 -0
  41. package/legion/skills/legion-architect/references/design-gate.md +206 -0
  42. package/legion/skills/legion-architect/references/merge-notices.md +91 -0
  43. package/legion/skills/legion-architect/references/wake-routing.md +41 -0
  44. package/legion/skills/legion-controller/SKILL.md +151 -0
  45. package/legion/skills/legion-controller/references/admission.md +22 -0
  46. package/legion/skills/legion-controller/references/backlog-stewardship.md +101 -0
  47. package/legion/skills/legion-controller/references/daily-report.md +75 -0
  48. package/legion/skills/legion-controller/references/escalation.md +82 -0
  49. package/legion/skills/legion-controller/references/finding-asks.md +181 -0
  50. package/legion/skills/legion-controller/references/parked-decisions.md +198 -0
  51. package/legion/skills/legion-controller/references/session.md +100 -0
  52. package/legion/skills/legion-controller/references/slack-mentions.md +154 -0
  53. package/legion/skills/legion-controller/references/triage.md +96 -0
  54. package/legion/skills/legion-controller/references/wakes.md +132 -0
  55. package/legion/skills/legion-oracle/SKILL.md +48 -0
  56. package/legion/skills/legion-retro/SKILL.md +112 -0
  57. package/legion/skills/legion-retro/references/learnings.md +60 -0
  58. package/legion/skills/legion-retro/references/pr-body-content.md +56 -0
  59. package/legion/skills/legion-worker/SKILL.md +69 -0
  60. package/legion/skills/legion-worker/references/cleanup-deletion.md +22 -0
  61. package/legion/skills/legion-worker/references/conflicts-and-rewrites.md +164 -0
  62. package/legion/skills/legion-worker/references/default-branch-check.md +53 -0
  63. package/legion/skills/legion-worker/references/findings.md +199 -0
  64. package/legion/skills/legion-worker/references/merge-gate.md +353 -0
  65. package/legion/skills/legion-worker/references/pr-body.md +218 -0
  66. package/legion/skills/legion-worker/references/review-threads.md +77 -0
  67. package/legion/skills/legion-worker/references/systematic-rename.md +19 -0
  68. package/legion/skills/legion-worker/references/tools.md +708 -0
  69. package/legion/skills/thermonuclear-code-quality/LICENSE +21 -0
  70. package/legion/skills/thermonuclear-code-quality/SKILL.md +60 -0
  71. package/legion/skills/thermonuclear-deep-review/LICENSE +21 -0
  72. package/legion/skills/thermonuclear-deep-review/SKILL.md +80 -0
  73. package/package.json +49 -1
  74. package/scripts/build-inputs.txt +84 -0
  75. package/scripts/build.ts +415 -0
  76. package/scripts/check-packed-skills.sh +50 -0
  77. package/scripts/pack-skills.sh +146 -0
  78. package/scripts/smoke-channel.sh +178 -0
  79. package/scripts/smoke-clear-rebind.sh +164 -0
  80. package/scripts/smoke-session-title.sh +245 -0
  81. package/scripts/verify-build.sh +88 -0
  82. package/skills/agent-secrets/SKILL.md +180 -0
  83. package/skills/asd-ste100/LICENSE +21 -0
  84. package/skills/asd-ste100/SKILL.md +140 -0
  85. package/skills/asd-ste100/examples/before-after.md +92 -0
  86. package/skills/asd-ste100/references/writing-rules.md +59 -0
  87. package/skills/dispatch/SKILL.md +89 -0
  88. package/skills/dispatch-first/SKILL.md +46 -0
  89. package/skills/dispatch-writing-specs/SKILL.md +80 -0
  90. package/skills/envoy/SKILL.md +82 -0
  91. package/skills/envoy/references/check-settlement.md +62 -0
  92. package/skills/envoy/references/deliveries.md +67 -0
  93. package/skills/envoy/references/legion.md +14 -0
  94. package/skills/envoy/references/roles.md +12 -0
  95. package/skills/envoy/references/sources.md +41 -0
  96. package/skills/outside-message/SKILL.md +111 -0
  97. package/skills/plain-words/SKILL.md +88 -0
  98. package/src/channel-forwarder.ts +262 -0
  99. package/src/claude-channel.ts +705 -0
  100. package/src/claude-session.ts +38 -0
  101. package/src/envoy-channel-server.ts +2115 -0
  102. package/src/envoy-client.ts +26 -0
  103. package/src/envoy-off.ts +62 -0
  104. package/src/legion-claim.ts +211 -0
  105. package/src/legion-inbox.ts +110 -0
  106. package/src/legion-phase-hook.ts +554 -0
  107. package/src/legion-tool.ts +69 -0
  108. package/src/replay-state.ts +97 -0
  109. package/src/send-arguments.ts +13 -0
  110. package/src/session-identity.ts +165 -0
  111. package/src/session-state.ts +142 -0
  112. package/src/session-title.ts +251 -0
  113. package/src/subscription-topics.ts +16 -0
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: legion-controller
3
+ description: Use when you are a Legion project's controller session, on any controller wake (triage, slot-free, todo, tick, unslotted, awaiting-person, parked-decision, held, worker-died, update-stalled), weekly-plan ranking, backlog stewardship, a clear defect or finding ask a role sends, an issue a person asks you to file, the daily report, an architect's escalation, or a person's message or mention.
4
+ ---
5
+
6
+ # Legion Controller
7
+
8
+ If this skill disagrees with the code, a command's output or its `--help`, follow those and say which line of this skill is wrong.
9
+
10
+ You are the one persistent, wake-driven session of a Legion project. You start no work yourself,
11
+ except a clear defect a role reports ([Issues handed to Legion](#issues-handed-to-legion)).
12
+
13
+ ## Rules
14
+
15
+ - Handle no wake until the extension has registered this session with the daemon as the project's controller, at session startup or, in a controller an operator started, from its first command once Oh My Pi has started, because without that registration the daemon does not know you as its controller.
16
+ - Answer a person's direct message first, in the conversation. A plain user turn that is neither the start message nor a task sent with `legion claims deliver` is a person writing from Dispatch's Agents page.
17
+ - A direct Dispatch message that decides scope, priority or an owner is a ruling: after you answer it, run the weekly-plan reconciliation (`skill://legion-controller/references/backlog-stewardship.md`), because the plan must record the rule.
18
+ - Handle one wake per turn, then end the turn. Never poll or wait for an event, because the daemon wakes you again (`tick` at least every `controller_wake_interval_seconds`).
19
+ - Before any side effect, read `read_state` and the Dispatch issue, because a wake can be stale or sent twice.
20
+ - Read a session's state from the Envoy registry, never from its absence from a list: a session is `live`, `unresponsive` or `gone`. An `unresponsive` session is past the live window and inside the gone horizon; it is working, it keeps its role claim and its issue claim, and messages wait for it. Only a `gone` session is stopped. Start no replacement for an `unresponsive` session, take nothing from it, and never report it as gone.
21
+ - Keep no notes between turns, because a restarted session has only `read_state` and Dispatch.
22
+ - Reach the daemon only through the `legion` tool: `read_state`, and `set_status` to `todo`, `backlog` or `icebox`. Run no `legion` from bash, because the tool holds the controller grant.
23
+ - Never edit a repository, push a commit or call `handoff_complete`, and never forward a raw event to an architect, because the daemon runs the phases and raw events only fill the architect's context.
24
+ - Never open a design gate, because only a person's approval of the root's spec in Dispatch opens it.
25
+ - When a `dispatch` command fails, or a `curl` read of Dispatch fails or prints no JSON array, act on nothing it would decide and name its first line in your summary (the turn's last message), because an empty read is no evidence. A Slack read answers a JSON object, and `skill://legion-controller/references/slack-mentions.md` holds its own success test.
26
+ - Before you say that a service or an API is out of reach, run the call through the secrets broker's client with the secret your instructions name (`skill://agent-secrets`), and report it unreachable only with the command you ran and its output.
27
+ - Write every Dispatch comment, message and ask as `skill://dispatch-writing-specs`'s "Rules for the text" say.
28
+
29
+ Launch, `gh`, pull requests, takeover: `skill://legion-controller/references/session.md`.
30
+
31
+ ## Every turn
32
+
33
+ 1. Recheck [trees waiting on a root claim](#trees-waiting-on-a-root-claim).
34
+ 2. Do the wake's work, or, on a start message, the start procedure below.
35
+ 3. On your first turn of each UTC day, whatever woke you: run the weekly-plan reconciliation
36
+ (`skill://legion-controller/references/backlog-stewardship.md`), then post the
37
+ [daily report](#daily-report).
38
+ 4. Name the [free slots](#free-slots) in your summary, so a person sees room to hand Legion work.
39
+
40
+ ## What happened before you started
41
+
42
+ Every launch opens with a start message (`Legion controller start: …`). Run this procedure on it,
43
+ because Envoy keeps no copy of a wake sent while no controller ran.
44
+
45
+ 1. Read `read_state`. Handle each issue whose `phase` is `held` with `holdReason: escalated`, and
46
+ each root whose `architect.state` is `failed` and whose `phase` is not `done`, as its wake says.
47
+ Name in your summary each root at `phase: done` whose `status` is `in_progress`, `testing`,
48
+ `needs_review` or `retro`: Legion runs it no longer, as the `unslotted` wake reports: someone
49
+ else moved it there while it waited for a slot, or it waited on a person (`awaiting_person`) and
50
+ lost its label or its Dispatch claim, which leaves the `in_progress` it ran with.
51
+ 2. Triage the roots handed to Legion while you were away, as
52
+ `skill://legion-controller/references/triage.md` "At every start" says.
53
+ 3. Catch up on the report issue
54
+ (`skill://legion-controller/references/finding-asks.md#catch-up-at-every-start`).
55
+ 4. Run the parked-root walk (`skill://legion-controller/references/parked-decisions.md#the-walk`),
56
+ because a decision a person made on a parked tree while no controller ran reaches you only there.
57
+
58
+ ## Wakes
59
+
60
+ Wake details: `skill://legion-controller/references/wakes.md`.
61
+
62
+ | Wake | Action |
63
+ |---|---|
64
+ | `triage on <KEY>` | `skill://legion-controller/references/triage.md` |
65
+ | `slot-free on <KEY>`, `todo on <KEY>` | Name the free slots; take nothing |
66
+ | `tick on <PROJECT>` | The every-turn steps, then the parked-root walk (`skill://legion-controller/references/parked-decisions.md#the-walk`) |
67
+ | `unslotted on <KEY>` | Re-admit nothing; name it in your summary |
68
+ | `awaiting-person on <KEY>` | Nothing: do not park or re-admit it, and take nothing for the slot it returned |
69
+ | `parked-decision on <KEY>` | `skill://legion-controller/references/parked-decisions.md#deciding` |
70
+ | `held on <KEY>` | Nothing, unless `reason: "escalated"`: then an escalation |
71
+ | `worker-died on <KEY>` (`role: "architect"`) | Re-admit once its cause is gone, else park it |
72
+ | `update-stalled on <KEY>` | `skill://legion-controller/references/escalation.md#an-update-round-that-is-not-finishing-update-stalled` |
73
+ | An architect's escalation | `skill://legion-controller/references/escalation.md` |
74
+ | A finding ask's reference, or its answer | `skill://legion-controller/references/finding-asks.md` |
75
+ | A clear defect a role sends | `skill://legion-controller/references/finding-asks.md#hand-over-a-clear-defect` |
76
+ | A finding sent as text | Open an ask ([Findings](#findings-and-the-issues-you-file)) |
77
+ | READY, a scope question, a routing request | `skill://legion-controller/references/backlog-stewardship.md` |
78
+ | A GitHub mention | Answer it, or route it to the owning architect |
79
+ | A Slack mention | `skill://legion-controller/references/slack-mentions.md` |
80
+
81
+ ## Trees waiting on a root claim
82
+
83
+ A root architect whose claim on its root was refused waits, holding its slot, and nothing tells it
84
+ when the holder lets go. `dispatch read` each root in `admission.active` whose `phase` is still
85
+ `admitted`. When `Claimed by:` is `nobody` or ends `· not running`, `envoy_publish` `<KEY>: the
86
+ claim is free; claim it again.` to `notifications.role.<issues.<KEY>.architect.locator.claim>`.
87
+
88
+ ## Issues handed to Legion
89
+
90
+ The daemon works only issues carrying the Dispatch label `legion` (any case). Work reaches Legion
91
+ by a person's decision: they set the label from the issue header, or ask you to (a direct
92
+ message, a Slack request that asks Legion to do the work, their answer to a finding ask, or a
93
+ ruling the weekly plan records with its link). The one exception is a clear defect a role hands
94
+ you whose code no running tree owns: you file it with the label, because its fix is how to do work
95
+ a person already wants, and humans decide what, agents decide how (the operator's answer on one
96
+ such case: a clear defect is fixed, not asked about). A defect whose code a running
97
+ tree owns is that tree's, and you file nothing for it. Label an issue only then, because
98
+ a labelled root in `todo` is admitted, so an unasked label starts unasked work. Never take a
99
+ person's label off, because it is their decision. Label an issue before you `set_status` it to
100
+ `todo`, because `todo` admits only a labelled root. How:
101
+ `skill://legion-controller/references/triage.md` "Label an issue for a person". Admission states:
102
+ `skill://legion-controller/references/admission.md`.
103
+
104
+ Two handovers are queued by the daemon, with no write of yours. A person's `legion` label on an
105
+ unrecorded root in `triage` is queued by the daemon: it writes the root's `todo` through its
106
+ outbox. A person's approval of the primary document of a parked (`backlog` or `icebox`) labelled
107
+ root with no live tree is queued by the daemon the same way. You write `todo` only for the roots
108
+ you label at a person's request or file yourself.
109
+
110
+ ## Free slots
111
+
112
+ A free slot is no reason to take an issue: a labelled root in `todo` takes the next slot through
113
+ `admission.waiting` without you, and a parked issue (status `backlog` or `icebox`) means "do not
114
+ run now". Move an issue out of `backlog` or `icebox` only on a request or a plan ruling that names
115
+ it, and cite that request. The free slots are `admission.cap` minus the roots in
116
+ `admission.active` and the roots in `admission.waiting` that are not in `admission.blocked`, since
117
+ a blocked root takes no slot until its blockers are done. Every move to `todo` you make is one of
118
+ these:
119
+
120
+ - a root you labelled at a person's request or filed for a clear defect no tree owns, at triage (a
121
+ root a person labelled is queued by the daemon);
122
+ - an issue a person asks you to start;
123
+ - a tree you re-admit after its architect failed or after a hold, once you checked its cause is
124
+ gone, or because a person asks;
125
+ - a tree you parked for a hold (`Parked by Legion while it waits on:`), once a person's decision
126
+ gives it a next step (`skill://legion-controller/references/parked-decisions.md`);
127
+ - stopped-session work that a person's ruling in the weekly plan moves to Legion.
128
+
129
+ ## Findings, and the issues you file
130
+
131
+ File an issue only when a person asks you to (a direct message, a Slack request, or their answer
132
+ to a finding ask), for a clear defect no running tree owns, and the report issue. A clear defect,
133
+ yours or a role's, goes first to the tree that owns its code, and becomes a Legion root only when
134
+ no tree does: `skill://legion-controller/references/finding-asks.md#hand-over-a-clear-defect`.
135
+ Every other problem is a finding: one you find, or one a role sends you as text. Open a
136
+ `dispatch ask` for it on the report issue, as `skill://legion-worker/references/findings.md` says,
137
+ and file nothing until a person answers, because a person decides whether it needs an issue and
138
+ where. Each issue you file carries a `Filed for` line:
139
+ `skill://legion-controller/references/finding-asks.md`.
140
+
141
+ ## Daily report
142
+
143
+ On your first turn of each UTC day, post what Legion finished, what it closed without a change
144
+ and why, and what runs: `skill://legion-controller/references/daily-report.md`.
145
+
146
+ ## Deployment instructions
147
+
148
+ The operator's deployment instructions in your system prompt (required checks, deploy commands,
149
+ code owners, roles you may consult) override this skill where the two conflict, and may narrow
150
+ what you label for Legion. They never hand Legion work: a person does, or a role reports a clear
151
+ defect.
@@ -0,0 +1,22 @@
1
+ # Reading admission
2
+
3
+ `read_state` says where each issue Legion records stands, and its `phase` alone does not: an issue
4
+ is `admitted` from the moment the daemon records it until its first phase starts, whether it runs,
5
+ waits, or neither. Read its key against `admission` and its tree's root.
6
+
7
+ | What `read_state` shows | What it means | What you do |
8
+ |---|---|---|
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) |
10
+ | The key is in `admission.waiting` and not in `admission.blocked` | A root handed to Legion in `todo`, queued for the next free slot: a root a person answered while it waited in `awaiting_person` comes first, oldest answer first, then every root that never ran, in Dispatch rank order. A root that left another tree (handed over by its finished tree, or admitted as an orphan) waits here as its own root even under a live tree, the old root's return included: no tree adopts it | Nothing: it takes the next free slot by itself |
11
+ | `phase: awaiting_person`, `waitingOn` | A root that already ran and now waits only on a person: an answer to its architect's question (`waitingOn.kind` `ask`) or a review of its spec (`gate`). It holds no slot, its architect stays live to read the person's comments and replies, and its tree stays live. Unanswered, it is in neither list; `waitingOn.answeredAt` means the daemon has promoted it to the front of `admission.waiting` | Nothing: it is not something to admit, label or park, and the slot it returned is no reason to take an issue ([Free slots](skill://legion-controller/SKILL.md#free-slots)). The daemon delivers the answer task to its architect once the root takes the next free slot. If its label goes or a person or outside session claims it on Dispatch, the daemon sends `unslotted` and does not resume work under another owner |
12
+ | The key is in `admission.waiting` and in `admission.blocked` | A root in the line that waits on the Dispatch issues `admission.blocked` names: admission passes it over, and it takes no slot, until every one of them is `done` | Nothing: the wait ends when each blocker reaches `done` (a person's close, or the blocker's own merge) or its link is taken off (by a person or the tree's architect). Name the wait in the day's report |
13
+ | `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 root whose `phase` is `done` it waits with it. A labelled `todo` issue here is not in the line either: one whose recorded parent is in a live tree runs in that tree (the daemon adopted it there), or is held with that parent while the parent itself is held under an unreleased child, and one whose parent is not recorded but sits under a live tree is held under an unreleased child of that tree until its architect releases it or its parent. That hold is not `admission.blocked`, which names only blocked-by links | 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 and no live tree is above it |
14
+ | `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 |
15
+ | `phase: done` while `status` is `in_progress`, `testing`, `needs_review` or `retro` | Legion is not running it under that status, and it sent the `unslotted` wake. Either someone other than Legion moved a root that waited for a slot there, and the daemon took it out of the workflow at that move since no slot would ever reach it; or a root waiting on a person could not be resumed — its `legion` label is gone, or a person or an outside session took its Dispatch claim — and the daemon took it out the same way, leaving the `in_progress` it ran with. A parked child stays parked. Legion starts nothing for any of them | Re-admit nothing yourself: `dispatch read` shows who works it now. Name it in your summary; `todo` with the `legion` label queues it again |
16
+
17
+ A root in the workflow with no slot that is in neither `admission.waiting`, nor the label-removed
18
+ row above, nor `todo` under a live tree does not stay that way: every pass of admission (a Dispatch
19
+ event, the daemon's boot read, its tick) takes it out of the workflow and sends the `unslotted`
20
+ wake — an unanswered `awaiting_person` root only when its label or its Dispatch claim makes a
21
+ resume unsafe. The wake is live only, so a controller that starts later finds such a root by the
22
+ last row, as its start procedure does.
@@ -0,0 +1,101 @@
1
+ # Backlog stewardship
2
+
3
+ The controller keeps the Dispatch backlog's facts aligned with the deployment's current weekly
4
+ plan. The plan is the source of the work order and its one-owner map; Dispatch board rank is the
5
+ executable form of that order. The deployment instructions name the weekly-planning issue and its
6
+ `plan-week-*` artifact.
7
+
8
+ ## Controller routing address
9
+
10
+ Claim no Envoy role other than `legion-<project>-controller`, your startup role, because changing
11
+ roles ends the controller's wake delivery. READY packets, scope questions and routing requests
12
+ reach you on `notifications.role.legion-<project>-controller`.
13
+
14
+ ## Reconcile the weekly plan
15
+
16
+ Run this pass on the first turn of every UTC day and after every ruling from a person:
17
+
18
+ 1. Read the weekly-planning issue and select its newest `plan-week-*` artifact by its latest
19
+ `artifact.version` event. Read that artifact before changing any issue.
20
+ 2. For every plan line whose issue key carries this project's prefix (`daemon.project` in
21
+ `read_state`), make the project's board order match the line order exactly, because Legion
22
+ admits waiting roots by board rank alone. Set the priority the plan line states, and leave the
23
+ priority where it states none; priority changes no admission order. When one plan line lists
24
+ several issues, keep them in that order. Take the order from the plan alone, never from a
25
+ title or a route. Reorder with
26
+ `dispatch issue-update --issue <KEY> --before <OTHER>` or `--after <OTHER>`.
27
+ 3. Reconcile the plan's one-owner map. `envoy_sessions` gives each session a `state` (`live`,
28
+ `unresponsive` or `gone`) and a `last_seen`; read the state, never the presence or absence of
29
+ the row. A `live` or `unresponsive` owner still owns its work: change nothing for it. Only an
30
+ owner that reads `gone`, or that the registry has no record of, is without a session. For
31
+ unowned work, find an owner whose `envoy_sessions` `dir` is a
32
+ checkout of the issue's repository, route the issue to it
33
+ (`dispatch issue-update --issue <KEY> --route session:<session id>`) and `envoy_send` it
34
+ `You own <KEY>: <title>.`. Routing starts nothing on Legion and sets no label. Never route an
35
+ issue that carries the `legion` label, because Legion's own roles work it. Do not change the
36
+ human `Assignee:` field: it decides who receives asks, not who works the issue.
37
+ 4. Edit that issue's line in the plan artifact (its owner column) with `dispatch doc-edit`,
38
+ guarded by the document token from the current `dispatch doc-read`. Replace superseded text
39
+ instead of keeping a change log in it, because the artifact states the current rule. A comment
40
+ or message does not update the one-owner map.
41
+
42
+ A ruling from a person is a direct Dispatch message to this controller. Answer it first, carry out
43
+ its scope or priority decision, then record the resulting current rule in the affected weekly-plan
44
+ row (or its ruling section when it has no one line). Link the message when Dispatch gave it a
45
+ reference.
46
+ A ruling triggers the full reconciliation pass even when it appears to affect only one issue.
47
+
48
+ ## Answering controller deliveries
49
+
50
+ Read every delivery to `notifications.role.legion-<project>-controller` against live Dispatch and
51
+ daemon state. You decide labels, the statuses `todo`, `backlog` and `icebox`, and board order.
52
+ Answer a scope question from the weekly plan and the deployment instructions. Anything else goes
53
+ with `envoy_send` to the session the plan's one-owner map names for that issue.
54
+
55
+ For a READY packet, confirm the Dispatch record and leave the merge to the daemon. Never merge or
56
+ use a GitHub credential to merge from the controller, because the daemon submits the merge itself
57
+ at READY's head.
58
+
59
+ ## The owner audit
60
+
61
+ At each pass, read `dispatch issues --project <KEY> --priority 0 --priority 1 --all`:
62
+
63
+ - An unclaimed row in `triage`, `icebox`, `backlog` or `todo` is a decision: someone builds it, or
64
+ it closes. A claimed or in-flight row already has an owner.
65
+ - A close that says a defect cannot happen cites the code that prevents it; without that, the issue
66
+ is unread, not foreclosed.
67
+ - A plan or measurement nobody builds needs an owner.
68
+ - A merged pull request names an issue not yet in `testing` or `done`: check it live, then move it.
69
+ - Someone repeats the issue's work by hand: fix its status.
70
+ - A route nobody holds (`--route-status no_holder`, any priority), after the two reads that
71
+ "Recovering stopped-session work" names: staff the role, route the issue to a live holder, or
72
+ clear the route and assign it.
73
+
74
+ ## Recovering stopped-session work
75
+
76
+ Treat a session route as stopped only after two reads of
77
+ `dispatch issues --project <KEY> --route-status no_holder --all` at least ten minutes apart
78
+ both list it, as `dispatch issues --help` says for `route_status`, because a
79
+ restarting session is absent for minutes. Do not poll to create the second read; use the next
80
+ controller wake. Then read the owner's state with `envoy_sessions` (the session the route names,
81
+ or one that took it over). A `live` owner still holds the work. So does an `unresponsive` one,
82
+ which is past the live window but inside the gone horizon: it keeps its claims, and a message you
83
+ send it waits for it. Recover the work only from an owner that reads `gone`, or that reads
84
+ `unresponsive` with a `last_seen` more than 20 minutes old, which is as long as an issue claim is
85
+ protected.
86
+
87
+ For work that a ruling of a person's, recorded in the plan with its link (the ruling section
88
+ above), says moves to Legion when its session stops, preserve its existing labels, add `legion`,
89
+ then set it `todo` with the `legion` tool's `set_status`. A plan row you wrote with no such ruling
90
+ behind it moves nothing. The label comes first: it is what
91
+ lets the daemon admit the root. Do not apply that recovery to an issue already in an active Legion
92
+ tree; its architect and daemon own its lifecycle. Update the weekly-plan owner row after the move.
93
+
94
+ ## A root the plan defers
95
+
96
+ A person's `legion` label outranks a plan's deferral. The daemon queues the root at once: a
97
+ person's label on an unrecorded root in `triage` is the root's `todo`, written by the daemon. A
98
+ labelled root the weekly plan says to defer is parked by you at your next backlog pass, not
99
+ before: `set_status` it to `backlog`, or to `icebox` for a longer deferral, and cite the plan's
100
+ ruling in a comment on the issue. The cost is the architect's work between the daemon's `todo`
101
+ and your park.
@@ -0,0 +1,75 @@
1
+ # Daily report
2
+
3
+ Read this on your first turn of each UTC day, whatever woke you, and when you open, follow or
4
+ answer a finding ask: the report issue holds them all.
5
+
6
+ ## Find the report issue
7
+
8
+ Use the issue the deployment instructions name for Legion's reports. With none named, use the
9
+ project's `Legion daily report` issue:
10
+ `dispatch search --query '"Legion daily report"' --project <PROJECT>`. When it does not exist,
11
+ create it once (the one issue you file unasked) and park it in `icebox`, so nobody takes it as
12
+ work. It never carries the `legion` label. Assign it to the person who answers for the project:
13
+ the one the deployment instructions name, else the owner `dispatch whoami` names, because its asks
14
+ reach that person's Inbox.
15
+
16
+ ```text
17
+ dispatch issue --project <PROJECT> --title 'Legion daily report' --assignee '<email>' --spec-file - <<'EOF'
18
+ ## Summary
19
+
20
+ 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 post here the evidence of each clear defect they found outside their own work, which the controller hands to the tree that owns the code, or files for Legion when no tree does. They ask here whether a problem that needs a decision needs an issue. This issue is not work, so it carries no `legion` label and stays in icebox.
21
+ EOF
22
+ legion({ op: "set_status", issue: "<report KEY>", status: "icebox" })
23
+ ```
24
+
25
+ With no person known, leave out `--assignee`: the issue then goes to your token's owner, or, under
26
+ the shared token, to the project's default assignee when a person set one, and is otherwise
27
+ unassigned.
28
+
29
+ ## Keep it answerable
30
+
31
+ Each time you read it:
32
+
33
+ - `Status: done` means someone closed it: the close withdrew its open asks (each recorded
34
+ `withdrawn`; a reopen brings them back), and Dispatch refuses an ask or a message there
35
+ (`409 ISSUE_CLOSED`). Reopen it with `set_status` to `icebox` and say so in the day's report.
36
+ - `Assignee: unassigned` with a person known: assign it, naming your session (`dispatch whoami`):
37
+ `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>"`.
38
+ An answer that does not print the issue with its new `assignee` left the assignee as it was
39
+ (`400 ASSIGNEE_NOT_ALLOWED` names an email nobody has signed in to Dispatch with): name it, with
40
+ its first line, in your summary.
41
+ - While `Assignee:` says `unassigned`, open each report with one sentence asking a person to take
42
+ the issue from its header, because its asks reach only the Inbox's `Unassigned` filter.
43
+
44
+ ## When
45
+
46
+ After the turn's own work, read the report issue with `dispatch read`, keep it answerable, and
47
+ post when its `Events:` show no `message.created` from today.
48
+
49
+ ## What
50
+
51
+ One `dispatch message --issue <report KEY> --body-file -` of at most 2,000 characters, because a
52
+ person reads every report in their Inbox. Write it as `skill://dispatch-writing-specs`'s "Rules for the text" say:
53
+ every issue by its `dispatch://` link and what it does for the reader, every pull request by its URL.
54
+
55
+ - **Finished.** `dispatch issues --project <PROJECT> --status done --updated-since <the previous
56
+ report's time, or 24 hours ago> --all`, keeping the issues `read_state` records
57
+ under `issues` whose `issue.closed` event is after the previous report. `dispatch read` each: the
58
+ pull request under `External links:` is the one that merged.
59
+ - **Closed without a change.** Those with no pull request, each with the reason its closing
60
+ message gave (the `message.created` just before `issue.closed` among `Events:`).
61
+ - **Running.** Each root in `admission.active` with its `issues.<KEY>.phase`, and the roots in
62
+ `admission.waiting`. Name as waiting on a claim each root that this turn's "Trees waiting on a
63
+ root claim" check found, with the holder its `Claimed by:` line names.
64
+ - **Waiting on Dispatch links.** Each root and child in `admission.blocked`, with the issues it
65
+ waits on. The wait ends when each blocker reaches `done` (a person's close, or the blocker's own
66
+ merge) or its link is taken off (by a person or the tree's architect), so take nothing for them.
67
+ - **Free slots.** Counted as the skill's "Free slots" says; each stays free until a root in `todo`
68
+ with the `legion` label takes it.
69
+ - **Open capabilities.** Each row of `read_state`'s `capabilities` whose `status` is `open`: its
70
+ name, its `detail`, and a deployment row's `configLine`. Say who acts on it, as
71
+ `skill://legion-controller/references/wakes.md` says for `tick`. With none open, say nothing of
72
+ them.
73
+
74
+ A day with nothing finished says so in one sentence. When the lists do not fit, give each list's
75
+ count and name its issues of `--priority 0` and `--priority 1`, in board rank order.
@@ -0,0 +1,82 @@
1
+ # Architect escalation
2
+
3
+ Read this when a tree's architect sends you a matter, when a `held` wake carries
4
+ `reason: "escalated"`, or when an `update-stalled` wake says the daemon cannot replace an issue's
5
+ pod. Three matters come to you from an architect: a child that looks independent of its tree,
6
+ capacity (an architect says its tree cannot run what it needs under the slots it has), and a
7
+ conflict between trees. An issue-design decision is not one of them: the owning architect writes
8
+ it as a decision block and opens its own finding asks. Send a design decision back to that
9
+ architect with `envoy_publish` to its role, because it is not yours to make.
10
+
11
+ For capacity, the only lever is order. You cannot change `admission.cap`: it is the operator's
12
+ `legion.yaml` setting, and `set_status` is your only admission move. Read `admission.active` and
13
+ `admission.waiting`, tell the architect its root's place in the line with `envoy_publish`, and
14
+ open a finding ask when the deployment needs a larger cap.
15
+
16
+ For a conflict between trees, read both roots in `read_state` and Dispatch, publish what you read
17
+ to both architects' roles, and open a finding ask when the two need a person's decision.
18
+
19
+ ## A child that looks independent
20
+
21
+ Check the child and its parent against `read_state` and Dispatch. When it still looks independent,
22
+ open an ask on your report issue, as the skill's "Findings, and the issues you file" says. The ask
23
+ names the child, why it looks independent and the root you would file, with three options:
24
+ `File an issue` (a root a person takes), `File it for Legion` (a root Legion runs) and
25
+ `Do not file` (it stays in its tree). Never add the `legion` label to a child or cut its
26
+ `child_of` link, because that admits it as a root while its tree still records it. Only on a
27
+ person's yes:
28
+
29
+ 1. File the root with no `--parent`:
30
+ `dispatch issue --project <PROJECT> --title '<title>' --spec-file -`. Its spec cites the ask and
31
+ carries its `Filed for` line (`skill://legion-controller/references/finding-asks.md`). Add
32
+ `--label legion` only when the answer asks Legion to run it. `<PROJECT>` is the issue key's
33
+ prefix before `-<n>` (`ACME-3` → `ACME`), which `read_state` shows as `daemon.project`; it is
34
+ not the lowercase project token in role names such as `legion-<project>-controller`.
35
+ 2. Park the child (`set_status` to `icebox`) and comment a pointer to the new root on it. You
36
+ cannot close it, because only its architect or the daemon sets `done`.
37
+ 3. A root filed with the label opens in `triage`, and its triage wake brings it back to you
38
+ (`skill://legion-controller/references/triage.md`).
39
+
40
+ ## An update round that is not finishing (`update-stalled`)
41
+
42
+ `{kind: "update-stalled", reason, update: {round, pod?, reasons, waiting}}`, on the issue whose
43
+ pod it is. The daemon must replace the pod that runs an issue's roles, because the configuration
44
+ the pod was built from has moved. It never replaces a live pod where it stands: every role of the
45
+ issue works on that pod's disk, so the daemon opens an update round instead and tasks each worker
46
+ in the pod to push its work and answer `ready_for_update` first. This wake says that round is not
47
+ finishing.
48
+
49
+ It names the round (`round`: `pod:<pod uid>` for the whole pod, `plugin:<daemon contract>` for
50
+ one claim's own process), the pod it was opened over (`pod`, absent for a claim's own plugin
51
+ round), the drift the task states (`reasons`), the one line that says why it is stuck (`reason`),
52
+ and every claim the round is blocked on (`waiting`, each with its `claim` token, `role`, `state`
53
+ and `answered`). Every worker is asked alike. The end of the turn the daemon's task started is
54
+ that worker's answer at every plugin contract, so a worker whose `legion` tool has no
55
+ `ready_for_update` operation needs nothing of you: a round over one finishes by itself, and no
56
+ drain is asked of anybody. A `failed` claim is never in a pod round's `waiting`: it runs no turn
57
+ on the pod, so the round does not wait for it. Its retry is the architect's or the operator's as
58
+ for any failed role, and a retried claim is asked like any other.
59
+
60
+ Before it replaces the pod, the daemon checks every workspace on the pod's disk for work the
61
+ remote lacks, a failed claim's included, and pushes what it finds to a rescue branch of its own,
62
+ `legion/<KEY>-rescue-<pod>-<n>`. When it pushed one, it writes the issue a message naming each
63
+ rescue branch and its commit, for the issue's roles to merge into the issue branch; that message
64
+ asks nothing of you. When the check or a push fails for any workspace, the daemon does not replace
65
+ the pod, and this wake's `reason` says that the check for unpushed work failed and names the claim
66
+ the check ran through, each workspace and its error. Everything else of the pod's disk goes with
67
+ the pod: the round's task tells each worker to put any scratch evidence it still needs on Dispatch
68
+ before it answers.
69
+ One round wakes you once, however many sweeps find it open, so handle it when it arrives.
70
+ Read `read_state` and the issue first, as for every wake: a round that finished since needs
71
+ nothing.
72
+
73
+ Take each `waiting` entry by its shape:
74
+
75
+ | Entry | What it is | What you do |
76
+ |---|---|---|
77
+ | `answered: true` | It has already answered this round. The round is blocked on the daemon's own side: a turn that has not ended, a check for unpushed work that fails, or a pod replacement the cluster keeps refusing. `reason` names which. | Send it nothing. A message to it starts a turn. How the worker answered decides what that turn does: it takes back an answer the worker gave with the `ready_for_update` call, which puts the round further from finishing, and it leaves standing an answer the end of the task's own turn gave. Open a finding ask naming the issue, the round and `reason`, with the turn, the workspace and its push error, or the `ReplacePod` error it names, so an operator reads the pod. |
78
+ | `state: "suspended"` | An operator's own suspension holds it: the daemon revives a suspension of its own for the task. The claim already holds the task and takes it on the resume. | Open a finding ask naming the issue and the role, so the operator resumes it. |
79
+ | any other state | Its worker holds the task and has not answered. The daemon asks it again every `pod_update_retask_hours`. | `envoy_publish` to `notifications.role.<claim>` with the issue and the update task's three steps (push every commit to keep to the issue branch and put any scratch evidence still needed on Dispatch, call the `legion` tool with `op: "ready_for_update"` and the `round` this notice names, which is the id the worker's own task carries, end the turn), and say in the same message that a worker whose `legion` tool has no `ready_for_update` operation does step 1 and step 3 alone. Name it in your summary. Your publish starts a turn of its own, which answers nothing: only the end of the turn the daemon's own task started is that worker's answer, so what your message buys is the next ask. |
80
+
81
+ Park nothing and write no status for this wake. The issue's workflow runs on while the round
82
+ waits, and replacing the pod is the daemon's own move once every worker has answered.
@@ -0,0 +1,181 @@
1
+ # Finding asks
2
+
3
+ Read this when a clear defect or a finding ask's reference reaches you on the controller topic,
4
+ when one you follow is answered, when you file an issue a person asked for, and at every start.
5
+ What a finding ask is and where it goes is `skill://legion-worker/references/findings.md`; what
6
+ you may file is the skill's "Findings, and the issues you file" section.
7
+
8
+ ## The `Filed for` line
9
+
10
+ Every issue you file for a person carries one line in its spec, `Filed for <person> at their
11
+ request: <request>`, because triage reads it to tell your filing from anyone else's label
12
+ (`skill://legion-controller/references/triage.md`). The request, and the person it names, are:
13
+
14
+ - an answer to an ask: `dispatch://<REPORT KEY>/ask/<id>`, for the person who answered it;
15
+ - a Slack request: `Slack <channel_id> message <ts>`, for the mention's `<@user_id>`. When the
16
+ mention adopts an earlier message's request by asking for it
17
+ (`skill://legion-controller/references/slack-mentions.md`), the request reads
18
+ `Slack <channel_id> message <mention ts>, adopting message <earlier ts>`, and the person it names
19
+ is still the mention's `<@user_id>`;
20
+ - a direct message: `direct message <id>`, with the id its frame's `reply_with` names after
21
+ `--in-reply-to`, for the frame's `actor`. A message that arrives as your own user turn shows no
22
+ id: write `direct message to session <your session id>`, for the person by the name they gave,
23
+ else `the person who messaged this session`.
24
+
25
+ For a clear defect no running tree owns, the line is `Filed for Legion from <role> on <issue>:
26
+ <evidence>` ([Hand over a clear defect](#hand-over-a-clear-defect)).
27
+
28
+ ## Hand over a clear defect
29
+
30
+ A role sends a clear defect on the controller topic: its role token, the defect, the issue it came
31
+ from, where the fix belongs, the fix, and its evidence comment on the report issue, whose first line
32
+ is `Clear defect from <role>`. When the report issue was missing or closed, the message carries the
33
+ evidence: create or reopen the report issue (`skill://legion-controller/references/daily-report.md`)
34
+ and post it there with that first line. A clear defect you find yourself gets the same comment, with
35
+ `controller` as the role. A clear-defect ask (`findings.md`, "When no controller takes it") needs
36
+ no answer: take it as below, the ask its evidence. Resolve it (`dispatch resolve-ask --kind
37
+ resolved`) only once it carries your reply: `Handed` or `Filed` from step 2, 3 or 5, or, when
38
+ step 1 finds it is not clear, `Moved dispatch://<REPORT KEY>/ask/<new id>` naming the decision ask
39
+ you open. With no reply, leave it open: catch-up step 2 takes it again at your next start.
40
+
41
+ The work goes to whoever already owns that code. You file an issue only when nobody does.
42
+
43
+ 1. Check that it is clear: the fix needs no choice a person must make, and it belongs in a
44
+ repository your addressing line lists. Otherwise open an ask for it, as `findings.md` says.
45
+ 2. Search first (`skill://dispatch-first`). When an open issue tracks it, comment the evidence
46
+ there, reply `Filed dispatch://<that KEY>` under the evidence, and file nothing.
47
+ 3. Find the running tree whose code it is: a root in `admission.active` whose repository and
48
+ component match, read from `read_state` and `dispatch read`. When one matches, hand it over.
49
+ `envoy_publish` the defect, its evidence reference and the fix to that tree's architect topic,
50
+ `notifications.role.<issues.<KEY>.architect.locator.claim>`. The architect decides whether it
51
+ is a child of the tree or part of an open issue's work. Reply `Handed dispatch://<that KEY>`
52
+ under the evidence only when a live holder took the publish. When the root has no locator, or
53
+ the listener kept the publish for the role's next holder, reply nothing: no architect reads a
54
+ comment on its root, so the evidence stays unhandled, and catch-up step 4 lists it again at
55
+ your next start. Publish it again at your next wake. Once the root leaves `admission.active`,
56
+ no running tree owns the code, and step 4 files it. File nothing while a tree owns it: a
57
+ second issue splits the work.
58
+ 4. No running tree owns that code: file it on the repository where the fix belongs, in the
59
+ project that repository maps to:
60
+
61
+ ```text
62
+ dispatch issue --project <KEY> --repo <owner/name> --title '<the defect, as a title>' --label legion --spec-file -
63
+ ```
64
+
65
+ Without `--repo`, Dispatch records the project's default repository, and the daemon admits the
66
+ issue there. The spec states the defect, where the fix belongs and the fix, and ends with the
67
+ `Filed for Legion from` line: the sending role's token (or `controller`), the `dispatch://<KEY>`
68
+ it came from, and the evidence comment's or the ask's reference. It opens in `triage` with the
69
+ label, and triage admits it (`skill://legion-controller/references/triage.md`).
70
+ 5. Reply under the evidence comment (`--reply-to '<comment id>'`) or the ask
71
+ (`--reply-to-ask '<ask id>'`, on the ask's issue), its first line exactly
72
+ `Filed dispatch://<NEW KEY>` or `Handed dispatch://<KEY>`. A later session reads that reply at
73
+ triage and at every start.
74
+
75
+ ## Follow it
76
+
77
+ A finding ask another role opened reaches you as its reference, `dispatch://<KEY>/ask/<id>`, on
78
+ the controller topic. Follow it, so that its answer reaches you:
79
+
80
+ ```text
81
+ dispatch follow --ask 'dispatch://<KEY>/ask/<id>' --action follow
82
+ ```
83
+
84
+ When `dispatch read --ref` shows it answered already, act on its answer now.
85
+
86
+ ## Act on its answer
87
+
88
+ Once it is answered (an ask you follow brings its answer to you), read it with
89
+ `dispatch read --ref 'dispatch://<KEY>/ask/<id>'` and act on the newest answer. When its typed
90
+ text asks for a different one of the four actions than the option selected, do what the text
91
+ asks; when the text asks for something that is none of the four, reply under the ask with your
92
+ question and file nothing:
93
+
94
+ - `Hand it to the owning tree`: take it as [Hand over a clear defect](#hand-over-a-clear-defect)
95
+ says, from its step 3.
96
+ - `Do not file`: file nothing.
97
+ - `File an issue`: `dispatch issue --project <PROJECT> --title '<the problem, as a title>'
98
+ --spec-file -` in the project where the fix belongs, as the answer or the ask names it; when
99
+ neither names one, in this project. The spec
100
+ states the problem and where the fix belongs, cites the ask and its evidence by their
101
+ `dispatch://` references, and carries the line `Filed for <the answer's By:> at their request:
102
+ dispatch://<REPORT KEY>/ask/<id>` (above). No `legion` label.
103
+ - `File it for Legion`: the same, with `--label legion`. It opens in `triage` with the label, so
104
+ its triage wake brings it back to you (`skill://legion-controller/references/triage.md`).
105
+
106
+ An ask that re-files an escalated child also takes the steps of
107
+ `skill://legion-controller/references/escalation.md`.
108
+
109
+ Then reply under the ask with what you did, its first line exactly `Filed dispatch://<NEW KEY>`,
110
+ `Handed dispatch://<KEY>` or
111
+ `Not filed: <reason>`. The ask's issue is the report issue, or the role's own issue for an ask no
112
+ controller took:
113
+
114
+ ```text
115
+ dispatch comment --issue <the ask's issue> --reply-to-ask '<ask id>' --body-file -
116
+ ```
117
+
118
+ Dispatch refuses to resolve an answered ask, so that reply is its record. An open finding ask that
119
+ turns moot (an issue now tracks the problem, or the problem is gone) you close with
120
+ `dispatch resolve-ask --kind resolved`, naming why.
121
+
122
+ ## Catch up at every start
123
+
124
+ An answer that arrives while no controller runs never wakes you, a reference published then
125
+ reaches nobody, and a new session follows none of the asks an earlier one followed. So at every
126
+ start, catch up on the report issue (with none yet, there is nothing to catch up), and on the
127
+ finding asks roles opened on their own issues while no controller ran:
128
+
129
+ 1. Read it with `dispatch read`. Reopen it when its `Status:` is `done`, and assign it when its
130
+ `Assignee:` says `unassigned` and a person is known, both as
131
+ `skill://legion-controller/references/daily-report.md` "Keep it answerable" says.
132
+ 2. Take each open ask: `dispatch open-asks --project <PROJECT>` lists every unanswered ask of the
133
+ project, with no cap, each line naming its issue. It leaves out an ask on a closed issue
134
+ (`asks.go`'s `listOpenAsks` takes `i.closed_at is null`), which is why an architect holds its
135
+ issue open for one. Follow those on the report issue, and take each whose question opens
136
+ `Clear defect from ` as [Hand over a clear defect](#hand-over-a-clear-defect) says. One on
137
+ another issue whose question opens `Finding from ` or `Clear defect from ` is a role's
138
+ own-issue ask. Take a clear defect the same way. Move a finding that needs a
139
+ decision: ask it again on the report issue with the same question, options and `--ref`, then
140
+ reply `Moved dispatch://<REPORT KEY>/ask/<new id>` under the old ask. Resolve the old ask once it
141
+ carries a `Moved`, `Handed` or `Filed` reply. A clear defect no holder took stays open, and the
142
+ next start takes it again.
143
+ 3. List the answered asks still waiting on you, which no `dispatch` command lists:
144
+
145
+ ```text
146
+ asks=$(curl -sS --fail-with-body -H "Authorization: Bearer $(cat "$DISPATCH_TOKEN_FILE")" "$DISPATCH_URL/api/v1/issues/<ISSUE KEY>/asks?state=answered&thread=1") || { echo "Dispatch read failed" >&2; exit 1; }
147
+ printf '%s' "$asks" | jq -cn 'input | if type == "array" then [.[] | 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://|Handed dispatch://|Admitted dispatch://|Not filed:|Not admitted:)")))] | length == 0) | {id, question, answer}] else error("Dispatch did not answer the list") end'
148
+ ```
149
+
150
+ Read it for the report issue, then for each issue in `needs_review`, whose architect holds its
151
+ acceptance on a finding ask's answer. Act on each as above, a triage ask as
152
+ `skill://legion-controller/references/triage.md` says. The read lands in `asks` first, because a
153
+ pipeline reports only the status of `jq`. `jq -n 'input …'` fails on an empty body, where a
154
+ plain `jq` would succeed on it, so exit 0 means the list was read: `[]` is nothing waiting.
155
+ Anything else is a failed read and admits nothing.
156
+
157
+ Take the `needs_review` issues as one snapshot before you act on any of them:
158
+ `dispatch issues --project <PROJECT> --status needs_review --all`. `--all` reads every match in
159
+ one request. Never page this list: pages by `offset` skip or repeat an issue whose status
160
+ changes between reads, and your reply moves issues out of `needs_review`. `No issues in
161
+ <PROJECT>.` means nothing waits, and a command that fails admits nothing. An issue that enters
162
+ `needs_review` after the snapshot is read at your next start.
163
+ 4. List the clear defects nobody took yet: each evidence comment whose first line starts
164
+ `Clear defect from `, with no session reply under it that starts `Handed dispatch://`,
165
+ `Filed dispatch://`, `Not filed:` or `Asked dispatch://` (a role that asked instead, which step
166
+ 2 takes):
167
+
168
+ ```text
169
+ notes=$(curl -sS --fail-with-body -H "Authorization: Bearer $(cat "$DISPATCH_TOKEN_FILE")" "$DISPATCH_URL/api/v1/issues/<REPORT KEY>/comments") || { echo "Dispatch read failed" >&2; exit 1; }
170
+ printf '%s' "$notes" | jq -cn 'input | if type == "array" then (. as $all | [.[] | select(.reply_to == null and (.body | startswith("Clear defect from "))) | .id as $id | select([$all[] | select(.reply_to == $id and .author.kind == "session" and (.body | test("^(Handed dispatch://|Filed dispatch://|Not filed:|Asked dispatch://)")))] | length == 0) | {id, body}]) else error("Dispatch did not answer the list") end'
171
+ ```
172
+
173
+ It reads as step 3's read does: exit 0 with `[]` is nothing waiting, anything else failed.
174
+ Take each as [Hand over a clear defect](#hand-over-a-clear-defect) says. One you could not hand
175
+ over at an earlier wake, because no architect held its tree's topic, has no reply, so this list
176
+ brings it back until a tree takes it or step 4 of the hand-over files it. When one is not clear,
177
+ reply `Not filed: <reason>` under it and open an ask for it instead.
178
+
179
+ A read that fails, and an assignment Dispatch refuses, are handled as
180
+ `skill://legion-controller/SKILL.md#rules` and
181
+ `skill://legion-controller/references/daily-report.md` say.