@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 +119 -10
- package/dist/index.d.ts +91 -11
- package/dist/index.js +137 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
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))
|
|
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
|
-
| `
|
|
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
|
|
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
|
-
| `
|
|
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
|
-
|
|
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:
|
|
1385
|
+
readonly key: keyof typeof AGENT_OPTION_EXPECTED;
|
|
1315
1386
|
/** The rejected value, as given. */
|
|
1316
1387
|
readonly value: unknown;
|
|
1317
|
-
constructor(key:
|
|
1388
|
+
constructor(key: keyof typeof AGENT_OPTION_EXPECTED, value: unknown);
|
|
1318
1389
|
}
|
|
1319
|
-
/**
|
|
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
|
-
|
|
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 };
|