@sjawhar/pi-legion 10.29.2 → 10.30.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/legion.js +96 -13
- package/dist/skills/legion-architect/references/acceptance.md +3 -2
- package/dist/skills/legion-architect/references/decomposition.md +10 -6
- package/dist/skills/legion-worker/SKILL.md +1 -1
- package/dist/skills/legion-worker/references/merge-gate.md +9 -7
- package/dist/skills/legion-worker/references/tools.md +16 -14
- package/package.json +2 -2
package/dist/legion.js
CHANGED
|
@@ -14445,6 +14445,7 @@ var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passag
|
|
|
14445
14445
|
var SEARCH_KIND_DEPTH = 100;
|
|
14446
14446
|
var PROJECT_KEY_PATTERN = /^[A-Z][A-Z0-9]{1,9}$/;
|
|
14447
14447
|
var PROJECT_KEY_RULE = "an uppercase letter, then 1 to 9 uppercase letters or digits, such as CORE";
|
|
14448
|
+
var ARCHITECTURE_SOURCE_REPO_PATTERN = /^[A-Za-z0-9-]+\/(?!\.\.?$)[A-Za-z0-9._-]+$/;
|
|
14448
14449
|
var ISSUE_STATUSES = [
|
|
14449
14450
|
"triage",
|
|
14450
14451
|
"icebox",
|
|
@@ -14810,7 +14811,7 @@ var dispatchToolSpecs = [
|
|
|
14810
14811
|
{
|
|
14811
14812
|
name: "dispatch_issues",
|
|
14812
14813
|
example: { project: "PROJ", limit: 250, offset: 250 },
|
|
14813
|
-
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, open-ask count, progress (its spec's task-list items and " + "its direct children, each done of total, as `tasks 3/7 \xB7 children 2/5`, each only when it has " + "any), and route with whether it reaches anyone, " + "so you can see backlog shape without opening every issue. Optionally filter by status, parent, " + "label, priority, route status, or how recently it changed; priority takes one or more of 0-3 " + "(P0-P3) and null for an issue with no priority, so an owner's P0/P1 audit is priority [0, 1]. " + 'route_status "no_holder" lists every open issue whose route names a role nobody holds or a ' + "session that is not running at the moment of the read, whatever its priority. A restarting " + "session is absent for minutes, so an issue is unowned only when a read ten minutes later agrees. " + "Do not use it to search by keyword or phrase; dispatch search remains the keyword surface. " + "Dispatch pages the list: limit sets the page size (default " + `${DEFAULT_ISSUE_PAGE_LIMIT}, max ${MAX_ISSUE_PAGE_LIMIT}) and offset selects where it starts ` + "(default 0), and the answer names how many issues match, so repeat with the next offset to " + "walk every matching issue. A walk is exact only while the list does not change: an issue " + "that enters or leaves what the filters match, or whose status or rank changes, between two " + "pages shifts rows across a page boundary, so one issue can come back twice and another never.",
|
|
14814
|
+
description: "List a project's issues for a roadmap or backlog pass: every issue in one project, each carrying " + "its status, priority, parent, labels, open-ask count, progress (its spec's task-list items and " + "its direct children, each done of total, as `tasks 3/7 \xB7 children 2/5`, each only when it has " + "any), and route with whether it reaches anyone, " + "so you can see backlog shape without opening every issue. Optionally filter by status, parent, " + "label, priority, route status, or how recently it changed; priority takes one or more of 0-3 " + "(P0-P3) and null for an issue with no priority, so an owner's P0/P1 audit is priority [0, 1]. " + 'route_status "no_holder" lists every open issue whose route names a role nobody holds or a ' + "session that is not running at the moment of the read, whatever its priority. A restarting " + "session is absent for minutes, so an issue is unowned only when a read ten minutes later agrees. " + "Do not use it to search by keyword or phrase; dispatch search remains the keyword surface. " + "Dispatch pages the list: limit sets the page size (default " + `${DEFAULT_ISSUE_PAGE_LIMIT}, max ${MAX_ISSUE_PAGE_LIMIT}) and offset selects where it starts ` + "(default 0), and the answer names how many issues match, so repeat with the next offset to " + "walk every matching issue. A walk is exact only while the list does not change: an issue " + "that enters or leaves what the filters match, or whose status or rank changes, between two " + "pages shifts rows across a page boundary, so one issue can come back twice and another never. " + "all takes the whole filtered list in one read instead, for a caller that must not miss or " + "repeat a row while other writers change the list, and especially for one whose own writes " + "move issues out of what it filtered on.",
|
|
14814
14815
|
arguments: (z2) => ({
|
|
14815
14816
|
project: z2.string().describe("Project key to list issues from."),
|
|
14816
14817
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
@@ -14820,7 +14821,8 @@ var dispatchToolSpecs = [
|
|
|
14820
14821
|
updated_since: z2.string().describe("Optional RFC3339 timestamp; only issues updated at or after it.").optional(),
|
|
14821
14822
|
route_status: z2.enum(ISSUE_ROUTE_STATUSES).describe("Optional: only open issues whose route is in this state. live: a running session holds " + "the role or is the routed session. no_holder: nobody running holds the role, or the " + "session is not running, right now. unknown: the Envoy listener did not answer.").optional(),
|
|
14822
14823
|
limit: z2.number({ int: true, min: 1, max: MAX_ISSUE_PAGE_LIMIT }).describe(`Maximum rows, 1-${MAX_ISSUE_PAGE_LIMIT}; default ${DEFAULT_ISSUE_PAGE_LIMIT}.`).optional(),
|
|
14823
|
-
offset: z2.number({ int: true, min: 0 }).describe("Rows to skip before the page; nonnegative integer; default 0.").optional()
|
|
14824
|
+
offset: z2.number({ int: true, min: 0 }).describe("Rows to skip before the page; nonnegative integer; default 0.").optional(),
|
|
14825
|
+
all: z2.boolean().describe("Take every matching issue in one unpaged read, so no row is missed or repeated while " + "the list changes. Refused with limit or offset.").optional()
|
|
14824
14826
|
})
|
|
14825
14827
|
},
|
|
14826
14828
|
{
|
|
@@ -14842,11 +14844,19 @@ var dispatchToolSpecs = [
|
|
|
14842
14844
|
},
|
|
14843
14845
|
{
|
|
14844
14846
|
name: "dispatch_architecture_sync",
|
|
14845
|
-
example: { project: "CORE" },
|
|
14846
|
-
description: "Import a project's architecture model from its
|
|
14847
|
+
example: { project: "CORE", repo: "acme/infra" },
|
|
14848
|
+
description: "Import a project's architecture model from its source repositories now, instead of waiting " + "for the server's five-minute schedule. A project may have several sources, one per " + "repository, and its model is every source's components together. Without repo every " + "source imports and each answers on its own line; with repo only that source does. Each " + "line names the imported commit, or the recorded error when that source's model was " + "rejected (its previous components stay up, and every other source is untouched). Sources " + "are saved in Settings or with PUT /api/v1/projects/{key}/architecture-sources/{owner}/{repo}; " + "404 SOURCE_NOT_FOUND when the project has none, or none for that repository.",
|
|
14847
14849
|
arguments: (z2) => ({
|
|
14848
|
-
project: z2.string().describe("Project key whose architecture
|
|
14850
|
+
project: z2.string().describe("Project key whose architecture sources to sync, such as CORE."),
|
|
14851
|
+
repo: z2.string().describe("One source repository of the project to sync alone, owner/name; omit it to sync every source.").optional()
|
|
14849
14852
|
}),
|
|
14853
|
+
validation: {
|
|
14854
|
+
check: (value) => {
|
|
14855
|
+
const { repo } = value;
|
|
14856
|
+
return typeof repo !== "string" || ARCHITECTURE_SOURCE_REPO_PATTERN.test(repo);
|
|
14857
|
+
},
|
|
14858
|
+
message: "repo is not a repository: give it as owner/name, such as acme/infra."
|
|
14859
|
+
},
|
|
14850
14860
|
strict: true
|
|
14851
14861
|
},
|
|
14852
14862
|
{
|
|
@@ -15063,6 +15073,9 @@ function asErrorShape(value) {
|
|
|
15063
15073
|
function isJson(response) {
|
|
15064
15074
|
return response.headers.get("content-type")?.includes("application/json") ?? false;
|
|
15065
15075
|
}
|
|
15076
|
+
function isNoRoute(error48) {
|
|
15077
|
+
return error48 instanceof DispatchServiceError && error48.status === 404 && error48.code === "NOT_FOUND" && error48.message.startsWith("no route for ");
|
|
15078
|
+
}
|
|
15066
15079
|
var ISSUE_EVENTS_PAGE_MAX = 200;
|
|
15067
15080
|
var DISPATCH_TOOL_DEADLINE_MS = 60000;
|
|
15068
15081
|
function requestSignal(signal) {
|
|
@@ -15090,6 +15103,18 @@ function whyNotAPage(answer) {
|
|
|
15090
15103
|
}
|
|
15091
15104
|
return `${what}. Retrying will not help: the same request gets the same answer until that ` + "Dispatch is upgraded or fixed.";
|
|
15092
15105
|
}
|
|
15106
|
+
function whyNotAnArray(answer) {
|
|
15107
|
+
if (typeof answer === "string")
|
|
15108
|
+
return `text that is not JSON, ${GATEWAY_PAGE}, so a retry may succeed`;
|
|
15109
|
+
if (answer === null)
|
|
15110
|
+
return "null";
|
|
15111
|
+
if (typeof answer !== "object")
|
|
15112
|
+
return `a ${typeof answer}`;
|
|
15113
|
+
if ("issues" in answer && Array.isArray(answer.issues)) {
|
|
15114
|
+
return "a page, so this Dispatch pages a request that carries no limit and no offset";
|
|
15115
|
+
}
|
|
15116
|
+
return "an object that is not the listing";
|
|
15117
|
+
}
|
|
15093
15118
|
var GATEWAY_PAGE = "which looks like a proxy or gateway page rather than Dispatch's own answer";
|
|
15094
15119
|
function gatewayAdvice(method, status) {
|
|
15095
15120
|
if (!transientStatus(status)) {
|
|
@@ -15165,6 +15190,14 @@ class DispatchClient {
|
|
|
15165
15190
|
return answer;
|
|
15166
15191
|
throw new Error(`GET ${this.#url(path2, query)} asked for a page ({issues, total, limit, offset}) and got ` + refusal);
|
|
15167
15192
|
}
|
|
15193
|
+
async listAllIssues(options) {
|
|
15194
|
+
const path2 = ["api", "v1", "issues"];
|
|
15195
|
+
const query = { ...options };
|
|
15196
|
+
const answer = await this.#json("GET", path2, undefined, query);
|
|
15197
|
+
if (Array.isArray(answer))
|
|
15198
|
+
return answer;
|
|
15199
|
+
throw new Error(`GET ${this.#url(path2, query)} asked for the unpaged listing and got ${whyNotAnArray(answer)}.`);
|
|
15200
|
+
}
|
|
15168
15201
|
async listProjectArtifacts(project, unlinked = false) {
|
|
15169
15202
|
return this.#json("GET", ["api", "v1", "projects", project, "artifacts"], undefined, unlinked ? { unlinked: "true" } : undefined);
|
|
15170
15203
|
}
|
|
@@ -15253,19 +15286,69 @@ class DispatchClient {
|
|
|
15253
15286
|
async listProjects() {
|
|
15254
15287
|
return this.#json("GET", ["api", "v1", "projects"]);
|
|
15255
15288
|
}
|
|
15256
|
-
async
|
|
15257
|
-
|
|
15258
|
-
|
|
15259
|
-
|
|
15289
|
+
async listArchitectureSources(project) {
|
|
15290
|
+
try {
|
|
15291
|
+
return await this.#json("GET", ["api", "v1", "projects", project, "architecture-sources"]);
|
|
15292
|
+
} catch (error48) {
|
|
15293
|
+
if (!isNoRoute(error48))
|
|
15294
|
+
throw error48;
|
|
15295
|
+
}
|
|
15260
15296
|
try {
|
|
15261
|
-
|
|
15297
|
+
const source = await this.#json("GET", [
|
|
15298
|
+
"api",
|
|
15299
|
+
"v1",
|
|
15300
|
+
"projects",
|
|
15301
|
+
project,
|
|
15302
|
+
"architecture-source"
|
|
15303
|
+
]);
|
|
15304
|
+
return source === null ? [] : [source];
|
|
15262
15305
|
} catch (error48) {
|
|
15263
15306
|
if (error48 instanceof DispatchServiceError && error48.status === 404 && error48.code === "SOURCE_NOT_FOUND") {
|
|
15264
|
-
return
|
|
15307
|
+
return [];
|
|
15265
15308
|
}
|
|
15266
15309
|
throw error48;
|
|
15267
15310
|
}
|
|
15268
15311
|
}
|
|
15312
|
+
async syncArchitectureSources(project) {
|
|
15313
|
+
try {
|
|
15314
|
+
return await this.#json("POST", [
|
|
15315
|
+
"api",
|
|
15316
|
+
"v1",
|
|
15317
|
+
"projects",
|
|
15318
|
+
project,
|
|
15319
|
+
"architecture-sources",
|
|
15320
|
+
"sync"
|
|
15321
|
+
]);
|
|
15322
|
+
} catch (error48) {
|
|
15323
|
+
if (!isNoRoute(error48))
|
|
15324
|
+
throw error48;
|
|
15325
|
+
}
|
|
15326
|
+
const source = await this.#json("POST", [
|
|
15327
|
+
"api",
|
|
15328
|
+
"v1",
|
|
15329
|
+
"projects",
|
|
15330
|
+
project,
|
|
15331
|
+
"architecture-source",
|
|
15332
|
+
"sync"
|
|
15333
|
+
]);
|
|
15334
|
+
return [source];
|
|
15335
|
+
}
|
|
15336
|
+
async syncArchitectureSource(project, repo2) {
|
|
15337
|
+
const [owner, name, ...rest] = repo2.split("/");
|
|
15338
|
+
if (!owner || !name || rest.length > 0) {
|
|
15339
|
+
throw new Error(`repository must be owner/name, got ${JSON.stringify(repo2)}`);
|
|
15340
|
+
}
|
|
15341
|
+
return this.#json("POST", [
|
|
15342
|
+
"api",
|
|
15343
|
+
"v1",
|
|
15344
|
+
"projects",
|
|
15345
|
+
project,
|
|
15346
|
+
"architecture-sources",
|
|
15347
|
+
owner,
|
|
15348
|
+
name,
|
|
15349
|
+
"sync"
|
|
15350
|
+
]);
|
|
15351
|
+
}
|
|
15269
15352
|
async resolveAsk(id, input) {
|
|
15270
15353
|
return this.#json("POST", ["api", "v1", "asks", id, "resolve"], input);
|
|
15271
15354
|
}
|
|
@@ -16956,7 +17039,7 @@ async function bootLegionClaim(host2) {
|
|
|
16956
17039
|
// package.json
|
|
16957
17040
|
var package_default = {
|
|
16958
17041
|
name: "@sjawhar/pi-legion",
|
|
16959
|
-
version: "10.
|
|
17042
|
+
version: "10.30.0",
|
|
16960
17043
|
license: "Apache-2.0",
|
|
16961
17044
|
type: "module",
|
|
16962
17045
|
omp: {
|
|
@@ -16968,7 +17051,7 @@ var package_default = {
|
|
|
16968
17051
|
]
|
|
16969
17052
|
},
|
|
16970
17053
|
legion: {
|
|
16971
|
-
daemonApiVersion:
|
|
17054
|
+
daemonApiVersion: 37
|
|
16972
17055
|
},
|
|
16973
17056
|
repository: {
|
|
16974
17057
|
type: "git",
|
|
@@ -120,8 +120,9 @@ The merge closes the issue: when GitHub reports the pull request merged while th
|
|
|
120
120
|
`pr-merged` notice names what it waits for and none follows as they depart, so read the pull
|
|
121
121
|
request on GitHub.
|
|
122
122
|
- A child's close reaches you as `child-closed`. The daemon retires five of its six claims and
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
leaves the implementer running, because it still owes the production check. When the issue's
|
|
124
|
+
pod and Sandbox go is in `skill://legion-worker/references/merge-gate.md`, *The production check
|
|
125
|
+
after the close*.
|
|
125
126
|
- A root's close writes `done`, frees the tree's admission slot and lingers the tree for
|
|
126
127
|
`linger_hours` with every member suspended. The linger's end, the tree's last step, waits until
|
|
127
128
|
every member's open production check has closed.
|
|
@@ -61,17 +61,21 @@ repositories with your gh's read-only access (`-R owner/repo`).
|
|
|
61
61
|
|
|
62
62
|
## Architecture components
|
|
63
63
|
|
|
64
|
-
A project's architecture
|
|
65
|
-
imports as its components (`skill://dispatch`, "Architecture components").
|
|
64
|
+
A project's architecture sources are the repositories whose `.dispatch/architecture/` files
|
|
65
|
+
Dispatch imports as its components (`skill://dispatch`, "Architecture components").
|
|
66
66
|
|
|
67
67
|
- **The components exist:** attach the root before you decompose, so every child inherits it:
|
|
68
68
|
`dispatch issue-update --issue <root> --components-json '{"mode":"explicit","ids":[…]}'`.
|
|
69
69
|
- **They do not exist yet (bootstrap):** in the root's first PR (the implement worker pushes it),
|
|
70
70
|
write `.dispatch/architecture/<id>.md` files for the planned components — front matter `title`,
|
|
71
|
-
`parent`, `depends_on`, `external`; no `paths` yet.
|
|
72
|
-
|
|
71
|
+
`parent`, `depends_on`, `external`; no `paths` yet. A project may have several sources, one per
|
|
72
|
+
repository, and its model is all of their components together: the file goes in the branch of
|
|
73
|
+
the source whose repository holds it, `parent` and `depends_on` name components of that same
|
|
74
|
+
source only, and its id must be one no other source of the project uses (a repeated id fails that
|
|
75
|
+
source's import, naming the repository that has it). The importer reads the source branch's
|
|
76
|
+
head, so leave the root on `inherit` until that PR has merged: an attach before then answers
|
|
73
77
|
`400 COMPONENTS_INPUT`, since the component does not exist. After the merge, run
|
|
74
|
-
`dispatch architecture-sync --project <KEY>`
|
|
75
|
-
inherit it.
|
|
78
|
+
`dispatch architecture-sync --project <KEY>` (every source; `--repo <owner/name>` syncs that one
|
|
79
|
+
alone) and attach the root; children created before then inherit it.
|
|
76
80
|
- Give a child its own `--components-json` only when it changes a narrower set, and
|
|
77
81
|
`{"mode":"none","reason":"…"}` when it changes no code or file a component describes.
|
|
@@ -11,7 +11,7 @@ You work one phase of one issue in a jj workspace every role of the issue shares
|
|
|
11
11
|
|
|
12
12
|
## Every phase, in this order
|
|
13
13
|
|
|
14
|
-
1. **Read first.** Read the issue, then each committed handoff before your phase, with `read` from `$LEGION_WORKSPACE/.legion/<issue>/`, in order: `plan.json`, `implement.json`, `test.json`, `review.json`. Where one disagrees with your memory or a transcript, the file wins: it is the copy that survived. At each later assignment read them again, your own included, since other phases may have committed since. After a `Your workspace was recreated…` message,
|
|
14
|
+
1. **Read first.** Read the issue, then each committed handoff before your phase, with `read` from `$LEGION_WORKSPACE/.legion/<issue>/`, in order: `plan.json`, `implement.json`, `test.json`, `review.json`. Where one disagrees with your memory or a transcript, the file wins: it is the copy that survived. At each later assignment read them again, your own included, since other phases may have committed since. After a `Your workspace was recreated…` message, anything unpushed is gone, and no file in the workspace says so.
|
|
15
15
|
2. **Commit only your phase's work**, with `jj -R "$LEGION_WORKSPACE" split -m "<message>" <paths…>`, each commit holding only your phase's paths, so the next phase sees what you changed.
|
|
16
16
|
3. **Put back what you copied in.** Before you write the handoff, run `jj -R "$LEGION_WORKSPACE" status` and remove or restore every file you copied or fetched in that is not your phase's work, such as another branch's test rig. With jj the working copy is the commit: the next role's first commit builds on what you left, and its push ships it.
|
|
17
17
|
4. **Write the handoff** your role prompt names. Each `proof` entry, the implementer's or the tester's, is an object of six non-empty strings: `criterion` (the acceptance line it proves), `surface` (where a user reaches the change), `command`, `observed`, `headSha` (the commit it ran at) and `negativeControl` (a deliberately broken input and the refusal seen); `skill://legion-worker/references/pr-body.md#what-a-proof-is` defines each. To add to a handoff already on disk, `read` it, edit it, and `write` it whole with `completed` set to now.
|
|
@@ -251,16 +251,17 @@ reviewing until you finish.
|
|
|
251
251
|
topic, and file no issue. It names this issue, the pull request, the merge commit, what you
|
|
252
252
|
drove and what failed. Name the ask (`dispatch://<REPORT KEY>/ask/<id>`) in the `Production:`
|
|
253
253
|
line and in the summary.
|
|
254
|
-
- **Nothing waits in the session for a deploy.** A `pending` ends the look
|
|
255
|
-
your claim with its session and the issue's
|
|
256
|
-
|
|
254
|
+
- **Nothing waits in the session for a deploy.** A `pending` ends the look. Where no wake is
|
|
255
|
+
queued yet, the daemon suspends your claim with its session kept, and the issue's Sandbox goes
|
|
256
|
+
with its pod and disk; where a wake arrived while the look was open, the next look starts at once
|
|
257
|
+
on the same pod. Either way the daemon wakes the check (after a suspension a new pod comes back
|
|
258
|
+
on a fresh disk, and your session resumes with a new check task) on Dispatch's
|
|
257
259
|
`issue.deployed` event for the pull request; when the project's `legion.yaml` names the
|
|
258
260
|
repository's deploy workflow (`projects.<KEY>.deploys`), at each completed run of that workflow
|
|
259
261
|
on the default branch whose named step (such as `Apply`) succeeded after the merge, once per such
|
|
260
262
|
job; or, for `sjawhar/legion`, on a Legion daemon's boot on a build containing the merge, on
|
|
261
263
|
Dispatch's `server.started` naming such a build, or on the build the daemon read from Dispatch at
|
|
262
|
-
its boot.
|
|
263
|
-
The woken look's task names the event (`Dispatch's issue.deployed: run <id> completed <t>`,
|
|
264
|
+
its boot. The woken look's task names the event (`Dispatch's issue.deployed: run <id> completed <t>`,
|
|
264
265
|
`the deploy workflow's run: <repo> <workflow> run <id> attempt <n>, job <job name>: <step> succeeded at <t>`,
|
|
265
266
|
`the Legion daemon's boot on build <sha>`, `Dispatch's start on build <sha>`,
|
|
266
267
|
`Dispatch's build <sha>`), and its `Production:` line names it too, so the pull request carries
|
|
@@ -279,5 +280,6 @@ reviewing until you finish.
|
|
|
279
280
|
on a closed issue (`ISSUE_CLOSED`), so the deployment instructions' "on the pull request and the
|
|
280
281
|
issue" is the pull request for a closed issue, and a defect reaches Dispatch as the ask above.
|
|
281
282
|
The architect accepted the issue before the merge and is asked nothing after it. `pass` or
|
|
282
|
-
`fail` closes the check: your claim is retired and
|
|
283
|
-
|
|
283
|
+
`fail` closes the check: your claim is retired and the issue's Sandbox goes with its pod and
|
|
284
|
+
disk, for a root as for a child. Neither ending retires or suspends anything of an issue back in
|
|
285
|
+
the workflow (a root reopened inside its linger): its claim and pod are the new run's.
|
|
@@ -86,11 +86,11 @@ for it*.
|
|
|
86
86
|
|
|
87
87
|
An install of your own — an interpreter, a browser, a second toolchain, an extraction prefix — is
|
|
88
88
|
on the sanctioned side when both of these hold: what it writes lands on the issue's own volume,
|
|
89
|
-
under `/legion`, which outlives
|
|
90
|
-
variable the runtime sets for you there or under a path on that volume your
|
|
91
|
-
itself — but outside the issue's workspace (`$LEGION_WORKSPACE`, under
|
|
92
|
-
the runtime's own directories (`/legion/repos`, `/legion/feed
|
|
93
|
-
the command exits nothing else resolves differently. The workspace is excluded because its jj
|
|
89
|
+
under `/legion`, which outlives a role's relaunch in its pod and is shared by the issue's roles —
|
|
90
|
+
whether through a variable the runtime sets for you there or under a path on that volume your
|
|
91
|
+
harness names itself — but outside the issue's workspace (`$LEGION_WORKSPACE`, under
|
|
92
|
+
`/legion/workspaces`) and the runtime's own directories (`/legion/repos`, `/legion/feed`); and
|
|
93
|
+
after the command exits nothing else resolves differently. The workspace is excluded because its jj
|
|
94
94
|
working copy tracks every new file under jj's `snapshot.max-new-file-size` (1 MiB in a role
|
|
95
95
|
container), so a root written there is added to the change every role shares, and a larger new
|
|
96
96
|
file is left untracked, every jj command in the workspace then warning `Refused to snapshot some
|
|
@@ -122,7 +122,7 @@ What each shape leaves resolving:
|
|
|
122
122
|
all. `uv sync` or `bun install` in your workspace materialises what that project already
|
|
123
123
|
declares, into that project's own directory, which a process reaches by asking for that project
|
|
124
124
|
and no other way — and the runtime puts `UV_PYTHON_INSTALL_DIR` and the workspace itself on the
|
|
125
|
-
issue's volume exactly so
|
|
125
|
+
issue's volume exactly so every role of the issue's pod reuses it.
|
|
126
126
|
- An extraction prefix: whatever you leave resolving through it, which is yours to decide. Unpack
|
|
127
127
|
Debian packages under `$HOME`, export `LD_LIBRARY_PATH` in the shell you go on working in, and
|
|
128
128
|
you change what every later command in that shell — and every subagent that inherits the
|
|
@@ -147,8 +147,8 @@ toolchain — is where the first condition does the work. `uv run --with pyyaml=
|
|
|
147
147
|
passes both: the wheel lands in `UV_CACHE_DIR`, under `wheels-v6/pypi/pyyaml/6.0.2-…`, and
|
|
148
148
|
`UV_PYTHON_INSTALL_DIR` is never created, because the image's own `python3` served the run. The
|
|
149
149
|
interpreter is the other run: a project whose `.python-version` names an interpreter uv must fetch
|
|
150
|
-
puts about 70 MB under `UV_PYTHON_INSTALL_DIR`, where the issue's
|
|
151
|
-
cache holds no wheel. A browser download passes too, and for Playwright — the browser driver this
|
|
150
|
+
puts about 70 MB under `UV_PYTHON_INSTALL_DIR`, where every role of the issue's pod finds it, while
|
|
151
|
+
the cache holds no wheel. A browser download passes too, and for Playwright — the browser driver this
|
|
152
152
|
repository's own Dispatch suite installs (`playwright install`, `packages/dispatch`'s
|
|
153
153
|
`e2e:install`) — it passes as it stands, with nothing pointed anywhere: playwright-core resolves
|
|
154
154
|
its browser registry under `XDG_CACHE_HOME`, which the runtime has already put on the issue's
|
|
@@ -164,11 +164,13 @@ here) at a directory under one of the paths above yourself, since an unset one l
|
|
|
164
164
|
wherever the tool defaults, which is nowhere this runtime chose.
|
|
165
165
|
|
|
166
166
|
Where it lands is what you pay. Under one of those variables it is on the issue's volume: it
|
|
167
|
-
outlives
|
|
168
|
-
`XDG_STATE_HOME`, which is `/legion/scratch/state/<role>`, "the one directory
|
|
169
|
-
scratch that is the role's own rather than the issue's" (`roleEnvironment`),
|
|
170
|
-
names its browser broker's lock from the state root and an abstract socket name
|
|
171
|
-
every container of the pod —
|
|
167
|
+
outlives a role's relaunch in its pod, and every role of the issue shares it — under every one of
|
|
168
|
+
those variables but `XDG_STATE_HOME`, which is `/legion/scratch/state/<role>`, "the one directory
|
|
169
|
+
of the issue's scratch that is the role's own rather than the issue's" (`roleEnvironment`),
|
|
170
|
+
because Oh My Pi names its browser broker's lock from the state root and an abstract socket name
|
|
171
|
+
is one name across every container of the pod — though not the pod itself: the issue's disk is
|
|
172
|
+
the pod's own and is deleted with it, so a later pod of the issue starts without it. Under
|
|
173
|
+
`$HOME` —
|
|
172
174
|
`/home/legion`, which no volume is mounted on — it is the container's own filesystem: one copy per
|
|
173
175
|
role, per pod, gone when the role relaunches, and charged to the container's ephemeral-storage
|
|
174
176
|
limit, which is per role and smaller than the volume. Moving exactly these writes off that
|
|
@@ -178,7 +180,7 @@ under `$HOME` fails the first condition and is outside the sanctioned form, howe
|
|
|
178
180
|
changes. The three artefacts above are the whole of that line's ground. Disk is not: every cache is
|
|
179
181
|
already relocated onto the issue's volume, which has the room for them (`docs/kubernetes.md`, "The
|
|
180
182
|
tools a role's own work needs", carries the figure), and a sanctioned install is paid once per
|
|
181
|
-
issue, on the issue's own disk, by the issue's roles together.
|
|
183
|
+
issue pod, on the issue's own disk, by the issue's roles together.
|
|
182
184
|
|
|
183
185
|
**The worked case, and the one a role actually meets: the Dispatch e2e stack.** A change to
|
|
184
186
|
`packages/dispatch` is proved by driving its browser suite, which needs a Postgres with pgvector;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjawhar/pi-legion",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.30.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"omp": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
]
|
|
13
13
|
},
|
|
14
14
|
"legion": {
|
|
15
|
-
"daemonApiVersion":
|
|
15
|
+
"daemonApiVersion": 37
|
|
16
16
|
},
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|