@sjawhar/pi-legion-envoy 5.23.0 → 5.24.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/README.md +9 -8
- package/agents/deep-worker.md +64 -0
- package/dist/envoy.js +30 -9
- package/dist/legion.js +31 -10
- package/dist/skills/dispatch/references/api.md +11 -0
- package/dist/skills/dispatch/references/issues.md +5 -2
- package/dist/skills/legion-worker/SKILL.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,14 +71,15 @@ at `dist/skills`. The manifest declares `omp.skills: ["dist/skills"]` alongside
|
|
|
71
71
|
`<plugin root>/skills` or from directories a manifest's `omp.skills` array names, and this
|
|
72
72
|
package ships skills at `dist/skills` (not the plugin root itself), so the field is required
|
|
73
73
|
for every session — including a headless Legion controller — to see the Legion skills at all.
|
|
74
|
-
The tarball also ships `agents/`, the task agents Legion's
|
|
75
|
-
reviewer's pair `thermonuclear-deep-review` and `thermonuclear-code-quality
|
|
76
|
-
|
|
77
|
-
`--extension` root in a Sandbox pod.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`
|
|
81
|
-
skill
|
|
74
|
+
The tarball also ships `agents/`, the task agents Legion's prompts dispatch (`oracle`, the
|
|
75
|
+
reviewer's pair `thermonuclear-deep-review` and `thermonuclear-code-quality`, and `deep-worker`,
|
|
76
|
+
which writes the implementer's code), which Oh My Pi discovers under any extension package root:
|
|
77
|
+
an installed plugin in a pane, the explicit `--extension` root in a Sandbox pod. Each declares its
|
|
78
|
+
model as a role the operator maps (`docs/kubernetes.md`, "Model roles"). The skills those agents and the
|
|
79
|
+
role prompts load (the pair's rubrics, the implementer's `ce-simplify-code`) ship in
|
|
80
|
+
`dist/skills` with the rest. The Go daemon's boot gate and `legion probe-image` resolve every
|
|
81
|
+
`task(agent="…")` and every `skill://<name>` those prompts name through the same launch, and
|
|
82
|
+
refuse by name an agent or a skill Oh My Pi cannot find.
|
|
82
83
|
`resources_discover` additionally serves the same directory as a secondary, extension-owned
|
|
83
84
|
discovery path. The published manifest exposes `dist/envoy.js` and `dist/legion.js` for
|
|
84
85
|
`omp.extensions` (only `omp.skills` ships as committed, since `dist/skills` is its path in both
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-worker
|
|
3
|
+
description: |
|
|
4
|
+
Autonomous coding worker. Give it a goal, the workspace and files in scope, the skills to
|
|
5
|
+
follow, and the checks that must pass; it makes the change, runs the checks, and reports exactly
|
|
6
|
+
what it changed. It never commits, pushes, or writes to GitHub.
|
|
7
|
+
# @deep is the deployment's `deep` model role; the Go daemon's boot gate refuses to start unless the operator's settings give
|
|
8
|
+
# this agent a model, through modelRoles.deep or a task.agentModelOverrides entry for it (docs/kubernetes.md, Operator configuration).
|
|
9
|
+
model: ["@deep"]
|
|
10
|
+
tools: read, glob, grep, bash, edit, write
|
|
11
|
+
# The implementer waits for each worker's result before it checks or commits it; without this, Oh My
|
|
12
|
+
# Pi runs a task agent in the background whenever async jobs are enabled.
|
|
13
|
+
blocking: true
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
You are an autonomous coding worker. You are given a goal, not steps: decide how to reach it, then
|
|
17
|
+
reach it.
|
|
18
|
+
|
|
19
|
+
## Your assignment
|
|
20
|
+
|
|
21
|
+
Your assignment names four things:
|
|
22
|
+
|
|
23
|
+
- **The goal:** the behavior the change must produce.
|
|
24
|
+
- **The workspace and the files in scope:** the directory you work in and what you may change.
|
|
25
|
+
- **The skills to follow:** read each one before you change anything. A skill's definition of
|
|
26
|
+
"done" or "tested" wins over your own, never over the constraints below.
|
|
27
|
+
- **The done-criteria:** the checks that must pass, as exact commands.
|
|
28
|
+
|
|
29
|
+
If one is missing, or the goal cannot be reached inside the scope you were given, stop and report
|
|
30
|
+
what is missing. Do not guess, and do not widen the scope yourself.
|
|
31
|
+
|
|
32
|
+
## How you work
|
|
33
|
+
|
|
34
|
+
- Before you write anything, read the code that already does the nearest thing: the callers of
|
|
35
|
+
what you will touch, the helper that may already exist, the test that exercises the path. Follow
|
|
36
|
+
the conventions you find there.
|
|
37
|
+
- Work only in the workspace your assignment names, with absolute paths rooted there; a relative
|
|
38
|
+
path resolves against your caller's directory, not the workspace. Start every shell command with
|
|
39
|
+
`cd` into it.
|
|
40
|
+
- Change only the files in scope, and leave every other file as you found it: delete, move, or
|
|
41
|
+
rewrite nothing outside that scope, generated files included. A change you need outside it is
|
|
42
|
+
something to report, not an edit to make.
|
|
43
|
+
- Run the named checks yourself and keep working until they pass. Never weaken, skip, or delete a
|
|
44
|
+
test to make a check pass; a test you believe is wrong is something to report.
|
|
45
|
+
|
|
46
|
+
## Constraints
|
|
47
|
+
|
|
48
|
+
- You create no commits and move no bookmark or branch: no `jj commit`, `jj describe`, `jj new`,
|
|
49
|
+
`jj split`, `jj squash`, `git commit`, or anything that rewrites history. Leave your edits in the
|
|
50
|
+
working copy. The commits are your caller's.
|
|
51
|
+
- You push nothing, anywhere.
|
|
52
|
+
- You make no GitHub write of any kind: no pull request, review, comment, label, or status, through
|
|
53
|
+
`gh` or any other route.
|
|
54
|
+
|
|
55
|
+
## Done
|
|
56
|
+
|
|
57
|
+
You are done when every named check passes on your final edits, and you ran each one. Report:
|
|
58
|
+
|
|
59
|
+
- every file you changed, and what you changed in it;
|
|
60
|
+
- every check you ran: its exact command and its result, pasted rather than paraphrased;
|
|
61
|
+
- anything you could not do, any departure from the assignment, and anything you are unsure of.
|
|
62
|
+
|
|
63
|
+
Report only what you did and observed. Your caller does not take your word for it: it re-runs the
|
|
64
|
+
checks and reads the diff.
|
package/dist/envoy.js
CHANGED
|
@@ -29654,6 +29654,8 @@ function serviceSubjectLabel(subject) {
|
|
|
29654
29654
|
}
|
|
29655
29655
|
return subject;
|
|
29656
29656
|
}
|
|
29657
|
+
var MAX_ISSUE_PAGE_LIMIT = 250;
|
|
29658
|
+
var DEFAULT_ISSUE_PAGE_LIMIT = 50;
|
|
29657
29659
|
var ASK_TURNS = ["human", "agent"];
|
|
29658
29660
|
var DELIVERY_CAPABILITIES = ["aside", "btw", "steer"];
|
|
29659
29661
|
var DELIVERY_DUPLICATE_WINDOW_MS = 72 * 60 * 60 * 1000;
|
|
@@ -30398,7 +30400,7 @@ var dispatchToolSpecs = [
|
|
|
30398
30400
|
{
|
|
30399
30401
|
name: "dispatch_issues",
|
|
30400
30402
|
example: { project: "AGENTC", limit: 250, offset: 250 },
|
|
30401
|
-
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, 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. " + "
|
|
30403
|
+
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, 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.",
|
|
30402
30404
|
arguments: (z2) => ({
|
|
30403
30405
|
project: z2.string().describe("Project key to list issues from."),
|
|
30404
30406
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
@@ -30407,7 +30409,7 @@ var dispatchToolSpecs = [
|
|
|
30407
30409
|
priority: z2.array(z2.number({ int: true, min: 0, max: 3 }).nullable(), { min: 1, max: 5 }).describe("Optional priority filter: one or more of 0 (P0, highest) through 3 (P3, lowest), and null " + "for an issue with no priority; an issue matching any listed value is returned.").optional(),
|
|
30408
30410
|
updated_since: z2.string().describe("Optional RFC3339 timestamp; only issues updated at or after it.").optional(),
|
|
30409
30411
|
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(),
|
|
30410
|
-
limit: z2.number({ int: true, min: 1, max:
|
|
30412
|
+
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(),
|
|
30411
30413
|
offset: z2.number({ int: true, min: 0 }).describe("Rows to skip before the page; nonnegative integer; default 0.").optional()
|
|
30412
30414
|
})
|
|
30413
30415
|
},
|
|
@@ -32351,8 +32353,26 @@ class DispatchClient {
|
|
|
32351
32353
|
async resolveIssue(issueReference) {
|
|
32352
32354
|
return this.#resolveIssue(issueReference);
|
|
32353
32355
|
}
|
|
32354
|
-
async
|
|
32355
|
-
|
|
32356
|
+
async listIssuePage(options, page) {
|
|
32357
|
+
const answer = await this.#json("GET", ["api", "v1", "issues"], undefined, {
|
|
32358
|
+
...options,
|
|
32359
|
+
limit: page.limit,
|
|
32360
|
+
offset: page.offset
|
|
32361
|
+
});
|
|
32362
|
+
if (Array.isArray(answer)) {
|
|
32363
|
+
const issues = answer;
|
|
32364
|
+
return {
|
|
32365
|
+
issues: issues.slice(page.offset, page.offset + page.limit),
|
|
32366
|
+
total: issues.length,
|
|
32367
|
+
limit: page.limit,
|
|
32368
|
+
offset: page.offset
|
|
32369
|
+
};
|
|
32370
|
+
}
|
|
32371
|
+
const served = answer;
|
|
32372
|
+
if (typeof served !== "object" || served === null || !Array.isArray(served.issues) || typeof served.total !== "number" || typeof served.limit !== "number" || typeof served.offset !== "number") {
|
|
32373
|
+
throw new Error("GET /api/v1/issues answered neither a page ({issues, total, limit, offset}) nor an array of issues");
|
|
32374
|
+
}
|
|
32375
|
+
return served;
|
|
32356
32376
|
}
|
|
32357
32377
|
async listProjectArtifacts(project, unlinked = false) {
|
|
32358
32378
|
return this.#json("GET", ["api", "v1", "projects", project, "artifacts"], undefined, unlinked ? { unlinked: "true" } : undefined);
|
|
@@ -34002,9 +34022,7 @@ async function executeDispatchTool(input) {
|
|
|
34002
34022
|
const priority = optionalPriorityFilter(args, "priority");
|
|
34003
34023
|
const updatedSince = optionalString(args, "updated_since");
|
|
34004
34024
|
const routeStatus = optionalString(args, "route_status");
|
|
34005
|
-
const
|
|
34006
|
-
const offset = Math.max(optionalNumber(args, "offset") ?? 0, 0);
|
|
34007
|
-
const issues = await client.listIssues({
|
|
34025
|
+
const page = await client.listIssuePage({
|
|
34008
34026
|
project,
|
|
34009
34027
|
...status === undefined ? {} : { status },
|
|
34010
34028
|
...parent === undefined ? {} : { parent },
|
|
@@ -34012,9 +34030,12 @@ async function executeDispatchTool(input) {
|
|
|
34012
34030
|
...priority === undefined ? {} : { priority },
|
|
34013
34031
|
...updatedSince === undefined ? {} : { updated_since: updatedSince },
|
|
34014
34032
|
...routeStatus === undefined ? {} : { route_status: routeStatus }
|
|
34033
|
+
}, {
|
|
34034
|
+
limit: optionalNumber(args, "limit") ?? DEFAULT_ISSUE_PAGE_LIMIT,
|
|
34035
|
+
offset: optionalNumber(args, "offset") ?? 0
|
|
34015
34036
|
});
|
|
34016
|
-
const total =
|
|
34017
|
-
const rows = issues.
|
|
34037
|
+
const { total, limit, offset } = page;
|
|
34038
|
+
const rows = page.issues.map((row) => ({
|
|
34018
34039
|
key: row.key,
|
|
34019
34040
|
title: row.title,
|
|
34020
34041
|
status: row.status,
|
package/dist/legion.js
CHANGED
|
@@ -29823,6 +29823,8 @@ function serviceSubjectLabel(subject) {
|
|
|
29823
29823
|
}
|
|
29824
29824
|
return subject;
|
|
29825
29825
|
}
|
|
29826
|
+
var MAX_ISSUE_PAGE_LIMIT = 250;
|
|
29827
|
+
var DEFAULT_ISSUE_PAGE_LIMIT = 50;
|
|
29826
29828
|
var ASK_TURNS = ["human", "agent"];
|
|
29827
29829
|
var DELIVERY_CAPABILITIES = ["aside", "btw", "steer"];
|
|
29828
29830
|
var DELIVERY_DUPLICATE_WINDOW_MS = 72 * 60 * 60 * 1000;
|
|
@@ -30567,7 +30569,7 @@ var dispatchToolSpecs = [
|
|
|
30567
30569
|
{
|
|
30568
30570
|
name: "dispatch_issues",
|
|
30569
30571
|
example: { project: "AGENTC", limit: 250, offset: 250 },
|
|
30570
|
-
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, 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. " + "
|
|
30572
|
+
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, 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.",
|
|
30571
30573
|
arguments: (z2) => ({
|
|
30572
30574
|
project: z2.string().describe("Project key to list issues from."),
|
|
30573
30575
|
status: z2.enum(ISSUE_STATUSES).describe("Optional lifecycle status filter.").optional(),
|
|
@@ -30576,7 +30578,7 @@ var dispatchToolSpecs = [
|
|
|
30576
30578
|
priority: z2.array(z2.number({ int: true, min: 0, max: 3 }).nullable(), { min: 1, max: 5 }).describe("Optional priority filter: one or more of 0 (P0, highest) through 3 (P3, lowest), and null " + "for an issue with no priority; an issue matching any listed value is returned.").optional(),
|
|
30577
30579
|
updated_since: z2.string().describe("Optional RFC3339 timestamp; only issues updated at or after it.").optional(),
|
|
30578
30580
|
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(),
|
|
30579
|
-
limit: z2.number({ int: true, min: 1, max:
|
|
30581
|
+
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(),
|
|
30580
30582
|
offset: z2.number({ int: true, min: 0 }).describe("Rows to skip before the page; nonnegative integer; default 0.").optional()
|
|
30581
30583
|
})
|
|
30582
30584
|
},
|
|
@@ -31267,8 +31269,26 @@ class DispatchClient {
|
|
|
31267
31269
|
async resolveIssue(issueReference) {
|
|
31268
31270
|
return this.#resolveIssue(issueReference);
|
|
31269
31271
|
}
|
|
31270
|
-
async
|
|
31271
|
-
|
|
31272
|
+
async listIssuePage(options, page) {
|
|
31273
|
+
const answer = await this.#json("GET", ["api", "v1", "issues"], undefined, {
|
|
31274
|
+
...options,
|
|
31275
|
+
limit: page.limit,
|
|
31276
|
+
offset: page.offset
|
|
31277
|
+
});
|
|
31278
|
+
if (Array.isArray(answer)) {
|
|
31279
|
+
const issues = answer;
|
|
31280
|
+
return {
|
|
31281
|
+
issues: issues.slice(page.offset, page.offset + page.limit),
|
|
31282
|
+
total: issues.length,
|
|
31283
|
+
limit: page.limit,
|
|
31284
|
+
offset: page.offset
|
|
31285
|
+
};
|
|
31286
|
+
}
|
|
31287
|
+
const served = answer;
|
|
31288
|
+
if (typeof served !== "object" || served === null || !Array.isArray(served.issues) || typeof served.total !== "number" || typeof served.limit !== "number" || typeof served.offset !== "number") {
|
|
31289
|
+
throw new Error("GET /api/v1/issues answered neither a page ({issues, total, limit, offset}) nor an array of issues");
|
|
31290
|
+
}
|
|
31291
|
+
return served;
|
|
31272
31292
|
}
|
|
31273
31293
|
async listProjectArtifacts(project, unlinked = false) {
|
|
31274
31294
|
return this.#json("GET", ["api", "v1", "projects", project, "artifacts"], undefined, unlinked ? { unlinked: "true" } : undefined);
|
|
@@ -32941,9 +32961,7 @@ async function executeDispatchTool(input) {
|
|
|
32941
32961
|
const priority = optionalPriorityFilter(args, "priority");
|
|
32942
32962
|
const updatedSince = optionalString(args, "updated_since");
|
|
32943
32963
|
const routeStatus = optionalString(args, "route_status");
|
|
32944
|
-
const
|
|
32945
|
-
const offset = Math.max(optionalNumber(args, "offset") ?? 0, 0);
|
|
32946
|
-
const issues = await client.listIssues({
|
|
32964
|
+
const page = await client.listIssuePage({
|
|
32947
32965
|
project,
|
|
32948
32966
|
...status === undefined ? {} : { status },
|
|
32949
32967
|
...parent === undefined ? {} : { parent },
|
|
@@ -32951,9 +32969,12 @@ async function executeDispatchTool(input) {
|
|
|
32951
32969
|
...priority === undefined ? {} : { priority },
|
|
32952
32970
|
...updatedSince === undefined ? {} : { updated_since: updatedSince },
|
|
32953
32971
|
...routeStatus === undefined ? {} : { route_status: routeStatus }
|
|
32972
|
+
}, {
|
|
32973
|
+
limit: optionalNumber(args, "limit") ?? DEFAULT_ISSUE_PAGE_LIMIT,
|
|
32974
|
+
offset: optionalNumber(args, "offset") ?? 0
|
|
32954
32975
|
});
|
|
32955
|
-
const total =
|
|
32956
|
-
const rows = issues.
|
|
32976
|
+
const { total, limit, offset } = page;
|
|
32977
|
+
const rows = page.issues.map((row) => ({
|
|
32957
32978
|
key: row.key,
|
|
32958
32979
|
title: row.title,
|
|
32959
32980
|
status: row.status,
|
|
@@ -33502,7 +33523,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
33502
33523
|
// package.json
|
|
33503
33524
|
var package_default = {
|
|
33504
33525
|
name: "@sjawhar/pi-legion-envoy",
|
|
33505
|
-
version: "5.
|
|
33526
|
+
version: "5.24.0",
|
|
33506
33527
|
type: "module",
|
|
33507
33528
|
omp: {
|
|
33508
33529
|
extensions: [
|
|
@@ -26,3 +26,14 @@ A path Dispatch does not serve under `/api` or `/v1` answers
|
|
|
26
26
|
route"}`; when you see that, you typed the path wrong — read the index rather than guessing. Every
|
|
27
27
|
`/api/v1` error body carries a `code`; branch on the code, never on the text.
|
|
28
28
|
|
|
29
|
+
`GET /api/v1/issues` answers every matching issue as an array, or one page when you name `limit`
|
|
30
|
+
(1–250) or `offset` (alone it pages 50): `{issues, total, limit, offset}`, where `total` counts
|
|
31
|
+
every issue your filters match. Walking the pages with the next `offset` is exact only while the
|
|
32
|
+
listing does not change: an issue that enters or leaves what the filters match, or whose status or
|
|
33
|
+
rank changes, between two reads shifts rows across a page boundary, and one issue comes back twice
|
|
34
|
+
and another never, even when you stop at `total`. The unpaged array is the only exact set one read
|
|
35
|
+
gives. `cursor` is `400 INVALID_QUERY`. `dispatch_issues` pages for you.
|
|
36
|
+
The event logs page with `after` or `before` and `limit`, and `GET /api/v1/search` takes a `limit`
|
|
37
|
+
of at most 50 and has no next page. Every other route answers without paging and ignores a paging
|
|
38
|
+
parameter.
|
|
39
|
+
|
|
@@ -144,8 +144,11 @@ when it last changed — a roadmap or backlog pass without opening every issue.
|
|
|
144
144
|
(a lifecycle status), `parent` (one issue's children), `label`, `priority` (a list of `0`–`3`, with
|
|
145
145
|
`null` for an issue with no priority: `[0, 1]` is every P0 and P1), `route_status` (below), or
|
|
146
146
|
`updated_since` (an RFC3339 timestamp, for "what moved this week"). `limit` is the page size, 50 by
|
|
147
|
-
default and 250 at most, and `offset` is where the page starts
|
|
148
|
-
|
|
147
|
+
default and 250 at most, and `offset` is where the page starts; the answer says how many issues
|
|
148
|
+
match. Repeating with the next offset walks every matching issue only while the list does not
|
|
149
|
+
change: an issue that enters or leaves what your filters match, or whose status or rank changes,
|
|
150
|
+
between two pages shifts rows across a page boundary, and the walk then shows one issue twice and
|
|
151
|
+
misses another.
|
|
149
152
|
|
|
150
153
|
This is not search: it matches no text. Use `dispatch_search` for a keyword or phrase, and
|
|
151
154
|
`dispatch_issues` when you want every issue in a project and its current state.
|
|
@@ -59,7 +59,7 @@ only on this phase's artifact.
|
|
|
59
59
|
|
|
60
60
|
You never spawn another Legion role: spawning a worker
|
|
61
61
|
(`legion({op: "spawn_worker", ... })`) is architect-only. You may still use ordinary `task`
|
|
62
|
-
|
|
62
|
+
subagents for your own phase work; none of them is a Legion role.
|
|
63
63
|
Escalate a product, scope, cross-phase, or lifecycle decision to the owning architect with
|
|
64
64
|
`envoy_publish` to its role topic (`notifications.role.` followed by its encoded token, see
|
|
65
65
|
above), carrying the verified facts and the decision needed. `hub` only reaches subagents
|