@tangle-network/agent-runtime 0.123.1 → 0.128.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 (53) hide show
  1. package/README.md +2 -2
  2. package/dist/agent.d.ts +1 -1
  3. package/dist/agent.js +2 -2
  4. package/dist/{environment-provider-PM9PeW_J.d.ts → environment-provider-CUFsyymu.d.ts} +7 -1
  5. package/dist/environment-provider.d.ts +1 -1
  6. package/dist/{index-xP27vtnt.d.ts → index-BhZhQw77.d.ts} +247 -145
  7. package/dist/{index-4NcThsDc.d.ts → index-BhuzfG2r.d.ts} +3 -3
  8. package/dist/{index-CQBioeAj.d.ts → index-DLM0W1h1.d.ts} +5 -5
  9. package/dist/index.d.ts +5 -5
  10. package/dist/index.js +5 -5
  11. package/dist/index.js.map +1 -1
  12. package/dist/intelligence.d.ts +1 -1
  13. package/dist/kernel.d.ts +3 -3
  14. package/dist/kernel.js +3 -3
  15. package/dist/{knowledge-BOHj6nGh.js → knowledge-DF63xPr4.js} +2 -2
  16. package/dist/{knowledge-BOHj6nGh.js.map → knowledge-DF63xPr4.js.map} +1 -1
  17. package/dist/knowledge.d.ts +1 -1
  18. package/dist/knowledge.js +1 -1
  19. package/dist/{loop-runner-bin-DUM37LLw.js → loop-runner-bin-CWqOpCEw.js} +3 -3
  20. package/dist/{loop-runner-bin-DUM37LLw.js.map → loop-runner-bin-CWqOpCEw.js.map} +1 -1
  21. package/dist/{loop-runner-bin-DMnNxZHw.d.ts → loop-runner-bin-Ckp_9tmD.d.ts} +3 -3
  22. package/dist/loop-runner-bin.d.ts +1 -1
  23. package/dist/loop-runner-bin.js +1 -1
  24. package/dist/mcp/bin.js +1 -1
  25. package/dist/mcp/index.d.ts +3 -7
  26. package/dist/mcp/index.js +3 -3
  27. package/dist/{openai-tools-Bm1SDJIp.js → openai-tools-D3XfrrQ6.js} +2 -2
  28. package/dist/{openai-tools-Bm1SDJIp.js.map → openai-tools-D3XfrrQ6.js.map} +1 -1
  29. package/dist/primeintellect/index.d.ts +1 -1
  30. package/dist/{runtime-DZblIf3R.js → runtime-5uDVVfER.js} +444 -132
  31. package/dist/runtime-5uDVVfER.js.map +1 -0
  32. package/dist/{supervise-Cx24b3lw.js → supervise-CsTKbH9R.js} +209 -24
  33. package/dist/supervise-CsTKbH9R.js.map +1 -0
  34. package/dist/testing.js +8 -8
  35. package/package.json +4 -4
  36. package/skills/agent-graphs/IMPROVE.md +58 -0
  37. package/skills/agent-graphs/SKILL.md +140 -0
  38. package/skills/agent-graphs/cases/artifact-mission-release-notes.json +10 -0
  39. package/skills/agent-graphs/cases/audited-single-writer.json +9 -0
  40. package/skills/agent-graphs/cases/cap-as-stop-mistake.json +8 -0
  41. package/skills/agent-graphs/cases/floor-trap-pi.json +11 -0
  42. package/skills/agent-graphs/cases/mission-in-deliverable.json +8 -0
  43. package/skills/agent-graphs/cases/review-pipeline.json +14 -0
  44. package/skills/agent-graphs/cases/runtime-discovered-fanout.json +8 -0
  45. package/skills/agent-graphs/cases/single-agent-suffices.json +7 -0
  46. package/skills/agent-graphs/cases/steer-heavy-drafting.json +9 -0
  47. package/skills/agent-graphs/cases/unmeasured-harness.json +9 -0
  48. package/skills/agent-graphs/generations/gen1-baseline.json +248 -0
  49. package/skills/agent-graphs/generations/gen2.json +375 -0
  50. package/skills/agent-graphs/generations/gen3.json +702 -0
  51. package/skills/build-with-agent-runtime/SKILL.md +1 -0
  52. package/dist/runtime-DZblIf3R.js.map +0 -1
  53. package/dist/supervise-Cx24b3lw.js.map +0 -1
@@ -1,16 +1,17 @@
1
1
  import { n as AnalystError, r as BackendTransportError, s as PlannerError, u as ValidationError } from "./errors-DEAvWQPy.js";
2
2
  import { i as normalizeBackendStreamEvent, o as newRuntimeSession, s as nowIso } from "./backends-CiOCyRHb.js";
3
- import { i as InMemorySpawnJournal, r as InMemoryResultBlobStore, v as isTraceAnalysisStore } from "./spawn-journal-DsZKDqeh.js";
3
+ import { i as InMemorySpawnJournal, r as InMemoryResultBlobStore, v as isTraceAnalysisStore, x as contentAddress } from "./spawn-journal-DsZKDqeh.js";
4
4
  import { a as randomSuffix, c as stringifySafe, f as zeroTokenUsage, r as isAbortError, s as sleep, t as addTokenUsage } from "./util-MVgdwuIS.js";
5
5
  import { i as redactProtectedValue, r as redactProtectedReason } from "./protected-redaction--F3v1oo8.js";
6
- import { _t as routerChatWithUsage, bt as runBrainLoop, en as isHarnessNativeModel, et as rollingDispatch, ht as routerBrain, l as withDriverExecutor, m as settledToIteration, n as createSupervisor } from "./supervisor-DpjO0Gmy.js";
6
+ import { $t as concreteProfileModel, Qt as concreteModelId, _t as routerChatWithUsage, bt as runBrainLoop, ct as attestRuntimeOwnedExecutor, dt as newExecutionAttemptId, en as isHarnessNativeModel, et as rollingDispatch, ht as routerBrain, l as withDriverExecutor, m as settledToIteration, n as createSupervisor } from "./supervisor-DpjO0Gmy.js";
7
7
  import { C as observe, O as strategyAuthorMethod, b as sample, v as refine, x as sampleThenRefine, y as runAgentic } from "./structural-rollout-zY0oqQzO.js";
8
8
  import { i as notifyRuntimeHookEvent, t as composeRuntimeHooks } from "./runtime-hooks-C7iJOWm3.js";
9
9
  import { a as notifySandboxEventObserver, i as mapSandboxToolEvent, r as mapSandboxEvent, t as createSandboxToolPartState } from "./sandbox-events-Yhd1GYWl.js";
10
- import { Ct as createWorktreeCliExecutor, Et as createPushTraceSource, Ft as probeSandboxCapabilities, Mt as runAgentRounds, Pt as createSandboxLineage, Qt as kernelPromptRegistry, St as createExecutorRegistry, Zt as formatPromptHandle, jt as defaultSelectWinner, n as supervise, nn as gateOnDeliverable, r as workerFromBackend, xt as createExecutor } from "./supervise-Cx24b3lw.js";
10
+ import { $t as kernelPromptRegistry, Ct as mergeAbortSignals, Ft as createSandboxLineage, It as probeSandboxCapabilities, Nt as defaultSelectWinner, Ot as createPushTraceSource, Pt as runAgentRounds, Qt as formatPromptHandle, St as createExecutorRegistry, Tt as createWorktreeCliExecutor, Ut as canonicalizeAuthoredProfile, n as supervise, r as workerFromBackend, rn as gateOnDeliverable, wt as taskToPrompt, xt as createExecutor } from "./supervise-CsTKbH9R.js";
11
11
  import { CODING_HARNESSES, InMemoryTraceStore, OUTPUT_VALUE, benjaminiHochberg, buildTrajectory, computeFindingId as computeFindingId$1, confidenceInterval, expandProfileAxes, harnessAxisOf, makeFinding as makeFinding$1, pairedBootstrap, paretoFrontier, scoreKnowledgeReadiness, wilcoxonSignedRank, wilson } from "@tangle-network/agent-eval";
12
12
  import { heldoutSignificance, runProfileMatrix } from "@tangle-network/agent-eval/campaign";
13
13
  import { agentProfileSchema, canonicalCandidateDigest, validateAgentProfileSecurity } from "@tangle-network/agent-interface";
14
+ import { randomUUID } from "node:crypto";
14
15
  import { mkdir, readFile, writeFile } from "node:fs/promises";
15
16
  import { appendFileSync, chmodSync, constants, copyFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
16
17
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
@@ -1295,106 +1296,13 @@ function resolveSandboxClient(opts) {
1295
1296
  }
1296
1297
  }
1297
1298
  //#endregion
1298
- //#region src/runtime/steering-drivers.ts
1299
- /**
1300
- * Interpret a {@link SteeringDirectiveData} as a loop `Driver` — the ONE interpreter both
1301
- * control policies share. The directive is data; only `applyContinuation` (how the caller's
1302
- * opaque Task carries a steering string) remains code, exactly as `taskToPrompt` does.
1303
- */
1304
- function steeringDriver(directive, applyContinuation, name) {
1305
- const driverName = name ?? directive.kind;
1306
- const maxIterations = directive.maxTraversals;
1307
- const continuationFor = (passed) => directive.kind === "naive" ? directive.continuation : passed ? directive.onPass : directive.onFail;
1308
- const rationale = directive.kind === "naive" ? "naive fixed continuation (no grade signal)" : "dumb pass/fail-only continuation (no grader findings)";
1309
- return {
1310
- name: driverName,
1311
- plan(task, history) {
1312
- if (history.length === 0) return Promise.resolve([task]);
1313
- const passed = history[history.length - 1]?.verdict?.valid === true;
1314
- if (passed) return Promise.resolve([]);
1315
- if (history.length >= maxIterations) return Promise.resolve([]);
1316
- return Promise.resolve([applyContinuation(task, continuationFor(passed))]);
1317
- },
1318
- decide(history) {
1319
- return decideUntilValidOrCapped(history, maxIterations);
1320
- },
1321
- describePlan() {
1322
- return {
1323
- kind: "refine",
1324
- rationale
1325
- };
1326
- }
1327
- };
1328
- }
1329
- /**
1330
- * Shared `decide()` for the steering drivers. Verbatim semantics of the
1331
- * reference refine driver: a valid iteration anywhere → terminal `'pick-winner'`;
1332
- * otherwise `'refine'` (non-terminal, run another shot) while history is under
1333
- * the shot cap, else terminal `'fail'`. Pure over history; reads only
1334
- * `verdict.valid` (never notes/scores) so it adds no leak of its own.
1335
- */
1336
- function decideUntilValidOrCapped(history, maxIterations) {
1337
- if (history.some((it) => it.verdict?.valid)) return "pick-winner";
1338
- return history.length < maxIterations ? "refine" : "fail";
1339
- }
1340
- /**
1341
- * `naiveDriver` — the no-signal steering control.
1342
- *
1343
- * `plan()` runs the initial `task` at shot 0, then issues the SAME fixed
1344
- * `continuation` every subsequent round until a shot is valid or the cap is
1345
- * hit. It reads NOTHING from `history[last].verdict` — not `.valid`, not
1346
- * `.notes`, not `.scores`. It is the floor a coached loop must beat to earn its
1347
- * coaching: any lift over naive that is not also present in `dumb` is
1348
- * attributable to the pass/fail bit, and any lift of `refine` over `dumb` is
1349
- * attributable to the grader's findings.
1350
- */
1351
- /** Thin compatibility wrapper over {@link steeringDriver} for the naive (no-signal) control.
1352
- * @deprecated The policy is DATA now — build the directive and interpret it:
1353
- * `steeringDriver({ kind: 'naive', continuation, maxTraversals }, applyContinuation)`. This
1354
- * wrapper survives for existing callers and will be removed in the next major. */
1355
- function naiveDriver(options) {
1356
- const { continuation, applyContinuation, maxIterations, name = "naive" } = options;
1357
- return steeringDriver({
1358
- kind: "naive",
1359
- continuation,
1360
- maxTraversals: maxIterations
1361
- }, applyContinuation, name);
1362
- }
1363
- /**
1364
- * `dumbDriver` — the pass/fail-only steering control.
1365
- *
1366
- * `plan()` runs the initial `task` at shot 0, then reads ONLY
1367
- * `history[last].verdict.valid` (the boolean) and issues `onPass` or `onFail`
1368
- * accordingly. It MUST NOT read `.notes` or `.scores` — that boundary is the
1369
- * leak-free firewall. A `verdict` with no `valid` set (or no verdict) is
1370
- * treated as not-valid, so the driver is total and never throws on a
1371
- * grader/transport gap.
1372
- *
1373
- * The `dumb → refine` gap is the headline measurement: refine reads the
1374
- * grader's `notes`, dumb reads only the pass/fail bit, so the difference is
1375
- * exactly the value the findings add over a bare boolean.
1376
- */
1377
- /** Thin compatibility wrapper over {@link steeringDriver} for the dumb (pass/fail-only) control.
1378
- * @deprecated The policy is DATA now — build the directive and interpret it:
1379
- * `steeringDriver({ kind: 'dumb', onPass, onFail, maxTraversals }, applyContinuation)`. This
1380
- * wrapper survives for existing callers and will be removed in the next major. */
1381
- function dumbDriver(options) {
1382
- const { onPass, onFail, applyContinuation, maxIterations, name = "dumb" } = options;
1383
- return steeringDriver({
1384
- kind: "dumb",
1385
- onPass,
1386
- onFail,
1387
- maxTraversals: maxIterations
1388
- }, applyContinuation, name);
1389
- }
1390
- //#endregion
1391
1299
  //#region src/runtime/define-leaderboard.ts
1392
1300
  /**
1393
1301
  * `defineLeaderboard` — the declarative eval-leaderboard facade.
1394
1302
  *
1395
1303
  * A product's harness×model leaderboard is always the same assembly: expand a
1396
1304
  * base profile across the harness×model axes (`expandProfileAxes`), run every
1397
- * (profile, case) cell as a driven loop (`loopDispatch` + the naive steering directive), score
1305
+ * (profile, case) cell as a driven loop (`loopDispatch` + the naive retry driver), score
1398
1306
  * with the domain's grader, and emit ONE `runProfileMatrix` call. Each product
1399
1307
  * hand-rolled that assembly (~650 lines each) and re-hit the same footguns:
1400
1308
  * stale cell-cache reuse, zero-token stub cells, missing model snapshots.
@@ -1420,6 +1328,36 @@ function dumbDriver(options) {
1420
1328
  *
1421
1329
  * @experimental
1422
1330
  */
1331
+ /**
1332
+ * The no-signal retry floor as a bare `Driver` literal: re-run the case VERBATIM as an
1333
+ * independent attempt until one shot scores (`verdict.valid`) or the shot cap. It reads ONLY
1334
+ * `verdict.valid` — never `notes`/`scores` — so a leaderboard cell's retries carry no grader
1335
+ * findings into the next shot (the leak-free firewall). Steering POLICY texts are registry data
1336
+ * (`delegates/naive-continuation` and siblings in the kernel prompt registry) attached to graph
1337
+ * edges; this loop-kernel control folds no text in — each shot is the same task object, and the
1338
+ * per-shot nonce in `taskToPrompt` keeps the attempts independent at the router.
1339
+ */
1340
+ function naiveRetryDriver(shots) {
1341
+ return {
1342
+ name: "naive",
1343
+ plan(task, history) {
1344
+ if (history.length === 0) return Promise.resolve([task]);
1345
+ if (history[history.length - 1]?.verdict?.valid === true) return Promise.resolve([]);
1346
+ if (history.length >= shots) return Promise.resolve([]);
1347
+ return Promise.resolve([task]);
1348
+ },
1349
+ decide(history) {
1350
+ if (history.some((it) => it.verdict?.valid)) return "pick-winner";
1351
+ return history.length < shots ? "refine" : "fail";
1352
+ },
1353
+ describePlan() {
1354
+ return {
1355
+ kind: "refine",
1356
+ rationale: "naive fixed continuation (no grade signal)"
1357
+ };
1358
+ }
1359
+ };
1360
+ }
1423
1361
  /** Read `--name <value>` from an argv array. */
1424
1362
  function argOf(argv, name) {
1425
1363
  const i = argv.indexOf(`--${name}`);
@@ -1588,11 +1526,7 @@ function defineLeaderboard(spec) {
1588
1526
  ...!isHarnessNativeModel(modelId) || backendName === "cli-bridge" ? { model: modelId } : {}
1589
1527
  };
1590
1528
  return {
1591
- driver: steeringDriver({
1592
- kind: "naive",
1593
- continuation: "",
1594
- maxTraversals: shots
1595
- }, (task) => task, "naive"),
1529
+ driver: naiveRetryDriver(shots),
1596
1530
  agentRun: {
1597
1531
  profile: cellProfile,
1598
1532
  taskToPrompt: (s) => `${promptOf(s)}\n\n<!-- independent-attempt:${shotNonce++} -->`,
@@ -4660,6 +4594,333 @@ function deriveTurnSignal(callerSignal, timeoutMs) {
4660
4594
  };
4661
4595
  }
4662
4596
  //#endregion
4597
+ //#region src/runtime/supervise/chat-transport-executor.ts
4598
+ /**
4599
+ * The chat-transport leaf executor: a worker whose runtime is a plain OpenAI-compatible
4600
+ * `/v1/chat/completions` transport — the worker IS a model conversation, not a sandboxed process
4601
+ * (#721). Tool calls are optional (none, or a caller-provided tool table executed on this host).
4602
+ * A chat worker gets everything real workers get through the open `Executor` port: node pinning,
4603
+ * conserved spend, settle/verdict, journal + edge ledger.
4604
+ *
4605
+ * Module home: a standalone leaf-executor module beside `worktree-cli-executor.ts` — a direct
4606
+ * `(options) → Executor` constructor, NOT a `createExecutor` backend variant. The reason is
4607
+ * continuity: `workerFromBackend` (the backend-as-data path every `ExecutorConfig` rides) creates
4608
+ * a fresh executor per spawn with no session re-attachment and deliberately FAILS LOUD on a
4609
+ * `continuity: 'resume'` spawn; the documented resume consumer is a session-owning
4610
+ * `makeWorkerAgent` seam. {@link chatWorkerSeam} is that seam, and this module ships both halves
4611
+ * together so no caller re-derives the resume wiring.
4612
+ *
4613
+ * Transport shape: NON-streaming, one buffered POST per turn — the simplest honest choice.
4614
+ * A streaming executor cannot mark an unmetered turn today (`UsageEvent`'s `tokens` variant has
4615
+ * no `tokensKnown: false` twin — see the documented limitation in `./types`), while the one-shot
4616
+ * path returns a whole `Spend` that carries both markers. Honesty wins over liveness here.
4617
+ *
4618
+ * Metering: tokens come from the transport's `usage` fields; a turn without usage marks
4619
+ * `tokensKnown: false`. Dollars come ONLY from the response's own cost fields (`usage.cost` /
4620
+ * `usage.cost_usd`, the cli-bridge and OpenRouter conventions); a turn without one marks
4621
+ * `usdKnown: false`. NEVER estimated from a local price table — this executor speaks to arbitrary
4622
+ * OpenAI-compatible endpoints whose models a local table cannot price, and a silent estimate is a
4623
+ * fabricated measurement.
4624
+ *
4625
+ * @experimental
4626
+ */
4627
+ /** The default transport: POST `${url}/chat/completions` with an optional bearer. Fail-loud on
4628
+ * any non-2xx — the status and body head become the settle reason. */
4629
+ function chatCompletionsTransport(opts) {
4630
+ if (typeof opts.url !== "string" || opts.url.length === 0) throw new ValidationError("chatCompletionsTransport: url required");
4631
+ const endpoint = `${opts.url.replace(/\/$/, "")}/chat/completions`;
4632
+ return async (body, signal) => {
4633
+ const res = await fetch(endpoint, {
4634
+ method: "POST",
4635
+ headers: {
4636
+ "content-type": "application/json",
4637
+ ...opts.bearer ? { authorization: `Bearer ${opts.bearer}` } : {}
4638
+ },
4639
+ body: JSON.stringify(body),
4640
+ ...signal ? { signal } : {}
4641
+ });
4642
+ if (!res.ok) throw new ValidationError(`chat transport ${res.status}: ${(await res.text()).slice(0, 200)}`);
4643
+ return res.json();
4644
+ };
4645
+ }
4646
+ /** In-memory `ChatSessionStore`. Entries are detached copies — a caller mutating a saved array
4647
+ * cannot corrupt a recorded session. */
4648
+ function createChatSessionStore() {
4649
+ const sessions = /* @__PURE__ */ new Map();
4650
+ return {
4651
+ load: (workerId) => sessions.get(workerId),
4652
+ save: (workerId, messages) => {
4653
+ sessions.set(workerId, structuredClone(messages));
4654
+ }
4655
+ };
4656
+ }
4657
+ const CHAT_TRANSPORT_RUNTIME = "chat-transport";
4658
+ /**
4659
+ * Build the chat-transport `Executor`: one `execute` = one conversation SHOT — seed (fresh system
4660
+ * prompt, or the resumed session's recorded history) + the task as the next user message, then
4661
+ * loop completion → host tool calls → tool messages until the model answers without a tool call
4662
+ * (or the turn cap). Settles with the final assistant text as `out`.
4663
+ *
4664
+ * Fail-loud contract: transport failures (non-2xx, network faults, malformed completions) throw
4665
+ * `ValidationError`, which the scope settles as an INFRA failure (`Settled.down.infra`) — never a
4666
+ * fake success. The accumulated conversation is still recorded before the throw when a store is
4667
+ * configured, because the inference HAPPENED and a resume may continue a failed session (the
4668
+ * kernel deliberately allows resume-after-failure; the seam decides).
4669
+ */
4670
+ function chatTransportExecutor(opts) {
4671
+ const model = concreteModelId(opts.model);
4672
+ if (!model) throw new ValidationError("chatTransportExecutor: model required");
4673
+ if (!opts.complete && (typeof opts.url !== "string" || opts.url.length === 0)) throw new ValidationError("chatTransportExecutor: url required (or inject `complete`)");
4674
+ for (const tool of opts.tools ?? []) if (typeof tool.spec?.function?.name !== "string" || typeof tool.execute !== "function") throw new ValidationError("chatTransportExecutor: every tools entry needs spec.function.name + execute");
4675
+ const maxTurns = opts.maxTurnsPerShot ?? 200;
4676
+ if (!Number.isInteger(maxTurns) || maxTurns < 1) throw new ValidationError("chatTransportExecutor: maxTurnsPerShot must be a positive integer");
4677
+ if (opts.maxTokens !== void 0 && (!Number.isInteger(opts.maxTokens) || opts.maxTokens < 1)) throw new ValidationError("chatTransportExecutor: maxTokens must be a positive integer");
4678
+ let seed;
4679
+ if (opts.resume) {
4680
+ if (!opts.sessions) throw new ValidationError("chatTransportExecutor: a 'resume' spawn needs `sessions` — the store holding the conversation this shot continues");
4681
+ const prior = opts.sessions.load(opts.resume.ofWorker);
4682
+ if (prior === void 0) throw new ValidationError(`chatTransportExecutor: no recorded conversation for worker '${opts.resume.ofWorker}' — the session store holds only conversations recorded by this process (the kernel’s process-local resume boundary)`);
4683
+ seed = structuredClone(prior);
4684
+ } else seed = opts.system !== void 0 && opts.system.length > 0 ? [{
4685
+ role: "system",
4686
+ content: opts.system
4687
+ }] : [];
4688
+ const transport = opts.complete ?? chatCompletionsTransport({
4689
+ url: opts.url,
4690
+ ...opts.bearer ? { bearer: opts.bearer } : {}
4691
+ });
4692
+ const toolSpecs = (opts.tools ?? []).map((tool) => tool.spec);
4693
+ const toolByName = new Map((opts.tools ?? []).map((tool) => [tool.spec.function.name, tool]));
4694
+ const controller = new AbortController();
4695
+ let artifact;
4696
+ let executed = false;
4697
+ const executionId = opts.sessionKey ?? `chat-session-${randomUUID()}`;
4698
+ const attemptId = opts.attemptId ?? newExecutionAttemptId(executionId);
4699
+ const executor = {
4700
+ runtime: CHAT_TRANSPORT_RUNTIME,
4701
+ async execute(task, signal) {
4702
+ if (executed) throw new ValidationError("chatTransportExecutor: execute() called twice on one instance");
4703
+ executed = true;
4704
+ const started = Date.now();
4705
+ const messages = seed;
4706
+ messages.push({
4707
+ role: "user",
4708
+ content: taskToPrompt(task)
4709
+ });
4710
+ const linked = mergeAbortSignals(signal, controller.signal);
4711
+ const tokens = zeroTokenUsage();
4712
+ let tokensKnown = true;
4713
+ let usd = 0;
4714
+ let usdKnown = true;
4715
+ let turns = 0;
4716
+ let lastText = "";
4717
+ try {
4718
+ for (let t = 0; t < maxTurns; t += 1) {
4719
+ const body = {
4720
+ model,
4721
+ messages,
4722
+ ...toolSpecs.length > 0 ? {
4723
+ tools: toolSpecs,
4724
+ tool_choice: "auto"
4725
+ } : {},
4726
+ ...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
4727
+ ...opts.maxTokens !== void 0 ? { max_tokens: opts.maxTokens } : {}
4728
+ };
4729
+ let raw;
4730
+ try {
4731
+ raw = await transport(body, linked);
4732
+ } catch (cause) {
4733
+ if (cause instanceof Error && cause.name === "AbortError") throw cause;
4734
+ if (cause instanceof ValidationError) throw cause;
4735
+ throw new ValidationError(`chatTransportExecutor: transport failed: ${cause instanceof Error ? cause.message : String(cause)}`);
4736
+ }
4737
+ turns += 1;
4738
+ const data = raw;
4739
+ const usage = data?.usage;
4740
+ if (usage && typeof usage.prompt_tokens === "number" && typeof usage.completion_tokens === "number") {
4741
+ tokens.input += usage.prompt_tokens;
4742
+ tokens.output += usage.completion_tokens;
4743
+ } else tokensKnown = false;
4744
+ const turnCost = typeof usage?.cost === "number" ? usage.cost : typeof usage?.cost_usd === "number" ? usage.cost_usd : void 0;
4745
+ if (turnCost !== void 0) usd += turnCost;
4746
+ else usdKnown = false;
4747
+ const msg = data?.choices?.[0]?.message;
4748
+ if (msg === void 0) throw new ValidationError("chatTransportExecutor: transport returned no choices[0].message");
4749
+ if (typeof msg.content === "string" && msg.content.length > 0) lastText = msg.content;
4750
+ const toolCalls = msg.tool_calls ?? [];
4751
+ if (toolCalls.length === 0 || toolSpecs.length === 0) {
4752
+ messages.push({
4753
+ role: "assistant",
4754
+ content: msg.content ?? ""
4755
+ });
4756
+ break;
4757
+ }
4758
+ messages.push({
4759
+ role: "assistant",
4760
+ content: msg.content ?? "",
4761
+ tool_calls: toolCalls.map((tc, i) => ({
4762
+ id: tc.id ?? `call_${i}`,
4763
+ type: "function",
4764
+ function: {
4765
+ name: tc.function?.name ?? "",
4766
+ arguments: tc.function?.arguments ?? "{}"
4767
+ }
4768
+ }))
4769
+ });
4770
+ for (let i = 0; i < toolCalls.length; i += 1) {
4771
+ const tc = toolCalls[i];
4772
+ const id = tc?.id ?? `call_${i}`;
4773
+ const name = tc?.function?.name ?? "";
4774
+ const tool = toolByName.get(name);
4775
+ if (!tool) {
4776
+ messages.push({
4777
+ role: "tool",
4778
+ tool_call_id: id,
4779
+ content: `error: unknown tool '${name}'`
4780
+ });
4781
+ continue;
4782
+ }
4783
+ let args;
4784
+ try {
4785
+ args = JSON.parse(tc?.function?.arguments ?? "{}");
4786
+ } catch {
4787
+ messages.push({
4788
+ role: "tool",
4789
+ tool_call_id: id,
4790
+ content: "error: tool arguments were not valid JSON"
4791
+ });
4792
+ continue;
4793
+ }
4794
+ let result;
4795
+ try {
4796
+ result = await tool.execute(args, task);
4797
+ } catch (cause) {
4798
+ result = `error: ${cause instanceof Error ? cause.message : String(cause)}`;
4799
+ }
4800
+ messages.push({
4801
+ role: "tool",
4802
+ tool_call_id: id,
4803
+ content: result
4804
+ });
4805
+ }
4806
+ }
4807
+ } finally {
4808
+ if (opts.sessions && opts.sessionKey !== void 0) opts.sessions.save(opts.sessionKey, messages);
4809
+ }
4810
+ const spent = {
4811
+ iterations: turns,
4812
+ tokens,
4813
+ ...tokensKnown ? {} : { tokensKnown: false },
4814
+ usd,
4815
+ ...usdKnown ? {} : { usdKnown: false },
4816
+ ms: Date.now() - started
4817
+ };
4818
+ artifact = {
4819
+ outRef: contentAddress({
4820
+ kind: "chat-transport",
4821
+ model,
4822
+ content: lastText,
4823
+ turns
4824
+ }),
4825
+ out: lastText,
4826
+ spent
4827
+ };
4828
+ return artifact;
4829
+ },
4830
+ teardown(_grace) {
4831
+ controller.abort();
4832
+ return Promise.resolve({ destroyed: true });
4833
+ },
4834
+ resultArtifact() {
4835
+ if (!artifact) throw new ValidationError("chatTransportExecutor: resultArtifact() read before execute()");
4836
+ return {
4837
+ ...artifact,
4838
+ spent: artifact.spent
4839
+ };
4840
+ }
4841
+ };
4842
+ if (opts.profile === void 0) return executor;
4843
+ return attestRuntimeOwnedExecutor(executor, {
4844
+ effectiveProfile: opts.profile,
4845
+ backend: "chat-transport",
4846
+ model: {
4847
+ status: "known",
4848
+ id: model
4849
+ },
4850
+ execution: {
4851
+ kind: "session",
4852
+ id: executionId
4853
+ },
4854
+ materializer: "chat-transport-conversation",
4855
+ plan: {
4856
+ kind: "openai-chat-conversation",
4857
+ model,
4858
+ maxTurnsPerShot: maxTurns,
4859
+ tools: toolSpecs,
4860
+ resumeOf: opts.resume?.ofWorker ?? null
4861
+ }
4862
+ }, {
4863
+ attemptId,
4864
+ binding: {
4865
+ endpoint: opts.complete ? "injected-transport" : opts.url,
4866
+ model,
4867
+ sessionKey: opts.sessionKey ?? null
4868
+ },
4869
+ descriptor: {
4870
+ kind: "chat-transport-session",
4871
+ transport: opts.complete ? "injected" : "http",
4872
+ backend: "chat-transport"
4873
+ }
4874
+ });
4875
+ }
4876
+ /**
4877
+ * The `makeWorkerAgent` seam over {@link chatTransportExecutor} — the continuity consumer
4878
+ * `workerFromBackend` refuses to be. Every spawn becomes one conversation shot: the spawned
4879
+ * profile's system prompt + instructions (which is where a graph's delegates directive lands)
4880
+ * seed a fresh session, and a `'resume'` spawn re-attaches by loading `resume.ofWorker`'s
4881
+ * recorded message list from the seam's session store. Conversations are recorded under the
4882
+ * kernel node id, which is exactly what a later `resume.ofWorker` names.
4883
+ */
4884
+ function chatWorkerSeam(opts) {
4885
+ if (!opts.complete && (typeof opts.url !== "string" || opts.url.length === 0)) throw new ValidationError("chatWorkerSeam: url required (or inject `complete`)");
4886
+ const sessions = opts.sessions ?? createChatSessionStore();
4887
+ return (rawProfile, spawnContext) => {
4888
+ const parsed = agentProfileSchema.safeParse(canonicalizeAuthoredProfile(rawProfile));
4889
+ if (!parsed.success) throw new ValidationError(`chatWorkerSeam: invalid AgentProfile: ${parsed.error.message}`);
4890
+ const profile = parsed.data;
4891
+ const model = concreteProfileModel(profile) ?? concreteModelId(opts.model);
4892
+ if (!model) throw new ValidationError("chatWorkerSeam: no model — set ChatWorkerSeamOptions.model or AgentProfile.model.default");
4893
+ const system = [profile.prompt?.systemPrompt, ...profile.prompt?.instructions ?? []].filter((line) => typeof line === "string" && line.trim().length > 0).join("\n");
4894
+ return {
4895
+ name: profile.name ?? "chat-worker",
4896
+ act: async () => void 0,
4897
+ executorSpec: {
4898
+ profile,
4899
+ harness: null,
4900
+ executorFactory: (executorSpec, ctx) => {
4901
+ const executor = chatTransportExecutor({
4902
+ url: opts.url,
4903
+ ...opts.bearer !== void 0 ? { bearer: opts.bearer } : {},
4904
+ model,
4905
+ ...system.length > 0 ? { system } : {},
4906
+ ...opts.tools !== void 0 ? { tools: opts.tools } : {},
4907
+ ...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
4908
+ ...opts.maxTokens !== void 0 ? { maxTokens: opts.maxTokens } : {},
4909
+ ...opts.maxTurnsPerShot !== void 0 ? { maxTurnsPerShot: opts.maxTurnsPerShot } : {},
4910
+ ...opts.complete !== void 0 ? { complete: opts.complete } : {},
4911
+ sessions,
4912
+ ...ctx.node?.nodeId !== void 0 ? { sessionKey: ctx.node.nodeId } : {},
4913
+ ...spawnContext?.resume !== void 0 ? { resume: spawnContext.resume } : {},
4914
+ profile: executorSpec.profile,
4915
+ ...ctx.node?.attemptId !== void 0 ? { attemptId: ctx.node.attemptId } : {}
4916
+ });
4917
+ return opts.deliverable ? gateOnDeliverable(executor, opts.deliverable) : executor;
4918
+ }
4919
+ }
4920
+ };
4921
+ };
4922
+ }
4923
+ //#endregion
4663
4924
  //#region src/runtime/supervise/graph.ts
4664
4925
  /**
4665
4926
  *
@@ -4674,8 +4935,8 @@ function deriveTurnSignal(callerSignal, timeoutMs) {
4674
4935
  * NOT A SECOND SCHEDULER. `runGraph` is an interpretation layer over what already runs:
4675
4936
  * `supervise()` is the execution core — the same `supervisorAgent`/`driverAgent` machinery,
4676
4937
  * `makeWorkerAgent` seam, conserved-pool budget, and deliverable-gated settlement every
4677
- * supervised run uses. (`runLoop` is a deprecated alias of `runAgentRounds` and is deliberately
4678
- * NOT the substrate here.) What the graph layer ADDS is exactly what a bespoke driver loop never
4938
+ * supervised run uses. (`runAgentRounds` is deliberately NOT the substrate here.) What the graph
4939
+ * layer ADDS is exactly what a bespoke driver loop never
4679
4940
  * had:
4680
4941
  *
4681
4942
  * 1. **Node pinning** — a spawn names a node (`profile.name` = node id) and the node's canonical
@@ -4690,11 +4951,20 @@ function deriveTurnSignal(callerSignal, timeoutMs) {
4690
4951
  * edge is a versioned optimization target, never prose hardcoded in a builder function.
4691
4952
  * 4. **Per-edge traversal caps** — the cyclic-graph backstop. A delegates edge whose cap is
4692
4953
  * exhausted REFUSES further traversals (fail loud), so a cycle cannot spin the pool dry.
4954
+ * 5. **Continuity as data** — a delegates edge may declare `continuity: 'resume'`, so each spawn
4955
+ * after the node's first re-attaches to its latest SETTLED session (the spawn context hands
4956
+ * the executor seam `resume: { ofWorker, sequence }`; the kernel keeps identity, ordering,
4957
+ * ledger truth, and the one conserved pool). Every ledger row states how its hop continued:
4958
+ * `'fresh' | 'resume'` for spawns, `'steer'` for mid-run deliveries — fresh respawns, session
4959
+ * resumes, and live steers are all plain data, each a ledgered fact.
4693
4960
  *
4694
- * ORACLES ARE ENVIRONMENT, NEVER NODES. Graders/verifiers must not be addressable in the graph —
4695
- * an edge to them leaks the rubric. Analysts (`analyzes` edges) are LENSES from the environment's
4696
- * registry reading trace evidence; they are not nodes either, and a graph that names a node id as
4697
- * its analyst is refused.
4961
+ * ORACLES ARE ENVIRONMENT, NEVER WORKERS. Graders/verifiers must not be spawnable in the graph —
4962
+ * a delegates edge to them leaks the rubric. An `analyzes` edge names its analyst in one of two
4963
+ * forms: a LENS id from the environment's registry (a pure function over trace evidence), or the
4964
+ * id of a graph NODE — a tool-equipped analyst AGENT spawned on each matching settle with the
4965
+ * node's pinned profile, whose settle output IS the findings. Either way the oracle doctrine
4966
+ * holds: an analyst node can never be a delegates target (refused loudly), so no driver can hand
4967
+ * it work, and an id living in both the registry and the nodes is refused as ambiguous.
4698
4968
  *
4699
4969
  * @experimental
4700
4970
  */
@@ -4746,6 +5016,7 @@ function validateGraph(graph, registry, analysts) {
4746
5016
  requireNode(edge.from, edgeId(edge));
4747
5017
  requireNode(edge.to, edgeId(edge));
4748
5018
  if (edge.from === edge.to) throw new ValidationError(`runGraph: ${edgeId(edge)} delegates to itself — the driver↔worker cycle is the settle-return loop, not a self-edge`);
5019
+ if (edge.continuity !== void 0 && edge.continuity !== "fresh" && edge.continuity !== "resume") throw new ValidationError(`runGraph: ${edgeId(edge)} has invalid continuity ${JSON.stringify(edge.continuity)} — a delegates edge's continuity is 'fresh' or 'resume'`);
4749
5020
  }
4750
5021
  const delegatedTo = new Set(delegates.map((edge) => edge.to));
4751
5022
  const roots = [...new Set(delegates.map((edge) => edge.from))].filter((id) => !delegatedTo.has(id));
@@ -4753,16 +5024,28 @@ function validateGraph(graph, registry, analysts) {
4753
5024
  const root = requireNode(roots[0], "root resolution");
4754
5025
  for (const edge of delegates) if (edge.from !== root.id) throw new ValidationError(`runGraph: ${edgeId(edge)} delegates from a non-root node — P0 executes one driver over its workers (the 2-node cyclic case, star-generalized); deeper delegation is P3`);
4755
5026
  const analystIds = /* @__PURE__ */ new Set();
5027
+ const analystNodes = /* @__PURE__ */ new Map();
4756
5028
  for (const edge of analyzes) {
5029
+ if (edge.continuity !== void 0) throw new ValidationError(`runGraph: ${edgeId(edge)} carries continuity — analysts are spawned by the analyst machinery (every analyst run is a fresh session over settled evidence), so continuity is a delegates-edge axis only`);
4757
5030
  if (analystIds.has(edge.analyst)) throw new ValidationError(`runGraph: two analyzes edges share analyst '${edge.analyst}' — one analyzes edge per analyst lens (traversals are ledgered by analyst id; a second edge would silently absorb the first's). Register the lens under a second id for a second edge.`);
4758
5031
  analystIds.add(edge.analyst);
4759
- if (byId.has(edge.analyst)) throw new ValidationError(`runGraph: ${edgeId(edge)} names node '${edge.analyst}' as its analyst — oracles and analysts are ENVIRONMENT, never nodes; pass a lens id from RunGraphOptions.analysts`);
4760
- if (!analysts) throw new ValidationError(`runGraph: ${edgeId(edge)} needs RunGraphOptions.analysts (the lens registry its analyst resolves against)`);
4761
- if (!analysts.kinds.some((kind) => kind.id === edge.analyst)) throw new ValidationError(`runGraph: ${edgeId(edge)} analyst '${edge.analyst}' is not in the analysts registry (known: ${analysts.kinds.map((kind) => kind.id).join(", ") || "none"})`);
5032
+ const analystNode = byId.get(edge.analyst);
5033
+ const inRegistry = analysts?.kinds.some((kind) => kind.id === edge.analyst) === true;
5034
+ if (analystNode !== void 0 && inRegistry) throw new ValidationError(`runGraph: ${edgeId(edge)} analyst '${edge.analyst}' is BOTH a graph node and a lens in the analysts registry the id alone distinguishes the two analyst forms, so this is ambiguous; rename the node or register the lens under another id`);
5035
+ if (analystNode !== void 0) {
5036
+ if (analystNode.id === root.id) throw new ValidationError(`runGraph: ${edgeId(edge)} names the ROOT as its analyst — the root is the driver; give the analyst its own node with no delegates edge pointing at it`);
5037
+ if (delegatedTo.has(analystNode.id)) throw new ValidationError(`runGraph: ${edgeId(edge)} names node '${edge.analyst}' as its analyst, but that node is a delegates target — oracle doctrine: an analyst is never delegated to. An analyst NODE is legal only with NO delegates edge pointing at it; give the analyst its own delegates-free node or pass a lens id from RunGraphOptions.analysts.`);
5038
+ analystNodes.set(analystNode.id, analystNode);
5039
+ } else if (!analysts) throw new ValidationError(`runGraph: ${edgeId(edge)} analyst '${edge.analyst}' is not a graph node, and no RunGraphOptions.analysts registry was provided to resolve it as a lens`);
5040
+ else if (!inRegistry) throw new ValidationError(`runGraph: ${edgeId(edge)} analyst '${edge.analyst}' is neither a graph node nor in the analysts registry (known lenses: ${analysts.kinds.map((kind) => kind.id).join(", ") || "none"})`);
4762
5041
  if (edge.over.length === 0) throw new ValidationError(`runGraph: ${edgeId(edge)} must analyze at least one node`);
4763
- for (const over of edge.over) requireNode(over, edgeId(edge));
5042
+ for (const over of edge.over) {
5043
+ requireNode(over, edgeId(edge));
5044
+ if (over === root.id) throw new ValidationError(`runGraph: ${edgeId(edge)} analyzes the ROOT — analysts observe settled workers, and the root never settles as one, so this edge would silently never fire; list delegates-target nodes only`);
5045
+ }
4764
5046
  requireNode(edge.to, edgeId(edge));
4765
5047
  }
5048
+ for (const edge of analyzes) for (const over of edge.over) if (analystNodes.has(over)) throw new ValidationError(`runGraph: ${edgeId(edge)} analyzes '${over}', which is an analyst node — an analyst run settles as a finding, never as a worker, so this edge would silently never fire; analyst nodes are not analyzable`);
4766
5049
  const workers = /* @__PURE__ */ new Map();
4767
5050
  const delegatesByWorker = /* @__PURE__ */ new Map();
4768
5051
  for (const edge of delegates) {
@@ -4770,12 +5053,13 @@ function validateGraph(graph, registry, analysts) {
4770
5053
  delegatesByWorker.set(edge.to, edge);
4771
5054
  workers.set(edge.to, requireNode(edge.to, edgeId(edge)));
4772
5055
  }
4773
- for (const node of graph.nodes) if (node.id !== root.id && !workers.has(node.id)) throw new ValidationError(`runGraph: node '${node.id}' has no delegates edge to it — an unreachable node never runs`);
5056
+ for (const node of graph.nodes) if (node.id !== root.id && !workers.has(node.id) && !analystNodes.has(node.id)) throw new ValidationError(`runGraph: node '${node.id}' has no delegates edge to it — an unreachable node never runs`);
4774
5057
  return {
4775
5058
  root,
4776
5059
  workers,
4777
5060
  delegatesByWorker,
4778
- analyzes
5061
+ analyzes,
5062
+ analystNodes
4779
5063
  };
4780
5064
  }
4781
5065
  const byteLength = (text) => Buffer.byteLength(text, "utf8");
@@ -4797,7 +5081,7 @@ function stringifyPayload(payload) {
4797
5081
  */
4798
5082
  function runGraph(graph, opts) {
4799
5083
  const registry = opts.registry ?? kernelPromptRegistry();
4800
- const { root, workers, delegatesByWorker, analyzes } = validateGraph(graph, registry, opts.analysts);
5084
+ const { root, workers, delegatesByWorker, analyzes, analystNodes } = validateGraph(graph, registry, opts.analysts);
4801
5085
  if (!opts.backend && !opts.makeWorkerAgent) throw new ValidationError("runGraph: provide opts.backend (where nodes run) or opts.makeWorkerAgent");
4802
5086
  const journal = opts.journal ?? new InMemorySpawnJournal();
4803
5087
  const blobs = opts.blobs ?? new InMemoryResultBlobStore();
@@ -4824,6 +5108,7 @@ function runGraph(graph, opts) {
4824
5108
  },
4825
5109
  traversal: entry.traversal,
4826
5110
  outcome: entry.outcome,
5111
+ continuity: entry.continuity,
4827
5112
  bytes: entry.bytes,
4828
5113
  ...entry.reason !== void 0 ? { reason: entry.reason } : {},
4829
5114
  seq: ledgerSeq++,
@@ -4848,12 +5133,19 @@ function runGraph(graph, opts) {
4848
5133
  const pendingByAssignment = /* @__PURE__ */ new Map();
4849
5134
  const graphWorker = (authoredProfile, spawnContext) => {
4850
5135
  const requested = typeof authoredProfile?.name === "string" ? authoredProfile.name : void 0;
5136
+ if (spawnContext?.analyst !== void 0) {
5137
+ const analystNode = analystNodes.get(spawnContext.analyst);
5138
+ if (!analystNode || requested !== analystNode.id) throw new ValidationError(`runGraph: analyst run for ${JSON.stringify(spawnContext.analyst)} does not name an analyst node of this graph (analyst nodes: ${[...analystNodes.keys()].join(", ") || "none"})`);
5139
+ return makeLeaf(analystNode.profile, spawnContext);
5140
+ }
4851
5141
  const node = requested !== void 0 ? workers.get(requested) : void 0;
4852
5142
  if (!node) throw new ValidationError(`runGraph: spawn_agent named profile ${JSON.stringify(requested)} which is not a worker node of this graph (nodes: ${[...workers.keys()].join(", ")}). Spawn by node id: profile.name selects the node; the node profile itself is pinned by the graph.`);
4853
5143
  const edge = delegatesByWorker.get(node.id);
4854
5144
  const id = edgeId(edge);
4855
5145
  const cap = edge.maxTraversals ?? 32;
4856
- if ((traversalCounts.get(id) ?? 0) >= cap) {
5146
+ const used = traversalCounts.get(id) ?? 0;
5147
+ const spawnContinuity = spawnContext?.continuity ?? "fresh";
5148
+ if (used >= cap) {
4857
5149
  exhausted.add(id);
4858
5150
  exhaustedDelegates.add(id);
4859
5151
  record({
@@ -4863,6 +5155,7 @@ function runGraph(graph, opts) {
4863
5155
  to: edge.to,
4864
5156
  directive: formatPromptHandle(edge.directive),
4865
5157
  outcome: "unpropagated",
5158
+ continuity: spawnContinuity,
4866
5159
  bytes: 0,
4867
5160
  reason: `traversal-cap-exhausted (max ${cap})`
4868
5161
  }, true);
@@ -4878,6 +5171,7 @@ function runGraph(graph, opts) {
4878
5171
  to: edge.to,
4879
5172
  directive: formatPromptHandle(edge.directive),
4880
5173
  outcome: bytes === 0 ? "empty" : "delivered",
5174
+ continuity: spawnContinuity,
4881
5175
  bytes,
4882
5176
  ...bytes === 0 ? { reason: "no directive text and no task payload" } : {}
4883
5177
  }, false);
@@ -4892,24 +5186,38 @@ function runGraph(graph, opts) {
4892
5186
  };
4893
5187
  return makeLeaf(pinned, spawnContext);
4894
5188
  };
4895
- const routes = analyzes.map((edge) => edge.to === root.id ? {
4896
- kind: edge.analyst,
4897
- over: edge.over
4898
- } : {
4899
- kind: edge.analyst,
4900
- over: edge.over,
4901
- to: edge.to,
4902
- directive: registry.resolve(edge.directive).text
5189
+ const routes = analyzes.map((edge) => {
5190
+ const analystNode = analystNodes.get(edge.analyst);
5191
+ if (analystNode) return {
5192
+ kind: edge.analyst,
5193
+ over: edge.over,
5194
+ agent: analystNode.profile,
5195
+ directive: registry.resolve(edge.directive).text,
5196
+ ...edge.to === root.id ? {} : { to: edge.to }
5197
+ };
5198
+ return edge.to === root.id ? {
5199
+ kind: edge.analyst,
5200
+ over: edge.over
5201
+ } : {
5202
+ kind: edge.analyst,
5203
+ over: edge.over,
5204
+ to: edge.to,
5205
+ directive: registry.resolve(edge.directive).text
5206
+ };
4903
5207
  });
4904
- const driverAnalyzesBriefs = analyzes.filter((edge) => edge.to === root.id).map((edge) => `Findings from analyst '${edge.analyst}' (over: ${edge.over.join(", ")}) will arrive as finding events.\n${registry.resolve(edge.directive).text}`);
5208
+ const driverAnalyzesBriefs = analyzes.filter((edge) => edge.to === root.id).map((edge) => analystNodes.has(edge.analyst) ? `Findings from analyst '${edge.analyst}' (a tool-equipped analyst agent node, over: ${edge.over.join(", ")}) will arrive as finding events.` : `Findings from analyst '${edge.analyst}' (over: ${edge.over.join(", ")}) will arrive as finding events.\n${registry.resolve(edge.directive).text}`);
5209
+ const continuityByProfile = {};
5210
+ for (const [nodeId, edge] of delegatesByWorker) if (edge.continuity !== void 0) continuityByProfile[nodeId] = edge.continuity;
4905
5211
  const graphBrief = [
4906
5212
  "AGENT GRAPH: you are the driver node of a fixed topology. You may spawn ONLY these worker",
4907
5213
  "nodes, by EXACT name (spawn_agent with profile: { name: '<node id>' }; the node's full",
4908
5214
  "profile is pinned by the graph — any other profile fields you author are ignored):",
4909
5215
  ...[...workers.values()].map((node) => {
4910
- const cap = delegatesByWorker.get(node.id).maxTraversals ?? 32;
5216
+ const edge = delegatesByWorker.get(node.id);
5217
+ const cap = edge.maxTraversals ?? 32;
4911
5218
  const description = typeof node.profile.description === "string" && node.profile.description.length > 0 ? ` — ${node.profile.description}` : "";
4912
- return `- '${node.id}'${description} (delegation cap: ${cap} traversals)`;
5219
+ const continuityNote = edge.continuity === "resume" ? "; continuity: resume — each spawn after the first re-attaches to this node's latest settled session (spawn again to continue it; steer while it is live)" : "";
5220
+ return `- '${node.id}'${description} (delegation cap: ${cap} traversals${continuityNote})`;
4913
5221
  }),
4914
5222
  ...driverAnalyzesBriefs.length > 0 ? ["", ...driverAnalyzesBriefs] : []
4915
5223
  ].join("\n");
@@ -4944,6 +5252,7 @@ function runGraph(graph, opts) {
4944
5252
  to: edge.to,
4945
5253
  directive: formatPromptHandle(edge.directive),
4946
5254
  outcome: capped ? "unpropagated" : outcome,
5255
+ continuity: "steer",
4947
5256
  bytes,
4948
5257
  ...capped ? { reason: `traversal-cap-exhausted (max ${edge.maxTraversals ?? 32})` } : reason !== void 0 ? { reason } : {},
4949
5258
  ...workerId !== void 0 ? { workerId } : {}
@@ -4982,6 +5291,7 @@ function runGraph(graph, opts) {
4982
5291
  to: edge.to,
4983
5292
  directive: formatPromptHandle(edge.directive),
4984
5293
  outcome: !down.delivered ? "unpropagated" : stripped ? "stripped" : "delivered",
5294
+ continuity: "steer",
4985
5295
  bytes: byteLength(down.instruction),
4986
5296
  ...!down.delivered ? { reason: down.outcome } : stripped ? { reason: `authorization narrowed ${stripped.composedBytes} composed bytes` } : {},
4987
5297
  workerId: down.toWorker
@@ -5013,10 +5323,12 @@ function runGraph(graph, opts) {
5013
5323
  runId,
5014
5324
  hooks,
5015
5325
  onCoordinationEvent,
5016
- ...routes.length > 0 && opts.analysts ? {
5017
- analysts: opts.analysts,
5018
- analyzeOnSettle: routes
5326
+ ...routes.length > 0 ? {
5327
+ analyzeOnSettle: routes,
5328
+ ...opts.analysts ? { analysts: opts.analysts } : {}
5019
5329
  } : {},
5330
+ ...Object.keys(continuityByProfile).length > 0 ? { continuityByProfile } : {},
5331
+ ...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
5020
5332
  ...opts.router ? { router: opts.router } : {},
5021
5333
  ...opts.brain ? { brain: opts.brain } : {},
5022
5334
  ...authorizeMessage ? { authorizeMessage } : {},
@@ -6189,6 +6501,6 @@ function tail(s) {
6189
6501
  return s.slice(-400);
6190
6502
  }
6191
6503
  //#endregion
6192
- export { panel as $, runStrategyEvolution as A, defaultAuditorInstruction as At, equalKOnCost as B, secretEnvOfMcpServer as Bt, GraphEdgeCapError as C, leaderboard as Ct, streamAgentTurn as D, renderLeaderboardSvg as Dt, collectAgentTurn as E, renderLeaderboardMarkdown as Et, SandboxRunAbortError as F, sanitizeMcpToolSchema as Ft, createShapeRegistry as G, definePersona as H, openSandboxRun as I, envKeyProvider as It, InMemoryCorpus as J, registerShape as K, printBenchmarkReport as L, mcpSecretEnvMetadataKey as Lt, assertStrategyContract as M, connectStdioMcp as Mt, authorStrategy as N, materializeLocalMcp as Nt, discriminatingMeans as O, renderPairwiseMarkdown as Ot, strategyAuthorContract as P, createMcpEnvironment as Pt, loopUntil as Q, runBenchmark as R, resolveMcpServerLaunch as Rt, runCoderChecks as S, stopSentinel as St, runGraph as T, renderLeaderboardHtml as Tt, runPersonified as U, trajectoryReport as V, builtinShapes as W, fanout as X, renderCorpusToInstructions as Y, flatWidenGate as Z, settledWorkerOut as _, loopCampaignDispatch as _t, localShell as a, buildSteerContext as at, analyzeTrace as b, deterministicCompletion as bt, createVerifierEnvironment as c, inProcessSandboxClient as ct, worktreeFanout as d, dumbDriver as dt, pipeline as et, EVIDENCE_MAX_CHARS as f, naiveDriver as ft, composeWorkerEvidence as g, inlineSandboxClient as gt, closingWorkerNote as h, localSandboxClient as ht, jjWorkspace as i, assertTraceDerivedFindings as it, selectChampion as j, McpSpawnFault as jt, pickChampion as k, auditIntent as kt, failuresAnalyst as l, harvestCorpus as lt, VERIFY_TAIL_CHARS as m, resolveSandboxClient as mt, makeFinding$1 as n, verify as nt, runInWorkspace as o, createScopeAnalyst as ot, NOTE_MAX_CHARS as p, steeringDriver as pt, FileCorpus as q, gitWorkspace as r, widen as rt, createWaterfallCollector as s, registryScopeAnalyst as st, computeFindingId$1 as t, selectValidWinner as tt, superviseSurface as u, defineLeaderboard as ut, copyUntrackedIntoClone as v, loopDispatch as vt, defaultEdgeTraversalCap as w, pairwiseSignificance as wt, patchDelivered as x, sentinelCompletion as xt, withUntrackedArtifacts as y, completionAuthorizes as yt, promotionGate as z, resolveSecretEnv as zt };
6504
+ export { renderCorpusToInstructions as $, collectAgentTurn as A, auditIntent as At, openSandboxRun as B, resolveSecretEnv as Bt, GraphEdgeCapError as C, stopSentinel as Ct, chatTransportExecutor as D, renderLeaderboardMarkdown as Dt, chatCompletionsTransport as E, renderLeaderboardHtml as Et, selectChampion as F, createMcpEnvironment as Ft, trajectoryReport as G, runBenchmark as H, assertStrategyContract as I, sanitizeMcpToolSchema as It, builtinShapes as J, definePersona as K, authorStrategy as L, envKeyProvider as Lt, discriminatingMeans as M, McpSpawnFault as Mt, pickChampion as N, connectStdioMcp as Nt, chatWorkerSeam as O, renderLeaderboardSvg as Ot, runStrategyEvolution as P, materializeLocalMcp as Pt, InMemoryCorpus as Q, strategyAuthorContract as R, mcpSecretEnvMetadataKey as Rt, runCoderChecks as S, sentinelCompletion as St, runGraph as T, pairwiseSignificance as Tt, promotionGate as U, printBenchmarkReport as V, secretEnvOfMcpServer as Vt, equalKOnCost as W, registerShape as X, createShapeRegistry as Y, FileCorpus as Z, settledWorkerOut as _, inlineSandboxClient as _t, localShell as a, selectValidWinner as at, analyzeTrace as b, completionAuthorizes as bt, createVerifierEnvironment as c, assertTraceDerivedFindings as ct, worktreeFanout as d, registryScopeAnalyst as dt, fanout as et, EVIDENCE_MAX_CHARS as f, inProcessSandboxClient as ft, composeWorkerEvidence as g, localSandboxClient as gt, closingWorkerNote as h, resolveSandboxClient as ht, jjWorkspace as i, pipeline as it, streamAgentTurn as j, defaultAuditorInstruction as jt, createChatSessionStore as k, renderPairwiseMarkdown as kt, failuresAnalyst as l, buildSteerContext as lt, VERIFY_TAIL_CHARS as m, defineLeaderboard as mt, makeFinding$1 as n, loopUntil as nt, runInWorkspace as o, verify as ot, NOTE_MAX_CHARS as p, harvestCorpus as pt, runPersonified as q, gitWorkspace as r, panel as rt, createWaterfallCollector as s, widen as st, computeFindingId$1 as t, flatWidenGate as tt, superviseSurface as u, createScopeAnalyst as ut, copyUntrackedIntoClone as v, loopCampaignDispatch as vt, defaultEdgeTraversalCap as w, leaderboard as wt, patchDelivered as x, deterministicCompletion as xt, withUntrackedArtifacts as y, loopDispatch as yt, SandboxRunAbortError as z, resolveMcpServerLaunch as zt };
6193
6505
 
6194
- //# sourceMappingURL=runtime-DZblIf3R.js.map
6506
+ //# sourceMappingURL=runtime-5uDVVfER.js.map