@sjawhar/pi-legion-envoy 7.5.1 → 7.6.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/envoy.js CHANGED
@@ -30073,6 +30073,7 @@ var ASK_URGENCIES = ["low", "med", "high", "blocking"];
30073
30073
  var ASK_QUESTION_MAX = 800;
30074
30074
  var SEARCH_QUERY_MAX = 1000;
30075
30075
  var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passage";
30076
+ var SEARCH_KIND_DEPTH = 100;
30076
30077
  var PROJECT_KEY_PATTERN = /^[A-Z][A-Z0-9]{1,9}$/;
30077
30078
  var ISSUE_STATUSES = [
30078
30079
  "triage",
@@ -30408,7 +30409,7 @@ var dispatchToolSpecs = [
30408
30409
  {
30409
30410
  name: "dispatch_search",
30410
30411
  example: { query: "astrolabe" },
30411
- description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR.',
30412
+ description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR. ' + "Each kind of content is ranked on its own and the lists are merged, so the top holds the best " + "issue, document, ask, comment and message; an issue key searched alone lists that issue first. " + `The answer names how many results match; offset pages through them, and each kind lists at most its best ${SEARCH_KIND_DEPTH}, ` + "so narrow the query or name a project to reach the rest. Paging is exact only while the " + "corpus holds still: content added, changed, or removed between two offsets can shift rows " + "across a page boundary, so one hit can come back twice and another never.",
30412
30413
  arguments: (z2) => ({
30413
30414
  query: z2.string({
30414
30415
  min: 2,
@@ -30416,7 +30417,8 @@ var dispatchToolSpecs = [
30416
30417
  maxHint: SEARCH_QUERY_HINT
30417
30418
  }).describe(`Keyword, phrase, or websearch expression; 2 to ${SEARCH_QUERY_MAX} characters.`),
30418
30419
  project: z2.string().describe("Optional project key to search within.").optional(),
30419
- limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional()
30420
+ limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional(),
30421
+ offset: z2.number({ int: true, min: 0 }).describe("Results to skip before the page; nonnegative integer; default 0.").optional()
30420
30422
  }),
30421
30423
  validation: {
30422
30424
  check: (value) => {
@@ -32513,6 +32515,71 @@ class DispatchClient {
32513
32515
  }
32514
32516
  }
32515
32517
 
32518
+ // ../envoy-client/src/search-answer.ts
32519
+ function pageSummaryText(offset, count, total) {
32520
+ return `showing ${offset + 1}-${offset + count} of ${total}`;
32521
+ }
32522
+ function searchResultLine(result, baseUrl) {
32523
+ const href = new URL(result.href, baseUrl).toString();
32524
+ const { owner } = result;
32525
+ if (owner.kind === "document") {
32526
+ const reference = dispatchDocumentRef(owner.project, owner.slug);
32527
+ return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
32528
+ }
32529
+ const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
32530
+ const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
32531
+ return `${label}: ${snippetText(result.snippet)} -> ${href}`;
32532
+ }
32533
+ function searchAnswer(search, query, offset, configUrl) {
32534
+ const results = search.results;
32535
+ const count = results.length;
32536
+ const lines = results.map((result) => searchResultLine(result, configUrl));
32537
+ const noun = count === 1 ? "result" : "results";
32538
+ const noResults = `No results for "${query}".`;
32539
+ if (typeof search.total !== "number") {
32540
+ if (offset !== undefined && offset > 0) {
32541
+ throw new Error(`Dispatch answered without a total: it predates search paging and ignored offset ${offset}, so this would be its first page again.`);
32542
+ }
32543
+ return {
32544
+ text: count === 0 ? noResults : [`${count} ${noun} for "${query}" (${search.took_ms} ms)`, ...lines].join(`
32545
+ `),
32546
+ details: { query, results }
32547
+ };
32548
+ }
32549
+ const { total, reachable, offset: pageOffset } = search;
32550
+ const end = pageOffset + count;
32551
+ const cut = reachable < total ? `Each kind lists only its best ${SEARCH_KIND_DEPTH} matches, so ${reachable} of the ${total} can be paged to; narrow the query or name a project to reach the rest.` : undefined;
32552
+ const details = {
32553
+ query,
32554
+ results,
32555
+ total,
32556
+ reachable,
32557
+ offset: pageOffset,
32558
+ limit: search.limit
32559
+ };
32560
+ if (count === 0) {
32561
+ return {
32562
+ text: total === 0 ? noResults : [
32563
+ `No results for "${query}" at offset ${pageOffset}: it matches ${total}, and the pages reach the first ${reachable}.`,
32564
+ ...cut === undefined ? [] : [cut]
32565
+ ].join(`
32566
+ `),
32567
+ details
32568
+ };
32569
+ }
32570
+ const showing = pageOffset === 0 && count === total ? "" : `${pageSummaryText(pageOffset, count, total)}, `;
32571
+ return {
32572
+ text: [
32573
+ `${count} ${noun} for "${query}" (${showing}${search.took_ms} ms)`,
32574
+ ...cut === undefined ? [] : [cut],
32575
+ ...lines,
32576
+ ...end < reachable ? [`Next page: offset ${end}.`] : []
32577
+ ].join(`
32578
+ `),
32579
+ details
32580
+ };
32581
+ }
32582
+
32516
32583
  // ../envoy-client/src/tool-input-errors.ts
32517
32584
  class ToolInputError extends Error {
32518
32585
  tool;
@@ -32822,17 +32889,6 @@ function duplicateCandidates(error48) {
32822
32889
  throw error48;
32823
32890
  return error48.candidates;
32824
32891
  }
32825
- function searchResultLine(result, baseUrl) {
32826
- const href = new URL(result.href, baseUrl).toString();
32827
- const { owner } = result;
32828
- if (owner.kind === "document") {
32829
- const reference = dispatchDocumentRef(owner.project, owner.slug);
32830
- return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
32831
- }
32832
- const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
32833
- const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
32834
- return `${label}: ${snippetText(result.snippet)} -> ${href}`;
32835
- }
32836
32892
  function askUrgency(args) {
32837
32893
  const value = args.urgency;
32838
32894
  return ASK_URGENCIES.find((urgency) => urgency === value);
@@ -34031,20 +34087,13 @@ async function executeDispatchTool(input) {
34031
34087
  const query = stringArg(args, "query");
34032
34088
  const project = optionalString(args, "project");
34033
34089
  const limit = optionalNumber(args, "limit");
34090
+ const offset = optionalNumber(args, "offset");
34034
34091
  const search = await client.search(query, {
34035
34092
  ...project === undefined ? {} : { project },
34036
- ...limit === undefined ? {} : { limit }
34093
+ ...limit === undefined ? {} : { limit },
34094
+ ...offset === undefined ? {} : { offset }
34037
34095
  });
34038
- const results = search.results;
34039
- const count = results.length;
34040
- return {
34041
- text: count === 0 ? `No results for "${query}".` : [
34042
- `${count} ${count === 1 ? "result" : "results"} for "${query}" (${search.took_ms} ms)`,
34043
- ...results.map((result) => searchResultLine(result, configUrl))
34044
- ].join(`
34045
- `),
34046
- details: { query, results }
34047
- };
34096
+ return searchAnswer(search, query, offset, configUrl);
34048
34097
  }
34049
34098
  case "dispatch_issues": {
34050
34099
  const project = stringArg(args, "project");
@@ -34084,7 +34133,7 @@ async function executeDispatchTool(input) {
34084
34133
  }));
34085
34134
  const titles = await liveSessionTitles(client, rows.some((row) => holdsSession(row.claim)));
34086
34135
  const isPartial = offset !== 0 || rows.length !== total;
34087
- const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` : `showing ${offset + 1}-${offset + rows.length} of ${total}`;
34136
+ const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` : pageSummaryText(offset, rows.length, total);
34088
34137
  return {
34089
34138
  text: rows.length === 0 ? `No issues in ${project}.${isPartial ? ` (${showing})` : ""}` : [
34090
34139
  `${rows.length} ${rows.length === 1 ? "issue" : "issues"} in ${project}` + (isPartial ? ` (${showing})` : ""),
package/dist/legion.js CHANGED
@@ -14160,6 +14160,7 @@ var ASK_URGENCIES = ["low", "med", "high", "blocking"];
14160
14160
  var ASK_QUESTION_MAX = 800;
14161
14161
  var SEARCH_QUERY_MAX = 1000;
14162
14162
  var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passage";
14163
+ var SEARCH_KIND_DEPTH = 100;
14163
14164
  var PROJECT_KEY_PATTERN = /^[A-Z][A-Z0-9]{1,9}$/;
14164
14165
  var ISSUE_STATUSES = [
14165
14166
  "triage",
@@ -14495,7 +14496,7 @@ var dispatchToolSpecs = [
14495
14496
  {
14496
14497
  name: "dispatch_search",
14497
14498
  example: { query: "astrolabe" },
14498
- description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR.',
14499
+ description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR. ' + "Each kind of content is ranked on its own and the lists are merged, so the top holds the best " + "issue, document, ask, comment and message; an issue key searched alone lists that issue first. " + `The answer names how many results match; offset pages through them, and each kind lists at most its best ${SEARCH_KIND_DEPTH}, ` + "so narrow the query or name a project to reach the rest. Paging is exact only while the " + "corpus holds still: content added, changed, or removed between two offsets can shift rows " + "across a page boundary, so one hit can come back twice and another never.",
14499
14500
  arguments: (z2) => ({
14500
14501
  query: z2.string({
14501
14502
  min: 2,
@@ -14503,7 +14504,8 @@ var dispatchToolSpecs = [
14503
14504
  maxHint: SEARCH_QUERY_HINT
14504
14505
  }).describe(`Keyword, phrase, or websearch expression; 2 to ${SEARCH_QUERY_MAX} characters.`),
14505
14506
  project: z2.string().describe("Optional project key to search within.").optional(),
14506
- limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional()
14507
+ limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional(),
14508
+ offset: z2.number({ int: true, min: 0 }).describe("Results to skip before the page; nonnegative integer; default 0.").optional()
14507
14509
  }),
14508
14510
  validation: {
14509
14511
  check: (value) => {
@@ -15298,6 +15300,71 @@ function dispatchChildRef(ownerRef, kind, id) {
15298
15300
  return `${ownerRef}/${kind}/${id}`;
15299
15301
  }
15300
15302
 
15303
+ // ../envoy-client/src/search-answer.ts
15304
+ function pageSummaryText(offset, count, total) {
15305
+ return `showing ${offset + 1}-${offset + count} of ${total}`;
15306
+ }
15307
+ function searchResultLine(result, baseUrl) {
15308
+ const href = new URL(result.href, baseUrl).toString();
15309
+ const { owner } = result;
15310
+ if (owner.kind === "document") {
15311
+ const reference = dispatchDocumentRef(owner.project, owner.slug);
15312
+ return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
15313
+ }
15314
+ const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
15315
+ const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
15316
+ return `${label}: ${snippetText(result.snippet)} -> ${href}`;
15317
+ }
15318
+ function searchAnswer(search, query, offset, configUrl) {
15319
+ const results = search.results;
15320
+ const count = results.length;
15321
+ const lines = results.map((result) => searchResultLine(result, configUrl));
15322
+ const noun = count === 1 ? "result" : "results";
15323
+ const noResults = `No results for "${query}".`;
15324
+ if (typeof search.total !== "number") {
15325
+ if (offset !== undefined && offset > 0) {
15326
+ throw new Error(`Dispatch answered without a total: it predates search paging and ignored offset ${offset}, so this would be its first page again.`);
15327
+ }
15328
+ return {
15329
+ text: count === 0 ? noResults : [`${count} ${noun} for "${query}" (${search.took_ms} ms)`, ...lines].join(`
15330
+ `),
15331
+ details: { query, results }
15332
+ };
15333
+ }
15334
+ const { total, reachable, offset: pageOffset } = search;
15335
+ const end = pageOffset + count;
15336
+ const cut = reachable < total ? `Each kind lists only its best ${SEARCH_KIND_DEPTH} matches, so ${reachable} of the ${total} can be paged to; narrow the query or name a project to reach the rest.` : undefined;
15337
+ const details = {
15338
+ query,
15339
+ results,
15340
+ total,
15341
+ reachable,
15342
+ offset: pageOffset,
15343
+ limit: search.limit
15344
+ };
15345
+ if (count === 0) {
15346
+ return {
15347
+ text: total === 0 ? noResults : [
15348
+ `No results for "${query}" at offset ${pageOffset}: it matches ${total}, and the pages reach the first ${reachable}.`,
15349
+ ...cut === undefined ? [] : [cut]
15350
+ ].join(`
15351
+ `),
15352
+ details
15353
+ };
15354
+ }
15355
+ const showing = pageOffset === 0 && count === total ? "" : `${pageSummaryText(pageOffset, count, total)}, `;
15356
+ return {
15357
+ text: [
15358
+ `${count} ${noun} for "${query}" (${showing}${search.took_ms} ms)`,
15359
+ ...cut === undefined ? [] : [cut],
15360
+ ...lines,
15361
+ ...end < reachable ? [`Next page: offset ${end}.`] : []
15362
+ ].join(`
15363
+ `),
15364
+ details
15365
+ };
15366
+ }
15367
+
15301
15368
  // ../envoy-client/src/tool-input-errors.ts
15302
15369
  class ToolInputError extends Error {
15303
15370
  tool;
@@ -15607,17 +15674,6 @@ function duplicateCandidates(error48) {
15607
15674
  throw error48;
15608
15675
  return error48.candidates;
15609
15676
  }
15610
- function searchResultLine(result, baseUrl) {
15611
- const href = new URL(result.href, baseUrl).toString();
15612
- const { owner } = result;
15613
- if (owner.kind === "document") {
15614
- const reference = dispatchDocumentRef(owner.project, owner.slug);
15615
- return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
15616
- }
15617
- const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
15618
- const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
15619
- return `${label}: ${snippetText(result.snippet)} -> ${href}`;
15620
- }
15621
15677
  function askUrgency(args) {
15622
15678
  const value = args.urgency;
15623
15679
  return ASK_URGENCIES.find((urgency) => urgency === value);
@@ -16816,20 +16872,13 @@ async function executeDispatchTool(input) {
16816
16872
  const query = stringArg(args, "query");
16817
16873
  const project = optionalString(args, "project");
16818
16874
  const limit = optionalNumber(args, "limit");
16875
+ const offset = optionalNumber(args, "offset");
16819
16876
  const search = await client.search(query, {
16820
16877
  ...project === undefined ? {} : { project },
16821
- ...limit === undefined ? {} : { limit }
16878
+ ...limit === undefined ? {} : { limit },
16879
+ ...offset === undefined ? {} : { offset }
16822
16880
  });
16823
- const results = search.results;
16824
- const count = results.length;
16825
- return {
16826
- text: count === 0 ? `No results for "${query}".` : [
16827
- `${count} ${count === 1 ? "result" : "results"} for "${query}" (${search.took_ms} ms)`,
16828
- ...results.map((result) => searchResultLine(result, configUrl))
16829
- ].join(`
16830
- `),
16831
- details: { query, results }
16832
- };
16881
+ return searchAnswer(search, query, offset, configUrl);
16833
16882
  }
16834
16883
  case "dispatch_issues": {
16835
16884
  const project = stringArg(args, "project");
@@ -16869,7 +16918,7 @@ async function executeDispatchTool(input) {
16869
16918
  }));
16870
16919
  const titles = await liveSessionTitles(client, rows.some((row) => holdsSession(row.claim)));
16871
16920
  const isPartial = offset !== 0 || rows.length !== total;
16872
- const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` : `showing ${offset + 1}-${offset + rows.length} of ${total}`;
16921
+ const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` : pageSummaryText(offset, rows.length, total);
16873
16922
  return {
16874
16923
  text: rows.length === 0 ? `No issues in ${project}.${isPartial ? ` (${showing})` : ""}` : [
16875
16924
  `${rows.length} ${rows.length === 1 ? "issue" : "issues"} in ${project}` + (isPartial ? ` (${showing})` : ""),
@@ -17475,7 +17524,7 @@ function endInjectedUserTurns(sessionID) {
17475
17524
  // package.json
17476
17525
  var package_default = {
17477
17526
  name: "@sjawhar/pi-legion-envoy",
17478
- version: "7.5.1",
17527
+ version: "7.6.0",
17479
17528
  license: "Apache-2.0",
17480
17529
  type: "module",
17481
17530
  omp: {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dispatch
3
- description: "Use before posting a message, a status update, or a periodic status update; before asking a question that references another message, artifact, or eval; before asking a design question or brainstorming a change; and when asking Sami a question, updating the spec, commenting on a document, attaching an artifact, or calling a dispatch_* tool."
3
+ description: "Use before posting a message, a status update, or a periodic status update; before asking a question that references another message, artifact, or eval; before asking a design question or brainstorming a change; and when asking the human a question, updating the spec, commenting on a document, attaching an artifact, or calling a dispatch_* tool."
4
4
  ---
5
5
 
6
6
  # Dispatch
@@ -66,7 +66,7 @@ not share this session's vocabulary, and is often on a phone. Write for that per
66
66
  has a human subject, even on a sentence you already simplified; a lead naming what a change does
67
67
  inside a system leaves the reader nothing to act on. Where the judgment rule below applies, the
68
68
  judgment leads and this rule shapes the sentence under it.
69
- - Before posting, test it: could Sami, reading only this text on his phone, know what he is being
69
+ - Before posting, test it: could the human, reading only this text on their phone, know what they are being
70
70
  told or asked? If not, rewrite it. Length is not the problem; density is.
71
71
  - When an ask or message communicates a judgment, lead with that judgment in one sentence and put the mechanism underneath it. Do not make the reader ask a second time whether the result is a win. This shapes communication only when a judgment exists; it does not pre-decide an open question or remove its genuine options.
72
72
  - When a Dispatch message states a root cause, include the reproducing command or test in that same message. Without it, label the diagnosis a hypothesis; a diagnosis still in progress may say so plainly. This boundary applies to causal claims, not to reporting that an investigation has started.
@@ -160,15 +160,14 @@ follow [Writing a spec](#writing-a-spec).
160
160
  you plan, start a design document, file an issue, ask, post a finding or start work, and what to
161
161
  do with each hit. What it leaves out:
162
162
  ```ts
163
- dispatch_search({ query, project?, limit? })
163
+ dispatch_search({ query, project?, limit?, offset? })
164
164
  ```
165
- Websearch syntax applies: `"merge queue"`, `-daemon`, `OR`. It returns the best `limit` hits (20
166
- by default, 50 at most) across issues, documents, comments, asks, and messages. A query over
167
- 1,000 characters is refused before it is sent: search with the few words `skill://dispatch-first`
168
- describes, never a pasted passage. Issue-owned hit lines start with the issue key; standalone
169
- project-document hit lines start with `dispatch://PROJECT/artifact/<slug>`, followed by the
170
- absolute link. Cite the hit you build on (`dispatch://KEY` or the document reference), or state
171
- "no prior issue" in the spec.
165
+ Websearch syntax applies: `"merge queue"`, `-daemon`, `OR`. Issues, documents, asks, comments, and messages rank separately; the
166
+ merged page orders each kind's best in turn — issue, document, ask, comment, message — and a bare issue key ranks first. A page
167
+ holds `limit` hits (20 default, 50 max); the first line gives the total (`showing 1-20 of 312`); see
168
+ [Search paging](skill://dispatch/references/issues.md#search-paging) for more. Queries over 1,000 characters are refused: use the
169
+ few words `skill://dispatch-first` names, never a pasted passage. Issue hits start with the issue key; document hits start with
170
+ `dispatch://PROJECT/artifact/<slug>`, then the link. Cite the hit (`dispatch://KEY` or the doc ref) or say "no prior issue".
172
171
 
173
172
  `dispatch_issue` refuses a title that near-duplicates an issue in the same project and returns the candidates (`POSSIBLE_DUPLICATE`).
174
173
  Read them; reference the existing issue, or repeat the call with `force: true` when it is genuinely new work.
@@ -228,7 +227,7 @@ Every `dispatch_ask` passes four gates first:
228
227
  internals are your lane's to decide where the work happens, in the plan or the code, not in the
229
228
  spec. A contract between two lanes is settled by those two lanes over Envoy, and you open no ask
230
229
  for it. A halt condition (a change to IAM, deletion or exposure of production data, anything
231
- that reaches a customer) passes this gate: it is your own `dispatch_ask` to Sami on your own
230
+ that reaches a customer) passes this gate: it is your own `dispatch_ask` to the human on your own
232
231
  issue.
233
232
  2. **Is there genuine uncertainty, and have you measured what you can?** If there is none, it is
234
233
  a plan you execute. The one legitimate ask without uncertainty is permission for an action
@@ -323,7 +322,7 @@ they must read to decide belongs in the spec in the first place — see [Artifac
323
322
 
324
323
  Before saying you are waiting for human input, call `dispatch_open_asks`. With no arguments it lists this session's active asks across open issues and project documents, including whether the human or agent owes the next reply. With `dispatch_open_asks({ project })` it lists every open ask in that project — on its issues and on its documents, whoever authored them — which is how you see what a whole project is waiting on rather than just your own asks.
325
324
 
326
- **Unsettled product shape needs a decision before implementation.** When a page, navigation entry, table key, customer-scoping rule, or persisted sidecar would set product shape that Sami has not already settled, write a decision block in the document that records the work before the first implementation commit; in a Legion tree the architect writes it, and a phase worker sends the decision to its architect. A lane's schema decision or a contract two lanes agree does not settle product shape. This does not turn a user-specified decision or routine implementation into an approval request. A control or behaviour the human asked for in words is settled by those words, together with every choice inside it that his words do not make (where it sits, its defaults, its options): build it without a block, as gate 4 of [Before you ask](#before-you-ask) says. This rule covers only product shape outside what he asked for, and its block comes before the commit that sets that shape.
325
+ **Unsettled product shape needs a decision before implementation.** When a page, navigation entry, table key, customer-scoping rule, or persisted sidecar would set product shape that the human has not already settled, write a decision block in the document that records the work before the first implementation commit; in a Legion tree the architect writes it, and a phase worker sends the decision to its architect. A lane's schema decision or a contract two lanes agree does not settle product shape. This does not turn a user-specified decision or routine implementation into an approval request. A control or behaviour the human asked for in words is settled by those words, together with every choice inside it that his words do not make (where it sits, its defaults, …
327
326
 
328
327
  **Anything you are blocked on a human for is visible in Dispatch.** An agent waits on a human only
329
328
  through an open ask. A to-do, permission, credential or grant renewal, setting only they can
@@ -359,7 +358,7 @@ Use `mode: "none"` with a concrete reason only when the work is genuinely non-ar
359
358
  ## Close what you opened
360
359
 
361
360
  An ask you opened is yours until it is answered or you resolve it. When the answer arrives some
362
- other way — Sami said it live, a later comment settled it, or the question became moot because the
361
+ other way — the human said it live, a later comment settled it, or the question became moot because the
363
362
  design moved — resolve it yourself with `dispatch_resolve_ask` in the same turn you learn that.
364
363
  Never leave it for the human to clear.
365
364
 
@@ -9,13 +9,12 @@ project's architecture model, or list or audit a project's backlog.
9
9
  When you finish an issue, or are told to work on the next thing, take the top ready issue of the
10
10
  whole backlog, across every project: status `todo`, highest priority first, then board rank. There
11
11
  are no areas: a standing role, a product owner and a lane each take the top issue like everyone
12
- else (Sami's ruling). `todo` means ready: specced, unblocked, and waiting on neither a deploy nor a
12
+ else. `todo` means ready: specced, unblocked, and waiting on neither a deploy nor a
13
13
  decision. An issue that waits on one belongs in `backlog`, with what it waits on said on the issue.
14
14
 
15
15
  Hold at most three issues in flight (`in_progress`, `testing`, `needs_review` or `retro`), of any
16
- kind (Sami's ruling). The limit is per agent and has nothing to do with the week's priorities
17
- (Sami, correcting a reading that tied the two together): the priorities decide only what you pull
18
- next. Past three:
16
+ kind. The limit is per agent and has nothing to do with the week's priorities: the priorities
17
+ decide only what you pull next. Past three:
19
18
  push any unfinished work, say where in one comment on the issue, move it to `backlog` and clear
20
19
  its route. Each issue counts on its own; a child does not ride under its parent's slot.
21
20
  In-flight issues with no owner at all go
@@ -27,7 +26,7 @@ writes on the root of a tree Legion is running is set back and the tree's archit
27
26
  it; only a person in the dashboard, or `legion status`, stops that tree. On an issue under one, a
28
27
  status that takes it out of the flow parks that issue and stops its workers.
29
28
 
30
- One agent keeps the backlog's order against those priorities, with Sami, as he ruled. Setting an
29
+ One agent keeps the backlog's order against those priorities. Setting an
31
30
  issue's priority stays yours ([Priority is yours to set](#priority-is-yours-to-set)); reordering
32
31
  the board does not.
33
32
  When the top of the backlog looks wrong, or a priority's next step is not yet a ready issue,
@@ -118,7 +117,7 @@ when work has started.
118
117
  Priority is the coarse bucket a backlog is read by: `0` is P0, the highest, through `3`, P3, the
119
118
  lowest, and `null` clears it. Agents set it (`dispatch://LEGION/artifact/issue-status-conventions-md`)
120
119
  — on creation, and on a grooming pass over issues that have none — and say what you set and why;
121
- Sami overrides anything he disagrees with from the dashboard. A closed
120
+ the human overrides anything they disagree with from the dashboard. A closed
122
121
  issue takes only `rank`, `components`, and a reopening `status` (any status but `done`);
123
122
  everything else, `priority` included, waits for the reopen (`409 ISSUE_CLOSED`). So reopen it
124
123
  first, then set the priority — the two cannot go in one call. `rank` itself is not a tool field:
@@ -194,6 +193,16 @@ The audit finds four shapes:
194
193
  Run the audit as a step of a coordinator's loop, at each checkpoint, not as a habit: these shapes
195
194
  are found by running the check, not by noticing them.
196
195
 
196
+ ## Search paging
197
+
198
+ `skill://dispatch` sends you here when a `dispatch_search` page is not enough: more hits remain,
199
+ or a kind runs out before the page does.
200
+
201
+ A page holds `limit` hits (20 by default, 50 at most); the first line names how many match
202
+ (`showing 1-20 of 312`), and, while more can be reached, the last line names the next `offset`.
203
+ Each kind lists at most its best 100, so when the result says the rest cannot be paged to, narrow
204
+ the query or name a `project`.
205
+
197
206
  ## Syncing a project's architecture model
198
207
 
199
208
  Import a project's architecture model from its configured source repository now (a human configures the source in Settings):
@@ -79,7 +79,7 @@ exercise a criterion end to end, building that path is a child issue of this tre
79
79
 
80
80
  Specifications written into Dispatch follow `skill://dispatch`'s [Writing a spec](../dispatch/SKILL.md#writing-a-spec).
81
81
  Wave releases, child closures, and your own status are visible from the issue tree and the
82
- handoffs; do not narrate them into the spec or a `dispatch_message`. A to-do only Sami can clear
82
+ handoffs; do not narrate them into the spec or a `dispatch_message`. A to-do only a human can clear
83
83
  is a `dispatch_ask`.
84
84
 
85
85
  The issue's primary document **is** the root specification. Extend it in place: a new version
@@ -338,7 +338,7 @@ cross-tree conflict. Report those to the controller with `envoy_publish` to the
338
338
  your `Legion addressing` line names. Handle everything else in the
339
339
  tree. A product, scope, or design decision that needs the human, yours or one a worker escalated,
340
340
  is a decision block you write (section 1 says what one does to the root spec's gate). A standalone
341
- human to-do may use `dispatch_ask`; workers may reach Sami directly with it the same way. Do not
341
+ human to-do may use `dispatch_ask`; workers may reach the human directly with it the same way. Do not
342
342
  create a wait loop for any wake source.
343
343
 
344
344
  Never yield while waiting on a human. A human is waiting on you only where an open ask sits in
@@ -16,10 +16,10 @@ The Legion extension claims `legion-<project>-controller` and registers controll
16
16
  with the daemon during session startup. Do not handle a wake unless that startup succeeded.
17
17
 
18
18
  The daemon runs the controller as an interactive OMP terminal session in its private tmux
19
- server (the pane runs plain `omp`, not `--mode rpc`, and no `legion worker-shim`; Sami reaches
19
+ server (the pane runs plain `omp`, not `--mode rpc`, and no `legion worker-shim`; the operator reaches
20
20
  it with `tmux -L legion-<project> select-window -t <window id> \; attach -t legion-<project>`,
21
21
  the window id being `controllerLocator.tmuxWindowId` in `legion state --json` — every window
22
- opens detached, so a bare `attach` lands on whichever window is current). Sami may attach and
22
+ opens detached, so a bare `attach` lands on whichever window is current). The operator may attach and
23
23
  type into this session at any time. The pane carries no GitHub credential: its GitHub token
24
24
  variables are emptied, and both `legion gh -- <args>` and `legion threads resolve` are refused.
25
25
  The controller reads Dispatch and applies its controller capability with `legion status <KEY>
@@ -29,9 +29,8 @@ retrospective's durable output.
29
29
  role when configured. A human merges under the repository's GitHub branch-protection and
30
30
  CODEOWNERS requirements; GitHub's merge queue participates only when the repository enables it.
31
31
  5. After that merge, the implementer — not the reviewer or merger — verifies the change in production
32
- and records it on the PR and the issue (Sami, 2026-09-13, verbatim:
33
- "the agent that developed it should be responsible for testing in production"). The
34
- architect's sign-off waits for that record.
32
+ and records it on the PR and the issue: the agent that developed it is responsible for testing
33
+ in production. The architect's sign-off waits for that record.
35
34
  The record is the pull request's `Production:` line, one pull-request comment, and a
36
35
  `dispatch_message` on the issue, each naming what was driven, how, what was observed, and the
37
36
  merge commit. A defect the production check finds becomes a corrective child issue of the same tree,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "7.5.1",
3
+ "version": "7.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "omp": {