@noetaris/harness 0.1.0 → 0.2.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/dist/index.d.ts CHANGED
@@ -1,9 +1,27 @@
1
1
  declare const _fieldType: unique symbol;
2
+ /**
3
+ * Descriptor for a single state field, produced by {@link field}.
4
+ *
5
+ * - `default` — factory called once on session start to populate the field when no
6
+ * initial value is supplied.
7
+ * - `reduce` — merge function invoked on resume: `reduce(storedValue, incomingValue)`.
8
+ * When absent, incoming values overwrite stored ones on resume.
9
+ */
2
10
  type FieldDefinition<T> = {
3
11
  readonly [_fieldType]: T;
4
12
  readonly default?: () => T;
5
13
  readonly reduce?: (current: T, update: T) => T;
6
14
  };
15
+ /**
16
+ * Derives the concrete state record type from a schema object whose values are
17
+ * {@link FieldDefinition} descriptors.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const schema = { count: field<number>({ default: () => 0 }) }
22
+ * type State = StateFromSchema<typeof schema> // { count: number }
23
+ * ```
24
+ */
7
25
  type StateFromSchema<S> = {
8
26
  [K in keyof S]: S[K] extends FieldDefinition<infer T> ? T : never;
9
27
  };
@@ -11,25 +29,296 @@ type FieldOptions<T> = {
11
29
  default?: () => T;
12
30
  reduce?: (current: T, update: T) => T;
13
31
  };
32
+ /**
33
+ * Declare a typed state field with an optional default factory and optional
34
+ * reduce function.
35
+ *
36
+ * @param options.default - Factory called once at session start.
37
+ * @param options.reduce - Merge function for resume: `(stored, incoming) => merged`.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const schema = {
42
+ * messages: field<string[]>({ default: () => [], reduce: (a, b) => [...a, ...b] }),
43
+ * }
44
+ * ```
45
+ */
14
46
  declare function field<T>(options?: FieldOptions<T>): FieldDefinition<T>;
15
47
 
48
+ /** @internal Discriminant tag for {@link RequiredMarker}. */
16
49
  declare const REQUIRED_TAG: "__noetaris_required__";
50
+ /** @internal Discriminant tag for {@link RuntimeMarker}. */
17
51
  declare const RUNTIME_TAG: "__noetaris_runtime__";
52
+ /**
53
+ * Sentinel returned by {@link required} to mark a context slot that **must** be
54
+ * supplied at `createAgent()` time (not at `agent.run()` time).
55
+ */
18
56
  type RequiredMarker = {
19
57
  readonly _tag: typeof REQUIRED_TAG;
20
58
  };
59
+ /**
60
+ * Sentinel returned by {@link runtime} to mark a context slot that **must** be
61
+ * supplied at `agent.run()` time (not at `createAgent()` time).
62
+ */
21
63
  type RuntimeMarker = {
22
64
  readonly _tag: typeof RUNTIME_TAG;
23
65
  };
66
+ /**
67
+ * Recursive variant of `T` that allows any nested position to also be a
68
+ * {@link RequiredMarker} or {@link RuntimeMarker}. Used as the `value`
69
+ * parameter type on {@link Harness.provide}.
70
+ */
24
71
  type DeepWithMarkers<T> = T extends (...args: any[]) => any ? T | RequiredMarker | RuntimeMarker : T extends object ? {
25
72
  [K in keyof T]: DeepWithMarkers<T[K]> | RequiredMarker | RuntimeMarker;
26
73
  } | RequiredMarker | RuntimeMarker : T | RequiredMarker | RuntimeMarker;
74
+ /**
75
+ * Return a {@link RequiredMarker} sentinel that instructs the harness to
76
+ * require this slot at `createAgent()` time.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * h.provide('llm', required())
81
+ * ```
82
+ */
27
83
  declare function required(): RequiredMarker;
84
+ /**
85
+ * Return a {@link RuntimeMarker} sentinel that instructs the harness to
86
+ * require this slot at `agent.run()` time instead of `createAgent()` time.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * h.provide('signal', runtime())
91
+ * ```
92
+ */
28
93
  declare function runtime(): RuntimeMarker;
94
+ /**
95
+ * Type guard — returns `true` when `value` is a {@link RequiredMarker}.
96
+ */
29
97
  declare function isRequiredMarker(value: unknown): value is RequiredMarker;
98
+ /**
99
+ * Type guard — returns `true` when `value` is a {@link RuntimeMarker}.
100
+ */
30
101
  declare function isRuntimeMarker(value: unknown): value is RuntimeMarker;
31
102
 
32
- /** Framework fields injected into every step function's state argument. */
103
+ /**
104
+ * Options passed to {@link SessionStore.claim}.
105
+ *
106
+ * `ttlMs` is the initial lease duration in milliseconds. The claim expires
107
+ * if not renewed within this window — enabling crash recovery by other instances.
108
+ */
109
+ interface ClaimOptions {
110
+ /** Lease duration in milliseconds. Must be a positive integer. */
111
+ readonly ttlMs: number;
112
+ }
113
+ /**
114
+ * A successfully acquired distributed lease on a session.
115
+ *
116
+ * Returned by {@link SessionStore.claim} when no other instance holds the
117
+ * session. The framework holds this object for the duration of the run and
118
+ * passes it to {@link SessionStore.release} and {@link SessionStore.extendClaim}.
119
+ */
120
+ interface Lease {
121
+ /**
122
+ * Wall-clock expiry timestamp (milliseconds since epoch).
123
+ * The framework checks `Date.now() >= lease.expiresAt` at each step boundary
124
+ * to detect TTL expiry without contacting the store.
125
+ */
126
+ readonly expiresAt: number;
127
+ /**
128
+ * The `agentId` this lease was issued for.
129
+ * Stored in the claim record for stuck-lease diagnosis.
130
+ */
131
+ readonly agentId: string;
132
+ /**
133
+ * The `sessionId` this lease covers.
134
+ */
135
+ readonly sessionId: string;
136
+ /**
137
+ * The `instanceId` of the holder, if provided to `createAgent()`.
138
+ * Written into the store's claim record for operational diagnostics.
139
+ * Absent when `instanceId` was not configured.
140
+ */
141
+ readonly instanceId?: string;
142
+ /**
143
+ * Store-implementation-specific token — e.g. a Redis key name, a lock token,
144
+ * or a database row ID. Opaque to the framework; passed back unmodified on
145
+ * `release` and `extendClaim` calls so the store can locate the record.
146
+ */
147
+ readonly token: unknown;
148
+ }
149
+ /**
150
+ * Operational metadata written by the framework into every {@link StoredRun}.
151
+ *
152
+ * The framework writes only `instanceId`. All other fields are reserved for
153
+ * domain-specific operational annotations (tenantId, region, requestId, etc.).
154
+ */
155
+ interface StoredRunMetadata {
156
+ /** The `instanceId` of the instance that produced this run. Absent when not configured. */
157
+ instanceId?: string;
158
+ /** Open extension point — domain-specific fields. */
159
+ [key: string]: unknown;
160
+ }
161
+ /**
162
+ * Persistence contract for agent sessions.
163
+ *
164
+ * Implementations must be safe to call concurrently for **different** sessions;
165
+ * concurrent calls for the **same** session are prevented by the harness concurrency guards.
166
+ *
167
+ * `loadHistory` and `branch` are optional extensions:
168
+ * - `loadHistory` — full run history for a session (used by branching and debugging tools).
169
+ * - `branch` — fork a session at a specific past run, returning a new session ID.
170
+ */
171
+ interface SessionStore {
172
+ /**
173
+ * Load the most recent {@link StoredRun} for the given session, or `null` if
174
+ * the session has never been saved.
175
+ */
176
+ load(agentId: string, sessionId: string): Promise<StoredRun | null>;
177
+ /**
178
+ * Persist a run record as a conditional write.
179
+ *
180
+ * Implementations must compare `run.version` against the version of the
181
+ * currently stored record before writing:
182
+ * - If the stored version equals `run.version - 1` (or there is no stored
183
+ * record and `run.version === 0`), the write succeeds.
184
+ * - Otherwise, another writer committed a newer version concurrently — the
185
+ * implementation must throw `ConcurrentModificationError`.
186
+ */
187
+ save(agentId: string, sessionId: string, run: StoredRun): Promise<void>;
188
+ /**
189
+ * Return the full ordered run history for a session, oldest first.
190
+ * Optional — omit if your store does not support history.
191
+ */
192
+ loadHistory?(agentId: string, sessionId: string): Promise<StoredRun[]>;
193
+ /**
194
+ * Fork the session at the state captured by `runId`, returning a new session ID
195
+ * whose initial state equals the forked run's `finalState`.
196
+ * Optional — omit if your store does not support branching.
197
+ *
198
+ * @throws {@link BranchNotFoundError} when `runId` is not found in history.
199
+ */
200
+ branch?(agentId: string, sessionId: string, runId: string): Promise<string>;
201
+ /**
202
+ * Attempt to acquire a distributed claim on the session before starting a run.
203
+ *
204
+ * - Returns a {@link Lease} when the claim is acquired.
205
+ * - Returns `null` when another instance already holds the claim — the framework
206
+ * throws {@link SessionBusyError} immediately without starting any LLM work.
207
+ *
208
+ * Optional — stores that do not support distributed locking omit this method.
209
+ * The framework checks for its presence before calling it.
210
+ */
211
+ claim?(agentId: string, sessionId: string, options: ClaimOptions): Promise<Lease | null>;
212
+ /**
213
+ * Release a held claim after the run settles.
214
+ *
215
+ * Called by the framework after `save()` completes (or after an error that
216
+ * prevents saving). Stores may use this to remove the lock record immediately
217
+ * rather than waiting for TTL expiry.
218
+ *
219
+ * Optional — omit if your store does not support claim/release.
220
+ */
221
+ release?(lease: Lease): Promise<void>;
222
+ /**
223
+ * Extend the TTL of an active claim.
224
+ *
225
+ * Called by `ctx.keepAlive()` (both one-shot and background interval modes).
226
+ * The store must update `expiresAt` on the claim record; the new expiry is
227
+ * `Date.now() + options.ttlMs`. The returned `Lease` object carries the updated
228
+ * `expiresAt`; the framework replaces its held reference with the returned value.
229
+ *
230
+ * Optional — omit if your store does not support claim/release.
231
+ */
232
+ extendClaim?(lease: Lease, options: ClaimOptions): Promise<Lease>;
233
+ }
234
+ /**
235
+ * Serializable snapshot of a single agent run, written to the store after
236
+ * every run settles (either `'paused'` on an interrupt or `'completed'`).
237
+ *
238
+ * - `phase: 'paused'` — the run paused on an interrupt; `step` is the step that
239
+ * issued the interrupt and `signal` is `'$interrupt'`.
240
+ * - `phase: 'completed'` — the loop exited normally; `signal` holds the exit
241
+ * signal (or is absent when the loop ended without emitting a signal).
242
+ */
243
+ interface StoredRun {
244
+ readonly agentId: string;
245
+ readonly runId: string;
246
+ readonly sessionId: string;
247
+ readonly version: number;
248
+ readonly startedAt: string;
249
+ readonly settledAt: string;
250
+ readonly phase: 'paused' | 'completed';
251
+ readonly initialState: Record<string, unknown>;
252
+ readonly finalState: Record<string, unknown>;
253
+ readonly signal?: string;
254
+ readonly step?: string;
255
+ /**
256
+ * Operational metadata written by the framework.
257
+ *
258
+ * The framework writes `instanceId` when configured; all other fields are
259
+ * domain-defined. Absent for runs produced before this field was added.
260
+ */
261
+ readonly metadata?: StoredRunMetadata;
262
+ }
263
+ /**
264
+ * Discriminated union returned by {@link Agent.status} describing the lifecycle
265
+ * phase of a session.
266
+ *
267
+ * - `'fresh'` — no run has been stored yet.
268
+ * - `'in-flight'` — a run is currently executing (in-process guard only; not
269
+ * detectable cross-process from the store alone).
270
+ * - `'paused'` — the last run settled on an interrupt; `step` identifies the
271
+ * step that issued the interrupt.
272
+ * - `'completed'` — the last run exited the loop; `signal` is the exit signal.
273
+ */
274
+ type SessionPhase = {
275
+ readonly phase: 'fresh';
276
+ } | {
277
+ readonly phase: 'in-flight';
278
+ readonly step: null;
279
+ } | {
280
+ readonly phase: 'paused';
281
+ readonly signal?: string;
282
+ readonly step: string;
283
+ } | {
284
+ readonly phase: 'completed';
285
+ readonly signal?: string;
286
+ };
287
+
288
+ /**
289
+ * Options for background renewal mode.
290
+ */
291
+ interface KeepAliveOptions {
292
+ /**
293
+ * Renewal interval in milliseconds. A background timer calls
294
+ * `store.extendClaim()` at this cadence.
295
+ * Must be less than the original claim `ttlMs` to ensure renewal
296
+ * reaches the store before expiry.
297
+ */
298
+ readonly every: number;
299
+ }
300
+ /**
301
+ * The stop function returned by `ctx.keepAlive({ every })`.
302
+ * Always call this in a `finally` block to cancel the background timer.
303
+ */
304
+ type StopFn = () => void;
305
+ /**
306
+ * The `ctx.keepAlive` function type injected into the step ctx.
307
+ *
308
+ * - Called with no arguments: one-shot TTL extension. Returns a Promise.
309
+ * - Called with `{ every: ms }`: starts a background interval renewal timer.
310
+ * Returns a `StopFn` synchronously.
311
+ */
312
+ type KeepAliveFn = ((() => Promise<void>) & ((options: KeepAliveOptions) => StopFn));
313
+
314
+ /**
315
+ * Framework-managed fields injected into every step function's `state` argument.
316
+ *
317
+ * - `$error` — the `Error` thrown by the previous step, or `null` if no error occurred.
318
+ * Only populated on the error path; non-error-aware steps never see a non-null value here.
319
+ * - `$interrupt` — set while a step is awaiting a resume response. The `response` field
320
+ * is populated after {@link Agent.resume} is called.
321
+ */
33
322
  interface FrameworkState {
34
323
  readonly $error: Error | null;
35
324
  readonly $interrupt: {
@@ -38,7 +327,10 @@ interface FrameworkState {
38
327
  readonly response?: unknown;
39
328
  } | null;
40
329
  }
41
- /** The full state type visible inside step functions: user state + framework fields. */
330
+ /**
331
+ * The complete state type visible inside {@link RunFn} step functions:
332
+ * the user-defined state `S` merged with {@link FrameworkState}.
333
+ */
42
334
  type StepState<S> = S & FrameworkState;
43
335
  /**
44
336
  * State transformer. Receives the full step state and ctx; returns a partial update
@@ -46,9 +338,13 @@ type StepState<S> = S & FrameworkState;
46
338
  * May be async.
47
339
  */
48
340
  type RunFn<S, Ctx> = (state: StepState<S>, ctx: Ctx & {
341
+ readonly agentId: string;
49
342
  readonly sessionId: string;
343
+ readonly runId: string;
344
+ readonly signal: AbortSignal;
50
345
  readonly interrupt: (prompt: unknown, id?: string) => Promise<unknown>;
51
346
  readonly emit: (name: string, payload?: unknown) => void;
347
+ readonly keepAlive: KeepAliveFn;
52
348
  }) => Promise<Partial<Omit<S, '$error' | '$interrupt'>>> | Partial<Omit<S, '$error' | '$interrupt'>>;
53
349
  /**
54
350
  * Pure signal emitter. Receives step state without $error — the route is not called on the error
@@ -117,72 +413,137 @@ interface LoopBuilder<S, Ctx> {
117
413
  onError(step: string): LoopBuilder<S, Ctx>;
118
414
  }
119
415
 
416
+ /**
417
+ * Immutable builder that accumulates context-slot declarations, store bindings,
418
+ * and the loop definition for an agent. Produced by {@link createHarness} and
419
+ * consumed by {@link createAgent}.
420
+ *
421
+ * The four type parameters track what has been declared so far:
422
+ * - `Ctx` — the full context shape expected by step functions.
423
+ * - `State` — the state record shape derived from the schema passed to `createHarness()`.
424
+ * - `Req` — keys declared as {@link required} (must be supplied to `createAgent`).
425
+ * - `Run` — keys declared as {@link runtime} (must be supplied to `agent.run()`).
426
+ *
427
+ * Each call returns a **new** `Harness` instance; the original is unchanged.
428
+ *
429
+ * @example
430
+ * ```ts
431
+ * const h = createHarness<Ctx>()(schema)
432
+ * .provide('llm', required())
433
+ * .provide('model', 'claude-3-5-haiku-20241022')
434
+ * .store({ session: new InMemorySessionStore() })
435
+ * .loop(l => {
436
+ * l.start().step('run', { run, route }).on('done').end()
437
+ * })
438
+ * ```
439
+ */
120
440
  type Harness<Ctx, State, Req extends keyof Ctx = never, Run extends keyof Ctx = never> = {
441
+ /**
442
+ * Declare a context slot.
443
+ *
444
+ * - Pass {@link required} to require the value at `createAgent()` time.
445
+ * - Pass {@link runtime} to require the value at `agent.run()` time.
446
+ * - Pass any concrete value (or a deep object mixing in markers) to bind it now.
447
+ */
121
448
  provide<K extends keyof Ctx>(key: K, value: RequiredMarker): Harness<Ctx, State, Req | K, Run>;
122
449
  provide<K extends keyof Ctx>(key: K, value: RuntimeMarker): Harness<Ctx, State, Req, Run | K>;
123
450
  provide<K extends keyof Ctx>(key: K, value: DeepWithMarkers<Ctx[K]>): Harness<Ctx, State, Req, Run>;
451
+ /**
452
+ * Bind session store(s). Pass `{ session: myStore }` to enable persistence and
453
+ * interrupt/resume across processes.
454
+ */
124
455
  store(stores: DeepWithMarkers<{
125
456
  session?: unknown;
126
457
  } & Record<string, unknown>>): Harness<Ctx, State, Req, Run>;
458
+ /**
459
+ * Define the execution loop using the {@link LoopBuilder} DSL.
460
+ *
461
+ * @param builder - Callback that receives a `LoopBuilder` and uses it to
462
+ * declare steps, transitions, and the entry point.
463
+ */
127
464
  loop(builder: (l: LoopBuilder<State, Ctx>) => void): Harness<Ctx, State, Req, Run>;
128
465
  };
466
+ /**
467
+ * Create a harness builder for the given context type.
468
+ *
469
+ * Call the returned function (optionally with a state schema) to get the
470
+ * {@link Harness} builder on which you chain `.provide()`, `.store()`, and `.loop()`.
471
+ *
472
+ * @example
473
+ * ```ts
474
+ * interface Ctx { llm: LLM; model: string }
475
+ * const schema = { messages: field<string[]>({ default: () => [] }) }
476
+ *
477
+ * const h = createHarness<Ctx>()(schema)
478
+ * ```
479
+ */
129
480
  declare function createHarness<Ctx = any>(): <S extends object = {}>(// any: allows createHarness() without an explicit Ctx type parameter
130
481
  stateSchema?: S) => Harness<Ctx, StateFromSchema<S>>;
131
482
 
132
- interface SessionStore {
133
- load(sessionId: string): Promise<StoredRun | null>;
134
- save(sessionId: string, run: StoredRun): Promise<void>;
135
- loadHistory?(sessionId: string): Promise<StoredRun[]>;
136
- branch?(sessionId: string, runId: string): Promise<string>;
137
- }
138
- interface StoredRun {
139
- readonly runId: string;
140
- readonly sessionId: string;
141
- readonly startedAt: string;
142
- readonly settledAt: string;
143
- readonly phase: 'paused' | 'completed';
144
- readonly initialState: Record<string, unknown>;
145
- readonly finalState: Record<string, unknown>;
146
- readonly signal?: string;
147
- readonly step?: string;
148
- }
149
- type SessionPhase = {
150
- readonly phase: 'fresh';
151
- } | {
152
- readonly phase: 'in-flight';
153
- readonly step: null;
154
- } | {
155
- readonly phase: 'paused';
156
- readonly signal?: string;
157
- readonly step: string;
158
- } | {
159
- readonly phase: 'completed';
160
- readonly signal?: string;
161
- };
162
-
483
+ /**
484
+ * The resolved value of a {@link RunHandle} promise: the final state and the
485
+ * signal that caused the loop to exit.
486
+ *
487
+ * `signal` is `null` when the loop exited via `.end()` without an explicit signal,
488
+ * `'$interrupt'` when paused on an interrupt, or any custom signal string returned
489
+ * by a route function.
490
+ */
163
491
  interface RunOutcome {
164
492
  readonly state: Record<string, unknown>;
165
493
  readonly signal: string | null;
166
494
  }
495
+ /**
496
+ * Handle returned synchronously by {@link Agent.run} and {@link Agent.resume}.
497
+ *
498
+ * Implements `PromiseLike<RunOutcome>` so it can be `await`-ed directly.
499
+ *
500
+ * @example
501
+ * ```ts
502
+ * const handle = agent.run({}, { llm })
503
+ * console.log(handle.sessionId) // available immediately
504
+ * const { state, signal } = await handle
505
+ * ```
506
+ */
167
507
  interface RunHandle extends PromiseLike<RunOutcome> {
168
508
  /** Cancel the in-flight run at the next safe point between steps. Idempotent. */
169
509
  stop(): void;
170
510
  /**
171
- * Provide a response to a pending ctx.interrupt() call.
172
- * Returns a new RunHandle for the resumed execution.
173
- * Stub in F8 — implemented in F9.
511
+ * Provide a response to a pending `ctx.interrupt()` call and start a new run.
512
+ * Returns a new {@link RunHandle} for the resumed execution.
513
+ *
514
+ * Throws {@link NoInterruptError} when the run has not settled on `signal: '$interrupt'`.
515
+ *
516
+ * @param response - The value to deliver as the interrupt response.
517
+ * @param interruptId - The `interruptId` from `state.$interrupt`.
174
518
  */
175
519
  resume(response: unknown, interruptId: string): RunHandle;
176
520
  /** The session identity for this run. */
177
521
  readonly sessionId: string;
522
+ /**
523
+ * Unique identifier for this specific invocation.
524
+ * One UUID per `agent.run()` or `agent.resume()` call.
525
+ */
526
+ readonly runId: string;
178
527
  /**
179
528
  * The name of the step currently executing in this process.
180
- * null before the first step runs, after execution settles, and always when inspected cross-process.
529
+ * `null` before the first step runs, after execution settles, and always
530
+ * when inspected cross-process.
181
531
  */
182
532
  readonly currentStep: string | null;
183
533
  }
184
534
 
535
+ /** Options passed to createAgent(). */
536
+ interface AgentOptions {
537
+ /**
538
+ * Stable identifier for the process or replica running this agent instance.
539
+ * Written to `StoredRun.metadata` on every save and propagated to observers
540
+ * via `RunContext.instanceId`.
541
+ */
542
+ readonly instanceId?: string;
543
+ }
185
544
  interface Agent {
545
+ /** The agent's unique identifier, as provided to createAgent(). */
546
+ readonly id: string;
186
547
  /**
187
548
  * Start a new run. Returns a RunHandle synchronously before execution begins.
188
549
  */
@@ -191,29 +552,183 @@ interface Agent {
191
552
  * Cross-process entry point for responding to a pending interrupt.
192
553
  * Returns a RunHandle synchronously; the execution promise performs the resume.
193
554
  */
194
- resume(response: unknown, sessionId: string, interruptId: string): RunHandle;
555
+ resume(response: unknown, sessionId: string, interruptId: string, options?: {
556
+ events?: {
557
+ onStoreError?: (error: unknown, phase: 'load' | 'persist' | 'claim') => void;
558
+ };
559
+ }): RunHandle;
195
560
  /**
196
561
  * Query the session store for the current phase of a session.
197
562
  */
198
563
  status(sessionId: string): Promise<SessionPhase>;
199
564
  }
200
- declare function createAgent<Ctx, State, Req extends keyof Ctx, Run extends keyof Ctx>(h: Harness<Ctx, State, Req, Run>, slots: Pick<Ctx, Req>): Agent;
565
+ /**
566
+ * Instantiate an agent from a fully-configured {@link Harness}.
567
+ *
568
+ * Validates that all `required()` slots are present in `slots`, that no
569
+ * `runtime()` slots are passed here, and that the harness has a loop defined.
570
+ *
571
+ * @param id - A stable, human-readable identifier for this agent (used as the
572
+ * first argument to every store operation).
573
+ * @param h - The harness produced by `createHarness()...loop()`.
574
+ * @param slots - Values for every slot declared with `required()` in the harness.
575
+ * @param options - Optional agent-level options (e.g., instanceId).
576
+ *
577
+ * @throws {@link MissingLoopError} when `h.loop()` was never called.
578
+ * @throws {@link MissingSlotError} when a `required()` slot is absent from `slots`.
579
+ * @throws {@link RuntimeSlotInAgentError} when a `runtime()` slot is passed in `slots`.
580
+ * @throws {@link UnknownSlotError} when `slots` contains a key not declared in the harness.
581
+ *
582
+ * @example
583
+ * ```ts
584
+ * const agent = createAgent('my-agent', h, { llm: new Claude('claude-3-5-haiku-20241022') })
585
+ * const handle = agent.run({}, {})
586
+ * const { state, signal } = await handle
587
+ * ```
588
+ */
589
+ declare function createAgent<Ctx, State, Req extends keyof Ctx, Run extends keyof Ctx>(id: string, h: Harness<Ctx, State, Req, Run>, slots: Pick<Ctx, Req>, agentOptions?: AgentOptions): Agent;
201
590
 
591
+ /**
592
+ * Thrown by {@link RunHandle.resume} or {@link Agent.resume} when the session
593
+ * is not currently paused on a matching interrupt.
594
+ *
595
+ * Common causes: the run has not yet settled, the session does not exist,
596
+ * or the supplied `interruptId` does not match the pending interrupt.
597
+ */
202
598
  declare class NoInterruptError extends Error {
203
599
  constructor();
204
600
  }
205
601
 
602
+ /**
603
+ * Thrown by {@link Agent.run} or {@link Agent.resume} when the given session
604
+ * is already executing a run in the same process.
605
+ *
606
+ * Wait for the in-flight {@link RunHandle} to settle before starting a new run.
607
+ */
206
608
  declare class SessionInFlightError extends Error {
609
+ /** The session that was already in-flight. */
207
610
  readonly sessionId: string;
208
611
  constructor(sessionId: string);
209
612
  }
613
+ /**
614
+ * Thrown by {@link Agent.run} when the session is paused on an unanswered
615
+ * interrupt. Call {@link Agent.resume} (or `handle.resume()`) instead.
616
+ */
210
617
  declare class SessionPendingInterruptError extends Error {
618
+ /** The session that is awaiting an interrupt response. */
211
619
  readonly sessionId: string;
212
620
  constructor(sessionId: string);
213
621
  }
622
+ /**
623
+ * Thrown by the harness when the session store's `load()` call rejects.
624
+ * The original error is available on the `cause` property.
625
+ */
214
626
  declare class StoreLoadError extends Error {
215
627
  readonly cause: unknown;
216
628
  constructor(cause: unknown);
217
629
  }
218
630
 
219
- export { type Agent, type DeepWithMarkers, type FieldDefinition, type Harness, NoInterruptError, REQUIRED_TAG, RUNTIME_TAG, type RequiredMarker, type RuntimeMarker, SessionInFlightError, SessionPendingInterruptError, type SessionStore, type StateFromSchema, StoreLoadError, type StoredRun, createAgent, createHarness, field, isRequiredMarker, isRuntimeMarker, required, runtime };
631
+ /**
632
+ * Identifies the agent and session for run-level observer callbacks.
633
+ */
634
+ interface RunContext {
635
+ readonly agentId: string;
636
+ readonly sessionId: string;
637
+ /**
638
+ * The UUID for this specific run invocation. Always present in RunContext
639
+ * passed to observer methods.
640
+ */
641
+ readonly runId: string;
642
+ /**
643
+ * Optional parent run UUID for cross-process trace correlation. Present
644
+ * when parentRunId was passed to agent.run() resources. Absent for top-level
645
+ * runs or when not provided by the caller.
646
+ */
647
+ readonly parentRunId?: string;
648
+ /**
649
+ * The physical instance ID of the agent process, if configured via
650
+ * `createAgent()` options. Absent when `instanceId` was not provided.
651
+ */
652
+ readonly instanceId?: string;
653
+ }
654
+ /**
655
+ * Identifies the agent, session, and current step for step-level observer callbacks.
656
+ */
657
+ interface StepContext {
658
+ readonly agentId: string;
659
+ readonly sessionId: string;
660
+ readonly stepName: string;
661
+ }
662
+ /**
663
+ * Observability hook interface. All methods are optional — implement only
664
+ * the hooks you need.
665
+ *
666
+ * Pass an `Observer` implementation in `agent.run()` resources under the key
667
+ * `'observer'`, or bind it to an {@link ObserverAware} slot before running.
668
+ *
669
+ * LLM adapters emit `'llm.response'` events via `onEvent` carrying an
670
+ * `LLMUsageEvent` payload for token tracking.
671
+ *
672
+ * @example
673
+ * ```ts
674
+ * const obs: Observer = {
675
+ * onRunStart: (ctx) => console.log('run started', ctx.sessionId),
676
+ * onEvent: (ctx, type, payload) => metrics.record(type, payload),
677
+ * }
678
+ * agent.run({}, { llm, observer: obs })
679
+ * ```
680
+ */
681
+ interface Observer {
682
+ /** Called once when a run begins, before the first step executes. */
683
+ onRunStart?: (ctx: RunContext) => void;
684
+ /** Called once when a run settles (completed or stopped). */
685
+ onRunEnd?: (ctx: RunContext, event: {
686
+ signal: string;
687
+ durationMs: number;
688
+ }) => void;
689
+ /** Called immediately before each step's `run` function is invoked. */
690
+ onStepStart?: (ctx: StepContext) => void;
691
+ /** Called after a step completes successfully. */
692
+ onStepEnd?: (ctx: StepContext, event: {
693
+ durationMs: number;
694
+ }) => void;
695
+ /** Called when a step's `run` function throws. */
696
+ onStepError?: (ctx: StepContext, event: {
697
+ error: unknown;
698
+ durationMs: number;
699
+ }) => void;
700
+ /** Called when a step issues a `ctx.interrupt()`. */
701
+ onInterrupt?: (ctx: StepContext, event: {
702
+ prompt: unknown;
703
+ interruptId: string;
704
+ }) => void;
705
+ /**
706
+ * Called for arbitrary named events emitted by step code via `ctx.emit()` or
707
+ * by LLM adapters (e.g. `'llm.response'`).
708
+ */
709
+ onEvent?: (ctx: StepContext, type: string, payload: unknown) => void;
710
+ }
711
+ /**
712
+ * Implemented by resources (e.g. LLM adapters) that accept an {@link Observer}
713
+ * at run time. The harness calls `bindObserver` on every slot value that
714
+ * implements this interface before the first step runs.
715
+ */
716
+ interface ObserverAware {
717
+ /**
718
+ * Receive the run's observer. The harness calls this once per `agent.run()`
719
+ * invocation before execution begins.
720
+ */
721
+ bindObserver(observer: Observer): void;
722
+ }
723
+ /**
724
+ * Combine multiple {@link Observer} instances into one. Each hook on the
725
+ * composite forwards to all constituent observers in order.
726
+ *
727
+ * @example
728
+ * ```ts
729
+ * const observer = composeObservers(otelObserver, metricsObserver)
730
+ * ```
731
+ */
732
+ declare function composeObservers(...observers: Observer[]): Observer;
733
+
734
+ export { type Agent, type ClaimOptions, type DeepWithMarkers, type FieldDefinition, type Harness, type Lease, NoInterruptError, type Observer, type ObserverAware, REQUIRED_TAG, RUNTIME_TAG, type RequiredMarker, type RunContext, type RuntimeMarker, SessionInFlightError, SessionPendingInterruptError, type SessionStore, type StateFromSchema, type StepContext, StoreLoadError, type StoredRun, type StoredRunMetadata, composeObservers, createAgent, createHarness, field, isRequiredMarker, isRuntimeMarker, required, runtime };