@noetaris/harness 0.7.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 +35 -3
- package/dist/index.d.ts +65 -3
- package/dist/index.js +89 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
@@ -255,7 +262,7 @@ throws on an adapter event never fails a step. Every hook is optional.
|
|
|
255
262
|
| Hook | Fires |
|
|
256
263
|
|---|---|
|
|
257
264
|
| `onRunStart(ctx)` | once when a run starts |
|
|
258
|
-
| `onRunEnd(ctx, { signal, durationMs })` | once when a run settles — completed, stopped (`$stopped`), paused, or rejected by a thrown error (`$error`) |
|
|
265
|
+
| `onRunEnd(ctx, { signal, durationMs, error? })` | once when a run settles — completed, stopped (`$stopped`), paused, or rejected by a thrown error (`$error`). `error` is set when the run ends with `$error` because of an error no `onError` step handled (paused) or a rejection; a route that returns `'$error'` itself gives no `error`, so test for `error`, not the signal |
|
|
259
266
|
| `onStepStart(ctx)` | before each step |
|
|
260
267
|
| `onStepEnd(ctx, { durationMs })` | after a step's `run` succeeds, before its `route` |
|
|
261
268
|
| `onStepError(ctx, { error, durationMs })` | when a step's `run` throws |
|
|
@@ -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
|
|
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. |
|
|
@@ -424,6 +455,7 @@ See [LIMITATIONS.md](LIMITATIONS.md) for known limitations.
|
|
|
424
455
|
- [`@noetaris/harness-openai`](https://github.com/noetaris-lab/harness-openai) — OpenAI adapter
|
|
425
456
|
- [`@noetaris/harness-google`](https://github.com/noetaris-lab/harness-google) — Google Gemini adapter
|
|
426
457
|
- [`@noetaris/harness-otel`](https://github.com/noetaris-lab/harness-otel) — OpenTelemetry observer bridge
|
|
458
|
+
- [`@noetaris/harness-otel-genai`](https://github.com/noetaris-lab/harness-otel-genai) — GenAI semantic-conventions extension for the bridge
|
|
427
459
|
|
|
428
460
|
## License
|
|
429
461
|
|
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.
|
|
@@ -247,12 +260,19 @@ interface Observer {
|
|
|
247
260
|
onRunStart?: (ctx: RunContext) => void;
|
|
248
261
|
/**
|
|
249
262
|
* Called once when a run settles — completed, stopped, paused, or rejected by a thrown error
|
|
250
|
-
* (a graph-definition error or a throwing callback).
|
|
251
|
-
*
|
|
263
|
+
* (a graph-definition error or a throwing callback).
|
|
264
|
+
*
|
|
265
|
+
* When the run ends because of an error, `signal` is `'$error'` and `error` holds it: the error
|
|
266
|
+
* a step's `run` or `route` threw, or a failed fork's branch error, when no `onError` step
|
|
267
|
+
* handled it (the run resolves paused); or the original thrown value when the run rejects (it
|
|
268
|
+
* rejects right after). `error` is absent on every other ending. A route may also return
|
|
269
|
+
* `'$error'` as an ordinary signal; that ending has no `error` key, so test for `error`, not
|
|
270
|
+
* for the signal.
|
|
252
271
|
*/
|
|
253
272
|
onRunEnd?: (ctx: RunContext, event: {
|
|
254
273
|
signal: string;
|
|
255
274
|
durationMs: number;
|
|
275
|
+
error?: unknown;
|
|
256
276
|
}) => void;
|
|
257
277
|
/** Called immediately before each step's `run` function is invoked. */
|
|
258
278
|
onStepStart?: (ctx: StepContext) => void;
|
|
@@ -297,6 +317,15 @@ interface Observer {
|
|
|
297
317
|
* Not guarded: a wrapper that never settles hangs the step it encloses.
|
|
298
318
|
*/
|
|
299
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;
|
|
300
329
|
}
|
|
301
330
|
/**
|
|
302
331
|
* The telemetry a resource needs to attribute an `invoke()` call to the correct
|
|
@@ -455,6 +484,18 @@ interface StoredRunMetadata {
|
|
|
455
484
|
/** Open extension point — domain-specific fields. */
|
|
456
485
|
[key: string]: unknown;
|
|
457
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
|
+
}
|
|
458
499
|
/**
|
|
459
500
|
* Persistence contract for agent sessions.
|
|
460
501
|
*
|
|
@@ -492,6 +533,12 @@ interface SessionStore {
|
|
|
492
533
|
* whose initial state equals the forked run's `finalState`.
|
|
493
534
|
* Optional — omit if your store does not support branching.
|
|
494
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
|
+
*
|
|
495
542
|
* @throws {@link BranchNotFoundError} when `runId` is not found in history.
|
|
496
543
|
*/
|
|
497
544
|
branch?(agentId: string, sessionId: string, runId: string): Promise<string>;
|
|
@@ -560,6 +607,19 @@ interface StoredRun {
|
|
|
560
607
|
* domain-defined. Absent for runs produced before this field was added.
|
|
561
608
|
*/
|
|
562
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
|
+
};
|
|
563
623
|
}
|
|
564
624
|
/**
|
|
565
625
|
* Discriminated union returned by {@link Agent.status} describing the lifecycle
|
|
@@ -999,6 +1059,8 @@ interface Agent {
|
|
|
999
1059
|
*
|
|
1000
1060
|
* The optional fourth argument `resources` accepts:
|
|
1001
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)
|
|
1002
1064
|
* - `events?.onStoreError?` — raw store-error callback (unchanged from prior shape)
|
|
1003
1065
|
*/
|
|
1004
1066
|
resume(response: unknown, sessionId: string, interruptId: string, resources?: Record<string, unknown>): RunHandle;
|
|
@@ -1103,4 +1165,4 @@ declare class LeaseExpiredError extends Error {
|
|
|
1103
1165
|
constructor(sessionId: string);
|
|
1104
1166
|
}
|
|
1105
1167
|
|
|
1106
|
-
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,19 +738,27 @@ 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
|
-
...
|
|
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
|
-
const endRun = (signal) => {
|
|
758
|
+
const endRun = (signal, failure) => {
|
|
729
759
|
runEnded = true;
|
|
730
|
-
|
|
760
|
+
const event = { signal, durationMs: Date.now() - runStart, ...failure !== void 0 ? { error: failure.error } : {} };
|
|
761
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, event), onObserverError);
|
|
731
762
|
};
|
|
732
763
|
const callCountRef = { current: 0 };
|
|
733
764
|
const stepCtxRef = { current: null };
|
|
@@ -740,7 +771,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
740
771
|
while (true) {
|
|
741
772
|
if (shouldStop?.()) {
|
|
742
773
|
endRun("$stopped");
|
|
743
|
-
return { state, signal: null, cursor, paused: true };
|
|
774
|
+
return withCarrier({ state, signal: null, cursor, paused: true });
|
|
744
775
|
}
|
|
745
776
|
if (isForkCursor(cursor)) {
|
|
746
777
|
const forkCursor = cursor;
|
|
@@ -764,7 +795,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
764
795
|
graph
|
|
765
796
|
);
|
|
766
797
|
if (outcome.done) {
|
|
767
|
-
return outcome.result;
|
|
798
|
+
return withCarrier(outcome.result);
|
|
768
799
|
}
|
|
769
800
|
cursor = outcome.nextCursor;
|
|
770
801
|
continue;
|
|
@@ -787,7 +818,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
787
818
|
graph
|
|
788
819
|
);
|
|
789
820
|
if (outcome.done) {
|
|
790
|
-
return outcome.result;
|
|
821
|
+
return withCarrier(outcome.result);
|
|
791
822
|
}
|
|
792
823
|
cursor = outcome.nextCursor;
|
|
793
824
|
continue;
|
|
@@ -833,7 +864,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
833
864
|
safeInvoke("onInterrupt", () => obs?.onInterrupt?.(stepCtx, { prompt: e.prompt, interruptId: e.interruptId }), onObserverError);
|
|
834
865
|
settle({ outcome: "interrupt", interrupt: { interruptId: e.interruptId, prompt: e.prompt }, next: { kind: "pause" } });
|
|
835
866
|
endRun("$interrupt");
|
|
836
|
-
return { state, signal: "$interrupt", cursor, paused: true };
|
|
867
|
+
return withCarrier({ state, signal: "$interrupt", cursor, paused: true });
|
|
837
868
|
}
|
|
838
869
|
state.$error = e instanceof Error ? e : new Error(String(e));
|
|
839
870
|
onError?.(state.$error, cursor);
|
|
@@ -854,8 +885,8 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
854
885
|
} catch (e) {
|
|
855
886
|
state.$error = e instanceof Error ? e : new Error(String(e));
|
|
856
887
|
settle({ outcome: "error", error: state.$error, next: { kind: "pause" } });
|
|
857
|
-
endRun("$error");
|
|
858
|
-
return { state, signal: "$error", cursor, paused: true };
|
|
888
|
+
endRun("$error", { error: state.$error });
|
|
889
|
+
return withCarrier({ state, signal: "$error", cursor, paused: true });
|
|
859
890
|
}
|
|
860
891
|
const routed = runSucceeded || step.run === void 0 ? { outcome: "ok", signal } : { outcome: "error", signal, error: state.$error };
|
|
861
892
|
const transition = step.transitions.find((t) => t.signal === signal);
|
|
@@ -868,7 +899,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
868
899
|
settle({ ...routed, next: { kind: "end" } });
|
|
869
900
|
onComplete?.(state, signal);
|
|
870
901
|
endRun(signal);
|
|
871
|
-
return { state, signal, cursor: null, paused: false };
|
|
902
|
+
return withCarrier({ state, signal, cursor: null, paused: false });
|
|
872
903
|
}
|
|
873
904
|
settle({ ...routed, next: { kind: "step", name: transition.target.name } });
|
|
874
905
|
cursor = transition.target.name;
|
|
@@ -878,8 +909,8 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
878
909
|
cursor = graph.onError;
|
|
879
910
|
} else {
|
|
880
911
|
settle({ outcome: "error", error: state.$error, next: { kind: "pause" } });
|
|
881
|
-
endRun("$error");
|
|
882
|
-
return { state, signal: "$error", cursor, paused: true };
|
|
912
|
+
endRun("$error", { error: state.$error });
|
|
913
|
+
return withCarrier({ state, signal: "$error", cursor, paused: true });
|
|
883
914
|
}
|
|
884
915
|
} else {
|
|
885
916
|
const next = step.next ?? implicitNextMap.get(cursor) ?? null;
|
|
@@ -893,7 +924,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
893
924
|
}
|
|
894
925
|
}
|
|
895
926
|
} catch (e) {
|
|
896
|
-
if (!runEnded) endRun("$error");
|
|
927
|
+
if (!runEnded) endRun("$error", { error: e });
|
|
897
928
|
throw e;
|
|
898
929
|
}
|
|
899
930
|
}
|
|
@@ -1036,7 +1067,7 @@ async function handleFork(forkNode, priorBranches, state, ctx, schema, shouldSto
|
|
|
1036
1067
|
return { done: false, nextCursor: graph.onError };
|
|
1037
1068
|
}
|
|
1038
1069
|
fireSettled(obs, stepCtx, stepStart, { outcome: "error", error: state.$error, next: { kind: "pause" } }, onObserverError);
|
|
1039
|
-
endRun("$error");
|
|
1070
|
+
endRun("$error", { error: state.$error });
|
|
1040
1071
|
return { done: true, result: { state, signal: "$error", cursor: forkNode.name, paused: true } };
|
|
1041
1072
|
}
|
|
1042
1073
|
state.$error = null;
|
|
@@ -1248,6 +1279,13 @@ async function querySessionPhase(store, agentId, sessionId) {
|
|
|
1248
1279
|
const loaded = await store.load(agentId, sessionId);
|
|
1249
1280
|
return storedSessionToPhase(loaded);
|
|
1250
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
|
+
}
|
|
1251
1289
|
async function runWithSession(store, agentId, sessionId, runId, graph, initialStateArg, schema, ctx, options) {
|
|
1252
1290
|
const leaseRef = options?.leaseRef ?? { current: null };
|
|
1253
1291
|
const keepAlive = createKeepAliveFn(leaseRef, store, options?.claimTtlMs ?? 3e4);
|
|
@@ -1299,7 +1337,8 @@ async function runWithSession(store, agentId, sessionId, runId, graph, initialSt
|
|
|
1299
1337
|
const result = await runLoop(graph, state, ctx, schema, composedShouldStop, loaded?.step, {
|
|
1300
1338
|
...options,
|
|
1301
1339
|
runId,
|
|
1302
|
-
...options?.parentRunId !== void 0 ? { parentRunId: options.parentRunId } : {}
|
|
1340
|
+
...options?.parentRunId !== void 0 ? { parentRunId: options.parentRunId } : {},
|
|
1341
|
+
...runRefsOf(loaded)
|
|
1303
1342
|
});
|
|
1304
1343
|
if (leaseExpired) {
|
|
1305
1344
|
const expiredError = new LeaseExpiredError(sessionId);
|
|
@@ -1323,7 +1362,8 @@ async function runWithSession(store, agentId, sessionId, runId, graph, initialSt
|
|
|
1323
1362
|
finalState: result.state,
|
|
1324
1363
|
step: result.cursor,
|
|
1325
1364
|
...result.signal !== null ? { signal: result.signal } : {},
|
|
1326
|
-
...metadata !== void 0 ? { metadata } : {}
|
|
1365
|
+
...metadata !== void 0 ? { metadata } : {},
|
|
1366
|
+
...result.carrier !== void 0 ? { carrier: result.carrier } : {}
|
|
1327
1367
|
};
|
|
1328
1368
|
await store.save(agentId, sessionId, saved);
|
|
1329
1369
|
} else {
|
|
@@ -1338,7 +1378,8 @@ async function runWithSession(store, agentId, sessionId, runId, graph, initialSt
|
|
|
1338
1378
|
initialState: initialStateSnapshot,
|
|
1339
1379
|
finalState: result.state,
|
|
1340
1380
|
...result.signal !== null ? { signal: result.signal } : {},
|
|
1341
|
-
...metadata !== void 0 ? { metadata } : {}
|
|
1381
|
+
...metadata !== void 0 ? { metadata } : {},
|
|
1382
|
+
...result.carrier !== void 0 ? { carrier: result.carrier } : {}
|
|
1342
1383
|
};
|
|
1343
1384
|
await store.save(agentId, sessionId, saved);
|
|
1344
1385
|
}
|
|
@@ -1415,7 +1456,10 @@ async function injectInterruptResponse(store, agentId, sessionId, interruptId, r
|
|
|
1415
1456
|
initialState: loaded.initialState,
|
|
1416
1457
|
finalState: result.state,
|
|
1417
1458
|
...result.cursor !== void 0 ? { step: result.cursor } : {},
|
|
1418
|
-
...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 } : {}
|
|
1419
1463
|
};
|
|
1420
1464
|
await store.save(agentId, sessionId, updated);
|
|
1421
1465
|
}
|
|
@@ -1660,7 +1704,8 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1660
1704
|
const reservedRunKeys = /* @__PURE__ */ new Set(["sessionId", "signal", "events", "listeners", "observer", "onObserverError", "claimOptions", "parentRunId"]);
|
|
1661
1705
|
const inFlightSessions = /* @__PURE__ */ new Set();
|
|
1662
1706
|
const interruptPendingSessions = /* @__PURE__ */ new Set();
|
|
1663
|
-
const makeAgentResumeHandle = (resp, sId, iId,
|
|
1707
|
+
const makeAgentResumeHandle = (resp, sId, iId, resumeResources = {}) => {
|
|
1708
|
+
const { onStoreError, observer, onObserverError, telemetrySlots, parentRunId } = resumeResources;
|
|
1664
1709
|
let _stopped = false;
|
|
1665
1710
|
const abortController = new AbortController();
|
|
1666
1711
|
const flag = {
|
|
@@ -1685,14 +1730,14 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1685
1730
|
lease = await capturedStore.claim(id, sId, claimOptions);
|
|
1686
1731
|
} catch (claimError) {
|
|
1687
1732
|
const loadError = new StoreLoadError(claimError);
|
|
1688
|
-
|
|
1733
|
+
onStoreError?.(loadError, "claim");
|
|
1689
1734
|
const failState = initializeState(null, {}, agentInternals.stateSchema);
|
|
1690
1735
|
failState["$error"] = loadError;
|
|
1691
1736
|
return { state: failState, signal: "$error" };
|
|
1692
1737
|
}
|
|
1693
1738
|
if (lease === null) {
|
|
1694
1739
|
const busyError = new SessionBusyError(sId);
|
|
1695
|
-
|
|
1740
|
+
onStoreError?.(busyError, "claim");
|
|
1696
1741
|
const failState = initializeState(null, {}, agentInternals.stateSchema);
|
|
1697
1742
|
failState["$error"] = busyError;
|
|
1698
1743
|
return { state: failState, signal: "$error" };
|
|
@@ -1706,7 +1751,7 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1706
1751
|
} catch (injectError) {
|
|
1707
1752
|
if (injectError instanceof NoInterruptError) throw injectError;
|
|
1708
1753
|
const storeError = new StoreLoadError(injectError);
|
|
1709
|
-
|
|
1754
|
+
onStoreError?.(storeError, "claim");
|
|
1710
1755
|
const failState = initializeState(null, {}, agentInternals.stateSchema);
|
|
1711
1756
|
failState["$error"] = storeError;
|
|
1712
1757
|
return { state: failState, signal: "$error" };
|
|
@@ -1716,7 +1761,8 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1716
1761
|
agentId: id,
|
|
1717
1762
|
sessionId: sId,
|
|
1718
1763
|
runId: rId,
|
|
1719
|
-
signal: abortController.signal
|
|
1764
|
+
signal: abortController.signal,
|
|
1765
|
+
...agentOptions?.instanceId !== void 0 ? { instanceId: agentOptions.instanceId } : {}
|
|
1720
1766
|
};
|
|
1721
1767
|
const r = await runWithSession(
|
|
1722
1768
|
capturedStore,
|
|
@@ -1732,11 +1778,13 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1732
1778
|
onBeforeStep: (n) => {
|
|
1733
1779
|
ref.current = n;
|
|
1734
1780
|
},
|
|
1735
|
-
...
|
|
1781
|
+
...onStoreError !== void 0 ? { onStoreError } : {},
|
|
1736
1782
|
...leaseRef !== void 0 ? { leaseRef, claimTtlMs: claimOptions.ttlMs } : {},
|
|
1737
1783
|
...observer !== void 0 ? { observer } : {},
|
|
1738
1784
|
...onObserverError !== void 0 ? { onObserverError } : {},
|
|
1739
|
-
...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 } : {}
|
|
1740
1788
|
}
|
|
1741
1789
|
);
|
|
1742
1790
|
if (r.signal === "$interrupt") interruptPendingSessions.add(sId);
|
|
@@ -1764,7 +1812,8 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1764
1812
|
if (inFlightSessions.has(sId)) throw new SessionInFlightError(sId);
|
|
1765
1813
|
interruptPendingSessions.delete(sId);
|
|
1766
1814
|
inFlightSessions.add(sId);
|
|
1767
|
-
|
|
1815
|
+
const { parentRunId: _staleParentRunId, ...inherited } = resumeResources;
|
|
1816
|
+
return makeAgentResumeHandle(r, sId, i, inherited);
|
|
1768
1817
|
};
|
|
1769
1818
|
return createRunHandle(sId, rId, exec, flag, ref, resumeFn);
|
|
1770
1819
|
};
|
|
@@ -1900,7 +1949,7 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1900
1949
|
}
|
|
1901
1950
|
);
|
|
1902
1951
|
if (r.signal === "$interrupt") interruptPendingSessions.add(sessionId);
|
|
1903
|
-
lastResult = { state: r.state, cursor: r.cursor };
|
|
1952
|
+
lastResult = { state: r.state, cursor: r.cursor, runId, ...r.carrier !== void 0 ? { carrier: r.carrier } : {} };
|
|
1904
1953
|
return { state: r.state, signal: r.signal };
|
|
1905
1954
|
} catch (error) {
|
|
1906
1955
|
if (error instanceof LeaseExpiredError) {
|
|
@@ -1973,11 +2022,12 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1973
2022
|
...Object.keys(listeners).length > 0 ? { listeners } : {},
|
|
1974
2023
|
...observer !== void 0 ? { observer } : {},
|
|
1975
2024
|
...onObserverError !== void 0 ? { onObserverError } : {},
|
|
1976
|
-
...telemetrySlots.length > 0 ? { telemetrySlots } : {}
|
|
2025
|
+
...telemetrySlots.length > 0 ? { telemetrySlots } : {},
|
|
2026
|
+
...agentOptions?.instanceId !== void 0 ? { instanceId: agentOptions.instanceId } : {}
|
|
1977
2027
|
}
|
|
1978
2028
|
);
|
|
1979
2029
|
if (r2.signal === "$interrupt") interruptPendingSessions.add(sessionId);
|
|
1980
|
-
lastResult = { state: r2.state, cursor: r2.cursor };
|
|
2030
|
+
lastResult = { state: r2.state, cursor: r2.cursor, runId: resumeRunId, ...r2.carrier !== void 0 ? { carrier: r2.carrier } : {} };
|
|
1981
2031
|
return { state: r2.state, signal: r2.signal };
|
|
1982
2032
|
}
|
|
1983
2033
|
const prev = lastResult;
|
|
@@ -1995,6 +2045,7 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1995
2045
|
cursor,
|
|
1996
2046
|
{
|
|
1997
2047
|
runId: resumeRunId,
|
|
2048
|
+
resumedFrom: { runId: prev.runId, ...prev.carrier !== void 0 ? { carrier: prev.carrier } : {} },
|
|
1998
2049
|
onBeforeStep: (n, s) => {
|
|
1999
2050
|
resumeStepRef.current = n;
|
|
2000
2051
|
events.onBeforeStep?.(n, s);
|
|
@@ -2010,7 +2061,7 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
2010
2061
|
}
|
|
2011
2062
|
);
|
|
2012
2063
|
if (r.signal === "$interrupt") interruptPendingSessions.add(sessionId);
|
|
2013
|
-
lastResult = { state: r.state, cursor: r.cursor };
|
|
2064
|
+
lastResult = { state: r.state, cursor: r.cursor, runId: resumeRunId, ...r.carrier !== void 0 ? { carrier: r.carrier } : {} };
|
|
2014
2065
|
return { state: r.state, signal: r.signal };
|
|
2015
2066
|
} catch (error) {
|
|
2016
2067
|
if (error instanceof LeaseExpiredError) {
|
|
@@ -2033,11 +2084,17 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
2033
2084
|
interruptPendingSessions.delete(sessionId);
|
|
2034
2085
|
inFlightSessions.add(sessionId);
|
|
2035
2086
|
const events = extractRunEvents(resources ?? {});
|
|
2036
|
-
const resumeOpts = events.onStoreError !== void 0 ? { onStoreError: events.onStoreError } : void 0;
|
|
2037
2087
|
const observer = extractRunObserver(resources ?? {});
|
|
2038
2088
|
const onObserverError = extractRunErrorSink(resources ?? {});
|
|
2039
2089
|
const telemetrySlots = buildTelemetrySlots(agentInternals.resolvedProviders.entries());
|
|
2040
|
-
|
|
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
|
+
});
|
|
2041
2098
|
},
|
|
2042
2099
|
status: (sessionId) => querySessionPhase(capturedStore, id, sessionId),
|
|
2043
2100
|
[_agentInternals]: agentInternals
|