@tangle-network/agent-runtime 0.190.0 → 0.192.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/{activation-BgRWFKZi.js → activation-D9WGuwcK.js} +2 -2
  2. package/dist/{activation-BgRWFKZi.js.map → activation-D9WGuwcK.js.map} +1 -1
  3. package/dist/agent.d.ts +1 -1
  4. package/dist/agent.js +2 -2
  5. package/dist/{authoring-dQQ2z1fT.js → authoring-BzMyFv9z.js} +7 -3
  6. package/dist/{authoring-dQQ2z1fT.js.map → authoring-BzMyFv9z.js.map} +1 -1
  7. package/dist/conversation.d.ts +1 -1
  8. package/dist/{coordination-driver-Da1IGdTP.js → coordination-driver-_wzRdIK_.js} +668 -35
  9. package/dist/coordination-driver-_wzRdIK_.js.map +1 -0
  10. package/dist/durable.d.ts +2 -2
  11. package/dist/durable.js +1 -1
  12. package/dist/{environment-provider-Bf-hgR6p.d.ts → environment-provider-CJ43FJfJ.d.ts} +2 -2
  13. package/dist/{environment-provider-DcelIawX.js → environment-provider-DEwsolvi.js} +43 -6
  14. package/dist/environment-provider-DEwsolvi.js.map +1 -0
  15. package/dist/environment-provider.d.ts +1 -1
  16. package/dist/environment-provider.js +1 -1
  17. package/dist/{graph-Bd1elhdL.js → graph-y0AW_Txn.js} +3 -2
  18. package/dist/graph-y0AW_Txn.js.map +1 -0
  19. package/dist/graph.d.ts +3 -3
  20. package/dist/graph.js +3 -3
  21. package/dist/{redact-Cv1GfOFO.d.ts → improve-Cbbt3jE6.d.ts} +4 -36
  22. package/dist/{improvement-cycle-BanlFMyL.js → improvement-cycle-BPyj98Id.js} +3 -3
  23. package/dist/{improvement-cycle-BanlFMyL.js.map → improvement-cycle-BPyj98Id.js.map} +1 -1
  24. package/dist/{index-DpNil9jh.d.ts → index-B_fEqr5V.d.ts} +328 -13
  25. package/dist/{index-C-gBk5M9.d.ts → index-COznK4kB.d.ts} +4 -4
  26. package/dist/{index-Clb7_qML.d.ts → index-OSRVFARy.d.ts} +2 -2
  27. package/dist/index.d.ts +9 -8
  28. package/dist/index.js +7 -8
  29. package/dist/index.js.map +1 -1
  30. package/dist/intelligence.d.ts +5 -4
  31. package/dist/intelligence.js +4 -5
  32. package/dist/intelligence.js.map +1 -1
  33. package/dist/kernel.d.ts +6 -6
  34. package/dist/kernel.js +9 -9
  35. package/dist/{knowledge-BXdXlv48.js → knowledge-BIo_3VwO.js} +3 -3
  36. package/dist/{knowledge-BXdXlv48.js.map → knowledge-BIo_3VwO.js.map} +1 -1
  37. package/dist/knowledge.d.ts +1 -1
  38. package/dist/knowledge.js +1 -1
  39. package/dist/{loop-runner-bin-CGQzzWAc.js → loop-runner-bin-C-FnTrM8.js} +3 -3
  40. package/dist/{loop-runner-bin-CGQzzWAc.js.map → loop-runner-bin-C-FnTrM8.js.map} +1 -1
  41. package/dist/{loop-runner-bin-Cy4f9M6p.d.ts → loop-runner-bin-DiiZMHw7.d.ts} +3 -3
  42. package/dist/loop-runner-bin.d.ts +1 -1
  43. package/dist/loop-runner-bin.js +1 -1
  44. package/dist/mcp/bin.js +3 -3
  45. package/dist/mcp/index.d.ts +4 -4
  46. package/dist/mcp/index.js +6 -6
  47. package/dist/{openai-tools-BSDk_umV.js → openai-tools-PzShuaP4.js} +2 -2
  48. package/dist/{openai-tools-BSDk_umV.js.map → openai-tools-PzShuaP4.js.map} +1 -1
  49. package/dist/primeintellect/index.d.ts +1 -1
  50. package/dist/{provision-supervisor-bwpzajiz.js → provision-supervisor-DkVH1f9I.js} +4 -4
  51. package/dist/{provision-supervisor-bwpzajiz.js.map → provision-supervisor-DkVH1f9I.js.map} +1 -1
  52. package/dist/{supervisor-CqJW4qap.js → redact-h5zAxyNV.js} +179 -12
  53. package/dist/redact-h5zAxyNV.js.map +1 -0
  54. package/dist/{runtime-Ddh7PyCy.js → runtime-1JqxyKDI.js} +84 -30
  55. package/dist/runtime-1JqxyKDI.js.map +1 -0
  56. package/dist/{runtime-BMWmSu4A.d.ts → runtime-DI4mJNXI.d.ts} +3 -3
  57. package/dist/{stream-agent-turn-uqzf9ZEh.d.ts → stream-agent-turn-_GgUW49y.d.ts} +35 -3
  58. package/dist/{structural-rollout-0NKvKuFm.js → structural-rollout-CunhVUWJ.js} +2 -2
  59. package/dist/{structural-rollout-0NKvKuFm.js.map → structural-rollout-CunhVUWJ.js.map} +1 -1
  60. package/dist/{supervise-yDiZAqlN.js → supervise-Bx1Ps8xQ.js} +21 -6
  61. package/dist/supervise-Bx1Ps8xQ.js.map +1 -0
  62. package/dist/testing.d.ts +2 -2
  63. package/dist/testing.js +12 -12
  64. package/dist/tui/index.d.ts +1 -1
  65. package/dist/tui/index.js +1 -1
  66. package/dist/{types-02hXqnSP.d.ts → types-DKA_PDv0.d.ts} +27 -2
  67. package/package.json +1 -1
  68. package/dist/coordination-driver-Da1IGdTP.js.map +0 -1
  69. package/dist/environment-provider-DcelIawX.js.map +0 -1
  70. package/dist/graph-Bd1elhdL.js.map +0 -1
  71. package/dist/redact-D-u-rrcn.js +0 -82
  72. package/dist/redact-D-u-rrcn.js.map +0 -1
  73. package/dist/runtime-Ddh7PyCy.js.map +0 -1
  74. package/dist/supervise-yDiZAqlN.js.map +0 -1
  75. package/dist/supervisor-CqJW4qap.js.map +0 -1
@@ -4,7 +4,7 @@ import { t as detachedFrozen } from "./snapshot-CTAf4uuA.js";
4
4
  import { b as workerTraceAnalysisStore, i as InMemorySpawnJournal, n as FileSpawnJournal, r as InMemoryResultBlobStore, t as FileResultBlobStore, y as parseWorkerToolTraceArtifact } from "./spawn-journal-B9eTmVAn.js";
5
5
  import { r as chargedTokens, u as promptCacheTokenClasses, v as unmeteredSpend } from "./util-mCNUeboW.js";
6
6
  import { i as writeAllBytes, n as parseCommittedJsonLines, r as prepareJsonlAppend, t as isNoEntError } from "./jsonl-file-CDfsCI5s.js";
7
- import { Dt as createPeerMailbox, J as assertValidBudget, Kt as runBrainLoop, Mt as isTerminalNodeStatus, St as createInbox, U as freeSlots, c as pickBestDelivered, f as withDriverExecutor, g as meterRuntimeOwnedProviderAttempt, jt as isLiveNodeStatus, l as runFinalizer, o as bestDelivered, ot as createExecutorRegistry, u as runTree } from "./supervisor-CqJW4qap.js";
7
+ import { At as createPeerMailbox, Ft as isTerminalNodeStatus, K as freeSlots, Pt as isLiveNodeStatus, Tt as createInbox, Yt as runBrainLoop, Z as assertValidBudget, d as pickBestDelivered, f as runFinalizer, h as withDriverExecutor, l as bestDelivered, lt as createExecutorRegistry, p as runTree, r as resolveRedactor, y as meterRuntimeOwnedProviderAttempt } from "./redact-h5zAxyNV.js";
8
8
  import { A as writeWorkerCancellation, M as writeWorkerSteerAcknowledgement, c as readWorkerCancelRequests, d as readWorkerSteerRequests, k as writeRunCancellation, l as readWorkerCancellation, o as readRunCancelRequest, r as claimWorkerSteerDelivery, s as readRunCancellation, u as readWorkerSteerAcknowledgement } from "./run-layout-B8I_LXN-.js";
9
9
  import { agentProfileSchema, canonicalCandidateDigest } from "@tangle-network/agent-interface";
10
10
  import { argHash, errorStreakDetector, observeAll, repeatedActionDetector } from "@tangle-network/agent-eval";
@@ -139,6 +139,50 @@ function createEventBus(now = Date.now) {
139
139
  * nothing else, so the decision type states them and nothing else. */
140
140
  const questionEscalationTargets = ["parent", "user"];
141
141
  const isQuestionEscalationTarget = (value) => questionEscalationTargets.includes(value);
142
+ /**
143
+ * The trace-tool sets a DEFINED analyst may ask for — the exact group names agent-eval's
144
+ * `buildTraceToolsForGroup` accepts, restated here so the `define_analyst` JSON Schema can
145
+ * enumerate them for the model that writes one. Named, not free-form, because the group is the
146
+ * lens's cost ceiling: `all` grants seven trace tools, `discovery` grants three and no deep reads.
147
+ * A name eval does not know throws at registration, which is where the two lists are held together.
148
+ */
149
+ const analystToolGroupNames = [
150
+ "all",
151
+ "discovery",
152
+ "discoveryAndRead",
153
+ "discoveryAndSearch",
154
+ "targeted",
155
+ "singleTrace"
156
+ ];
157
+ /** Every bound `define_analyst` enforces before a definition reaches a registry.
158
+ *
159
+ * MOTIVE, stated as numbers, because a fence whose motive cannot be stated is over-engineering:
160
+ * a defined lens spends real model calls from the run's own account on every settle it is routed
161
+ * over, and its `instructions` are re-sent on every one of them. So the two fields that multiply
162
+ * — `instructions` bytes and `maxLlmCalls` — are the ones with hard ceilings, and the ceilings are
163
+ * twice agent-eval's own defaults (`maxIterations` 12, `maxLlmCalls` 8, `maxToolCalls` 48,
164
+ * `maxOutputChars` 10_000): enough headroom for a deeper question than the shipped lenses ask,
165
+ * not enough for one definition to become the dominant cost of a run. */
166
+ const ANALYST_DEFINITION_BOUNDS = Object.freeze({
167
+ idPattern: /^[a-z0-9][a-z0-9-]{1,63}$/,
168
+ maxDescriptionChars: 300,
169
+ maxAreaChars: 64,
170
+ maxQuestionChars: 1e3,
171
+ maxInstructionsChars: 2e4,
172
+ maxModelChars: 128,
173
+ maxIterations: 24,
174
+ maxLlmCalls: 16,
175
+ maxToolCalls: 96,
176
+ maxOutputChars: 2e4,
177
+ maxEvidenceCitations: 10,
178
+ /** Lenses ONE manager may define, counting those it defined in a PRIOR process of a durable run.
179
+ * This bounds MENU GROWTH, not spend: a definition costs nothing until `run_analyst` is called,
180
+ * so the failure it stops is a manager that keeps re-authoring a lens instead of running one —
181
+ * each definition adds a line every later `list_analysts` re-reads. Eight is more than the five
182
+ * calibrated lenses agent-eval ships. Per-run analyst SPEND is bounded by the conserved pool the
183
+ * engine draws from, not here. */
184
+ maxDefinitionsPerManager: 8
185
+ });
142
186
  /** Producer-side cleanliness for the `finding` event. The findings payload is arbitrary analyst
143
187
  * output, the digest a subscriber computes (RFC 8785) throws on ANY `undefined` value — nested
144
188
  * included — and a throwing subscriber leaves the event invisible to EVERY subscriber. The
@@ -164,10 +208,163 @@ function canonicalFindingEvent(finding) {
164
208
  };
165
209
  }
166
210
  }
211
+ /**
212
+ * Validate and BOUND one `define_analyst` argument.
213
+ *
214
+ * Every reason is collected, never short-circuited: a manager re-authoring from a partial list of
215
+ * complaints spends a turn per complaint. Numeric limits are CLAMPED rather than rejected — asking
216
+ * for 200 tool calls is a mis-estimate, not a malformed definition, and the accepted value is
217
+ * returned so the manager reads the ceiling it actually got.
218
+ */
219
+ function parseAuthoredAnalystDefinition(raw) {
220
+ const issues = [];
221
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { issues: [{
222
+ path: "definition",
223
+ message: "must be an object"
224
+ }] };
225
+ const o = raw;
226
+ const bounds = ANALYST_DEFINITION_BOUNDS;
227
+ const text = (field, max) => {
228
+ const value = o[field];
229
+ if (typeof value !== "string" || value.trim().length === 0) {
230
+ issues.push({
231
+ path: field,
232
+ message: "is required and must be a non-empty string"
233
+ });
234
+ return "";
235
+ }
236
+ const trimmed = value.trim();
237
+ if (trimmed.length > max) {
238
+ issues.push({
239
+ path: field,
240
+ message: `must be at most ${max} characters (received ${trimmed.length})`
241
+ });
242
+ return "";
243
+ }
244
+ return trimmed;
245
+ };
246
+ const id = text("id", 64);
247
+ if (id.length > 0 && !bounds.idPattern.test(id)) issues.push({
248
+ path: "id",
249
+ message: "must be 2-64 characters of lowercase letters, digits and hyphens, starting with a letter or digit"
250
+ });
251
+ const description = text("description", bounds.maxDescriptionChars);
252
+ const area = text("area", bounds.maxAreaChars);
253
+ const question = text("question", bounds.maxQuestionChars);
254
+ const instructions = text("instructions", bounds.maxInstructionsChars);
255
+ const toolGroup = o.toolGroup;
256
+ if (!analystToolGroupNames.includes(toolGroup)) issues.push({
257
+ path: "toolGroup",
258
+ message: `must be one of ${analystToolGroupNames.join(", ")}`
259
+ });
260
+ let model;
261
+ if (o.model !== void 0) if (typeof o.model !== "string" || o.model.trim().length === 0) issues.push({
262
+ path: "model",
263
+ message: "must be a non-empty string when present"
264
+ });
265
+ else if (o.model.length > bounds.maxModelChars) issues.push({
266
+ path: "model",
267
+ message: `must be at most ${bounds.maxModelChars} characters`
268
+ });
269
+ else model = o.model.trim();
270
+ const clampedLimit = (field, max, source) => {
271
+ const value = source[field];
272
+ if (value === void 0) return void 0;
273
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
274
+ issues.push({
275
+ path: `limits.${field}`,
276
+ message: "must be a positive integer"
277
+ });
278
+ return;
279
+ }
280
+ return Math.min(value, max);
281
+ };
282
+ let limits;
283
+ if (o.limits !== void 0) if (!o.limits || typeof o.limits !== "object" || Array.isArray(o.limits)) issues.push({
284
+ path: "limits",
285
+ message: "must be an object"
286
+ });
287
+ else {
288
+ const source = o.limits;
289
+ const knownLimits = /* @__PURE__ */ new Set([
290
+ "maxIterations",
291
+ "maxLlmCalls",
292
+ "maxToolCalls",
293
+ "maxOutputChars"
294
+ ]);
295
+ for (const key of Object.keys(source)) if (!knownLimits.has(key)) issues.push({
296
+ path: `limits.${key}`,
297
+ message: `is not an investigation limit (accepted: ${[...knownLimits].join(", ")})`
298
+ });
299
+ const maxIterations = clampedLimit("maxIterations", bounds.maxIterations, source);
300
+ const maxLlmCalls = clampedLimit("maxLlmCalls", bounds.maxLlmCalls, source);
301
+ const maxToolCalls = clampedLimit("maxToolCalls", bounds.maxToolCalls, source);
302
+ const maxOutputChars = clampedLimit("maxOutputChars", bounds.maxOutputChars, source);
303
+ const present = {
304
+ ...maxIterations === void 0 ? {} : { maxIterations },
305
+ ...maxLlmCalls === void 0 ? {} : { maxLlmCalls },
306
+ ...maxToolCalls === void 0 ? {} : { maxToolCalls },
307
+ ...maxOutputChars === void 0 ? {} : { maxOutputChars }
308
+ };
309
+ if (Object.keys(present).length > 0) limits = present;
310
+ }
311
+ let minimumEvidenceCitations;
312
+ if (o.minimumEvidenceCitations !== void 0) {
313
+ const value = o.minimumEvidenceCitations;
314
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) issues.push({
315
+ path: "minimumEvidenceCitations",
316
+ message: "must be a positive integer"
317
+ });
318
+ else minimumEvidenceCitations = Math.min(value, bounds.maxEvidenceCitations);
319
+ }
320
+ const known = /* @__PURE__ */ new Set([
321
+ "id",
322
+ "description",
323
+ "area",
324
+ "question",
325
+ "instructions",
326
+ "toolGroup",
327
+ "model",
328
+ "limits",
329
+ "minimumEvidenceCitations"
330
+ ]);
331
+ for (const key of Object.keys(o)) if (!known.has(key)) issues.push({
332
+ path: key,
333
+ message: `is not part of an authored analyst definition (accepted: ${[...known].join(", ")})`
334
+ });
335
+ if (issues.length > 0) return { issues };
336
+ return { definition: Object.freeze({
337
+ id,
338
+ description,
339
+ area,
340
+ question,
341
+ instructions,
342
+ toolGroup,
343
+ ...model === void 0 ? {} : { model },
344
+ ...limits === void 0 ? {} : { limits: Object.freeze(limits) },
345
+ ...minimumEvidenceCitations === void 0 ? {} : { minimumEvidenceCitations }
346
+ }) };
347
+ }
167
348
  /** Normalize the two spellings of an analyst-on-settle entry to the route form. */
168
349
  function normalizeAnalyzeOnSettle(entry) {
169
350
  return typeof entry === "string" ? { kind: entry } : entry;
170
351
  }
352
+ /**
353
+ * Why one parent→child delivery did not land, in the words the MANAGER needs.
354
+ *
355
+ * `DownMessageDeliveryOutcome` is the machine code; this is the sentence beside it. A code alone
356
+ * left the manager to guess whether to retry, spawn, or give up — `already-settled` and
357
+ * `scope-stopped` look alike and need opposite moves. Every refusal a coordination verb returns
358
+ * carries a sentence like these, because the manager cannot see the state that produced it.
359
+ */
360
+ const downMessageRefusalReasons = Object.freeze({
361
+ delivered: "the message reached the worker",
362
+ "unknown-worker": "no worker of this manager has that id — check observe_agent for the live ids",
363
+ "already-settled": "that worker already settled, so nothing can reach it — spawn a fresh worker for the follow-up work",
364
+ "runtime-has-no-inbox": "that worker runs on an executor with no inbox, so it can never receive a message — re-spawn it on a steerable executor if it must be corrected mid-run",
365
+ "scope-stopped": "this manager’s scope has stopped, so no further message may be sent from it",
366
+ "runtime-error": "the executor threw while accepting the message; the worker may or may not have received it"
367
+ });
171
368
  /** Every cause at zero — a pre-flight publishes its whole ledger from the first read. */
172
369
  function emptyPreflightCounts() {
173
370
  return {
@@ -180,6 +377,29 @@ function emptyPreflightCounts() {
180
377
  * MCP client request timeout so the call returns a `pending` liveness snapshot instead of erroring;
181
378
  * the supervisor re-polls until the worker settles. */
182
379
  const DEFAULT_AWAIT_EVENT_TIMEOUT_MS = 15e3;
380
+ /** Default and maximum bounds for one {@link CoordinationTools} journal read. The defaults are
381
+ * sized for a driver turn (a page a model can read and still have room to act on); the maxima
382
+ * bound what a single tool result may cost even when the caller asks for everything. */
383
+ const JOURNAL_READ_BOUNDS = Object.freeze({
384
+ defaultLimit: 50,
385
+ maxLimit: 500,
386
+ defaultMaxBytes: 16384,
387
+ maxMaxBytes: 262144
388
+ });
389
+ /** Every `CoordinationEvent` kind `read_journal` can return — the tool advertises this list in its
390
+ * JSON Schema AND filters on it, so the schema cannot promise a kind the filter drops. */
391
+ const journalEventKinds = [
392
+ "question",
393
+ "settled",
394
+ "finding",
395
+ "steer",
396
+ "answer",
397
+ "instruction",
398
+ "delivery-attempt",
399
+ "mail",
400
+ "escalation",
401
+ "analyst-defined"
402
+ ];
183
403
  /** The reserved coordination verb names — the complete set `createCoordinationTools` can emit
184
404
  * (the analyst pair is conditional but still reserved). A driver's extra WORK tools must not
185
405
  * collide with any of these, or it could no longer coordinate; callers validate eagerly against
@@ -202,8 +422,10 @@ const coordinationVerbNames = [
202
422
  "ask_parent",
203
423
  "submit_result",
204
424
  "stop",
425
+ "read_journal",
205
426
  "list_analysts",
206
- "run_analyst"
427
+ "run_analyst",
428
+ "define_analyst"
207
429
  ];
208
430
  /**
209
431
  * The `CoordinationEvent` kinds a driver may name in `await_event`. The pull queue carries the
@@ -969,6 +1191,14 @@ function createCoordinationTools(opts) {
969
1191
  type: "mail",
970
1192
  ...ev.mail
971
1193
  };
1194
+ if (ev.type === "escalation") return {
1195
+ type: "escalation",
1196
+ ...ev.escalation
1197
+ };
1198
+ if (ev.type === "analyst-defined") return {
1199
+ type: "analyst-defined",
1200
+ ...ev.analyst
1201
+ };
972
1202
  return {
973
1203
  type: ev.type,
974
1204
  ...ev.down
@@ -1032,6 +1262,13 @@ function createCoordinationTools(opts) {
1032
1262
  }, { priority: urgencyPriority(record.question.urgency) });
1033
1263
  return record.question;
1034
1264
  };
1265
+ /** The refusal every `answer_question` path returns for an id this manager has no question for.
1266
+ * A THROW here reaches the manager as a transport error with no vocabulary it can act on, and
1267
+ * this is the verb the `no-parent` guidance tells it to use — so it names the condition. */
1268
+ const unknownQuestion = (questionId) => ({
1269
+ error: "unknown-question",
1270
+ reason: `this manager has no question with the id ${JSON.stringify(questionId)}; call list_questions for the ids you may decide`
1271
+ });
1035
1272
  const decideQuestion = (questionId, decision) => {
1036
1273
  const idx = questions.findIndex((q) => q.id === questionId);
1037
1274
  if (idx < 0) throw new Error(`unknown questionId ${JSON.stringify(questionId)}`);
@@ -1045,6 +1282,36 @@ function createCoordinationTools(opts) {
1045
1282
  questions[idx] = next;
1046
1283
  return next;
1047
1284
  };
1285
+ const escalations = [...opts.priorEscalations ?? []];
1286
+ const escalate = async (question) => {
1287
+ let outcome;
1288
+ if (opts.escalateQuestion === void 0) outcome = {
1289
+ delivered: false,
1290
+ reason: "this manager has no parent inbox in this run, so nothing above it can receive the question"
1291
+ };
1292
+ else try {
1293
+ outcome = await opts.escalateQuestion(question);
1294
+ } catch (error) {
1295
+ outcome = {
1296
+ delivered: false,
1297
+ reason: `the parent channel refused the question: ${error instanceof Error ? error.message : String(error)}`
1298
+ };
1299
+ }
1300
+ const record = Object.freeze({
1301
+ questionId: question.id,
1302
+ from: question.from,
1303
+ urgency: question.urgency,
1304
+ delivered: outcome.delivered,
1305
+ ...outcome.delivered ? { to: outcome.to } : { reason: outcome.reason },
1306
+ at: Date.now()
1307
+ });
1308
+ escalations.push(record);
1309
+ await bus.publish({
1310
+ type: "escalation",
1311
+ escalation: record
1312
+ }, { queue: false });
1313
+ return record;
1314
+ };
1048
1315
  const blockingQuestionsForStop = () => {
1049
1316
  if (questionPolicy === "auto" || questionPolicy === "bubble") return [];
1050
1317
  return questions.filter((q) => {
@@ -1053,6 +1320,165 @@ function createCoordinationTools(opts) {
1053
1320
  return q.status !== "answered" && q.status !== "deferred";
1054
1321
  });
1055
1322
  };
1323
+ const redactJournalEvent = resolveRedactor(opts.redactJournal);
1324
+ const clampBound = (raw, field, fallback, max) => {
1325
+ if (raw === void 0) return fallback;
1326
+ if (typeof raw !== "number" || !Number.isInteger(raw)) throw new Error(`coordination tools: "${field}" must be an integer`);
1327
+ return Math.min(Math.max(raw, 1), max);
1328
+ };
1329
+ const journalKinds = (raw) => {
1330
+ if (raw === void 0) return void 0;
1331
+ if (!Array.isArray(raw)) throw new Error("coordination tools: \"kinds\" must be an array of event kinds");
1332
+ for (const kind of raw) if (!journalEventKinds.includes(kind)) throw new Error(`coordination tools: "kinds" accepts ${journalEventKinds.join(", ")}; received ${JSON.stringify(kind)}`);
1333
+ return new Set(raw);
1334
+ };
1335
+ const redactedRow = (record) => {
1336
+ try {
1337
+ return redactJournalEvent(detachedFrozen(record.event));
1338
+ } catch (error) {
1339
+ return {
1340
+ type: record.event.type,
1341
+ redactionFailed: error instanceof Error ? error.message : String(error)
1342
+ };
1343
+ }
1344
+ };
1345
+ const priorJournal = opts.priorJournal ?? [];
1346
+ const journalRows = () => {
1347
+ const rows = [];
1348
+ let row = 0;
1349
+ for (const record of priorJournal) rows.push({
1350
+ row: row++,
1351
+ seq: record.seq,
1352
+ at: record.at,
1353
+ priority: record.priority,
1354
+ prior: true,
1355
+ event: void 0,
1356
+ record
1357
+ });
1358
+ for (const record of bus.history()) rows.push({
1359
+ row: row++,
1360
+ seq: record.seq,
1361
+ at: record.at,
1362
+ priority: record.priority,
1363
+ event: void 0,
1364
+ record
1365
+ });
1366
+ return rows;
1367
+ };
1368
+ const readJournal = (raw) => {
1369
+ const a = raw === void 0 ? {} : obj(raw);
1370
+ const sinceRow = a.sinceRow === void 0 ? 0 : typeof a.sinceRow === "number" && Number.isInteger(a.sinceRow) && a.sinceRow >= 0 ? a.sinceRow : (() => {
1371
+ throw new Error("coordination tools: \"sinceRow\" must be a non-negative integer");
1372
+ })();
1373
+ const limit = clampBound(a.limit, "limit", JOURNAL_READ_BOUNDS.defaultLimit, JOURNAL_READ_BOUNDS.maxLimit);
1374
+ const maxBytes = clampBound(a.maxBytes, "maxBytes", JOURNAL_READ_BOUNDS.defaultMaxBytes, JOURNAL_READ_BOUNDS.maxMaxBytes);
1375
+ const kinds = journalKinds(a.kinds);
1376
+ const matching = journalRows().filter((candidate) => candidate.row >= sinceRow && (!kinds || kinds.has(candidate.record.event.type)));
1377
+ const entries = [];
1378
+ let usedBytes = 2;
1379
+ let index = 0;
1380
+ let bounded = false;
1381
+ for (; index < matching.length; index++) {
1382
+ if (entries.length >= limit) {
1383
+ bounded = true;
1384
+ break;
1385
+ }
1386
+ const { record, event: _placeholder, ...stamp } = matching[index];
1387
+ const row = {
1388
+ ...stamp,
1389
+ event: redactedRow(record)
1390
+ };
1391
+ const bytes = Buffer.byteLength(safeJsonText(row), "utf8") + (entries.length === 0 ? 0 : 1);
1392
+ if (usedBytes + bytes > maxBytes) {
1393
+ bounded = true;
1394
+ if (entries.length > 0) break;
1395
+ const marker = {
1396
+ ...stamp,
1397
+ event: { type: record.event.type },
1398
+ oversize: {
1399
+ type: record.event.type,
1400
+ bytes
1401
+ }
1402
+ };
1403
+ entries.push(marker);
1404
+ usedBytes += Buffer.byteLength(safeJsonText(marker), "utf8");
1405
+ index++;
1406
+ break;
1407
+ }
1408
+ entries.push(row);
1409
+ usedBytes += bytes;
1410
+ }
1411
+ const last = entries[entries.length - 1];
1412
+ return {
1413
+ entries,
1414
+ nextRow: last === void 0 ? sinceRow : last.row + 1,
1415
+ remaining: matching.length - index,
1416
+ truncated: bounded,
1417
+ bounds: {
1418
+ sinceRow,
1419
+ limit,
1420
+ maxBytes,
1421
+ usedBytes
1422
+ },
1423
+ priorRows: priorJournal.length
1424
+ };
1425
+ };
1426
+ const definedAnalysts = [...opts.priorAnalystDefinitions ?? []];
1427
+ const analystMenu = () => [...opts.analysts?.kinds ?? [], ...definedAnalysts.map((record) => record.kind)];
1428
+ const maxDefinedAnalysts = opts.maxDefinedAnalysts ?? ANALYST_DEFINITION_BOUNDS.maxDefinitionsPerManager;
1429
+ const defineAnalyst = async (raw) => {
1430
+ const register = opts.analysts?.register;
1431
+ if (register === void 0) return {
1432
+ error: "authoring-unavailable",
1433
+ reason: "this run's analyst registry admits no authored lens"
1434
+ };
1435
+ if (maxDefinedAnalysts > 0 && definedAnalysts.length >= maxDefinedAnalysts) return {
1436
+ error: "max-defined-analysts",
1437
+ reason: `you have defined ${definedAnalysts.length} lenses, the cap for this manager; run the ones you have instead of defining more`,
1438
+ defined: definedAnalysts.map((record) => record.kind.id)
1439
+ };
1440
+ const parsed = parseAuthoredAnalystDefinition(raw);
1441
+ if ("issues" in parsed) return {
1442
+ error: "invalid-definition",
1443
+ reason: parsed.issues.map((issue) => `${issue.path} ${issue.message}`).join("; "),
1444
+ issues: parsed.issues
1445
+ };
1446
+ const { definition } = parsed;
1447
+ const taken = analystMenu().find((kind) => kind.id === definition.id);
1448
+ if (taken !== void 0) return {
1449
+ error: "duplicate-analyst",
1450
+ reason: `the lens id ${JSON.stringify(definition.id)} is already on the menu (${taken.description}); choose a different id or run the existing lens`
1451
+ };
1452
+ let kind;
1453
+ try {
1454
+ kind = await register(definition);
1455
+ } catch (error) {
1456
+ return {
1457
+ error: "register-refused",
1458
+ reason: error instanceof Error ? error.message : String(error)
1459
+ };
1460
+ }
1461
+ const record = Object.freeze({
1462
+ definition,
1463
+ kind: Object.freeze({
1464
+ id: kind.id,
1465
+ description: kind.description,
1466
+ area: kind.area
1467
+ }),
1468
+ digest: canonicalCandidateDigest(definition),
1469
+ definedAt: Date.now()
1470
+ });
1471
+ definedAnalysts.push(record);
1472
+ await bus.publish({
1473
+ type: "analyst-defined",
1474
+ analyst: record
1475
+ }, { queue: false });
1476
+ return {
1477
+ analyst: record.kind,
1478
+ digest: record.digest,
1479
+ defined: definedAnalysts.length
1480
+ };
1481
+ };
1056
1482
  const maxLiveWorkers = opts.maxLiveWorkers;
1057
1483
  const localLiveWorkerCount = () => opts.scope.view.nodes.filter((n) => isLiveNodeStatus(n.status)).length;
1058
1484
  const sharedWorkerCapacity = () => {
@@ -1207,14 +1633,16 @@ function createCoordinationTools(opts) {
1207
1633
  handler: async (raw) => {
1208
1634
  const a = obj(raw);
1209
1635
  const key = a.key === void 0 ? void 0 : str(a.key, "key");
1210
- if (!(key !== void 0 && completedKeys.has(key)) && !usesTreeWideLimit() && maxLiveWorkers !== void 0 && maxLiveWorkers > 0 && liveWorkerCount() >= maxLiveWorkers) return Promise.resolve({
1636
+ if (!(key !== void 0 && completedKeys.has(key)) && !usesTreeWideLimit() && maxLiveWorkers !== void 0 && maxLiveWorkers > 0 && liveWorkerCount() >= maxLiveWorkers) return Promise.resolve(((live) => ({
1211
1637
  error: "max-live-workers",
1212
- live: liveWorkerCount(),
1638
+ reason: `${live} worker${live === 1 ? " is" : "s are"} already live, which is this manager's ceiling — await_event until one settles, then spawn`,
1639
+ live,
1213
1640
  freeSlots: freeWorkerSlots()
1214
- });
1641
+ }))(liveWorkerCount()));
1215
1642
  const parsedProfile = agentProfileSchema.safeParse(a.profile);
1216
1643
  if (!parsedProfile.success) return Promise.resolve({
1217
1644
  error: "invalid-profile",
1645
+ reason: `the profile you wrote is not a valid AgentProfile: ${parsedProfile.error.issues.map((issue) => `${issue.path.join(".") || "profile"} ${issue.message}`).join("; ")}`,
1218
1646
  issues: parsedProfile.error.issues.map((issue) => ({
1219
1647
  path: issue.path.join("."),
1220
1648
  message: issue.message
@@ -1224,6 +1652,7 @@ function createCoordinationTools(opts) {
1224
1652
  const continuity = resolveContinuity(parseContinuity(a.continuity), profile.name, key);
1225
1653
  if ("error" in continuity) return Promise.resolve({
1226
1654
  error: continuity.error,
1655
+ reason: continuity.hint,
1227
1656
  hint: continuity.hint,
1228
1657
  live: liveWorkerCount(),
1229
1658
  freeSlots: freeWorkerSlots()
@@ -1241,6 +1670,7 @@ function createCoordinationTools(opts) {
1241
1670
  preflightCounts[refusal.cause] += 1;
1242
1671
  return {
1243
1672
  error: "preflight-refused",
1673
+ reason: `the spawn pre-flight refused this profile (${refusal.cause}): ${refusal.detail}`,
1244
1674
  cause: refusal.cause,
1245
1675
  detail: refusal.detail,
1246
1676
  live: liveWorkerCount(),
@@ -1306,6 +1736,7 @@ function createCoordinationTools(opts) {
1306
1736
  ...priorHistory
1307
1737
  } : {
1308
1738
  error: res.reason,
1739
+ reason: res.reason === "usd-unbudgeted" ? "this run's root budget declares no maxUsd, so a child budget naming maxUsd can never be admitted at any amount — spawn with a budget that omits maxUsd" : `the conserved pool refused this spawn (${String(res.reason)}); the run has no allocation left to give this worker`,
1309
1740
  ...res.reason === "usd-unbudgeted" ? { hint: "This run's root budget declares no maxUsd, so a child budget naming maxUsd can never be admitted — at any amount. Retrying with a smaller maxUsd will fail identically. Spawn with a budget that omits maxUsd, or ask the caller to give the run a root maxUsd." } : {},
1310
1741
  live: liveWorkerCount(),
1311
1742
  freeSlots: freeWorkerSlots()
@@ -1325,7 +1756,10 @@ function createCoordinationTools(opts) {
1325
1756
  const node = opts.scope.view.nodes.find((n) => n.id === id);
1326
1757
  if (!node) {
1327
1758
  const resumed = opts.scope.resume?.view.nodes.find((n) => n.id === id);
1328
- if (!resumed) return { error: `unknown workerId ${JSON.stringify(id)}` };
1759
+ if (!resumed) return {
1760
+ error: "unknown-worker",
1761
+ reason: `no worker of this manager has the id ${JSON.stringify(id)}; spawn_worker returns the ids you may observe`
1762
+ };
1329
1763
  const output = resumed.outRef ? await opts.blobs.get(resumed.outRef) : void 0;
1330
1764
  return {
1331
1765
  ...projectNodeEvidence(resumed, true),
@@ -1373,7 +1807,8 @@ function createCoordinationTools(opts) {
1373
1807
  };
1374
1808
  return {
1375
1809
  delivered: false,
1376
- reason: delivery.outcome,
1810
+ outcome: delivery.outcome,
1811
+ reason: downMessageRefusalReasons[delivery.outcome],
1377
1812
  progress: readProgress(workerId) ?? null
1378
1813
  };
1379
1814
  }
@@ -1453,7 +1888,7 @@ function createCoordinationTools(opts) {
1453
1888
  if (typeof a.answer === "string" && a.answer.length > 0) {
1454
1889
  const answer = a.answer;
1455
1890
  const pendingQuestion = questions.find((question) => question.id === questionId);
1456
- if (pendingQuestion === void 0) throw new Error(`unknown questionId ${JSON.stringify(questionId)}`);
1891
+ if (pendingQuestion === void 0) return unknownQuestion(questionId);
1457
1892
  const interrupt = pendingQuestion.urgency === "blocks-run" || pendingQuestion.urgency === "blocks-step";
1458
1893
  const authorized = authorizeInstruction("answer", pendingQuestion.from, answer, interrupt, questionId);
1459
1894
  await recordInstruction(authorized);
@@ -1469,23 +1904,44 @@ function createCoordinationTools(opts) {
1469
1904
  by: typeof a.by === "string" && a.by.length > 0 ? a.by : "user"
1470
1905
  }) : pendingQuestion,
1471
1906
  delivered: delivery.delivered,
1472
- ...delivery.delivered ? {} : { reason: delivery.outcome }
1907
+ ...delivery.delivered ? {} : {
1908
+ outcome: delivery.outcome,
1909
+ reason: downMessageRefusalReasons[delivery.outcome]
1910
+ }
1473
1911
  };
1474
1912
  }
1475
- if (typeof a.deferReason === "string" && a.deferReason.length > 0) return Promise.resolve({ question: decideQuestion(questionId, {
1476
- kind: "defer",
1477
- reason: a.deferReason
1478
- }) });
1913
+ if (typeof a.deferReason === "string" && a.deferReason.length > 0) {
1914
+ if (!questions.some((question) => question.id === questionId)) return Promise.resolve(unknownQuestion(questionId));
1915
+ return Promise.resolve({ question: decideQuestion(questionId, {
1916
+ kind: "defer",
1917
+ reason: a.deferReason
1918
+ }) });
1919
+ }
1479
1920
  if (typeof a.escalateTo === "string" && a.escalateTo.length > 0) {
1480
- if (!isQuestionEscalationTarget(a.escalateTo)) throw new Error(`answer_question: escalateTo must be one of ${questionEscalationTargets.join(", ")}; received ${JSON.stringify(a.escalateTo)}`);
1921
+ if (!isQuestionEscalationTarget(a.escalateTo)) return Promise.resolve({
1922
+ error: "invalid-escalation-target",
1923
+ reason: `escalateTo must be one of ${questionEscalationTargets.join(", ")}; received ${JSON.stringify(a.escalateTo)}`
1924
+ });
1925
+ if (!questions.some((question) => question.id === questionId)) return Promise.resolve(unknownQuestion(questionId));
1481
1926
  const escalateReason = typeof a.escalateReason === "string" && a.escalateReason.length > 0 ? a.escalateReason : "driver escalated";
1482
- return Promise.resolve({ question: decideQuestion(questionId, {
1927
+ const decided = decideQuestion(questionId, {
1483
1928
  kind: "escalate",
1484
1929
  to: a.escalateTo,
1485
1930
  reason: escalateReason
1486
- }) });
1931
+ });
1932
+ if (a.escalateTo !== "parent") return Promise.resolve({ question: decided });
1933
+ return escalate(decided).then((escalation) => ({
1934
+ question: decided,
1935
+ escalated: escalation.delivered,
1936
+ outcome: escalation.delivered ? "queued-for-parent" : "no-parent",
1937
+ reason: escalation.delivered ? `the question is held by ${escalation.to}` : escalation.reason,
1938
+ ...escalation.delivered ? {} : { guidance: "No inbox above this manager is configured to receive it, so nothing will route an answer back to you. The question stays on the run record for anyone watching. Do not BLOCK on it: answer_question it yourself, or answer_question with deferReason to record that it stays open." }
1939
+ }));
1487
1940
  }
1488
- throw new Error("answer_question: provide answer, deferReason, or escalateTo");
1941
+ return Promise.resolve({
1942
+ error: "no-decision",
1943
+ reason: "a decision is required: pass `answer` to answer the question, `deferReason` to record that it stays open, or `escalateTo` to hand it further up"
1944
+ });
1489
1945
  }
1490
1946
  },
1491
1947
  {
@@ -1525,7 +1981,7 @@ function createCoordinationTools(opts) {
1525
1981
  handler: async (raw) => {
1526
1982
  const a = obj(raw);
1527
1983
  const from = str(a.from, "from");
1528
- return { question: await emitNewQuestion(addQuestion({
1984
+ const q = await emitNewQuestion(addQuestion({
1529
1985
  from,
1530
1986
  level: level(a.level),
1531
1987
  question: str(a.question, "question"),
@@ -1535,7 +1991,15 @@ function createCoordinationTools(opts) {
1535
1991
  kind: "escalate",
1536
1992
  to: "parent",
1537
1993
  reason: "asked parent"
1538
- })) };
1994
+ }));
1995
+ const escalation = await escalate(q);
1996
+ return {
1997
+ question: q,
1998
+ escalated: escalation.delivered,
1999
+ outcome: escalation.delivered ? "queued-for-parent" : "no-parent",
2000
+ reason: escalation.delivered ? `the question is held by ${escalation.to}` : escalation.reason,
2001
+ ...escalation.delivered ? {} : { guidance: "Nobody above this manager received the question. It is journaled for the operator, and YOU are the last decider: answer_question it yourself, or answer_question with deferReason to record that it stays open. Do not wait for an answer." }
2002
+ };
1539
2003
  }
1540
2004
  },
1541
2005
  ...deliverable ? [{
@@ -1561,14 +2025,17 @@ function createCoordinationTools(opts) {
1561
2025
  if (!Object.hasOwn(a, "result")) throw new Error("submit_result: \"result\" is required");
1562
2026
  const result = structuredClone(a.result);
1563
2027
  let accepted = false;
2028
+ let thrown;
1564
2029
  try {
1565
2030
  accepted = await deliverable.check(result) === true;
1566
- } catch {
2031
+ } catch (error) {
1567
2032
  accepted = false;
2033
+ thrown = error instanceof Error ? error.message : String(error);
1568
2034
  }
1569
2035
  if (!accepted) return {
1570
2036
  accepted: false,
1571
- stop: false
2037
+ stop: false,
2038
+ reason: thrown === void 0 ? `the independent check did not pass on this result${deliverable.describe ? `. Expected: ${deliverable.describe}` : ""}` : `the independent check THREW, so nothing was accepted: ${thrown}. This is a fault in the check, not necessarily in your result — report it rather than resubmitting unchanged`
1572
2039
  };
1573
2040
  if (submitted) return {
1574
2041
  accepted: true,
@@ -1598,28 +2065,73 @@ function createCoordinationTools(opts) {
1598
2065
  },
1599
2066
  handler: (raw) => {
1600
2067
  const blocking = blockingQuestionsForStop();
1601
- if (blocking.length) return Promise.resolve({
1602
- stopped: false,
1603
- error: "unresolved-blocking-questions",
1604
- questions: blocking
1605
- });
2068
+ if (blocking.length) {
2069
+ const unheard = blocking.filter((question) => escalations.some((record) => record.questionId === question.id && record.delivered === false));
2070
+ return Promise.resolve({
2071
+ stopped: false,
2072
+ error: "unresolved-blocking-questions",
2073
+ reason: `${blocking.length} blocking question${blocking.length === 1 ? "" : "s"} ${blocking.length === 1 ? "is" : "are"} still undecided` + (unheard.length > 0 ? `, and ${unheard.length} of them reached no parent — nothing above this manager will answer ${unheard.length === 1 ? "it" : "them"}. Decide ${unheard.length === 1 ? "it" : "them"} with answer_question (answer, or deferReason to record that it stays open) and stop again.` : ". Decide each with answer_question (answer, deferReason, or escalateTo) and stop again."),
2074
+ questions: blocking,
2075
+ ...unheard.length > 0 ? { unheardQuestionIds: unheard.map((q) => q.id) } : {}
2076
+ });
2077
+ }
1606
2078
  stopped = true;
1607
2079
  const r = obj(raw).reason;
1608
2080
  reason = typeof r === "string" ? r : void 0;
1609
2081
  notifyStop();
1610
2082
  return Promise.resolve({ stopped: true });
1611
2083
  }
2084
+ },
2085
+ {
2086
+ name: "read_journal",
2087
+ description: [
2088
+ "Re-read YOUR OWN coordination journal: every spawn you made, every worker that settled,",
2089
+ "every question raised or decided, every steer you authorized, and every analyst finding —",
2090
+ "oldest first, on this node only, including what you did before a restart. Read it before",
2091
+ "deciding what to do next, so you can see what you already tried. Bounded: page with the",
2092
+ "returned `nextRow`, and check `truncated` before concluding you have read everything."
2093
+ ].join(" "),
2094
+ inputSchema: {
2095
+ type: "object",
2096
+ properties: {
2097
+ sinceRow: {
2098
+ type: "integer",
2099
+ minimum: 0,
2100
+ description: "First journal row to return (inclusive, 0 = the first record of the run). Pass the previous call’s `nextRow` to continue."
2101
+ },
2102
+ kinds: {
2103
+ type: "array",
2104
+ items: {
2105
+ type: "string",
2106
+ enum: [...journalEventKinds]
2107
+ },
2108
+ description: "Return only these event kinds. Omit for every kind."
2109
+ },
2110
+ limit: {
2111
+ type: "integer",
2112
+ minimum: 1,
2113
+ description: `Maximum rows to return. Default ${JOURNAL_READ_BOUNDS.defaultLimit}; anything above ${JOURNAL_READ_BOUNDS.maxLimit} is clamped to it.`
2114
+ },
2115
+ maxBytes: {
2116
+ type: "integer",
2117
+ minimum: 1,
2118
+ description: `Byte budget for the returned rows. Default ${JOURNAL_READ_BOUNDS.defaultMaxBytes}; anything above ${JOURNAL_READ_BOUNDS.maxMaxBytes} is clamped to it.`
2119
+ }
2120
+ },
2121
+ additionalProperties: false
2122
+ },
2123
+ handler: async (raw) => readJournal(raw)
1612
2124
  }
1613
2125
  ];
1614
2126
  if (opts.analysts) {
1615
2127
  tools.push({
1616
2128
  name: "list_analysts",
1617
- description: "List trace-analyst lenses available to run over a settled worker.",
2129
+ description: "List trace-analyst lenses available to run over a settled worker — the ones this run was given plus any you defined with define_analyst.",
1618
2130
  inputSchema: {
1619
2131
  type: "object",
1620
2132
  properties: {}
1621
2133
  },
1622
- handler: () => Promise.resolve({ analysts: opts.analysts?.kinds })
2134
+ handler: () => Promise.resolve({ analysts: analystMenu() })
1623
2135
  });
1624
2136
  tools.push({
1625
2137
  name: "run_analyst",
@@ -1639,8 +2151,14 @@ function createCoordinationTools(opts) {
1639
2151
  const a = obj(raw);
1640
2152
  const id = str(a.workerId, "workerId");
1641
2153
  const node = nodeForWorker(id);
1642
- if (!node) return { error: `unknown workerId ${JSON.stringify(id)}` };
1643
- if (isLiveNodeStatus(node.status)) return { error: `worker ${JSON.stringify(id)} has not settled — no trace to analyze yet` };
2154
+ if (!node) return {
2155
+ error: "unknown-worker",
2156
+ reason: `no worker of this manager has the id ${JSON.stringify(id)}; spawn_worker returns the ids you may analyze`
2157
+ };
2158
+ if (isLiveNodeStatus(node.status)) return {
2159
+ error: "worker-not-settled",
2160
+ reason: `worker ${JSON.stringify(id)} has not settled, so it has no trace to analyze yet — await_event until it settles, then run the lens`
2161
+ };
1644
2162
  const trace = ledger.find((worker) => worker.id === id)?.trace ?? node.trace ?? {
1645
2163
  status: "unavailable",
1646
2164
  reason: "legacy-settlement-without-trace-evidence"
@@ -1650,13 +2168,105 @@ function createCoordinationTools(opts) {
1650
2168
  store = await workerTraceAnalysisStore(trace, opts.blobs);
1651
2169
  } catch (error) {
1652
2170
  return {
1653
- error: error instanceof Error ? error.message : String(error),
2171
+ error: "trace-unreadable",
2172
+ reason: `the settled worker's trace evidence could not be read: ${error instanceof Error ? error.message : String(error)}`,
1654
2173
  trace
1655
2174
  };
1656
2175
  }
1657
- return { findings: await opts.analysts?.run(str(a.kind, "kind"), store) };
2176
+ const kind = str(a.kind, "kind");
2177
+ if (!analystMenu().some((entry) => entry.id === kind)) return {
2178
+ error: "unknown-analyst",
2179
+ reason: `no lens named ${JSON.stringify(kind)} is on this manager's menu; call list_analysts to see what you may run`
2180
+ };
2181
+ return { findings: await opts.analysts?.run(kind, store) };
1658
2182
  }
1659
2183
  });
2184
+ if (opts.analysts.register) tools.push({
2185
+ name: "define_analyst",
2186
+ description: [
2187
+ "Define a NEW trace-analyst lens for this run, then run it with run_analyst like any",
2188
+ "other. You write the research question, the policy for answering it, which trace tools",
2189
+ "it may use, and the model seat — data only, never code. The definition is journaled, so",
2190
+ "the lens you invented is reproducible and attributed to you."
2191
+ ].join(" "),
2192
+ inputSchema: {
2193
+ type: "object",
2194
+ properties: {
2195
+ id: {
2196
+ type: "string",
2197
+ description: "Stable lens id: lowercase letters, digits and hyphens, 2-64 characters. It is the `kind` you pass to run_analyst and the analyst_id on every finding."
2198
+ },
2199
+ description: {
2200
+ type: "string",
2201
+ description: `One line naming what this lens looks for. At most ${ANALYST_DEFINITION_BOUNDS.maxDescriptionChars} characters.`
2202
+ },
2203
+ area: {
2204
+ type: "string",
2205
+ description: "The finding area this lens reports under, e.g. coordination, tool-use, cost."
2206
+ },
2207
+ question: {
2208
+ type: "string",
2209
+ description: `The research question, in your own words. At most ${ANALYST_DEFINITION_BOUNDS.maxQuestionChars} characters.`
2210
+ },
2211
+ instructions: {
2212
+ type: "string",
2213
+ description: `How to answer it: evidence rules, what counts as a finding, what to refuse to infer. At most ${ANALYST_DEFINITION_BOUNDS.maxInstructionsChars} characters.`
2214
+ },
2215
+ toolGroup: {
2216
+ type: "string",
2217
+ enum: [...analystToolGroupNames],
2218
+ description: "Smallest trace-tool set that can answer the question. discovery = overview/query/count only; discoveryAndRead adds deep reads; discoveryAndSearch adds regex; targeted and singleTrace narrow further; all grants every trace tool."
2219
+ },
2220
+ model: {
2221
+ type: "string",
2222
+ description: "The model seat this lens should run on. Omit for the run default. It is a request: registration may refuse a seat this run cannot serve."
2223
+ },
2224
+ limits: {
2225
+ type: "object",
2226
+ properties: {
2227
+ maxIterations: {
2228
+ type: "integer",
2229
+ minimum: 1,
2230
+ maximum: ANALYST_DEFINITION_BOUNDS.maxIterations
2231
+ },
2232
+ maxLlmCalls: {
2233
+ type: "integer",
2234
+ minimum: 1,
2235
+ maximum: ANALYST_DEFINITION_BOUNDS.maxLlmCalls
2236
+ },
2237
+ maxToolCalls: {
2238
+ type: "integer",
2239
+ minimum: 1,
2240
+ maximum: ANALYST_DEFINITION_BOUNDS.maxToolCalls
2241
+ },
2242
+ maxOutputChars: {
2243
+ type: "integer",
2244
+ minimum: 1,
2245
+ maximum: ANALYST_DEFINITION_BOUNDS.maxOutputChars
2246
+ }
2247
+ },
2248
+ additionalProperties: false,
2249
+ description: "Investigation ceilings. Values above the maximum are clamped, not refused."
2250
+ },
2251
+ minimumEvidenceCitations: {
2252
+ type: "integer",
2253
+ minimum: 1,
2254
+ maximum: ANALYST_DEFINITION_BOUNDS.maxEvidenceCitations,
2255
+ description: "Distinct evidence citations required per finding. Default 1."
2256
+ }
2257
+ },
2258
+ required: [
2259
+ "id",
2260
+ "description",
2261
+ "area",
2262
+ "question",
2263
+ "instructions",
2264
+ "toolGroup"
2265
+ ],
2266
+ additionalProperties: false
2267
+ },
2268
+ handler: async (raw) => defineAnalyst(raw)
2269
+ });
1660
2270
  }
1661
2271
  const abortWorker = (ref, reason) => {
1662
2272
  const live = opts.scope.view.nodes.filter((node) => isLiveNodeStatus(node.status));
@@ -1688,6 +2298,8 @@ function createCoordinationTools(opts) {
1688
2298
  submittedResult: () => submitted,
1689
2299
  settled: () => ledger,
1690
2300
  questions: () => questions,
2301
+ escalations: () => escalations,
2302
+ definedAnalysts: () => definedAnalysts,
1691
2303
  drainResolved,
1692
2304
  abortWorker,
1693
2305
  ...peerMail ? { peerMail } : {}
@@ -1785,6 +2397,8 @@ var FileCoordinationLog = class {
1785
2397
  }
1786
2398
  const byId = /* @__PURE__ */ new Map();
1787
2399
  const findings = [];
2400
+ const escalations = [];
2401
+ const analystDefinitions = [];
1788
2402
  const continuations = [];
1789
2403
  const deliveryEvidence = [];
1790
2404
  const mail = [];
@@ -1822,11 +2436,15 @@ var FileCoordinationLog = class {
1822
2436
  });
1823
2437
  } else if (ev.type === "instruction") continuations.push(ev.instruction);
1824
2438
  else if (ev.type === "mail") mail.push(ev.mail);
2439
+ else if (ev.type === "escalation") escalations.push(ev.escalation);
2440
+ else if (ev.type === "analyst-defined") analystDefinitions.push(ev.analyst);
1825
2441
  }
1826
2442
  return {
1827
2443
  ...ownerId !== void 0 ? { ownerId } : {},
1828
2444
  questions: [...byId.values()],
1829
2445
  findings,
2446
+ escalations,
2447
+ analystDefinitions,
1830
2448
  continuations,
1831
2449
  deliveryEvidence,
1832
2450
  mail,
@@ -1839,6 +2457,8 @@ function emptyPriorCoordination(ownerId) {
1839
2457
  ...ownerId !== void 0 ? { ownerId } : {},
1840
2458
  questions: [],
1841
2459
  findings: [],
2460
+ escalations: [],
2461
+ analystDefinitions: [],
1842
2462
  continuations: [],
1843
2463
  deliveryEvidence: [],
1844
2464
  mail: [],
@@ -2447,6 +3067,15 @@ function createSteerAcknowledger(deps) {
2447
3067
  }
2448
3068
  } };
2449
3069
  }
3070
+ /**
3071
+ * The OPERATOR-facing sentence for a steer outcome, written into the durable acknowledgement file.
3072
+ *
3073
+ * Deliberately separate from `downMessageRefusalReasons`, which is the MANAGER-facing sentence: one
3074
+ * says what happened for someone reading the run afterwards, the other tells a model what to do
3075
+ * next, and merging them would make one audience read the other's copy. They must nonetheless cover
3076
+ * the SAME outcomes, so `tests/kernel/refusal-reasons.test.ts` holds both maps total over
3077
+ * `DownMessageDeliveryOutcome` — a new code cannot land in only one.
3078
+ */
2450
3079
  function steerAcknowledgementDetail(outcome) {
2451
3080
  switch (outcome.outcome) {
2452
3081
  case "delivered": return "the owning manager delivered the steer to the exact live worker";
@@ -2753,9 +3382,13 @@ function driverAgent(opts) {
2753
3382
  ...opts.continuityByProfile ? { continuityByProfile: opts.continuityByProfile } : {},
2754
3383
  ...opts.preflightSpawn ? { preflightSpawn: opts.preflightSpawn } : {},
2755
3384
  ...opts.resolveSpawnProfile ? { resolveSpawnProfile: opts.resolveSpawnProfile } : {},
3385
+ ...opts.escalateQuestion ? { escalateQuestion: opts.escalateQuestion } : {},
2756
3386
  ...opts.onEvent ? { onEvent: opts.onEvent } : {},
2757
3387
  ...opts.replaySettlements ? { replaySettlements: true } : {},
2758
- ...opts.priorCoordination?.questions.length ? { priorQuestions: opts.priorCoordination.questions } : {}
3388
+ ...opts.priorCoordination?.questions.length ? { priorQuestions: opts.priorCoordination.questions } : {},
3389
+ ...opts.priorCoordination?.escalations?.length ? { priorEscalations: opts.priorCoordination.escalations } : {},
3390
+ ...opts.priorCoordination?.records.length ? { priorJournal: opts.priorCoordination.records } : {},
3391
+ ...opts.priorCoordination?.analystDefinitions?.length ? { priorAnalystDefinitions: opts.priorCoordination.analystDefinitions } : {}
2759
3392
  });
2760
3393
  await coord.ready();
2761
3394
  opts.onCoordinationTools?.(coord.tools);
@@ -3066,6 +3699,6 @@ function errMessage(e) {
3066
3699
  return e instanceof Error ? e.message : String(e);
3067
3700
  }
3068
3701
  //#endregion
3069
- export { coordinationVerbNames as C, createEventBus as D, questionEscalationTargets as E, defaultToolDetectors as O, canonicalFindingEvent as S, normalizeAnalyzeOnSettle as T, renderAnytimeTable as _, allOf as a, FileCoordinationLog as b, createProgressTracker as c, progressStop as d, sampleFromSettled as f, plateauLength as g, bestSoFar as h, finalizeBestDelivered as i, watchTrace as k, noProgressFor as l, areaUnderCurve as m, createSteerAcknowledger as n, allWorkersStalled as o, anytimeReport as p, driverAgent as r, anyOf as s, createCancelAcknowledger as t, plateau as u, createFileRunContext as v, createCoordinationTools as w, DEFAULT_AWAIT_EVENT_TIMEOUT_MS as x, createInMemoryRunContext as y };
3702
+ export { questionEscalationTargets as A, analystToolGroupNames as C, downMessageRefusalReasons as D, createCoordinationTools as E, defaultToolDetectors as M, watchTrace as N, normalizeAnalyzeOnSettle as O, DEFAULT_AWAIT_EVENT_TIMEOUT_MS as S, coordinationVerbNames as T, renderAnytimeTable as _, allOf as a, FileCoordinationLog as b, createProgressTracker as c, progressStop as d, sampleFromSettled as f, plateauLength as g, bestSoFar as h, finalizeBestDelivered as i, createEventBus as j, parseAuthoredAnalystDefinition as k, noProgressFor as l, areaUnderCurve as m, createSteerAcknowledger as n, allWorkersStalled as o, anytimeReport as p, driverAgent as r, anyOf as s, createCancelAcknowledger as t, plateau as u, createFileRunContext as v, canonicalFindingEvent as w, ANALYST_DEFINITION_BOUNDS as x, createInMemoryRunContext as y };
3070
3703
 
3071
- //# sourceMappingURL=coordination-driver-Da1IGdTP.js.map
3704
+ //# sourceMappingURL=coordination-driver-_wzRdIK_.js.map