@noetaris/harness 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -156,6 +156,13 @@ const resumed = run.resume(response, interruptId)
156
156
  const resumed = agent.resume(response, sessionId, interruptId)
157
157
  ```
158
158
 
159
+ `agent.resume()` takes an optional fourth argument with these reserved resources (a subset of
160
+ `run()`'s): `observer`, `onObserverError`, `events: { onStoreError }`, and `parentRunId` (the caller's run id,
161
+ for a sub agent resumed from a step). `parentRunId` applies to that resumed run only: calling
162
+ `resume()` again on the handle it returns keeps the other resources but not `parentRunId`, which
163
+ is stale by then. With a store, both `agent.resume()` and `run.resume()` record the agent's
164
+ `instanceId` in the resumed run's `metadata`.
165
+
159
166
  **Resume replays the interrupted step from the top.** On resume, the step that called
160
167
  `ctx.interrupt()` runs again from its first line; each `ctx.interrupt()` call it reaches
161
168
  returns the stored response instead of pausing. State updates from the paused attempt are
@@ -263,6 +270,7 @@ throws on an adapter event never fails a step. Every hook is optional.
263
270
  | `onEvent(ctx, type, payload)` | on `ctx.emit()` and adapter events such as `llm.response` |
264
271
  | `onStepSettled(ctx, event)` | once per step, when its outcome and destination are final |
265
272
  | `wrap(ctx, scope, fn)` | around a step's `run`, and around calls made with `ctx.within` |
273
+ | `carry(ctx)` | once per top-level run, right after `onRunStart`; returns a string map to keep on the run's record (see [Linking runs](#linking-runs)) |
266
274
 
267
275
  Step hooks receive a `StepContext`: `agentId`, `sessionId`, `runId`, `stepName` and, inside a
268
276
  fork branch, `branchPath`. `runId` is the same value as `RunContext.runId`. Steps inside a branch
@@ -314,6 +322,27 @@ await createAgent('demo', h, {}).run({}, { observer: logger })
314
322
  // decide: ok end (signal done)
315
323
  ```
316
324
 
325
+ ### Linking runs
326
+
327
+ Some runs continue an earlier one. Their `RunContext` says which, so an observer can link them:
328
+
329
+ | The run is | `RunContext` field |
330
+ |---|---|
331
+ | the next run on a session that paused — on an interrupt, a stop, or an unhandled error — whether started by `agent.resume()`, `run.resume()` (with or without a store), or `agent.run()` on that session | `resumedFrom: { runId, carrier? }` — the run that paused |
332
+ | the first run of a session created by `SessionStore.branch()` | `branchedFrom: { sessionId, runId, carrier? }` — the run it was branched from, in the source session |
333
+
334
+ A run has at most one of the two, only on its top-level context (never on a fork branch's), and a
335
+ session that already completed runs nothing, so it has neither.
336
+
337
+ `carrier` is what the earlier run's observer returned from `carry(ctx)`: core calls `carry` once,
338
+ right after `onRunStart`, keeps its string values, and saves them as `StoredRun.carrier` (or on the
339
+ handle, for `run.resume()` without a store). It describes that run only — when the earlier run had
340
+ no observer with `carry`, `carrier` is absent rather than taken from an older run.
341
+ `@noetaris/harness-otel` uses this to link each run's root span to the one before it.
342
+ `composeObservers` merges the maps its observers return (on a shared key the later observer wins);
343
+ a `carry` that throws is reported to `onObserverError` with hook name `'carry'` and counts as
344
+ returning nothing.
345
+
317
346
  ### Running code inside a span
318
347
 
319
348
  Hooks are notifications: they return before the step runs, so they cannot make a span the
@@ -387,14 +416,16 @@ The harness never trusts a wrapper. `fn` always runs exactly once, and the calle
387
416
  | `runtime()` | Marks a provider slot as required at `agent.run()`. |
388
417
  | `composeObservers([a, b], onObserverError?)` | Merges multiple `Observer` instances into one fan-out observer; a throwing observer is isolated and reported to `onObserverError`. |
389
418
  | `SessionStore` | Interface for session persistence backends. |
390
- | `StoredRun` | Type for a persisted run snapshot. Includes `agentId`, `runId`, `sessionId`, `phase`, and state. |
419
+ | `StoredRun` | Type for a persisted run snapshot. Includes `agentId`, `runId`, `sessionId`, `phase`, state, and optionally `carrier` (the run's `carry` result) and `branchedFrom` (on a branch seed). |
420
+ | `RunRef` | `{ runId, carrier? }` — points at an earlier run; used by `RunContext.resumedFrom` and `branchedFrom` (which adds `sessionId`). |
421
+ | `RunCarrier` | `Readonly<Record<string, string>>` — the opaque map an observer's `carry` returns. |
391
422
  | `Observer` | Interface for telemetry hooks on run and step lifecycle events (see [Observers](#observers)). |
392
423
  | `StepSettledEvent` | Payload of `Observer.onStepSettled` — outcome, duration, update, signal, next, error, interrupt, fork. |
393
424
  | `StepSettledNext` | Where the run goes after a step settles: `step`, `end`, `pause`, or `throw`. |
394
425
  | `ObserverAware` | Interface for provider objects that want per-step telemetry: the harness calls `withTelemetry(telemetry)` once per step and installs the returned view on `ctx`. |
395
426
  | `TelemetryContext` | What `withTelemetry` receives: `observer` (the run's observer, fault-isolated), `stepContext`, and `within(type, payload, fn)` bound to that step. |
396
427
  | `WrapScope` | What `Observer.wrap` is asked to enclose: `{ kind: 'step' }` or `{ kind: 'event', type, payload }`. |
397
- | `RunContext` | Context passed to run-level observer hooks — `agentId`, `sessionId`, `runId`, `parentRunId?`, `instanceId?`, `branchPath?`. |
428
+ | `RunContext` | Context passed to run-level observer hooks — `agentId`, `sessionId`, `runId`, `parentRunId?`, `instanceId?`, `branchPath?`, `resumedFrom?`, `branchedFrom?` (see [Linking runs](#linking-runs)). |
398
429
  | `StepContext` | Context passed to step-level observer hooks — `agentId`, `sessionId`, `runId`, `stepName`, `branchPath?`. |
399
430
  | `NoInterruptError` | Thrown when `resume()` is called but the session is not paused on a matching interrupt. |
400
431
  | `SessionInFlightError` | Thrown when a session is already running. |
package/dist/index.d.ts CHANGED
@@ -130,6 +130,19 @@ interface RunContext {
130
130
  * from the real top-level pair. Absent for the top-level run.
131
131
  */
132
132
  readonly branchPath?: readonly string[];
133
+ /**
134
+ * The run this one continues: the run that paused the session (on an interrupt, a stop or
135
+ * an unhandled error), with the carrier its observer produced. Present on a resumed
136
+ * top-level run only — never on a fork branch's context, never together with `branchedFrom`.
137
+ */
138
+ readonly resumedFrom?: RunRef;
139
+ /**
140
+ * The run this session was branched from (`SessionStore.branch`), in the source session.
141
+ * Present on the branched session's first top-level run only.
142
+ */
143
+ readonly branchedFrom?: RunRef & {
144
+ readonly sessionId: string;
145
+ };
133
146
  }
134
147
  /**
135
148
  * Identifies the agent, session, and current step for step-level observer callbacks.
@@ -304,6 +317,15 @@ interface Observer {
304
317
  * Not guarded: a wrapper that never settles hangs the step it encloses.
305
318
  */
306
319
  wrap?: <T>(ctx: StepContext, scope: WrapScope, fn: () => Promise<T>) => Promise<T>;
320
+ /**
321
+ * Called once per top-level run, right after `onRunStart` (never for a fork branch). Return
322
+ * an opaque string map that lets a later run refer back to this one — e.g. the run's trace
323
+ * context. Core saves it on the run's record and hands it back as `RunContext.resumedFrom.carrier`
324
+ * (or `branchedFrom.carrier`) to the run that continues or branches from it. Return
325
+ * `undefined` for nothing. Only string values are kept; a throw is reported via
326
+ * `onObserverError` (hook name `'carry'`) and counts as `undefined`.
327
+ */
328
+ carry?: (ctx: RunContext) => Record<string, string> | undefined;
307
329
  }
308
330
  /**
309
331
  * The telemetry a resource needs to attribute an `invoke()` call to the correct
@@ -462,6 +484,18 @@ interface StoredRunMetadata {
462
484
  /** Open extension point — domain-specific fields. */
463
485
  [key: string]: unknown;
464
486
  }
487
+ /**
488
+ * Opaque string map an observer's `carry` hook returns for a run (e.g. a trace
489
+ * context). Core stores it on the run's record and hands it back to later runs; it never
490
+ * reads it.
491
+ */
492
+ type RunCarrier = Readonly<Record<string, string>>;
493
+ /** Points at one earlier run, with the carrier that run's observer produced (if any). */
494
+ interface RunRef {
495
+ readonly runId: string;
496
+ /** Absent when that run had no observer that carries, or its `carry` returned nothing. */
497
+ readonly carrier?: RunCarrier;
498
+ }
465
499
  /**
466
500
  * Persistence contract for agent sessions.
467
501
  *
@@ -499,6 +533,12 @@ interface SessionStore {
499
533
  * whose initial state equals the forked run's `finalState`.
500
534
  * Optional — omit if your store does not support branching.
501
535
  *
536
+ * Save the new session's version-0 record as a seed the next run continues: `phase: 'paused'`,
537
+ * no `step`, both states equal to the source run's `finalState`, and `branchedFrom` set to the
538
+ * source session, `runId` and the source run's `carrier` (when it has one). When the source is
539
+ * itself such a seed, copy its `branchedFrom` instead (or leave it out if it has none): a seed's
540
+ * own `runId` never ran. The seed has no `carrier` of its own.
541
+ *
502
542
  * @throws {@link BranchNotFoundError} when `runId` is not found in history.
503
543
  */
504
544
  branch?(agentId: string, sessionId: string, runId: string): Promise<string>;
@@ -567,6 +607,19 @@ interface StoredRun {
567
607
  * domain-defined. Absent for runs produced before this field was added.
568
608
  */
569
609
  readonly metadata?: StoredRunMetadata;
610
+ /**
611
+ * What the run's observer returned from `carry` (e.g. its trace context). Describes this
612
+ * run only. Absent when no observer carried anything.
613
+ */
614
+ readonly carrier?: RunCarrier;
615
+ /**
616
+ * Set by `SessionStore.branch` on the seed record of a branched session: the run it was
617
+ * branched from, in the source session. The branched session's first run reports it as
618
+ * `RunContext.branchedFrom`.
619
+ */
620
+ readonly branchedFrom?: RunRef & {
621
+ readonly sessionId: string;
622
+ };
570
623
  }
571
624
  /**
572
625
  * Discriminated union returned by {@link Agent.status} describing the lifecycle
@@ -1006,6 +1059,8 @@ interface Agent {
1006
1059
  *
1007
1060
  * The optional fourth argument `resources` accepts:
1008
1061
  * - `observer?: Observer` — structured telemetry for the resumed run (see Observability)
1062
+ * - `parentRunId?: string` — the caller's run id, as for `run()`; set on this resumed run's
1063
+ * `RunContext` only (a later `handle.resume()` on the returned handle does not inherit it)
1009
1064
  * - `events?.onStoreError?` — raw store-error callback (unchanged from prior shape)
1010
1065
  */
1011
1066
  resume(response: unknown, sessionId: string, interruptId: string, resources?: Record<string, unknown>): RunHandle;
@@ -1110,4 +1165,4 @@ declare class LeaseExpiredError extends Error {
1110
1165
  constructor(sessionId: string);
1111
1166
  }
1112
1167
 
1113
- export { type Agent, type BranchCursor, type BranchDef, type ClaimOptions, type Cursor, type DeepWithMarkers, type FieldDefinition, type ForkCursor, type ForkDef, type FrameworkState, type Harness, type Lease, LeaseExpiredError, type LoopDefinition, type LoopNode, LoopNotDefinedError, NoInterruptError, type Observer, type ObserverAware, type ObserverErrorContext, type ObserverErrorSink, REQUIRED_TAG, RUNTIME_TAG, type RequiredMarker, type RouteFn, type RunContext, type RunFn, type RuntimeMarker, SessionBusyError, SessionInFlightError, SessionPendingInterruptError, type SessionStore, type SignalTransition, type StateFromSchema, type StepContext, type StepDef, type StepSettledEvent, type StepSettledNext, type StepState, StoreLoadError, type StoredRun, type StoredRunMetadata, type TelemetryContext, type TransitionTarget, type WrapScope, composeObservers, createAgent, createHarness, field, isForkCursor, isForkDef, isRequiredMarker, isRuntimeMarker, required, runtime };
1168
+ export { type Agent, type BranchCursor, type BranchDef, type ClaimOptions, type Cursor, type DeepWithMarkers, type FieldDefinition, type ForkCursor, type ForkDef, type FrameworkState, type Harness, type Lease, LeaseExpiredError, type LoopDefinition, type LoopNode, LoopNotDefinedError, NoInterruptError, type Observer, type ObserverAware, type ObserverErrorContext, type ObserverErrorSink, REQUIRED_TAG, RUNTIME_TAG, type RequiredMarker, type RouteFn, type RunCarrier, type RunContext, type RunFn, type RunRef, type RuntimeMarker, SessionBusyError, SessionInFlightError, SessionPendingInterruptError, type SessionStore, type SignalTransition, type StateFromSchema, type StepContext, type StepDef, type StepSettledEvent, type StepSettledNext, type StepState, StoreLoadError, type StoredRun, type StoredRunMetadata, type TelemetryContext, type TransitionTarget, type WrapScope, composeObservers, createAgent, createHarness, field, isForkCursor, isForkDef, isRequiredMarker, isRuntimeMarker, required, runtime };
package/dist/index.js CHANGED
@@ -510,6 +510,14 @@ function safeInvoke(hookName, call, sink) {
510
510
  reportObserverError(error, { hookName }, sink);
511
511
  }
512
512
  }
513
+ function safeCall(hookName, call, sink) {
514
+ try {
515
+ return call();
516
+ } catch (error) {
517
+ reportObserverError(error, { hookName }, sink);
518
+ return void 0;
519
+ }
520
+ }
513
521
  async function runWrapped(wrap, fn, onObserverError) {
514
522
  let p;
515
523
  const inner = () => {
@@ -556,6 +564,16 @@ function composeObservers(observers, onObserverError) {
556
564
  onStepSettled: (ctx, event) => {
557
565
  for (const o of observers) safeInvoke("onStepSettled", () => o.onStepSettled?.(ctx, event), onObserverError);
558
566
  },
567
+ // Each observer isolated on its own; later keys win.
568
+ carry: (ctx) => {
569
+ let merged;
570
+ for (const o of observers) {
571
+ const carried = safeCall("carry", () => o.carry?.(ctx), onObserverError);
572
+ if (typeof carried !== "object" || carried === null || Array.isArray(carried)) continue;
573
+ merged = Object.assign(merged ?? {}, carried);
574
+ }
575
+ return merged;
576
+ },
559
577
  // First observer outermost; each layer isolated on its own by runWrapped.
560
578
  wrap: (ctx, scope, fn) => {
561
579
  const layer = (i) => i === wrappers.length ? fn() : runWrapped((inner) => wrappers[i].wrap(ctx, scope, inner), () => layer(i + 1), onObserverError);
@@ -706,6 +724,11 @@ function stepContextOf(runCtx, stepName) {
706
724
  function asError(e) {
707
725
  return e instanceof Error ? e : new Error(String(e));
708
726
  }
727
+ function normalizeCarrier(carried) {
728
+ if (typeof carried !== "object" || carried === null || Array.isArray(carried)) return void 0;
729
+ const entries = Object.entries(carried).filter(([, v]) => typeof v === "string");
730
+ return entries.length > 0 ? Object.freeze(Object.fromEntries(entries)) : void 0;
731
+ }
709
732
  async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callbacks) {
710
733
  const { onBeforeStep, onAfterStep, onError, onComplete, onInterrupt } = callbacks ?? {};
711
734
  const obs = callbacks?.observer;
@@ -715,15 +738,22 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
715
738
  if (!("$interrupt" in state)) state.$interrupt = null;
716
739
  if (!("$interruptResponses" in state)) state.$interruptResponses = {};
717
740
  const runStart = Date.now();
741
+ const branchPath = callbacks?.branchPath;
742
+ const isBranch = branchPath !== void 0 && branchPath.length > 0;
718
743
  const runCtx = {
719
744
  agentId: ctx.agentId,
720
745
  sessionId: ctx.sessionId,
721
746
  runId: typeof callbacks?.runId === "string" ? callbacks.runId : "",
722
747
  ...typeof callbacks?.parentRunId === "string" ? { parentRunId: callbacks.parentRunId } : {},
723
748
  ...typeof ctx.instanceId === "string" ? { instanceId: ctx.instanceId } : {},
724
- ...callbacks?.branchPath !== void 0 && callbacks.branchPath.length > 0 ? { branchPath: callbacks.branchPath } : {}
749
+ ...isBranch ? { branchPath } : {},
750
+ // Branch runLoops receive the outer callbacks by spread, so the refs are dropped here.
751
+ ...!isBranch && callbacks?.resumedFrom !== void 0 ? { resumedFrom: callbacks.resumedFrom } : {},
752
+ ...!isBranch && callbacks?.branchedFrom !== void 0 ? { branchedFrom: callbacks.branchedFrom } : {}
725
753
  };
726
754
  safeInvoke("onRunStart", () => obs?.onRunStart?.(runCtx), onObserverError);
755
+ const carrier = isBranch ? void 0 : normalizeCarrier(safeCall("carry", () => obs?.carry?.(runCtx), onObserverError));
756
+ const withCarrier = (result) => carrier !== void 0 ? { ...result, carrier } : result;
727
757
  let runEnded = false;
728
758
  const endRun = (signal, failure) => {
729
759
  runEnded = true;
@@ -741,7 +771,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
741
771
  while (true) {
742
772
  if (shouldStop?.()) {
743
773
  endRun("$stopped");
744
- return { state, signal: null, cursor, paused: true };
774
+ return withCarrier({ state, signal: null, cursor, paused: true });
745
775
  }
746
776
  if (isForkCursor(cursor)) {
747
777
  const forkCursor = cursor;
@@ -765,7 +795,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
765
795
  graph
766
796
  );
767
797
  if (outcome.done) {
768
- return outcome.result;
798
+ return withCarrier(outcome.result);
769
799
  }
770
800
  cursor = outcome.nextCursor;
771
801
  continue;
@@ -788,7 +818,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
788
818
  graph
789
819
  );
790
820
  if (outcome.done) {
791
- return outcome.result;
821
+ return withCarrier(outcome.result);
792
822
  }
793
823
  cursor = outcome.nextCursor;
794
824
  continue;
@@ -834,7 +864,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
834
864
  safeInvoke("onInterrupt", () => obs?.onInterrupt?.(stepCtx, { prompt: e.prompt, interruptId: e.interruptId }), onObserverError);
835
865
  settle({ outcome: "interrupt", interrupt: { interruptId: e.interruptId, prompt: e.prompt }, next: { kind: "pause" } });
836
866
  endRun("$interrupt");
837
- return { state, signal: "$interrupt", cursor, paused: true };
867
+ return withCarrier({ state, signal: "$interrupt", cursor, paused: true });
838
868
  }
839
869
  state.$error = e instanceof Error ? e : new Error(String(e));
840
870
  onError?.(state.$error, cursor);
@@ -856,7 +886,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
856
886
  state.$error = e instanceof Error ? e : new Error(String(e));
857
887
  settle({ outcome: "error", error: state.$error, next: { kind: "pause" } });
858
888
  endRun("$error", { error: state.$error });
859
- return { state, signal: "$error", cursor, paused: true };
889
+ return withCarrier({ state, signal: "$error", cursor, paused: true });
860
890
  }
861
891
  const routed = runSucceeded || step.run === void 0 ? { outcome: "ok", signal } : { outcome: "error", signal, error: state.$error };
862
892
  const transition = step.transitions.find((t) => t.signal === signal);
@@ -869,7 +899,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
869
899
  settle({ ...routed, next: { kind: "end" } });
870
900
  onComplete?.(state, signal);
871
901
  endRun(signal);
872
- return { state, signal, cursor: null, paused: false };
902
+ return withCarrier({ state, signal, cursor: null, paused: false });
873
903
  }
874
904
  settle({ ...routed, next: { kind: "step", name: transition.target.name } });
875
905
  cursor = transition.target.name;
@@ -880,7 +910,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
880
910
  } else {
881
911
  settle({ outcome: "error", error: state.$error, next: { kind: "pause" } });
882
912
  endRun("$error", { error: state.$error });
883
- return { state, signal: "$error", cursor, paused: true };
913
+ return withCarrier({ state, signal: "$error", cursor, paused: true });
884
914
  }
885
915
  } else {
886
916
  const next = step.next ?? implicitNextMap.get(cursor) ?? null;
@@ -1249,6 +1279,13 @@ async function querySessionPhase(store, agentId, sessionId) {
1249
1279
  const loaded = await store.load(agentId, sessionId);
1250
1280
  return storedSessionToPhase(loaded);
1251
1281
  }
1282
+ function runRefsOf(loaded) {
1283
+ if (loaded === null || loaded.phase !== "paused") return {};
1284
+ if (loaded.step !== void 0) {
1285
+ return { resumedFrom: { runId: loaded.runId, ...loaded.carrier !== void 0 ? { carrier: loaded.carrier } : {} } };
1286
+ }
1287
+ return loaded.branchedFrom !== void 0 ? { branchedFrom: loaded.branchedFrom } : {};
1288
+ }
1252
1289
  async function runWithSession(store, agentId, sessionId, runId, graph, initialStateArg, schema, ctx, options) {
1253
1290
  const leaseRef = options?.leaseRef ?? { current: null };
1254
1291
  const keepAlive = createKeepAliveFn(leaseRef, store, options?.claimTtlMs ?? 3e4);
@@ -1300,7 +1337,8 @@ async function runWithSession(store, agentId, sessionId, runId, graph, initialSt
1300
1337
  const result = await runLoop(graph, state, ctx, schema, composedShouldStop, loaded?.step, {
1301
1338
  ...options,
1302
1339
  runId,
1303
- ...options?.parentRunId !== void 0 ? { parentRunId: options.parentRunId } : {}
1340
+ ...options?.parentRunId !== void 0 ? { parentRunId: options.parentRunId } : {},
1341
+ ...runRefsOf(loaded)
1304
1342
  });
1305
1343
  if (leaseExpired) {
1306
1344
  const expiredError = new LeaseExpiredError(sessionId);
@@ -1324,7 +1362,8 @@ async function runWithSession(store, agentId, sessionId, runId, graph, initialSt
1324
1362
  finalState: result.state,
1325
1363
  step: result.cursor,
1326
1364
  ...result.signal !== null ? { signal: result.signal } : {},
1327
- ...metadata !== void 0 ? { metadata } : {}
1365
+ ...metadata !== void 0 ? { metadata } : {},
1366
+ ...result.carrier !== void 0 ? { carrier: result.carrier } : {}
1328
1367
  };
1329
1368
  await store.save(agentId, sessionId, saved);
1330
1369
  } else {
@@ -1339,7 +1378,8 @@ async function runWithSession(store, agentId, sessionId, runId, graph, initialSt
1339
1378
  initialState: initialStateSnapshot,
1340
1379
  finalState: result.state,
1341
1380
  ...result.signal !== null ? { signal: result.signal } : {},
1342
- ...metadata !== void 0 ? { metadata } : {}
1381
+ ...metadata !== void 0 ? { metadata } : {},
1382
+ ...result.carrier !== void 0 ? { carrier: result.carrier } : {}
1343
1383
  };
1344
1384
  await store.save(agentId, sessionId, saved);
1345
1385
  }
@@ -1416,7 +1456,10 @@ async function injectInterruptResponse(store, agentId, sessionId, interruptId, r
1416
1456
  initialState: loaded.initialState,
1417
1457
  finalState: result.state,
1418
1458
  ...result.cursor !== void 0 ? { step: result.cursor } : {},
1419
- ...loaded.signal !== void 0 ? { signal: loaded.signal } : {}
1459
+ ...loaded.signal !== void 0 ? { signal: loaded.signal } : {},
1460
+ ...loaded.metadata !== void 0 ? { metadata: loaded.metadata } : {},
1461
+ ...loaded.carrier !== void 0 ? { carrier: loaded.carrier } : {},
1462
+ ...loaded.branchedFrom !== void 0 ? { branchedFrom: loaded.branchedFrom } : {}
1420
1463
  };
1421
1464
  await store.save(agentId, sessionId, updated);
1422
1465
  }
@@ -1661,7 +1704,8 @@ function createAgent(id, h, slots, agentOptions) {
1661
1704
  const reservedRunKeys = /* @__PURE__ */ new Set(["sessionId", "signal", "events", "listeners", "observer", "onObserverError", "claimOptions", "parentRunId"]);
1662
1705
  const inFlightSessions = /* @__PURE__ */ new Set();
1663
1706
  const interruptPendingSessions = /* @__PURE__ */ new Set();
1664
- const makeAgentResumeHandle = (resp, sId, iId, resumeOpts, observer, onObserverError, telemetrySlots) => {
1707
+ const makeAgentResumeHandle = (resp, sId, iId, resumeResources = {}) => {
1708
+ const { onStoreError, observer, onObserverError, telemetrySlots, parentRunId } = resumeResources;
1665
1709
  let _stopped = false;
1666
1710
  const abortController = new AbortController();
1667
1711
  const flag = {
@@ -1686,14 +1730,14 @@ function createAgent(id, h, slots, agentOptions) {
1686
1730
  lease = await capturedStore.claim(id, sId, claimOptions);
1687
1731
  } catch (claimError) {
1688
1732
  const loadError = new StoreLoadError(claimError);
1689
- resumeOpts?.onStoreError?.(loadError, "claim");
1733
+ onStoreError?.(loadError, "claim");
1690
1734
  const failState = initializeState(null, {}, agentInternals.stateSchema);
1691
1735
  failState["$error"] = loadError;
1692
1736
  return { state: failState, signal: "$error" };
1693
1737
  }
1694
1738
  if (lease === null) {
1695
1739
  const busyError = new SessionBusyError(sId);
1696
- resumeOpts?.onStoreError?.(busyError, "claim");
1740
+ onStoreError?.(busyError, "claim");
1697
1741
  const failState = initializeState(null, {}, agentInternals.stateSchema);
1698
1742
  failState["$error"] = busyError;
1699
1743
  return { state: failState, signal: "$error" };
@@ -1707,7 +1751,7 @@ function createAgent(id, h, slots, agentOptions) {
1707
1751
  } catch (injectError) {
1708
1752
  if (injectError instanceof NoInterruptError) throw injectError;
1709
1753
  const storeError = new StoreLoadError(injectError);
1710
- resumeOpts?.onStoreError?.(storeError, "claim");
1754
+ onStoreError?.(storeError, "claim");
1711
1755
  const failState = initializeState(null, {}, agentInternals.stateSchema);
1712
1756
  failState["$error"] = storeError;
1713
1757
  return { state: failState, signal: "$error" };
@@ -1717,7 +1761,8 @@ function createAgent(id, h, slots, agentOptions) {
1717
1761
  agentId: id,
1718
1762
  sessionId: sId,
1719
1763
  runId: rId,
1720
- signal: abortController.signal
1764
+ signal: abortController.signal,
1765
+ ...agentOptions?.instanceId !== void 0 ? { instanceId: agentOptions.instanceId } : {}
1721
1766
  };
1722
1767
  const r = await runWithSession(
1723
1768
  capturedStore,
@@ -1733,11 +1778,13 @@ function createAgent(id, h, slots, agentOptions) {
1733
1778
  onBeforeStep: (n) => {
1734
1779
  ref.current = n;
1735
1780
  },
1736
- ...resumeOpts?.onStoreError !== void 0 ? { onStoreError: resumeOpts.onStoreError } : {},
1781
+ ...onStoreError !== void 0 ? { onStoreError } : {},
1737
1782
  ...leaseRef !== void 0 ? { leaseRef, claimTtlMs: claimOptions.ttlMs } : {},
1738
1783
  ...observer !== void 0 ? { observer } : {},
1739
1784
  ...onObserverError !== void 0 ? { onObserverError } : {},
1740
- ...telemetrySlots !== void 0 && telemetrySlots.length > 0 ? { telemetrySlots } : {}
1785
+ ...telemetrySlots !== void 0 && telemetrySlots.length > 0 ? { telemetrySlots } : {},
1786
+ ...agentOptions?.instanceId !== void 0 ? { instanceId: agentOptions.instanceId } : {},
1787
+ ...parentRunId !== void 0 ? { parentRunId } : {}
1741
1788
  }
1742
1789
  );
1743
1790
  if (r.signal === "$interrupt") interruptPendingSessions.add(sId);
@@ -1765,7 +1812,8 @@ function createAgent(id, h, slots, agentOptions) {
1765
1812
  if (inFlightSessions.has(sId)) throw new SessionInFlightError(sId);
1766
1813
  interruptPendingSessions.delete(sId);
1767
1814
  inFlightSessions.add(sId);
1768
- return makeAgentResumeHandle(r, sId, i);
1815
+ const { parentRunId: _staleParentRunId, ...inherited } = resumeResources;
1816
+ return makeAgentResumeHandle(r, sId, i, inherited);
1769
1817
  };
1770
1818
  return createRunHandle(sId, rId, exec, flag, ref, resumeFn);
1771
1819
  };
@@ -1901,7 +1949,7 @@ function createAgent(id, h, slots, agentOptions) {
1901
1949
  }
1902
1950
  );
1903
1951
  if (r.signal === "$interrupt") interruptPendingSessions.add(sessionId);
1904
- lastResult = { state: r.state, cursor: r.cursor };
1952
+ lastResult = { state: r.state, cursor: r.cursor, runId, ...r.carrier !== void 0 ? { carrier: r.carrier } : {} };
1905
1953
  return { state: r.state, signal: r.signal };
1906
1954
  } catch (error) {
1907
1955
  if (error instanceof LeaseExpiredError) {
@@ -1974,11 +2022,12 @@ function createAgent(id, h, slots, agentOptions) {
1974
2022
  ...Object.keys(listeners).length > 0 ? { listeners } : {},
1975
2023
  ...observer !== void 0 ? { observer } : {},
1976
2024
  ...onObserverError !== void 0 ? { onObserverError } : {},
1977
- ...telemetrySlots.length > 0 ? { telemetrySlots } : {}
2025
+ ...telemetrySlots.length > 0 ? { telemetrySlots } : {},
2026
+ ...agentOptions?.instanceId !== void 0 ? { instanceId: agentOptions.instanceId } : {}
1978
2027
  }
1979
2028
  );
1980
2029
  if (r2.signal === "$interrupt") interruptPendingSessions.add(sessionId);
1981
- lastResult = { state: r2.state, cursor: r2.cursor };
2030
+ lastResult = { state: r2.state, cursor: r2.cursor, runId: resumeRunId, ...r2.carrier !== void 0 ? { carrier: r2.carrier } : {} };
1982
2031
  return { state: r2.state, signal: r2.signal };
1983
2032
  }
1984
2033
  const prev = lastResult;
@@ -1996,6 +2045,7 @@ function createAgent(id, h, slots, agentOptions) {
1996
2045
  cursor,
1997
2046
  {
1998
2047
  runId: resumeRunId,
2048
+ resumedFrom: { runId: prev.runId, ...prev.carrier !== void 0 ? { carrier: prev.carrier } : {} },
1999
2049
  onBeforeStep: (n, s) => {
2000
2050
  resumeStepRef.current = n;
2001
2051
  events.onBeforeStep?.(n, s);
@@ -2011,7 +2061,7 @@ function createAgent(id, h, slots, agentOptions) {
2011
2061
  }
2012
2062
  );
2013
2063
  if (r.signal === "$interrupt") interruptPendingSessions.add(sessionId);
2014
- lastResult = { state: r.state, cursor: r.cursor };
2064
+ lastResult = { state: r.state, cursor: r.cursor, runId: resumeRunId, ...r.carrier !== void 0 ? { carrier: r.carrier } : {} };
2015
2065
  return { state: r.state, signal: r.signal };
2016
2066
  } catch (error) {
2017
2067
  if (error instanceof LeaseExpiredError) {
@@ -2034,11 +2084,17 @@ function createAgent(id, h, slots, agentOptions) {
2034
2084
  interruptPendingSessions.delete(sessionId);
2035
2085
  inFlightSessions.add(sessionId);
2036
2086
  const events = extractRunEvents(resources ?? {});
2037
- const resumeOpts = events.onStoreError !== void 0 ? { onStoreError: events.onStoreError } : void 0;
2038
2087
  const observer = extractRunObserver(resources ?? {});
2039
2088
  const onObserverError = extractRunErrorSink(resources ?? {});
2040
2089
  const telemetrySlots = buildTelemetrySlots(agentInternals.resolvedProviders.entries());
2041
- return makeAgentResumeHandle(response, sessionId, interruptId, resumeOpts, observer ?? void 0, onObserverError ?? void 0, telemetrySlots.length > 0 ? telemetrySlots : void 0);
2090
+ const parentRunId = resources?.["parentRunId"];
2091
+ return makeAgentResumeHandle(response, sessionId, interruptId, {
2092
+ ...events.onStoreError !== void 0 ? { onStoreError: events.onStoreError } : {},
2093
+ ...observer !== void 0 ? { observer } : {},
2094
+ ...onObserverError !== void 0 ? { onObserverError } : {},
2095
+ ...telemetrySlots.length > 0 ? { telemetrySlots } : {},
2096
+ ...typeof parentRunId === "string" ? { parentRunId } : {}
2097
+ });
2042
2098
  },
2043
2099
  status: (sessionId) => querySessionPhase(capturedStore, id, sessionId),
2044
2100
  [_agentInternals]: agentInternals