@orkestrel/agent 0.0.30 → 0.0.32

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
@@ -1590,29 +1798,42 @@ export declare function buildJudgeResult(model: string, results: readonly JudgeR
1590
1798
  */
1591
1799
  export declare function buildJudgments(request: JudgeRequest, result: JudgeResult, sources: readonly string[], state: string, model: string): readonly JudgmentInput[];
1592
1800
 
1801
+ /**
1802
+ * Derives complete source identities, first occurrences, and local references from the actual briefing lines.
1803
+ * @param messages - Original messages whose indexed sentences establish completeness
1804
+ * @param lines - Retained lines in rendering order after deduplication and trimming
1805
+ * @param amendments - Accepted direct message amendment edges
1806
+ * @returns Immutable rendering facts for this retained population
1807
+ *
1808
+ * @example
1809
+ * ```ts
1810
+ * buildLedgerRenderContext([], [], []).complete.size // 0
1811
+ * ```
1812
+ */
1813
+ export declare function buildLedgerRenderContext(messages: readonly Message[], lines: readonly LedgerLine[], amendments: readonly LedgerAmendment[]): LedgerRenderContext;
1814
+
1593
1815
  /**
1594
1816
  * Builds the record lines of one message.
1595
1817
  *
1596
1818
  * @remarks
1597
- * Each sentence that is not stale becomes a line. A sentence that opens with a pronoun and follows
1819
+ * Each original indexed sentence becomes a line. A sentence that opens with a pronoun and follows
1598
1820
  * another sentence takes the last name of the sentence before it that is neither an owner name nor
1599
1821
  * a system name, and its line text opens with that party and a colon.
1600
1822
  *
1601
1823
  * @param input - The classification whose topics the lines carry
1602
1824
  * @param byId - The messages by id
1603
1825
  * @param id - The message id
1604
- * @param dead - The stale sentences as `SOURCE SENTENCE` keys
1605
1826
  * @param holders - The owner names, which never serve as a party
1606
1827
  * @param system - The system text's names, which never serve as a party
1607
1828
  * @returns The lines in sentence order; empty when the message is absent
1608
1829
  *
1609
1830
  * @example
1610
1831
  * ```ts
1611
- * buildLines(input, byId, 'user-1', new Set(), [], []).map((line) => line.text)
1832
+ * buildLines(input, byId, 'user-1', [], []).map((line) => line.text)
1612
1833
  * // ['Odile Marlow phoned about BW-5512.', 'Odile Marlow: She wants a refund.']
1613
1834
  * ```
1614
1835
  */
1615
- export declare function buildLines(input: LedgerProjectionInput, byId: ReadonlyMap<string, Message>, id: string, dead: ReadonlySet<string>, holders: readonly string[], system: readonly string[]): readonly LedgerLine[];
1836
+ export declare function buildLines(input: LedgerProjectionInput, byId: ReadonlyMap<string, Message>, id: string, holders: readonly string[], system: readonly string[]): readonly LedgerLine[];
1616
1837
 
1617
1838
  /**
1618
1839
  * Builds a request's JSON headers, awaiting the caller's header hook inside the call's
@@ -1647,13 +1868,14 @@ export declare function buildProviderHeaders(hook: ProviderOptions['headers'], s
1647
1868
  * @param thinking - The joined reasoning
1648
1869
  * @param tools - The accumulated calls
1649
1870
  * @param usage - The reported token usage
1871
+ * @param truncated - Whether the model stopped at its own limit, or undefined when the wire did not report it
1650
1872
  * @returns The assembled result
1651
1873
  * @example
1652
1874
  * ```ts
1653
- * buildProviderResult('ok', '', [], undefined) // { content: 'ok' }
1875
+ * buildProviderResult('ok', '', [], undefined, undefined) // { content: 'ok' }
1654
1876
  * ```
1655
1877
  */
1656
- export declare function buildProviderResult(content: string, thinking: string, tools: readonly ToolCall[], usage: TokenUsage | undefined): ProviderResult;
1878
+ export declare function buildProviderResult(content: string, thinking: string, tools: readonly ToolCall[], usage: TokenUsage | undefined, truncated: boolean | undefined): ProviderResult;
1657
1879
 
1658
1880
  /**
1659
1881
  * Builds the placeholder assistant recall call used to price recall framing.
@@ -1701,8 +1923,8 @@ export declare function buildRecapMessage(section: Section): Message;
1701
1923
  * argument. A message that names no owner joins where its earlier side of an amendment pair joins,
1702
1924
  * or on the rules record when it is filed as a rule or a correction, and is an orphan otherwise.
1703
1925
  *
1704
- * A sentence an amending message made stale is left out and listed in `stale`. An amending message
1705
- * that is itself replaced keeps that effect, so the old value never revives.
1926
+ * Every live sentence is retained. Accepted direct edges are listed in `amendments`. An amending message
1927
+ * that is itself superseded retains its accepted edge without restoring its text.
1706
1928
  *
1707
1929
  * A sentence that opens with a pronoun takes the party named in the sentence before it. The reading
1708
1930
  * trusts capitals, so a capitalized word that is no person, such as a carrier named mid-sentence,
@@ -1710,7 +1932,7 @@ export declare function buildRecapMessage(section: Section): Message;
1710
1932
  * the follow-up facts the records exist for.
1711
1933
  *
1712
1934
  * @param input - The messages, readings, entities, and classification to project
1713
- * @returns The records with owner records ordered by their first member and the rules record last, the stale sentences, and the live message ids no record placed
1935
+ * @returns The records with owner records ordered by their first member and the rules record last, the accepted amendments, and the live message ids no record placed
1714
1936
  *
1715
1937
  * @example
1716
1938
  * ```ts
@@ -1734,8 +1956,8 @@ export declare function buildRecords(input: LedgerProjectionInput): LedgerProjec
1734
1956
  *
1735
1957
  * @remarks
1736
1958
  * 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
1959
+ * oldest sections into one (a summary over their section summaries); the recap label is absent
1960
+ * from that merge input. A rolling fold instead re-reads the framed recap — see
1739
1961
  * {@link buildRecapMessage}.
1740
1962
  *
1741
1963
  * @param section - The compacted section to render
@@ -1757,10 +1979,13 @@ export declare function buildSummaryMessage(section: Section): Message;
1757
1979
  * its `events` are drained.
1758
1980
  *
1759
1981
  * @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`.
1982
+ * A waiting `drain` parks its void resolver in `#waiting`; `push` / `close` / `fail`
1983
+ * enqueue/flag, then fire and clear every parked resolver, so each parked reader wakes,
1984
+ * re-reads the shared buffer, and either takes the next chunk, returns (on `close`), throws
1985
+ * (on `fail`), or parks again when another reader took the chunk first. Taking a chunk is
1986
+ * synchronous, so each value reaches exactly one reader.
1987
+ * A `return()` issued while a read is parked waits for the next `push`, `close`, or `fail`.
1988
+ * The parked read takes a pushed value before that `return()` settles.
1764
1989
  *
1765
1990
  * @example
1766
1991
  * ```ts
@@ -1818,6 +2043,14 @@ export declare interface ChannelInterface<T> {
1818
2043
  /**
1819
2044
  * Reads the values back live, in write order — returning on `close` and throwing on `fail`.
1820
2045
  *
2046
+ * @remarks
2047
+ * Concurrent readers share one buffer: each value goes to exactly one reader, and `close` or
2048
+ * `fail` ends every reader, parked or not, after the buffer empties. A `push` after `close` or
2049
+ * `fail` is buffered rather than refused, so a reader still draining or a later `drain` yields
2050
+ * it before reporting the end.
2051
+ * A `return()` issued while a read is parked waits for the next `push`, `close`, or `fail`.
2052
+ * The parked read takes a pushed value before that `return()` settles.
2053
+ *
1821
2054
  * @returns A generator yielding each pushed value, returning on `close` and throwing on `fail`
1822
2055
  */
1823
2056
  drain(): AsyncGenerator<T, void>;
@@ -1825,11 +2058,12 @@ export declare interface ChannelInterface<T> {
1825
2058
 
1826
2059
  /**
1827
2060
  * 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.
2061
+ * draw is never less than the report and nothing is charged twice.
1829
2062
  *
1830
2063
  * @remarks
1831
2064
  * 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
2065
+ * and `total` is consumed less `charged`, floored at 0. An estimate above the report is not
2066
+ * refunded. The usage must already be sanitized: a
1833
2067
  * non-finite field yields a non-finite charge, which the installed `Budget` refuses by throwing
1834
2068
  * a `range` `ContractError`. Without a budget the call consumes nothing.
1835
2069
  *
@@ -1883,7 +2117,7 @@ export declare class Classifier implements ClassifierInterface {
1883
2117
  * Asks the judge every question the filing still lacks, in the measured order.
1884
2118
  * @param requests - The request ids, which skip category questions and receive only enabled request-topic questions
1885
2119
  * @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`
2120
+ * @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
2121
  */
1888
2122
  classify(requests: ReadonlySet<string>, signal: AbortSignal): Promise<ClassifierResult>;
1889
2123
  /**
@@ -1930,7 +2164,7 @@ export declare interface ClassifierInterface {
1930
2164
  *
1931
2165
  * @param requests - The request ids, which skip category questions and receive only enabled request-topic questions
1932
2166
  * @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`
2167
+ * @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
2168
  */
1935
2169
  classify(requests: ReadonlySet<string>, signal: AbortSignal): Promise<ClassifierResult>;
1936
2170
  /**
@@ -1987,14 +2221,86 @@ export declare interface ClassifierOptions {
1987
2221
  readonly entities: LedgerEntityHandler;
1988
2222
  }
1989
2223
 
1990
- /** Carries the judgment keys one classification rests on and the judge usage it spent. */
2224
+ /**
2225
+ * Names what became of one filing question.
2226
+ *
2227
+ * @remarks
2228
+ * `cached` means the conversation already held a matching judgment, so the classifier did not call the judgment manager.
2229
+ * `skipped` means the classifier held the question after a deterministic failure and did not call the judgment manager.
2230
+ * `resolved` means the judgment manager's resolve call fulfilled. `failed` means it rejected while the caller's signal was live.
2231
+ * `aborted` means it rejected with a `JudgeAbortError` or after the caller's signal aborted.
2232
+ */
2233
+ export declare type ClassifierOutcome = 'cached' | 'skipped' | 'resolved' | 'failed' | 'aborted';
2234
+
2235
+ /** 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. */
2236
+ export declare type ClassifierPhase = 'category' | 'topic' | 'amends' | 'supersedes';
2237
+
2238
+ /**
2239
+ * Carries what became of one filing question: its judgment key, its phase, its outcome, and the time spent resolving it.
2240
+ *
2241
+ * @remarks
2242
+ * `key` is the judgment key the classifier asked or reused. `duration` is the monotonic milliseconds
2243
+ * the classifier waited on the judgment manager's resolve call, present for `resolved`, `failed`, and `aborted`
2244
+ * and absent for `cached` and `skipped`, which make no resolve call. A reading counts one logical
2245
+ * question attempt. It counts neither the physical wire calls a judge made nor a request that joined
2246
+ * another pending request, and a question the caller's signal aborted before its resolve call began
2247
+ * has no reading.
2248
+ */
2249
+ export declare interface ClassifierReading {
2250
+ readonly key: string;
2251
+ readonly phase: ClassifierPhase;
2252
+ readonly outcome: ClassifierOutcome;
2253
+ readonly duration?: number;
2254
+ }
2255
+
2256
+ /**
2257
+ * Carries the judgment keys one classification rests on and the judge usage it spent.
2258
+ *
2259
+ * @remarks
2260
+ * `readings` lists one {@link ClassifierReading} per logical question attempt, in asking order, including
2261
+ * the attempt a caller abort interrupted. Each result owns its array.
2262
+ */
1991
2263
  export declare interface ClassifierResult {
1992
2264
  readonly judgments: readonly string[];
1993
2265
  readonly usage?: TokenUsage;
2266
+ readonly readings: readonly ClassifierReading[];
1994
2267
  /** 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
2268
  readonly fault?: Error;
1996
2269
  }
1997
2270
 
2271
+ /**
2272
+ * Carries the optional cause and context an error class with a machine-readable `code` takes at
2273
+ * construction.
2274
+ *
2275
+ * @remarks
2276
+ * `cause` holds the underlying failure. `context` holds the values that locate the failure, such
2277
+ * as the category and name of a missing entry.
2278
+ */
2279
+ export declare interface CodedErrorOptions {
2280
+ readonly cause?: unknown;
2281
+ readonly context?: Readonly<Record<string, unknown>>;
2282
+ }
2283
+
2284
+ /**
2285
+ * Collects accepted direct amendments of live messages.
2286
+ *
2287
+ * @remarks
2288
+ * An accepted edge relates an earlier live message to an amender without claiming which sentences
2289
+ * changed. An amender takes effect while live and retains its edge after another message supersedes
2290
+ * it, without restoring the superseded text. An excluded or quiet user or tool message has no effect.
2291
+ *
2292
+ * @param input - The exclusions and classification to read
2293
+ * @param byId - The messages by id
2294
+ * @param live - The live message ids from {@link collectLive}
2295
+ * @returns The distinct accepted direct edges in conversation order
2296
+ *
2297
+ * @example
2298
+ * ```ts
2299
+ * collectAmendments(input, byId, ['user-1', 'user-3']) // [{ earlier: 'user-1', later: 'user-3' }]
2300
+ * ```
2301
+ */
2302
+ export declare function collectAmendments(input: LedgerProjectionInput, byId: ReadonlyMap<string, Message>, live: readonly string[]): readonly LedgerAmendment[];
2303
+
1998
2304
  /**
1999
2305
  * Collects whole exchanges, joining every exchange spanned by a tool group.
2000
2306
  *
@@ -2041,7 +2347,8 @@ export declare function collectImageData(files: readonly FileInterface[]): reado
2041
2347
  * A message is live when it is a user message, or a tool message whose reading is the last reading of its
2042
2348
  * call, and the input neither excludes it nor files it quiet or replaced. A reading replaces any
2043
2349
  * earlier reading of the same call, an empty one included, so an empty lookup leaves the earlier
2044
- * result out of every record.
2350
+ * result out of every record. A reading with no canonical identity stays live as its own source
2351
+ * and participates in no replacement.
2045
2352
  *
2046
2353
  * @param input - The messages, readings, and classification to read
2047
2354
  * @returns The live message ids
@@ -2076,7 +2383,7 @@ export declare function collectNames(text: string): readonly string[];
2076
2383
  * @returns The distinct live message ids
2077
2384
  * @example
2078
2385
  * ```ts
2079
- * collectProjectionIds({ records: [], stale: [], orphans: ['message-1'] }) // Set { 'message-1' }
2386
+ * collectProjectionIds({ records: [], amendments: [], orphans: ['message-1'] }) // Set { 'message-1' }
2080
2387
  * ```
2081
2388
  */
2082
2389
  export declare function collectProjectionIds(projection: LedgerProjection): ReadonlySet<string>;
@@ -2101,27 +2408,6 @@ export declare function collectProjectionIds(projection: LedgerProjection): Read
2101
2408
  */
2102
2409
  export declare function collectRegistry(readings: readonly LedgerLookupReading[]): LedgerRegistry;
2103
2410
 
2104
- /**
2105
- * Collects the sentences that live messages made stale.
2106
- *
2107
- * @remarks
2108
- * A sentence of a live message is stale when it shares an id or a number with a message that
2109
- * amends it. An amending message takes effect while it is live, and keeps its effect after another
2110
- * message supersedes it, so the value it replaced never revives. A user or tool message that the
2111
- * input excludes or files quiet never takes effect.
2112
- *
2113
- * @param input - The exclusions and classification to read
2114
- * @param byId - The messages by id
2115
- * @param live - The live message ids from {@link collectLive}
2116
- * @returns The stale sentences in conversation order, each with the tokens it shares
2117
- *
2118
- * @example
2119
- * ```ts
2120
- * collectStale(input, byId, ['user-1', 'user-3']) // [{ source: 'user-1', sentence: 1, tokens: ['ESC-2291'] }]
2121
- * ```
2122
- */
2123
- export declare function collectStale(input: LedgerProjectionInput, byId: ReadonlyMap<string, Message>, live: readonly string[]): readonly LedgerStaleSentence[];
2124
-
2125
2411
  /**
2126
2412
  * Collects each assistant message that carries calls together with the tool messages that answer
2127
2413
  * it, then each run of tool messages that no assistant message owns.
@@ -2157,13 +2443,21 @@ export declare function collectToolGroups(messages: readonly Message[]): Readonl
2157
2443
  * `DEFAULT_CONVERSATION_KEEP`). `sections` overrides the configured `sections` cap of the
2158
2444
  * conversation for this compaction only — after the folded section is appended, an overflow past
2159
2445
  * `sections` folds the oldest sections into one merged section. Default: the cap of the
2160
- * conversation (or no cap).
2446
+ * conversation (or no cap). `keep` must be a nonnegative safe integer; any other value throws a
2447
+ * {@link import('./errors.js').ConversationError} with code `'KEEP'`. A `sections` cap on a
2448
+ * rolling conversation throws code `'SECTIONS'`. `signal` cancels this
2449
+ * compaction: an already-aborted signal rejects with its reason before the summarizer is called,
2450
+ * the signal is passed to the {@link ConversationSummaryHandler}, and an abort while the
2451
+ * summarizer runs rejects with the reason at once and commits nothing after the abort. A fold
2452
+ * committed before an aborted overflow merge stays. Default: no signal.
2161
2453
  */
2162
2454
  export declare interface CompactOptions {
2163
2455
  /** Overrides the retained-tail size for this compaction. Default: the `keep` of the conversation. */
2164
2456
  readonly keep?: number;
2165
2457
  /** Overrides the `sections` cap for this compaction. Default: the cap of the conversation (or no cap). */
2166
2458
  readonly sections?: number;
2459
+ /** Cancels this compaction and is passed to the summarizer. Default: no signal. */
2460
+ readonly signal?: AbortSignal;
2167
2461
  }
2168
2462
 
2169
2463
  /**
@@ -2278,7 +2572,14 @@ export declare interface ContextSectionFormat<T> {
2278
2572
  * exchanges. A cut inside an assistant call group, which only a group spanning two exchanges
2279
2573
  * allows, moves before the group, so a tool result never stays live without its call. Returns
2280
2574
  * the section, or `undefined` when nothing folds. A {@link ConversationError} is thrown when no
2281
- * `#summarize` was supplied.
2575
+ * `#summarize` was supplied. Calls run one at a time, a fold commits only when its messages are
2576
+ * still the live entries after the summarizer settles, and `options.signal` rejects the call
2577
+ * with its reason at once and commits nothing after the abort.
2578
+ * - **Rolling.** With `rolling`, a fold makes one summarizer call over the framed recaps of all
2579
+ * prior sections then the slice, and replaces those sections with one that retains their
2580
+ * originals then the slice. It emits `compact` once and never `collapse`, commits nothing when
2581
+ * the call throws, aborts, or the slice or prior sections changed, and cannot combine with a
2582
+ * `sections` cap (`'SECTIONS'`).
2282
2583
  * - **`rehydrate(id)` / `search(query)`.** `rehydrate` returns the full original messages of a
2283
2584
  * known section and emits its `id` through `rehydrate`, even when the list is empty. An unknown
2284
2585
  * id returns `undefined` without an event. The read never reinserts messages. `search` is a case-insensitive
@@ -2344,9 +2645,10 @@ export declare const CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages]
2344
2645
 
2345
2646
  /**
2346
2647
  * 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'`.
2648
+ * `sections` cap below `1`, a `keep` that is not a nonnegative safe integer, or a judgment or
2649
+ * judge request that JSON cannot carry — thrown by the `compact()` method of a
2650
+ * {@link ConversationInterface}, its construction, or its judgment store, carrying the
2651
+ * machine-readable `code` `'SUMMARIZER' | 'SECTIONS' | 'JUDGMENT' | 'KEEP'`.
2350
2652
  *
2351
2653
  * @remarks
2352
2654
  * Compaction requires a summarizer (it digests the folded slice into a section summary); a
@@ -2354,18 +2656,40 @@ export declare const CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages]
2354
2656
  * is a programmer error and throws this with `'SUMMARIZER'`.
2355
2657
  * A `sections` cap (on {@link import('./types.js').ConversationOptions} /
2356
2658
  * {@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`.
2659
+ * {@link import('./types.js').CompactOptions}) must be `>= 1`, and cannot combine with
2660
+ * `rolling` — either is a programmer error and throws this with `'SECTIONS'`. A `keep` on the same options must be a nonnegative
2661
+ * safe integer — any other value is a programmer error and throws this with `'KEEP'`. A judgment
2662
+ * record or a judge request that JSON cannot carry, or whose copy fails its guard, is a
2663
+ * programmer error and throws this with `'JUDGMENT'`, its `context` naming the `question` id
2664
+ * where one exists. Narrow a caught value with {@link isConversationError} and branch on
2665
+ * `error.code`.
2362
2666
  */
2363
2667
  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);
2668
+ /** 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. */
2669
+ readonly code: ConversationErrorCode;
2670
+ /** Holds the values that locate the failure, such as the `question` id of a malformed question, or `undefined` when none were supplied. */
2671
+ readonly context: Readonly<Record<string, unknown>> | undefined;
2672
+ /**
2673
+ * Creates the error with its code, message, and optional cause and context.
2674
+ *
2675
+ * @param code - The machine-readable {@link ConversationErrorCode}
2676
+ * @param message - The human-readable description
2677
+ * @param options - The optional {@link CodedErrorOptions}; `cause` reaches `Error.cause` only when present
2678
+ */
2679
+ constructor(code: ConversationErrorCode, message: string, options?: CodedErrorOptions);
2367
2680
  }
2368
2681
 
2682
+ /**
2683
+ * Names the machine-readable condition a {@link import('./errors.js').ConversationError} reports.
2684
+ *
2685
+ * @remarks
2686
+ * `'SUMMARIZER'`: a `compact()` with no summarizer. `'SECTIONS'`: a `sections` cap below `1`, or
2687
+ * any `sections` cap combined with `rolling`.
2688
+ * `'JUDGMENT'`: a judgment or judge request that JSON cannot carry, or whose copy fails its guard.
2689
+ * `'KEEP'`: a `keep` that is not a nonnegative safe integer.
2690
+ */
2691
+ export declare type ConversationErrorCode = 'SUMMARIZER' | 'SECTIONS' | 'JUDGMENT' | 'KEEP';
2692
+
2369
2693
  /**
2370
2694
  * Maps the push observation surface of a {@link ConversationInterface} — the compaction
2371
2695
  * moments a fire-and-forget observer subscribes to through `conversation.emitter.on`.
@@ -2395,15 +2719,18 @@ export declare type ConversationEventMap = {
2395
2719
  /**
2396
2720
  * Carries the data to author a {@link ConversationInterface} through a {@link
2397
2721
  * 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.
2722
+ * `sections` cap override, the `rolling` switch, the reserved `on` hooks, and a
2723
+ * {@link ConversationSnapshot} to hydrate from.
2400
2724
  *
2401
2725
  * @remarks
2402
2726
  * `id` is the identity of the conversation. Default: a minted id. `summarize` overrides the
2403
2727
  * default {@link ConversationSummaryHandler} of the manager for this conversation. Default: the
2404
2728
  * 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
2729
+ * overrides the default `sections` cap of the manager. `rolling` switches on rolling folds; a
2730
+ * manager `sections` default it would inherit throws `'SECTIONS'`. `on` is the reserved listener key
2731
+ * (initial {@link ConversationEventMap} listeners). `error` is the listener-error handler of the
2732
+ * conversation's emitter, so a throwing `on` listener reports there. Default: the emitter's own
2733
+ * handler. `snapshot` is the construction-time hydration
2407
2734
  * seam — a {@link ConversationSnapshot} whose `id` / `sections` / live tail are restored into the
2408
2735
  * conversation (the live `summarize` / `keep` / `on` re-supplied alongside it), the conversation
2409
2736
  * analogue of the `seed` option of {@link import('@orkestrel/workspace').WorkspaceOptions},
@@ -2420,7 +2747,11 @@ export declare interface ConversationInput {
2420
2747
  readonly keep?: number;
2421
2748
  /** Overrides the default `sections` cap of the manager for this conversation. */
2422
2749
  readonly sections?: number;
2750
+ /** Rolls every fold of this conversation into one section, passed on as {@link ConversationOptions.rolling}. Default: `false`; the manager holds no default. */
2751
+ readonly rolling?: boolean;
2423
2752
  readonly on?: EmitterHooks<ConversationEventMap>;
2753
+ /** Holds the listener-error handler of the conversation's emitter — a listener throw routes here, not to a domain event. */
2754
+ readonly error?: EmitterErrorHandler;
2424
2755
  /** Hydrates from a {@link ConversationSnapshot}, passed on as {@link ConversationOptions.snapshot}. */
2425
2756
  readonly snapshot?: ConversationSnapshot;
2426
2757
  }
@@ -2455,10 +2786,11 @@ export declare interface ConversationInput {
2455
2786
  * thrown when no `summarize` was supplied.
2456
2787
  * - **`summarizable` — whether a `compact()` can fold.** `true` when a
2457
2788
  * {@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
2789
+ * of the agent loop (`AgentOptions.window`) gates its fold on it so a conversation that has no
2459
2790
  * 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.
2791
+ * error from the auto path); the loop still measures its requests against the window, and a
2792
+ * strict agent refuses one that is over. A manual `compact()` still throws without a summarizer
2793
+ * — only the auto path is guarded.
2462
2794
  * - **`rehydrate(id)` / `search(query)` — read the retained originals.** `rehydrate` returns
2463
2795
  * the full original messages of a known section and emits its `id` through `rehydrate`, even
2464
2796
  * when the list is empty. An unknown id returns `undefined` without an event. The read never
@@ -2483,9 +2815,10 @@ export declare interface ConversationInterface {
2483
2815
  readonly sections: readonly Section[];
2484
2816
  /**
2485
2817
  * 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
2818
+ * The automatic compaction of the agent loop (`AgentOptions.window`) gates its fold on it (a
2487
2819
  * 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.
2820
+ * `SUMMARIZER` error, though its requests are still measured); a manual `compact()` still throws
2821
+ * without a summarizer.
2489
2822
  */
2490
2823
  readonly summarizable: boolean;
2491
2824
  /** Counts the live (uncompacted) messages stored in the tail. */
@@ -2549,16 +2882,46 @@ export declare interface ConversationInterface {
2549
2882
  * a group moves before its assistant message, then back to whole exchanges again. Only a group
2550
2883
  * that spans two exchanges reaches that rule.
2551
2884
  *
2885
+ * A rolling conversation makes one summarizer call per fold, over the framed recaps of every
2886
+ * prior section followed by the slice with thinking stripped; the originals behind the recaps
2887
+ * are not sent. After the call settles, the slice and the prior sections must be unchanged; the
2888
+ * prior sections are then replaced atomically by one section retaining their originals in order
2889
+ * and then the slice, and the slice leaves the live tail. It emits `compact` once and never
2890
+ * `collapse`. A throw, an abort, or a changed input commits nothing. A fold with no eligible
2891
+ * slice makes no call. A `sections` cap, configured or supplied here, throws `'SECTIONS'`.
2892
+ *
2552
2893
  * When a `sections` cap is set and the fold pushes the section count over it, an overflow
2553
2894
  * merge step folds the oldest sections into one. If the `summarize` call of that merge throws,
2554
2895
  * the merge is skipped (sections transiently sit at `cap + 1`, no loss), no `compact` is
2555
2896
  * emitted, and the error propagates; the next successful `compact()` merges the section count
2556
2897
  * back to `cap`.
2557
2898
  *
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`)
2899
+ * Calls run one at a time: a call made while another is in flight waits for it, then computes
2900
+ * its own fold from the live tail it finds. A fold commits only when every folded message is
2901
+ * still the same live entry after the summarizer settles, and a merge commits only when the
2902
+ * folded sections are still the oldest; otherwise the call resolves `undefined` and emits
2903
+ * nothing, so a `remove` or `clear` during the summarizer leaves no section.
2904
+ *
2905
+ * A summarizer must not call `compact` on the conversation it serves. A call that finds no fold in
2906
+ * flight calls its summarizer before its own fold counts as in flight, so that summarizer's own
2907
+ * `compact` finds nothing to wait for and summarizes the same slice again; the commit check above
2908
+ * keeps one section, because the later fold finds its slice already folded and resolves
2909
+ * `undefined`. A call queued behind an earlier fold counts as in flight before its summarizer
2910
+ * runs, so its summarizer's own `compact` waits for the fold that called it.
2911
+ *
2912
+ * `options.signal` cancels the call. An already-aborted signal rejects with its reason before
2913
+ * the summarizer is called. An abort while waiting for an earlier call or while the summarizer
2914
+ * runs rejects with the reason at once, even when the summarizer ignores the signal, and the
2915
+ * call commits nothing afterwards; a fold committed before an aborted merge stays, as when the
2916
+ * merge throws.
2917
+ *
2918
+ * @param options - Optional {@link CompactOptions} (`keep`, `sections`, `signal`)
2919
+ * @returns The folded {@link Section}, or `undefined` when nothing folded or the folded
2920
+ * messages or sections changed while the summarizer ran
2921
+ * @throws ConversationError Thrown when no summarizer was supplied (code `'SUMMARIZER'`), when the
2922
+ * effective sections cap does not satisfy `>= 1` or combines with `rolling` (code `'SECTIONS'`,
2923
+ * including `NaN`), or when
2924
+ * the effective `keep` is not a nonnegative safe integer (code `'KEEP'`)
2562
2925
  */
2563
2926
  compact(options?: CompactOptions): Promise<Section | undefined>;
2564
2927
  /**
@@ -2750,7 +3113,11 @@ export declare interface ConversationManagerInterface {
2750
3113
  * into a fresh {@link ConversationInterface} through the `snapshot` option
2751
3114
  * (`add({ snapshot, ... })`, flowing the default `summarize` / `keep` of the manager in),
2752
3115
  * which registers and activates it, and it is returned.
2753
- * - Else (no store, or a store miss) ⇒ `undefined` (lenient — no throw).
3116
+ * - A conversation registered under `id` while the store read was pending wins: it is
3117
+ * activated and returned, and the stored snapshot is discarded. So a concurrent `add({ id })`
3118
+ * keeps its turns, and concurrent `open(id)` calls return the one registered instance.
3119
+ * - Else (no store, a store miss, or a stored snapshot whose `id` is not `id`) ⇒ `undefined`
3120
+ * (lenient — no throw).
2754
3121
  *
2755
3122
  * @param id - The conversation id to open
2756
3123
  * @returns The activated {@link ConversationInterface}, or `undefined` when neither registered nor stored
@@ -2759,12 +3126,12 @@ export declare interface ConversationManagerInterface {
2759
3126
  /**
2760
3127
  * Persists a registered conversation's {@link ConversationInterface.snapshot} to the optional
2761
3128
  * {@link ConversationStoreInterface} (`store`) — `true` when persisted, `false` when there is
2762
- * no store or the id is unknown, and never throwing.
3129
+ * no store or the id is unknown; a store failure propagates.
2763
3130
  *
2764
3131
  * @remarks
2765
3132
  * Lenient: when a `store` is set and `id` is registered, `store.set(conversation.snapshot())` is
2766
3133
  * 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`.
3134
+ * `false`. A failure from `store.set` propagates.
2768
3135
  *
2769
3136
  * @param id - The id of the registered conversation to persist
2770
3137
  * @returns True if the snapshot was persisted; false otherwise (no store, or an unknown id)
@@ -2792,7 +3159,8 @@ export declare interface ConversationManagerInterface {
2792
3159
  * `summarize` is the default summarizer flowed into every conversation the manager creates
2793
3160
  * (a per-`add` {@link ConversationInput.summarize} overrides it); a conversation created
2794
3161
  * 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:
3162
+ * retained-tail size (a per-`add` {@link ConversationInput.keep} overrides it); it must be a
3163
+ * nonnegative safe integer, else construction throws a `ConversationError` with code `'KEEP'`. Default:
2796
3164
  * {@link import('./constants.js').DEFAULT_CONVERSATION_KEEP}. `sections` is the default cap
2797
3165
  * on the compacted `sections` list of a created conversation (a per-`add`
2798
3166
  * {@link ConversationInput.sections} overrides it). Default: no cap.
@@ -2826,13 +3194,18 @@ export declare interface ConversationManagerOptions {
2826
3194
  * {@link ConversationSummaryHandler} compaction needs — without it `compact()` throws a
2827
3195
  * {@link import('./errors.js').ConversationError} (a conversation can still store + view a
2828
3196
  * 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()`
3197
+ * retains verbatim (folding only the older ones); it must be a nonnegative safe integer, else
3198
+ * construction throws a {@link import('./errors.js').ConversationError} with code `'KEEP'`.
3199
+ * Default: {@link import('./constants.js').DEFAULT_CONVERSATION_KEEP} (`0` — a manual `compact()`
2831
3200
  * folds every exchange before the newest user message into one section). `sections` is an
2832
3201
  * optional cap on the compacted `sections` list — when set (`>= 1`), a `compact()` that would
2833
3202
  * leave more than `sections` entries folds the oldest overflow into one merged section,
2834
3203
  * emitting `collapse`. A successful capped merge retains at most `Math.ceil(sections)` entries;
2835
- * fractions and positive infinity are accepted. Default: no cap.
3204
+ * fractions and positive infinity are accepted. Default: no cap. `rolling` makes each `compact()`
3205
+ * one summarizer call over the framed recaps of every prior section then the slice, replacing all
3206
+ * prior sections with one that retains their originals then the slice, with one `compact` event
3207
+ * and no `collapse`; it cannot combine with `sections`, which throws a `ConversationError` with
3208
+ * code `'SECTIONS'`. Default: `false`.
2836
3209
  * `snapshot` is the hydration seam — a {@link ConversationSnapshot} whose `id`, compacted
2837
3210
  * `sections`, and live tail are restored into the conversation, with the live `summarize` /
2838
3211
  * `keep` / `on` supplied alongside it (a summarizer is a function, not serialized data).
@@ -2851,6 +3224,13 @@ export declare interface ConversationOptions {
2851
3224
  readonly keep?: number;
2852
3225
  /** Caps the compacted `sections` list (`>= 1`); overflow folds into one merged section. Default: no cap. */
2853
3226
  readonly sections?: number;
3227
+ /**
3228
+ * Rolls every fold into one section: the summarizer digests the framed recaps of all prior
3229
+ * sections followed by the slice, and one section retaining their originals then the slice
3230
+ * replaces them. Combining it with a `sections` cap throws a `ConversationError` (`'SECTIONS'`).
3231
+ * Default: `false`.
3232
+ */
3233
+ readonly rolling?: boolean;
2854
3234
  /** Hydrates from a {@link ConversationSnapshot} — its `id` wins over `id`; restoring is silent. */
2855
3235
  readonly snapshot?: ConversationSnapshot;
2856
3236
  }
@@ -2995,10 +3375,16 @@ export declare interface ConversationStoreInterface {
2995
3375
  * The core conversation layer treats it as an opaque async function — it never reads which
2996
3376
  * backend produced the digest, keeping `core` free of any provider coupling.
2997
3377
  *
3378
+ * `signal` is the {@link CompactOptions.signal} of the `compact()` call the fold serves, or
3379
+ * `undefined` when that call supplied none. A summarizer must stop its work when it aborts. One
3380
+ * that ignores it still cannot hold the caller: `compact()` races the summarizer against the
3381
+ * signal, rejects with the abort reason, and discards a digest that arrives later.
3382
+ *
2998
3383
  * @param messages - The folded messages to digest into a summary
3384
+ * @param signal - The cancellation signal of the `compact()` call, or `undefined` when it supplied none
2999
3385
  * @returns The summary text (the model-written digest of those messages)
3000
3386
  */
3001
- export declare type ConversationSummaryHandler = (messages: readonly Message[]) => Promise<string>;
3387
+ export declare type ConversationSummaryHandler = (messages: readonly Message[], signal?: AbortSignal) => Promise<string>;
3002
3388
 
3003
3389
  /**
3004
3390
  * Owns a value by serializing it to JSON and parsing the text, so the copy shares nothing with its source.
@@ -3025,9 +3411,9 @@ export declare function copyJSON(value: unknown): unknown;
3025
3411
  * `generate` and a live `stream`.
3026
3412
  *
3027
3413
  * @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()`)
3414
+ * One private loop drives the turn; `generate` awaits the settled result of the same run
3415
+ * `stream` exposes and leaves its live chunks undrained, so they can never diverge. Each turn is bounded by one cancel folded from
3416
+ * `signal` + `timeout` + `budget` + `allowance` (through `AbortSignal.any`) — any trip (or `abort()`)
3031
3417
  * commits a partial result (the stream's `result` resolves on a cancel, rejects only
3032
3418
  * on a genuine provider / tool error). The `scheduler` paces between turns; tool
3033
3419
  * iteration is capped at `limit`. Default: `DEFAULT_AGENT_LIMIT`. Tools are advertised
@@ -3072,15 +3458,18 @@ export declare function createAgent(provider: ProviderInterface, options?: Agent
3072
3458
  * `system` is the optional system prompt; `tools` / `instructions` / `workspaces` are pre-built
3073
3459
  * managers to reuse (empty ones are created when omitted, so `context.workspaces` is always
3074
3460
  * 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
3461
+ * through `context.apply(...)`). `conversations` is the message source: a supplied manager's
3462
+ * active conversation is reused as `messages`, and a default conversation is added only when it
3463
+ * has none (a fresh registry with one default conversation is created when omitted). `build()`
3464
+ * folds the scoped instructions — plus the active workspace's scope-filtered text files (fenced) —
3465
+ * into one leading `system` message and appends the active conversation's `view()` (attaching the
3466
+ * active workspace's scope-filtered image files' `base64` payload to the last user message), built
3467
+ * fresh each call; the scope never filters the conversation. The active workspace is the sole
3468
+ * document/image context. Tools are advertised structurally (through
3080
3469
  * `tools.definitions()`, scope-filtered by the loop), never serialized into the prompt.
3081
3470
  *
3082
- * @param options - Optional `system` / `tools` / `instructions` / `workspaces` / `scope`
3083
- * (see {@link AgentContextOptions})
3471
+ * @param options - Optional `system` / `tools` / `instructions` / `workspaces` /
3472
+ * `conversations` / `scope` / `select` (see {@link AgentContextOptions})
3084
3473
  * @returns A working {@link AgentContextInterface}
3085
3474
  *
3086
3475
  * @example
@@ -3107,9 +3496,10 @@ export declare function createAgentContext(options?: AgentContextOptions): Agent
3107
3496
  * through `store` (+ `restore()` after a crash) are all the backing Queue's. `enqueue`
3108
3497
  * returns a per-job promise.
3109
3498
  * - **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.
3499
+ * persists outstanding jobs; `restore()` re-enqueues them and the `registry` rehydrates the
3500
+ * live pieces from the names. A job survives a crash only through a store whose rows outlive
3501
+ * the process (for example `createDatabaseQueueStore`); a `createMemoryQueueStore` row is
3502
+ * gone when the process exits.
3113
3503
  * - **Partial policy.** A partial result throws an
3114
3504
  * {@link import('./errors.js').AgentJobError} by default, so a job cancelled by its
3115
3505
  * attempt deadline / a queue abort retries while attempts remain; `partial: true`
@@ -3124,11 +3514,13 @@ export declare function createAgentContext(options?: AgentContextOptions): Agent
3124
3514
  *
3125
3515
  * @example
3126
3516
  * ```ts
3517
+ * import type { AgentJobInput, ProviderInterface } from '@orkestrel/agent'
3518
+ * import type { QueueStoreInterface } from '@orkestrel/queue'
3127
3519
  * import { createAgentQueue, createAgentRegistry } from '@orkestrel/agent'
3128
- * import { createMemoryQueueStore } from '@orkestrel/queue'
3129
3520
  *
3521
+ * declare const provider: ProviderInterface // any concrete implementation supplied by the host app
3522
+ * declare const store: QueueStoreInterface<AgentJobInput> // a durable store the host supplies
3130
3523
  * const registry = createAgentRegistry({ providers: { main: provider } })
3131
- * const store = createMemoryQueueStore(agentJobShape) // survives a restart through restore()
3132
3524
  * const queue = createAgentQueue({ registry, concurrency: 2, retries: 1, store })
3133
3525
  * const result = await queue.enqueue({ provider: 'main', messages: [{ role: 'user', content: 'ok?' }] })
3134
3526
  * ```
@@ -3179,7 +3571,9 @@ export declare function createAgentRegistry(options: AgentRegistryOptions): Agen
3179
3571
  * the handler adds only rehydration + the partial policy.
3180
3572
  * - **Sub-agent fan-out.** Each unit's handler receives a `ControllerInterface` whose
3181
3573
  * `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
3574
+ * child's result joins the run after the declared units, in spawn order). A unit spawns its
3575
+ * declared `children` on its first attempt only, so a retried parent never runs a child
3576
+ * twice. On a bounded
3183
3577
  * runner, fan out and return — do not await a spawn inline from within the handler (a
3184
3578
  * slot-holding handler awaiting its own spawn can deadlock; see `ControllerInterface`).
3185
3579
  * - **Partial policy + cancellation.** Same as `createAgentQueue`: a partial result
@@ -3294,14 +3688,14 @@ export declare function createChannel<T>(): ChannelInterface<T>;
3294
3688
  * // Append the instruction as the FINAL user turn: a chat model emits nothing when the prompt
3295
3689
  * // ends on an assistant turn, so a leading-system instruction is unreliable.
3296
3690
  * const conversation = createConversation({
3297
- * summarize: async (messages) =>
3691
+ * summarize: async (messages, signal) =>
3298
3692
  * (
3299
3693
  * await provider.generate(
3300
3694
  * [
3301
3695
  * ...messages,
3302
3696
  * { id: 's', role: 'user', content: 'Summarize the conversation so far concisely.' },
3303
3697
  * ],
3304
- * AbortSignal.timeout(30_000),
3698
+ * signal ?? AbortSignal.timeout(30_000), // the compact() call's signal, when it passed one
3305
3699
  * )
3306
3700
  * ).content,
3307
3701
  * keep: 2, // retain at least the 2 most recent messages verbatim on each compaction
@@ -3541,7 +3935,8 @@ export declare function createRelay(options: RelayOptions): RelayHandler;
3541
3935
  * is the server half this one pairs with.
3542
3936
  *
3543
3937
  * @param options - The endpoint, parser factory, and HTTP call configuration
3544
- * @returns The concrete relay provider
3938
+ * @returns The relay provider behind the shared provider interface; construct
3939
+ * {@link RelayProvider} directly to reach its wire seams
3545
3940
  * @example Reaching the relay from the browser
3546
3941
  * ```ts
3547
3942
  * import type { ProviderInterface, ProviderParserInterface } from '@orkestrel/agent'
@@ -3555,14 +3950,13 @@ export declare function createRelay(options: RelayOptions): RelayHandler;
3555
3950
  * this.#pending += chunk
3556
3951
  * const lines = this.#pending.split(/\r\n|\n/)
3557
3952
  * 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
3953
+ * return lines
3954
+ * .filter((line) => line.trim().length > 0)
3955
+ * .map((line) => {
3956
+ * const record = parseJSONAs(line, isRecord)
3957
+ * if (record === undefined) throw new ProviderError('PROTOCOL', 'invalid JSON record')
3958
+ * return record
3959
+ * })
3566
3960
  * }
3567
3961
  * clear(): void {
3568
3962
  * this.#pending = ''
@@ -3581,7 +3975,7 @@ export declare function createRelay(options: RelayOptions): RelayHandler;
3581
3975
  * const result = await browser.generate(messages, abort.signal) // a ProviderResult like a local provider's
3582
3976
  * ```
3583
3977
  */
3584
- export declare function createRelayProvider(options: RelayProviderOptions): RelayProvider;
3978
+ export declare function createRelayProvider(options: RelayProviderOptions): ProviderInterface;
3585
3979
 
3586
3980
  /**
3587
3981
  * Creates a named scope — an immutable {@link ScopeInterface} from its `name` and its
@@ -3612,8 +4006,8 @@ export declare function createScope(input: ScopeInput): ScopeInterface;
3612
4006
  * by their minted `id`, in insertion order.
3613
4007
  *
3614
4008
  * @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;
4009
+ * Starts empty; `add` mints each scope's `id` and stores it (keyed by `id`, so it
4010
+ * never overwrites — two scopes may share a `name`); `scopes()` lists them in insertion order;
3617
4011
  * `remove` (one or a batch) reports `true` only when every supplied id was removed; `clear`
3618
4012
  * empties it. Carries an observable `emitter`
3619
4013
  * ({@link import('./types.js').ScopeManagerEventMap}) wired through the reserved `on`
@@ -3628,7 +4022,7 @@ export declare function createScope(input: ScopeInput): ScopeInterface;
3628
4022
  * import { createScopeManager } from '@orkestrel/agent'
3629
4023
  *
3630
4024
  * const scopes = createScopeManager()
3631
- * const reader = scopes.create({ name: 'reader', tools: ['search'] })
4025
+ * const reader = scopes.add({ name: 'reader', tools: ['search'] })
3632
4026
  * ```
3633
4027
  */
3634
4028
  export declare function createScopeManager(options?: ScopeManagerOptions): ScopeManagerInterface;
@@ -3724,7 +4118,8 @@ export declare function cutListing(entries: readonly string[], room: number): st
3724
4118
  * the row `{ id: snapshot.id, snapshot }`.
3725
4119
  * - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to
3726
4120
  * a {@link ConversationSnapshot} ({@link import('../validators.js').isConversationSnapshot} — the
3727
- * total guard for an untrusted storage read), or `undefined` if none is stored.
4121
+ * total guard for an untrusted storage read), or `undefined` if none is stored, the blob is
4122
+ * malformed, or the blob's own `id` is not the requested one.
3728
4123
  * - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
3729
4124
  *
3730
4125
  * Unlike a session store there is no idle-TTL / eviction — a persisted conversation lives until an
@@ -3755,11 +4150,34 @@ export declare class DatabaseConversationStore implements ConversationStoreInter
3755
4150
  * {@link ConversationSnapshotRow} `{ id; snapshot }` shape (the snapshot one opaque JSON column)
3756
4151
  */
3757
4152
  constructor(table: TableInterface<ConversationSnapshotRow>);
3758
- /** Resolves the persisted snapshot for `id`, narrowing the opaque JSON column back to a `ConversationSnapshot`. */
4153
+ /**
4154
+ * Resolves the persisted snapshot for `id`, narrowing the opaque JSON column back to a
4155
+ * `ConversationSnapshot`.
4156
+ *
4157
+ * @remarks
4158
+ * The column is an untrusted storage read, so a blob that fails
4159
+ * {@link import('../validators.js').isConversationSnapshot}, or whose own `id` is not `id`,
4160
+ * resolves `undefined` rather than a conversation under the wrong key.
4161
+ *
4162
+ * @param id - The conversation id to resolve (the row key)
4163
+ * @returns The persisted snapshot, or `undefined` when no row is stored, the blob is malformed,
4164
+ * or the blob names another id
4165
+ */
3759
4166
  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 }`. */
4167
+ /**
4168
+ * Inserts or replaces under the snapshot's own `id` (no separate id param) — the row is
4169
+ * `{ id, snapshot }`.
4170
+ *
4171
+ * @param snapshot - The snapshot to store, keyed by its `id`
4172
+ * @returns A promise that resolves once the row is written
4173
+ */
3761
4174
  set(snapshot: ConversationSnapshot): Promise<void>;
3762
- /** Drops a snapshot by id; an absent id is a no-op (no throw). */
4175
+ /**
4176
+ * Drops a snapshot by id; an absent id is a no-op (no throw).
4177
+ *
4178
+ * @param id - The conversation id to drop
4179
+ * @returns A promise that resolves once the row is gone
4180
+ */
3763
4181
  delete(id: string): Promise<void>;
3764
4182
  }
3765
4183
 
@@ -3844,8 +4262,8 @@ export declare const DETERMINISTIC_JUDGE_ERROR: RegExp;
3844
4262
  /**
3845
4263
  * Estimates the context-token footprint of a batch of messages — each message's content plus
3846
4264
  * {@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
4265
+ * {@link import('./constants.js').IMAGE_TOKEN_ESTIMATE} for each attached image. The {@link estimateRequest} message term
4266
+ * for an agent's context budget (the
3849
4267
  * {@link import('./types.js').AgentOptions} `window`), a provider-independent approximation for conforming messages rather than an exact tokenizer count.
3850
4268
  *
3851
4269
  * @remarks
@@ -3855,12 +4273,12 @@ export declare const DETERMINISTIC_JUDGE_ERROR: RegExp;
3855
4273
  * `calls` plus, when present, {@link estimateTokens} over its `thinking` plus `images.length * `{@link import('./constants.js').IMAGE_TOKEN_ESTIMATE} (a coarse,
3856
4274
  * deliberately-approximate per-image cost — a base64 length is not a token proxy). Deterministic
3857
4275
  * 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
4276
+ * It is the message term of the request estimate an agent's auto-compaction context budget
4277
+ * measures; a caller wanting a sharper count supplies its own `consumer` to `createBudget`
4278
+ * instead. A `calls` list JSON cannot carry (a circular or `BigInt`
4279
+ * `ToolCall.arguments`) contributes a fixed
3862
4280
  * {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD} (the same per-message overhead scale)
3863
- * instead of estimating the (unreachable) serialized length.
4281
+ * instead of an estimate of the (unreachable) serialized length.
3864
4282
  *
3865
4283
  * @param messages - The messages to estimate (a turn's appended assistant + tool messages)
3866
4284
  * @returns The summed estimated token count (`0` when empty)
@@ -3873,6 +4291,32 @@ export declare const DETERMINISTIC_JUDGE_ERROR: RegExp;
3873
4291
  */
3874
4292
  export declare function estimateMessages(messages: readonly Message[]): number;
3875
4293
 
4294
+ /**
4295
+ * Estimates the context-token footprint of a complete provider request — its messages through
4296
+ * {@link estimateMessages}, plus the rendered advertised `tools`, plus the rendered
4297
+ * `options.schema`. The `consumer` estimator an agent's `window` budget measures every request
4298
+ * through, a provider-independent heuristic rather than an exact tokenizer count.
4299
+ *
4300
+ * @remarks
4301
+ * Adds {@link estimateTokens} over the JSON rendering of `tools` and of `options.schema` to the
4302
+ * message estimate. An absent or empty `tools` list and an absent or empty `schema` add `0`, so a
4303
+ * request that advertises neither equals {@link estimateMessages} of its messages. A value JSON
4304
+ * cannot carry contributes a fixed {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD}. The
4305
+ * estimate counts JSON characters, not a provider's template: the chat template's own tokens, how
4306
+ * a provider encodes tool definitions, and the `think` flag are not counted, and no tokenizer
4307
+ * runs. A model's real prompt count can exceed it, so choose the window `max` with headroom
4308
+ * measured against the provider.
4309
+ *
4310
+ * @param request - The {@link ProviderRequest} to estimate: messages, advertised tools, call options
4311
+ * @returns The estimated token count (`0` for an empty request)
4312
+ *
4313
+ * @example
4314
+ * ```ts
4315
+ * estimateRequest({ messages: [{ id: '1', role: 'user', content: 'hello' }] }) // 6, equal to estimateMessages
4316
+ * ```
4317
+ */
4318
+ export declare function estimateRequest(request: ProviderRequest): number;
4319
+
3876
4320
  /**
3877
4321
  * Estimates the context-token footprint of a string — the deterministic `ceil(length / 4)`
3878
4322
  * character heuristic {@link estimateMessages} sums over a conversation's messages (the default
@@ -3966,9 +4410,17 @@ export declare function extractTokens(text: string): LedgerTokenSet;
3966
4410
  */
3967
4411
  export declare function extractWords(message: Message): LedgerWordSet;
3968
4412
 
3969
- /** Carries an HTTP failure message and the underlying body-read cause when available. */
4413
+ /**
4414
+ * Carries an HTTP failure message, the response status, and the underlying body-read cause when
4415
+ * available.
4416
+ *
4417
+ * @remarks
4418
+ * The members spread past `message` form the options of a coded HTTP error: `status` is the
4419
+ * response status, and `cause` is present only when the error body could not be read.
4420
+ */
3970
4421
  export declare interface FailureRead {
3971
4422
  readonly message: string;
4423
+ readonly status: number;
3972
4424
  readonly cause?: unknown;
3973
4425
  }
3974
4426
 
@@ -4103,7 +4555,7 @@ export declare interface GaugeCall {
4103
4555
  * Prices prompts in tokens and measures the room a request has left.
4104
4556
  *
4105
4557
  * @remarks
4106
- * `scale` and `overhead` hold the price the gauge read from its last observation.
4558
+ * `scale` holds the price from the last observation; `overhead` holds the construction price.
4107
4559
  */
4108
4560
  export declare interface GaugeInterface extends LedgerGauge {
4109
4561
  /**
@@ -4193,38 +4645,36 @@ export declare interface GaugeOptions extends LedgerGauge {
4193
4645
  export declare function handleAgentQueueJob(registry: AgentRegistryInterface, partial: boolean, input: AgentJobInput, context: QueueContext): Promise<AgentResult>;
4194
4646
 
4195
4647
  /**
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.
4648
+ * Checks whether a message carries at least one tool call.
4199
4649
  *
4200
4650
  * @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
4651
+ * Pure and total. An absent `calls` member and an empty list both return false, so a tool-free
4652
+ * assistant turn and a turn whose provider reported an empty call list read alike.
4208
4653
  *
4654
+ * @param message - The message to inspect; only its `calls` member is read
4655
+ * @returns True if `calls` holds at least one call; false otherwise
4209
4656
  * @example
4210
4657
  * ```ts
4211
- * const result = await handleAgentRunnerJob(registry, false, controller)
4658
+ * hasCalls({ calls: [{ id: 'order', name: 'lookup', arguments: {} }] }) // true
4659
+ * hasCalls({ calls: [] }) // false
4660
+ * hasCalls({}) // false
4212
4661
  * ```
4213
4662
  */
4214
- export declare function handleAgentRunnerJob(registry: AgentRegistryInterface, partial: boolean, controller: ControllerInterface<AgentJobInput, AgentResult>): Promise<AgentResult>;
4663
+ export declare function hasCalls(message: Pick<Message, 'calls'>): boolean;
4215
4664
 
4216
4665
  /**
4217
4666
  * Identifies a lookup reading for projection by its tool name and normalized arguments.
4218
4667
  *
4219
4668
  * @remarks
4220
4669
  * 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.
4670
+ * string argument passes through {@link normalizeArgument} first, so `"lh-1 "` and `"LH-1"` name one call.
4222
4671
  * The ledger's repeat stop instead compares the tool name and canonical arguments without this
4223
4672
  * string normalization, so those argument spellings remain distinct within a request.
4673
+ * An uncanonicalizable call has no identity and neither replaces nor is replaced by another reading.
4224
4674
  *
4225
4675
  * @param name - The tool name
4226
4676
  * @param args - The arguments the call carried
4227
- * @returns The identity: the name, a space, and the canonical arguments
4677
+ * @returns The name, a space, and the canonical arguments, or `undefined` when uncanonicalizable
4228
4678
  *
4229
4679
  * @example
4230
4680
  * ```ts
@@ -4232,7 +4682,7 @@ export declare function handleAgentRunnerJob(registry: AgentRegistryInterface, p
4232
4682
  * identifyLookup('lookup_order', { opts: { region: 'north', tier: 2 }, id: ' BW-5512' }) // true
4233
4683
  * ```
4234
4684
  */
4235
- export declare function identifyLookup(name: string, args: Readonly<Record<string, unknown>>): string;
4685
+ export declare function identifyLookup(name: string, args: Readonly<Record<string, unknown>>): string | undefined;
4236
4686
 
4237
4687
  /**
4238
4688
  * Names the coarse, deliberately approximate per-image token cost
@@ -4247,6 +4697,43 @@ export declare function identifyLookup(name: string, args: Readonly<Record<strin
4247
4697
  */
4248
4698
  export declare const IMAGE_TOKEN_ESTIMATE = 512;
4249
4699
 
4700
+ /**
4701
+ * Infers which of a run's three failure layers stopped it — the meter allowance, a cost budget, or
4702
+ * the model's own token limit — and returns the stop record with the final call's readings, or
4703
+ * `undefined` when no layer stopped the run.
4704
+ *
4705
+ * @remarks
4706
+ * The layer that stopped the final provider call first wins. A model-truncated final answer whose
4707
+ * call returned unaborted is a `model` stop, even when that call's reconciled usage then exhausted a
4708
+ * budget or a cancel then landed before the run settled. Otherwise a `BudgetError` reason is a
4709
+ * `meter` stop when the allowance is exhausted, the reason is the allowance signal's own reason, its
4710
+ * context carries the allowance's `id` and `max`, and either the allowance is a separate instance or
4711
+ * the run had aborted when the call returned; any other `BudgetError` is a `budget` stop. The
4712
+ * identity test tells an allowance and a budget that share an `id` apart, and the readings test
4713
+ * tells a parent budget linked through the allowance's `signal` option apart from the allowance. An
4714
+ * external cancel, a deadline, or an `abort()` reason yields `undefined`. Two inputs still read as
4715
+ * the meter's: a charge a caller makes to the run's shared allowance from its own code while a call
4716
+ * streams, and a parent with the allowance's own `id` and `max` that aborted before `start()` when
4717
+ * the allowance then exhausts.
4718
+ *
4719
+ * @param reason - The run signal's abort reason (`undefined` while it is unaborted)
4720
+ * @param allowance - The run's meter allowance, or `undefined` for a run with no meter layer
4721
+ * @param budget - The run's cost budget, or `undefined` for an unbudgeted run
4722
+ * @param provider - The provider's trace `id`, named by a `model` stop
4723
+ * @param turn - The run's final turn record, or `undefined` when the run took no turn
4724
+ * @returns The stop record, or `undefined` when no layer stopped the run
4725
+ *
4726
+ * @example
4727
+ * ```ts
4728
+ * const allowance = createTokenBudget({ id: 'meter', max: 2 })
4729
+ * allowance.start()
4730
+ * allowance.consume({ prompt: 0, completion: 3, total: 3 })
4731
+ * inferStop(allowance.signal.reason, allowance, undefined, 'ollama', undefined)
4732
+ * // { layer: 'meter', id: 'meter', max: 2, consumed: 3 }
4733
+ * ```
4734
+ */
4735
+ export declare function inferStop(reason: unknown, allowance: BudgetInterface<TokenUsage> | undefined, budget: BudgetInterface<TokenUsage> | undefined, provider: string, turn: AgentTurn | undefined): AgentStop | undefined;
4736
+
4250
4737
  /**
4251
4738
  * Represents an immutable named directive — an {@link InstructionInterface} assembled once from
4252
4739
  * its input (`name` / `content` and an optional `priority`), the `id` minted at
@@ -4428,21 +4915,39 @@ export declare interface InstructionManagerInterface {
4428
4915
  /**
4429
4916
  * Adds one {@link InstructionInput}, or a batch — mints each `id`; adding an instruction whose
4430
4917
  * name is already stored overwrites it, last write wins.
4918
+ *
4919
+ * @param input - One {@link InstructionInput}, or a batch
4920
+ * @returns The stored instruction, or the stored batch in input order
4431
4921
  */
4432
4922
  add(input: InstructionInput): InstructionInterface;
4433
4923
  add(inputs: readonly InstructionInput[]): readonly InstructionInterface[];
4434
- /** Looks up one instruction by name (`undefined` when absent). */
4924
+ /**
4925
+ * Looks up one instruction by name (`undefined` when absent).
4926
+ *
4927
+ * @param name - The instruction name to look up
4928
+ * @returns The stored instruction, or `undefined` when absent
4929
+ */
4435
4930
  instruction(name: string): InstructionInterface | undefined;
4436
- /** Lists every instruction, sorted by descending `priority` (stable for equal priorities). */
4931
+ /**
4932
+ * Lists every instruction, sorted by descending `priority` (stable for equal priorities).
4933
+ *
4934
+ * @returns Every stored instruction, highest `priority` first
4935
+ */
4437
4936
  instructions(): readonly InstructionInterface[];
4438
4937
  /**
4439
4938
  * Renders one instruction for the prompt — its `override`, else the manager-options
4440
4939
  * `render`, else its `content`.
4940
+ *
4941
+ * @param instruction - The instruction to render
4942
+ * @returns The instruction's prompt text, `''` when its `override` is `''`
4441
4943
  */
4442
4944
  render(instruction: InstructionInterface): string;
4443
4945
  /**
4444
4946
  * Removes one instruction by name, or a batch. True if every supplied name was removed; false
4445
4947
  * otherwise.
4948
+ *
4949
+ * @param name - One instruction name, or a batch
4950
+ * @returns True if every supplied name was present and removed; false otherwise
4446
4951
  */
4447
4952
  remove(name: string): boolean;
4448
4953
  remove(names: readonly string[]): boolean;
@@ -4669,8 +5174,11 @@ export declare function isJudgeQuestion(value: unknown): value is JudgeQuestion;
4669
5174
  * Checks whether a stored judgment carries a valid question and exactly one answer or refusal.
4670
5175
  *
4671
5176
  * @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.
5177
+ * Unknown members, malformed members, and unreadable inputs return false. Answer fields follow
5178
+ * the root judge value types; this storage guard does not impose a wire's probability or
5179
+ * candidate limits. The `time`, `noul`, and probability numbers must be finite, because a stored
5180
+ * record is owned through JSON, which turns `NaN` and the infinities into `null`; so every record
5181
+ * this guard accepts hydrates through a `JudgmentManager`.
4674
5182
  *
4675
5183
  * @param value - The unknown stored record
4676
5184
  * @returns True if the record satisfies the judgment contract; false otherwise
@@ -4717,6 +5225,27 @@ export declare function isLedgerError(value: unknown): value is LedgerError;
4717
5225
  */
4718
5226
  export declare function isMessage(value: unknown): value is Message;
4719
5227
 
5228
+ /**
5229
+ * Checks whether a value is a non-negative safe integer, `-0` included.
5230
+ *
5231
+ * @remarks
5232
+ * Total: a non-number, `NaN`, an infinity, a fraction, a negative number, and an integer past
5233
+ * `Number.MAX_SAFE_INTEGER` return false. It differs from `isNonNegativeInteger` in
5234
+ * `@orkestrel/contract` at two values: that guard rejects `-0` and admits an unsafe integer such
5235
+ * as `2 ** 53`, while this guard admits `-0` and rejects `2 ** 53`.
5236
+ *
5237
+ * @param value - The unknown count or limit candidate
5238
+ * @returns True if the value is a safe integer at or above zero; false otherwise
5239
+ * @example
5240
+ * ```ts
5241
+ * isNonNegativeSafeInteger(0) // true
5242
+ * isNonNegativeSafeInteger(-0) // true
5243
+ * isNonNegativeSafeInteger(2 ** 53) // false
5244
+ * isNonNegativeSafeInteger(1.5) // false
5245
+ * ```
5246
+ */
5247
+ export declare function isNonNegativeSafeInteger(value: unknown): value is number;
5248
+
4720
5249
  /**
4721
5250
  * Checks whether a value is a positive safe integer.
4722
5251
  * @param value - The value to inspect
@@ -5076,13 +5605,15 @@ export declare class Ledger implements LedgerInterface {
5076
5605
  */
5077
5606
  calibrate(signal: AbortSignal): Promise<LedgerGauge>;
5078
5607
  /**
5079
- * Appends a request and serves it, recovering an unfinished first pass with one answer pass.
5608
+ * Appends a request and serves it, recovering an unfinished or rejected first pass with one answer pass.
5080
5609
  * 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.
5610
+ * When the answer pass rejects, `respond` rejects with that error. Every rejection leaves the ledger free for a later call.
5081
5611
  * @param content - The request text
5082
5612
  * @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
5613
+ * @returns The final pass, the usage of every pass, and the filing readings of this request alone
5084
5614
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
5085
5615
  * @throws {LedgerError} Thrown when a calibration call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
5616
+ * @throws {ContractError} Thrown when `signal` is defined and is not a native `AbortSignal`
5086
5617
  */
5087
5618
  respond(content: string, signal?: AbortSignal): Promise<LedgerResult>;
5088
5619
  }
@@ -5134,6 +5665,12 @@ export declare const LEDGER_SCALE_DRIFT = 0.06;
5134
5665
  */
5135
5666
  export declare type LedgerAgentOptions = Pick<AgentOptions, 'limit' | 'timeout' | 'on'>;
5136
5667
 
5668
+ /** Identifies an accepted direct message amendment whose sentence scope remains unresolved. */
5669
+ export declare interface LedgerAmendment {
5670
+ readonly earlier: string;
5671
+ readonly later: string;
5672
+ }
5673
+
5137
5674
  /** Names the category the ledger files a message under, one of {@link LEDGER_CATEGORIES}. */
5138
5675
  export declare type LedgerCategory = (typeof LEDGER_CATEGORIES)[number];
5139
5676
 
@@ -5184,6 +5721,12 @@ export declare type LedgerEntityHandler = (text: string, partial: boolean) => Re
5184
5721
  export declare class LedgerError extends Error {
5185
5722
  /** Names the machine-readable condition; the {@link LedgerErrorCode} union describes each code. */
5186
5723
  readonly code: LedgerErrorCode;
5724
+ /**
5725
+ * Records the failure condition and its description.
5726
+ *
5727
+ * @param code - The machine-readable condition, exposed as `error.code`
5728
+ * @param message - The human-readable description of the failure
5729
+ */
5187
5730
  constructor(code: LedgerErrorCode, message: string);
5188
5731
  }
5189
5732
 
@@ -5204,7 +5747,9 @@ export declare type LedgerErrorCode =
5204
5747
  /** Reports a lookup tool named `recall` or a name another lookup tool carries. */
5205
5748
  | 'LOOKUP'
5206
5749
  /** Reports a supplied gauge outside its bounds, or a calibration call that reports no prompt usage, or a prompt usage of 0 or less. */
5207
- | 'GAUGE';
5750
+ | 'GAUGE'
5751
+ /** Reports a render context that holds no reference for a source it annotates. */
5752
+ | 'REFERENCE';
5208
5753
 
5209
5754
  /**
5210
5755
  * Carries the price of a prompt in tokens, measured against the model the ledger serves.
@@ -5242,17 +5787,22 @@ export declare interface LedgerInterface {
5242
5787
  * @remarks
5243
5788
  * The ledger calibrates first while `gauge` is undefined. When the first pass ends without final
5244
5789
  * 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
5790
+ * answer pass that advertises no tools. A rejected first pass counts as unfinished; when the
5791
+ * answer pass rejects as well, `respond` rejects with that pass's error. A `respond` or `calibrate` call while either is in flight
5246
5792
  * 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
5793
  * `LedgerError` code `'GAUGE'`; a provider error during calibration rejects with that error.
5248
5794
  * Direct agent runs share an active request's gauge readings and repeat stop, even when their
5249
5795
  * selections fault. Only `respond` and `calibrate` calls are refused by the concurrency guard.
5796
+ * A run no `respond` call owns, including a run during calibration, reads no request's state:
5797
+ * its lookup and `recall` calls run with no repeat stop, recall limit, or close rule.
5798
+ * Every rejection leaves the ledger free for a later call.
5250
5799
  *
5251
5800
  * @param content - The request text
5252
5801
  * @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
5802
+ * @returns The reply, the passes the request took, and the filing readings of this request alone
5254
5803
  * @throws {LedgerError} Thrown when a calibration call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
5255
5804
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
5805
+ * @throws {ContractError} Thrown when `signal` is defined and is not a native `AbortSignal`
5256
5806
  */
5257
5807
  respond(content: string, signal?: AbortSignal): Promise<LedgerResult>;
5258
5808
  /**
@@ -5423,12 +5973,12 @@ export declare interface LedgerOwner {
5423
5973
  export declare type LedgerPlanningGroup = 1 | 2 | 3;
5424
5974
 
5425
5975
  /**
5426
- * Carries the records projected from a conversation, the stale sentences, and the live messages no
5976
+ * Carries the records projected from a conversation, accepted amendments, and the live messages no
5427
5977
  * record placed.
5428
5978
  */
5429
5979
  export declare interface LedgerProjection {
5430
5980
  readonly records: readonly LedgerRecord[];
5431
- readonly stale: readonly LedgerStaleSentence[];
5981
+ readonly amendments: readonly LedgerAmendment[];
5432
5982
  readonly orphans: readonly string[];
5433
5983
  }
5434
5984
 
@@ -5496,8 +6046,9 @@ export declare interface LedgerRecallOptions {
5496
6046
  *
5497
6047
  * @remarks
5498
6048
  * `key` is `rules` for the rules record and `owner:` followed by the owner id for an owner record.
5499
- * `title` is `Rules`, or the owner's first name with its id. `members` lists the placed message ids
5500
- * in conversation order.
6049
+ * `title` is `Rules`, or the owner's first name with its id. In a request view, owner records with
6050
+ * the same member ids in the same order render once, under their titles joined with `; `.
6051
+ * `members` lists the placed message ids in conversation order.
5501
6052
  */
5502
6053
  export declare interface LedgerRecord {
5503
6054
  readonly key: string;
@@ -5519,6 +6070,220 @@ export declare interface LedgerRegistry {
5519
6070
  readonly owners: ReadonlyMap<string, readonly string[]>;
5520
6071
  }
5521
6072
 
6073
+ /**
6074
+ * Supplies accepted amendments and completely included sources for one briefing rendering.
6075
+ *
6076
+ * @remarks
6077
+ * `first` identifies each source's first retained line object in actual emission order. Build this
6078
+ * context from the same line objects passed to the renderers after deduplication and trimming;
6079
+ * recompute it when the retained population changes. `references` assigns each distinct retained
6080
+ * source its decimal display reference in first-occurrence order, starting at 1. These
6081
+ * briefing-local references can change after trimming; the projection, recall, and storage retain
6082
+ * original source IDs.
6083
+ */
6084
+ export declare interface LedgerRenderContext {
6085
+ readonly amendments: readonly LedgerAmendment[];
6086
+ readonly complete: ReadonlySet<string>;
6087
+ readonly first: ReadonlyMap<string, LedgerLine>;
6088
+ readonly references: ReadonlyMap<string, string>;
6089
+ }
6090
+
6091
+ /**
6092
+ * Holds what one ledger request keeps while it is served: its message, its filing readings and
6093
+ * plan, the gauge calls of its passes, and its repeat and recall stops.
6094
+ *
6095
+ * @remarks
6096
+ * A ledger creates one request per `respond` call and drops it when the call settles. Each pass the
6097
+ * request opens holds its own selection, boundary, usage, and calls; the request reads the current
6098
+ * pass, the last one opened. {@link LedgerRequestInterface} describes each member.
6099
+ *
6100
+ * @example
6101
+ * ```ts
6102
+ * const request = new LedgerRequest('What approval is needed?', 1)
6103
+ * request.enter(conversation) // the appended user message
6104
+ * request.admit('lookup', { id: 'BW-20931' })
6105
+ * request.admit('lookup', { id: 'BW-20931' }) // throws Error(LEDGER_NOTES.repeat)
6106
+ * request.recall()
6107
+ * request.recall() // throws Error(LEDGER_NOTES.closure): the limit of 1 is spent
6108
+ * ```
6109
+ */
6110
+ export declare class LedgerRequest implements LedgerRequestInterface {
6111
+ #private;
6112
+ /**
6113
+ * Holds the request's content and its recall limit.
6114
+ *
6115
+ * @param content - The request text `enter` appends
6116
+ * @param limit - The `recall` calls the request can make. Default: the `DEFAULT_RECALL_LIMIT` constant
6117
+ * @throws {LedgerError} Thrown when `limit` is not a nonnegative safe integer (code `'LIMIT'`)
6118
+ */
6119
+ constructor(content: string, limit?: number);
6120
+ /**
6121
+ * Returns the request message.
6122
+ *
6123
+ * @returns The message `enter` appended, or undefined before it
6124
+ */
6125
+ get message(): Message | undefined;
6126
+ /**
6127
+ * Returns the selection the request's last successful plan produced.
6128
+ *
6129
+ * @returns The planned selection, or undefined before one
6130
+ */
6131
+ get plan(): Selection | undefined;
6132
+ /**
6133
+ * Returns the selection the current pass's run adopted last.
6134
+ *
6135
+ * @returns The selection, or undefined before one
6136
+ */
6137
+ get selection(): Selection | undefined;
6138
+ /**
6139
+ * Returns the conversation length the current pass's calls count additions from.
6140
+ *
6141
+ * @returns The length, or undefined before a pass
6142
+ */
6143
+ get boundary(): number | undefined;
6144
+ /**
6145
+ * Returns the summed usage of the current pass.
6146
+ *
6147
+ * @returns The summed usage, or undefined while none reported usage
6148
+ */
6149
+ get usage(): TokenUsage | undefined;
6150
+ /**
6151
+ * Returns the gauge calls of every pass.
6152
+ *
6153
+ * @returns The calls in order, the call in flight last
6154
+ */
6155
+ get calls(): readonly GaugeCall[];
6156
+ /**
6157
+ * Returns the filing readings of the request's planning selections.
6158
+ *
6159
+ * @returns The readings in asking order
6160
+ */
6161
+ get readings(): readonly ClassifierReading[];
6162
+ enter(conversation: ConversationInterface): Message;
6163
+ open(boundary: number): void;
6164
+ adopt(selection: Selection, boundary: number): void;
6165
+ observe(call: GaugeCall, position: number): void;
6166
+ spend(usage: TokenUsage): void;
6167
+ think(messages: readonly Message[]): void;
6168
+ file(readings: readonly ClassifierReading[], plan?: Selection): void;
6169
+ admit(name: string, args: Readonly<Record<string, unknown>>): void;
6170
+ recall(): void;
6171
+ close(): void;
6172
+ list(result: string, lines: ReadonlyMap<string, string>): void;
6173
+ listing(result: string): ReadonlyMap<string, string> | undefined;
6174
+ }
6175
+
6176
+ /**
6177
+ * Holds what one `respond` call of a ledger keeps while it serves its request: the request message,
6178
+ * the filing readings and the plan its selections produced, the gauge calls of its passes, and its
6179
+ * repeat and recall stops.
6180
+ *
6181
+ * @remarks
6182
+ * A ledger creates one request per `respond` call and drops it when the call settles, so no request
6183
+ * state reaches a later call or a run no call owns. Each agent run the call makes is a pass: `open`
6184
+ * starts one, and `selection`, `boundary`, and `usage` read the current pass, the last one opened.
6185
+ * `calls` lists the gauge calls of every pass in order. A call stays in flight, taking its usage and
6186
+ * thinking, until its pass observes the next call, and is final from then on.
6187
+ */
6188
+ export declare interface LedgerRequestInterface {
6189
+ /** Holds the request message, or undefined until `enter` appends it. */
6190
+ readonly message: Message | undefined;
6191
+ /** Holds the selection the request's last successful plan produced, or undefined before one. */
6192
+ readonly plan: Selection | undefined;
6193
+ /** Holds the selection the current pass's run adopted last, or undefined before one. */
6194
+ readonly selection: Selection | undefined;
6195
+ /** Holds the conversation length at the current pass's start or at its last adopted selection, or undefined before a pass. */
6196
+ readonly boundary: number | undefined;
6197
+ /** Holds the summed usage of the current pass's selections and calls, or undefined while none reported usage. */
6198
+ readonly usage: TokenUsage | undefined;
6199
+ /** Holds the gauge calls of every pass in order, the call in flight last. */
6200
+ readonly calls: readonly GaugeCall[];
6201
+ /** Holds the filing readings of every planning selection the request made, in asking order. */
6202
+ readonly readings: readonly ClassifierReading[];
6203
+ /**
6204
+ * Appends the request's content to a conversation as a user message, once, and holds it as `message`.
6205
+ *
6206
+ * @param conversation - The conversation the request is served from
6207
+ * @returns The request message; a later call returns it without appending again
6208
+ */
6209
+ enter(conversation: ConversationInterface): Message;
6210
+ /**
6211
+ * Opens a pass, which becomes the current pass.
6212
+ *
6213
+ * @param boundary - The conversation length when the pass's run starts
6214
+ */
6215
+ open(boundary: number): void;
6216
+ /**
6217
+ * Records the selection the current pass's run adopted and adds the usage it spent.
6218
+ *
6219
+ * @param selection - The selection the run built its prompt from
6220
+ * @param boundary - The conversation length when the run adopted it
6221
+ */
6222
+ adopt(selection: Selection, boundary: number): void;
6223
+ /**
6224
+ * Starts a gauge call in the current pass, making that pass's call in flight final.
6225
+ *
6226
+ * @param call - The call's estimate and advertised tool count
6227
+ * @param position - The conversation length when the call starts
6228
+ */
6229
+ observe(call: GaugeCall, position: number): void;
6230
+ /**
6231
+ * Adds one call's reported usage to the current pass and records it on the call in flight.
6232
+ *
6233
+ * @param usage - The usage the provider reported for the call
6234
+ */
6235
+ spend(usage: TokenUsage): void;
6236
+ /**
6237
+ * Records the thinking share of the call in flight, read from the first assistant message after
6238
+ * the call's position.
6239
+ *
6240
+ * @remarks
6241
+ * Records nothing while the call in flight has no reported completion or no such message carries
6242
+ * thinking.
6243
+ *
6244
+ * @param messages - The conversation's messages
6245
+ */
6246
+ think(messages: readonly Message[]): void;
6247
+ /**
6248
+ * Appends the readings of one planning selection and holds its plan when planning succeeded.
6249
+ *
6250
+ * @param readings - The selection's filing readings, in asking order
6251
+ * @param plan - The planned selection, or undefined when planning failed
6252
+ */
6253
+ file(readings: readonly ClassifierReading[], plan?: Selection): void;
6254
+ /**
6255
+ * Admits one tool call of the request, compared by tool name and canonical arguments.
6256
+ *
6257
+ * @param name - The tool name
6258
+ * @param args - The call's arguments
6259
+ * @throws {Error} Thrown with the {@link LedgerNote} `repeat` text when the request already admitted the same call
6260
+ * @throws {Error} Thrown when the arguments have no canonical identity
6261
+ */
6262
+ admit(name: string, args: Readonly<Record<string, unknown>>): void;
6263
+ /**
6264
+ * Counts one `recall` call, closing the request's recall tool when its recalls reach the limit.
6265
+ *
6266
+ * @throws {Error} Thrown with the {@link LedgerNote} `closure` text when the recall tool is closed
6267
+ */
6268
+ recall(): void;
6269
+ /** Closes the request's recall tool, so every later `recall` call throws the closure note. */
6270
+ close(): void;
6271
+ /**
6272
+ * Holds the stored first line of each lookup a recall result listed, keyed by its listed line.
6273
+ *
6274
+ * @param result - The recall result text
6275
+ * @param lines - Each listed lookup line mapped to the first line of its stored content
6276
+ */
6277
+ list(result: string, lines: ReadonlyMap<string, string>): void;
6278
+ /**
6279
+ * Returns the stored first lines a recall result listed.
6280
+ *
6281
+ * @param result - The recall result text
6282
+ * @returns Each listed lookup line mapped to its stored first line, or undefined when no recall returned `result`
6283
+ */
6284
+ listing(result: string): ReadonlyMap<string, string> | undefined;
6285
+ }
6286
+
5522
6287
  /**
5523
6288
  * Carries the outcome of one request a ledger served: the agent result of its reply and every pass
5524
6289
  * it took.
@@ -5526,10 +6291,14 @@ export declare interface LedgerRegistry {
5526
6291
  * @remarks
5527
6292
  * `passes` holds the first pass and, when the ledger ran one, the answer pass. `content` and
5528
6293
  * `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.
6294
+ * reasoning; each optional member is present when any pass reported it. `stop` is the last pass's
6295
+ * stop, when one stopped it. `readings` lists every {@link ClassifierReading} the request's
6296
+ * selections recorded, in asking order. A request's reading count is its logical questions, never
6297
+ * its physical wire calls.
5530
6298
  */
5531
6299
  export declare interface LedgerResult extends AgentResult {
5532
6300
  readonly passes: readonly AgentResult[];
6301
+ readonly readings: readonly ClassifierReading[];
5533
6302
  }
5534
6303
 
5535
6304
  /**
@@ -5546,20 +6315,6 @@ export declare interface LedgerShare {
5546
6315
  readonly tail: number;
5547
6316
  }
5548
6317
 
5549
- /**
5550
- * Carries one sentence a later message made stale, with the tokens the two share.
5551
- *
5552
- * @remarks
5553
- * `source` is the earlier message's id and `sentence` the zero-based index of the sentence. The
5554
- * projection leaves a stale sentence out of the records and the briefing. Recall and answer notes
5555
- * keep the stored content. The seed tail replaces lookup results with status stubs.
5556
- */
5557
- export declare interface LedgerStaleSentence {
5558
- readonly source: string;
5559
- readonly sentence: number;
5560
- readonly tokens: readonly string[];
5561
- }
5562
-
5563
6318
  /**
5564
6319
  * Carries the probability cutoff of each reading the ledger takes from its judge.
5565
6320
  *
@@ -5695,6 +6450,54 @@ export declare function matchesJudgment(judgment: Judgment, question: JudgeQuest
5695
6450
  */
5696
6451
  export declare const MAX_ERROR_BODY_LENGTH = 2048;
5697
6452
 
6453
+ /**
6454
+ * Measures the disjoint estimate components of one message — its content, framing, rendered calls,
6455
+ * thinking, and attached images — as the same heuristic units {@link estimateMessages} sums.
6456
+ *
6457
+ * @remarks
6458
+ * The result is a structural estimate, not a tokenizer allocation. `id` and `role` are joins back to
6459
+ * the message, not token components, and a provider's template effects and the estimation error are
6460
+ * not individually attributed. `content` is `ceil(length / 4)`, `framing` is
6461
+ * {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD}, `calls` is the estimate of the JSON-rendered
6462
+ * calls (`0` when the message carries none, the framing overhead when they cannot be rendered),
6463
+ * `thinking` is the estimate of the thinking (`0` when absent), and `images` is the attached image
6464
+ * count times {@link import('./constants.js').IMAGE_TOKEN_ESTIMATE}. The message is not mutated.
6465
+ *
6466
+ * @param message - The message to measure
6467
+ * @returns The message's identity and its estimate components
6468
+ *
6469
+ * @example
6470
+ * ```ts
6471
+ * measureMessage({ id: '1', role: 'user', content: 'hello' })
6472
+ * // { id: '1', role: 'user', content: 2, framing: 4, calls: 0, thinking: 0, images: 0 }
6473
+ * ```
6474
+ */
6475
+ export declare function measureMessage(message: Message): MessageMeasure;
6476
+
6477
+ /**
6478
+ * Measures the disjoint estimate components of a complete provider request — each message through
6479
+ * {@link measureMessage}, plus the rendered advertised `tools` and `options.schema` — as the same
6480
+ * heuristic units {@link estimateRequest} sums.
6481
+ *
6482
+ * @remarks
6483
+ * The result is a structural estimate, not a tokenizer allocation; a provider's template effects, how
6484
+ * it encodes tool definitions, the `think` flag, and the estimation error are not individually
6485
+ * attributed. `messages` keeps the request's message order, duplicate ids included. `tools` and
6486
+ * `schema` are each `0` when absent or empty and the estimate of their JSON rendering otherwise, or
6487
+ * the {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD} when JSON cannot carry them. The request
6488
+ * is not mutated.
6489
+ *
6490
+ * @param request - The {@link ProviderRequest} to measure: messages, advertised tools, call options
6491
+ * @returns The per-message components and the tools and schema estimates
6492
+ *
6493
+ * @example
6494
+ * ```ts
6495
+ * measureRequest({ messages: [{ id: '1', role: 'user', content: 'hello' }] })
6496
+ * // { messages: [{ id: '1', role: 'user', content: 2, framing: 4, calls: 0, thinking: 0, images: 0 }], tools: 0, schema: 0 }
6497
+ * ```
6498
+ */
6499
+ export declare function measureRequest(request: ProviderRequest): RequestMeasure;
6500
+
5698
6501
  /**
5699
6502
  * Implements the {@link ConversationStoreInterface} in memory — a process-lifetime `Map` of
5700
6503
  * {@link ConversationSnapshot} records keyed by conversation id, the default store
@@ -5910,6 +6713,30 @@ export declare interface MessageManagerInterface {
5910
6713
  clear(): void;
5911
6714
  }
5912
6715
 
6716
+ /**
6717
+ * Holds the disjoint estimate components of one message, in heuristic estimate units.
6718
+ *
6719
+ * @remarks
6720
+ * The structural estimate is not a tokenizer allocation. `id` and `role` are joins back to the
6721
+ * message, not token components. A message's estimate is the sum of `content`, `framing`, `calls`,
6722
+ * `thinking`, and `images`; no total is stored. A provider's template effects and the estimation
6723
+ * error are not individually attributed.
6724
+ */
6725
+ export declare interface MessageMeasure {
6726
+ readonly id: string;
6727
+ readonly role: MessageRole;
6728
+ /** Holds the `ceil(length / 4)` estimate of the message `content`. */
6729
+ readonly content: number;
6730
+ /** Holds the fixed per-message framing overhead. */
6731
+ readonly framing: number;
6732
+ /** Holds the estimate of the JSON-rendered `calls`, or the framing overhead when they cannot be rendered; `0` when the message carries none. */
6733
+ readonly calls: number;
6734
+ /** Holds the estimate of the message `thinking`; `0` when absent. */
6735
+ readonly thinking: number;
6736
+ /** Holds the per-image estimate times the attached image count; `0` when none. */
6737
+ readonly images: number;
6738
+ }
6739
+
5913
6740
  /** Names the role a {@link Message} plays in a conversation turn. */
5914
6741
  export declare type MessageRole = (typeof MESSAGE_ROLES)[number];
5915
6742
 
@@ -5934,6 +6761,23 @@ images: OptionalShape<ArrayShape<StringShape>>;
5934
6761
  thinking: OptionalShape<StringShape>;
5935
6762
  }, boolean | ContractShape>;
5936
6763
 
6764
+ /**
6765
+ * Normalizes a lookup argument string to the spelling that lookup identity and owner linking compare.
6766
+ *
6767
+ * @remarks
6768
+ * {@link identifyLookup} and {@link linkOwners} read an argument through this one form, so a reading
6769
+ * a later lookup replaces and the owner link of its argument agree on the spelling.
6770
+ *
6771
+ * @param value - The string argument a lookup call carried
6772
+ * @returns The argument trimmed and uppercased
6773
+ *
6774
+ * @example
6775
+ * ```ts
6776
+ * normalizeArgument(' bw-5512 ') // 'BW-5512'
6777
+ * ```
6778
+ */
6779
+ export declare function normalizeArgument(value: string): string;
6780
+
5937
6781
  /** Carries the probability that the answer is yes; the protocol's noul field. */
5938
6782
  export declare interface NoulAnswer {
5939
6783
  readonly form: 'noul';
@@ -5972,11 +6816,20 @@ export declare const PLACED_CATEGORIES: readonly LedgerCategory[];
5972
6816
  * A message that is not live still places, because a correction joins where its earlier side would
5973
6817
  * join.
5974
6818
  *
6819
+ * With `settled`, the walk returns a placement the map holds without walking again, and records each
6820
+ * placement whose walk, its nested walks included, excluded no earlier side through `seen`. Such a
6821
+ * walk reaches no cycle. While every `seen` holds only the path that reached the message, as
6822
+ * {@link buildRecords} passes it, that path never holds a message the recorded walk reached, so a
6823
+ * fresh walk returns the same keys in the same order. A placement a cycle cut stays unrecorded,
6824
+ * because its keys depend on the path that reached it. Sharing one map across the members of one
6825
+ * input bounds a dense amendment graph to one walk per message.
6826
+ *
5975
6827
  * @param input - The owners, entities, and classification to read
5976
6828
  * @param links - The owner of each linked lookup argument, from {@link linkOwners}
5977
6829
  * @param amending - The earlier sides of each message's amendment pairs
5978
6830
  * @param id - The message id
5979
6831
  * @param seen - The ids already visited, which stops a cycle of amendment pairs
6832
+ * @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
6833
  * @returns The record keys, empty when the message is an orphan
5981
6834
  *
5982
6835
  * @example
@@ -5984,7 +6837,7 @@ export declare const PLACED_CATEGORIES: readonly LedgerCategory[];
5984
6837
  * placeMember(input, new Map(), new Map(), 'user-1', new Set()) // Set { 'owner:BW-20931' }
5985
6838
  * ```
5986
6839
  */
5987
- export declare function placeMember(input: LedgerProjectionInput, links: ReadonlyMap<string, string>, amending: ReadonlyMap<string, readonly string[]>, id: string, seen: ReadonlySet<string>): ReadonlySet<string>;
6840
+ 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
6841
 
5989
6842
  /**
5990
6843
  * Reports a provider stream cancelled mid-flight by its bound signal — thrown by the
@@ -6008,8 +6861,8 @@ export declare class ProviderAbortError extends Error {
6008
6861
 
6009
6862
  /**
6010
6863
  * 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.
6864
+ * the channel it belongs to, so the agent loop can forward answer content and live reasoning
6865
+ * separately as each delta arrives.
6013
6866
  *
6014
6867
  * @remarks
6015
6868
  * The discriminant `channel` names the axis that varies: a
@@ -6067,6 +6920,7 @@ export declare interface ProviderIncrement {
6067
6920
  readonly thinking: string;
6068
6921
  readonly tools: readonly ToolCall[];
6069
6922
  readonly usage?: TokenUsage;
6923
+ readonly truncated?: boolean;
6070
6924
  readonly result?: ProviderResult;
6071
6925
  }
6072
6926
 
@@ -6238,7 +7092,9 @@ schema: OptionalShape<ObjectShape<Record<never, never>, JSONShape>>;
6238
7092
  * allows. `tools` is present only when the model requests tool calls (an
6239
7093
  * empty array is never surfaced — its absence means "no calls"). `usage` is present
6240
7094
  * 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.
7095
+ * body), so a caller folds it into a token budget exactly when it exists. `truncated` is present
7096
+ * only when the provider's wire reports that the model stopped at its limit; the agent attributes a
7097
+ * truncated final answer to the model layer.
6242
7098
  */
6243
7099
  export declare interface ProviderResult {
6244
7100
  readonly content: string;
@@ -6248,6 +7104,12 @@ export declare interface ProviderResult {
6248
7104
  readonly tools?: readonly ToolCall[];
6249
7105
  /** Carries token consumption for this turn when present (from the wire's `done` line / body). */
6250
7106
  readonly usage?: TokenUsage;
7107
+ /**
7108
+ * Reports that the model ended its answer at its own token limit (an output cap or a full context
7109
+ * window) rather than by finishing; present only when the provider's wire reports that stop. It
7110
+ * is not the request's prompt `truncate` setting.
7111
+ */
7112
+ readonly truncated?: boolean;
6251
7113
  }
6252
7114
 
6253
7115
  /** Validates and projects provider results at a JSON wire boundary. */
@@ -6267,6 +7129,7 @@ prompt: number;
6267
7129
  completion: number;
6268
7130
  total: number;
6269
7131
  } & {}>;
7132
+ truncated?: boolean;
6270
7133
  }>>;
6271
7134
 
6272
7135
  /**
@@ -6289,6 +7152,7 @@ prompt: NumberShape;
6289
7152
  completion: NumberShape;
6290
7153
  total: NumberShape;
6291
7154
  }, boolean | ContractShape>>;
7155
+ truncated: OptionalShape<BooleanShape>;
6292
7156
  }, boolean | ContractShape>;
6293
7157
 
6294
7158
  /**
@@ -6328,6 +7192,29 @@ export declare function questionToSystemOne(question: JudgeQuestion): SystemOneQ
6328
7192
  /** Lists the categories whose messages the projection leaves out as quiet. */
6329
7193
  export declare const QUIET_CATEGORIES: readonly LedgerCategory[];
6330
7194
 
7195
+ /**
7196
+ * Settles with a promise, or rejects with the reason of a signal that aborts first, so a caller
7197
+ * is released at once even when the work behind the promise ignores the signal.
7198
+ *
7199
+ * @remarks
7200
+ * An absent signal returns `promise` itself. An already-aborted signal rejects with its reason
7201
+ * without waiting. The abort listener is removed as soon as `promise` settles. A rejection of
7202
+ * `promise` after the abort is handled here and never surfaces as unhandled.
7203
+ *
7204
+ * @param promise - The work to wait for
7205
+ * @param signal - The cancellation signal, or `undefined` for none
7206
+ * @returns A promise that settles as `promise` does, or rejects with `signal.reason` on abort
7207
+ *
7208
+ * @example
7209
+ * ```ts
7210
+ * const controller = new AbortController()
7211
+ * const raced = raceSignal(new Promise<string>(() => {}), controller.signal)
7212
+ * controller.abort(new Error('stop'))
7213
+ * await raced // rejects with the Error 'stop'
7214
+ * ```
7215
+ */
7216
+ export declare function raceSignal<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T>;
7217
+
6331
7218
  /**
6332
7219
  * Ranks a briefing source for removal before the next prompt.
6333
7220
  *
@@ -6362,14 +7249,22 @@ export declare function readChunks(body: ReadableStream<Uint8Array>, signal?: Ab
6362
7249
  /**
6363
7250
  * Reads a bounded HTTP failure excerpt and preserves an unreadable body's cause.
6364
7251
  *
7252
+ * @remarks
7253
+ * Both HTTP engines, the provider and the judge, translate a non-OK response here and throw
7254
+ * their own coded error from the result. When the excerpt is read and `signal` has aborted, the
7255
+ * abort is the call's outcome, so the signal's reason is thrown. When the body read fails, the
7256
+ * failure is returned as `cause` whether or not `signal` has aborted, so a read that raced the
7257
+ * abort stays recorded on the error the caller throws.
7258
+ *
6365
7259
  * @param response - The failed HTTP response
6366
7260
  * @param prefix - The error taxonomy prefix, including its trailing colon
6367
7261
  * @param signal - The call's combined caller and deadline signal
6368
- * @returns The status message with any excerpt or body-read failure
7262
+ * @returns The status message with any excerpt, the response status, and any body-read failure
7263
+ * @throws Thrown when the excerpt is read after `signal` aborts, with the signal's reason
6369
7264
  * @example
6370
7265
  * ```ts
6371
7266
  * await readFailure(new Response('unavailable', { status: 503 }), 'provider error:', new AbortController().signal)
6372
- * // { message: 'provider error: 503 - unavailable' }
7267
+ * // { message: 'provider error: 503 - unavailable', status: 503 }
6373
7268
  * ```
6374
7269
  */
6375
7270
  export declare function readFailure(response: Response, prefix: string, signal: AbortSignal): Promise<FailureRead>;
@@ -6459,6 +7354,7 @@ prompt: number;
6459
7354
  completion: number;
6460
7355
  total: number;
6461
7356
  } & {}>;
7357
+ truncated?: boolean;
6462
7358
  }>;
6463
7359
  } & {}> | Readonly<{
6464
7360
  channel: "abort";
@@ -6478,6 +7374,7 @@ prompt: number;
6478
7374
  completion: number;
6479
7375
  total: number;
6480
7376
  } & {}>;
7377
+ truncated?: boolean;
6481
7378
  }>;
6482
7379
  } & {}> | Readonly<{
6483
7380
  channel: "error";
@@ -6510,6 +7407,7 @@ prompt: NumberShape;
6510
7407
  completion: NumberShape;
6511
7408
  total: NumberShape;
6512
7409
  }, boolean | ContractShape>>;
7410
+ truncated: OptionalShape<BooleanShape>;
6513
7411
  }, boolean | ContractShape>;
6514
7412
  }, boolean | ContractShape>, ObjectShape< {
6515
7413
  channel: LiteralShape<readonly ["abort"]>;
@@ -6526,6 +7424,7 @@ prompt: NumberShape;
6526
7424
  completion: NumberShape;
6527
7425
  total: NumberShape;
6528
7426
  }, boolean | ContractShape>>;
7427
+ truncated: OptionalShape<BooleanShape>;
6529
7428
  }, boolean | ContractShape>;
6530
7429
  }, boolean | ContractShape>, ObjectShape< {
6531
7430
  channel: LiteralShape<readonly ["error"]>;
@@ -6725,10 +7624,13 @@ export declare function renderCauseChain(error: unknown): string;
6725
7624
  *
6726
7625
  * @remarks
6727
7626
  * 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).
7627
+ * fenced code block tagged with `language`, the `content` verbatim inside. The fence is one
7628
+ * backtick longer than the longest backtick run in `content`, and never shorter than three (the
7629
+ * CommonMark rule), so file text holding a triple backtick cannot close the block early and
7630
+ * land outside it; content with no run of three or more keeps the three-backtick fence. Pure
7631
+ * string assembly, total — never throws. The one fenced-file format string for the whole
7632
+ * module — `AgentContext.build()` frames an active workspace's text files with it (each carries
7633
+ * its own `language` on its {@link import('@orkestrel/workspace').FileContent} text arm).
6732
7634
  *
6733
7635
  * @param path - The file path shown on the `File:` label line
6734
7636
  * @param language - The fenced-code language tag (for example `'typescript'`)
@@ -6745,10 +7647,46 @@ export declare function renderCauseChain(error: unknown): string;
6745
7647
  */
6746
7648
  export declare function renderFencedFile(path: string, language: string, content: string): string;
6747
7649
 
7650
+ /**
7651
+ * Renders a value as the JSON text `JSON.stringify` produces, or `undefined` when JSON cannot
7652
+ * carry the value.
7653
+ *
7654
+ * @remarks
7655
+ * Pure and total. Where `JSON.stringify` returns text, the result is the same text, property order
7656
+ * included; `canonicalStringify` from `@orkestrel/contract` sorts keys instead. A value
7657
+ * `JSON.stringify` throws on, such as a `BigInt` or a cycle, and a value it renders as nothing,
7658
+ * such as `undefined`, a function, or a symbol, return `undefined`.
7659
+ *
7660
+ * @param value - The value to render
7661
+ * @returns The JSON text, or `undefined` when the value has no JSON text
7662
+ * @example
7663
+ * ```ts
7664
+ * renderJSON({ order: 'LH-81660', late: true }) // '{"order":"LH-81660","late":true}'
7665
+ * renderJSON(10n) // undefined
7666
+ * renderJSON(undefined) // undefined
7667
+ * ```
7668
+ */
7669
+ export declare function renderJSON(value: unknown): string | undefined;
7670
+
7671
+ /**
7672
+ * Renders source framing and unresolved amendment scope at a source's first retained line.
7673
+ * @param line - The retained occurrence to annotate
7674
+ * @param context - Actual complete sources, first occurrences, local references, and accepted amendment edges
7675
+ * @returns Source framing and truthful inclusion notes, or empty text for an unrelated or repeated occurrence
7676
+ * @throws {LedgerError} Thrown when the context holds no reference for the annotated source or a fully included amender (code `'REQUEST'`)
7677
+ *
7678
+ * @example
7679
+ * ```ts
7680
+ * renderLedgerAnnotation({ text: 'Unchanged.', source: 'user-1', sentence: 0, topics: [], role: 'user' }, buildLedgerRenderContext([], [], [])) // ''
7681
+ * ```
7682
+ */
7683
+ export declare function renderLedgerAnnotation(line: LedgerLine, context: LedgerRenderContext): string;
7684
+
6748
7685
  /**
6749
7686
  * Renders one owner record under a `###` heading, which the briefing nests under its one `## Pinned` heading.
6750
7687
  *
6751
7688
  * @param record - An owner record or view with a `title` and `lines`
7689
+ * @param context - Rendering facts for optional accepted amendment annotations
6752
7690
  * @returns `### TITLE` followed by `- LINE` for each line
6753
7691
  *
6754
7692
  * @example
@@ -6756,7 +7694,7 @@ export declare function renderFencedFile(path: string, language: string, content
6756
7694
  * renderLedgerPinned({ title: 'Odile Marlow (account OM-30418)', lines: [] }) // '### Odile Marlow (account OM-30418)'
6757
7695
  * ```
6758
7696
  */
6759
- export declare function renderLedgerPinned(record: Pick<LedgerRecord, 'title' | 'lines'>): string;
7697
+ export declare function renderLedgerPinned(record: Pick<LedgerRecord, 'title' | 'lines'>, context?: LedgerRenderContext): string;
6760
7698
 
6761
7699
  /**
6762
7700
  * Renders one record as a heading and one list item per line.
@@ -6765,6 +7703,7 @@ export declare function renderLedgerPinned(record: Pick<LedgerRecord, 'title' |
6765
7703
  * The briefing joins rendered records with one blank line.
6766
7704
  *
6767
7705
  * @param record - A record or view with a `title` and `lines`
7706
+ * @param context - Rendering facts for optional accepted amendment annotations
6768
7707
  * @returns `## TITLE` followed by `- LINE` for each line
6769
7708
  *
6770
7709
  * @example
@@ -6773,7 +7712,26 @@ export declare function renderLedgerPinned(record: Pick<LedgerRecord, 'title' |
6773
7712
  * // '## Rules\n- Refunds need a manager.'
6774
7713
  * ```
6775
7714
  */
6776
- export declare function renderLedgerRecord(record: Pick<LedgerRecord, 'title' | 'lines'>): string;
7715
+ export declare function renderLedgerRecord(record: Pick<LedgerRecord, 'title' | 'lines'>, context?: LedgerRenderContext): string;
7716
+
7717
+ /**
7718
+ * Renders a lookup call as its tool name and its JSON arguments.
7719
+ *
7720
+ * @remarks
7721
+ * The arguments keep the text `JSON.stringify` writes, key order included. Arguments JSON cannot
7722
+ * carry, such as a `BigInt` or a cycle, render the bare tool name instead of throwing.
7723
+ *
7724
+ * @param name - The lookup tool name
7725
+ * @param args - The arguments the call carried
7726
+ * @returns The tool name, a space, and the JSON arguments; the bare tool name when JSON cannot carry them
7727
+ *
7728
+ * @example
7729
+ * ```ts
7730
+ * renderLookupCall('lookup_order', { id: 'BW-5512' }) // 'lookup_order {"id":"BW-5512"}'
7731
+ * renderLookupCall('lookup_order', { count: 1n }) // 'lookup_order'
7732
+ * ```
7733
+ */
7734
+ export declare function renderLookupCall(name: string, args: Readonly<Record<string, unknown>>): string;
6777
7735
 
6778
7736
  /**
6779
7737
  * Renders one context section — the resolved `open`, each item's rendering, and the resolved
@@ -6781,16 +7739,17 @@ export declare function renderLedgerRecord(record: Pick<LedgerRecord, 'title' |
6781
7739
  *
6782
7740
  * @remarks
6783
7741
  * 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.
7742
+ * scoped-out manager stays silent — its `open` / `close` appear only when the section has items.
7743
+ * An empty or absent slot is dropped before the join, so an `open: ''`, a `close: ''`, an unset
7744
+ * `close` (there is no built-in close), and an item rendered to `''` each add no blank line; a
7745
+ * section whose every slot is empty renders nothing (`undefined`).
6787
7746
  *
6788
7747
  * @typeParam T - The section item being rendered
6789
7748
  * @param open - The section's resolved leading text
6790
7749
  * @param members - The already scope-filtered items
6791
7750
  * @param render - Renders one item to its prompt text
6792
7751
  * @param close - The section's resolved trailing text, or `undefined` for none
6793
- * @returns The rendered section, or `undefined` when there are no items
7752
+ * @returns The rendered section, or `undefined` when there are no items or every slot is empty
6794
7753
  *
6795
7754
  * @example
6796
7755
  * ```ts
@@ -6805,7 +7764,7 @@ export declare function renderSection<T>(open: string, members: readonly T[], re
6805
7764
  * Renders the tail stub of a lookup result from an earlier request.
6806
7765
  *
6807
7766
  * @param name - The lookup tool name
6808
- * @param args - The arguments the call carried
7767
+ * @param args - The arguments the call carried; arguments JSON cannot carry leave the call as its bare tool name, per {@link renderLookupCall}
6809
7768
  * @param state - What became of the result: `failed`, `empty`, `shown` in the briefing, or `hidden` from it
6810
7769
  * @returns The stub text, which carries the call and its state
6811
7770
  *
@@ -6817,6 +7776,28 @@ export declare function renderSection<T>(open: string, members: readonly T[], re
6817
7776
  */
6818
7777
  export declare function renderStub(name: string, args: Readonly<Record<string, unknown>>, state: LedgerLookupState): string;
6819
7778
 
7779
+ /**
7780
+ * Renders a tool's output as the string content of its tool message.
7781
+ *
7782
+ * @remarks
7783
+ * A string passes through unchanged and `undefined` renders as `''`. Any other value renders as
7784
+ * its `JSON.stringify` text, byte for byte, when JSON can carry it. A value JSON cannot carry (a
7785
+ * `BigInt`, a cycle, a symbol, a throwing `toJSON`) renders as `String(value)`, and as `''` when
7786
+ * that conversion throws too, so a tool's output never rejects the run that dispatched it.
7787
+ *
7788
+ * @param value - The tool's output (`ToolSuccess.value`, which may be `undefined`)
7789
+ * @returns The message content
7790
+ *
7791
+ * @example
7792
+ * ```ts
7793
+ * renderToolValue('sunny') // 'sunny'
7794
+ * renderToolValue({ count: 2 }) // '{"count":2}'
7795
+ * renderToolValue(undefined) // ''
7796
+ * renderToolValue(10n) // '10'
7797
+ * ```
7798
+ */
7799
+ export declare function renderToolValue(value: unknown): string;
7800
+
6820
7801
  /**
6821
7802
  * Renders topic names as a comma-separated list in their supplied order.
6822
7803
  * @param topics - The configured desk topics
@@ -6828,13 +7809,74 @@ export declare function renderStub(name: string, args: Readonly<Record<string, u
6828
7809
  */
6829
7810
  export declare function renderTopicNames(topics: readonly LedgerTopic[]): string;
6830
7811
 
7812
+ /**
7813
+ * Holds the disjoint estimate components of one provider request, in heuristic estimate units.
7814
+ *
7815
+ * @remarks
7816
+ * `messages` keeps the request's message order, duplicate ids included. `tools` and `schema` are the
7817
+ * estimates of the rendered advertised tools and `options.schema`, each `0` when absent or empty and
7818
+ * the framing overhead when unrenderable. A request's estimate is the sum of every message component
7819
+ * plus `tools` and `schema`; no total is stored.
7820
+ */
7821
+ export declare interface RequestMeasure {
7822
+ readonly messages: readonly MessageMeasure[];
7823
+ readonly tools: number;
7824
+ readonly schema: number;
7825
+ }
7826
+
7827
+ /**
7828
+ * Projects a provider request onto the declared members the relay wire carries.
7829
+ *
7830
+ * @remarks
7831
+ * Each message keeps `id`, `role`, `content`, and any `calls`, `call`, `images`, and `thinking`;
7832
+ * each call keeps only `id`, `name`, and `arguments`, so a local execution context never reaches
7833
+ * the wire. Each tool keeps `name` and any `description` and `parameters`, and the options keep
7834
+ * any `think` and `schema`. An absent member stays absent. The projection shares every nested
7835
+ * value with the input rather than copying it, so the caller snapshots and validates the result;
7836
+ * a throwing getter propagates its failure.
7837
+ *
7838
+ * @param request - The domain conversation and call configuration
7839
+ * @returns The request with only the members the relay wire declares
7840
+ * @example
7841
+ * ```ts
7842
+ * requestToRelay({
7843
+ * messages: [{ id: 'turn-1', role: 'user', content: 'Track BW-5512' }],
7844
+ * options: { think: false },
7845
+ * })
7846
+ * // { messages: [{ id: 'turn-1', role: 'user', content: 'Track BW-5512' }], options: { think: false } }
7847
+ * ```
7848
+ */
7849
+ export declare function requestToRelay(request: ProviderRequest): ProviderRequest;
7850
+
7851
+ /**
7852
+ * Returns a run's budget or allowance after checking it implements `BudgetInterface`, or throws a
7853
+ * coded error before the run starts it.
7854
+ *
7855
+ * @remarks
7856
+ * The check is `isBudget` from `@orkestrel/budget`, a total guard. `undefined` passes, because an
7857
+ * absent bound is no bound. The error's `context` carries the option path, the limit
7858
+ * `'BudgetInterface'`, and a preview of the refused value.
7859
+ *
7860
+ * @param value - The effective `budget` or `allowance`, or `undefined`
7861
+ * @param key - The option the value came from, named in the error's path
7862
+ * @returns The value unchanged
7863
+ * @throws {ContractError} Thrown when `value` is defined and does not implement `BudgetInterface` (code `'placement'`)
7864
+ *
7865
+ * @example
7866
+ * ```ts
7867
+ * requireBudget(createTokenBudget({ max: 10 }), 'budget') // the budget
7868
+ * requireBudget({ consume() {} }, 'allowance') // throws ContractError ('placement')
7869
+ * ```
7870
+ */
7871
+ export declare function requireBudget(value: BudgetInterface<TokenUsage> | undefined, key: 'budget' | 'allowance'): BudgetInterface<TokenUsage> | undefined;
7872
+
6831
7873
  /**
6832
7874
  * Returns a registered value or throws when its name is absent.
6833
7875
  * @param pool - The named values
6834
7876
  * @param category - The registry category named in an error
6835
7877
  * @param name - The requested key
6836
7878
  * @returns The registered value
6837
- * @throws {AgentError} Thrown when the name is absent, with code REGISTRY.
7879
+ * @throws {AgentError} Thrown when the name is absent, with code REGISTRY and a `context` naming the `category` and the `name`.
6838
7880
  * @example
6839
7881
  * ```ts
6840
7882
  * requireEntry(new Map([['main', provider]]), 'provider', 'main') // provider
@@ -6861,6 +7903,71 @@ export declare function requireEntry<T>(pool: ReadonlyMap<string, T>, category:
6861
7903
  */
6862
7904
  export declare function requireJudgment(value: unknown): Judgment;
6863
7905
 
7906
+ /**
7907
+ * Returns a retained-tail size after checking it is a nonnegative safe integer, or throws a coded
7908
+ * error.
7909
+ *
7910
+ * @remarks
7911
+ * The check is {@link import('../validators.js').isNonNegativeSafeInteger}, so `0` and `-0`
7912
+ * pass while `NaN`, a fraction, a negative, and either infinity are refused. The error's `context`
7913
+ * carries the refused value as `keep`.
7914
+ *
7915
+ * @param keep - The retained-tail size to check
7916
+ * @returns The size unchanged
7917
+ * @throws ConversationError Thrown when `keep` is not a nonnegative safe integer (code `'KEEP'`)
7918
+ *
7919
+ * @example
7920
+ * ```ts
7921
+ * requireKeep(2) // 2
7922
+ * requireKeep(1.5) // throws ConversationError ('KEEP')
7923
+ * ```
7924
+ */
7925
+ export declare function requireKeep(keep: number): number;
7926
+
7927
+ /**
7928
+ * Returns a tool-iteration cap after checking it is a nonnegative safe integer, or throws a coded
7929
+ * error.
7930
+ *
7931
+ * @remarks
7932
+ * The check is {@link import('../validators.js').isNonNegativeSafeInteger}, so `0` and `-0`
7933
+ * pass while `NaN`, a fraction, a negative, and either infinity are refused. The error's `context`
7934
+ * carries the refused value as `limit`.
7935
+ *
7936
+ * @param limit - The tool-iteration cap to check
7937
+ * @returns The cap unchanged
7938
+ * @throws {AgentError} Thrown when `limit` is not a nonnegative safe integer (code `'LIMIT'`)
7939
+ *
7940
+ * @example
7941
+ * ```ts
7942
+ * requireLimit(3) // 3
7943
+ * requireLimit(1.5) // throws AgentError ('LIMIT')
7944
+ * ```
7945
+ */
7946
+ export declare function requireLimit(limit: number): number;
7947
+
7948
+ /**
7949
+ * Returns the rolling switch after checking it does not combine with a `sections` cap, or throws a
7950
+ * coded error.
7951
+ *
7952
+ * @remarks
7953
+ * A rolling conversation keeps one section, so a cap has nothing to bound. The cap checked is the
7954
+ * effective one: inherited from a manager, configured on the conversation, or supplied to one
7955
+ * `compact()` call.
7956
+ *
7957
+ * @param rolling - Whether folds roll every prior section into one
7958
+ * @param cap - The effective `sections` cap, or `undefined` for none
7959
+ * @returns The rolling switch unchanged
7960
+ * @throws ConversationError Thrown when `rolling` is true and a cap is present (code `'SECTIONS'`)
7961
+ *
7962
+ * @example
7963
+ * ```ts
7964
+ * requireRolling(true, undefined) // true
7965
+ * requireRolling(false, 2) // false
7966
+ * requireRolling(true, 2) // throws ConversationError ('SECTIONS')
7967
+ * ```
7968
+ */
7969
+ export declare function requireRolling(rolling: boolean, cap: number | undefined): boolean;
7970
+
6864
7971
  /**
6865
7972
  * Returns a `sections` cap after checking its lower bound, or throws a coded error.
6866
7973
  *
@@ -6896,7 +8003,7 @@ export declare function requireSectionsCap(cap: number | undefined): number | un
6896
8003
  export declare function resolvePredict(predict: number | undefined, capacity: number): number;
6897
8004
 
6898
8005
  /**
6899
- * Sanitizes one reported token count into a safe non-negative integer — a non-finite or
8006
+ * Sanitizes one reported token count into a non-negative integer — a non-finite or
6900
8007
  * non-positive value becomes `0`, and a positive fractional value floors down.
6901
8008
  *
6902
8009
  * @param value - The token count to sanitize
@@ -6911,7 +8018,7 @@ export declare function resolvePredict(predict: number | undefined, capacity: nu
6911
8018
  export declare function sanitizeToken(value: number): number;
6912
8019
 
6913
8020
  /**
6914
- * Sanitizes a {@link TokenUsage} into safe, non-negative integers — the guard an agent's
8021
+ * Sanitizes a {@link TokenUsage} into non-negative integers — the guard an agent's
6915
8022
  * abort-usage path applies to a provider's partial usage before it is charged against a
6916
8023
  * budget or folded into the run total.
6917
8024
  *
@@ -6921,7 +8028,7 @@ export declare function sanitizeToken(value: number): number;
6921
8028
  * non-negative integer part. No upper cap is applied. Total — never throws.
6922
8029
  *
6923
8030
  * @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
8031
+ * @returns A new {@link TokenUsage} with every field a non-negative integer
6925
8032
  *
6926
8033
  * @example
6927
8034
  * ```ts
@@ -7062,20 +8169,20 @@ export declare interface ScopeInterface extends ScopeFilter {
7062
8169
 
7063
8170
  /**
7064
8171
  * 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,
8172
+ * minted `id`, in insertion order, where `add` always mints and stores rather than overwriting,
7066
8173
  * and an observable `emitter` reports each change.
7067
8174
  *
7068
8175
  * @remarks
7069
8176
  * - **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
8177
+ * `add` mints a {@link Scope} from a {@link ScopeInput} (an `id` plus the three
7071
8178
  * allow-lists), stores it, and returns it. `count` is the map size, `scope(id)` looks
7072
8179
  * one up, and `scopes()` lists them in insertion order. (Unlike the name-keyed
7073
8180
  * 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.)
8181
+ * `name`; `add` therefore never overwrites.)
7075
8182
  * - **Removal.** `remove` drops one by id, or a batch — `true` only when every supplied id
7076
8183
  * was removed; `clear` empties the registry.
7077
8184
  * - **Observable.** The owned {@link emitter} ({@link ScopeManagerEventMap}) carries
7078
- * `create` (the created scope) / `remove` (the id) / `clear`. Every event is emitted
8185
+ * `add` (the added scope) / `remove` (the id) / `clear`. Every event is emitted
7079
8186
  * directly, strictly after the map mutation completes; the emitter isolates a listener
7080
8187
  * throw and routes it to its `error` handler (the `error` option), so a buggy observer can
7081
8188
  * never corrupt a mutation.
@@ -7083,7 +8190,7 @@ export declare interface ScopeInterface extends ScopeFilter {
7083
8190
  * @example
7084
8191
  * ```ts
7085
8192
  * const manager = new ScopeManager()
7086
- * const reader = manager.create({ name: 'reader', tools: ['search', 'read'] })
8193
+ * const reader = manager.add({ name: 'reader', tools: ['search', 'read'] })
7087
8194
  * manager.scope(reader.id) // the same scope
7088
8195
  * manager.count // 1
7089
8196
  * ```
@@ -7093,7 +8200,7 @@ export declare class ScopeManager implements ScopeManagerInterface {
7093
8200
  constructor(options?: ScopeManagerOptions);
7094
8201
  get emitter(): EmitterInterface<ScopeManagerEventMap>;
7095
8202
  get count(): number;
7096
- create(input: ScopeInput): ScopeInterface;
8203
+ add(input: ScopeInput): ScopeInterface;
7097
8204
  scope(id: string): ScopeInterface | undefined;
7098
8205
  scopes(): readonly ScopeInterface[];
7099
8206
  remove(id: string): boolean;
@@ -7103,17 +8210,17 @@ export declare class ScopeManager implements ScopeManagerInterface {
7103
8210
 
7104
8211
  /**
7105
8212
  * 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`.
8213
+ * {@link InstructionManagerEventMap}, but keyed by the minted `id`, so `add` never reports a
8214
+ * replaced scope.
7108
8215
  *
7109
8216
  * @remarks
7110
- * `create` carries the created {@link ScopeInterface}; `remove` carries the removed
8217
+ * `add` carries the added {@link ScopeInterface}; `remove` carries the removed
7111
8218
  * scope's `id`; `clear` is a pure signal. The emitter isolates a listener throw and routes
7112
8219
  * it to its `error` handler. A `type` alias so it satisfies `EventMap` structurally.
7113
8220
  */
7114
8221
  export declare type ScopeManagerEventMap = {
7115
- /** Reports a scope created — the created scope. */
7116
- readonly create: readonly [scope: ScopeInterface];
8222
+ /** Reports a scope added — the added scope. */
8223
+ readonly add: readonly [scope: ScopeInterface];
7117
8224
  /** Reports a scope removed — its `id`. */
7118
8225
  readonly remove: readonly [id: string];
7119
8226
  /** Reports every scope removed. */
@@ -7121,16 +8228,16 @@ export declare type ScopeManagerEventMap = {
7121
8228
  };
7122
8229
 
7123
8230
  /**
7124
- * Registers reusable {@link ScopeInterface} instances keyed by their minted `id` — `create`
8231
+ * Registers reusable {@link ScopeInterface} instances keyed by their minted `id` — `add`
7125
8232
  * mints + stores one (never overwrites), `scopes()` lists them in insertion order.
7126
8233
  *
7127
8234
  * @remarks
7128
- * - **Registry.** `create(input)` mints a scope (an `id` + the per-category allow-lists) and
8235
+ * - **Registry.** `add(input)` mints a scope (an `id` + the per-category allow-lists) and
7129
8236
  * stores it; `count` is how many are stored. `scope(id)` looks one up; `scopes()` lists
7130
8237
  * them in insertion order. (Keyed by minted `id`, not `name`, so two scopes may share a
7131
- * `name` and `create` always adds.)
8238
+ * `name` and `add` never overwrites.)
7132
8239
  * - **Observable.** The owned `emitter` ({@link ScopeManagerEventMap}) carries
7133
- * `create` / `remove` / `clear` for fire-and-forget observers; the emitter isolates a
8240
+ * `add` / `remove` / `clear` for fire-and-forget observers; the emitter isolates a
7134
8241
  * listener throw and routes it to its `error` handler (the `error` option).
7135
8242
  */
7136
8243
  export declare interface ScopeManagerInterface {
@@ -7139,13 +8246,30 @@ export declare interface ScopeManagerInterface {
7139
8246
  /**
7140
8247
  * Mints a scope from a {@link ScopeInput} (a `name` plus the per-category allow-lists) and
7141
8248
  * stores it — always adds, never overwrites.
8249
+ *
8250
+ * @param input - The scope's `name`, allow-lists, and optional `select` handler
8251
+ * @returns The stored scope, carrying its minted `id`
8252
+ */
8253
+ add(input: ScopeInput): ScopeInterface;
8254
+ /**
8255
+ * Looks up one scope by id (`undefined` when absent).
8256
+ *
8257
+ * @param id - The minted scope id to look up
8258
+ * @returns The stored scope, or `undefined` when absent
7142
8259
  */
7143
- create(input: ScopeInput): ScopeInterface;
7144
- /** Looks up one scope by id (`undefined` when absent). */
7145
8260
  scope(id: string): ScopeInterface | undefined;
7146
- /** Lists every scope, in insertion order. */
8261
+ /**
8262
+ * Lists every scope, in insertion order.
8263
+ *
8264
+ * @returns Every stored scope, in insertion order
8265
+ */
7147
8266
  scopes(): readonly ScopeInterface[];
7148
- /** Removes one scope by id, or a batch. True if every supplied id was removed; false otherwise. */
8267
+ /**
8268
+ * Removes one scope by id, or a batch. True if every supplied id was removed; false otherwise.
8269
+ *
8270
+ * @param id - One scope id, or a batch
8271
+ * @returns True if every supplied id was present and removed; false otherwise
8272
+ */
7149
8273
  remove(id: string): boolean;
7150
8274
  remove(ids: readonly string[]): boolean;
7151
8275
  /** Removes every scope. */
@@ -7253,12 +8377,14 @@ export declare interface Selection {
7253
8377
  export declare type SelectionHandler = (conversation: ConversationInterface, request: Message, signal: AbortSignal) => Promise<Selection>;
7254
8378
 
7255
8379
  /**
7256
- * Selects a request's view of the projected records.
8380
+ * Selects a request's records, collapsing owners with equal ordered member ids under titles joined with `; `.
7257
8381
  *
7258
8382
  * @remarks
7259
8383
  * The view holds the owner records the request names in the order it names them, then the rules
7260
8384
  * record with the lines whose topics meet the request's first, each group in position order. The
7261
- * returned records and lines are copies.
8385
+ * returned records and lines are copies. Owner records with the same member ids in the same order
8386
+ * collapse into the first requested record, keeping its key, members, and lines and joining their
8387
+ * titles with `; ` before trimming.
7262
8388
  *
7263
8389
  * @param projection - The output of {@link buildRecords}
7264
8390
  * @param request - The owners and the desk topics the request names
@@ -7299,6 +8425,23 @@ export declare function selectRecords(projection: LedgerProjection, request: Led
7299
8425
  */
7300
8426
  export declare function settleAgentJob(agent: AgentInterface, partial: boolean): Promise<AgentResult>;
7301
8427
 
8428
+ /**
8429
+ * Splits a tool result into its lines at each line feed, with or without a carriage return before it.
8430
+ *
8431
+ * @remarks
8432
+ * The recall listing keys each recalled line by its first line, and the answer note reads a result
8433
+ * line by line, so both read lines through this one split.
8434
+ *
8435
+ * @param text - The tool result text
8436
+ * @returns The lines in order, an empty line included; one empty line for an empty text
8437
+ *
8438
+ * @example
8439
+ * ```ts
8440
+ * splitResultLines('Account BW-20931.\r\nRefund is $148.50.') // ['Account BW-20931.', 'Refund is $148.50.']
8441
+ * ```
8442
+ */
8443
+ export declare function splitResultLines(text: string): readonly string[];
8444
+
7302
8445
  /**
7303
8446
  * Splits a message into its sentences.
7304
8447
  *
@@ -7591,9 +8734,9 @@ export declare const THINK_OPEN = "<think>";
7591
8734
  export declare type ThinkingReplay = 'none' | 'turn' | 'all';
7592
8735
 
7593
8736
  /**
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
8737
+ * Separates `<think>…</think>` spans from raw content deltas with per-stream state: text inside a
8738
+ * span accumulates on `thinking`, and text outside it returns as clean content, so a provider
8739
+ * yields the answer alone and surfaces the reasoning as
7597
8740
  * {@link import('./types.js').ProviderResult.thinking}. A tag split across deltas is held until
7598
8741
  * disambiguated, `flush()` settles the stream end, and one splitter serves one stream.
7599
8742
  *
@@ -7645,7 +8788,7 @@ export declare class ThinkSplitter implements ThinkSplitterInterface {
7645
8788
  * - **Stateful across deltas.** A tag may arrive split across wire chunks (`'<thi'`
7646
8789
  * ending one delta, `'nk>'` opening the next) — `split` holds any ambiguous tail
7647
8790
  * 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.
8791
+ * neither returned as content nor routed to thinking.
7649
8792
  * - **`split(delta)`** feeds one raw content delta and returns the clean content to
7650
8793
  * surface for it (possibly `''` — for example mid-think). Text inside a
7651
8794
  * `<think>…</think>` span accumulates on `thinking`; multiple spans accumulate in
@@ -7676,8 +8819,8 @@ export declare interface ThinkSplitterInterface {
7676
8819
  readonly thinking: string;
7677
8820
  /**
7678
8821
  * 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.
8822
+ * `''`) — a tag split across deltas is held until disambiguated, so it is neither returned
8823
+ * as content nor routed to thinking.
7681
8824
  */
7682
8825
  split(delta: string): string;
7683
8826
  /**
@@ -7687,6 +8830,31 @@ export declare interface ThinkSplitterInterface {
7687
8830
  flush(): string;
7688
8831
  }
7689
8832
 
8833
+ /**
8834
+ * Projects an unknown value onto a fresh, exact `TokenUsage` — capturing `prompt`, `completion`, and
8835
+ * `total` once each through a total boundary, preserving finite negative and fractional counts,
8836
+ * dropping extras, and resolving `undefined` for a non-object, a missing or non-finite count, a
8837
+ * throwing getter, or a hostile or revoked proxy.
8838
+ *
8839
+ * @remarks
8840
+ * The capture runs inside Contract's sanctioned exception boundary, so conforming accessors and
8841
+ * inherited properties are read while a throwing getter or a revoked proxy returns `undefined`.
8842
+ * Counts are not normalized. A caller that treats an absent usage as valid checks for `undefined`
8843
+ * before it projects, because this function refuses `undefined` like any other non-object.
8844
+ *
8845
+ * @param value - The unknown value to project
8846
+ * @returns A fresh `{ prompt, completion, total }`, or `undefined` when the value is not a finite usage triple
8847
+ *
8848
+ * @example
8849
+ * ```ts
8850
+ * import { tokenUsageToJSON } from '@orkestrel/agent'
8851
+ *
8852
+ * tokenUsageToJSON({ prompt: 2, completion: -1, total: 1.5, cached: 4 }) // { prompt: 2, completion: -1, total: 1.5 }
8853
+ * tokenUsageToJSON({ prompt: 2, completion: Infinity, total: 3 }) // undefined
8854
+ * ```
8855
+ */
8856
+ export declare function tokenUsageToJSON(value: unknown): TokenUsage | undefined;
8857
+
7690
8858
  /**
7691
8859
  * Describes a tool call's JSON wire projection.
7692
8860
  *
@@ -7717,15 +8885,16 @@ export declare const UPSTREAM_RELAY_STATUS = 502;
7717
8885
  /**
7718
8886
  * Names the section header the `build()` method of {@link import('./AgentContext.js').AgentContext}
7719
8887
  * 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.
8888
+ * workspace block in the system message.
7722
8889
  *
7723
8890
  * @remarks
7724
8891
  * `build()` owns the workspace render (a `Workspace` / `WorkspaceManager` stays file-focused — no
7725
8892
  * `open` / `format` getters), so this header lives here as the contexts module's one
7726
8893
  * 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.
8894
+ * beneath it as a fenced `` File: <path>\n```<language>\n<text>\n``` `` block through
8895
+ * {@link import('./helpers.js').renderFencedFile}, and the block is placed immediately after the
8896
+ * instructions section in the system block. The workspace's image files render no section: their
8897
+ * `base64` payload attaches to the last user message.
7729
8898
  */
7730
8899
  export declare const WORKSPACE_SECTION_HEADER = "## Workspace";
7731
8900