@noetaris/harness 0.12.0 → 0.13.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
@@ -29,13 +29,15 @@ interface Ctx {
29
29
  }
30
30
 
31
31
  // 2. Create the harness — fixes Ctx and infers State from the field schema
32
- const h = createHarness<Ctx>()({
32
+ const base = createHarness<Ctx>()({
33
33
  messages: field<string[]>({ default: () => [], reduce: (a, b) => [...a, ...b] }),
34
34
  toolCalls: field<any[]> ({ default: () => [] }),
35
+ toolResults: field<any[]>({ default: () => [] }),
35
36
  })
36
37
 
37
- // 3. Define the loop — validated immediately at call time
38
- h.loop(l =>
38
+ // 3. Define the loop — validated immediately at call time.
39
+ // Each builder call returns a new harness: keep the result.
40
+ const withLoop = base.loop(l =>
39
41
  l.start()
40
42
  .step('think', {
41
43
  run: async (state, ctx) => {
@@ -57,9 +59,10 @@ h.loop(l =>
57
59
  )
58
60
 
59
61
  // 4. Declare providers
60
- h.provide('tools', { search: new MySearchTool() }) // hard-coded
61
- h.provide('prompts', required()) // must be supplied at createAgent()
62
- h.provide('model', runtime()) // must be supplied at agent.run()
62
+ const h = withLoop
63
+ .provide('tools', { search: new MySearchTool() }) // hard-coded
64
+ .provide('prompts', required()) // must be supplied at createAgent()
65
+ .provide('model', runtime()) // must be supplied at agent.run()
63
66
 
64
67
  // 5. Create an agent — assigns an ID and fills required() slots
65
68
  const agent = createAgent('my-agent', h, {
@@ -166,6 +169,7 @@ does depends on the session's latest run:
166
169
  | completed | a new turn | returns its outcome, runs nothing |
167
170
  | paused on an interrupt | resolves `$error` with `SessionPendingInterruptError` | returns its outcome (`signal: '$interrupt'`), runs nothing |
168
171
  | paused by a stop or an unhandled error | merges the input and continues from where it paused | continues from where it paused, **without** merging the input again |
172
+ | a checkpoint (`'running'`: its instance died mid-run, see [Checkpoints](#checkpoints)) | merges the input and continues from the last finished step | continues after the last finished step, **without** merging the input again |
169
173
 
170
174
  A retry of an earlier request (not the latest) is found with the store's optional
171
175
  `findRequest` and its outcome is returned; it never runs again, since later turns have
@@ -187,6 +191,106 @@ Notes:
187
191
  - `sessionId` and `requestId` must be non-empty strings when given; otherwise `agent.run()`
188
192
  throws `InvalidRunResourceError`.
189
193
 
194
+ ### Checkpoints
195
+
196
+ By default a session is saved only when a run settles. If the instance dies in the middle of a
197
+ long turn, the next run starts the whole turn again, and the side effects of the steps that
198
+ already finished happen again. With `checkpoint: 'step'`, the session is also saved after every
199
+ finished top-level step:
200
+
201
+ ```ts
202
+ const agent = createAgent('agent', h, {}, { checkpoint: 'step' }) // every run of this agent
203
+ agent.run(input, { sessionId, requestId, checkpoint: 'off' }) // or per call: 'off' | 'step'
204
+ ```
205
+
206
+ ```
207
+ turn ── a ── save ── b ── save ── c ──✕ crash
208
+ ↑
209
+ next run ─────────┘ continues from c; a and b do not run again
210
+ ```
211
+
212
+ - Each checkpoint is a new stored run with `phase: 'running'`, `step` = the next step to run,
213
+ and the state after the finished step. `agent.status()` reports it as
214
+ `{ phase: 'running', step, instanceId?, checkpointedAt }`.
215
+ - Running the session again continues from that step, like a stopped run (see the table above):
216
+ a retry with the same `requestId` does not merge its input again. With a store that supports
217
+ claims, another instance gets `SessionBusyError` while the owner's claim is alive, and can take
218
+ over once it expires.
219
+ - Steps run **at least once**: only the step that was running when the instance died runs
220
+ again, so only that step needs to be idempotent.
221
+ - A fork is saved as a whole: there is a checkpoint before it and after it, none inside. A crash
222
+ inside a fork runs the whole fork again.
223
+ - After each checkpoint the claim is extended. A single step that runs longer than the claim TTL
224
+ still needs `ctx.keepAlive()`.
225
+ - `onCheckpoint(ctx, { step, version, durationMs })` on an [observer](#observers) reports each
226
+ saved checkpoint. A checkpoint that is skipped (its state can't be copied with
227
+ `structuredClone`, or the store failed) is reported to `events.onStoreError` with phase
228
+ `'checkpoint'`, and the run goes on.
229
+ - When the store reports that another writer saved the session first, or that this instance's
230
+ claim was taken over, the run stops at once with `LeaseExpiredError` (reported with phase
231
+ `'claim'`) and saves nothing more. Detecting a taken-over claim needs a store that checks
232
+ `SaveOptions.lease` on `save()` (see [Fencing](#fencing)).
233
+ - Without a session store the option does nothing. Each checkpoint is one store write per step.
234
+
235
+ Limits:
236
+ - Nothing in core looks for runs whose instance died. The session continues only when something
237
+ runs it again: a retry with the same `requestId`, a queue that redelivers the request, or your
238
+ own sweep over sessions whose `agent.status()` is `'running'`.
239
+ - Every checkpoint is a stored run, and none are removed: `loadHistory()` grows by one run per
240
+ finished step. Filter by `phase` when you read it.
241
+ - `branch()` starts from the last stored run of a `runId`. It can't start from an earlier
242
+ checkpoint of that run.
243
+ - It is all top-level steps or none; you can't pick which steps save.
244
+ - A step has no fencing token to pass to other systems. A stale instance is stopped at its next
245
+ save to the session store, but what its step already wrote elsewhere stays written.
246
+ - If a save reached the store but the call still failed (a timeout, a dropped connection), the
247
+ next checkpoint conflicts with it and the run stops with `LeaseExpiredError`. The next run
248
+ continues from that save; steps still run at least once.
249
+
250
+ > **Upgrading.**
251
+ > - `StoredRun.phase` and `SessionPhase` have a new value, `'running'`. A `switch` that handles
252
+ > every phase needs a case for it.
253
+ > - `checkpoint` is now a reserved run resource. A `runtime()` slot named `checkpoint` no longer
254
+ > reaches `ctx`, and the value passed under that key must be `'off'` or `'step'`. Rename the slot.
255
+ > - `SessionStore.save` takes an optional fourth argument, `SaveOptions`. Existing stores keep
256
+ > working, without [fencing](#fencing).
257
+ > - With a store that fences, a run whose final save is rejected because it lost its claim now
258
+ > settles `$error` with `LeaseExpiredError`. Before, it returned its result although the store
259
+ > did not keep it.
260
+
261
+ ### Fencing
262
+
263
+ With a store that supports claims, a run holds a lease on its session. A lease can be lost while
264
+ the run still works: the claim expires (a step ran longer than the claim TTL without
265
+ `ctx.keepAlive()`), and another instance claims the session and runs it. The first instance is
266
+ now stale. Without fencing, its next save would overwrite the new owner's progress.
267
+
268
+ Core already stops a run at the next step boundary once its lease has expired by the local clock.
269
+ Fencing covers what that check can't see: a save made before that boundary, and clocks that differ
270
+ between instances.
271
+
272
+ So every save a run makes while it holds a claim (each checkpoint, the final save, and writing an
273
+ interrupt answer on `resume()`) passes the lease as `SaveOptions.lease`. A store that fences
274
+ checks, in the same atomic step as the version check, that the session's claim is still that
275
+ lease. If not, it writes nothing and throws an error named `LeaseLostError`.
276
+
277
+ ```
278
+ A (stale): save {lease A} ──► store: claim is now B's → LeaseLostError → A stops, $error
279
+ B (owner): save {lease B} ──► store: claim ok, version ok → saved
280
+ ```
281
+
282
+ What the caller of the stale run sees:
283
+ - The run settles `signal: '$error'` with `LeaseExpiredError` in `state.$error`. Its `cause` is the
284
+ store's `LeaseLostError`. `events.onStoreError` gets the same `LeaseExpiredError` with phase
285
+ `'claim'`.
286
+ - Nothing more is saved for that run.
287
+ - Any other error from the final save is still only reported (phase `'persist'`), and the run
288
+ keeps its signal.
289
+
290
+ `PostgresSessionStore` and `RedisSessionStore` fence. `InMemorySessionStore` and
291
+ `LocalFileSessionStore` have no claims, so there is nothing to fence. A custom store that ignores
292
+ `SaveOptions` keeps only the version check.
293
+
190
294
  ### Interrupts
191
295
 
192
296
  A run can be stopped or resumed:
@@ -404,6 +508,7 @@ call uses it once. Hook errors go to the per-call `onObserverError`, else the ag
404
508
  | `onStepSettled(ctx, event)` | once per step, when its outcome and destination are final |
405
509
  | `wrap(ctx, scope, fn)` | around a step's `run`, and around calls made with `ctx.within` |
406
510
  | `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)) |
511
+ | `onCheckpoint(ctx, { step, version, durationMs })` | after a checkpoint was saved, before `step` starts; top-level runs with `checkpoint: 'step'` only (see [Checkpoints](#checkpoints)) |
407
512
 
408
513
  Step hooks receive a `StepContext`: `agentId`, `sessionId`, `runId`, `stepName` and, inside a
409
514
  fork branch, `branchPath`. `runId` is the same value as `RunContext.runId`. Steps inside a branch
@@ -547,29 +652,33 @@ The harness never trusts a wrapper. `fn` always runs exactly once, and the calle
547
652
  | Export | Description |
548
653
  |---|---|
549
654
  | `createHarness<Ctx>()(schema)` | Creates a harness. Fixes `Ctx`, infers `State` from schema. |
550
- | `createAgent(id, h, slots, options?)` | Assigns the agent an ID and fills `required()` slots. `options` (`AgentOptions`) takes `instanceId`, `stores` (for `required()` store slots), `claimOptions` (this agent's default claim TTL; per-call `claimOptions` wins, then `HARNESS_CLAIM_TTL_MS`, then 30 000 ms), `observer` (for every run of the agent; see [Observers](#observers)) and `onObserverError` (the default sink for hook errors). Returns an `Agent`. |
655
+ | `createAgent(id, h, slots, options?)` | Assigns the agent an ID and fills `required()` slots. `options` (`AgentOptions`) takes `instanceId`, `stores` (for `required()` store slots), `claimOptions` (this agent's default claim TTL; per-call `claimOptions` wins, then `HARNESS_CLAIM_TTL_MS`, then 30 000 ms), `observer` (for every run of the agent; see [Observers](#observers)), `onObserverError` (the default sink for hook errors) and `checkpoint` (`'off'` or `'step'`; see [Checkpoints](#checkpoints)). Returns an `Agent`. |
551
656
  | `field<T>(opts)` | Declares a state field with a default and optional reduce function. |
552
657
  | `required()` | Marks a provider slot as required at `createAgent()`. |
553
658
  | `runtime()` | Marks a provider slot as required at `agent.run()`. |
554
659
  | `composeObservers([a, b], onObserverError?)` | Merges multiple `Observer` instances into one fan-out observer; a throwing observer is isolated and reported to `onObserverError`. |
555
660
  | `SessionStore` | Interface for session persistence backends. Optional extensions: `loadHistory`, `branch`, `claim` / `release` / `extendClaim`, and `findRequest` (the newest run of a request, for retries of earlier requests). |
556
- | `StoredRun` | Type for a persisted run snapshot. Includes `agentId`, `runId`, `sessionId`, `phase`, state, and optionally `requestId` (the run's turn), `carrier` (the run's `carry` result) and `branchedFrom` (on a branch seed). |
661
+ | `SaveOptions` | Last argument of `SessionStore.save`: `{ lease? }`, the lease the caller holds. A store that supports claims rejects a save whose lease is no longer the session's claim with an error named `LeaseLostError`. |
662
+ | `CheckpointMode` | `'off' \| 'step'` — the `checkpoint` option and resource. |
663
+ | `StoredRun` | Type for a persisted run snapshot. Includes `agentId`, `runId`, `sessionId`, `phase` (`'paused'`, `'completed'` or `'running'` for a checkpoint), state, and optionally `requestId` (the run's turn), `carrier` (the run's `carry` result) and `branchedFrom` (on a branch seed). |
557
664
  | `RunRef` | `{ runId, carrier? }` — points at an earlier run; used by `RunContext.resumedFrom`, `continuedFrom` and `branchedFrom` (which adds `sessionId`). |
558
665
  | `RunCarrier` | `Readonly<Record<string, string>>` — the opaque map an observer's `carry` returns. |
559
666
  | `Observer` | Interface for telemetry hooks on run and step lifecycle events (see [Observers](#observers)). |
560
667
  | `StepSettledEvent` | Payload of `Observer.onStepSettled` — outcome, duration, update, signal, next, error, interrupt, fork. |
668
+ | `CheckpointEvent` | Payload of `Observer.onCheckpoint` — `step`, `version`, `durationMs`. |
561
669
  | `StepSettledNext` | Where the run goes after a step settles: `step`, `end`, `pause`, or `throw`. |
562
670
  | `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`. |
563
671
  | `TelemetryContext` | What `withTelemetry` receives: `observer` (the run's observer, fault-isolated), `stepContext`, and `within(type, payload, fn)` bound to that step. |
564
672
  | `WrapScope` | What `Observer.wrap` is asked to enclose: `{ kind: 'step' }` or `{ kind: 'event', type, payload }`. |
565
673
  | `RunContext` | Context passed to run-level observer hooks — `agentId`, `sessionId`, `runId`, `parentRunId?`, `instanceId?`, `branchPath?`, `resumedFrom?`, `branchedFrom?`, `continuedFrom?`, `requestId?` (see [Linking runs](#linking-runs)). |
566
674
  | `StepContext` | Context passed to step-level observer hooks — `agentId`, `sessionId`, `runId`, `stepName`, `branchPath?`. |
567
- | `InvalidRunResourceError` | Thrown by `agent.run()` when `sessionId` or `requestId` is given but is not a non-empty string; `key` and `value` name it. |
675
+ | `InvalidRunResourceError` | Thrown by `agent.run()` when `sessionId` or `requestId` is given but is not a non-empty string, and by `run()`, `debug()` and `resume()` when `checkpoint` is not `'off'` or `'step'`; `key` and `value` name it. |
568
676
  | `NoInterruptError` | Thrown when `resume()` is called but the session is not paused on a matching interrupt. |
569
677
  | `SessionInFlightError` | Thrown when a session is already running. |
570
678
  | `SessionPendingInterruptError` | `agent.run()` on a session paused on a pending interrupt — use `agent.resume()` instead. With a session store, the run resolves with `signal: '$error'` and this error in `state.$error`; without one, `agent.run()` throws it. |
571
679
  | `StoreLoadError` | Thrown when the session store fails to load state. |
572
- | `InvalidAgentOptionError` | Thrown by `createAgent()` when `observer` is not an object or `onObserverError` is not a function; `key` and `value` name it. |
680
+ | `LeaseExpiredError` | In `state.$error` when a run lost its claim: the lease expired, or the store rejected a save because the session's claim is no longer this run's lease (see [Fencing](#fencing)). `sessionId`; `cause` holds the store's error when there is one. |
681
+ | `InvalidAgentOptionError` | Thrown by `createAgent()` when `observer` is not an object, `onObserverError` is not a function, or `checkpoint` is not `'off'` or `'step'`; `key` and `value` name it. |
573
682
  | `InvalidClaimOptionsError` | Thrown by `createAgent()` when `claimOptions.ttlMs` is not a finite number greater than 0. |
574
683
  | `DebugSession` | Returned by `agent.debug()`: a run driven step by step (see [Debugging a run](#debugging-a-run)). |
575
684
  | `DebugStop` | Where a debug session stopped: `step`, `interrupt` or `end`. |
package/dist/index.d.ts CHANGED
@@ -339,6 +339,22 @@ interface Observer {
339
339
  * `onObserverError` (hook name `'carry'`) and counts as `undefined`.
340
340
  */
341
341
  carry?: (ctx: RunContext) => Record<string, string> | undefined;
342
+ /**
343
+ * Called after a checkpoint was saved (`checkpoint: 'step'`): the session can now continue
344
+ * from `event.step` if this instance dies. Not called when a checkpoint was skipped or failed —
345
+ * those go to `onStoreError` with phase `'checkpoint'` (or `'claim'` when the run lost the
346
+ * session). Top-level runs only.
347
+ */
348
+ onCheckpoint?: (ctx: RunContext, event: CheckpointEvent) => void;
349
+ }
350
+ /** What {@link Observer.onCheckpoint} gets. */
351
+ interface CheckpointEvent {
352
+ /** The step the session continues from. */
353
+ readonly step: string;
354
+ /** The version of the saved row. */
355
+ readonly version: number;
356
+ /** Time spent on the checkpoint: snapshot, save and lease extension. */
357
+ readonly durationMs: number;
342
358
  }
343
359
  /**
344
360
  * The telemetry a resource needs to attribute an `invoke()` call to the correct
@@ -485,6 +501,24 @@ interface Lease {
485
501
  */
486
502
  readonly token: unknown;
487
503
  }
504
+ /**
505
+ * `'step'`: save the session after every finished top-level step, so a run whose instance dies
506
+ * continues from its last finished step. `'off'`: save only when a run settles.
507
+ */
508
+ type CheckpointMode = 'off' | 'step';
509
+ /** Options passed to {@link SessionStore.save}. */
510
+ interface SaveOptions {
511
+ /**
512
+ * The lease the caller holds on the session. When given, a store that supports claims
513
+ * must check — atomically with the version check — that the session's current claim is
514
+ * the one this lease was issued for (by the store's own claim identity, e.g. the nonce in
515
+ * `lease.token`; never by comparing times). If it is not, write nothing and throw an
516
+ * `Error` whose `name` is `'LeaseLostError'`, whatever the version: this check takes
517
+ * precedence over the version check, because the framework treats a version conflict on a
518
+ * run's last save as a failed write and carries on.
519
+ */
520
+ readonly lease?: Lease;
521
+ }
488
522
  /**
489
523
  * Operational metadata written by the framework into every {@link StoredRun}.
490
524
  *
@@ -534,8 +568,15 @@ interface SessionStore {
534
568
  * record and `run.version === 0`), the write succeeds.
535
569
  * - Otherwise, another writer committed a newer version concurrently — the
536
570
  * implementation must throw `ConcurrentModificationError`.
571
+ *
572
+ * The framework passes `options.lease` on every save it makes while holding a claim, and
573
+ * no `options` otherwise. A store that supports claims fences the write with it (see
574
+ * {@link SaveOptions.lease}), so an instance whose lease was taken over cannot overwrite
575
+ * the new owner's progress; the framework stops that run with `LeaseExpiredError`. A store
576
+ * without claims, or one that ignores `options`, keeps the version check only and gives no
577
+ * protection against such a stale owner.
537
578
  */
538
- save(agentId: string, sessionId: string, run: StoredRun): Promise<void>;
579
+ save(agentId: string, sessionId: string, run: StoredRun, options?: SaveOptions): Promise<void>;
539
580
  /**
540
581
  * Return the full ordered run history for a session, oldest first.
541
582
  * Optional — omit if your store does not support history.
@@ -543,7 +584,8 @@ interface SessionStore {
543
584
  loadHistory?(agentId: string, sessionId: string): Promise<StoredRun[]>;
544
585
  /**
545
586
  * Fork the session at the state captured by `runId`, returning a new session ID
546
- * whose initial state equals the forked run's `finalState`.
587
+ * whose initial state equals the forked run's `finalState`. When a run has several records,
588
+ * use its last one.
547
589
  * Optional — omit if your store does not support branching.
548
590
  *
549
591
  * Save the new session's version-0 record as a seed the next run continues: `phase: 'paused'`,
@@ -610,6 +652,9 @@ interface SessionStore {
610
652
  * existed is still a valid `Cursor`, so old rows load and resume unchanged.
611
653
  * - `phase: 'completed'` — the loop exited normally; `signal` holds the exit
612
654
  * signal (or is absent when the loop ended without emitting a signal).
655
+ * - `phase: 'running'` — a checkpoint, written after a finished step when checkpoints are on
656
+ * (`checkpoint: 'step'`); `step` is the step to run next, always a plain step name, and there
657
+ * is no `signal`. The run has not settled since: it is still running, or its instance died.
613
658
  */
614
659
  interface StoredRun {
615
660
  readonly agentId: string;
@@ -618,7 +663,7 @@ interface StoredRun {
618
663
  readonly version: number;
619
664
  readonly startedAt: string;
620
665
  readonly settledAt: string;
621
- readonly phase: 'paused' | 'completed';
666
+ readonly phase: 'paused' | 'completed' | 'running';
622
667
  readonly initialState: Record<string, unknown>;
623
668
  readonly finalState: Record<string, unknown>;
624
669
  readonly signal?: string;
@@ -661,6 +706,9 @@ interface StoredRun {
661
706
  * step that issued the interrupt. When the run paused inside a fork, `step` is
662
707
  * the fork's own name (not the richer per-branch detail — see `pendingInterrupts`).
663
708
  * - `'completed'` — the last run exited the loop; `signal` is the exit signal.
709
+ * - `'running'` — the last run saved a checkpoint and has not settled since: it is still
710
+ * running, or its instance died. Running the session again continues it from `step`
711
+ * (another instance gets `SessionBusyError` while the owner's claim is alive).
664
712
  */
665
713
  type SessionPhase = {
666
714
  readonly phase: 'fresh';
@@ -681,6 +729,14 @@ type SessionPhase = {
681
729
  } | {
682
730
  readonly phase: 'completed';
683
731
  readonly signal?: string;
732
+ } | {
733
+ readonly phase: 'running';
734
+ /** The step the session continues from. */
735
+ readonly step: string;
736
+ /** The `instanceId` of the instance that saved the checkpoint, when it had one. */
737
+ readonly instanceId?: string;
738
+ /** When the checkpoint was saved (ISO 8601). */
739
+ readonly checkpointedAt: string;
684
740
  };
685
741
  /** One outstanding `ctx.interrupt()` call awaiting a response. See {@link SessionPhase}. */
686
742
  interface PendingInterrupt$1 {
@@ -1256,6 +1312,13 @@ interface AgentOptions {
1256
1312
  * Must be a function; otherwise `createAgent` throws `InvalidAgentOptionError`.
1257
1313
  */
1258
1314
  readonly onObserverError?: ObserverErrorSink;
1315
+ /**
1316
+ * Save the session after every finished top-level step, so a run whose instance dies continues
1317
+ * from its last finished step instead of re-running the turn: `'step'`. `'off'` (the default)
1318
+ * saves only when a run settles. Ignored without a session store. A per-call `checkpoint`
1319
+ * resource overrides it. Any other value makes `createAgent` throw `InvalidAgentOptionError`.
1320
+ */
1321
+ readonly checkpoint?: CheckpointMode;
1259
1322
  }
1260
1323
  interface Agent {
1261
1324
  /** The agent's unique identifier, as provided to createAgent(). */
@@ -1308,21 +1371,32 @@ declare class InvalidClaimOptionsError extends Error {
1308
1371
  readonly ttlMs: unknown;
1309
1372
  constructor(ttlMs: unknown);
1310
1373
  }
1311
- /** Thrown by {@link createAgent} when `agentOptions.observer` is not an object or `agentOptions.onObserverError` is not a function. */
1374
+ declare const AGENT_OPTION_EXPECTED: {
1375
+ readonly observer: "an object";
1376
+ readonly onObserverError: "a function";
1377
+ readonly checkpoint: "'off' or 'step'";
1378
+ };
1379
+ /**
1380
+ * Thrown by {@link createAgent} when `agentOptions.observer` is not an object, `agentOptions.onObserverError`
1381
+ * is not a function, or `agentOptions.checkpoint` is not `'off'` or `'step'`.
1382
+ */
1312
1383
  declare class InvalidAgentOptionError extends Error {
1313
1384
  /** The option that was rejected. */
1314
- readonly key: 'observer' | 'onObserverError';
1385
+ readonly key: keyof typeof AGENT_OPTION_EXPECTED;
1315
1386
  /** The rejected value, as given. */
1316
1387
  readonly value: unknown;
1317
- constructor(key: 'observer' | 'onObserverError', value: unknown);
1388
+ constructor(key: keyof typeof AGENT_OPTION_EXPECTED, value: unknown);
1318
1389
  }
1319
- /** Thrown by `agent.run()` when `sessionId` or `requestId` is given but is not a non-empty string. */
1390
+ /**
1391
+ * Thrown by `agent.run()` when `sessionId` or `requestId` is given but is not a non-empty string, and by
1392
+ * `agent.run()`, `agent.debug()` and `agent.resume()` when `checkpoint` is given but is not `'off'` or `'step'`.
1393
+ */
1320
1394
  declare class InvalidRunResourceError extends Error {
1321
1395
  /** The resource key that was rejected. */
1322
- readonly key: 'sessionId' | 'requestId';
1396
+ readonly key: 'sessionId' | 'requestId' | 'checkpoint';
1323
1397
  /** The rejected value, as given. */
1324
1398
  readonly value: unknown;
1325
- constructor(key: 'sessionId' | 'requestId', value: unknown);
1399
+ constructor(key: 'sessionId' | 'requestId' | 'checkpoint', value: unknown);
1326
1400
  }
1327
1401
  /**
1328
1402
  * Instantiate an agent from a fully-configured {@link Harness}.
@@ -1417,7 +1491,13 @@ declare class SessionBusyError extends Error {
1417
1491
  declare class LeaseExpiredError extends Error {
1418
1492
  /** The session whose lease expired. */
1419
1493
  readonly sessionId: string;
1420
- constructor(sessionId: string);
1494
+ /**
1495
+ * The store's error when the store rejected a save: its `LeaseLostError` (the session's
1496
+ * claim is no longer this run's lease), or, on a checkpoint, its `ConcurrentModificationError`
1497
+ * (another writer saved first). Absent when the expiry was seen on the local clock.
1498
+ */
1499
+ readonly cause?: unknown;
1500
+ constructor(sessionId: string, cause?: unknown);
1421
1501
  }
1422
1502
 
1423
1503
  /**
@@ -1431,4 +1511,4 @@ declare class NoInterruptError extends Error {
1431
1511
  constructor();
1432
1512
  }
1433
1513
 
1434
- export { type Agent, type AgentOptions, type BranchCursor, type BranchDef, type ClaimOptions, type Cursor, type DebugBreakpoint, DebugCommandError, type DebugFrame, type DebugSession, type DebugStop, type DeepWithMarkers, type EndedFrame, type FieldDefinition, type ForkCursor, type ForkDef, type FrameworkState, type Harness, InvalidAgentOptionError, InvalidClaimOptionsError, InvalidRunResourceError, type Lease, LeaseExpiredError, type LoopDefinition, type LoopNode, LoopNotDefinedError, NoInterruptError, type Observer, type ObserverAware, type ObserverErrorContext, type ObserverErrorSink, type PendingInterrupt, REQUIRED_TAG, RUNTIME_TAG, type RequiredMarker, type RouteFn, type RunCarrier, type RunContext, type RunFn, type RunRef, type RuntimeMarker, SessionBusyError, SessionInDebugError, SessionInFlightError, SessionPendingInterruptError, type SessionStore, type SettledStep, type SignalTransition, type Snapshot, 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 };
1514
+ export { type Agent, type AgentOptions, type BranchCursor, type BranchDef, type CheckpointEvent, type CheckpointMode, type ClaimOptions, type Cursor, type DebugBreakpoint, DebugCommandError, type DebugFrame, type DebugSession, type DebugStop, type DeepWithMarkers, type EndedFrame, type FieldDefinition, type ForkCursor, type ForkDef, type FrameworkState, type Harness, InvalidAgentOptionError, InvalidClaimOptionsError, InvalidRunResourceError, type Lease, LeaseExpiredError, type LoopDefinition, type LoopNode, LoopNotDefinedError, NoInterruptError, type Observer, type ObserverAware, type ObserverErrorContext, type ObserverErrorSink, type PendingInterrupt, REQUIRED_TAG, RUNTIME_TAG, type RequiredMarker, type RouteFn, type RunCarrier, type RunContext, type RunFn, type RunRef, type RuntimeMarker, type SaveOptions, SessionBusyError, SessionInDebugError, SessionInFlightError, SessionPendingInterruptError, type SessionStore, type SettledStep, type SignalTransition, type Snapshot, 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 };