@tangle-network/agent-runtime 6.0.8 → 8.0.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 (119) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +23 -12
  3. package/dist/{activation-Cwy3_Qn_.js → activation-Ce4zUcu8.js} +4 -4
  4. package/dist/{activation-Cwy3_Qn_.js.map → activation-Ce4zUcu8.js.map} +1 -1
  5. package/dist/{activation-CWx7I1PU.d.ts → activation-xxvLF1-1.d.ts} +2 -2
  6. package/dist/agent.d.ts +1 -1
  7. package/dist/agent.js +3 -3
  8. package/dist/{bundle-Cy5igJdv.js → bundle-CcpL2lfr.js} +2 -2
  9. package/dist/{bundle-Cy5igJdv.js.map → bundle-CcpL2lfr.js.map} +1 -1
  10. package/dist/candidate-execution/index.js +4 -4
  11. package/dist/continuation-CIyKnS1Q.js +451 -0
  12. package/dist/continuation-CIyKnS1Q.js.map +1 -0
  13. package/dist/{coordination-manager-D9z7To7p.js → coordination-manager-sa_PkISw.js} +247 -149
  14. package/dist/coordination-manager-sa_PkISw.js.map +1 -0
  15. package/dist/{coordination-map-routing-CKRNZSaV.js → coordination-map-routing-XZXTlCmn.js} +2 -4
  16. package/dist/coordination-map-routing-XZXTlCmn.js.map +1 -0
  17. package/dist/{delegate-ws-KBA2H.js → delegate-DhTkJIta.js} +3 -3
  18. package/dist/{delegate-ws-KBA2H.js.map → delegate-DhTkJIta.js.map} +1 -1
  19. package/dist/durable.d.ts +71 -4
  20. package/dist/durable.js +162 -13
  21. package/dist/durable.js.map +1 -1
  22. package/dist/{errors-AzSuGI23.js → errors-DG8yufqX.js} +8 -1
  23. package/dist/errors-DG8yufqX.js.map +1 -0
  24. package/dist/{failure-cause-BzjUUsRz.js → failure-cause-CVB-YE88.js} +17 -16
  25. package/dist/failure-cause-CVB-YE88.js.map +1 -0
  26. package/dist/{graph-registry-7X2nrreR.js → graph-registry-czAwYwNj.js} +133 -9
  27. package/dist/graph-registry-czAwYwNj.js.map +1 -0
  28. package/dist/{graph-registry-gYbq4L5Z.js → graph-registry-uTIyyy7A.js} +1 -1
  29. package/dist/graph-tSIT_eWK.js +1793 -0
  30. package/dist/graph-tSIT_eWK.js.map +1 -0
  31. package/dist/{improve-BxfqTsZy.js → improve-H08hiIw9.js} +19 -48
  32. package/dist/improve-H08hiIw9.js.map +1 -0
  33. package/dist/index.d.ts +3 -3
  34. package/dist/index.js +16 -16
  35. package/dist/intelligence.d.ts +2 -2
  36. package/dist/intelligence.js +8 -8
  37. package/dist/intelligence.js.map +1 -1
  38. package/dist/kernel.d.ts +2 -2
  39. package/dist/kernel.js +16 -15
  40. package/dist/{loop-runner-CUMypgRw.d.ts → loop-runner-BGwhcn1p.d.ts} +2 -2
  41. package/dist/{loop-runner-BmGZ4y1W.js → loop-runner-ChqK8kSz.js} +3 -3
  42. package/dist/{loop-runner-BmGZ4y1W.js.map → loop-runner-ChqK8kSz.js.map} +1 -1
  43. package/dist/loop-runner-bin.d.ts +1 -1
  44. package/dist/loop-runner-bin.js +1 -1
  45. package/dist/mcp/bin.js +7 -5
  46. package/dist/mcp/bin.js.map +1 -1
  47. package/dist/mcp/index.d.ts +1 -1
  48. package/dist/mcp/index.js +9 -9
  49. package/dist/mcp/memory-bin.js +2 -2
  50. package/dist/{model-policy-JOcg3NRs.js → model-policy-yxOrDTTj.js} +9 -9
  51. package/dist/model-policy-yxOrDTTj.js.map +1 -0
  52. package/dist/{prepare-Dtfk_Wi_.js → prepare-BnF8nGIg.js} +4 -4
  53. package/dist/{prepare-Dtfk_Wi_.js.map → prepare-BnF8nGIg.js.map} +1 -1
  54. package/dist/{profile-CNSQjOes.js → profile-CD25rTJF.js} +4 -4
  55. package/dist/{profile-CNSQjOes.js.map → profile-CD25rTJF.js.map} +1 -1
  56. package/dist/profiles.d.ts +1 -1
  57. package/dist/profiles.js +2 -2
  58. package/dist/{provision-supervisor-CeQTPHRr.js → provision-supervisor-Dx4TXTMM.js} +8 -8
  59. package/dist/{provision-supervisor-CeQTPHRr.js.map → provision-supervisor-Dx4TXTMM.js.map} +1 -1
  60. package/dist/{run-cancellation-D_4vJ522.js → run-cancellation-DYszFJtQ.js} +4 -4
  61. package/dist/{run-cancellation-D_4vJ522.js.map → run-cancellation-DYszFJtQ.js.map} +1 -1
  62. package/dist/{run-state-CTZVngI_.js → run-state-DHBoJjaF.js} +6 -6
  63. package/dist/{run-state-CTZVngI_.js.map → run-state-DHBoJjaF.js.map} +1 -1
  64. package/dist/{runtime-COm9uBBx.js → runtime-CaM5Gbbv.js} +76 -987
  65. package/dist/runtime-CaM5Gbbv.js.map +1 -0
  66. package/dist/{sandbox-profile-compat-oFGWESLn.js → sandbox-profile-compat-l-SLQnGb.js} +2 -2
  67. package/dist/{sandbox-profile-compat-oFGWESLn.js.map → sandbox-profile-compat-l-SLQnGb.js.map} +1 -1
  68. package/dist/{search-executor-DTEIl58w.js → search-executor-B9L8W_1O.js} +17 -46
  69. package/dist/search-executor-B9L8W_1O.js.map +1 -0
  70. package/dist/{server-zO9Nz9Nk.js → server-BlDV8sj_.js} +32 -8
  71. package/dist/{server-zO9Nz9Nk.js.map → server-BlDV8sj_.js.map} +1 -1
  72. package/dist/{settle-record-U7fkQEZa.js → settle-record-WWQasnDq.js} +12 -4
  73. package/dist/settle-record-WWQasnDq.js.map +1 -0
  74. package/dist/{snapshot-Cgj2Wzi6.js → snapshot-BKI_w3JD.js} +19 -5
  75. package/dist/snapshot-BKI_w3JD.js.map +1 -0
  76. package/dist/{structural-rollout-BwTwTUXF.js → structural-rollout-C1xxoWcN.js} +39 -57
  77. package/dist/structural-rollout-C1xxoWcN.js.map +1 -0
  78. package/dist/{supervise-tmqDU6YT.js → supervise-DWiSgPLB.js} +103 -111
  79. package/dist/supervise-DWiSgPLB.js.map +1 -0
  80. package/dist/{supervision-discovery-CGbXayGv.js → supervision-discovery-C9kfBLKy.js} +4 -4
  81. package/dist/{supervision-discovery-CGbXayGv.js.map → supervision-discovery-C9kfBLKy.js.map} +1 -1
  82. package/dist/{supervisor-1A27fE6k.js → supervisor-Dpeu7rnZ.js} +209 -999
  83. package/dist/supervisor-Dpeu7rnZ.js.map +1 -0
  84. package/dist/{scope-gIYQ5ZiL.js → supervisor-accounting-Bw4MJk-b.js} +755 -458
  85. package/dist/supervisor-accounting-Bw4MJk-b.js.map +1 -0
  86. package/dist/testing.d.ts +1 -1
  87. package/dist/testing.js +13 -13
  88. package/dist/{tool-server-B9q2dtnW.js → tool-server-DYwRbi0I.js} +3 -3
  89. package/dist/{tool-server-B9q2dtnW.js.map → tool-server-DYwRbi0I.js.map} +1 -1
  90. package/dist/{top-web-CS4PD4ck.js → top-web-B3HOAtrZ.js} +9 -7
  91. package/dist/{top-web-CS4PD4ck.js.map → top-web-B3HOAtrZ.js.map} +1 -1
  92. package/dist/tui/bin.js +6 -6
  93. package/dist/tui/bin.js.map +1 -1
  94. package/dist/tui/index.d.ts +1 -1
  95. package/dist/tui/index.js +2 -2
  96. package/dist/{types-w3Oo1uRJ.d.ts → types-C_qbnw9N.d.ts} +1263 -1497
  97. package/dist/{util-C_cdPNC4.js → util-fcckrkKp.js} +39 -113
  98. package/dist/util-fcckrkKp.js.map +1 -0
  99. package/dist/{workspace-streams-B7yNMahl.js → workspace-streams-DA9R3j-p.js} +2 -2
  100. package/dist/{workspace-streams-B7yNMahl.js.map → workspace-streams-DA9R3j-p.js.map} +1 -1
  101. package/package.json +1 -1
  102. package/dist/coordination-manager-D9z7To7p.js.map +0 -1
  103. package/dist/coordination-map-routing-CKRNZSaV.js.map +0 -1
  104. package/dist/errors-AzSuGI23.js.map +0 -1
  105. package/dist/failure-cause-BzjUUsRz.js.map +0 -1
  106. package/dist/graph-2_nX4ib4.js +0 -579
  107. package/dist/graph-2_nX4ib4.js.map +0 -1
  108. package/dist/graph-registry-7X2nrreR.js.map +0 -1
  109. package/dist/improve-BxfqTsZy.js.map +0 -1
  110. package/dist/model-policy-JOcg3NRs.js.map +0 -1
  111. package/dist/runtime-COm9uBBx.js.map +0 -1
  112. package/dist/scope-gIYQ5ZiL.js.map +0 -1
  113. package/dist/search-executor-DTEIl58w.js.map +0 -1
  114. package/dist/settle-record-U7fkQEZa.js.map +0 -1
  115. package/dist/snapshot-Cgj2Wzi6.js.map +0 -1
  116. package/dist/structural-rollout-BwTwTUXF.js.map +0 -1
  117. package/dist/supervise-tmqDU6YT.js.map +0 -1
  118. package/dist/supervisor-1A27fE6k.js.map +0 -1
  119. package/dist/util-C_cdPNC4.js.map +0 -1
@@ -1,14 +1,16 @@
1
- import { $t as FileResultBlobStore, B as FileSpawnJournal, C as writeWorkerSteerAcknowledgement, H as spawnJournalRecordEvents, M as workerTraceAnalysisStore, Pt as runtimeGraphMetadataKey, et as meterRuntimeOwnedAccounting, f as readWorkerCancelRequests, h as readWorkerSteerRequests, j as parseWorkerToolTraceArtifact, l as claimWorkerSteerDelivery, m as readWorkerSteerAcknowledgement, or as isLiveNodeStatus, p as readWorkerCancellation, sr as isTerminalNodeStatus, vt as resolveSpawnResourcePaths, x as writeWorkerCancellation } from "./scope-gIYQ5ZiL.js";
2
- import { $ as assertValidBudget, M as contentAddress, o as settlementFailureCause } from "./failure-cause-BzjUUsRz.js";
3
- import { a as errMessage, d as resolveRedactor, t as detachedFrozen } from "./snapshot-Cgj2Wzi6.js";
4
- import { Et as failedItems, Y as INBOX_RECEIPT_KEY, _ as withDriverExecutor, b as createExecutorRegistry, gt as CheckUnavailableError, nt as createPeerMailbox, v as InMemoryResultBlobStore, wt as checkVerdictOf, y as InMemorySpawnJournal } from "./supervisor-1A27fE6k.js";
5
- import { c as writeRunCancellation, i as readRunCancellation, r as readRunCancelRequest, t as applyRunCancellation, v as FileCoordinationLog } from "./run-cancellation-D_4vJ522.js";
6
- import { a as admitRuntimeSpawn, i as workerSettlement, n as resolveJsonPointer, o as safeJsonText, r as textBytes, t as createMapRouting } from "./coordination-map-routing-CKRNZSaV.js";
7
- import { agentProfileSchema, canonicalAgentProfileDigest, canonicalCandidateDigest } from "@tangle-network/agent-interface";
1
+ import { E as writeWorkerCancellation, F as parseWorkerToolTraceArtifact, I as workerTraceAnalysisStore, K as spawnJournalRecordEvents, O as writeWorkerSteerAcknowledgement, Rt as runtimeGraphMetadataKey, S as workerCancelRequestsFile, St as resolveSpawnResourcePaths, T as workerSteerRequestsDir, W as FileSpawnJournal, _ as readWorkerSteerAcknowledgement, d as claimWorkerSteerDelivery, dr as isLiveNodeStatus, fr as isTerminalNodeStatus, g as readWorkerCancellation, h as readWorkerCancelRequests, in as FileResultBlobStore, it as meterRuntimeOwnedAccounting, v as readWorkerSteerRequests } from "./supervisor-accounting-Bw4MJk-b.js";
2
+ import { m as ValidationError } from "./errors-DG8yufqX.js";
3
+ import { M as contentAddress, et as assertValidBudget, o as settlementFailureCause } from "./failure-cause-CVB-YE88.js";
4
+ import { a as errMessage, f as resolveRedactor, t as detachedFrozen } from "./snapshot-BKI_w3JD.js";
5
+ import { Y as INBOX_RECEIPT_KEY, _ as withDriverExecutor, b as createExecutorRegistry, gt as CheckUnavailableError, nt as createPeerMailbox, v as InMemoryResultBlobStore, y as InMemorySpawnJournal } from "./supervisor-Dpeu7rnZ.js";
6
+ import { i as checkVerdictOf, o as failedItems } from "./continuation-CIyKnS1Q.js";
7
+ import { c as writeRunCancellation, i as readRunCancellation, r as readRunCancelRequest, t as applyRunCancellation, v as FileCoordinationLog } from "./run-cancellation-DYszFJtQ.js";
8
+ import { a as admitRuntimeSpawn, i as workerSettlement, n as resolveJsonPointer, o as safeJsonText, r as textBytes, t as createMapRouting } from "./coordination-map-routing-XZXTlCmn.js";
9
+ import { ResourceRequestSchema, agentProfileSchema, canonicalAgentProfileDigest, canonicalCandidateDigest } from "@tangle-network/agent-interface";
8
10
  import { argHash, errorStreakDetector, observeAll, repeatedActionDetector } from "@tangle-network/agent-eval";
9
11
  import { registry } from "zod";
10
12
  import { randomUUID } from "node:crypto";
11
- import { readFileSync } from "node:fs";
13
+ import { closeSync, openSync, readSync, statSync } from "node:fs";
12
14
  import { join } from "node:path";
13
15
  // src/runtime/supervise/run-context.ts
14
16
  /**
@@ -112,6 +114,7 @@ const journalEventKinds = [
112
114
  "analyst-assignment",
113
115
  "acknowledgement",
114
116
  "tool-outcome",
117
+ "tool-timing",
115
118
  "lead-message",
116
119
  "map",
117
120
  "route"
@@ -484,18 +487,62 @@ const BUDGET_FIELD = {
484
487
  usd: "maxUsd"
485
488
  };
486
489
  // src/runtime/supervise/driver-control-acknowledgers.ts
487
- /** The journal file `createFileRunContext` writes inside the run directory. The acknowledger
488
- * reads it as EVIDENCE for the terminated-descendants set — the nested trees of a cancelled
489
- * lead journal their terminal records there before the lead settles into this scope. */
490
490
  const SPAWN_JOURNAL_FILE = "spawn-journal.jsonl";
491
- /**
492
- * Apply externally admitted steers once through the owning manager's control path.
493
- *
494
- * The `unknown` acknowledgement lands before authorization or delivery. A crash after that write
495
- * can lose this steer, but a restarted manager never delivers it again. This is the same
496
- * at-most-once crash boundary as coordination instruction receipts: no duplicate instruction is
497
- * safer than replaying a mutation whose first delivery may already have succeeded.
498
- */
491
+ function appendedLines(tail) {
492
+ let size;
493
+ try {
494
+ size = statSync(tail.file).size;
495
+ } catch {
496
+ return;
497
+ }
498
+ if (size < tail.offset) {
499
+ tail.offset = 0;
500
+ tail.partial = "";
501
+ tail.at.clear();
502
+ }
503
+ if (size === tail.offset) return void 0;
504
+ const buf = Buffer.allocUnsafe(size - tail.offset);
505
+ const fd = openSync(tail.file, "r");
506
+ try {
507
+ const got = readSync(fd, buf, 0, buf.length, tail.offset);
508
+ tail.offset += got;
509
+ const text = tail.partial + buf.subarray(0, got).toString("utf8");
510
+ const parts = text.split("\n");
511
+ tail.partial = text.endsWith("\n") ? "" : parts.pop() ?? "";
512
+ if (text.endsWith("\n")) parts.pop();
513
+ return parts;
514
+ } finally {
515
+ closeSync(fd);
516
+ }
517
+ }
518
+ function indexDeaths(tail) {
519
+ const lines = appendedLines(tail);
520
+ if (lines === void 0) return false;
521
+ let grew = false;
522
+ for (const line of lines) {
523
+ const trimmed = line.trim();
524
+ if (!trimmed) continue;
525
+ try {
526
+ for (const event of spawnJournalRecordEvents(JSON.parse(trimmed))) {
527
+ if (event === void 0) continue;
528
+ if (!(event.kind === "settled" && event.status === "down" || event.kind === "cancelled") || typeof event.id !== "string" || typeof event.at !== "string") continue;
529
+ const prev = tail.at.get(event.id);
530
+ if (prev !== void 0 && prev >= event.at) continue;
531
+ tail.at.set(event.id, event.at);
532
+ grew = true;
533
+ }
534
+ } catch {}
535
+ }
536
+ return grew;
537
+ }
538
+ function descendantsSince(tail, nodeId, sinceIso) {
539
+ indexDeaths(tail);
540
+ const prefix = `${nodeId}:`;
541
+ const ids = [...tail.at.keys()].filter((id) => id.startsWith(prefix) && (tail.at.get(id) ?? "") >= sinceIso);
542
+ ids.sort();
543
+ return ids;
544
+ }
545
+ /** Unknown ack lands before delivery. A crash can lose the steer; a restart must not deliver it twice. */
499
546
  function createSteerAcknowledger(deps) {
500
547
  const iso = () => new Date(deps.now()).toISOString();
501
548
  const awaiting = /* @__PURE__ */ new Map();
@@ -520,9 +567,29 @@ function createSteerAcknowledger(deps) {
520
567
  worker: request.worker,
521
568
  requestedAt: request.at
522
569
  });
570
+ let steerStamp = "";
571
+ let steerIdle = false;
572
+ const steerChanged = (phase) => {
573
+ let stamp = "missing";
574
+ try {
575
+ const st = statSync(workerSteerRequestsDir(deps.dir), { bigint: true });
576
+ stamp = `${st.dev}:${st.ino}:${st.mtimeNs}:${st.size}`;
577
+ } catch {
578
+ stamp = "missing";
579
+ }
580
+ const changed = stamp !== steerStamp;
581
+ if (changed && phase !== "boundary") steerStamp = stamp;
582
+ return changed;
583
+ };
523
584
  return { async pass(phase) {
585
+ const changed = steerChanged(phase);
586
+ if (phase !== "final" && !changed && steerIdle) return;
587
+ let waiting = false;
524
588
  for (const request of readWorkerSteerRequests(deps.dir)) {
525
- if (phase !== "final" && deps.signal?.aborted) return;
589
+ if (phase !== "final" && deps.signal?.aborted) {
590
+ steerIdle = false;
591
+ return;
592
+ }
526
593
  const rootRequest = request.worker === deps.ownerId && deps.deliverRoot !== void 0;
527
594
  if (phase === "boundary" && !rootRequest) continue;
528
595
  if (!rootRequest && !isDirectChildNodeId(deps, request.worker)) continue;
@@ -546,7 +613,10 @@ function createSteerAcknowledger(deps) {
546
613
  });
547
614
  continue;
548
615
  }
549
- if (rootRequest && deps.deliverRootReady !== void 0 && !deps.deliverRootReady()) continue;
616
+ if (rootRequest && deps.deliverRootReady !== void 0 && !deps.deliverRootReady()) {
617
+ waiting = true;
618
+ continue;
619
+ }
550
620
  if (!claimWorkerSteerDelivery(deps.dir, {
551
621
  ...base(request),
552
622
  effect: "unknown",
@@ -595,17 +665,9 @@ function createSteerAcknowledger(deps) {
595
665
  });
596
666
  }
597
667
  }
668
+ if (phase === "turn") steerIdle = !waiting;
598
669
  } };
599
670
  }
600
- /**
601
- * The OPERATOR-facing sentence for a steer outcome, written into the durable acknowledgement file.
602
- *
603
- * Deliberately separate from `downMessageRefusalReasons`, which is the MANAGER-facing sentence: one
604
- * says what happened for someone reading the run afterwards, the other tells a model what to do
605
- * next, and merging them would make one audience read the other's copy. They must nonetheless cover
606
- * the SAME outcomes, so `tests/kernel/refusal-reasons.test.ts` holds both maps total over
607
- * `DownMessageDeliveryOutcome` — a new code cannot land in only one.
608
- */
609
671
  function steerAcknowledgementDetail(outcome) {
610
672
  switch (outcome.outcome) {
611
673
  case "delivered": return "the owning manager delivered the steer to the exact live worker";
@@ -616,42 +678,32 @@ function steerAcknowledgementDetail(outcome) {
616
678
  case "runtime-error": return "delivery outcome is unknown after a runtime error";
617
679
  }
618
680
  }
619
- /**
620
- * The worker-cancel ACKNOWLEDGER — the runtime-side half of `run-layout`'s `cancelWorker`
621
- * contract. Router managers check between turns; native managers observe throughout their invocation.
622
- * Every manager with a `controlDir` mounts one;
623
- * OWNERSHIP keeps them from colliding: a request naming a node id is owned by the manager whose
624
- * own id is that node's parent, and a label/profile-name reference is owned by the `'run'`-scoped
625
- * (root) manager only — so exactly one acknowledger can ever apply one operation.
626
- *
627
- * Two-phase, honestly reported: `cancel_requested` is written the moment a live worker's abort is
628
- * issued (through the per-child abort chain the scope already owns, so siblings are untouched);
629
- * `cancelled` is written only when that worker's settlement is DELIVERED on the settle path with
630
- * a terminal `down`, and then the record names every subtree node id proven terminated. A worker
631
- * that already settled — or that settles `done` despite the abort — records `not_live`; a
632
- * reference matching nothing this manager owns stays pending (`cancelWorker` reports it
633
- * `unknown`). No path reports success for a missing worker.
634
- *
635
- * Expiry is run end, not a clock: `finish()` (after the final post-drain pass) writes `not_live`
636
- * for every owned request never applied and `unknown` for an issued abort whose settle the run
637
- * ended too soon to observe. A pending request can therefore never outlive its run and abort a
638
- * future spawn that happens to reuse a label.
639
- *
640
- * Idempotency is a lookup, in-process and across processes: an operation with a durable
641
- * acknowledgement is returned as-is and never re-applied.
642
- */
643
681
  function createCancelAcknowledger(deps) {
644
682
  const tracked = /* @__PURE__ */ new Map();
645
683
  let runTracked;
646
684
  const abortIssuedAt = /* @__PURE__ */ new Map();
647
685
  const iso = () => new Date(deps.now()).toISOString();
686
+ const waiting = /* @__PURE__ */ new Map();
687
+ const reconciling = /* @__PURE__ */ new Map();
688
+ const regrowing = /* @__PURE__ */ new Set();
689
+ let inboxBytes;
690
+ const deaths = {
691
+ file: join(deps.dir, SPAWN_JOURNAL_FILE),
692
+ offset: 0,
693
+ partial: "",
694
+ at: /* @__PURE__ */ new Map()
695
+ };
696
+ const watch = (record) => {
697
+ if (record.effect === "cancel_requested") reconciling.set(record.operationId, record);
698
+ else reconciling.delete(record.operationId);
699
+ if (record.effect === "cancelled" && abortIssuedAt.has(record.operationId)) regrowing.add(record.operationId);
700
+ else regrowing.delete(record.operationId);
701
+ };
648
702
  const write = (record) => {
649
703
  writeWorkerCancellation(deps.dir, record);
650
704
  tracked.set(record.operationId, record);
705
+ watch(record);
651
706
  };
652
- /** Whether this acknowledger owns `ref`. A node id deeper in this subtree belongs to the nested
653
- * manager that parents it; anything that is not a node id under this manager is a
654
- * label/profile-name reference, owned by the `'run'`-scoped manager alone. */
655
707
  const owned = (ref) => {
656
708
  if (isDirectChildNodeId(deps, ref)) return true;
657
709
  if (deps.controlScope !== "run") return false;
@@ -694,15 +746,13 @@ function createCancelAcknowledger(deps) {
694
746
  terminated: []
695
747
  });
696
748
  };
697
- /** The proven-terminated set for one record: the worker plus every subtree id with a terminal
698
- * journal record at/after the abort was issued. Union with what the record already names, so
699
- * the set only ever grows (a late teardown journal adds; nothing removes). */
749
+ /** Grows only: the worker, the record's ids, and subtree deaths at or after the abort. */
700
750
  const provenTerminated = (record, workerId) => {
701
751
  const since = abortIssuedAt.get(record.operationId) ?? record.observedAt;
702
752
  return [.../* @__PURE__ */ new Set([
703
753
  ...record.terminated,
704
754
  workerId,
705
- ...terminatedDescendants(deps.dir, workerId, since)
755
+ ...descendantsSince(deaths, workerId, since)
706
756
  ])].sort();
707
757
  };
708
758
  const reconcile = (record) => {
@@ -728,10 +778,6 @@ function createCancelAcknowledger(deps) {
728
778
  detail: `worker '${workerId}' settled done despite the abort request; nothing was terminated`
729
779
  });
730
780
  };
731
- /** Re-scan a `cancelled` record while the manager still turns: a descendant whose teardown
732
- * journals after the lead's settle joins the set on a later pass instead of being lost. Only
733
- * a grown set is re-written; the window needs the in-process abort instant, so a record a
734
- * PRIOR process closed stays as that process proved it. */
735
781
  const regrow = (record) => {
736
782
  const workerId = record.workerId;
737
783
  if (workerId === void 0 || !abortIssuedAt.has(record.operationId)) return;
@@ -742,36 +788,49 @@ function createCancelAcknowledger(deps) {
742
788
  terminated
743
789
  });
744
790
  };
745
- /**
746
- * The RUN-scoped request: seen once, `cancel_requested` written the moment the run's cascading
747
- * abort is issued through the one controller the run already has. The `supervise()` settle path
748
- * records what the run then actually did — this manager cannot observe its own tree's terminal
749
- * state from inside `act`.
750
- *
751
- * Applied only at a TURN boundary, never on the final post-drain pass: by then the driver has
752
- * finished and drained, so a root abort could only void work that is already delivered. A
753
- * request that arrives that late expires in `finish()` instead — it terminated nothing.
754
- */
755
791
  const passRun = () => {
756
792
  if (deps.controlScope !== "run" || deps.abortRun === void 0) return;
757
793
  if (runTracked !== void 0) return;
758
794
  runTracked = applyRunCancellation(deps.dir, deps.abortRun, iso);
759
795
  };
760
- const pass = (phase) => {
761
- if (phase === "turn") passRun();
796
+ const ingest = () => {
797
+ const file = workerCancelRequestsFile(deps.dir);
798
+ let size;
799
+ try {
800
+ size = statSync(file).size;
801
+ } catch {
802
+ size = null;
803
+ }
804
+ if (size === inboxBytes) return;
805
+ inboxBytes = size;
762
806
  for (const request of readWorkerCancelRequests(deps.dir)) {
763
- if (!owned(request.worker)) continue;
764
- let record = tracked.get(request.operationId);
765
- if (record === void 0) {
766
- record = readWorkerCancellation(deps.dir, request.operationId);
767
- if (record !== void 0) tracked.set(request.operationId, record);
768
- }
769
- if (record === void 0) {
770
- if (phase === "turn") apply(request);
771
- continue;
807
+ if (!owned(request.worker) || tracked.has(request.operationId) || waiting.has(request.operationId)) continue;
808
+ const record = readWorkerCancellation(deps.dir, request.operationId);
809
+ if (record === void 0) waiting.set(request.operationId, request);
810
+ else {
811
+ tracked.set(record.operationId, record);
812
+ watch(record);
772
813
  }
773
- if (record.effect === "cancel_requested") reconcile(record);
774
- else if (record.effect === "cancelled") regrow(record);
814
+ }
815
+ };
816
+ const pass = (phase) => {
817
+ if (phase === "turn") passRun();
818
+ ingest();
819
+ const pending = [...reconciling.values()];
820
+ if (phase === "turn") for (const request of [...waiting.values()]) {
821
+ waiting.delete(request.operationId);
822
+ apply(request);
823
+ if (!tracked.has(request.operationId)) waiting.set(request.operationId, request);
824
+ }
825
+ const grew = indexDeaths(deaths);
826
+ for (const record of pending) {
827
+ const current = tracked.get(record.operationId);
828
+ if (current?.effect === "cancel_requested") reconcile(current);
829
+ }
830
+ if (!grew) return;
831
+ for (const id of [...regrowing]) {
832
+ const record = tracked.get(id);
833
+ if (record !== void 0) regrow(record);
775
834
  }
776
835
  };
777
836
  return {
@@ -813,43 +872,6 @@ function createCancelAcknowledger(deps) {
813
872
  }
814
873
  };
815
874
  }
816
- /**
817
- * Subtree node ids with a terminal `down`/`cancelled` journal record at or after `sinceIso` —
818
- * the abort-issue instant (the acknowledger's own `observedAt` on the `cancel_requested` record,
819
- * runtime clock), never the client's `requestedAt` — read from the durable spawn journal beside
820
- * the run layout. The set is proven at acknowledgement time and is approximate about post-abort
821
- * causation: a descendant that died of its OWN cause after the abort was issued is
822
- * indistinguishable from the cascade and may be included; one whose teardown journals late joins
823
- * on a later acknowledger pass; a teardown journal still absent when the run ends is absent from
824
- * the set. Ids are hierarchical (`parent:sN`), so `${nodeId}:` prefixes exactly the subtree.
825
- * Tolerant of a missing or partially-written journal: evidence that cannot be read names fewer
826
- * nodes, never wrong ones.
827
- */
828
- function terminatedDescendants(dir, nodeId, sinceIso) {
829
- let raw;
830
- try {
831
- raw = readFileSync(join(dir, SPAWN_JOURNAL_FILE), "utf8");
832
- } catch {
833
- return [];
834
- }
835
- const prefix = `${nodeId}:`;
836
- const ids = /* @__PURE__ */ new Set();
837
- for (const line of raw.split("\n")) {
838
- const trimmed = line.trim();
839
- if (!trimmed) continue;
840
- try {
841
- for (const event of spawnJournalRecordEvents(JSON.parse(trimmed))) {
842
- if (event === void 0) continue;
843
- if (!(event.kind === "settled" && event.status === "down" || event.kind === "cancelled")) continue;
844
- if (typeof event.id !== "string" || !event.id.startsWith(prefix)) continue;
845
- if (typeof event.at !== "string" || event.at < sinceIso) continue;
846
- ids.add(event.id);
847
- }
848
- } catch {}
849
- }
850
- return [...ids].sort();
851
- }
852
- /** A direct child has exactly one generated spawn segment below its owner. */
853
875
  function isDirectChildNodeId(owner, ref) {
854
876
  return ref.startsWith(`${owner.ownerId}:s`) && /^s\d+$/.test(ref.slice(owner.ownerId.length + 1));
855
877
  }
@@ -1866,15 +1888,13 @@ function mountGraphsTools({ opts, tools, admitSpawn }) {
1866
1888
  error: "invalid-graph",
1867
1889
  reason: prepared.reason
1868
1890
  };
1869
- const profile = detachedFrozen(prepared.profile);
1870
1891
  return admitSpawn({
1871
- profile,
1892
+ profile: detachedFrozen(prepared.profile),
1872
1893
  args: a,
1873
1894
  task: deliverable,
1874
1895
  key,
1875
1896
  continuity: { continuity: "fresh" },
1876
1897
  successorOf: void 0,
1877
- preflightProfile: () => opts.resolveSpawnProfile ? detachedFrozen(opts.resolveSpawnProfile(profile)) : profile,
1878
1898
  resourcesFromPath: [],
1879
1899
  defaultLabel: `graph:${prepared.root}`,
1880
1900
  accepted: { graph: {
@@ -2153,6 +2173,10 @@ const projectEvent = (ev) => {
2153
2173
  type: "tool-outcome",
2154
2174
  ...ev.outcome
2155
2175
  };
2176
+ if (ev.type === "tool-timing") return {
2177
+ type: "tool-timing",
2178
+ ...ev.call
2179
+ };
2156
2180
  if (ev.type === "lead-message") return {
2157
2181
  type: "lead-message",
2158
2182
  ...ev.message
@@ -3020,7 +3044,6 @@ async function spawnNode(state, marker, profile, task, key, continuity, traversa
3020
3044
  key,
3021
3045
  continuity,
3022
3046
  successorOf: state.settledWorker.get(marker.node),
3023
- preflightProfile: () => state.opts.resolveSpawnProfile ? detachedFrozen(state.opts.resolveSpawnProfile(profile)) : profile,
3024
3047
  resourcesFromPath: [],
3025
3048
  defaultLabel: marker.node,
3026
3049
  route: Object.freeze({ ...marker })
@@ -3680,14 +3703,21 @@ function resolveContinuity(state, requested, profileName, key) {
3680
3703
  * assignment, budget reservation, `Scope.spawn` — is synchronous and commits state.
3681
3704
  * Returns the refusal body, or `undefined` when the spawn may proceed.
3682
3705
  */
3683
- async function preflightRefusal(state, input, label, task) {
3706
+ /** `spawn_worker`'s optional machine request, as the canonical resource request. */
3707
+ function spawnResources(raw) {
3708
+ if (raw === void 0) return void 0;
3709
+ const parsed = ResourceRequestSchema.omit({ providerOptions: true }).strict().safeParse(raw);
3710
+ if (!parsed.success) throw new ValidationError(`spawn_worker resources: ${parsed.error.message}`);
3711
+ return detachedFrozen(parsed.data);
3712
+ }
3713
+ async function preflightRefusal(state, effective, spawn) {
3684
3714
  const { opts } = state;
3685
3715
  if (!opts.preflightSpawn) return void 0;
3686
- const { key } = input;
3687
- const refusal = await opts.preflightSpawn(input.preflightProfile(), {
3688
- label,
3716
+ const { key, resources, ...rest } = spawn;
3717
+ const refusal = await opts.preflightSpawn(effective, {
3718
+ ...rest,
3689
3719
  ...key !== void 0 ? { key } : {},
3690
- task
3720
+ ...resources !== void 0 ? { resources } : {}
3691
3721
  });
3692
3722
  if (!refusal) return void 0;
3693
3723
  state.preflightCounts[refusal.cause] += 1;
@@ -3706,7 +3736,14 @@ async function admitSpawn(state, input) {
3706
3736
  const a = input.args;
3707
3737
  const task = detachedFrozen(input.task);
3708
3738
  const label = typeof a.label === "string" ? a.label : input.defaultLabel;
3709
- const refused = await preflightRefusal(state, input, label, task);
3739
+ const effective = opts.resolveSpawnProfile ? detachedFrozen(opts.resolveSpawnProfile(profile)) : profile;
3740
+ const resources = spawnResources(a.resources);
3741
+ const refused = await preflightRefusal(state, effective, {
3742
+ label,
3743
+ task,
3744
+ key,
3745
+ resources
3746
+ });
3710
3747
  if (refused !== void 0) return refused;
3711
3748
  const budget = Object.freeze(a.budget === void 0 ? opts.perWorker : mergeBudget(opts.perWorker, a.budget));
3712
3749
  const assignmentId = key !== void 0 ? `key:${key}` : `ordinal:${state.unkeyedAssignment.next++}`;
@@ -3718,6 +3755,7 @@ async function admitSpawn(state, input) {
3718
3755
  task,
3719
3756
  label,
3720
3757
  ...key !== void 0 ? { key } : {},
3758
+ ...resources !== void 0 ? { resources } : {},
3721
3759
  continuity: continuity.continuity,
3722
3760
  ...continuity.continuity === "resume" ? { resume: continuity.resume } : {},
3723
3761
  ...successorOf !== void 0 ? { successorOf } : {},
@@ -3725,7 +3763,7 @@ async function admitSpawn(state, input) {
3725
3763
  ...input.map !== void 0 ? { map: input.map } : {},
3726
3764
  ...input.route !== void 0 ? { route: input.route } : {}
3727
3765
  });
3728
- const res = opts.scope.spawn(() => opts.makeWorkerAgent(profile, context), task, {
3766
+ const res = opts.scope.spawn(() => opts.makeWorkerAgent(effective, context), task, {
3729
3767
  budget,
3730
3768
  label,
3731
3769
  assignmentId,
@@ -4368,7 +4406,7 @@ function spawnWorkerTool(ctx) {
4368
4406
  const { profileTable } = ctx;
4369
4407
  return {
4370
4408
  name: "spawn_worker",
4371
- description: "Start a worker the driver will drive. `profile` is the worker or another driver; `task` is what it should do. Reserves budget from the conserved pool and fails closed. Pass an optional `budget` (per-field) to give a sub-task more or less than the default — it merges over the per-worker default; the conserved pool is still the hard fence. Each worker reserves only the channels capped by the pool. workerSlots sets concurrency. A funded worker waits with `status: \"queued\"` when every slot is busy and starts when one frees. A new worker whose budget cannot fit is refused immediately. Wait for settlements or request a smaller allocation before trying again. End your turn to be woken with results. Pass a `key` naming the assignment to make it run-once ACROSS restarts: a key that already completed returns the finished result (`resumed: \"completed\"` — no work re-runs, nothing is spent). A keyed worker that settled with a `budgetStop` keeps its session for a while: spawn the same key with a larger `budget` and a new worker continues that same session where it stopped (`resumed: \"continued\"`, `priorWorkerId`), or cancel_worker the stopped worker to release it. A key whose prior attempt failed (`down`) spawns fresh and says so (`resumed: \"retried\"`), and a key the process died with IN FLIGHT retries under the same key when the executor provably died with the process (an inline worker — the same `resumed: \"retried\"`, naming the interruption). A key with no terminal receipt whose execution may still exist elsewhere (sandbox, CLI bridge, router) is refused (`error: \"in-doubt\"`) until its exact prior execution is recovered. A key still running is refused (`error: \"duplicate-key\"`). Returns `freeSlots`: how many MORE workers start at once (`null` = no slot bound), and `queued`: how many of your workers wait for a slot. Parallel workers finish the run sooner than one at a time." + (profileTable === void 0 ? "" : " This run has a profiles table: pass `profile` as one of the table's names to run that exact profile, or write a new profile under a name the table does not hold."),
4409
+ description: "Start a worker the driver will drive. `profile` is the worker or another driver; `task` is what it should do. Reserves budget from the conserved pool and fails closed. Pass an optional `budget` (per-field) to give a sub-task more or less than the default — it merges over the per-worker default; the conserved pool is still the hard fence. Each worker reserves only the channels capped by the pool. workerSlots sets concurrency. A funded worker waits with `status: \"queued\"` when every slot is busy and starts when one frees. A new worker whose budget cannot fit is refused immediately. Wait for settlements or request a smaller allocation before trying again. End your turn to be woken with results. Pass a `key` naming the assignment to make it run-once ACROSS restarts: a key that already completed returns the finished result (`resumed: \"completed\"` — no work re-runs, nothing is spent). A keyed worker that settled with a `budgetStop` keeps its session for a while: spawn the same key with a larger `budget` and a new worker continues that same session where it stopped (`resumed: \"continued\"`, `priorWorkerId`), or cancel_worker the stopped worker to release it. A key whose prior attempt failed (`down`) spawns fresh and says so (`resumed: \"retried\"`), and a key the process died with IN FLIGHT retries under the same key when the executor provably died with the process (an inline worker — the same `resumed: \"retried\"`, naming the interruption). A key with no terminal receipt whose execution may still exist elsewhere (sandbox, CLI bridge, router) is refused (`error: \"in-doubt\"`) until its exact prior execution is recovered. A key still running is refused (`error: \"duplicate-key\"`). Returns `freeSlots`: how many MORE workers start at once (`null` = no slot bound), and `queued`: how many of your workers wait for a slot. Parallel workers finish the run sooner than one at a time. An authored profile must be fully executable before it spawns: `harness`, `model.provider`, a concrete `model.default`, and no legacy `systemPrompt` — one refusal names every field still missing." + (profileTable === void 0 ? "" : " This run has a profiles table: pass `profile` as one of the table's names to run that exact profile, or write a new profile under a name the table does not hold."),
4372
4410
  inputSchema: {
4373
4411
  type: "object",
4374
4412
  properties: {
@@ -4389,9 +4427,29 @@ function spawnWorkerTool(ctx) {
4389
4427
  },
4390
4428
  successorOf: {
4391
4429
  type: "string",
4392
- description: "The workerId of a SETTLED worker you spawned that this new worker replaces — for example one that failed, stalled, or finished with a weak result, restarted with a changed profile or task. The run record then names the predecessor on the new worker. Refused for an id you did not spawn (`error: \"successor-unknown\"`) and for a worker still live (`error: \"successor-live\"` — end your turn until it settles). Put what the successor needs from its predecessor in `task`."
4430
+ description: "The workerId of a worker you spawned that this new worker revises or replaces — for example one that failed, stalled, or finished with a weak result, restarted with a changed profile or task. The predecessor may still be live; cancel_worker it if the successor supersedes it. The run record names the predecessor on the new worker. Refused for an id you did not spawn (`error: \"successor-unknown\"`). Put what the successor needs from its predecessor in `task`."
4393
4431
  },
4394
- budget: spawnBudgetArg
4432
+ budget: spawnBudgetArg,
4433
+ resources: {
4434
+ type: "object",
4435
+ description: "Optional machine for this worker: `cpu`, `memoryMb`, `diskMb`, `gpu`. Omit for the run's default. Refused, naming the ceiling, above the backend's `maxWorkerResources`.",
4436
+ properties: {
4437
+ cpu: {
4438
+ type: "number",
4439
+ exclusiveMinimum: 0
4440
+ },
4441
+ memoryMb: {
4442
+ type: "number",
4443
+ exclusiveMinimum: 0
4444
+ },
4445
+ diskMb: {
4446
+ type: "number",
4447
+ exclusiveMinimum: 0
4448
+ },
4449
+ gpu: { type: "string" }
4450
+ },
4451
+ additionalProperties: false
4452
+ }
4395
4453
  },
4396
4454
  required: ["profile", "task"]
4397
4455
  },
@@ -4446,15 +4504,9 @@ function successorRefusal({ opts, spawn, workers }, successorOf) {
4446
4504
  live: workers.liveWorkerCount(),
4447
4505
  freeSlots: workers.freeWorkerSlots()
4448
4506
  };
4449
- if (isLiveNodeStatus(predecessor.status)) return {
4450
- error: "successor-live",
4451
- reason: `worker '${successorOf}' has not settled — end your turn until it does, then spawn its successor (steer_agent redirects a live worker instead)`,
4452
- live: workers.liveWorkerCount(),
4453
- freeSlots: workers.freeWorkerSlots()
4454
- };
4455
4507
  }
4456
4508
  async function spawnWorkerHandler(ctx, raw) {
4457
- const { opts, spawn, workers } = ctx;
4509
+ const { spawn, workers } = ctx;
4458
4510
  const a = obj(raw);
4459
4511
  const key = a.key === void 0 ? void 0 : str(a.key, "key");
4460
4512
  const resolvedProfile = await resolveSpawnedProfile(ctx, a.profile);
@@ -4480,7 +4532,6 @@ async function spawnWorkerHandler(ctx, raw) {
4480
4532
  key,
4481
4533
  continuity,
4482
4534
  successorOf,
4483
- preflightProfile: () => opts.resolveSpawnProfile ? detachedFrozen(opts.resolveSpawnProfile(profile)) : profile,
4484
4535
  resourcesFromPath: resolved,
4485
4536
  defaultLabel: typeof profile.name === "string" && profile.name.length > 0 ? profile.name : "worker"
4486
4537
  });
@@ -4926,7 +4977,54 @@ function assembleTools(parts) {
4926
4977
  analystRunContext: analysis.analystRunContext,
4927
4978
  defineAnalyst: analysis.defineAnalyst
4928
4979
  });
4929
- return tools;
4980
+ return tools.map((tool) => timeCoordinationTool(bus, tool));
4981
+ }
4982
+ /** Record handler duration for every coordination tool call without changing its result. */
4983
+ function timeCoordinationTool(bus, tool) {
4984
+ return {
4985
+ ...tool,
4986
+ handler: async (raw) => {
4987
+ const receivedAt = Date.now();
4988
+ try {
4989
+ const value = await tool.handler(raw);
4990
+ await recordToolTiming(bus, tool.name, receivedAt, true, value);
4991
+ return value;
4992
+ } catch (error) {
4993
+ await recordToolTiming(bus, tool.name, receivedAt, false, error);
4994
+ throw error;
4995
+ }
4996
+ }
4997
+ };
4998
+ }
4999
+ async function recordToolTiming(bus, tool, receivedAt, ok, value) {
5000
+ try {
5001
+ await bus.publish({
5002
+ type: "tool-timing",
5003
+ call: {
5004
+ tool,
5005
+ receivedAt,
5006
+ elapsedMs: Math.max(0, Date.now() - receivedAt),
5007
+ responseBytes: responseUtf8Bytes(ok ? value : errorText(value)),
5008
+ ok
5009
+ }
5010
+ }, { queue: false });
5011
+ } catch (error) {
5012
+ console.error("Runtime could not record coordination tool timing", {
5013
+ tool,
5014
+ error: error instanceof Error ? error.message : String(error)
5015
+ });
5016
+ }
5017
+ }
5018
+ function responseUtf8Bytes(value) {
5019
+ if (typeof value === "string") return Buffer.byteLength(value);
5020
+ try {
5021
+ return Buffer.byteLength(JSON.stringify(value) ?? "null");
5022
+ } catch {
5023
+ return Buffer.byteLength(String(value));
5024
+ }
5025
+ }
5026
+ function errorText(error) {
5027
+ return error instanceof Error ? error.message : String(error);
4930
5028
  }
4931
5029
  function managerSurface(parts, tools) {
4932
5030
  const { opts, bus, state, ledger, questionLedger, spawnAdmission, analysis, settlements, messages, workers, waiting, intake, journal, peerMail, ackEnv, ready } = parts;
@@ -5135,4 +5233,4 @@ function createResumeReplay({ opts, bus, ledger, analysis, projectSettled }) {
5135
5233
  }
5136
5234
  export { downMessageRefusalReasons as a, createSteerAcknowledger as c, coordinationVerbNames as d, createFileRunContext as f, watchTrace as i, budgetOverspendText as l, withRunContext as m, createCoordinationToolsForManager as n, createEventBus as o, createInMemoryRunContext as p, defaultToolDetectors as r, createCancelAcknowledger as s, createCoordinationTools as t, normalizeAnalyzeOnSettle as u };
5137
5235
 
5138
- //# sourceMappingURL=coordination-manager-D9z7To7p.js.map
5236
+ //# sourceMappingURL=coordination-manager-sa_PkISw.js.map