@orkestrel/agent 0.0.30 → 0.0.31

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.
@@ -42,22 +42,25 @@ import type { WorkspaceManagerInterface } from '@orkestrel/workspace';
42
42
  *
43
43
  * @remarks
44
44
  * - **One loop, two faces.** A single private async generator (`#execute`) drives the
45
- * whole turn. `stream` kicks off an eager pump that iterates `#execute` into a private
46
- * {@link Channel}, settling `result` from the run's outcome — so `result` settles
45
+ * whole run. `stream` drives it eagerly into a private {@link Channel} and settles `result`
46
+ * from the outcome the generator returns, through one `.then` — so `result` settles
47
47
  * whether or not the live `events` are drained; `generate` awaits that same
48
48
  * settled `result` — so the two can never diverge.
49
- * - **The turn.** `#execute` builds the provider input once (`context.build()` into a
50
- * working array) then loops up to `limit`: drive `provider.stream(...)` accumulating
51
- * + yielding each content delta as a `token` chunk; fold the turn's usage into the
52
- * running total + the `budget` and yield a `usage` chunk; if the model requested
53
- * tools, append the assistant turn, pass them to the tool manager's `execute` method, yield a `tool` chunk per call,
54
- * append each tool result message, and continue; otherwise append the final
55
- * assistant message and stop. Each assistant message stores its call's non-empty thinking.
49
+ * - **The turn.** `#execute` builds the provider input once, then runs up to `limit` turns,
50
+ * each appending one readonly {@link AgentTurn} record: drive `provider.stream(...)`,
51
+ * yielding each content delta as a `token` chunk and charging its estimate to the
52
+ * `allowance` when one is set; reconcile the turn's usage against the `budget`, before a
53
+ * genuine provider error rejects the run too, and yield a `usage` chunk; if the model
54
+ * requested tools, append the assistant turn, pass them to the tool manager's `execute`
55
+ * method, yield a `tool` chunk per call, append each tool result message, and continue;
56
+ * otherwise append the final assistant message and stop. The run's content,
57
+ * thinking, usage, and stop derive from the turn records. Each assistant message stores its
58
+ * call's non-empty thinking.
56
59
  * Provider calls and prompt estimates apply the provider's `replay` policy to the working
57
60
  * array; an absent policy means `'none'`.
58
61
  * - **Bounded.** Each run arms one cancel through `createAbort({ signal: AbortSignal.any([
59
- * …]) })` folding the external `signal`, the `timeout` deadline, and the `budget`
60
- * signal; `abort()` fires it. Any trip stops the loop and commits a partial result
62
+ * …]) })` folding the external `signal`, the `timeout` deadline, and the `budget` and
63
+ * `allowance` signals; `abort()` fires it. Any trip stops the loop and commits a partial result
61
64
  * (the `result` promise resolves, never rejects, on a cancel) — only a genuine
62
65
  * provider / tool error rejects.
63
66
  * - **Paced + capped.** The loop calls the `scheduler` object's `yield` method between turns when given; tool
@@ -105,6 +108,8 @@ export declare class Agent implements AgentInterface {
105
108
  * emitted after the tool runs (so a consumer sees what was called and what came back).
106
109
  * - `usage` — one provider call's {@link TokenUsage}, emitted after each turn's
107
110
  * provider response that reported it (folded into the running total + any budget).
111
+ * `estimate` is the token estimate of the answer the call streamed, the meter layer's reading
112
+ * beside the reported usage.
108
113
  */
109
114
  export declare type AgentChunk = {
110
115
  readonly category: 'token';
@@ -119,6 +124,7 @@ export declare type AgentChunk = {
119
124
  } | {
120
125
  readonly category: 'usage';
121
126
  readonly usage: TokenUsage;
127
+ readonly estimate: number;
122
128
  };
123
129
 
124
130
  /**
@@ -314,8 +320,9 @@ export declare interface AgentContextInterface {
314
320
  * `scope.files` (a three-way allow-list; `undefined` ⇒ all active files), then split by
315
321
  * carrier: text files ({@link import('@orkestrel/workspace').isText}) render into a dedicated
316
322
  * `## Workspace` section in the system block — each a fenced
317
- * `` File: <path>\n```<language>\n<text>\n``` `` block — placed immediately after the instructions
318
- * section; binary files whose MIME starts with `image/` have their `base64` payload
323
+ * `` File: <path>\n```<language>\n<text>\n``` `` block whose fence is one backtick longer than
324
+ * the longest backtick run in the text, and never shorter than three — placed immediately after
325
+ * the instructions section; binary files whose MIME starts with `image/` have their `base64` payload
319
326
  * attached to the last user message (a vision provider reads images off a user turn).
320
327
  * Active-only — never the other registered workspaces; with no active workspace nothing is
321
328
  * rendered for workspaces.
@@ -411,25 +418,41 @@ export declare interface AgentContextOptions {
411
418
  }
412
419
 
413
420
  /**
414
- * Reports a concurrent run sharing construction accounting or a name absent from a registry pool. The `stream` and `generate` methods and registry accessors throw synchronously, before returning a handle or promise. Await the call inside `try`/`catch` to catch it.
421
+ * Reports a run sharing accounting with a live run, a `limit` that is not a nonnegative safe integer, a name absent from a registry pool, or a strict run whose request exceeds its window. Agent construction, the `stream` and `generate` methods, and registry accessors throw synchronously, before returning a handle or promise; a `'WINDOW'` refusal rejects the run's `result` instead. Await the call inside `try`/`catch` to catch it.
415
422
  *
416
423
  * @remarks
417
- * `'CONCURRENCY'` reports a run already in flight on the same agent, plus a construction-level
418
- * `window` (a shared context budget) or a construction-level `budget` with no per-run override
419
- * (a shared cost budget) — a second concurrent `stream()` would race its charges against the
420
- * same shared instance, corrupting the accounting. Use separate agents, or per-run `budget`
421
- * overrides with no `window`, for genuinely concurrent runs. `'REGISTRY'` reports a rehydration
422
- * name absent from its registry pool, on `provider` / `tool` / `authority` / `scheduler` /
423
- * `build`. Narrow a caught value with {@link isAgentError} and branch on `error.code`.
424
+ * `'CONCURRENCY'` reports a run started while another run on the same agent is live, when the
425
+ * agent carries a `window` (a shared context budget) or when the run's effective `budget` or
426
+ * `allowance` is an instance a live run holds — the second run's `start()` would replace that
427
+ * instance's signal and race its charges against the first run's. Use separate agents, or
428
+ * distinct budget and allowance instances per run with no `window`, for concurrent runs. Its
429
+ * `context` names the `agent` id.
430
+ * `'LIMIT'` reports a construction or per-run `limit` that is not a nonnegative safe integer,
431
+ * including one a rehydrated job carries into `build`. `'REGISTRY'` reports a rehydration name
432
+ * absent from its registry pool, on `provider` / `tool` / `authority` / `scheduler` / `build`;
433
+ * its `context` names the `category` and the `name`. `'WINDOW'` reports a `strict` run whose
434
+ * complete provider request is still over the `window` budget, equal to its `max` included, after
435
+ * the one permitted fold or with no fold possible; it settles the run `error` before the provider
436
+ * is called, and its `context` holds the `agent` id, the measured `consumed`, and the window `max`.
437
+ * Narrow a caught value with {@link isAgentError} and branch on `error.code`.
424
438
  */
425
439
  export declare class AgentError extends Error {
426
- /** Names the machine-readable condition — `'CONCURRENCY'`: a concurrent run on a shared accounting agent; `'REGISTRY'`: a rehydration name absent from its registry pool. */
440
+ /** Names the machine-readable condition — `'CONCURRENCY'`: a run sharing accounting with a live run; `'LIMIT'`: a `limit` that is not a nonnegative safe integer; `'REGISTRY'`: a rehydration name absent from its registry pool; `'WINDOW'`: a strict run whose complete request is still over its window budget. */
427
441
  readonly code: AgentErrorCode;
428
- constructor(code: AgentErrorCode, message: string);
442
+ /** Holds the values that locate the failure, such as the `agent` id of a refused run, or `undefined` when none were supplied. */
443
+ readonly context: Readonly<Record<string, unknown>> | undefined;
444
+ /**
445
+ * Creates the error with its code, message, and optional cause and context.
446
+ *
447
+ * @param code - The machine-readable {@link AgentErrorCode}
448
+ * @param message - The human-readable description
449
+ * @param options - The optional {@link CodedErrorOptions}; `cause` reaches `Error.cause` only when present
450
+ */
451
+ constructor(code: AgentErrorCode, message: string, options?: CodedErrorOptions);
429
452
  }
430
453
 
431
- /** Names the machine-readable agent concurrency and registry failures. */
432
- export declare type AgentErrorCode = 'CONCURRENCY' | 'REGISTRY';
454
+ /** Names the machine-readable agent concurrency, limit, registry, and window failures. */
455
+ export declare type AgentErrorCode = 'CONCURRENCY' | 'LIMIT' | 'REGISTRY' | 'WINDOW';
433
456
 
434
457
  /**
435
458
  * Maps the push observation surface of an {@link AgentInterface} — the lifecycle, usage, and tool
@@ -466,8 +489,12 @@ export declare type AgentEventMap = {
466
489
  readonly turn: readonly [index: number];
467
490
  /** Reports a dispatched {@link ToolCall} paired with its {@link ToolResult} (executed or a denial). */
468
491
  readonly tool: readonly [call: ToolCall, result: ToolResult];
469
- /** Reports a turn's {@link TokenUsage} — emitted after a usage-bearing provider call. */
470
- readonly usage: readonly [usage: TokenUsage];
492
+ /**
493
+ * Reports a turn's {@link TokenUsage} — emitted after a usage-bearing provider call. Carries the
494
+ * call's streamed-answer `estimate` beside its reported usage, so the meter's allowance can be
495
+ * calibrated without a failure.
496
+ */
497
+ readonly usage: readonly [usage: TokenUsage, estimate: number];
471
498
  /**
472
499
  * Reports a call denied by scope or authority, before dispatch.
473
500
  * A turn advertising no tools drops each call with `no tool is advertised in the active scope`.
@@ -490,7 +517,7 @@ export declare type AgentEventMap = {
490
517
  readonly exhaust: readonly [turns: number];
491
518
  /**
492
519
  * Reports automatic compaction's summarizer throwing — a non-fatal warn channel (the run continues; see
493
- * {@link AgentOptions.window}). When the loop's between-turns / pre-first-turn auto-compaction
520
+ * {@link AgentOptions.window}). When the loop's pre-dispatch auto-compaction
494
521
  * (`conversation.compact()`) rejects, the run does not crash: the loop skips compaction that
495
522
  * turn and surfaces the caught error here so the failure is observable, never silently lost.
496
523
  * The run still settles through the other events — `finish` for the lenient default, and
@@ -511,21 +538,67 @@ export declare type AgentEventMap = {
511
538
  readonly select: readonly [selection: Selection];
512
539
  };
513
540
 
541
+ /**
542
+ * Executes one runner's agent jobs — spawning each unit's declared children on its first attempt
543
+ * only, then rehydrating the unit through a registry with the controller signal and settling it
544
+ * under the shared partial-result policy.
545
+ *
546
+ * @remarks
547
+ * One instance serves one runner, because it records every unit it has fanned out. The runner
548
+ * calls its handler once per attempt with the same unit id, so a retried attempt finds its unit
549
+ * recorded and spawns no child again. Children are fired through the controller without being
550
+ * awaited inline, so a bounded runner frees the parent's slot for them. `createAgentRunner`
551
+ * creates one instance per runner and binds its `execute` method as the runner's handler.
552
+ *
553
+ * @example
554
+ * ```ts
555
+ * import type { AgentJobInput, ProviderInterface } from '@orkestrel/agent'
556
+ * import { AgentFanout, createAgentRegistry } from '@orkestrel/agent'
557
+ * import { createRunner } from '@orkestrel/workflow'
558
+ *
559
+ * declare const provider: ProviderInterface // any concrete implementation supplied by the host app
560
+ * const fanout = new AgentFanout(createAgentRegistry({ providers: { main: provider } }), false)
561
+ * const runner = createRunner({ handler: fanout.execute.bind(fanout), retries: 1 })
562
+ * const child: AgentJobInput = { provider: 'main', messages: [{ role: 'user', content: 'child' }] }
563
+ * const parent: AgentJobInput = { ...child, messages: [{ role: 'user', content: 'parent' }], children: [child] }
564
+ * const results = await runner.execute([parent]) // the parent's result, then the child's
565
+ * ```
566
+ */
567
+ export declare class AgentFanout {
568
+ #private;
569
+ /**
570
+ * Creates the fan-out for one runner over a registry and a partial policy.
571
+ *
572
+ * @param registry - The registry that rehydrates each serializable job
573
+ * @param partial - The partial policy. If `true`, a partial result resolves; if `false`, it throws an `AgentJobError`
574
+ */
575
+ constructor(registry: AgentRegistryInterface, partial: boolean);
576
+ /**
577
+ * Executes one unit: spawns its declared children unless an earlier attempt of the unit already
578
+ * did, then runs the rehydrated agent and settles it under the partial policy.
579
+ *
580
+ * @param controller - The runner controller for this unit's attempt
581
+ * @returns The settled agent result of the unit
582
+ * @throws {AgentJobError} Thrown when the run ended partial and the partial policy is `false`
583
+ */
584
+ execute(controller: ControllerInterface<AgentJobInput, AgentResult>): Promise<AgentResult>;
585
+ }
586
+
514
587
  /**
515
588
  * Composes a {@link ProviderInterface}, an {@link AgentContextInterface}, and a
516
589
  * {@link ToolManagerInterface} into a bounded context → provider → tools → repeat turn.
517
590
  *
518
591
  * @remarks
519
592
  * - **One loop, two faces.** `generate` and `stream` share one private run, so they
520
- * can never diverge: `generate` drains the same stream `stream` exposes, then
521
- * resolves its settled {@link AgentResult}.
593
+ * can never diverge: `generate` awaits the settled {@link AgentResult} of the run `stream`
594
+ * exposes and leaves its live chunks undrained.
522
595
  * - **Bounded.** Each turn arms a single cancel folded from the external `signal`, the
523
- * `timeout` deadline, and the `budget` signal (through `AbortSignal.any`); any of them —
524
- * or `abort()` — stops the loop and settles the result `partial: true`.
596
+ * `timeout` deadline, and the `budget` and `allowance` signals (through `AbortSignal.any`); any
597
+ * of them — or `abort()` — stops the loop and settles the result `partial: true`.
525
598
  * Tool handlers receive that run's signal through their `ToolContext`. An agent abort,
526
- * stream abort, external signal, or deadline can reach a running handler. The budget is
527
- * charged during provider streaming and between turns, before tool dispatch; exhaustion
528
- * ends the run without dispatching, never inside a handler. An abort before dispatch
599
+ * stream abort, external signal, or deadline can reach a running handler. The allowance is
600
+ * charged during provider streaming and the budget after each provider call and between turns,
601
+ * before tool dispatch; exhaustion ends the run without dispatching, never inside a handler. An abort before dispatch
529
602
  * appends neither the assistant call turn nor tool messages and emits no tool chunk;
530
603
  * streamed content remains in the partial result. Cancellation after entry is cooperative:
531
604
  * the loop awaits a running handler even when it ignores the signal, records its tool message,
@@ -550,20 +623,24 @@ export declare interface AgentInterface {
550
623
  readonly status: AgentStatus;
551
624
  readonly context: AgentContextInterface;
552
625
  /**
553
- * Runs the turn to completion, discarding the live chunks — drains the shared stream and
554
- * resolves the settled {@link AgentResult} (`partial: true` when cancelled).
626
+ * Runs the turn to completion — awaits the settled {@link AgentResult} (`partial: true` when
627
+ * cancelled) and leaves the live chunks undrained.
555
628
  *
556
629
  * @remarks
557
- * A concurrent run on a shared accounting agent throws an
558
- * {@link import('./errors.js').AgentError} (`code: 'CONCURRENCY'`) — and it throws
559
- * synchronously, before any `Promise` is returned. A fire-and-forget
560
- * `agent.generate().catch(...)` therefore will not catch it (the throw happens on the call
561
- * itself, ahead of the `.catch` ever attaching) — await the call (inside a `try`/`catch`)
562
- * or wrap the call expression itself in `try`/`catch`.
630
+ * A run started while another is live throws an {@link import('./errors.js').AgentError} with
631
+ * `code: 'CONCURRENCY'` when the agent carries a `window`, or when the run's effective
632
+ * `budget` or `allowance` is an instance a live run holds; a run whose `limit` is not a nonnegative safe
633
+ * integer throws one with `code: 'LIMIT'`. A `budget` or `allowance` that does not implement
634
+ * `BudgetInterface` throws a `ContractError` with code `'placement'` before either bound starts.
635
+ * Each throws synchronously, before any `Promise` is
636
+ * returned. A fire-and-forget `agent.generate().catch(...)` therefore will not catch them (the
637
+ * throw happens on the call itself, ahead of the `.catch` ever attaching) — await the call
638
+ * (inside a `try`/`catch`) or wrap the call expression itself in `try`/`catch`.
563
639
  *
564
640
  * @param options - Optional per-run {@link AgentRunOptions} (for example `think`); omitted ⇒ defaults
565
641
  * @returns The settled {@link AgentResult} (`partial: true` when cancelled)
566
- * @throws {AgentError} Thrown when a concurrent run shares construction accounting (`code: 'CONCURRENCY'`), synchronously before a handle returns.
642
+ * @throws {AgentError} Thrown when a live run shares the `window` or a budget or allowance instance (`code: 'CONCURRENCY'`) or the `limit` is not a nonnegative safe integer (`code: 'LIMIT'`), synchronously before a handle returns.
643
+ * @throws {ContractError} Thrown when the `budget` or `allowance` does not implement `BudgetInterface` (`code: 'placement'`), synchronously before a handle returns.
567
644
  */
568
645
  generate(options?: AgentRunOptions): Promise<AgentResult>;
569
646
  /**
@@ -572,14 +649,18 @@ export declare interface AgentInterface {
572
649
  * on a genuine error.
573
650
  *
574
651
  * @remarks
575
- * Like `generate()`, a concurrent run on a shared accounting agent throws an
576
- * {@link import('./errors.js').AgentError} (`code: 'CONCURRENCY'`) synchronously — before the
652
+ * Like `generate()`, a run that would share the `window` or a live run's budget or allowance
653
+ * instance throws an {@link import('./errors.js').AgentError} with `code: 'CONCURRENCY'`, an
654
+ * invalid `limit` throws one with `code: 'LIMIT'`, and a `budget` or `allowance` that does not
655
+ * implement `BudgetInterface` throws a `ContractError` with code `'placement'`, synchronously — before the
577
656
  * {@link AgentStreamInterface} handle is even returned, so it cannot be caught by chaining
578
- * off the (never-produced) handle; wrap the call itself in `try`/`catch`.
657
+ * off the (never-produced) handle; wrap the call itself in `try`/`catch`. A `signal` that
658
+ * `AbortSignal.any` refuses throws its `TypeError` the same way, before any deadline is armed.
579
659
  *
580
660
  * @param options - Optional per-run {@link AgentRunOptions} (for example `think`); omitted ⇒ defaults
581
661
  * @returns A live {@link AgentStreamInterface} handle (events + result + abort)
582
- * @throws {AgentError} Thrown when a concurrent run shares construction accounting (`code: 'CONCURRENCY'`), synchronously before a handle returns.
662
+ * @throws {AgentError} Thrown when a live run shares the `window` or a budget or allowance instance (`code: 'CONCURRENCY'`) or the `limit` is not a nonnegative safe integer (`code: 'LIMIT'`), synchronously before a handle returns.
663
+ * @throws {ContractError} Thrown when the `budget` or `allowance` does not implement `BudgetInterface` (`code: 'placement'`), synchronously before a handle returns.
583
664
  */
584
665
  stream(options?: AgentRunOptions): AgentStreamInterface;
585
666
  /**
@@ -766,13 +847,17 @@ export declare interface AgentJudgeInterface extends JudgeInterface {
766
847
  * - `system` — an optional system prompt prepended to the turn (seeds the context).
767
848
  * - `tools` — an optional pre-built {@link ToolManagerInterface} the loop dispatches
768
849
  * the model's calls through; an empty one is created when omitted.
769
- * - `limit` — the maximum number of tool-iteration turns before the loop stops.
850
+ * - `limit` — the maximum number of tool-iteration turns before the loop stops, a nonnegative
851
+ * safe integer; construction throws an `AgentError` with code `'LIMIT'` when it is not.
770
852
  * Default: `DEFAULT_AGENT_LIMIT`.
771
853
  * - `timeout` — an optional wall-clock deadline (ms) for the whole turn; its signal
772
854
  * folds into the turn's bound, committing a partial result on expiry.
773
855
  * - `budget` — an optional token {@link BudgetInterface} cost bound; the loop charges
774
856
  * each provider call's usage and its signal folds into the turn's bound, committing
775
857
  * a partial result after the budget is exhausted.
858
+ * - `allowance` — an optional token {@link BudgetInterface} the meter layer charges each content
859
+ * delta's mid-stream estimate against; its signal folds into the turn's bound beside the
860
+ * `budget`'s. Pass the `budget` itself to share one ceiling. Omitted ⇒ no meter layer.
776
861
  * - `scheduler` — an optional {@link SchedulerInterface} that paces the loop —
777
862
  * the loop calls its `yield` method between turns, so the host regains control between expensive provider calls.
778
863
  * - `signal` — an optional external `AbortSignal` whose abort cancels the turn (a
@@ -781,17 +866,20 @@ export declare interface AgentJudgeInterface extends JudgeInterface {
781
866
  * context as the message source (so `context.messages` is its active conversation's live tail);
782
867
  * omitted ⇒ a fresh registry holding one default conversation. Auto-compaction (`window`) folds
783
868
  * the active conversation when it is summarizable.
784
- * - `window` — an optional context {@link BudgetInterface} for automatic conversation
785
- * compaction: when set, the loop measures the current full prompt against this budget each turn
786
- * (its `consumer` is a token estimator, its `max` the context window) and, when the prompt
787
- * reaches the window and the active conversation is summarizable, compacts the active
788
- * conversation + continues on the rebuilt smaller view — compact-and-continue, distinct from
869
+ * - `window` — an optional context {@link BudgetInterface} over the complete
870
+ * {@link ProviderRequest} for automatic conversation compaction: when set, the loop measures the
871
+ * exact request, messages after replay, advertised tools, and per-call options, before every
872
+ * provider dispatch (its `consumer` is a token estimator, its `max` the context window) and, when
873
+ * the request reaches the window and the active conversation is summarizable, compacts the active
874
+ * conversation once + dispatches the rebuilt smaller request — compact-and-continue, distinct from
789
875
  * hard abort of the `budget` object. Omitted ⇒ no auto-compaction.
790
876
  * - `strict` — when `true`, a summarizer failure during automatic compaction aborts the run
791
877
  * (rethrown after the `fault` event, propagating through `#execute` to a genuine `error`
792
878
  * settle) instead of skipping compaction and continuing over-window. The selection faults (a
793
879
  * thrown handler, a returned `fault`, a conversation changed under the handler) settle the same
794
- * way. Default: `false`. The run continues over-window, or on `view()` after a selection fault.
880
+ * way, and a request still over the `window` after the permitted fold, or one that cannot fold,
881
+ * settles `error` with an `AgentError` coded `'WINDOW'` before the provider is called.
882
+ * Default: `false`. The run continues over-window, or on `view()` after a selection fault.
795
883
  * - `select` — an optional default {@link SelectionHandler} forwarded to the agent's context; the
796
884
  * active scope's `select` overrides it. The loop runs it at run entry and after each automatic
797
885
  * compaction rebuild, emits each {@link Selection} it builds from on `select`, and charges the
@@ -830,12 +918,33 @@ export declare interface AgentOptions {
830
918
  * compaction fold, or a compaction rebuild.
831
919
  */
832
920
  readonly scope?: ScopeInterface;
833
- /** Caps the tool-iteration turns before the loop stops. Default: `DEFAULT_AGENT_LIMIT`. */
921
+ /**
922
+ * Caps the tool-iteration turns before the loop stops, as a nonnegative safe integer.
923
+ * Thrown as an `AgentError` with code `'LIMIT'` when it is not. Default: `DEFAULT_AGENT_LIMIT`.
924
+ */
834
925
  readonly limit?: number;
835
926
  /** Sets a wall-clock deadline (ms) for the whole turn; its abort commits a partial result. */
836
927
  readonly timeout?: number;
837
- /** Bounds the token cost; each provider call's usage is charged and its abort commits a partial. */
928
+ /**
929
+ * Bounds the token cost; each provider call's usage is charged and its abort commits a partial.
930
+ * A call that reports no usage charges the `budget` its streamed-answer estimate, unless the
931
+ * `allowance` is the `budget` and already charged it.
932
+ */
838
933
  readonly budget?: BudgetInterface<TokenUsage>;
934
+ /**
935
+ * Holds the meter layer's allowance: the token budget each content delta's mid-stream estimate is
936
+ * charged against, beside the reported usage the cost `budget` is reconciled to.
937
+ * The meter charges completion growth as `{ prompt: 0, completion: n, total: n }`, so a
938
+ * `completion`- or `total`-scoped allowance tracks the answer, while a `prompt`-scoped allowance
939
+ * is charged `0` and never trips through the meter. Its signal folds into the run's bound with
940
+ * the `budget`'s, so the estimate can stop a stream before the provider
941
+ * reports usage. Pass the `budget` itself to make the estimate and the reported usage share one
942
+ * ceiling; pass a separate budget with a smaller `max` to let the estimate stop the run first, or
943
+ * a larger one to let the reported usage or the model's own limit stop it instead. Cumulative
944
+ * across turns and runs, like the `budget`. Default: no meter layer; the estimate is still
945
+ * measured for the `usage` event and charges nothing mid-stream.
946
+ */
947
+ readonly allowance?: BudgetInterface<TokenUsage>;
839
948
  /** Paces the loop — the loop yields to it between turns so the host regains control. */
840
949
  readonly scheduler?: SchedulerInterface;
841
950
  /** Carries an external cancel; its abort commits a partial result. */
@@ -858,22 +967,34 @@ export declare interface AgentOptions {
858
967
  */
859
968
  readonly conversations?: ConversationManagerInterface;
860
969
  /**
861
- * Holds an optional context {@link BudgetInterface} for automatic compaction. Its `consumer` is a
862
- * token estimator (for example the exported {@link import('./helpers.js').estimateMessages}) and
863
- * its `max` is the context window. When set, the loop measures the current full prompt (the
864
- * next provider request) against this budget each turn; when that prompt reaches the window and
865
- * the active conversation is summarizable, it **compacts the active conversation + continues on
866
- * the rebuilt smaller view** (compact-and-continue) — the same consume-to-a-ceiling primitive
867
- * as the cost `budget`, but compaction is the ceiling action instead of abort. Omit to disable.
868
- */
869
- readonly window?: BudgetInterface<readonly Message[]>;
970
+ * Holds an optional context {@link BudgetInterface} that admits each provider request. Its
971
+ * `consumer` is a token estimator over the complete {@link ProviderRequest} (for example the
972
+ * exported {@link import('./helpers.js').estimateRequest}) and its `max` is the context window.
973
+ * Before every provider dispatch the loop measures the exact request it will send: the messages
974
+ * after the provider's `replay` policy, the tools the turn advertises after the scope's
975
+ * allow-list, and the per-call `options` (`think`, `schema`). The budget is cleared, consumes that
976
+ * request, and the request is over the window when the budget is `exhausted`, which includes a
977
+ * request equal to `max`. When it is over and the active conversation is summarizable, the loop
978
+ * **compacts the active conversation once, rebuilds with the same tools and options, measures the
979
+ * rebuilt request, and dispatches it** (compact-and-continue) — the same consume-to-a-ceiling
980
+ * primitive as the cost `budget`, but compaction is the ceiling action instead of abort. A
981
+ * non-summarizable conversation is still measured, but cannot fold. After a run `consumed` holds
982
+ * the last measured request. With {@link AgentOptions.strict}, a request still over the window
983
+ * after the one permitted fold, or one that cannot fold, is refused before dispatch with an
984
+ * `AgentError` coded `'WINDOW'`. Omit to disable.
985
+ */
986
+ readonly window?: BudgetInterface<ProviderRequest>;
870
987
  /**
871
988
  * If `true`, a summarizer failure during automatic compaction emits `fault` and settles the
872
989
  * run `error`; a thrown selection handler, a returned selection `fault`, and a conversation
873
- * changed during selection follow the same rule. If `false`, the run continues — over-window
874
- * after a summarizer failure, and on the active conversation's `view()` after a selection
875
- * fault. A cancel at a selection site, a `fault` listener's included, settles partial instead
876
- * of `error`. Default: `false`.
990
+ * changed during selection follow the same rule. A request that the {@link AgentOptions.window}
991
+ * still finds over the window, equal to `max` included, after the one permitted fold, or that
992
+ * cannot fold, is refused before the provider is called: the run settles `error` with an
993
+ * `AgentError` coded `'WINDOW'` whose `context` holds the `agent`, the measured `consumed`, and
994
+ * the window `max`, and no `fault` fires for it. If `false`, the run continues — over-window
995
+ * after a summarizer failure or a futile fold, and on the active conversation's `view()` after a
996
+ * selection fault. A cancel at a selection site or a fold, a `fault` listener's included, settles
997
+ * partial instead of `error`. Default: `false`.
877
998
  */
878
999
  readonly strict?: boolean;
879
1000
  /**
@@ -1058,7 +1179,8 @@ export declare interface AgentQueueOptions {
1058
1179
  * the threaded `signal`; seed its context with the job's `messages`; return it. The
1059
1180
  * `signal` is the queue attempt's / runner unit's cancel, so a bounded abort propagates
1060
1181
  * into the agent (which commits a partial — the job's `partial` policy then
1061
- * decides success vs. retry).
1182
+ * decides success vs. retry). A job `limit` that is not a nonnegative safe integer throws an
1183
+ * {@link AgentError} carrying `code: 'LIMIT'` from the agent's construction.
1062
1184
  * - **Event-free.** A pure resolver — no Emitter, no events.
1063
1185
  *
1064
1186
  * @example
@@ -1140,7 +1262,7 @@ export declare interface AgentRegistryInterface {
1140
1262
  * @param input - The serializable {@link AgentJobInput} to rehydrate
1141
1263
  * @param signal - An optional cancel threaded into the agent (a queue / runner abort)
1142
1264
  * @returns The ready agent, its context seeded with the job's messages
1143
- * @throws {AgentError} Thrown when a referenced provider, tool, authority, or scheduler name is unregistered (`code: 'REGISTRY'`)
1265
+ * @throws {AgentError} Thrown when a referenced provider, tool, authority, or scheduler name is unregistered (`code: 'REGISTRY'`), or when the job's `limit` is not a nonnegative safe integer (`code: 'LIMIT'`)
1144
1266
  */
1145
1267
  build(input: AgentJobInput, signal?: AbortSignal): AgentInterface;
1146
1268
  }
@@ -1191,7 +1313,10 @@ export declare interface AgentRegistryOptions {
1191
1313
  * it stays out of `content`. Each call's non-empty thinking is recorded on the assistant
1192
1314
  * message that call appends. The joined result also includes thinking from calls that appended
1193
1315
  * no message, such as an aborted call. Only recorded thinking can return to a provider,
1194
- * as its `replay` policy allows.
1316
+ * as its `replay` policy allows. A run stopped by the `meter` or `budget` layer settles
1317
+ * `partial: true`. A `model` stop settles `partial: false`, because its answer is complete as the
1318
+ * model ended it, unless that call's reconciled usage then exhausted a budget or a cancel landed
1319
+ * before the run settled, which settles `partial: true` with that reason on the `abort` event.
1195
1320
  */
1196
1321
  export declare interface AgentResult {
1197
1322
  readonly content: string;
@@ -1209,6 +1334,13 @@ export declare interface AgentResult {
1209
1334
  * contains calls: those calls are dropped and observed through the `deny` event.
1210
1335
  */
1211
1336
  readonly partial: boolean;
1337
+ /**
1338
+ * Names the layer that stopped the run — the meter's estimate allowance, the cost budget, or the
1339
+ * model's own token limit — with the readings it crossed. Absent when the run finished by
1340
+ * itself, reached its `limit`, or was cancelled by a signal, a deadline, or `abort()` before the
1341
+ * model ended its final answer.
1342
+ */
1343
+ readonly stop?: AgentStop;
1212
1344
  }
1213
1345
 
1214
1346
  /**
@@ -1223,8 +1355,11 @@ export declare interface AgentResult {
1223
1355
  * through Contract's sanctioned exception boundary, so conforming accessors and inherited
1224
1356
  * properties are supported while a throwing getter or revoked proxy returns `undefined`.
1225
1357
  * Present usage counts must be finite numbers; negative and fractional values are preserved,
1226
- * not normalized. Extra input properties are dropped while a fresh exact plain object is rebuilt
1227
- * and deep-gated through
1358
+ * not normalized. The result is exact `{ content, thinking?, usage?, partial, stop? }`. A present
1359
+ * `stop` is read once, then its `layer`, `id`, `max`, `consumed`, `estimate`, and `usage` once each
1360
+ * in that order, `max` and `consumed` only on a `meter` or `budget` arm; the arm is rebuilt
1361
+ * exactly, omitting absent readings, and a malformed `stop` refuses the whole value. Extra input
1362
+ * properties are dropped while a fresh exact plain object is rebuilt and deep-gated through
1228
1363
  * {@link import('@orkestrel/contract').parseJSONValue}.
1229
1364
  *
1230
1365
  * @param value - The unknown value to project
@@ -1268,7 +1403,7 @@ export declare interface AgentRunnerOptions {
1268
1403
  * Every member is optional and resolved independently, so an omitted member leaves the
1269
1404
  * agent's constructed value in force and a caller that passes no options runs exactly the
1270
1405
  * agent it configured. `think` / `schema` ride through to the provider as
1271
- * {@link ProviderStreamOptions}; `limit` / `timeout` / `budget` replace their construction
1406
+ * {@link ProviderStreamOptions}; `limit` / `timeout` / `budget` / `allowance` replace their construction
1272
1407
  * defaults for this run only; `signal` composes with the constructed `signal` (both fold into
1273
1408
  * the run's bound abort) rather than replacing it. Nothing here mutates the agent — the next
1274
1409
  * run reads the construction defaults again.
@@ -1289,7 +1424,8 @@ export declare interface AgentRunOptions {
1289
1424
  readonly schema?: Readonly<Record<string, unknown>>;
1290
1425
  /**
1291
1426
  * Overrides {@link AgentOptions.limit} for this run only — the max tool-iteration turns
1292
- * before the loop stops. Omitted ⇒ the agent's constructed `limit` applies.
1427
+ * before the loop stops, as a nonnegative safe integer; the call throws an `AgentError` with
1428
+ * code `'LIMIT'` when it is not. Omitted ⇒ the agent's constructed `limit` applies.
1293
1429
  */
1294
1430
  readonly limit?: number;
1295
1431
  /**
@@ -1303,6 +1439,11 @@ export declare interface AgentRunOptions {
1303
1439
  * Omitted ⇒ the agent's constructed `budget` applies.
1304
1440
  */
1305
1441
  readonly budget?: BudgetInterface<TokenUsage>;
1442
+ /**
1443
+ * Overrides {@link AgentOptions.allowance} for this run only; started for this run with
1444
+ * `start()`, as the constructed allowance is. Default: the agent's constructed `allowance`.
1445
+ */
1446
+ readonly allowance?: BudgetInterface<TokenUsage>;
1306
1447
  /**
1307
1448
  * Carries an additional per-run external cancel, composed with {@link AgentOptions.signal} (both
1308
1449
  * fold into the run's bound abort through `AbortSignal.any` — neither is dropped). Omitted ⇒
@@ -1322,7 +1463,8 @@ export declare interface AgentRunOptions {
1322
1463
  * a provider call or a selection reported it), `partial` is `true` when a cancel committed the run early or
1323
1464
  * when the loop exhausted its `limit` with unresolved tool intent, and `exhausted` is `true`
1324
1465
  * in that second case specifically (a distinct, non-cancel cause the {@link AgentEventMap}
1325
- * `exhaust` event observes). It is the settled outcome one run returns, before the agent folds
1466
+ * `exhaust` event observes), and `stop` the {@link AgentStop} of the layer that stopped the run
1467
+ * (`undefined` when none did). It is the settled outcome one run returns, before the agent folds
1326
1468
  * it into the {@link AgentResult} resolved by the stream handle's `result` promise.
1327
1469
  */
1328
1470
  export declare interface AgentRunResult {
@@ -1331,6 +1473,7 @@ export declare interface AgentRunResult {
1331
1473
  readonly usage: TokenUsage | undefined;
1332
1474
  readonly partial: boolean;
1333
1475
  readonly exhausted: boolean;
1476
+ readonly stop: AgentStop | undefined;
1334
1477
  }
1335
1478
 
1336
1479
  /**
@@ -1340,11 +1483,74 @@ export declare interface AgentRunResult {
1340
1483
  */
1341
1484
  export declare type AgentStatus = 'idle' | 'running' | 'done' | 'error';
1342
1485
 
1486
+ /**
1487
+ * Represents why one of a run's three allowed failure layers stopped it — the meter's estimate
1488
+ * allowance, the cost budget, or the model's own token limit — with the readings that layer crossed.
1489
+ *
1490
+ * @remarks
1491
+ * `layer` names the failure point. A `meter` or `budget` stop copies the tripped budget's `id`, its
1492
+ * `max`, and its `consumed` tally at the crossing from the `BudgetError` that aborted the run; a
1493
+ * `meter` stop is a crossing of the run's `allowance`, a `budget` stop any other budget's, including
1494
+ * a budget folded into an external signal or linked as the allowance's own parent signal. A `model`
1495
+ * stop carries the provider instance's trace `id`, not the model's name: the provider reported that
1496
+ * the model ended its answer at its own token limit, and the reason the wire gave is not carried
1497
+ * beyond that. `estimate` is the token estimate of the answer the stopped provider call streamed,
1498
+ * and `usage` the sanitized usage that call reported; each is present only when the stop landed on
1499
+ * a provider call that produced it.
1500
+ *
1501
+ * @example
1502
+ * ```ts
1503
+ * const stop: AgentStop = { layer: 'meter', id: 'meter', max: 2_000, consumed: 2_001, estimate: 2_001 }
1504
+ * ```
1505
+ */
1506
+ export declare type AgentStop = {
1507
+ readonly layer: 'meter' | 'budget';
1508
+ readonly id: string;
1509
+ readonly max: number;
1510
+ readonly consumed: number;
1511
+ readonly estimate?: number;
1512
+ readonly usage?: TokenUsage;
1513
+ } | {
1514
+ readonly layer: 'model';
1515
+ readonly id: string;
1516
+ readonly estimate?: number;
1517
+ readonly usage?: TokenUsage;
1518
+ };
1519
+
1343
1520
  /**
1344
1521
  * Names the agent turn's live handle carrying {@link AgentChunk} values and resolving an {@link AgentResult}.
1345
1522
  */
1346
1523
  export declare type AgentStreamInterface = StreamInterface<AgentChunk, AgentResult>;
1347
1524
 
1525
+ /**
1526
+ * Holds what one agent loop turn contributed to its run and how the turn ended — the readonly
1527
+ * record the loop appends for each turn, from which the run derives its {@link AgentRunResult}.
1528
+ *
1529
+ * @remarks
1530
+ * `content` is the final answer on an `answer` turn and the content the provider streamed on any
1531
+ * other turn. `thinking` and `usage` are this turn's own non-empty reasoning and sanitized usage;
1532
+ * `spent` is the sanitized usage of a selection this turn's admission rebuilt through. `messages`
1533
+ * is the working conversation the next turn starts from. `partial` is the run's partial flag if the
1534
+ * run ends after this turn, and `futile` is the compaction latch after this turn's admission.
1535
+ * `estimate` is the token estimate of the content this turn's call streamed, `undefined` on a turn
1536
+ * a cancel stopped before its call. `interrupted` reports that the run's signal had aborted when
1537
+ * this turn's call returned, before its usage was reconciled. `truncated` reports that the model
1538
+ * ended this turn's final answer at its own token limit.
1539
+ */
1540
+ export declare interface AgentTurn {
1541
+ readonly content: string;
1542
+ readonly thinking: string | undefined;
1543
+ readonly usage: TokenUsage | undefined;
1544
+ readonly spent: TokenUsage | undefined;
1545
+ readonly messages: readonly Message[];
1546
+ readonly ending: 'answer' | 'tools' | 'cancel';
1547
+ readonly partial: boolean;
1548
+ readonly futile: boolean;
1549
+ readonly estimate: number | undefined;
1550
+ readonly interrupted: boolean;
1551
+ readonly truncated: boolean;
1552
+ }
1553
+
1348
1554
  /**
1349
1555
  * Arms signal propagation to a byte reader and returns the controller that removes the listener.
1350
1556
  *
@@ -1362,22 +1568,23 @@ export declare type AgentStreamInterface = StreamInterface<AgentChunk, AgentResu
1362
1568
  export declare function armReaderAbort(reader: ReadableStreamDefaultReader<Uint8Array>, signal: AbortSignal | undefined): Promise<AbortController>;
1363
1569
 
1364
1570
  /**
1365
- * Assembles the settled {@link AgentResult} from a run's {@link AgentRunResult} — `thinking` and
1366
- * `usage` are carried only when the run surfaced them, and the loop-internal `exhausted` flag is
1367
- * left out.
1571
+ * Assembles the settled {@link AgentResult} from a run's {@link AgentRunResult} — `thinking`,
1572
+ * `usage`, and `stop` are carried only when the run surfaced them, and the loop-internal
1573
+ * `exhausted` flag is left out.
1368
1574
  *
1369
1575
  * @remarks
1370
1576
  * Pure and total. An absent optional is omitted rather than stored as `undefined` (the
1371
1577
  * present-when-given convention the message store follows), so a settled result JSON
1372
- * round-trips without an explicit `undefined` field. `exhausted` is loop bookkeeping and does
1373
- * not reach the public result — the `exhaust` event carries it instead.
1578
+ * round-trips without an explicit `undefined` field. The keys follow `content`, `partial`,
1579
+ * `thinking`, `usage`, then `stop`. `exhausted` is loop bookkeeping and does not reach the public
1580
+ * result — the `exhaust` event carries it instead.
1374
1581
  *
1375
1582
  * @param outcome - The run's settled outcome
1376
1583
  * @returns The public {@link AgentResult}
1377
1584
  *
1378
1585
  * @example
1379
1586
  * ```ts
1380
- * assembleResult({ content: 'hi', thinking: undefined, usage: undefined, partial: false, exhausted: false })
1587
+ * assembleResult({ content: 'hi', thinking: undefined, usage: undefined, partial: false, exhausted: false, stop: undefined })
1381
1588
  * // { content: 'hi', partial: false }
1382
1589
  * ```
1383
1590
  */
@@ -1385,12 +1592,12 @@ export declare function assembleResult(outcome: AgentRunResult): AgentResult;
1385
1592
 
1386
1593
  /**
1387
1594
  * Copies a message with image data merged onto its `images` — the message's own images first,
1388
- * then the attached data, carrying `calls` only when present and never mutating the original.
1595
+ * then the attached data, keeping every other field and never mutating the original.
1389
1596
  *
1390
1597
  * @remarks
1391
- * Pure and total: the original message is never mutated. `calls` is carried only when the
1392
- * source message has one (kept omitted otherwise, mirroring the store's present-when-given
1393
- * convention).
1598
+ * Pure and total: the original message is never mutated. Every other field the source message
1599
+ * carries (`calls`, `call`, `thinking`) is kept as given, and an absent one stays absent,
1600
+ * mirroring the store's present-when-given convention.
1394
1601
  *
1395
1602
  * @param message - The message to copy (left unchanged)
1396
1603
  * @param payloads - The base64 image data to attach
@@ -1414,7 +1621,8 @@ export declare function attachImages(message: Message, payloads: readonly string
1414
1621
  * Pure and total: the conversation and its messages are never mutated, and the returned array
1415
1622
  * replaces exactly the one target message with the copy {@link attachImages} builds. Empty
1416
1623
  * data returns the conversation unchanged; a conversation with no user message returns it
1417
- * unchanged too (there is nowhere to attach, and the images already rode the system block).
1624
+ * unchanged too (there is nowhere to attach, and the images already rode the system block). A hole
1625
+ * in a sparse conversation is not a user message.
1418
1626
  *
1419
1627
  * @param conversation - The messages to attach into (left unchanged)
1420
1628
  * @param payloads - The base64 image data to attach
@@ -1647,13 +1855,14 @@ export declare function buildProviderHeaders(hook: ProviderOptions['headers'], s
1647
1855
  * @param thinking - The joined reasoning
1648
1856
  * @param tools - The accumulated calls
1649
1857
  * @param usage - The reported token usage
1858
+ * @param truncated - Whether the model stopped at its own limit, or undefined when the wire did not report it
1650
1859
  * @returns The assembled result
1651
1860
  * @example
1652
1861
  * ```ts
1653
- * buildProviderResult('ok', '', [], undefined) // { content: 'ok' }
1862
+ * buildProviderResult('ok', '', [], undefined, undefined) // { content: 'ok' }
1654
1863
  * ```
1655
1864
  */
1656
- export declare function buildProviderResult(content: string, thinking: string, tools: readonly ToolCall[], usage: TokenUsage | undefined): ProviderResult;
1865
+ export declare function buildProviderResult(content: string, thinking: string, tools: readonly ToolCall[], usage: TokenUsage | undefined, truncated: boolean | undefined): ProviderResult;
1657
1866
 
1658
1867
  /**
1659
1868
  * Builds the placeholder assistant recall call used to price recall framing.
@@ -1734,8 +1943,8 @@ export declare function buildRecords(input: LedgerProjectionInput): LedgerProjec
1734
1943
  *
1735
1944
  * @remarks
1736
1945
  * Pure and total. This is the unframed form a `sections` cap merge digests when it folds the
1737
- * oldest sections into one (a summary over their section summaries); the recap label is a
1738
- * `view()` presentation concern kept out of what the summarizer re-reads — see
1946
+ * oldest sections into one (a summary over their section summaries); the recap label is absent
1947
+ * from that merge input. A rolling fold instead re-reads the framed recap — see
1739
1948
  * {@link buildRecapMessage}.
1740
1949
  *
1741
1950
  * @param section - The compacted section to render
@@ -1757,10 +1966,13 @@ export declare function buildSummaryMessage(section: Section): Message;
1757
1966
  * its `events` are drained.
1758
1967
  *
1759
1968
  * @remarks
1760
- * The standard resolver-swap: a waiting `drain` parks on `#wake` (a void resolver);
1761
- * `push` / `close` / `fail` enqueue/flag, then fire `#wake` so the parked reader wakes,
1762
- * re-reads the buffer, and either yields the next chunk, returns (on `close`), or throws
1763
- * (on `fail`). Event-free, no `!` / `as` / `any`.
1969
+ * A waiting `drain` parks its void resolver in `#waiting`; `push` / `close` / `fail`
1970
+ * enqueue/flag, then fire and clear every parked resolver, so each parked reader wakes,
1971
+ * re-reads the shared buffer, and either takes the next chunk, returns (on `close`), throws
1972
+ * (on `fail`), or parks again when another reader took the chunk first. Taking a chunk is
1973
+ * synchronous, so each value reaches exactly one reader.
1974
+ * A `return()` issued while a read is parked waits for the next `push`, `close`, or `fail`.
1975
+ * The parked read takes a pushed value before that `return()` settles.
1764
1976
  *
1765
1977
  * @example
1766
1978
  * ```ts
@@ -1818,6 +2030,14 @@ export declare interface ChannelInterface<T> {
1818
2030
  /**
1819
2031
  * Reads the values back live, in write order — returning on `close` and throwing on `fail`.
1820
2032
  *
2033
+ * @remarks
2034
+ * Concurrent readers share one buffer: each value goes to exactly one reader, and `close` or
2035
+ * `fail` ends every reader, parked or not, after the buffer empties. A `push` after `close` or
2036
+ * `fail` is buffered rather than refused, so a reader still draining or a later `drain` yields
2037
+ * it before reporting the end.
2038
+ * A `return()` issued while a read is parked waits for the next `push`, `close`, or `fail`.
2039
+ * The parked read takes a pushed value before that `return()` settles.
2040
+ *
1821
2041
  * @returns A generator yielding each pushed value, returning on `close` and throwing on `fail`
1822
2042
  */
1823
2043
  drain(): AsyncGenerator<T, void>;
@@ -1825,11 +2045,12 @@ export declare interface ChannelInterface<T> {
1825
2045
 
1826
2046
  /**
1827
2047
  * Consumes a reported usage against a budget over what was already charged, so a turn's total
1828
- * draw matches the report and nothing is charged twice.
2048
+ * draw is never less than the report and nothing is charged twice.
1829
2049
  *
1830
2050
  * @remarks
1831
2051
  * The full `prompt` count is consumed because no earlier charge covers it. Each of `completion`
1832
- * and `total` is consumed less `charged`, floored at 0. The usage must already be sanitized: a
2052
+ * and `total` is consumed less `charged`, floored at 0. An estimate above the report is not
2053
+ * refunded. The usage must already be sanitized: a
1833
2054
  * non-finite field yields a non-finite charge, which the installed `Budget` refuses by throwing
1834
2055
  * a `range` `ContractError`. Without a budget the call consumes nothing.
1835
2056
  *
@@ -1883,7 +2104,7 @@ export declare class Classifier implements ClassifierInterface {
1883
2104
  * Asks the judge every question the filing still lacks, in the measured order.
1884
2105
  * @param requests - The request ids, which skip category questions and receive only enabled request-topic questions
1885
2106
  * @param signal - The caller's signal; an abort returns a fault with completed judgments and usage
1886
- * @returns The judgment keys and summed usage; a throw from the assign or entities handler, or a caller abort, returns the partial result with `fault`; a judge rejection under a live signal leaves that question undecided and sets no `fault`
2107
+ * @returns The judgment keys, summed usage, and one reading per logical question attempt, in asking order; a throw from the assign or entities handler, or a caller abort, returns the partial result with `fault`; a judge rejection under a live signal leaves that question undecided and sets no `fault`
1887
2108
  */
1888
2109
  classify(requests: ReadonlySet<string>, signal: AbortSignal): Promise<ClassifierResult>;
1889
2110
  /**
@@ -1930,7 +2151,7 @@ export declare interface ClassifierInterface {
1930
2151
  *
1931
2152
  * @param requests - The request ids, which skip category questions and receive only enabled request-topic questions
1932
2153
  * @param signal - The signal that aborts the questions; completed judgments stay recorded
1933
- * @returns The judgment keys and usage spent; a throw from the assign or entities handler, or a caller abort, returns the partial result with `fault`; a judge rejection under a live signal leaves that question undecided and sets no `fault`
2154
+ * @returns The judgment keys, usage spent, and one reading per logical question attempt; a throw from the assign or entities handler, or a caller abort, returns the partial result with `fault`; a judge rejection under a live signal leaves that question undecided and sets no `fault`
1934
2155
  */
1935
2156
  classify(requests: ReadonlySet<string>, signal: AbortSignal): Promise<ClassifierResult>;
1936
2157
  /**
@@ -1987,14 +2208,66 @@ export declare interface ClassifierOptions {
1987
2208
  readonly entities: LedgerEntityHandler;
1988
2209
  }
1989
2210
 
1990
- /** Carries the judgment keys one classification rests on and the judge usage it spent. */
2211
+ /**
2212
+ * Names what became of one filing question.
2213
+ *
2214
+ * @remarks
2215
+ * `cached` means the conversation already held a matching judgment, so the classifier did not call the judgment manager.
2216
+ * `skipped` means the classifier held the question after a deterministic failure and did not call the judgment manager.
2217
+ * `resolved` means the judgment manager's resolve call fulfilled. `failed` means it rejected while the caller's signal was live.
2218
+ * `aborted` means it rejected with a `JudgeAbortError` or after the caller's signal aborted.
2219
+ */
2220
+ export declare type ClassifierOutcome = 'cached' | 'skipped' | 'resolved' | 'failed' | 'aborted';
2221
+
2222
+ /** Names the filing question a classifier asked: a message's category, a message's topic, or an earlier and later message's amendment or supersession. */
2223
+ export declare type ClassifierPhase = 'category' | 'topic' | 'amends' | 'supersedes';
2224
+
2225
+ /**
2226
+ * Carries what became of one filing question: its judgment key, its phase, its outcome, and the time spent resolving it.
2227
+ *
2228
+ * @remarks
2229
+ * `key` is the judgment key the classifier asked or reused. `duration` is the monotonic milliseconds
2230
+ * the classifier waited on the judgment manager's resolve call, present for `resolved`, `failed`, and `aborted`
2231
+ * and absent for `cached` and `skipped`, which make no resolve call. A reading counts one logical
2232
+ * question attempt. It counts neither the physical wire calls a judge made nor a request that joined
2233
+ * another pending request, and a question the caller's signal aborted before its resolve call began
2234
+ * has no reading.
2235
+ */
2236
+ export declare interface ClassifierReading {
2237
+ readonly key: string;
2238
+ readonly phase: ClassifierPhase;
2239
+ readonly outcome: ClassifierOutcome;
2240
+ readonly duration?: number;
2241
+ }
2242
+
2243
+ /**
2244
+ * Carries the judgment keys one classification rests on and the judge usage it spent.
2245
+ *
2246
+ * @remarks
2247
+ * `readings` lists one {@link ClassifierReading} per logical question attempt, in asking order, including
2248
+ * the attempt a caller abort interrupted. Each result owns its array.
2249
+ */
1991
2250
  export declare interface ClassifierResult {
1992
2251
  readonly judgments: readonly string[];
1993
2252
  readonly usage?: TokenUsage;
2253
+ readonly readings: readonly ClassifierReading[];
1994
2254
  /** Holds the error when an assign or entities handler throw, or a caller abort, interrupts classification; judgments and usage retain the partial result. A judge rejection under a live signal sets no fault. */
1995
2255
  readonly fault?: Error;
1996
2256
  }
1997
2257
 
2258
+ /**
2259
+ * Carries the optional cause and context an error class with a machine-readable `code` takes at
2260
+ * construction.
2261
+ *
2262
+ * @remarks
2263
+ * `cause` holds the underlying failure. `context` holds the values that locate the failure, such
2264
+ * as the category and name of a missing entry.
2265
+ */
2266
+ export declare interface CodedErrorOptions {
2267
+ readonly cause?: unknown;
2268
+ readonly context?: Readonly<Record<string, unknown>>;
2269
+ }
2270
+
1998
2271
  /**
1999
2272
  * Collects whole exchanges, joining every exchange spanned by a tool group.
2000
2273
  *
@@ -2041,7 +2314,8 @@ export declare function collectImageData(files: readonly FileInterface[]): reado
2041
2314
  * A message is live when it is a user message, or a tool message whose reading is the last reading of its
2042
2315
  * call, and the input neither excludes it nor files it quiet or replaced. A reading replaces any
2043
2316
  * earlier reading of the same call, an empty one included, so an empty lookup leaves the earlier
2044
- * result out of every record.
2317
+ * result out of every record. A reading with no canonical identity stays live as its own source
2318
+ * and participates in no replacement.
2045
2319
  *
2046
2320
  * @param input - The messages, readings, and classification to read
2047
2321
  * @returns The live message ids
@@ -2157,13 +2431,21 @@ export declare function collectToolGroups(messages: readonly Message[]): Readonl
2157
2431
  * `DEFAULT_CONVERSATION_KEEP`). `sections` overrides the configured `sections` cap of the
2158
2432
  * conversation for this compaction only — after the folded section is appended, an overflow past
2159
2433
  * `sections` folds the oldest sections into one merged section. Default: the cap of the
2160
- * conversation (or no cap).
2434
+ * conversation (or no cap). `keep` must be a nonnegative safe integer; any other value throws a
2435
+ * {@link import('./errors.js').ConversationError} with code `'KEEP'`. A `sections` cap on a
2436
+ * rolling conversation throws code `'SECTIONS'`. `signal` cancels this
2437
+ * compaction: an already-aborted signal rejects with its reason before the summarizer is called,
2438
+ * the signal is passed to the {@link ConversationSummaryHandler}, and an abort while the
2439
+ * summarizer runs rejects with the reason at once and commits nothing after the abort. A fold
2440
+ * committed before an aborted overflow merge stays. Default: no signal.
2161
2441
  */
2162
2442
  export declare interface CompactOptions {
2163
2443
  /** Overrides the retained-tail size for this compaction. Default: the `keep` of the conversation. */
2164
2444
  readonly keep?: number;
2165
2445
  /** Overrides the `sections` cap for this compaction. Default: the cap of the conversation (or no cap). */
2166
2446
  readonly sections?: number;
2447
+ /** Cancels this compaction and is passed to the summarizer. Default: no signal. */
2448
+ readonly signal?: AbortSignal;
2167
2449
  }
2168
2450
 
2169
2451
  /**
@@ -2278,7 +2560,14 @@ export declare interface ContextSectionFormat<T> {
2278
2560
  * exchanges. A cut inside an assistant call group, which only a group spanning two exchanges
2279
2561
  * allows, moves before the group, so a tool result never stays live without its call. Returns
2280
2562
  * the section, or `undefined` when nothing folds. A {@link ConversationError} is thrown when no
2281
- * `#summarize` was supplied.
2563
+ * `#summarize` was supplied. Calls run one at a time, a fold commits only when its messages are
2564
+ * still the live entries after the summarizer settles, and `options.signal` rejects the call
2565
+ * with its reason at once and commits nothing after the abort.
2566
+ * - **Rolling.** With `rolling`, a fold makes one summarizer call over the framed recaps of all
2567
+ * prior sections then the slice, and replaces those sections with one that retains their
2568
+ * originals then the slice. It emits `compact` once and never `collapse`, commits nothing when
2569
+ * the call throws, aborts, or the slice or prior sections changed, and cannot combine with a
2570
+ * `sections` cap (`'SECTIONS'`).
2282
2571
  * - **`rehydrate(id)` / `search(query)`.** `rehydrate` returns the full original messages of a
2283
2572
  * known section and emits its `id` through `rehydrate`, even when the list is empty. An unknown
2284
2573
  * id returns `undefined` without an event. The read never reinserts messages. `search` is a case-insensitive
@@ -2344,9 +2633,10 @@ export declare const CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages]
2344
2633
 
2345
2634
  /**
2346
2635
  * Reports a conversation with no {@link ConversationSummaryHandler} to fold its messages with, a
2347
- * `sections` cap below `1`, or a judgment or judge request that JSON cannot carry — thrown by the
2348
- * `compact()` method of a {@link ConversationInterface}, its construction, or its judgment store,
2349
- * carrying the machine-readable `code` `'SUMMARIZER' | 'SECTIONS' | 'JUDGMENT'`.
2636
+ * `sections` cap below `1`, a `keep` that is not a nonnegative safe integer, or a judgment or
2637
+ * judge request that JSON cannot carry — thrown by the `compact()` method of a
2638
+ * {@link ConversationInterface}, its construction, or its judgment store, carrying the
2639
+ * machine-readable `code` `'SUMMARIZER' | 'SECTIONS' | 'JUDGMENT' | 'KEEP'`.
2350
2640
  *
2351
2641
  * @remarks
2352
2642
  * Compaction requires a summarizer (it digests the folded slice into a section summary); a
@@ -2354,18 +2644,40 @@ export declare const CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages]
2354
2644
  * is a programmer error and throws this with `'SUMMARIZER'`.
2355
2645
  * A `sections` cap (on {@link import('./types.js').ConversationOptions} /
2356
2646
  * {@link import('./types.js').ConversationManagerOptions} /
2357
- * {@link import('./types.js').CompactOptions}) must be `>= 1` — a sub-1 cap is a programmer
2358
- * error and throws this with `'SECTIONS'`. A judgment record or a judge request that JSON cannot
2359
- * carry, or whose copy fails its guard, is a programmer error and throws this with `'JUDGMENT'`.
2360
- * Narrow a caught value with
2361
- * {@link isConversationError} and branch on `error.code`.
2647
+ * {@link import('./types.js').CompactOptions}) must be `>= 1`, and cannot combine with
2648
+ * `rolling` — either is a programmer error and throws this with `'SECTIONS'`. A `keep` on the same options must be a nonnegative
2649
+ * safe integer — any other value is a programmer error and throws this with `'KEEP'`. A judgment
2650
+ * record or a judge request that JSON cannot carry, or whose copy fails its guard, is a
2651
+ * programmer error and throws this with `'JUDGMENT'`, its `context` naming the `question` id
2652
+ * where one exists. Narrow a caught value with {@link isConversationError} and branch on
2653
+ * `error.code`.
2362
2654
  */
2363
2655
  export declare class ConversationError extends Error {
2364
- /** Names the machine-readable condition — `'SUMMARIZER'`: a `compact()` with no summarizer; `'SECTIONS'`: a sub-1 `sections` cap; `'JUDGMENT'`: a judgment or judge request that is not JSON. */
2365
- readonly code: 'SUMMARIZER' | 'SECTIONS' | 'JUDGMENT';
2366
- constructor(code: 'SUMMARIZER' | 'SECTIONS' | 'JUDGMENT', message: string);
2656
+ /** Names the machine-readable condition — `'SUMMARIZER'`: a `compact()` with no summarizer; `'SECTIONS'`: a sub-1 `sections` cap, or a cap combined with `rolling`; `'JUDGMENT'`: a judgment or judge request that is not JSON; `'KEEP'`: a `keep` that is not a nonnegative safe integer. */
2657
+ readonly code: ConversationErrorCode;
2658
+ /** Holds the values that locate the failure, such as the `question` id of a malformed question, or `undefined` when none were supplied. */
2659
+ readonly context: Readonly<Record<string, unknown>> | undefined;
2660
+ /**
2661
+ * Creates the error with its code, message, and optional cause and context.
2662
+ *
2663
+ * @param code - The machine-readable {@link ConversationErrorCode}
2664
+ * @param message - The human-readable description
2665
+ * @param options - The optional {@link CodedErrorOptions}; `cause` reaches `Error.cause` only when present
2666
+ */
2667
+ constructor(code: ConversationErrorCode, message: string, options?: CodedErrorOptions);
2367
2668
  }
2368
2669
 
2670
+ /**
2671
+ * Names the machine-readable condition a {@link import('./errors.js').ConversationError} reports.
2672
+ *
2673
+ * @remarks
2674
+ * `'SUMMARIZER'`: a `compact()` with no summarizer. `'SECTIONS'`: a `sections` cap below `1`, or
2675
+ * any `sections` cap combined with `rolling`.
2676
+ * `'JUDGMENT'`: a judgment or judge request that JSON cannot carry, or whose copy fails its guard.
2677
+ * `'KEEP'`: a `keep` that is not a nonnegative safe integer.
2678
+ */
2679
+ export declare type ConversationErrorCode = 'SUMMARIZER' | 'SECTIONS' | 'JUDGMENT' | 'KEEP';
2680
+
2369
2681
  /**
2370
2682
  * Maps the push observation surface of a {@link ConversationInterface} — the compaction
2371
2683
  * moments a fire-and-forget observer subscribes to through `conversation.emitter.on`.
@@ -2395,15 +2707,18 @@ export declare type ConversationEventMap = {
2395
2707
  /**
2396
2708
  * Carries the data to author a {@link ConversationInterface} through a {@link
2397
2709
  * ConversationManagerInterface} — the optional `id`, a `summarize` override, a `keep` override, a
2398
- * `sections` cap override, the reserved `on` hooks, and a {@link ConversationSnapshot} to hydrate
2399
- * from.
2710
+ * `sections` cap override, the `rolling` switch, the reserved `on` hooks, and a
2711
+ * {@link ConversationSnapshot} to hydrate from.
2400
2712
  *
2401
2713
  * @remarks
2402
2714
  * `id` is the identity of the conversation. Default: a minted id. `summarize` overrides the
2403
2715
  * default {@link ConversationSummaryHandler} of the manager for this conversation. Default: the
2404
2716
  * manager default. `keep` overrides the default retained-tail size of the manager. `sections`
2405
- * overrides the default `sections` cap of the manager. `on` is the reserved listener key
2406
- * (initial {@link ConversationEventMap} listeners). `snapshot` is the construction-time hydration
2717
+ * overrides the default `sections` cap of the manager. `rolling` switches on rolling folds; a
2718
+ * manager `sections` default it would inherit throws `'SECTIONS'`. `on` is the reserved listener key
2719
+ * (initial {@link ConversationEventMap} listeners). `error` is the listener-error handler of the
2720
+ * conversation's emitter, so a throwing `on` listener reports there. Default: the emitter's own
2721
+ * handler. `snapshot` is the construction-time hydration
2407
2722
  * seam — a {@link ConversationSnapshot} whose `id` / `sections` / live tail are restored into the
2408
2723
  * conversation (the live `summarize` / `keep` / `on` re-supplied alongside it), the conversation
2409
2724
  * analogue of the `seed` option of {@link import('@orkestrel/workspace').WorkspaceOptions},
@@ -2420,7 +2735,11 @@ export declare interface ConversationInput {
2420
2735
  readonly keep?: number;
2421
2736
  /** Overrides the default `sections` cap of the manager for this conversation. */
2422
2737
  readonly sections?: number;
2738
+ /** Rolls every fold of this conversation into one section, passed on as {@link ConversationOptions.rolling}. Default: `false`; the manager holds no default. */
2739
+ readonly rolling?: boolean;
2423
2740
  readonly on?: EmitterHooks<ConversationEventMap>;
2741
+ /** Holds the listener-error handler of the conversation's emitter — a listener throw routes here, not to a domain event. */
2742
+ readonly error?: EmitterErrorHandler;
2424
2743
  /** Hydrates from a {@link ConversationSnapshot}, passed on as {@link ConversationOptions.snapshot}. */
2425
2744
  readonly snapshot?: ConversationSnapshot;
2426
2745
  }
@@ -2455,10 +2774,11 @@ export declare interface ConversationInput {
2455
2774
  * thrown when no `summarize` was supplied.
2456
2775
  * - **`summarizable` — whether a `compact()` can fold.** `true` when a
2457
2776
  * {@link ConversationSummaryHandler} was supplied, `false` otherwise. The automatic compaction
2458
- * of the agent loop (`AgentOptions.window`) gates on it so a conversation that has no
2777
+ * of the agent loop (`AgentOptions.window`) gates its fold on it so a conversation that has no
2459
2778
  * summarizer is never auto-compacted (and the loop never throws the `compact()` `SUMMARIZER`
2460
- * error from the auto path). A manual `compact()` still throws without a summarizer — only the
2461
- * auto path is guarded.
2779
+ * error from the auto path); the loop still measures its requests against the window, and a
2780
+ * strict agent refuses one that is over. A manual `compact()` still throws without a summarizer
2781
+ * — only the auto path is guarded.
2462
2782
  * - **`rehydrate(id)` / `search(query)` — read the retained originals.** `rehydrate` returns
2463
2783
  * the full original messages of a known section and emits its `id` through `rehydrate`, even
2464
2784
  * when the list is empty. An unknown id returns `undefined` without an event. The read never
@@ -2483,9 +2803,10 @@ export declare interface ConversationInterface {
2483
2803
  readonly sections: readonly Section[];
2484
2804
  /**
2485
2805
  * Reports whether a `compact()` can fold — `true` when a {@link ConversationSummaryHandler} was supplied.
2486
- * The automatic compaction of the agent loop (`AgentOptions.window`) gates on it (a
2806
+ * The automatic compaction of the agent loop (`AgentOptions.window`) gates its fold on it (a
2487
2807
  * non-summarizable conversation is never auto-compacted, so the auto path never throws the
2488
- * `SUMMARIZER` error); a manual `compact()` still throws without a summarizer.
2808
+ * `SUMMARIZER` error, though its requests are still measured); a manual `compact()` still throws
2809
+ * without a summarizer.
2489
2810
  */
2490
2811
  readonly summarizable: boolean;
2491
2812
  /** Counts the live (uncompacted) messages stored in the tail. */
@@ -2549,16 +2870,46 @@ export declare interface ConversationInterface {
2549
2870
  * a group moves before its assistant message, then back to whole exchanges again. Only a group
2550
2871
  * that spans two exchanges reaches that rule.
2551
2872
  *
2873
+ * A rolling conversation makes one summarizer call per fold, over the framed recaps of every
2874
+ * prior section followed by the slice with thinking stripped; the originals behind the recaps
2875
+ * are not sent. After the call settles, the slice and the prior sections must be unchanged; the
2876
+ * prior sections are then replaced atomically by one section retaining their originals in order
2877
+ * and then the slice, and the slice leaves the live tail. It emits `compact` once and never
2878
+ * `collapse`. A throw, an abort, or a changed input commits nothing. A fold with no eligible
2879
+ * slice makes no call. A `sections` cap, configured or supplied here, throws `'SECTIONS'`.
2880
+ *
2552
2881
  * When a `sections` cap is set and the fold pushes the section count over it, an overflow
2553
2882
  * merge step folds the oldest sections into one. If the `summarize` call of that merge throws,
2554
2883
  * the merge is skipped (sections transiently sit at `cap + 1`, no loss), no `compact` is
2555
2884
  * emitted, and the error propagates; the next successful `compact()` merges the section count
2556
2885
  * back to `cap`.
2557
2886
  *
2558
- * @param options - Optional {@link CompactOptions} (`keep` overrides the retained-tail size)
2559
- * @returns The folded {@link Section}, or `undefined` when nothing folded
2560
- * @throws ConversationError Thrown when no summarizer was supplied, or when the effective
2561
- * sections cap does not satisfy `>= 1` (code `'SECTIONS'`, including `NaN`)
2887
+ * Calls run one at a time: a call made while another is in flight waits for it, then computes
2888
+ * its own fold from the live tail it finds. A fold commits only when every folded message is
2889
+ * still the same live entry after the summarizer settles, and a merge commits only when the
2890
+ * folded sections are still the oldest; otherwise the call resolves `undefined` and emits
2891
+ * nothing, so a `remove` or `clear` during the summarizer leaves no section.
2892
+ *
2893
+ * A summarizer must not call `compact` on the conversation it serves. A call that finds no fold in
2894
+ * flight calls its summarizer before its own fold counts as in flight, so that summarizer's own
2895
+ * `compact` finds nothing to wait for and summarizes the same slice again; the commit check above
2896
+ * keeps one section, because the later fold finds its slice already folded and resolves
2897
+ * `undefined`. A call queued behind an earlier fold counts as in flight before its summarizer
2898
+ * runs, so its summarizer's own `compact` waits for the fold that called it.
2899
+ *
2900
+ * `options.signal` cancels the call. An already-aborted signal rejects with its reason before
2901
+ * the summarizer is called. An abort while waiting for an earlier call or while the summarizer
2902
+ * runs rejects with the reason at once, even when the summarizer ignores the signal, and the
2903
+ * call commits nothing afterwards; a fold committed before an aborted merge stays, as when the
2904
+ * merge throws.
2905
+ *
2906
+ * @param options - Optional {@link CompactOptions} (`keep`, `sections`, `signal`)
2907
+ * @returns The folded {@link Section}, or `undefined` when nothing folded or the folded
2908
+ * messages or sections changed while the summarizer ran
2909
+ * @throws ConversationError Thrown when no summarizer was supplied (code `'SUMMARIZER'`), when the
2910
+ * effective sections cap does not satisfy `>= 1` or combines with `rolling` (code `'SECTIONS'`,
2911
+ * including `NaN`), or when
2912
+ * the effective `keep` is not a nonnegative safe integer (code `'KEEP'`)
2562
2913
  */
2563
2914
  compact(options?: CompactOptions): Promise<Section | undefined>;
2564
2915
  /**
@@ -2750,7 +3101,11 @@ export declare interface ConversationManagerInterface {
2750
3101
  * into a fresh {@link ConversationInterface} through the `snapshot` option
2751
3102
  * (`add({ snapshot, ... })`, flowing the default `summarize` / `keep` of the manager in),
2752
3103
  * which registers and activates it, and it is returned.
2753
- * - Else (no store, or a store miss) ⇒ `undefined` (lenient — no throw).
3104
+ * - A conversation registered under `id` while the store read was pending wins: it is
3105
+ * activated and returned, and the stored snapshot is discarded. So a concurrent `add({ id })`
3106
+ * keeps its turns, and concurrent `open(id)` calls return the one registered instance.
3107
+ * - Else (no store, a store miss, or a stored snapshot whose `id` is not `id`) ⇒ `undefined`
3108
+ * (lenient — no throw).
2754
3109
  *
2755
3110
  * @param id - The conversation id to open
2756
3111
  * @returns The activated {@link ConversationInterface}, or `undefined` when neither registered nor stored
@@ -2759,12 +3114,12 @@ export declare interface ConversationManagerInterface {
2759
3114
  /**
2760
3115
  * Persists a registered conversation's {@link ConversationInterface.snapshot} to the optional
2761
3116
  * {@link ConversationStoreInterface} (`store`) — `true` when persisted, `false` when there is
2762
- * no store or the id is unknown, and never throwing.
3117
+ * no store or the id is unknown; a store failure propagates.
2763
3118
  *
2764
3119
  * @remarks
2765
3120
  * Lenient: when a `store` is set and `id` is registered, `store.set(conversation.snapshot())` is
2766
3121
  * awaited and `true` is returned; otherwise (no store, or an unknown id) it is a no-op returning
2767
- * `false` — never a throw, consistent with the lenient `switch`.
3122
+ * `false`. A failure from `store.set` propagates.
2768
3123
  *
2769
3124
  * @param id - The id of the registered conversation to persist
2770
3125
  * @returns True if the snapshot was persisted; false otherwise (no store, or an unknown id)
@@ -2792,7 +3147,8 @@ export declare interface ConversationManagerInterface {
2792
3147
  * `summarize` is the default summarizer flowed into every conversation the manager creates
2793
3148
  * (a per-`add` {@link ConversationInput.summarize} overrides it); a conversation created
2794
3149
  * with neither cannot `compact` (it throws a `ConversationError`). `keep` is the default
2795
- * retained-tail size (a per-`add` {@link ConversationInput.keep} overrides it). Default:
3150
+ * retained-tail size (a per-`add` {@link ConversationInput.keep} overrides it); it must be a
3151
+ * nonnegative safe integer, else construction throws a `ConversationError` with code `'KEEP'`. Default:
2796
3152
  * {@link import('./constants.js').DEFAULT_CONVERSATION_KEEP}. `sections` is the default cap
2797
3153
  * on the compacted `sections` list of a created conversation (a per-`add`
2798
3154
  * {@link ConversationInput.sections} overrides it). Default: no cap.
@@ -2826,13 +3182,18 @@ export declare interface ConversationManagerOptions {
2826
3182
  * {@link ConversationSummaryHandler} compaction needs — without it `compact()` throws a
2827
3183
  * {@link import('./errors.js').ConversationError} (a conversation can still store + view a
2828
3184
  * live tail; it cannot fold). `keep` is how many recent live messages a `compact()`
2829
- * retains verbatim (folding only the older ones). Default:
2830
- * {@link import('./constants.js').DEFAULT_CONVERSATION_KEEP} (`0` — a manual `compact()`
3185
+ * retains verbatim (folding only the older ones); it must be a nonnegative safe integer, else
3186
+ * construction throws a {@link import('./errors.js').ConversationError} with code `'KEEP'`.
3187
+ * Default: {@link import('./constants.js').DEFAULT_CONVERSATION_KEEP} (`0` — a manual `compact()`
2831
3188
  * folds every exchange before the newest user message into one section). `sections` is an
2832
3189
  * optional cap on the compacted `sections` list — when set (`>= 1`), a `compact()` that would
2833
3190
  * leave more than `sections` entries folds the oldest overflow into one merged section,
2834
3191
  * emitting `collapse`. A successful capped merge retains at most `Math.ceil(sections)` entries;
2835
- * fractions and positive infinity are accepted. Default: no cap.
3192
+ * fractions and positive infinity are accepted. Default: no cap. `rolling` makes each `compact()`
3193
+ * one summarizer call over the framed recaps of every prior section then the slice, replacing all
3194
+ * prior sections with one that retains their originals then the slice, with one `compact` event
3195
+ * and no `collapse`; it cannot combine with `sections`, which throws a `ConversationError` with
3196
+ * code `'SECTIONS'`. Default: `false`.
2836
3197
  * `snapshot` is the hydration seam — a {@link ConversationSnapshot} whose `id`, compacted
2837
3198
  * `sections`, and live tail are restored into the conversation, with the live `summarize` /
2838
3199
  * `keep` / `on` supplied alongside it (a summarizer is a function, not serialized data).
@@ -2851,6 +3212,13 @@ export declare interface ConversationOptions {
2851
3212
  readonly keep?: number;
2852
3213
  /** Caps the compacted `sections` list (`>= 1`); overflow folds into one merged section. Default: no cap. */
2853
3214
  readonly sections?: number;
3215
+ /**
3216
+ * Rolls every fold into one section: the summarizer digests the framed recaps of all prior
3217
+ * sections followed by the slice, and one section retaining their originals then the slice
3218
+ * replaces them. Combining it with a `sections` cap throws a `ConversationError` (`'SECTIONS'`).
3219
+ * Default: `false`.
3220
+ */
3221
+ readonly rolling?: boolean;
2854
3222
  /** Hydrates from a {@link ConversationSnapshot} — its `id` wins over `id`; restoring is silent. */
2855
3223
  readonly snapshot?: ConversationSnapshot;
2856
3224
  }
@@ -2995,10 +3363,16 @@ export declare interface ConversationStoreInterface {
2995
3363
  * The core conversation layer treats it as an opaque async function — it never reads which
2996
3364
  * backend produced the digest, keeping `core` free of any provider coupling.
2997
3365
  *
3366
+ * `signal` is the {@link CompactOptions.signal} of the `compact()` call the fold serves, or
3367
+ * `undefined` when that call supplied none. A summarizer must stop its work when it aborts. One
3368
+ * that ignores it still cannot hold the caller: `compact()` races the summarizer against the
3369
+ * signal, rejects with the abort reason, and discards a digest that arrives later.
3370
+ *
2998
3371
  * @param messages - The folded messages to digest into a summary
3372
+ * @param signal - The cancellation signal of the `compact()` call, or `undefined` when it supplied none
2999
3373
  * @returns The summary text (the model-written digest of those messages)
3000
3374
  */
3001
- export declare type ConversationSummaryHandler = (messages: readonly Message[]) => Promise<string>;
3375
+ export declare type ConversationSummaryHandler = (messages: readonly Message[], signal?: AbortSignal) => Promise<string>;
3002
3376
 
3003
3377
  /**
3004
3378
  * Owns a value by serializing it to JSON and parsing the text, so the copy shares nothing with its source.
@@ -3025,9 +3399,9 @@ export declare function copyJSON(value: unknown): unknown;
3025
3399
  * `generate` and a live `stream`.
3026
3400
  *
3027
3401
  * @remarks
3028
- * One private loop drives the turn; `generate` drains the same stream `stream`
3029
- * exposes, so they can never diverge. Each turn is bounded by one cancel folded from
3030
- * `signal` + `timeout` + `budget` (through `AbortSignal.any`) — any trip (or `abort()`)
3402
+ * One private loop drives the turn; `generate` awaits the settled result of the same run
3403
+ * `stream` exposes and leaves its live chunks undrained, so they can never diverge. Each turn is bounded by one cancel folded from
3404
+ * `signal` + `timeout` + `budget` + `allowance` (through `AbortSignal.any`) — any trip (or `abort()`)
3031
3405
  * commits a partial result (the stream's `result` resolves on a cancel, rejects only
3032
3406
  * on a genuine provider / tool error). The `scheduler` paces between turns; tool
3033
3407
  * iteration is capped at `limit`. Default: `DEFAULT_AGENT_LIMIT`. Tools are advertised
@@ -3072,15 +3446,18 @@ export declare function createAgent(provider: ProviderInterface, options?: Agent
3072
3446
  * `system` is the optional system prompt; `tools` / `instructions` / `workspaces` are pre-built
3073
3447
  * managers to reuse (empty ones are created when omitted, so `context.workspaces` is always
3074
3448
  * present); `scope` is the initial active filter (`undefined` ⇒ no filtering, changeable afterwards
3075
- * through `context.apply(...)`). The `messages` store is always fresh. `build()` folds the scoped
3076
- * instructions — plus the active workspace's scope-filtered text files (fenced) — into one leading
3077
- * `system` message and appends the scoped conversation (attaching the active workspace's
3078
- * scope-filtered image files' `base64` payload to the last user message), built fresh each call; the active
3079
- * workspace is the sole document/image context. Tools are advertised structurally (through
3449
+ * through `context.apply(...)`). `conversations` is the message source: a supplied manager's
3450
+ * active conversation is reused as `messages`, and a default conversation is added only when it
3451
+ * has none (a fresh registry with one default conversation is created when omitted). `build()`
3452
+ * folds the scoped instructions — plus the active workspace's scope-filtered text files (fenced) —
3453
+ * into one leading `system` message and appends the active conversation's `view()` (attaching the
3454
+ * active workspace's scope-filtered image files' `base64` payload to the last user message), built
3455
+ * fresh each call; the scope never filters the conversation. The active workspace is the sole
3456
+ * document/image context. Tools are advertised structurally (through
3080
3457
  * `tools.definitions()`, scope-filtered by the loop), never serialized into the prompt.
3081
3458
  *
3082
- * @param options - Optional `system` / `tools` / `instructions` / `workspaces` / `scope`
3083
- * (see {@link AgentContextOptions})
3459
+ * @param options - Optional `system` / `tools` / `instructions` / `workspaces` /
3460
+ * `conversations` / `scope` / `select` (see {@link AgentContextOptions})
3084
3461
  * @returns A working {@link AgentContextInterface}
3085
3462
  *
3086
3463
  * @example
@@ -3107,9 +3484,10 @@ export declare function createAgentContext(options?: AgentContextOptions): Agent
3107
3484
  * through `store` (+ `restore()` after a crash) are all the backing Queue's. `enqueue`
3108
3485
  * returns a per-job promise.
3109
3486
  * - **Durable + serializable.** Because `AgentJobInput` is JSON-serializable, a `store`
3110
- * (for example `createMemoryQueueStore` / `createDatabaseQueueStore`) persists outstanding
3111
- * jobs; `restore()` re-enqueues them after a restart and the `registry` rehydrates the
3112
- * live pieces from the names — so a job survives a crash.
3487
+ * persists outstanding jobs; `restore()` re-enqueues them and the `registry` rehydrates the
3488
+ * live pieces from the names. A job survives a crash only through a store whose rows outlive
3489
+ * the process (for example `createDatabaseQueueStore`); a `createMemoryQueueStore` row is
3490
+ * gone when the process exits.
3113
3491
  * - **Partial policy.** A partial result throws an
3114
3492
  * {@link import('./errors.js').AgentJobError} by default, so a job cancelled by its
3115
3493
  * attempt deadline / a queue abort retries while attempts remain; `partial: true`
@@ -3124,11 +3502,13 @@ export declare function createAgentContext(options?: AgentContextOptions): Agent
3124
3502
  *
3125
3503
  * @example
3126
3504
  * ```ts
3505
+ * import type { AgentJobInput, ProviderInterface } from '@orkestrel/agent'
3506
+ * import type { QueueStoreInterface } from '@orkestrel/queue'
3127
3507
  * import { createAgentQueue, createAgentRegistry } from '@orkestrel/agent'
3128
- * import { createMemoryQueueStore } from '@orkestrel/queue'
3129
3508
  *
3509
+ * declare const provider: ProviderInterface // any concrete implementation supplied by the host app
3510
+ * declare const store: QueueStoreInterface<AgentJobInput> // a durable store the host supplies
3130
3511
  * const registry = createAgentRegistry({ providers: { main: provider } })
3131
- * const store = createMemoryQueueStore(agentJobShape) // survives a restart through restore()
3132
3512
  * const queue = createAgentQueue({ registry, concurrency: 2, retries: 1, store })
3133
3513
  * const result = await queue.enqueue({ provider: 'main', messages: [{ role: 'user', content: 'ok?' }] })
3134
3514
  * ```
@@ -3179,7 +3559,9 @@ export declare function createAgentRegistry(options: AgentRegistryOptions): Agen
3179
3559
  * the handler adds only rehydration + the partial policy.
3180
3560
  * - **Sub-agent fan-out.** Each unit's handler receives a `ControllerInterface` whose
3181
3561
  * `spawn(childJob)` launches a child agent job through the same bounded queue (the
3182
- * child's result joins the run after the declared units, in spawn order). On a bounded
3562
+ * child's result joins the run after the declared units, in spawn order). A unit spawns its
3563
+ * declared `children` on its first attempt only, so a retried parent never runs a child
3564
+ * twice. On a bounded
3183
3565
  * runner, fan out and return — do not await a spawn inline from within the handler (a
3184
3566
  * slot-holding handler awaiting its own spawn can deadlock; see `ControllerInterface`).
3185
3567
  * - **Partial policy + cancellation.** Same as `createAgentQueue`: a partial result
@@ -3294,14 +3676,14 @@ export declare function createChannel<T>(): ChannelInterface<T>;
3294
3676
  * // Append the instruction as the FINAL user turn: a chat model emits nothing when the prompt
3295
3677
  * // ends on an assistant turn, so a leading-system instruction is unreliable.
3296
3678
  * const conversation = createConversation({
3297
- * summarize: async (messages) =>
3679
+ * summarize: async (messages, signal) =>
3298
3680
  * (
3299
3681
  * await provider.generate(
3300
3682
  * [
3301
3683
  * ...messages,
3302
3684
  * { id: 's', role: 'user', content: 'Summarize the conversation so far concisely.' },
3303
3685
  * ],
3304
- * AbortSignal.timeout(30_000),
3686
+ * signal ?? AbortSignal.timeout(30_000), // the compact() call's signal, when it passed one
3305
3687
  * )
3306
3688
  * ).content,
3307
3689
  * keep: 2, // retain at least the 2 most recent messages verbatim on each compaction
@@ -3541,7 +3923,8 @@ export declare function createRelay(options: RelayOptions): RelayHandler;
3541
3923
  * is the server half this one pairs with.
3542
3924
  *
3543
3925
  * @param options - The endpoint, parser factory, and HTTP call configuration
3544
- * @returns The concrete relay provider
3926
+ * @returns The relay provider behind the shared provider interface; construct
3927
+ * {@link RelayProvider} directly to reach its wire seams
3545
3928
  * @example Reaching the relay from the browser
3546
3929
  * ```ts
3547
3930
  * import type { ProviderInterface, ProviderParserInterface } from '@orkestrel/agent'
@@ -3555,14 +3938,13 @@ export declare function createRelay(options: RelayOptions): RelayHandler;
3555
3938
  * this.#pending += chunk
3556
3939
  * const lines = this.#pending.split(/\r\n|\n/)
3557
3940
  * this.#pending = lines.pop() ?? ''
3558
- * const records: Array<Readonly<Record<string, unknown>>> = []
3559
- * for (const line of lines) {
3560
- * if (line.trim().length === 0) continue
3561
- * const record = parseJSONAs(line, isRecord)
3562
- * if (record === undefined) throw new ProviderError('PROTOCOL', 'invalid JSON record')
3563
- * records.push(record)
3564
- * }
3565
- * return records
3941
+ * return lines
3942
+ * .filter((line) => line.trim().length > 0)
3943
+ * .map((line) => {
3944
+ * const record = parseJSONAs(line, isRecord)
3945
+ * if (record === undefined) throw new ProviderError('PROTOCOL', 'invalid JSON record')
3946
+ * return record
3947
+ * })
3566
3948
  * }
3567
3949
  * clear(): void {
3568
3950
  * this.#pending = ''
@@ -3581,7 +3963,7 @@ export declare function createRelay(options: RelayOptions): RelayHandler;
3581
3963
  * const result = await browser.generate(messages, abort.signal) // a ProviderResult like a local provider's
3582
3964
  * ```
3583
3965
  */
3584
- export declare function createRelayProvider(options: RelayProviderOptions): RelayProvider;
3966
+ export declare function createRelayProvider(options: RelayProviderOptions): ProviderInterface;
3585
3967
 
3586
3968
  /**
3587
3969
  * Creates a named scope — an immutable {@link ScopeInterface} from its `name` and its
@@ -3612,8 +3994,8 @@ export declare function createScope(input: ScopeInput): ScopeInterface;
3612
3994
  * by their minted `id`, in insertion order.
3613
3995
  *
3614
3996
  * @remarks
3615
- * Starts empty; `create` mints each scope's `id` and stores it (keyed by `id`, so it
3616
- * always adds — two scopes may share a `name`); `scopes()` lists them in insertion order;
3997
+ * Starts empty; `add` mints each scope's `id` and stores it (keyed by `id`, so it
3998
+ * never overwrites — two scopes may share a `name`); `scopes()` lists them in insertion order;
3617
3999
  * `remove` (one or a batch) reports `true` only when every supplied id was removed; `clear`
3618
4000
  * empties it. Carries an observable `emitter`
3619
4001
  * ({@link import('./types.js').ScopeManagerEventMap}) wired through the reserved `on`
@@ -3628,7 +4010,7 @@ export declare function createScope(input: ScopeInput): ScopeInterface;
3628
4010
  * import { createScopeManager } from '@orkestrel/agent'
3629
4011
  *
3630
4012
  * const scopes = createScopeManager()
3631
- * const reader = scopes.create({ name: 'reader', tools: ['search'] })
4013
+ * const reader = scopes.add({ name: 'reader', tools: ['search'] })
3632
4014
  * ```
3633
4015
  */
3634
4016
  export declare function createScopeManager(options?: ScopeManagerOptions): ScopeManagerInterface;
@@ -3724,7 +4106,8 @@ export declare function cutListing(entries: readonly string[], room: number): st
3724
4106
  * the row `{ id: snapshot.id, snapshot }`.
3725
4107
  * - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to
3726
4108
  * a {@link ConversationSnapshot} ({@link import('../validators.js').isConversationSnapshot} — the
3727
- * total guard for an untrusted storage read), or `undefined` if none is stored.
4109
+ * total guard for an untrusted storage read), or `undefined` if none is stored, the blob is
4110
+ * malformed, or the blob's own `id` is not the requested one.
3728
4111
  * - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
3729
4112
  *
3730
4113
  * Unlike a session store there is no idle-TTL / eviction — a persisted conversation lives until an
@@ -3755,11 +4138,34 @@ export declare class DatabaseConversationStore implements ConversationStoreInter
3755
4138
  * {@link ConversationSnapshotRow} `{ id; snapshot }` shape (the snapshot one opaque JSON column)
3756
4139
  */
3757
4140
  constructor(table: TableInterface<ConversationSnapshotRow>);
3758
- /** Resolves the persisted snapshot for `id`, narrowing the opaque JSON column back to a `ConversationSnapshot`. */
4141
+ /**
4142
+ * Resolves the persisted snapshot for `id`, narrowing the opaque JSON column back to a
4143
+ * `ConversationSnapshot`.
4144
+ *
4145
+ * @remarks
4146
+ * The column is an untrusted storage read, so a blob that fails
4147
+ * {@link import('../validators.js').isConversationSnapshot}, or whose own `id` is not `id`,
4148
+ * resolves `undefined` rather than a conversation under the wrong key.
4149
+ *
4150
+ * @param id - The conversation id to resolve (the row key)
4151
+ * @returns The persisted snapshot, or `undefined` when no row is stored, the blob is malformed,
4152
+ * or the blob names another id
4153
+ */
3759
4154
  get(id: string): Promise<ConversationSnapshot | undefined>;
3760
- /** Inserts or replaces under the snapshot's own `id` (no separate id param) — the row is `{ id, snapshot }`. */
4155
+ /**
4156
+ * Inserts or replaces under the snapshot's own `id` (no separate id param) — the row is
4157
+ * `{ id, snapshot }`.
4158
+ *
4159
+ * @param snapshot - The snapshot to store, keyed by its `id`
4160
+ * @returns A promise that resolves once the row is written
4161
+ */
3761
4162
  set(snapshot: ConversationSnapshot): Promise<void>;
3762
- /** Drops a snapshot by id; an absent id is a no-op (no throw). */
4163
+ /**
4164
+ * Drops a snapshot by id; an absent id is a no-op (no throw).
4165
+ *
4166
+ * @param id - The conversation id to drop
4167
+ * @returns A promise that resolves once the row is gone
4168
+ */
3763
4169
  delete(id: string): Promise<void>;
3764
4170
  }
3765
4171
 
@@ -3844,8 +4250,8 @@ export declare const DETERMINISTIC_JUDGE_ERROR: RegExp;
3844
4250
  /**
3845
4251
  * Estimates the context-token footprint of a batch of messages — each message's content plus
3846
4252
  * {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD}, a tool-call JSON estimate, a thinking estimate, and
3847
- * {@link import('./constants.js').IMAGE_TOKEN_ESTIMATE} for each attached image. The default
3848
- * `consumer` estimator for an agent's context budget (the
4253
+ * {@link import('./constants.js').IMAGE_TOKEN_ESTIMATE} for each attached image. The {@link estimateRequest} message term
4254
+ * for an agent's context budget (the
3849
4255
  * {@link import('./types.js').AgentOptions} `window`), a provider-independent approximation for conforming messages rather than an exact tokenizer count.
3850
4256
  *
3851
4257
  * @remarks
@@ -3855,12 +4261,12 @@ export declare const DETERMINISTIC_JUDGE_ERROR: RegExp;
3855
4261
  * `calls` plus, when present, {@link estimateTokens} over its `thinking` plus `images.length * `{@link import('./constants.js').IMAGE_TOKEN_ESTIMATE} (a coarse,
3856
4262
  * deliberately-approximate per-image cost — a base64 length is not a token proxy). Deterministic
3857
4263
  * and provider-free — the same messages always yield the same estimate, with an empty batch `0`.
3858
- * It is the fully-swappable default an agent's auto-compaction context budget charges each
3859
- * turn's new messages through; a caller wanting a sharper count supplies its own `consumer` to
3860
- * `createBudget` instead. A `calls` `JSON.stringify` that throws (a
3861
- * circular `ToolCall.arguments`) is caught and replaced with a fixed contribution of
4264
+ * It is the message term of the request estimate an agent's auto-compaction context budget
4265
+ * measures; a caller wanting a sharper count supplies its own `consumer` to `createBudget`
4266
+ * instead. A `calls` list JSON cannot carry (a circular or `BigInt`
4267
+ * `ToolCall.arguments`) contributes a fixed
3862
4268
  * {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD} (the same per-message overhead scale)
3863
- * instead of estimating the (unreachable) serialized length.
4269
+ * instead of an estimate of the (unreachable) serialized length.
3864
4270
  *
3865
4271
  * @param messages - The messages to estimate (a turn's appended assistant + tool messages)
3866
4272
  * @returns The summed estimated token count (`0` when empty)
@@ -3873,6 +4279,32 @@ export declare const DETERMINISTIC_JUDGE_ERROR: RegExp;
3873
4279
  */
3874
4280
  export declare function estimateMessages(messages: readonly Message[]): number;
3875
4281
 
4282
+ /**
4283
+ * Estimates the context-token footprint of a complete provider request — its messages through
4284
+ * {@link estimateMessages}, plus the rendered advertised `tools`, plus the rendered
4285
+ * `options.schema`. The `consumer` estimator an agent's `window` budget measures every request
4286
+ * through, a provider-independent heuristic rather than an exact tokenizer count.
4287
+ *
4288
+ * @remarks
4289
+ * Adds {@link estimateTokens} over the JSON rendering of `tools` and of `options.schema` to the
4290
+ * message estimate. An absent or empty `tools` list and an absent or empty `schema` add `0`, so a
4291
+ * request that advertises neither equals {@link estimateMessages} of its messages. A value JSON
4292
+ * cannot carry contributes a fixed {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD}. The
4293
+ * estimate counts JSON characters, not a provider's template: the chat template's own tokens, how
4294
+ * a provider encodes tool definitions, and the `think` flag are not counted, and no tokenizer
4295
+ * runs. A model's real prompt count can exceed it, so choose the window `max` with headroom
4296
+ * measured against the provider.
4297
+ *
4298
+ * @param request - The {@link ProviderRequest} to estimate: messages, advertised tools, call options
4299
+ * @returns The estimated token count (`0` for an empty request)
4300
+ *
4301
+ * @example
4302
+ * ```ts
4303
+ * estimateRequest({ messages: [{ id: '1', role: 'user', content: 'hello' }] }) // 6, equal to estimateMessages
4304
+ * ```
4305
+ */
4306
+ export declare function estimateRequest(request: ProviderRequest): number;
4307
+
3876
4308
  /**
3877
4309
  * Estimates the context-token footprint of a string — the deterministic `ceil(length / 4)`
3878
4310
  * character heuristic {@link estimateMessages} sums over a conversation's messages (the default
@@ -3966,9 +4398,17 @@ export declare function extractTokens(text: string): LedgerTokenSet;
3966
4398
  */
3967
4399
  export declare function extractWords(message: Message): LedgerWordSet;
3968
4400
 
3969
- /** Carries an HTTP failure message and the underlying body-read cause when available. */
4401
+ /**
4402
+ * Carries an HTTP failure message, the response status, and the underlying body-read cause when
4403
+ * available.
4404
+ *
4405
+ * @remarks
4406
+ * The members spread past `message` form the options of a coded HTTP error: `status` is the
4407
+ * response status, and `cause` is present only when the error body could not be read.
4408
+ */
3970
4409
  export declare interface FailureRead {
3971
4410
  readonly message: string;
4411
+ readonly status: number;
3972
4412
  readonly cause?: unknown;
3973
4413
  }
3974
4414
 
@@ -4103,7 +4543,7 @@ export declare interface GaugeCall {
4103
4543
  * Prices prompts in tokens and measures the room a request has left.
4104
4544
  *
4105
4545
  * @remarks
4106
- * `scale` and `overhead` hold the price the gauge read from its last observation.
4546
+ * `scale` holds the price from the last observation; `overhead` holds the construction price.
4107
4547
  */
4108
4548
  export declare interface GaugeInterface extends LedgerGauge {
4109
4549
  /**
@@ -4193,38 +4633,36 @@ export declare interface GaugeOptions extends LedgerGauge {
4193
4633
  export declare function handleAgentQueueJob(registry: AgentRegistryInterface, partial: boolean, input: AgentJobInput, context: QueueContext): Promise<AgentResult>;
4194
4634
 
4195
4635
  /**
4196
- * Handles one runner agent job by fanning out its declared children, rehydrating the
4197
- * parent through a registry with the controller signal, and applying the shared
4198
- * partial-result policy.
4636
+ * Checks whether a message carries at least one tool call.
4199
4637
  *
4200
4638
  * @remarks
4201
- * Children are fired and tracked through the runner controller without awaiting them
4202
- * inline, preserving bounded-runner progress.
4203
- *
4204
- * @param registry - The registry that rehydrates serializable jobs
4205
- * @param partial - The partial policy. If `true`, a partial result resolves; if `false`, it throws
4206
- * @param controller - The runner controller for this parent job
4207
- * @returns The settled parent agent result
4639
+ * Pure and total. An absent `calls` member and an empty list both return false, so a tool-free
4640
+ * assistant turn and a turn whose provider reported an empty call list read alike.
4208
4641
  *
4642
+ * @param message - The message to inspect; only its `calls` member is read
4643
+ * @returns True if `calls` holds at least one call; false otherwise
4209
4644
  * @example
4210
4645
  * ```ts
4211
- * const result = await handleAgentRunnerJob(registry, false, controller)
4646
+ * hasCalls({ calls: [{ id: 'order', name: 'lookup', arguments: {} }] }) // true
4647
+ * hasCalls({ calls: [] }) // false
4648
+ * hasCalls({}) // false
4212
4649
  * ```
4213
4650
  */
4214
- export declare function handleAgentRunnerJob(registry: AgentRegistryInterface, partial: boolean, controller: ControllerInterface<AgentJobInput, AgentResult>): Promise<AgentResult>;
4651
+ export declare function hasCalls(message: Pick<Message, 'calls'>): boolean;
4215
4652
 
4216
4653
  /**
4217
4654
  * Identifies a lookup reading for projection by its tool name and normalized arguments.
4218
4655
  *
4219
4656
  * @remarks
4220
4657
  * Two calls share an identity whatever the key order of their arguments at any depth. A top-level
4221
- * string argument is trimmed and uppercased first, so `"lh-1 "` and `"LH-1"` name one call.
4658
+ * string argument passes through {@link normalizeArgument} first, so `"lh-1 "` and `"LH-1"` name one call.
4222
4659
  * The ledger's repeat stop instead compares the tool name and canonical arguments without this
4223
4660
  * string normalization, so those argument spellings remain distinct within a request.
4661
+ * An uncanonicalizable call has no identity and neither replaces nor is replaced by another reading.
4224
4662
  *
4225
4663
  * @param name - The tool name
4226
4664
  * @param args - The arguments the call carried
4227
- * @returns The identity: the name, a space, and the canonical arguments
4665
+ * @returns The name, a space, and the canonical arguments, or `undefined` when uncanonicalizable
4228
4666
  *
4229
4667
  * @example
4230
4668
  * ```ts
@@ -4232,7 +4670,7 @@ export declare function handleAgentRunnerJob(registry: AgentRegistryInterface, p
4232
4670
  * identifyLookup('lookup_order', { opts: { region: 'north', tier: 2 }, id: ' BW-5512' }) // true
4233
4671
  * ```
4234
4672
  */
4235
- export declare function identifyLookup(name: string, args: Readonly<Record<string, unknown>>): string;
4673
+ export declare function identifyLookup(name: string, args: Readonly<Record<string, unknown>>): string | undefined;
4236
4674
 
4237
4675
  /**
4238
4676
  * Names the coarse, deliberately approximate per-image token cost
@@ -4247,6 +4685,43 @@ export declare function identifyLookup(name: string, args: Readonly<Record<strin
4247
4685
  */
4248
4686
  export declare const IMAGE_TOKEN_ESTIMATE = 512;
4249
4687
 
4688
+ /**
4689
+ * Infers which of a run's three failure layers stopped it — the meter allowance, a cost budget, or
4690
+ * the model's own token limit — and returns the stop record with the final call's readings, or
4691
+ * `undefined` when no layer stopped the run.
4692
+ *
4693
+ * @remarks
4694
+ * The layer that stopped the final provider call first wins. A model-truncated final answer whose
4695
+ * call returned unaborted is a `model` stop, even when that call's reconciled usage then exhausted a
4696
+ * budget or a cancel then landed before the run settled. Otherwise a `BudgetError` reason is a
4697
+ * `meter` stop when the allowance is exhausted, the reason is the allowance signal's own reason, its
4698
+ * context carries the allowance's `id` and `max`, and either the allowance is a separate instance or
4699
+ * the run had aborted when the call returned; any other `BudgetError` is a `budget` stop. The
4700
+ * identity test tells an allowance and a budget that share an `id` apart, and the readings test
4701
+ * tells a parent budget linked through the allowance's `signal` option apart from the allowance. An
4702
+ * external cancel, a deadline, or an `abort()` reason yields `undefined`. Two inputs still read as
4703
+ * the meter's: a charge a caller makes to the run's shared allowance from its own code while a call
4704
+ * streams, and a parent with the allowance's own `id` and `max` that aborted before `start()` when
4705
+ * the allowance then exhausts.
4706
+ *
4707
+ * @param reason - The run signal's abort reason (`undefined` while it is unaborted)
4708
+ * @param allowance - The run's meter allowance, or `undefined` for a run with no meter layer
4709
+ * @param budget - The run's cost budget, or `undefined` for an unbudgeted run
4710
+ * @param provider - The provider's trace `id`, named by a `model` stop
4711
+ * @param turn - The run's final turn record, or `undefined` when the run took no turn
4712
+ * @returns The stop record, or `undefined` when no layer stopped the run
4713
+ *
4714
+ * @example
4715
+ * ```ts
4716
+ * const allowance = createTokenBudget({ id: 'meter', max: 2 })
4717
+ * allowance.start()
4718
+ * allowance.consume({ prompt: 0, completion: 3, total: 3 })
4719
+ * inferStop(allowance.signal.reason, allowance, undefined, 'ollama', undefined)
4720
+ * // { layer: 'meter', id: 'meter', max: 2, consumed: 3 }
4721
+ * ```
4722
+ */
4723
+ export declare function inferStop(reason: unknown, allowance: BudgetInterface<TokenUsage> | undefined, budget: BudgetInterface<TokenUsage> | undefined, provider: string, turn: AgentTurn | undefined): AgentStop | undefined;
4724
+
4250
4725
  /**
4251
4726
  * Represents an immutable named directive — an {@link InstructionInterface} assembled once from
4252
4727
  * its input (`name` / `content` and an optional `priority`), the `id` minted at
@@ -4428,21 +4903,39 @@ export declare interface InstructionManagerInterface {
4428
4903
  /**
4429
4904
  * Adds one {@link InstructionInput}, or a batch — mints each `id`; adding an instruction whose
4430
4905
  * name is already stored overwrites it, last write wins.
4906
+ *
4907
+ * @param input - One {@link InstructionInput}, or a batch
4908
+ * @returns The stored instruction, or the stored batch in input order
4431
4909
  */
4432
4910
  add(input: InstructionInput): InstructionInterface;
4433
4911
  add(inputs: readonly InstructionInput[]): readonly InstructionInterface[];
4434
- /** Looks up one instruction by name (`undefined` when absent). */
4912
+ /**
4913
+ * Looks up one instruction by name (`undefined` when absent).
4914
+ *
4915
+ * @param name - The instruction name to look up
4916
+ * @returns The stored instruction, or `undefined` when absent
4917
+ */
4435
4918
  instruction(name: string): InstructionInterface | undefined;
4436
- /** Lists every instruction, sorted by descending `priority` (stable for equal priorities). */
4919
+ /**
4920
+ * Lists every instruction, sorted by descending `priority` (stable for equal priorities).
4921
+ *
4922
+ * @returns Every stored instruction, highest `priority` first
4923
+ */
4437
4924
  instructions(): readonly InstructionInterface[];
4438
4925
  /**
4439
4926
  * Renders one instruction for the prompt — its `override`, else the manager-options
4440
4927
  * `render`, else its `content`.
4928
+ *
4929
+ * @param instruction - The instruction to render
4930
+ * @returns The instruction's prompt text, `''` when its `override` is `''`
4441
4931
  */
4442
4932
  render(instruction: InstructionInterface): string;
4443
4933
  /**
4444
4934
  * Removes one instruction by name, or a batch. True if every supplied name was removed; false
4445
4935
  * otherwise.
4936
+ *
4937
+ * @param name - One instruction name, or a batch
4938
+ * @returns True if every supplied name was present and removed; false otherwise
4446
4939
  */
4447
4940
  remove(name: string): boolean;
4448
4941
  remove(names: readonly string[]): boolean;
@@ -4669,8 +5162,11 @@ export declare function isJudgeQuestion(value: unknown): value is JudgeQuestion;
4669
5162
  * Checks whether a stored judgment carries a valid question and exactly one answer or refusal.
4670
5163
  *
4671
5164
  * @remarks
4672
- * Malformed members and unreadable inputs return false. Answer fields follow the root judge
4673
- * value types; this storage guard does not impose a wire's probability or candidate limits.
5165
+ * Unknown members, malformed members, and unreadable inputs return false. Answer fields follow
5166
+ * the root judge value types; this storage guard does not impose a wire's probability or
5167
+ * candidate limits. The `time`, `noul`, and probability numbers must be finite, because a stored
5168
+ * record is owned through JSON, which turns `NaN` and the infinities into `null`; so every record
5169
+ * this guard accepts hydrates through a `JudgmentManager`.
4674
5170
  *
4675
5171
  * @param value - The unknown stored record
4676
5172
  * @returns True if the record satisfies the judgment contract; false otherwise
@@ -4717,6 +5213,27 @@ export declare function isLedgerError(value: unknown): value is LedgerError;
4717
5213
  */
4718
5214
  export declare function isMessage(value: unknown): value is Message;
4719
5215
 
5216
+ /**
5217
+ * Checks whether a value is a non-negative safe integer, `-0` included.
5218
+ *
5219
+ * @remarks
5220
+ * Total: a non-number, `NaN`, an infinity, a fraction, a negative number, and an integer past
5221
+ * `Number.MAX_SAFE_INTEGER` return false. It differs from `isNonNegativeInteger` in
5222
+ * `@orkestrel/contract` at two values: that guard rejects `-0` and admits an unsafe integer such
5223
+ * as `2 ** 53`, while this guard admits `-0` and rejects `2 ** 53`.
5224
+ *
5225
+ * @param value - The unknown count or limit candidate
5226
+ * @returns True if the value is a safe integer at or above zero; false otherwise
5227
+ * @example
5228
+ * ```ts
5229
+ * isNonNegativeSafeInteger(0) // true
5230
+ * isNonNegativeSafeInteger(-0) // true
5231
+ * isNonNegativeSafeInteger(2 ** 53) // false
5232
+ * isNonNegativeSafeInteger(1.5) // false
5233
+ * ```
5234
+ */
5235
+ export declare function isNonNegativeSafeInteger(value: unknown): value is number;
5236
+
4720
5237
  /**
4721
5238
  * Checks whether a value is a positive safe integer.
4722
5239
  * @param value - The value to inspect
@@ -5076,13 +5593,15 @@ export declare class Ledger implements LedgerInterface {
5076
5593
  */
5077
5594
  calibrate(signal: AbortSignal): Promise<LedgerGauge>;
5078
5595
  /**
5079
- * Appends a request and serves it, recovering an unfinished first pass with one answer pass.
5596
+ * Appends a request and serves it, recovering an unfinished or rejected first pass with one answer pass.
5080
5597
  * A calibration call that reports no prompt usage, or a prompt usage of 0 or less, rejects with `LedgerError` code `'GAUGE'`; a provider error during calibration rejects with that error.
5598
+ * When the answer pass rejects, `respond` rejects with that error. Every rejection leaves the ledger free for a later call.
5081
5599
  * @param content - The request text
5082
5600
  * @param signal - The caller's cancellation signal; an abort during calibration rejects with its reason
5083
- * @returns The final pass and the usage of every pass
5601
+ * @returns The final pass, the usage of every pass, and the filing readings of this request alone
5084
5602
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
5085
5603
  * @throws {LedgerError} Thrown when a calibration call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
5604
+ * @throws {ContractError} Thrown when `signal` is defined and is not a native `AbortSignal`
5086
5605
  */
5087
5606
  respond(content: string, signal?: AbortSignal): Promise<LedgerResult>;
5088
5607
  }
@@ -5184,6 +5703,12 @@ export declare type LedgerEntityHandler = (text: string, partial: boolean) => Re
5184
5703
  export declare class LedgerError extends Error {
5185
5704
  /** Names the machine-readable condition; the {@link LedgerErrorCode} union describes each code. */
5186
5705
  readonly code: LedgerErrorCode;
5706
+ /**
5707
+ * Records the failure condition and its description.
5708
+ *
5709
+ * @param code - The machine-readable condition, exposed as `error.code`
5710
+ * @param message - The human-readable description of the failure
5711
+ */
5187
5712
  constructor(code: LedgerErrorCode, message: string);
5188
5713
  }
5189
5714
 
@@ -5242,17 +5767,22 @@ export declare interface LedgerInterface {
5242
5767
  * @remarks
5243
5768
  * The ledger calibrates first while `gauge` is undefined. When the first pass ends without final
5244
5769
  * text and the caller did not abort, the ledger adds the results and cue notes and makes one
5245
- * answer pass that advertises no tools. A `respond` or `calibrate` call while either is in flight
5770
+ * answer pass that advertises no tools. A rejected first pass counts as unfinished; when the
5771
+ * answer pass rejects as well, `respond` rejects with that pass's error. A `respond` or `calibrate` call while either is in flight
5246
5772
  * rejects with an `AgentError` whose `code` is `'CONCURRENCY'`. A calibration call that reports no prompt usage, or a prompt usage of 0 or less, rejects with
5247
5773
  * `LedgerError` code `'GAUGE'`; a provider error during calibration rejects with that error.
5248
5774
  * Direct agent runs share an active request's gauge readings and repeat stop, even when their
5249
5775
  * selections fault. Only `respond` and `calibrate` calls are refused by the concurrency guard.
5776
+ * A run no `respond` call owns, including a run during calibration, reads no request's state:
5777
+ * its lookup and `recall` calls run with no repeat stop, recall limit, or close rule.
5778
+ * Every rejection leaves the ledger free for a later call.
5250
5779
  *
5251
5780
  * @param content - The request text
5252
5781
  * @param signal - An optional caller signal; an abort during calibration rejects with its reason; afterward it ends the request partial and skips the answer pass
5253
- * @returns The reply and the passes the request took
5782
+ * @returns The reply, the passes the request took, and the filing readings of this request alone
5254
5783
  * @throws {LedgerError} Thrown when a calibration call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
5255
5784
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
5785
+ * @throws {ContractError} Thrown when `signal` is defined and is not a native `AbortSignal`
5256
5786
  */
5257
5787
  respond(content: string, signal?: AbortSignal): Promise<LedgerResult>;
5258
5788
  /**
@@ -5519,6 +6049,202 @@ export declare interface LedgerRegistry {
5519
6049
  readonly owners: ReadonlyMap<string, readonly string[]>;
5520
6050
  }
5521
6051
 
6052
+ /**
6053
+ * Holds what one ledger request keeps while it is served: its message, its filing readings and
6054
+ * plan, the gauge calls of its passes, and its repeat and recall stops.
6055
+ *
6056
+ * @remarks
6057
+ * A ledger creates one request per `respond` call and drops it when the call settles. Each pass the
6058
+ * request opens holds its own selection, boundary, usage, and calls; the request reads the current
6059
+ * pass, the last one opened. {@link LedgerRequestInterface} describes each member.
6060
+ *
6061
+ * @example
6062
+ * ```ts
6063
+ * const request = new LedgerRequest('What approval is needed?', 1)
6064
+ * request.enter(conversation) // the appended user message
6065
+ * request.admit('lookup', { id: 'BW-20931' })
6066
+ * request.admit('lookup', { id: 'BW-20931' }) // throws Error(LEDGER_NOTES.repeat)
6067
+ * request.recall()
6068
+ * request.recall() // throws Error(LEDGER_NOTES.closure): the limit of 1 is spent
6069
+ * ```
6070
+ */
6071
+ export declare class LedgerRequest implements LedgerRequestInterface {
6072
+ #private;
6073
+ /**
6074
+ * Holds the request's content and its recall limit.
6075
+ *
6076
+ * @param content - The request text `enter` appends
6077
+ * @param limit - The `recall` calls the request can make. Default: the `DEFAULT_RECALL_LIMIT` constant
6078
+ * @throws {LedgerError} Thrown when `limit` is not a nonnegative safe integer (code `'LIMIT'`)
6079
+ */
6080
+ constructor(content: string, limit?: number);
6081
+ /**
6082
+ * Returns the request message.
6083
+ *
6084
+ * @returns The message `enter` appended, or undefined before it
6085
+ */
6086
+ get message(): Message | undefined;
6087
+ /**
6088
+ * Returns the selection the request's last successful plan produced.
6089
+ *
6090
+ * @returns The planned selection, or undefined before one
6091
+ */
6092
+ get plan(): Selection | undefined;
6093
+ /**
6094
+ * Returns the selection the current pass's run adopted last.
6095
+ *
6096
+ * @returns The selection, or undefined before one
6097
+ */
6098
+ get selection(): Selection | undefined;
6099
+ /**
6100
+ * Returns the conversation length the current pass's calls count additions from.
6101
+ *
6102
+ * @returns The length, or undefined before a pass
6103
+ */
6104
+ get boundary(): number | undefined;
6105
+ /**
6106
+ * Returns the summed usage of the current pass.
6107
+ *
6108
+ * @returns The summed usage, or undefined while none reported usage
6109
+ */
6110
+ get usage(): TokenUsage | undefined;
6111
+ /**
6112
+ * Returns the gauge calls of every pass.
6113
+ *
6114
+ * @returns The calls in order, the call in flight last
6115
+ */
6116
+ get calls(): readonly GaugeCall[];
6117
+ /**
6118
+ * Returns the filing readings of the request's planning selections.
6119
+ *
6120
+ * @returns The readings in asking order
6121
+ */
6122
+ get readings(): readonly ClassifierReading[];
6123
+ enter(conversation: ConversationInterface): Message;
6124
+ open(boundary: number): void;
6125
+ adopt(selection: Selection, boundary: number): void;
6126
+ observe(call: GaugeCall, position: number): void;
6127
+ spend(usage: TokenUsage): void;
6128
+ think(messages: readonly Message[]): void;
6129
+ file(readings: readonly ClassifierReading[], plan?: Selection): void;
6130
+ admit(name: string, args: Readonly<Record<string, unknown>>): void;
6131
+ recall(): void;
6132
+ close(): void;
6133
+ list(result: string, lines: ReadonlyMap<string, string>): void;
6134
+ listing(result: string): ReadonlyMap<string, string> | undefined;
6135
+ }
6136
+
6137
+ /**
6138
+ * Holds what one `respond` call of a ledger keeps while it serves its request: the request message,
6139
+ * the filing readings and the plan its selections produced, the gauge calls of its passes, and its
6140
+ * repeat and recall stops.
6141
+ *
6142
+ * @remarks
6143
+ * A ledger creates one request per `respond` call and drops it when the call settles, so no request
6144
+ * state reaches a later call or a run no call owns. Each agent run the call makes is a pass: `open`
6145
+ * starts one, and `selection`, `boundary`, and `usage` read the current pass, the last one opened.
6146
+ * `calls` lists the gauge calls of every pass in order. A call stays in flight, taking its usage and
6147
+ * thinking, until its pass observes the next call, and is final from then on.
6148
+ */
6149
+ export declare interface LedgerRequestInterface {
6150
+ /** Holds the request message, or undefined until `enter` appends it. */
6151
+ readonly message: Message | undefined;
6152
+ /** Holds the selection the request's last successful plan produced, or undefined before one. */
6153
+ readonly plan: Selection | undefined;
6154
+ /** Holds the selection the current pass's run adopted last, or undefined before one. */
6155
+ readonly selection: Selection | undefined;
6156
+ /** Holds the conversation length at the current pass's start or at its last adopted selection, or undefined before a pass. */
6157
+ readonly boundary: number | undefined;
6158
+ /** Holds the summed usage of the current pass's selections and calls, or undefined while none reported usage. */
6159
+ readonly usage: TokenUsage | undefined;
6160
+ /** Holds the gauge calls of every pass in order, the call in flight last. */
6161
+ readonly calls: readonly GaugeCall[];
6162
+ /** Holds the filing readings of every planning selection the request made, in asking order. */
6163
+ readonly readings: readonly ClassifierReading[];
6164
+ /**
6165
+ * Appends the request's content to a conversation as a user message, once, and holds it as `message`.
6166
+ *
6167
+ * @param conversation - The conversation the request is served from
6168
+ * @returns The request message; a later call returns it without appending again
6169
+ */
6170
+ enter(conversation: ConversationInterface): Message;
6171
+ /**
6172
+ * Opens a pass, which becomes the current pass.
6173
+ *
6174
+ * @param boundary - The conversation length when the pass's run starts
6175
+ */
6176
+ open(boundary: number): void;
6177
+ /**
6178
+ * Records the selection the current pass's run adopted and adds the usage it spent.
6179
+ *
6180
+ * @param selection - The selection the run built its prompt from
6181
+ * @param boundary - The conversation length when the run adopted it
6182
+ */
6183
+ adopt(selection: Selection, boundary: number): void;
6184
+ /**
6185
+ * Starts a gauge call in the current pass, making that pass's call in flight final.
6186
+ *
6187
+ * @param call - The call's estimate and advertised tool count
6188
+ * @param position - The conversation length when the call starts
6189
+ */
6190
+ observe(call: GaugeCall, position: number): void;
6191
+ /**
6192
+ * Adds one call's reported usage to the current pass and records it on the call in flight.
6193
+ *
6194
+ * @param usage - The usage the provider reported for the call
6195
+ */
6196
+ spend(usage: TokenUsage): void;
6197
+ /**
6198
+ * Records the thinking share of the call in flight, read from the first assistant message after
6199
+ * the call's position.
6200
+ *
6201
+ * @remarks
6202
+ * Records nothing while the call in flight has no reported completion or no such message carries
6203
+ * thinking.
6204
+ *
6205
+ * @param messages - The conversation's messages
6206
+ */
6207
+ think(messages: readonly Message[]): void;
6208
+ /**
6209
+ * Appends the readings of one planning selection and holds its plan when planning succeeded.
6210
+ *
6211
+ * @param readings - The selection's filing readings, in asking order
6212
+ * @param plan - The planned selection, or undefined when planning failed
6213
+ */
6214
+ file(readings: readonly ClassifierReading[], plan?: Selection): void;
6215
+ /**
6216
+ * Admits one tool call of the request, compared by tool name and canonical arguments.
6217
+ *
6218
+ * @param name - The tool name
6219
+ * @param args - The call's arguments
6220
+ * @throws {Error} Thrown with the {@link LedgerNote} `repeat` text when the request already admitted the same call
6221
+ * @throws {Error} Thrown when the arguments have no canonical identity
6222
+ */
6223
+ admit(name: string, args: Readonly<Record<string, unknown>>): void;
6224
+ /**
6225
+ * Counts one `recall` call, closing the request's recall tool when its recalls reach the limit.
6226
+ *
6227
+ * @throws {Error} Thrown with the {@link LedgerNote} `closure` text when the recall tool is closed
6228
+ */
6229
+ recall(): void;
6230
+ /** Closes the request's recall tool, so every later `recall` call throws the closure note. */
6231
+ close(): void;
6232
+ /**
6233
+ * Holds the stored first line of each lookup a recall result listed, keyed by its listed line.
6234
+ *
6235
+ * @param result - The recall result text
6236
+ * @param lines - Each listed lookup line mapped to the first line of its stored content
6237
+ */
6238
+ list(result: string, lines: ReadonlyMap<string, string>): void;
6239
+ /**
6240
+ * Returns the stored first lines a recall result listed.
6241
+ *
6242
+ * @param result - The recall result text
6243
+ * @returns Each listed lookup line mapped to its stored first line, or undefined when no recall returned `result`
6244
+ */
6245
+ listing(result: string): ReadonlyMap<string, string> | undefined;
6246
+ }
6247
+
5522
6248
  /**
5523
6249
  * Carries the outcome of one request a ledger served: the agent result of its reply and every pass
5524
6250
  * it took.
@@ -5526,10 +6252,14 @@ export declare interface LedgerRegistry {
5526
6252
  * @remarks
5527
6253
  * `passes` holds the first pass and, when the ledger ran one, the answer pass. `content` and
5528
6254
  * `partial` are the last pass's, `usage` sums the passes' usage, and `thinking` joins the passes'
5529
- * reasoning; each optional member is present when any pass reported it.
6255
+ * reasoning; each optional member is present when any pass reported it. `stop` is the last pass's
6256
+ * stop, when one stopped it. `readings` lists every {@link ClassifierReading} the request's
6257
+ * selections recorded, in asking order. A request's reading count is its logical questions, never
6258
+ * its physical wire calls.
5530
6259
  */
5531
6260
  export declare interface LedgerResult extends AgentResult {
5532
6261
  readonly passes: readonly AgentResult[];
6262
+ readonly readings: readonly ClassifierReading[];
5533
6263
  }
5534
6264
 
5535
6265
  /**
@@ -5695,6 +6425,54 @@ export declare function matchesJudgment(judgment: Judgment, question: JudgeQuest
5695
6425
  */
5696
6426
  export declare const MAX_ERROR_BODY_LENGTH = 2048;
5697
6427
 
6428
+ /**
6429
+ * Measures the disjoint estimate components of one message — its content, framing, rendered calls,
6430
+ * thinking, and attached images — as the same heuristic units {@link estimateMessages} sums.
6431
+ *
6432
+ * @remarks
6433
+ * The result is a structural estimate, not a tokenizer allocation. `id` and `role` are joins back to
6434
+ * the message, not token components, and a provider's template effects and the estimation error are
6435
+ * not individually attributed. `content` is `ceil(length / 4)`, `framing` is
6436
+ * {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD}, `calls` is the estimate of the JSON-rendered
6437
+ * calls (`0` when the message carries none, the framing overhead when they cannot be rendered),
6438
+ * `thinking` is the estimate of the thinking (`0` when absent), and `images` is the attached image
6439
+ * count times {@link import('./constants.js').IMAGE_TOKEN_ESTIMATE}. The message is not mutated.
6440
+ *
6441
+ * @param message - The message to measure
6442
+ * @returns The message's identity and its estimate components
6443
+ *
6444
+ * @example
6445
+ * ```ts
6446
+ * measureMessage({ id: '1', role: 'user', content: 'hello' })
6447
+ * // { id: '1', role: 'user', content: 2, framing: 4, calls: 0, thinking: 0, images: 0 }
6448
+ * ```
6449
+ */
6450
+ export declare function measureMessage(message: Message): MessageMeasure;
6451
+
6452
+ /**
6453
+ * Measures the disjoint estimate components of a complete provider request — each message through
6454
+ * {@link measureMessage}, plus the rendered advertised `tools` and `options.schema` — as the same
6455
+ * heuristic units {@link estimateRequest} sums.
6456
+ *
6457
+ * @remarks
6458
+ * The result is a structural estimate, not a tokenizer allocation; a provider's template effects, how
6459
+ * it encodes tool definitions, the `think` flag, and the estimation error are not individually
6460
+ * attributed. `messages` keeps the request's message order, duplicate ids included. `tools` and
6461
+ * `schema` are each `0` when absent or empty and the estimate of their JSON rendering otherwise, or
6462
+ * the {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD} when JSON cannot carry them. The request
6463
+ * is not mutated.
6464
+ *
6465
+ * @param request - The {@link ProviderRequest} to measure: messages, advertised tools, call options
6466
+ * @returns The per-message components and the tools and schema estimates
6467
+ *
6468
+ * @example
6469
+ * ```ts
6470
+ * measureRequest({ messages: [{ id: '1', role: 'user', content: 'hello' }] })
6471
+ * // { messages: [{ id: '1', role: 'user', content: 2, framing: 4, calls: 0, thinking: 0, images: 0 }], tools: 0, schema: 0 }
6472
+ * ```
6473
+ */
6474
+ export declare function measureRequest(request: ProviderRequest): RequestMeasure;
6475
+
5698
6476
  /**
5699
6477
  * Implements the {@link ConversationStoreInterface} in memory — a process-lifetime `Map` of
5700
6478
  * {@link ConversationSnapshot} records keyed by conversation id, the default store
@@ -5910,6 +6688,30 @@ export declare interface MessageManagerInterface {
5910
6688
  clear(): void;
5911
6689
  }
5912
6690
 
6691
+ /**
6692
+ * Holds the disjoint estimate components of one message, in heuristic estimate units.
6693
+ *
6694
+ * @remarks
6695
+ * The structural estimate is not a tokenizer allocation. `id` and `role` are joins back to the
6696
+ * message, not token components. A message's estimate is the sum of `content`, `framing`, `calls`,
6697
+ * `thinking`, and `images`; no total is stored. A provider's template effects and the estimation
6698
+ * error are not individually attributed.
6699
+ */
6700
+ export declare interface MessageMeasure {
6701
+ readonly id: string;
6702
+ readonly role: MessageRole;
6703
+ /** Holds the `ceil(length / 4)` estimate of the message `content`. */
6704
+ readonly content: number;
6705
+ /** Holds the fixed per-message framing overhead. */
6706
+ readonly framing: number;
6707
+ /** Holds the estimate of the JSON-rendered `calls`, or the framing overhead when they cannot be rendered; `0` when the message carries none. */
6708
+ readonly calls: number;
6709
+ /** Holds the estimate of the message `thinking`; `0` when absent. */
6710
+ readonly thinking: number;
6711
+ /** Holds the per-image estimate times the attached image count; `0` when none. */
6712
+ readonly images: number;
6713
+ }
6714
+
5913
6715
  /** Names the role a {@link Message} plays in a conversation turn. */
5914
6716
  export declare type MessageRole = (typeof MESSAGE_ROLES)[number];
5915
6717
 
@@ -5934,6 +6736,23 @@ images: OptionalShape<ArrayShape<StringShape>>;
5934
6736
  thinking: OptionalShape<StringShape>;
5935
6737
  }, boolean | ContractShape>;
5936
6738
 
6739
+ /**
6740
+ * Normalizes a lookup argument string to the spelling that lookup identity and owner linking compare.
6741
+ *
6742
+ * @remarks
6743
+ * {@link identifyLookup} and {@link linkOwners} read an argument through this one form, so a reading
6744
+ * a later lookup replaces and the owner link of its argument agree on the spelling.
6745
+ *
6746
+ * @param value - The string argument a lookup call carried
6747
+ * @returns The argument trimmed and uppercased
6748
+ *
6749
+ * @example
6750
+ * ```ts
6751
+ * normalizeArgument(' bw-5512 ') // 'BW-5512'
6752
+ * ```
6753
+ */
6754
+ export declare function normalizeArgument(value: string): string;
6755
+
5937
6756
  /** Carries the probability that the answer is yes; the protocol's noul field. */
5938
6757
  export declare interface NoulAnswer {
5939
6758
  readonly form: 'noul';
@@ -5972,11 +6791,20 @@ export declare const PLACED_CATEGORIES: readonly LedgerCategory[];
5972
6791
  * A message that is not live still places, because a correction joins where its earlier side would
5973
6792
  * join.
5974
6793
  *
6794
+ * With `settled`, the walk returns a placement the map holds without walking again, and records each
6795
+ * placement whose walk, its nested walks included, excluded no earlier side through `seen`. Such a
6796
+ * walk reaches no cycle. While every `seen` holds only the path that reached the message, as
6797
+ * {@link buildRecords} passes it, that path never holds a message the recorded walk reached, so a
6798
+ * fresh walk returns the same keys in the same order. A placement a cycle cut stays unrecorded,
6799
+ * because its keys depend on the path that reached it. Sharing one map across the members of one
6800
+ * input bounds a dense amendment graph to one walk per message.
6801
+ *
5975
6802
  * @param input - The owners, entities, and classification to read
5976
6803
  * @param links - The owner of each linked lookup argument, from {@link linkOwners}
5977
6804
  * @param amending - The earlier sides of each message's amendment pairs
5978
6805
  * @param id - The message id
5979
6806
  * @param seen - The ids already visited, which stops a cycle of amendment pairs
6807
+ * @param settled - The placements already walked for the same `input`, `links`, and `amending`; the walk adds to it. Default: no map, so every call walks
5980
6808
  * @returns The record keys, empty when the message is an orphan
5981
6809
  *
5982
6810
  * @example
@@ -5984,7 +6812,7 @@ export declare const PLACED_CATEGORIES: readonly LedgerCategory[];
5984
6812
  * placeMember(input, new Map(), new Map(), 'user-1', new Set()) // Set { 'owner:BW-20931' }
5985
6813
  * ```
5986
6814
  */
5987
- export declare function placeMember(input: LedgerProjectionInput, links: ReadonlyMap<string, string>, amending: ReadonlyMap<string, readonly string[]>, id: string, seen: ReadonlySet<string>): ReadonlySet<string>;
6815
+ export declare function placeMember(input: LedgerProjectionInput, links: ReadonlyMap<string, string>, amending: ReadonlyMap<string, readonly string[]>, id: string, seen: ReadonlySet<string>, settled?: Map<string, ReadonlySet<string>>): ReadonlySet<string>;
5988
6816
 
5989
6817
  /**
5990
6818
  * Reports a provider stream cancelled mid-flight by its bound signal — thrown by the
@@ -6008,8 +6836,8 @@ export declare class ProviderAbortError extends Error {
6008
6836
 
6009
6837
  /**
6010
6838
  * Represents one streamed delta the `stream` method of a {@link ProviderInterface} yields — a unit tagged by
6011
- * the channel it belongs to, so the agent loop can re-surface answer content and live reasoning
6012
- * separately as it pumps.
6839
+ * the channel it belongs to, so the agent loop can forward answer content and live reasoning
6840
+ * separately as each delta arrives.
6013
6841
  *
6014
6842
  * @remarks
6015
6843
  * The discriminant `channel` names the axis that varies: a
@@ -6067,6 +6895,7 @@ export declare interface ProviderIncrement {
6067
6895
  readonly thinking: string;
6068
6896
  readonly tools: readonly ToolCall[];
6069
6897
  readonly usage?: TokenUsage;
6898
+ readonly truncated?: boolean;
6070
6899
  readonly result?: ProviderResult;
6071
6900
  }
6072
6901
 
@@ -6238,7 +7067,9 @@ schema: OptionalShape<ObjectShape<Record<never, never>, JSONShape>>;
6238
7067
  * allows. `tools` is present only when the model requests tool calls (an
6239
7068
  * empty array is never surfaced — its absence means "no calls"). `usage` is present
6240
7069
  * only when the wire reported it (on the stream's `done` line, or the non-stream
6241
- * body), so a caller folds it into a token budget exactly when it exists.
7070
+ * body), so a caller folds it into a token budget exactly when it exists. `truncated` is present
7071
+ * only when the provider's wire reports that the model stopped at its limit; the agent attributes a
7072
+ * truncated final answer to the model layer.
6242
7073
  */
6243
7074
  export declare interface ProviderResult {
6244
7075
  readonly content: string;
@@ -6248,6 +7079,12 @@ export declare interface ProviderResult {
6248
7079
  readonly tools?: readonly ToolCall[];
6249
7080
  /** Carries token consumption for this turn when present (from the wire's `done` line / body). */
6250
7081
  readonly usage?: TokenUsage;
7082
+ /**
7083
+ * Reports that the model ended its answer at its own token limit (an output cap or a full context
7084
+ * window) rather than by finishing; present only when the provider's wire reports that stop. It
7085
+ * is not the request's prompt `truncate` setting.
7086
+ */
7087
+ readonly truncated?: boolean;
6251
7088
  }
6252
7089
 
6253
7090
  /** Validates and projects provider results at a JSON wire boundary. */
@@ -6267,6 +7104,7 @@ prompt: number;
6267
7104
  completion: number;
6268
7105
  total: number;
6269
7106
  } & {}>;
7107
+ truncated?: boolean;
6270
7108
  }>>;
6271
7109
 
6272
7110
  /**
@@ -6289,6 +7127,7 @@ prompt: NumberShape;
6289
7127
  completion: NumberShape;
6290
7128
  total: NumberShape;
6291
7129
  }, boolean | ContractShape>>;
7130
+ truncated: OptionalShape<BooleanShape>;
6292
7131
  }, boolean | ContractShape>;
6293
7132
 
6294
7133
  /**
@@ -6328,6 +7167,29 @@ export declare function questionToSystemOne(question: JudgeQuestion): SystemOneQ
6328
7167
  /** Lists the categories whose messages the projection leaves out as quiet. */
6329
7168
  export declare const QUIET_CATEGORIES: readonly LedgerCategory[];
6330
7169
 
7170
+ /**
7171
+ * Settles with a promise, or rejects with the reason of a signal that aborts first, so a caller
7172
+ * is released at once even when the work behind the promise ignores the signal.
7173
+ *
7174
+ * @remarks
7175
+ * An absent signal returns `promise` itself. An already-aborted signal rejects with its reason
7176
+ * without waiting. The abort listener is removed as soon as `promise` settles. A rejection of
7177
+ * `promise` after the abort is handled here and never surfaces as unhandled.
7178
+ *
7179
+ * @param promise - The work to wait for
7180
+ * @param signal - The cancellation signal, or `undefined` for none
7181
+ * @returns A promise that settles as `promise` does, or rejects with `signal.reason` on abort
7182
+ *
7183
+ * @example
7184
+ * ```ts
7185
+ * const controller = new AbortController()
7186
+ * const raced = raceSignal(new Promise<string>(() => {}), controller.signal)
7187
+ * controller.abort(new Error('stop'))
7188
+ * await raced // rejects with the Error 'stop'
7189
+ * ```
7190
+ */
7191
+ export declare function raceSignal<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T>;
7192
+
6331
7193
  /**
6332
7194
  * Ranks a briefing source for removal before the next prompt.
6333
7195
  *
@@ -6362,14 +7224,22 @@ export declare function readChunks(body: ReadableStream<Uint8Array>, signal?: Ab
6362
7224
  /**
6363
7225
  * Reads a bounded HTTP failure excerpt and preserves an unreadable body's cause.
6364
7226
  *
7227
+ * @remarks
7228
+ * Both HTTP engines, the provider and the judge, translate a non-OK response here and throw
7229
+ * their own coded error from the result. When the excerpt is read and `signal` has aborted, the
7230
+ * abort is the call's outcome, so the signal's reason is thrown. When the body read fails, the
7231
+ * failure is returned as `cause` whether or not `signal` has aborted, so a read that raced the
7232
+ * abort stays recorded on the error the caller throws.
7233
+ *
6365
7234
  * @param response - The failed HTTP response
6366
7235
  * @param prefix - The error taxonomy prefix, including its trailing colon
6367
7236
  * @param signal - The call's combined caller and deadline signal
6368
- * @returns The status message with any excerpt or body-read failure
7237
+ * @returns The status message with any excerpt, the response status, and any body-read failure
7238
+ * @throws Thrown when the excerpt is read after `signal` aborts, with the signal's reason
6369
7239
  * @example
6370
7240
  * ```ts
6371
7241
  * await readFailure(new Response('unavailable', { status: 503 }), 'provider error:', new AbortController().signal)
6372
- * // { message: 'provider error: 503 - unavailable' }
7242
+ * // { message: 'provider error: 503 - unavailable', status: 503 }
6373
7243
  * ```
6374
7244
  */
6375
7245
  export declare function readFailure(response: Response, prefix: string, signal: AbortSignal): Promise<FailureRead>;
@@ -6459,6 +7329,7 @@ prompt: number;
6459
7329
  completion: number;
6460
7330
  total: number;
6461
7331
  } & {}>;
7332
+ truncated?: boolean;
6462
7333
  }>;
6463
7334
  } & {}> | Readonly<{
6464
7335
  channel: "abort";
@@ -6478,6 +7349,7 @@ prompt: number;
6478
7349
  completion: number;
6479
7350
  total: number;
6480
7351
  } & {}>;
7352
+ truncated?: boolean;
6481
7353
  }>;
6482
7354
  } & {}> | Readonly<{
6483
7355
  channel: "error";
@@ -6510,6 +7382,7 @@ prompt: NumberShape;
6510
7382
  completion: NumberShape;
6511
7383
  total: NumberShape;
6512
7384
  }, boolean | ContractShape>>;
7385
+ truncated: OptionalShape<BooleanShape>;
6513
7386
  }, boolean | ContractShape>;
6514
7387
  }, boolean | ContractShape>, ObjectShape< {
6515
7388
  channel: LiteralShape<readonly ["abort"]>;
@@ -6526,6 +7399,7 @@ prompt: NumberShape;
6526
7399
  completion: NumberShape;
6527
7400
  total: NumberShape;
6528
7401
  }, boolean | ContractShape>>;
7402
+ truncated: OptionalShape<BooleanShape>;
6529
7403
  }, boolean | ContractShape>;
6530
7404
  }, boolean | ContractShape>, ObjectShape< {
6531
7405
  channel: LiteralShape<readonly ["error"]>;
@@ -6725,10 +7599,13 @@ export declare function renderCauseChain(error: unknown): string;
6725
7599
  *
6726
7600
  * @remarks
6727
7601
  * Produces `` File: <path>\n```<language>\n<content>\n``` `` — the `File:` label line, then a
6728
- * fenced code block tagged with `language`, the `content` verbatim inside. Pure string assembly,
6729
- * total — never throws. The one fenced-file format string for the whole module — `AgentContext.build()`
6730
- * frames an active workspace's text files with it (each carries its own `language` on its
6731
- * {@link import('@orkestrel/workspace').FileContent} text arm).
7602
+ * fenced code block tagged with `language`, the `content` verbatim inside. The fence is one
7603
+ * backtick longer than the longest backtick run in `content`, and never shorter than three (the
7604
+ * CommonMark rule), so file text holding a triple backtick cannot close the block early and
7605
+ * land outside it; content with no run of three or more keeps the three-backtick fence. Pure
7606
+ * string assembly, total — never throws. The one fenced-file format string for the whole
7607
+ * module — `AgentContext.build()` frames an active workspace's text files with it (each carries
7608
+ * its own `language` on its {@link import('@orkestrel/workspace').FileContent} text arm).
6732
7609
  *
6733
7610
  * @param path - The file path shown on the `File:` label line
6734
7611
  * @param language - The fenced-code language tag (for example `'typescript'`)
@@ -6745,6 +7622,27 @@ export declare function renderCauseChain(error: unknown): string;
6745
7622
  */
6746
7623
  export declare function renderFencedFile(path: string, language: string, content: string): string;
6747
7624
 
7625
+ /**
7626
+ * Renders a value as the JSON text `JSON.stringify` produces, or `undefined` when JSON cannot
7627
+ * carry the value.
7628
+ *
7629
+ * @remarks
7630
+ * Pure and total. Where `JSON.stringify` returns text, the result is the same text, property order
7631
+ * included; `canonicalStringify` from `@orkestrel/contract` sorts keys instead. A value
7632
+ * `JSON.stringify` throws on, such as a `BigInt` or a cycle, and a value it renders as nothing,
7633
+ * such as `undefined`, a function, or a symbol, return `undefined`.
7634
+ *
7635
+ * @param value - The value to render
7636
+ * @returns The JSON text, or `undefined` when the value has no JSON text
7637
+ * @example
7638
+ * ```ts
7639
+ * renderJSON({ order: 'LH-81660', late: true }) // '{"order":"LH-81660","late":true}'
7640
+ * renderJSON(10n) // undefined
7641
+ * renderJSON(undefined) // undefined
7642
+ * ```
7643
+ */
7644
+ export declare function renderJSON(value: unknown): string | undefined;
7645
+
6748
7646
  /**
6749
7647
  * Renders one owner record under a `###` heading, which the briefing nests under its one `## Pinned` heading.
6750
7648
  *
@@ -6775,22 +7673,42 @@ export declare function renderLedgerPinned(record: Pick<LedgerRecord, 'title' |
6775
7673
  */
6776
7674
  export declare function renderLedgerRecord(record: Pick<LedgerRecord, 'title' | 'lines'>): string;
6777
7675
 
7676
+ /**
7677
+ * Renders a lookup call as its tool name and its JSON arguments.
7678
+ *
7679
+ * @remarks
7680
+ * The arguments keep the text `JSON.stringify` writes, key order included. Arguments JSON cannot
7681
+ * carry, such as a `BigInt` or a cycle, render the bare tool name instead of throwing.
7682
+ *
7683
+ * @param name - The lookup tool name
7684
+ * @param args - The arguments the call carried
7685
+ * @returns The tool name, a space, and the JSON arguments; the bare tool name when JSON cannot carry them
7686
+ *
7687
+ * @example
7688
+ * ```ts
7689
+ * renderLookupCall('lookup_order', { id: 'BW-5512' }) // 'lookup_order {"id":"BW-5512"}'
7690
+ * renderLookupCall('lookup_order', { count: 1n }) // 'lookup_order'
7691
+ * ```
7692
+ */
7693
+ export declare function renderLookupCall(name: string, args: Readonly<Record<string, unknown>>): string;
7694
+
6778
7695
  /**
6779
7696
  * Renders one context section — the resolved `open`, each item's rendering, and the resolved
6780
7697
  * `close` when one exists, blank-line joined; `undefined` when the section has no items.
6781
7698
  *
6782
7699
  * @remarks
6783
7700
  * Pure and total. A section with no items renders nothing (`undefined`), so an empty or fully
6784
- * scoped-out manager stays silent — its `open` / `close` appear only when the section has items. `close`
6785
- * is the only optional slot: an unset one (there is no built-in close) drops the
6786
- * trailing line.
7701
+ * scoped-out manager stays silent — its `open` / `close` appear only when the section has items.
7702
+ * An empty or absent slot is dropped before the join, so an `open: ''`, a `close: ''`, an unset
7703
+ * `close` (there is no built-in close), and an item rendered to `''` each add no blank line; a
7704
+ * section whose every slot is empty renders nothing (`undefined`).
6787
7705
  *
6788
7706
  * @typeParam T - The section item being rendered
6789
7707
  * @param open - The section's resolved leading text
6790
7708
  * @param members - The already scope-filtered items
6791
7709
  * @param render - Renders one item to its prompt text
6792
7710
  * @param close - The section's resolved trailing text, or `undefined` for none
6793
- * @returns The rendered section, or `undefined` when there are no items
7711
+ * @returns The rendered section, or `undefined` when there are no items or every slot is empty
6794
7712
  *
6795
7713
  * @example
6796
7714
  * ```ts
@@ -6805,7 +7723,7 @@ export declare function renderSection<T>(open: string, members: readonly T[], re
6805
7723
  * Renders the tail stub of a lookup result from an earlier request.
6806
7724
  *
6807
7725
  * @param name - The lookup tool name
6808
- * @param args - The arguments the call carried
7726
+ * @param args - The arguments the call carried; arguments JSON cannot carry leave the call as its bare tool name, per {@link renderLookupCall}
6809
7727
  * @param state - What became of the result: `failed`, `empty`, `shown` in the briefing, or `hidden` from it
6810
7728
  * @returns The stub text, which carries the call and its state
6811
7729
  *
@@ -6817,6 +7735,28 @@ export declare function renderSection<T>(open: string, members: readonly T[], re
6817
7735
  */
6818
7736
  export declare function renderStub(name: string, args: Readonly<Record<string, unknown>>, state: LedgerLookupState): string;
6819
7737
 
7738
+ /**
7739
+ * Renders a tool's output as the string content of its tool message.
7740
+ *
7741
+ * @remarks
7742
+ * A string passes through unchanged and `undefined` renders as `''`. Any other value renders as
7743
+ * its `JSON.stringify` text, byte for byte, when JSON can carry it. A value JSON cannot carry (a
7744
+ * `BigInt`, a cycle, a symbol, a throwing `toJSON`) renders as `String(value)`, and as `''` when
7745
+ * that conversion throws too, so a tool's output never rejects the run that dispatched it.
7746
+ *
7747
+ * @param value - The tool's output (`ToolSuccess.value`, which may be `undefined`)
7748
+ * @returns The message content
7749
+ *
7750
+ * @example
7751
+ * ```ts
7752
+ * renderToolValue('sunny') // 'sunny'
7753
+ * renderToolValue({ count: 2 }) // '{"count":2}'
7754
+ * renderToolValue(undefined) // ''
7755
+ * renderToolValue(10n) // '10'
7756
+ * ```
7757
+ */
7758
+ export declare function renderToolValue(value: unknown): string;
7759
+
6820
7760
  /**
6821
7761
  * Renders topic names as a comma-separated list in their supplied order.
6822
7762
  * @param topics - The configured desk topics
@@ -6828,13 +7768,74 @@ export declare function renderStub(name: string, args: Readonly<Record<string, u
6828
7768
  */
6829
7769
  export declare function renderTopicNames(topics: readonly LedgerTopic[]): string;
6830
7770
 
7771
+ /**
7772
+ * Holds the disjoint estimate components of one provider request, in heuristic estimate units.
7773
+ *
7774
+ * @remarks
7775
+ * `messages` keeps the request's message order, duplicate ids included. `tools` and `schema` are the
7776
+ * estimates of the rendered advertised tools and `options.schema`, each `0` when absent or empty and
7777
+ * the framing overhead when unrenderable. A request's estimate is the sum of every message component
7778
+ * plus `tools` and `schema`; no total is stored.
7779
+ */
7780
+ export declare interface RequestMeasure {
7781
+ readonly messages: readonly MessageMeasure[];
7782
+ readonly tools: number;
7783
+ readonly schema: number;
7784
+ }
7785
+
7786
+ /**
7787
+ * Projects a provider request onto the declared members the relay wire carries.
7788
+ *
7789
+ * @remarks
7790
+ * Each message keeps `id`, `role`, `content`, and any `calls`, `call`, `images`, and `thinking`;
7791
+ * each call keeps only `id`, `name`, and `arguments`, so a local execution context never reaches
7792
+ * the wire. Each tool keeps `name` and any `description` and `parameters`, and the options keep
7793
+ * any `think` and `schema`. An absent member stays absent. The projection shares every nested
7794
+ * value with the input rather than copying it, so the caller snapshots and validates the result;
7795
+ * a throwing getter propagates its failure.
7796
+ *
7797
+ * @param request - The domain conversation and call configuration
7798
+ * @returns The request with only the members the relay wire declares
7799
+ * @example
7800
+ * ```ts
7801
+ * requestToRelay({
7802
+ * messages: [{ id: 'turn-1', role: 'user', content: 'Track BW-5512' }],
7803
+ * options: { think: false },
7804
+ * })
7805
+ * // { messages: [{ id: 'turn-1', role: 'user', content: 'Track BW-5512' }], options: { think: false } }
7806
+ * ```
7807
+ */
7808
+ export declare function requestToRelay(request: ProviderRequest): ProviderRequest;
7809
+
7810
+ /**
7811
+ * Returns a run's budget or allowance after checking it implements `BudgetInterface`, or throws a
7812
+ * coded error before the run starts it.
7813
+ *
7814
+ * @remarks
7815
+ * The check is `isBudget` from `@orkestrel/budget`, a total guard. `undefined` passes, because an
7816
+ * absent bound is no bound. The error's `context` carries the option path, the limit
7817
+ * `'BudgetInterface'`, and a preview of the refused value.
7818
+ *
7819
+ * @param value - The effective `budget` or `allowance`, or `undefined`
7820
+ * @param key - The option the value came from, named in the error's path
7821
+ * @returns The value unchanged
7822
+ * @throws {ContractError} Thrown when `value` is defined and does not implement `BudgetInterface` (code `'placement'`)
7823
+ *
7824
+ * @example
7825
+ * ```ts
7826
+ * requireBudget(createTokenBudget({ max: 10 }), 'budget') // the budget
7827
+ * requireBudget({ consume() {} }, 'allowance') // throws ContractError ('placement')
7828
+ * ```
7829
+ */
7830
+ export declare function requireBudget(value: BudgetInterface<TokenUsage> | undefined, key: 'budget' | 'allowance'): BudgetInterface<TokenUsage> | undefined;
7831
+
6831
7832
  /**
6832
7833
  * Returns a registered value or throws when its name is absent.
6833
7834
  * @param pool - The named values
6834
7835
  * @param category - The registry category named in an error
6835
7836
  * @param name - The requested key
6836
7837
  * @returns The registered value
6837
- * @throws {AgentError} Thrown when the name is absent, with code REGISTRY.
7838
+ * @throws {AgentError} Thrown when the name is absent, with code REGISTRY and a `context` naming the `category` and the `name`.
6838
7839
  * @example
6839
7840
  * ```ts
6840
7841
  * requireEntry(new Map([['main', provider]]), 'provider', 'main') // provider
@@ -6861,6 +7862,71 @@ export declare function requireEntry<T>(pool: ReadonlyMap<string, T>, category:
6861
7862
  */
6862
7863
  export declare function requireJudgment(value: unknown): Judgment;
6863
7864
 
7865
+ /**
7866
+ * Returns a retained-tail size after checking it is a nonnegative safe integer, or throws a coded
7867
+ * error.
7868
+ *
7869
+ * @remarks
7870
+ * The check is {@link import('../validators.js').isNonNegativeSafeInteger}, so `0` and `-0`
7871
+ * pass while `NaN`, a fraction, a negative, and either infinity are refused. The error's `context`
7872
+ * carries the refused value as `keep`.
7873
+ *
7874
+ * @param keep - The retained-tail size to check
7875
+ * @returns The size unchanged
7876
+ * @throws ConversationError Thrown when `keep` is not a nonnegative safe integer (code `'KEEP'`)
7877
+ *
7878
+ * @example
7879
+ * ```ts
7880
+ * requireKeep(2) // 2
7881
+ * requireKeep(1.5) // throws ConversationError ('KEEP')
7882
+ * ```
7883
+ */
7884
+ export declare function requireKeep(keep: number): number;
7885
+
7886
+ /**
7887
+ * Returns a tool-iteration cap after checking it is a nonnegative safe integer, or throws a coded
7888
+ * error.
7889
+ *
7890
+ * @remarks
7891
+ * The check is {@link import('../validators.js').isNonNegativeSafeInteger}, so `0` and `-0`
7892
+ * pass while `NaN`, a fraction, a negative, and either infinity are refused. The error's `context`
7893
+ * carries the refused value as `limit`.
7894
+ *
7895
+ * @param limit - The tool-iteration cap to check
7896
+ * @returns The cap unchanged
7897
+ * @throws {AgentError} Thrown when `limit` is not a nonnegative safe integer (code `'LIMIT'`)
7898
+ *
7899
+ * @example
7900
+ * ```ts
7901
+ * requireLimit(3) // 3
7902
+ * requireLimit(1.5) // throws AgentError ('LIMIT')
7903
+ * ```
7904
+ */
7905
+ export declare function requireLimit(limit: number): number;
7906
+
7907
+ /**
7908
+ * Returns the rolling switch after checking it does not combine with a `sections` cap, or throws a
7909
+ * coded error.
7910
+ *
7911
+ * @remarks
7912
+ * A rolling conversation keeps one section, so a cap has nothing to bound. The cap checked is the
7913
+ * effective one: inherited from a manager, configured on the conversation, or supplied to one
7914
+ * `compact()` call.
7915
+ *
7916
+ * @param rolling - Whether folds roll every prior section into one
7917
+ * @param cap - The effective `sections` cap, or `undefined` for none
7918
+ * @returns The rolling switch unchanged
7919
+ * @throws ConversationError Thrown when `rolling` is true and a cap is present (code `'SECTIONS'`)
7920
+ *
7921
+ * @example
7922
+ * ```ts
7923
+ * requireRolling(true, undefined) // true
7924
+ * requireRolling(false, 2) // false
7925
+ * requireRolling(true, 2) // throws ConversationError ('SECTIONS')
7926
+ * ```
7927
+ */
7928
+ export declare function requireRolling(rolling: boolean, cap: number | undefined): boolean;
7929
+
6864
7930
  /**
6865
7931
  * Returns a `sections` cap after checking its lower bound, or throws a coded error.
6866
7932
  *
@@ -6896,7 +7962,7 @@ export declare function requireSectionsCap(cap: number | undefined): number | un
6896
7962
  export declare function resolvePredict(predict: number | undefined, capacity: number): number;
6897
7963
 
6898
7964
  /**
6899
- * Sanitizes one reported token count into a safe non-negative integer — a non-finite or
7965
+ * Sanitizes one reported token count into a non-negative integer — a non-finite or
6900
7966
  * non-positive value becomes `0`, and a positive fractional value floors down.
6901
7967
  *
6902
7968
  * @param value - The token count to sanitize
@@ -6911,7 +7977,7 @@ export declare function resolvePredict(predict: number | undefined, capacity: nu
6911
7977
  export declare function sanitizeToken(value: number): number;
6912
7978
 
6913
7979
  /**
6914
- * Sanitizes a {@link TokenUsage} into safe, non-negative integers — the guard an agent's
7980
+ * Sanitizes a {@link TokenUsage} into non-negative integers — the guard an agent's
6915
7981
  * abort-usage path applies to a provider's partial usage before it is charged against a
6916
7982
  * budget or folded into the run total.
6917
7983
  *
@@ -6921,7 +7987,7 @@ export declare function sanitizeToken(value: number): number;
6921
7987
  * non-negative integer part. No upper cap is applied. Total — never throws.
6922
7988
  *
6923
7989
  * @param usage - The token usage to sanitize (for example a provider's abort-partial usage)
6924
- * @returns A new {@link TokenUsage} with every field a safe non-negative integer
7990
+ * @returns A new {@link TokenUsage} with every field a non-negative integer
6925
7991
  *
6926
7992
  * @example
6927
7993
  * ```ts
@@ -7062,20 +8128,20 @@ export declare interface ScopeInterface extends ScopeFilter {
7062
8128
 
7063
8129
  /**
7064
8130
  * Registers the named filters a richer context reuses — immutable {@link Scope} instances keyed by their
7065
- * minted `id`, in insertion order, where `create` always mints and stores rather than overwriting,
8131
+ * minted `id`, in insertion order, where `add` always mints and stores rather than overwriting,
7066
8132
  * and an observable `emitter` reports each change.
7067
8133
  *
7068
8134
  * @remarks
7069
8135
  * - **Registry.** Scopes live in an insertion-ordered `Map` keyed by their minted `id`;
7070
- * `create` mints a {@link Scope} from a {@link ScopeInput} (an `id` plus the three
8136
+ * `add` mints a {@link Scope} from a {@link ScopeInput} (an `id` plus the three
7071
8137
  * allow-lists), stores it, and returns it. `count` is the map size, `scope(id)` looks
7072
8138
  * one up, and `scopes()` lists them in insertion order. (Unlike the name-keyed
7073
8139
  * instruction registry, a scope's key is its minted `id`, so two scopes may share a
7074
- * `name`; `create` therefore always adds — it never overwrites.)
8140
+ * `name`; `add` therefore never overwrites.)
7075
8141
  * - **Removal.** `remove` drops one by id, or a batch — `true` only when every supplied id
7076
8142
  * was removed; `clear` empties the registry.
7077
8143
  * - **Observable.** The owned {@link emitter} ({@link ScopeManagerEventMap}) carries
7078
- * `create` (the created scope) / `remove` (the id) / `clear`. Every event is emitted
8144
+ * `add` (the added scope) / `remove` (the id) / `clear`. Every event is emitted
7079
8145
  * directly, strictly after the map mutation completes; the emitter isolates a listener
7080
8146
  * throw and routes it to its `error` handler (the `error` option), so a buggy observer can
7081
8147
  * never corrupt a mutation.
@@ -7083,7 +8149,7 @@ export declare interface ScopeInterface extends ScopeFilter {
7083
8149
  * @example
7084
8150
  * ```ts
7085
8151
  * const manager = new ScopeManager()
7086
- * const reader = manager.create({ name: 'reader', tools: ['search', 'read'] })
8152
+ * const reader = manager.add({ name: 'reader', tools: ['search', 'read'] })
7087
8153
  * manager.scope(reader.id) // the same scope
7088
8154
  * manager.count // 1
7089
8155
  * ```
@@ -7093,7 +8159,7 @@ export declare class ScopeManager implements ScopeManagerInterface {
7093
8159
  constructor(options?: ScopeManagerOptions);
7094
8160
  get emitter(): EmitterInterface<ScopeManagerEventMap>;
7095
8161
  get count(): number;
7096
- create(input: ScopeInput): ScopeInterface;
8162
+ add(input: ScopeInput): ScopeInterface;
7097
8163
  scope(id: string): ScopeInterface | undefined;
7098
8164
  scopes(): readonly ScopeInterface[];
7099
8165
  remove(id: string): boolean;
@@ -7103,17 +8169,17 @@ export declare class ScopeManager implements ScopeManagerInterface {
7103
8169
 
7104
8170
  /**
7105
8171
  * Maps the push observation surface of a {@link ScopeManagerInterface} — analogous to
7106
- * {@link InstructionManagerEventMap}, but keyed by the minted `id` and carrying `create`
7107
- * (a scope always mints, never overwrites) rather than `add`.
8172
+ * {@link InstructionManagerEventMap}, but keyed by the minted `id`, so `add` never reports a
8173
+ * replaced scope.
7108
8174
  *
7109
8175
  * @remarks
7110
- * `create` carries the created {@link ScopeInterface}; `remove` carries the removed
8176
+ * `add` carries the added {@link ScopeInterface}; `remove` carries the removed
7111
8177
  * scope's `id`; `clear` is a pure signal. The emitter isolates a listener throw and routes
7112
8178
  * it to its `error` handler. A `type` alias so it satisfies `EventMap` structurally.
7113
8179
  */
7114
8180
  export declare type ScopeManagerEventMap = {
7115
- /** Reports a scope created — the created scope. */
7116
- readonly create: readonly [scope: ScopeInterface];
8181
+ /** Reports a scope added — the added scope. */
8182
+ readonly add: readonly [scope: ScopeInterface];
7117
8183
  /** Reports a scope removed — its `id`. */
7118
8184
  readonly remove: readonly [id: string];
7119
8185
  /** Reports every scope removed. */
@@ -7121,16 +8187,16 @@ export declare type ScopeManagerEventMap = {
7121
8187
  };
7122
8188
 
7123
8189
  /**
7124
- * Registers reusable {@link ScopeInterface} instances keyed by their minted `id` — `create`
8190
+ * Registers reusable {@link ScopeInterface} instances keyed by their minted `id` — `add`
7125
8191
  * mints + stores one (never overwrites), `scopes()` lists them in insertion order.
7126
8192
  *
7127
8193
  * @remarks
7128
- * - **Registry.** `create(input)` mints a scope (an `id` + the per-category allow-lists) and
8194
+ * - **Registry.** `add(input)` mints a scope (an `id` + the per-category allow-lists) and
7129
8195
  * stores it; `count` is how many are stored. `scope(id)` looks one up; `scopes()` lists
7130
8196
  * them in insertion order. (Keyed by minted `id`, not `name`, so two scopes may share a
7131
- * `name` and `create` always adds.)
8197
+ * `name` and `add` never overwrites.)
7132
8198
  * - **Observable.** The owned `emitter` ({@link ScopeManagerEventMap}) carries
7133
- * `create` / `remove` / `clear` for fire-and-forget observers; the emitter isolates a
8199
+ * `add` / `remove` / `clear` for fire-and-forget observers; the emitter isolates a
7134
8200
  * listener throw and routes it to its `error` handler (the `error` option).
7135
8201
  */
7136
8202
  export declare interface ScopeManagerInterface {
@@ -7139,13 +8205,30 @@ export declare interface ScopeManagerInterface {
7139
8205
  /**
7140
8206
  * Mints a scope from a {@link ScopeInput} (a `name` plus the per-category allow-lists) and
7141
8207
  * stores it — always adds, never overwrites.
8208
+ *
8209
+ * @param input - The scope's `name`, allow-lists, and optional `select` handler
8210
+ * @returns The stored scope, carrying its minted `id`
8211
+ */
8212
+ add(input: ScopeInput): ScopeInterface;
8213
+ /**
8214
+ * Looks up one scope by id (`undefined` when absent).
8215
+ *
8216
+ * @param id - The minted scope id to look up
8217
+ * @returns The stored scope, or `undefined` when absent
7142
8218
  */
7143
- create(input: ScopeInput): ScopeInterface;
7144
- /** Looks up one scope by id (`undefined` when absent). */
7145
8219
  scope(id: string): ScopeInterface | undefined;
7146
- /** Lists every scope, in insertion order. */
8220
+ /**
8221
+ * Lists every scope, in insertion order.
8222
+ *
8223
+ * @returns Every stored scope, in insertion order
8224
+ */
7147
8225
  scopes(): readonly ScopeInterface[];
7148
- /** Removes one scope by id, or a batch. True if every supplied id was removed; false otherwise. */
8226
+ /**
8227
+ * Removes one scope by id, or a batch. True if every supplied id was removed; false otherwise.
8228
+ *
8229
+ * @param id - One scope id, or a batch
8230
+ * @returns True if every supplied id was present and removed; false otherwise
8231
+ */
7149
8232
  remove(id: string): boolean;
7150
8233
  remove(ids: readonly string[]): boolean;
7151
8234
  /** Removes every scope. */
@@ -7299,6 +8382,23 @@ export declare function selectRecords(projection: LedgerProjection, request: Led
7299
8382
  */
7300
8383
  export declare function settleAgentJob(agent: AgentInterface, partial: boolean): Promise<AgentResult>;
7301
8384
 
8385
+ /**
8386
+ * Splits a tool result into its lines at each line feed, with or without a carriage return before it.
8387
+ *
8388
+ * @remarks
8389
+ * The recall listing keys each recalled line by its first line, and the answer note reads a result
8390
+ * line by line, so both read lines through this one split.
8391
+ *
8392
+ * @param text - The tool result text
8393
+ * @returns The lines in order, an empty line included; one empty line for an empty text
8394
+ *
8395
+ * @example
8396
+ * ```ts
8397
+ * splitResultLines('Account BW-20931.\r\nRefund is $148.50.') // ['Account BW-20931.', 'Refund is $148.50.']
8398
+ * ```
8399
+ */
8400
+ export declare function splitResultLines(text: string): readonly string[];
8401
+
7302
8402
  /**
7303
8403
  * Splits a message into its sentences.
7304
8404
  *
@@ -7591,9 +8691,9 @@ export declare const THINK_OPEN = "<think>";
7591
8691
  export declare type ThinkingReplay = 'none' | 'turn' | 'all';
7592
8692
 
7593
8693
  /**
7594
- * Feeds raw content deltas through a tiny stream-stateful state machine that routes everything
7595
- * inside a `<think>…</think>` span to `thinking` and returns everything outside it as clean
7596
- * content, so a provider yields the answer alone and surfaces the reasoning as
8694
+ * Separates `<think>…</think>` spans from raw content deltas with per-stream state: text inside a
8695
+ * span accumulates on `thinking`, and text outside it returns as clean content, so a provider
8696
+ * yields the answer alone and surfaces the reasoning as
7597
8697
  * {@link import('./types.js').ProviderResult.thinking}. A tag split across deltas is held until
7598
8698
  * disambiguated, `flush()` settles the stream end, and one splitter serves one stream.
7599
8699
  *
@@ -7645,7 +8745,7 @@ export declare class ThinkSplitter implements ThinkSplitterInterface {
7645
8745
  * - **Stateful across deltas.** A tag may arrive split across wire chunks (`'<thi'`
7646
8746
  * ending one delta, `'nk>'` opening the next) — `split` holds any ambiguous tail
7647
8747
  * back until the next delta (or `flush`) disambiguates it, so a partial tag is
7648
- * never leaked as content and never mis-eaten as thinking.
8748
+ * neither returned as content nor routed to thinking.
7649
8749
  * - **`split(delta)`** feeds one raw content delta and returns the clean content to
7650
8750
  * surface for it (possibly `''` — for example mid-think). Text inside a
7651
8751
  * `<think>…</think>` span accumulates on `thinking`; multiple spans accumulate in
@@ -7676,8 +8776,8 @@ export declare interface ThinkSplitterInterface {
7676
8776
  readonly thinking: string;
7677
8777
  /**
7678
8778
  * Feeds one raw delta and returns the clean, non-think content to surface for it (possibly
7679
- * `''`) — a tag split across deltas is held until disambiguated, never leaked as content
7680
- * and never mis-eaten as thinking.
8779
+ * `''`) — a tag split across deltas is held until disambiguated, so it is neither returned
8780
+ * as content nor routed to thinking.
7681
8781
  */
7682
8782
  split(delta: string): string;
7683
8783
  /**
@@ -7687,6 +8787,31 @@ export declare interface ThinkSplitterInterface {
7687
8787
  flush(): string;
7688
8788
  }
7689
8789
 
8790
+ /**
8791
+ * Projects an unknown value onto a fresh, exact `TokenUsage` — capturing `prompt`, `completion`, and
8792
+ * `total` once each through a total boundary, preserving finite negative and fractional counts,
8793
+ * dropping extras, and resolving `undefined` for a non-object, a missing or non-finite count, a
8794
+ * throwing getter, or a hostile or revoked proxy.
8795
+ *
8796
+ * @remarks
8797
+ * The capture runs inside Contract's sanctioned exception boundary, so conforming accessors and
8798
+ * inherited properties are read while a throwing getter or a revoked proxy returns `undefined`.
8799
+ * Counts are not normalized. A caller that treats an absent usage as valid checks for `undefined`
8800
+ * before it projects, because this function refuses `undefined` like any other non-object.
8801
+ *
8802
+ * @param value - The unknown value to project
8803
+ * @returns A fresh `{ prompt, completion, total }`, or `undefined` when the value is not a finite usage triple
8804
+ *
8805
+ * @example
8806
+ * ```ts
8807
+ * import { tokenUsageToJSON } from '@orkestrel/agent'
8808
+ *
8809
+ * tokenUsageToJSON({ prompt: 2, completion: -1, total: 1.5, cached: 4 }) // { prompt: 2, completion: -1, total: 1.5 }
8810
+ * tokenUsageToJSON({ prompt: 2, completion: Infinity, total: 3 }) // undefined
8811
+ * ```
8812
+ */
8813
+ export declare function tokenUsageToJSON(value: unknown): TokenUsage | undefined;
8814
+
7690
8815
  /**
7691
8816
  * Describes a tool call's JSON wire projection.
7692
8817
  *
@@ -7717,15 +8842,16 @@ export declare const UPSTREAM_RELAY_STATUS = 502;
7717
8842
  /**
7718
8843
  * Names the section header the `build()` method of {@link import('./AgentContext.js').AgentContext}
7719
8844
  * renders the active workspace's text files under — `'## Workspace'`, the leading line of the dedicated
7720
- * workspace block in the system message and the carrier-split counterpart to the documents and
7721
- * images section headers.
8845
+ * workspace block in the system message.
7722
8846
  *
7723
8847
  * @remarks
7724
8848
  * `build()` owns the workspace render (a `Workspace` / `WorkspaceManager` stays file-focused — no
7725
8849
  * `open` / `format` getters), so this header lives here as the contexts module's one
7726
8850
  * workspace-section framing constant rather than on a manager. Each workspace text file renders
7727
- * beneath it as a fenced `` File: <path>\n```<language>\n<text>\n``` `` block — the same framing
7728
- * the documents section uses — placed immediately after the documents section in the system block.
8851
+ * beneath it as a fenced `` File: <path>\n```<language>\n<text>\n``` `` block through
8852
+ * {@link import('./helpers.js').renderFencedFile}, and the block is placed immediately after the
8853
+ * instructions section in the system block. The workspace's image files render no section: their
8854
+ * `base64` payload attaches to the last user message.
7729
8855
  */
7730
8856
  export declare const WORKSPACE_SECTION_HEADER = "## Workspace";
7731
8857