@sjawhar/opencode-legion-envoy 1.14.0 → 1.15.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.
- package/dist/src/server.js +24 -6
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +5 -4
- package/skills/legion-architect/SKILL.md +28 -12
- package/skills/legion-controller/SKILL.md +7 -0
- package/skills/legion-oracle/SKILL.md +6 -5
- package/skills/legion-retro/SKILL.md +8 -0
- package/skills/legion-worker/SKILL.md +39 -3
package/dist/src/server.js
CHANGED
|
@@ -13567,6 +13567,7 @@ var IssueEventPayloadSchema = object({
|
|
|
13567
13567
|
title: string2().optional(),
|
|
13568
13568
|
labels: array(string2()).optional(),
|
|
13569
13569
|
status: string2().optional(),
|
|
13570
|
+
priority: number2().int().min(0).max(3).nullable().optional(),
|
|
13570
13571
|
rank: string2().optional(),
|
|
13571
13572
|
route: string2().nullish()
|
|
13572
13573
|
});
|
|
@@ -13663,7 +13664,9 @@ var ChildStatusEventPayloadSchema = object({
|
|
|
13663
13664
|
var SubscriptionRemovedEventPayloadSchema = object({
|
|
13664
13665
|
session_id: string2().optional(),
|
|
13665
13666
|
by: object({ kind: string2(), id: string2().optional() }).passthrough().optional(),
|
|
13666
|
-
topics: array(string2()).optional()
|
|
13667
|
+
topics: array(string2()).optional(),
|
|
13668
|
+
pending: boolean2().optional(),
|
|
13669
|
+
request_event_id: number2().int().positive().optional()
|
|
13667
13670
|
});
|
|
13668
13671
|
// ../contracts/src/dispatch-snippet.ts
|
|
13669
13672
|
var HTML_ENTITIES = [
|
|
@@ -13760,7 +13763,8 @@ var dispatchToolSpecs = [
|
|
|
13760
13763
|
external: z.string().describe("Optional external issue reference.").optional(),
|
|
13761
13764
|
force: z.boolean().describe("Create even though POSSIBLE_DUPLICATE listed similar issues; pass it only after reading them.").optional(),
|
|
13762
13765
|
spec: z.string().describe(`Optional initial primary-document markdown. ${SPEC_WRITING_GUIDANCE}`).optional(),
|
|
13763
|
-
labels: z.array(z.string({ min: 1, max: 40 }), { max: 20 }).describe("Optional initial labels, at most 20 labels of up to 40 characters.").optional()
|
|
13766
|
+
labels: z.array(z.string({ min: 1, max: 40 }), { max: 20 }).describe("Optional initial labels, at most 20 labels of up to 40 characters.").optional(),
|
|
13767
|
+
priority: z.number({ int: true, min: 0, max: 3 }).describe("Optional coarse priority: P0 is highest and P3 is lowest.").optional()
|
|
13764
13768
|
})
|
|
13765
13769
|
},
|
|
13766
13770
|
{
|
|
@@ -14616,6 +14620,11 @@ function asErrorShape(value) {
|
|
|
14616
14620
|
function isJson(response) {
|
|
14617
14621
|
return response.headers.get("content-type")?.includes("application/json") ?? false;
|
|
14618
14622
|
}
|
|
14623
|
+
var DISPATCH_TOOL_DEADLINE_MS = 60000;
|
|
14624
|
+
function requestSignal(signal) {
|
|
14625
|
+
const deadline = AbortSignal.timeout(DISPATCH_TOOL_DEADLINE_MS);
|
|
14626
|
+
return signal === undefined ? deadline : AbortSignal.any([signal, deadline]);
|
|
14627
|
+
}
|
|
14619
14628
|
|
|
14620
14629
|
class DispatchClient {
|
|
14621
14630
|
token;
|
|
@@ -14623,10 +14632,12 @@ class DispatchClient {
|
|
|
14623
14632
|
#baseUrl;
|
|
14624
14633
|
#resolvedIssues = new Map;
|
|
14625
14634
|
#creatingIssues = new Map;
|
|
14626
|
-
|
|
14635
|
+
#signal;
|
|
14636
|
+
constructor(baseUrl, token, fetchImpl = fetch, signal) {
|
|
14627
14637
|
this.token = token;
|
|
14628
14638
|
this.fetchImpl = fetchImpl;
|
|
14629
14639
|
this.#baseUrl = baseUrl.replace(/\/+$/, "");
|
|
14640
|
+
this.#signal = requestSignal(signal);
|
|
14630
14641
|
}
|
|
14631
14642
|
async issue(input) {
|
|
14632
14643
|
return this.#json("POST", ["api", "v1", "issues"], input);
|
|
@@ -14838,6 +14849,7 @@ class DispatchClient {
|
|
|
14838
14849
|
const response = await this.fetchImpl(this.#url(path, query), {
|
|
14839
14850
|
method,
|
|
14840
14851
|
headers,
|
|
14852
|
+
signal: this.#signal,
|
|
14841
14853
|
...body === undefined ? {} : { body: JSON.stringify(body) }
|
|
14842
14854
|
});
|
|
14843
14855
|
return this.#response(response);
|
|
@@ -14846,7 +14858,8 @@ class DispatchClient {
|
|
|
14846
14858
|
const response = await this.fetchImpl(this.#url(path), {
|
|
14847
14859
|
method,
|
|
14848
14860
|
headers: { Accept: "application/json", Authorization: `Bearer ${this.token}` },
|
|
14849
|
-
body
|
|
14861
|
+
body,
|
|
14862
|
+
signal: this.#signal
|
|
14850
14863
|
});
|
|
14851
14864
|
return this.#response(response);
|
|
14852
14865
|
}
|
|
@@ -15197,10 +15210,13 @@ function issueSummary(issue, events, references) {
|
|
|
15197
15210
|
const asks = issue.open_asks;
|
|
15198
15211
|
const spec = issue.artifacts?.find((artifact) => artifact.primary);
|
|
15199
15212
|
const specApproval = spec === undefined ? undefined : approvalLine(spec);
|
|
15213
|
+
if (issue.priority === undefined)
|
|
15214
|
+
throw new Error("Dispatch issue is missing priority");
|
|
15200
15215
|
return [
|
|
15201
15216
|
`Title: ${issue.title}`,
|
|
15202
15217
|
`Key: ${issue.key}`,
|
|
15203
15218
|
`Status: ${issue.status}`,
|
|
15219
|
+
...issue.priority === null ? [] : [`Priority: P${issue.priority}`],
|
|
15204
15220
|
`Labels: ${issue.labels.length === 0 ? "none" : issue.labels.join(", ")}`,
|
|
15205
15221
|
`Route: ${issue.route ?? "none"}`,
|
|
15206
15222
|
...specApproval === undefined ? [] : [`Spec ${specApproval.replace(/^Approval/, "approval")}`],
|
|
@@ -15311,7 +15327,7 @@ async function executeDispatchTool(input) {
|
|
|
15311
15327
|
const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec);
|
|
15312
15328
|
const args = toolSchema(input.tool).parse(ownerArguments.args);
|
|
15313
15329
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
15314
|
-
const client = new DispatchClient(configUrl, configToken, input.fetchImpl);
|
|
15330
|
+
const client = new DispatchClient(configUrl, configToken, input.fetchImpl, input.signal);
|
|
15315
15331
|
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
15316
15332
|
kind: "issue",
|
|
15317
15333
|
issue: await ensureIssue(client, ownerArguments.owner.issue, actor)
|
|
@@ -15334,6 +15350,7 @@ async function executeDispatchTool(input) {
|
|
|
15334
15350
|
const external = optionalString(args, "external");
|
|
15335
15351
|
const force = optionalBoolean(args, "force");
|
|
15336
15352
|
const spec = optionalString(args, "spec");
|
|
15353
|
+
const priority = optionalNumber(args, "priority");
|
|
15337
15354
|
const labels = args.labels;
|
|
15338
15355
|
try {
|
|
15339
15356
|
const created = await client.issue({
|
|
@@ -15343,6 +15360,7 @@ async function executeDispatchTool(input) {
|
|
|
15343
15360
|
...external === undefined ? {} : { external },
|
|
15344
15361
|
...force === undefined ? {} : { force },
|
|
15345
15362
|
...spec === undefined ? {} : { spec },
|
|
15363
|
+
...priority === undefined ? {} : { priority },
|
|
15346
15364
|
...Array.isArray(labels) ? { labels } : {},
|
|
15347
15365
|
actor
|
|
15348
15366
|
});
|
|
@@ -15894,7 +15912,7 @@ class EnvoyApiError extends Error {
|
|
|
15894
15912
|
function createEnvoyClient(config) {
|
|
15895
15913
|
const baseUrl = normalizeEnvoyUrl(config.baseUrl);
|
|
15896
15914
|
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
15897
|
-
const apiToken = process.env
|
|
15915
|
+
const { ENVOY_TOKEN: apiToken } = process.env;
|
|
15898
15916
|
const request = async (path, init) => {
|
|
15899
15917
|
const url = `${baseUrl}${path}`;
|
|
15900
15918
|
for (let attempt = 0;attempt < 2; attempt += 1) {
|
package/package.json
CHANGED
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -78,13 +78,13 @@ exactly one owner to every owner-scoped tool: `issue` for an issue, or `project`
|
|
|
78
78
|
[References](#references) for the resulting ref shape). On first use, an external issue reference creates its native issue in the
|
|
79
79
|
project configured for that repository in Dispatch Settings, then falls back to `DISPATCH_DEFAULT_PROJECT`.
|
|
80
80
|
|
|
81
|
-
Issue reads include `rank`, the server-owned ordering key used by project boards; reorder through `PATCH /api/v1/issues/{key}` with neighboring issue keys
|
|
81
|
+
Issue reads include `rank`, the server-owned ordering key used by project boards; reorder through `PATCH /api/v1/issues/{key}` with neighboring issue keys. They also include nullable coarse priority (`P0` highest through `P3` lowest).
|
|
82
82
|
|
|
83
83
|
Architects create newly tracked child work with:
|
|
84
84
|
```ts
|
|
85
|
-
dispatch_issue({ project, title, parent?, external?, spec?, force?, labels?: string[] })
|
|
85
|
+
dispatch_issue({ project, title, parent?, external?, spec?, force?, labels?: string[], priority?: 0 | 1 | 2 | 3 })
|
|
86
86
|
```
|
|
87
|
-
`labels` are optional initial labels: Dispatch trims them, preserves their case, and removes case-insensitive duplicates. It returns
|
|
87
|
+
`labels` are optional initial labels: Dispatch trims them, preserves their case, and removes case-insensitive duplicates. Set `priority` on creation only when the human's intent makes the bucket clear; otherwise priority remains the human's decision. It returns
|
|
88
88
|
`details` `{ issue, topic }`. Use `dispatch_issue` only to create an issue; never use it to park a question. When `spec` is supplied,
|
|
89
89
|
follow [Writing a spec](#writing-a-spec).
|
|
90
90
|
|
|
@@ -165,7 +165,8 @@ dispatch_edit_ask({
|
|
|
165
165
|
})
|
|
166
166
|
```
|
|
167
167
|
At least one field besides `ask` is required. Use this only while the same decision remains open: it keeps the prior text in the event
|
|
168
|
-
log
|
|
168
|
+
log and invalidates any answer draft against the prior `edited_at` revision, so the human sees the new wording and explicitly reconfirms.
|
|
169
|
+
An answered or resolved ask cannot be edited. If the decision is moot or superseded, retract the old ask and open a new one.
|
|
169
170
|
|
|
170
171
|
An ask stays open until a human answers, unless its question no longer needs that answer. Retract a moot or superseded question, or
|
|
171
172
|
self-resolve one after finding the answer:
|
|
@@ -34,6 +34,13 @@ separate coordinator to finish necessary work.
|
|
|
34
34
|
create and continue to own. Re-file a genuinely independent child through the
|
|
35
35
|
controller rather than treating it as an abandoned dependency.
|
|
36
36
|
|
|
37
|
+
## Deployment instructions
|
|
38
|
+
|
|
39
|
+
Deployment instructions, when present, are the operator's standing rules for this repository —
|
|
40
|
+
required checks, deploy/smoke commands, code-owner expectations, standing roles you may consult,
|
|
41
|
+
the merge credential. They override this skill's defaults where they conflict; they never
|
|
42
|
+
override a Sami ruling quoted here.
|
|
43
|
+
|
|
37
44
|
## 1. Decompose or adopt
|
|
38
45
|
|
|
39
46
|
Inspect the root issue, acceptance criteria, existing children, and current handoffs.
|
|
@@ -127,10 +134,12 @@ legion({
|
|
|
127
134
|
|
|
128
135
|
The daemon spawns that sub-architect as its own process with the child's context already
|
|
129
136
|
in its environment; a resume of an existing role continues the same process instead of
|
|
130
|
-
starting a fresh one. Keep the returned session identifiers
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
starting a fresh one. Keep the returned session identifiers; retro and adjustment resume
|
|
138
|
+
those same sessions through `spawn_worker` (a finished worker is retired after
|
|
139
|
+
`worker_idle_retire_seconds` and comes back from its session file). Park while children are
|
|
140
|
+
in flight. On each child closure, re-scope open work, close obsolete work with a reason, and
|
|
141
|
+
release the next wave only when it now makes sense. There is no inter-child dependency
|
|
142
|
+
mechanism to encode.
|
|
134
143
|
|
|
135
144
|
## 3. Children complete
|
|
136
145
|
|
|
@@ -160,18 +169,24 @@ review and the merge-gate sequence.
|
|
|
160
169
|
|
|
161
170
|
## 5. Retro
|
|
162
171
|
|
|
163
|
-
Retro is mandatory for every issue that passed review, before merge.
|
|
164
|
-
|
|
165
|
-
|
|
172
|
+
Retro is mandatory for every issue that passed review, before merge. Send the implementer
|
|
173
|
+
back in through the daemon — `spawn_worker` on the implementer carrying the retro task. This
|
|
174
|
+
resumes the same agent whether its pane is still live or the daemon has already retired it
|
|
175
|
+
idle (a finished worker is retired after `worker_idle_retire_seconds`, default 600 s, and
|
|
176
|
+
resumed from its session file on its next assignment). Never `envoy_publish` to a finished
|
|
177
|
+
worker's role topic for this: a retired role has no live holder and the publish is rejected
|
|
178
|
+
with 404.
|
|
166
179
|
|
|
167
180
|
```text
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
181
|
+
legion({
|
|
182
|
+
op: "spawn_worker",
|
|
183
|
+
issue: "LEGION-40",
|
|
184
|
+
role: "implementer",
|
|
185
|
+
task: "Run the legion-retro skill now. Capture durable learnings and post the issue comment; do not create a .legion handoff file."
|
|
171
186
|
})
|
|
172
187
|
```
|
|
173
188
|
|
|
174
|
-
Wait for the
|
|
189
|
+
Wait for the implementer to report its durable retro result. Retro output is
|
|
175
190
|
`docs/solutions/` plus an issue comment; it must not create a `.legion` file or change
|
|
176
191
|
the reviewer-approved head after cleanup.
|
|
177
192
|
|
|
@@ -189,7 +204,8 @@ Preserve this order exactly:
|
|
|
189
204
|
head. The deletion must land before that approval, which is head-pinned. An implementer
|
|
190
205
|
completion always writes the issue's status as `testing`; this one is not a test round,
|
|
191
206
|
so on its `phase-complete` wake call `legion({ op: "set_status", issue, status: "retro" })`
|
|
192
|
-
before
|
|
207
|
+
before `spawn_worker` on the reviewer to approve that head (a finished reviewer may already
|
|
208
|
+
be retired; `spawn_worker` resumes it);
|
|
193
209
|
3. retro completes without dirtying the branch beyond `docs/solutions/`;
|
|
194
210
|
4. the merger verifies the current head is the reviewer-approved head plus only the retro
|
|
195
211
|
commits and publishes `READY #<n> at <sha>` to `notifications.role.pr-queue`; it never
|
|
@@ -33,6 +33,13 @@ listener restart.
|
|
|
33
33
|
This handshake lets the daemon redeliver held controller work. It does not turn the controller
|
|
34
34
|
into a state holder: daemon state and the Dispatch project remain authoritative.
|
|
35
35
|
|
|
36
|
+
## Deployment instructions
|
|
37
|
+
|
|
38
|
+
Deployment instructions, when present, are the operator's standing rules for this repository —
|
|
39
|
+
required checks, deploy/smoke commands, code-owner expectations, standing roles you may consult,
|
|
40
|
+
the merge credential. They override this skill's defaults where they conflict; they never
|
|
41
|
+
override a Sami ruling quoted here.
|
|
42
|
+
|
|
36
43
|
## Turn discipline
|
|
37
44
|
|
|
38
45
|
- **Direct user message always first.** If this turn includes a direct user message, answer
|
|
@@ -36,11 +36,12 @@ digraph oracle_decision {
|
|
|
36
36
|
|
|
37
37
|
## Research Strategy
|
|
38
38
|
|
|
39
|
-
If the deployment instructions name a librarian role,
|
|
40
|
-
`notifications.role.<name>`
|
|
41
|
-
OK), and 3-4 if needed, with tools a Legion
|
|
42
|
-
|
|
43
|
-
read-only analysis when the answer needs
|
|
39
|
+
If the deployment instructions name a librarian (or oracle) role, publish your question to
|
|
40
|
+
`notifications.role.<name>` with `expects_reply: required` and wait for the reply before
|
|
41
|
+
researching yourself. Then run steps 1-2 (parallel OK), and 3-4 if needed, with tools a Legion
|
|
42
|
+
pane actually has: `read`, `grep`, `web_search`, and `task(agent="scout")` (fast read-only
|
|
43
|
+
codebase search) or `task(agent="oracle")` (deeper read-only analysis when the answer needs
|
|
44
|
+
judgment across many files). Do not name any other agent.
|
|
44
45
|
|
|
45
46
|
| Step | Tool | Query |
|
|
46
47
|
|------|------|-------|
|
|
@@ -21,6 +21,10 @@ retrospective's durable output.
|
|
|
21
21
|
Retro writes **no `.legion` file**, so it never re-dirties the cleaned handoff tree.
|
|
22
22
|
4. The merger verifies the approved head, publishes `READY`, and pushes nothing; the merge queue
|
|
23
23
|
merges under the repository's own rules.
|
|
24
|
+
5. After the merge lands, the implementer — not the reviewer, the merger, or the queue — verifies
|
|
25
|
+
the change in production and records it on the PR and the issue (Sami, 2026-09-13, verbatim:
|
|
26
|
+
"the agent that developed it should be responsible for testing in production"). The
|
|
27
|
+
architect's sign-off waits for that record.
|
|
24
28
|
|
|
25
29
|
Do not start retro before step 2, skip it because the change seems mechanical, or publish `READY`
|
|
26
30
|
before step 3. The design gate is not a substitute for review and retro.
|
|
@@ -28,6 +32,10 @@ before step 3. The design gate is not a substitute for review and retro.
|
|
|
28
32
|
## Two perspectives
|
|
29
33
|
|
|
30
34
|
1. Re-read the issue, its acceptance criteria, the PR, test evidence, and review evidence.
|
|
35
|
+
Confirm the PR's `E2E` line links a pre-merge proof on a production-like surface (a devN
|
|
36
|
+
stack, staging, or a local stack with real migrations). If it links only a unit suite or
|
|
37
|
+
nothing, the retro's first durable learning is that gap, and the issue goes back to the
|
|
38
|
+
tester before `READY`.
|
|
31
39
|
Do not rebase or create a new branch; work on the existing issue branch.
|
|
32
40
|
2. Spawn one fresh-eyes subagent. Give it the issue and PR, ask it to inspect the diff and
|
|
33
41
|
return concrete reusable learnings, and require it to return analysis rather than edit files.
|
|
@@ -55,13 +55,22 @@ round, a question) can deliver a new prompt to this same session. Treat it as a
|
|
|
55
55
|
continuation — re-read the current issue and your own prior handoff, since time has
|
|
56
56
|
passed — never as a fresh identity.
|
|
57
57
|
|
|
58
|
+
## Deployment instructions
|
|
59
|
+
|
|
60
|
+
Deployment instructions, when present, are the operator's standing rules for this repository —
|
|
61
|
+
required checks, deploy/smoke commands, code-owner expectations, standing roles you may consult,
|
|
62
|
+
the merge credential. They override this skill's defaults where they conflict; they never
|
|
63
|
+
override a Sami ruling quoted here.
|
|
64
|
+
|
|
58
65
|
## Asking another role
|
|
59
66
|
|
|
60
67
|
Reach any live role on this issue the same way you reach the architect: `envoy_publish` to
|
|
61
68
|
`notifications.role.` followed by that role's encoded token. Use it when you need context an
|
|
62
69
|
earlier phase has that its handoff doesn't cover — ask the planner why a constraint was
|
|
63
70
|
scoped that way, ask the implementer what a commit actually did. A role that finished its
|
|
64
|
-
phase
|
|
71
|
+
phase stays idle in its pane for the daemon's idle-retire window and answers; once retired (no
|
|
72
|
+
live holder, a publish is rejected 404), read its committed handoff or ask the architect to
|
|
73
|
+
`spawn_worker` it.
|
|
65
74
|
|
|
66
75
|
## Workspace and handoff precedence
|
|
67
76
|
|
|
@@ -240,7 +249,23 @@ Negative control: <deliberately broken input> → <refusal or failure observed>.
|
|
|
240
249
|
- The tester fills in the `E2E` section: the real surface a user reaches the criterion
|
|
241
250
|
through, the exact command or run id, what was observed, the head SHA, and one negative
|
|
242
251
|
control — a deliberately broken input and the refusal or failure it produced. A unit or
|
|
243
|
-
integration test is a regression lock, never proof of a criterion.
|
|
252
|
+
integration test is a regression lock, never proof of a criterion. The surface is
|
|
253
|
+
**production-like** — a devN stack, staging, or a local stack with real migrations, one that
|
|
254
|
+
has the resource the change touches — and the `E2E` line carries a **link** to that run,
|
|
255
|
+
screenshot, or e2e; the merge queue does not approve a user-facing change without it, and a
|
|
256
|
+
green unit suite is not it. Sami, 2026-09-13, verbatim: "They need to test everything in a
|
|
257
|
+
production-like environment before merging, and it is the agent that develops the feature
|
|
258
|
+
that is responsible for doing that. If there's anything blocking that, we need to fix it: if
|
|
259
|
+
it's infrastructure, we need to fix it; if it's tooling, we need to develop it; if it's
|
|
260
|
+
skills, we need to fix the skills ... it should not require deploying to production to
|
|
261
|
+
realize your feature doesn't work." A code path whose first execution is after merge — a
|
|
262
|
+
deploy workflow's inline step, a post-merge helper, a production-only resource — is untested
|
|
263
|
+
until the implementer has executed it against a devN stack; if no surface can reach it, the
|
|
264
|
+
tester names that missing surface as the blocker instead of passing the phase. Evidence for
|
|
265
|
+
the rule: in the week of 2026-09-08 three surfaces merged green and were wrong on inspection
|
|
266
|
+
(the Astrolabe IPI stack, Dispatch on ECS, the candidate flow), and on 2026-09-12 six deploy
|
|
267
|
+
slots died on code first executed after merge, including a production-only ECS bootstrap the
|
|
268
|
+
whole staging gate never ran. Environment or
|
|
244
269
|
secret-scrub evidence (e.g. "`LEGION_*`/`DISPATCH_*`/`ENVOY_*` unset") is recorded once, in
|
|
245
270
|
`.legion/test.json`, and only when the issue's acceptance criteria call for it — never
|
|
246
271
|
re-pasted into the PR body each round.
|
|
@@ -264,6 +289,15 @@ Negative control: <deliberately broken input> → <refusal or failure observed>.
|
|
|
264
289
|
`READY #<n> at <sha>` plus the PR body's gate facts to the merge queue's role
|
|
265
290
|
(`notifications.role.pr-queue`) with `envoy_publish`. The merger never merges; the queue
|
|
266
291
|
merges under its own authority.
|
|
292
|
+
- **After the queue merges, the implementer verifies in production.** Sami, 2026-09-13,
|
|
293
|
+
verbatim: "the agent that developed it should be responsible for testing in production."
|
|
294
|
+
The architect sends the implementer back once the merge lands; the implementer watches the
|
|
295
|
+
deploy slot that carries the merge to `production-apply` (or the equivalent publish step),
|
|
296
|
+
drives the changed path in production through the user's own access path, and records the
|
|
297
|
+
observation on the PR and the issue before the architect signs off. A staging pass is not
|
|
298
|
+
this: on 2026-09-12 a slot's entire staging gate passed at 00:02Z and its production-apply
|
|
299
|
+
failed at 00:12Z on a resource staging never runs. If the slot fails on the change, the
|
|
300
|
+
implementer owns the fix and the next slot.
|
|
267
301
|
|
|
268
302
|
## Completion gate: handoff write, verification, and persistence
|
|
269
303
|
|
|
@@ -316,7 +350,9 @@ record of this issue's active phase. Do not add pipeline labels, run a controlle
|
|
|
316
350
|
invent a different completion protocol — this is the whole contract.
|
|
317
351
|
|
|
318
352
|
**Stay in this session afterward.** Your process does not exit when your phase completes;
|
|
319
|
-
it goes idle in its pane
|
|
353
|
+
it goes idle in its pane, and after `worker_idle_retire_seconds` (default 600 s) idle with no
|
|
354
|
+
active phase the daemon retires it — your next assignment resumes this same session from its
|
|
355
|
+
session file, so it is still you. Other roles on this issue may reach you through Envoy with
|
|
320
356
|
questions about the work you did — answer them, reading `$LEGION_WORKSPACE` and your own
|
|
321
357
|
committed handoff as needed, without mutating anything (see Workspace and handoff
|
|
322
358
|
precedence above). You will also be the one resumed, with a new prompt in this same
|