@axlsdk/axl 0.17.2 → 0.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -863,6 +863,18 @@ type ChatMessage = {
863
863
  tool_call_id?: string;
864
864
  /** Provider-specific opaque metadata that must round-trip through conversation history. */
865
865
  providerMetadata?: Record<string, unknown>;
866
+ /** Name of the agent that produced this message. Populated on `assistant`
867
+ * messages committed via `ctx.ask()`; absent on `user` messages and on
868
+ * assistant messages pushed by external callers (e.g., the
869
+ * `Session.send` fallback when no agent context is available).
870
+ *
871
+ * This is observability metadata only — it is NOT sent on the wire to
872
+ * any provider. Provider adapters cherry-pick the fields they need
873
+ * (`role`, `content`, `name`, `tool_calls`, `tool_call_id`) and ignore
874
+ * the rest. Future per-agent history filtering may consume this field
875
+ * (the option name and shape are not yet stable); today it is purely
876
+ * informational. */
877
+ agent?: string;
866
878
  };
867
879
  type ToolCallMessage = {
868
880
  id: string;
@@ -949,6 +961,290 @@ type Agent = {
949
961
  */
950
962
  declare function agent(config: AgentConfig): Agent;
951
963
 
964
+ /** Options shared between `AxlEventBus` (and therefore `AxlStream` and
965
+ * `ctx.events`). Controls the overflow safety net on the iterator queue. */
966
+ interface EventStreamOptions {
967
+ /** Soft cap on the number of events held in the iterator queue while
968
+ * waiting for a consumer. Terminal events (`done`, `error`,
969
+ * `workflow_end`) are exempt and always pass through. Default 10_000.
970
+ *
971
+ * Set to `Infinity` to disable the cap (matches pre-0.x behavior). */
972
+ maxQueued?: number;
973
+ /** Policy when an event arrives and the queue is at `maxQueued`.
974
+ *
975
+ * - `'drop-oldest-non-terminal'` (default): scan the queue head-to-tail,
976
+ * splice out the oldest non-terminal event, push the new one. The first
977
+ * drop per bus instance fires a one-shot `console.warn` so saturating
978
+ * consumers see the signal without log spam.
979
+ * - `'throw'`: throw an `EventStreamOverflowError` at the producer call
980
+ * site. **This will fail the active workflow** — `_push` is called
981
+ * from `WorkflowContext.emitEvent`, which runs inside `ctx.ask()` /
982
+ * `runtime.execute()`. The throw unwinds the agent loop and the
983
+ * `runtime.execute()` (or `runtime.stream()`) promise rejects with
984
+ * the typed error. Useful in tests or strict environments where
985
+ * silent drop would mask a problem; not the right default for
986
+ * production where a slow consumer should degrade gracefully rather
987
+ * than abort the workflow. Catch with `instanceof
988
+ * EventStreamOverflowError` to distinguish from other workflow
989
+ * errors.
990
+ *
991
+ * `'block'` is intentionally absent: `_push` is synchronous and there's no
992
+ * producer to suspend. To bound queue growth via backpressure, the
993
+ * producer side has to model it explicitly (out of scope for v1). */
994
+ onOverflow?: 'drop-oldest-non-terminal' | 'throw';
995
+ }
996
+ /**
997
+ * The shape yielded by `AxlEventBus.partialObjects` (and `AxlStream.partialObjects`).
998
+ * Coalesced latest-per-`askId` snapshot of a `partial_object` event.
999
+ *
1000
+ * `attempt` is the 1-indexed attempt number from the underlying
1001
+ * `partial_object` event — pinned in the public type so consumers can
1002
+ * conditionally render a "regenerating" indicator when it jumps from 1
1003
+ * to 2. Failed attempts are dropped from the view via the
1004
+ * `pipeline(failed)` listener before they leak across the retry
1005
+ * boundary.
1006
+ */
1007
+ type CoalescedPartialObject = {
1008
+ askId: string;
1009
+ agent?: string;
1010
+ object: unknown;
1011
+ attempt: number;
1012
+ };
1013
+ /**
1014
+ * Thrown when an `AxlEventBus` queue exceeds `maxQueued` and `onOverflow`
1015
+ * is set to `'throw'`. Surfaced as a typed error so the runtime's emit
1016
+ * pipeline can distinguish a legitimate overflow signal (which must
1017
+ * propagate to fail the workflow) from a buggy trace-listener exception
1018
+ * (which gets swallowed).
1019
+ *
1020
+ * Consumers using `instanceof` to handle overflow specifically:
1021
+ * ```typescript
1022
+ * try {
1023
+ * await runtime.execute('wf', input, { events: { onOverflow: 'throw' } });
1024
+ * } catch (err) {
1025
+ * if (err instanceof EventStreamOverflowError) {
1026
+ * // overflow — back off, retry, or surface to user
1027
+ * }
1028
+ * }
1029
+ * ```
1030
+ */
1031
+ declare class EventStreamOverflowError extends Error {
1032
+ readonly maxQueued: number;
1033
+ readonly eventType: string;
1034
+ constructor(maxQueued: number, eventType: string);
1035
+ }
1036
+ /**
1037
+ * Iterable + EventEmitter event bus carrying `AxlEvent`.
1038
+ *
1039
+ * Public observation surface for `ctx.events` and the underlying machinery
1040
+ * for `AxlStream`. Provides:
1041
+ *
1042
+ * - `for await (const e of bus)` — sequential iteration over every pushed event
1043
+ * - `.on('agent_call_end', fn)` / `.off(...)` — EventEmitter style, gated on
1044
+ * `AXL_EVENT_TYPES` so unknown event names cannot be subscribed
1045
+ * - Curated views: `.text` (root-only token chunks), `.lifecycle` (structural
1046
+ * events only), `.textByAsk` (per-ask token chunks)
1047
+ *
1048
+ * Multiple async iterators on the same bus race for the same FIFO queue —
1049
+ * iterating both `bus` and `bus.text` (or `bus.lifecycle` / `bus.textByAsk`)
1050
+ * concurrently will see each event at most once because the views pull from
1051
+ * the same underlying iterator. For two independent observers, attach
1052
+ * `.on(type, handler)` listeners instead — those each receive every event.
1053
+ *
1054
+ * Multiple `.on()` listeners on the same event type each receive every
1055
+ * matching event. Subscriptions to non-`AxlEventType` names are silently
1056
+ * dropped (the bus emits no other event names).
1057
+ */
1058
+ declare class AxlEventBus implements AsyncIterable<AxlEvent> {
1059
+ protected readonly bus: EventEmitter<any>;
1060
+ protected eventQueue: AxlEvent[];
1061
+ protected waiters: Array<(value: IteratorResult<AxlEvent>) => void>;
1062
+ protected finished: boolean;
1063
+ private readonly maxQueued;
1064
+ private readonly onOverflow;
1065
+ private overflowWarned;
1066
+ /** Callbacks fired exactly once when `_finish()` is called. Used by
1067
+ * curated views (e.g., `partialObjects`) that are listener-based and
1068
+ * need a termination signal independent of any synthetic `done` event. */
1069
+ private finishCallbacks;
1070
+ /**
1071
+ * Latest coalesced partial-object snapshot per `askId`, maintained on
1072
+ * every `_push` regardless of consumer state. Seeds the
1073
+ * `partialObjects` view's `pending` map at subscription time so a
1074
+ * late subscriber recovers the most recent state per ask even if
1075
+ * earlier `partial_object` events were already delivered to a prior
1076
+ * iterator's waiter (and thus removed from `eventQueue`).
1077
+ *
1078
+ * The pre-fix snapshot walked `eventQueue` directly — that misses
1079
+ * events already drained by past consumers. The map walks past
1080
+ * `_push` calls instead, which is the actual definition of "what's
1081
+ * the latest partial per ask". `pipeline(status: 'failed')` clears
1082
+ * the entry for the affected askId so a discarded attempt can't
1083
+ * surface to a future subscriber.
1084
+ *
1085
+ * Memory bound: O(active asks holding a pending partial). Cleared
1086
+ * on `pipeline(failed)` (per ask) and otherwise grows with the
1087
+ * number of distinct askIds that emitted at least one partial.
1088
+ */
1089
+ private latestPartialByAsk;
1090
+ /**
1091
+ * Hook fired when an iterator obtained from `[Symbol.asyncIterator]`
1092
+ * is `await using`-disposed. Set via the second constructor argument.
1093
+ * Used by `AxlStream` to call `Readable.destroy()` on its owning
1094
+ * stream so disposing the iterator cascades to the Readable contract.
1095
+ * Set once at construction; never reassigned.
1096
+ *
1097
+ * Replaces the previous `AxlStreamEventBus extends AxlEventBus`
1098
+ * subclass — composition over inheritance for what is essentially a
1099
+ * single function-pointer hook.
1100
+ *
1101
+ * @internal
1102
+ */
1103
+ protected readonly onIteratorDispose?: () => void;
1104
+ /**
1105
+ * @param options Public stream options (queue cap, overflow policy).
1106
+ * @param onIteratorDispose @internal Invoked from `_disposeIterator`
1107
+ * when an iterator obtained from this bus is `await using`-disposed.
1108
+ * Used by `AxlStream` to forward to `Readable.destroy()`.
1109
+ */
1110
+ constructor(options?: EventStreamOptions, onIteratorDispose?: () => void);
1111
+ private unknownEventWarned;
1112
+ /** Subscribe to an `AxlEvent` variant by type. Names outside
1113
+ * `AXL_EVENT_TYPES` are dropped (the bus only emits those names) but
1114
+ * the first such call per bus instance fires a one-shot
1115
+ * `console.warn` so a typo (`'agent_call_ended'`) doesn't silently
1116
+ * vanish. Subclasses (e.g., `AxlStream`) pre-filter via
1117
+ * `knowsEventName` so their own routing for non-`AxlEvent` names
1118
+ * like `'close'` doesn't trip the warn. */
1119
+ on<T extends AxlEventType>(event: T, handler: (e: AxlEventOf<T>) => void): this;
1120
+ on(event: string, handler: (event: any) => void): this;
1121
+ off<T extends AxlEventType>(event: T, handler: (e: AxlEventOf<T>) => void): this;
1122
+ off(event: string, handler: (event: any) => void): this;
1123
+ /** One-shot warn on subscribe/unsubscribe for an event name outside
1124
+ * `AXL_EVENT_TYPES`. Pre-fix this branch silently dropped, which
1125
+ * meant a typo like `'agent_call_ended'` produced no signal. */
1126
+ private warnUnknownEventName;
1127
+ [Symbol.asyncIterator](): {
1128
+ next: () => Promise<IteratorResult<AxlEvent>>;
1129
+ return: () => Promise<IteratorResult<AxlEvent>>;
1130
+ [Symbol.asyncIterator](): /*elided*/ any;
1131
+ [Symbol.asyncDispose]: () => Promise<void>;
1132
+ };
1133
+ /** Hook for iterator disposal. Calls the constructor-supplied
1134
+ * `onIteratorDispose` callback if any. The default behavior is a
1135
+ * no-op when no callback was passed — a plain bus has no
1136
+ * Node-stream resource to release. */
1137
+ protected _disposeIterator(): Promise<void>;
1138
+ /** Iterator over root-only `token` chunks. Skips nested-ask tokens
1139
+ * (consumers wanting nested can iterate the full bus and filter on
1140
+ * `event.depth >= 1`). */
1141
+ get text(): AsyncIterable<string>;
1142
+ /** Iterator over `{ askId, agent?, text }` for every token across root and
1143
+ * nested asks. Useful for split-pane UIs that render each sub-agent's
1144
+ * output in its own lane. `agent` is undefined when the token was emitted
1145
+ * outside any ask (rare — synthesized test fixtures). */
1146
+ get textByAsk(): AsyncIterable<{
1147
+ askId: string;
1148
+ agent?: string;
1149
+ text: string;
1150
+ }>;
1151
+ /**
1152
+ * Coalescing iterator over `partial_object` events — yields the latest
1153
+ * payload per `askId`. When a newer `partial_object` arrives for an
1154
+ * `askId` while the consumer is busy, the older value is silently
1155
+ * superseded; the consumer sees only the most recent state per ask on
1156
+ * its next `.next()` await.
1157
+ *
1158
+ * Memory bound: O(active asks holding a pending partial), not O(events).
1159
+ * This is what UIs streaming structured output actually want — rendering
1160
+ * every intermediate snapshot just to overwrite it ms later is wasted
1161
+ * work, and unbounded queueing of intermediate JSON snapshots is the
1162
+ * memory-pressure scenario this view is designed to avoid. In practice
1163
+ * "active asks" is small (1-10 even for fan-out workflows); a workflow
1164
+ * fanning out to 10k concurrent asks each producing partials would
1165
+ * accumulate ≈10k pending entries (~1 MB), which is the realistic
1166
+ * ceiling for this view.
1167
+ *
1168
+ * Schema-retry awareness: each yielded value carries the 1-indexed
1169
+ * `attempt` field from the underlying `partial_object` event. When a
1170
+ * schema check fails and the agent retries, the runtime emits
1171
+ * `pipeline(status: 'failed')`; this view drops any undrained pending
1172
+ * partial for the affected ask so the consumer never sees stale
1173
+ * attempt-N snapshots after attempt-N+1 has begun. Mirrors the
1174
+ * per-attempt buffer reset that `AxlStream.fullText` applies to
1175
+ * tokens. (Validate-stage and guardrail retries trigger the same
1176
+ * reset.)
1177
+ *
1178
+ * Malformed `partial_object` events (those missing `data.object`) are
1179
+ * silently filtered — the variant shape requires the field, so an
1180
+ * undefined value is a producer bug rather than a meaningful state.
1181
+ *
1182
+ * Implementation note: this is a listener-based view (subscribes to the
1183
+ * bus's `partial_object` channel via `.on()`), not an iterator-based
1184
+ * filter like `.text` / `.lifecycle` / `.textByAsk`. As a result it does
1185
+ * NOT race with the main async iterator — running `for await (const e of bus)`
1186
+ * concurrently with `for await (const p of bus.partialObjects)` works
1187
+ * cleanly (each `partial_object` event fires the listener AND queues for
1188
+ * iterators).
1189
+ *
1190
+ * Termination: yields any pending coalesced values first, then `done: true`
1191
+ * once the bus is finished (via `_finish()`).
1192
+ */
1193
+ get partialObjects(): AsyncIterable<CoalescedPartialObject>;
1194
+ /** Iterator over structural lifecycle events only — skips per-token chatter
1195
+ * and progressive `partial_object` emissions. Useful for waterfall UIs
1196
+ * and any consumer that wants the "what happened" timeline. */
1197
+ get lifecycle(): AsyncIterable<AxlEvent>;
1198
+ /**
1199
+ * Push an event onto the bus. Emits on the typed channel (for `.on()`
1200
+ * subscribers) and queues for async iterators. No-op once `_finish()` has
1201
+ * been called.
1202
+ *
1203
+ * Subclasses (AxlStream) override to add token accounting and Readable
1204
+ * fan-out before delegating to this implementation.
1205
+ *
1206
+ * @internal — called by the runtime / WorkflowContext / AxlStream.
1207
+ */
1208
+ _push(event: AxlEvent): void;
1209
+ /** Apply the overflow policy when the queue is at `maxQueued` and a
1210
+ * non-terminal event arrives. Terminal events bypass this path entirely
1211
+ * (checked by the caller). */
1212
+ private handleOverflow;
1213
+ /**
1214
+ * Mark the bus as finished. Wakes any pending iterators with `done: true`
1215
+ * and drops further `_push` calls. Idempotent.
1216
+ *
1217
+ * Does not synthesize a terminal `done` AxlEvent — callers (AxlStream) that
1218
+ * want one push it via `_push` before calling `_finish`.
1219
+ *
1220
+ * Late iterators that connect after `_finish()` still drain any events
1221
+ * remaining in `eventQueue` (the `next()` short-circuit in
1222
+ * `[Symbol.asyncIterator]` checks the queue before checking `finished`),
1223
+ * then receive `done: true`.
1224
+ *
1225
+ * @internal
1226
+ */
1227
+ _finish(): void;
1228
+ /** Register a one-shot callback fired when `_finish()` is called.
1229
+ * Returns an unsubscribe function. If the bus is already finished,
1230
+ * the callback is invoked synchronously and the returned unsubscribe
1231
+ * is a no-op.
1232
+ *
1233
+ * Used by listener-based curated views (e.g., `partialObjects`) that
1234
+ * need a termination signal independent of any synthesized `done`
1235
+ * event. Iterator consumers don't need this — `[Symbol.asyncIterator]`
1236
+ * resolves with `done: true` automatically.
1237
+ *
1238
+ * @internal */
1239
+ _onFinish(cb: () => void): () => void;
1240
+ /** Whether `event` is one of the `AxlEventType` names this bus emits.
1241
+ * Subclasses (e.g., `AxlStream`) consult this to route AxlEvent names to
1242
+ * the bus and other names (`'close'`, `'data'`, ...) to a wider emitter. */
1243
+ knowsEventName(event: string): boolean;
1244
+ /** Whether `_finish()` has been called. */
1245
+ get isFinished(): boolean;
1246
+ }
1247
+
952
1248
  /** A pending human decision awaiting resolution. */
953
1249
  type PendingDecision = {
954
1250
  executionId: string;
@@ -1522,6 +1818,22 @@ type WorkflowContextInit = {
1522
1818
  }, meta: CallbackMeta) => void;
1523
1819
  /** Callback fired after each ctx.ask() completes (once per ask invocation). */
1524
1820
  onAgentCallComplete?: (call: AgentCallInfo) => void;
1821
+ /** Options for the lazy `ctx.events` `AxlEventBus`. Forwarded by
1822
+ * `runtime.createContext({ events })` / `runtime.execute(name, input,
1823
+ * { events })` / `runtime.stream(name, input, { events })`. If unset,
1824
+ * the bus uses its built-in defaults (`maxQueued: 10_000`,
1825
+ * `onOverflow: 'drop-oldest-non-terminal'`). Child contexts share the
1826
+ * parent's bus instance, so this option has no effect on
1827
+ * `createChildContext()` callers. */
1828
+ eventStreamOptions?: EventStreamOptions;
1829
+ /** Internal: shared mutable holder for the lazy `AxlEventBus`. Set by
1830
+ * `createChildContext` so parent + children read and write the same
1831
+ * slot — late allocation in the parent (after children exist) is
1832
+ * visible to those children, and child allocation is visible to the
1833
+ * parent. Direct callers should not pass this. */
1834
+ _busRef?: {
1835
+ current: AxlEventBus | undefined;
1836
+ };
1525
1837
  /** Internal: shared auto-checkpoint counters. Set by `createChildContext`
1526
1838
  * so parent + child share the same number space, preventing checkpoint
1527
1839
  * store key collisions across nested `WorkflowContext` instances.
@@ -1602,6 +1914,90 @@ declare class WorkflowContext<TInput = unknown> {
1602
1914
  private awaitHumanHandler?;
1603
1915
  private onAgentStart?;
1604
1916
  private onAgentCallComplete?;
1917
+ /** Shared mutable slot for the lazy `AxlEventBus`. The slot is shared
1918
+ * by reference across parent/child contexts so a late allocation
1919
+ * (consumer subscribes after a child context exists) is visible
1920
+ * everywhere. Until first access `_busRef.current` is undefined and
1921
+ * `emitEvent` skips the bus fan-out entirely (zero overhead for
1922
+ * contexts that never observe). */
1923
+ private readonly _busRef;
1924
+ private readonly _eventStreamOptions?;
1925
+ /** Removes the constructor-registered `'abort'` listener from
1926
+ * `this.signal` so a long-lived signal (e.g., a request-scoped or
1927
+ * process-wide signal reused across many `runtime.createContext()` /
1928
+ * `runtime.execute()` calls) doesn't accumulate listeners forever.
1929
+ * Called from `disposeEvents()` and from the workflow_end/error
1930
+ * emission in `emitEvent`. Undefined when no abort listener was
1931
+ * registered (no signal, signal already aborted, or non-root
1932
+ * context). */
1933
+ private abortListenerCleanup?;
1934
+ /**
1935
+ * Iterable + EventEmitter view over every `AxlEvent` emitted by this
1936
+ * context (and its child contexts — agent-as-tool nested asks bubble up).
1937
+ *
1938
+ * Use cases:
1939
+ * - **Inside a workflow handler**: subscribe before the first `ctx.ask()`
1940
+ * to observe `partial_object`, `agent_call_*`, `tool_call_*`, etc.
1941
+ * *between* asks. `ctx.events.partialObjects` is the coalescing view
1942
+ * designed for streaming-structured-output UIs.
1943
+ * - **Ad-hoc contexts** from `runtime.createContext()`: the same
1944
+ * iterable replaces the legacy `onToken` / `onToolCall` /
1945
+ * `onAgentStart` callbacks.
1946
+ * - **Cross-execution**: prefer `runtime.on('trace', …)` instead — that
1947
+ * fans out across all executions, while `ctx.events` is per-context.
1948
+ *
1949
+ * Lazy: the bus is allocated on first access. The bus's iterator
1950
+ * queue retains events emitted before any consumer iterates, so a
1951
+ * late `for await (const e of ctx.events)` still drains queued
1952
+ * events. The `partialObjects` view additionally seeds from a
1953
+ * per-bus `latestPartialByAsk` map, so a late subscriber may see
1954
+ * the latest coalesced state per ask even when earlier events were
1955
+ * already drained by another iterator. **Neither rescues a late
1956
+ * subscriber from the streaming-gate behavior** (see below).
1957
+ *
1958
+ * **Subscribe before the first `ctx.ask()`.** The streaming code
1959
+ * path inside `ctx.ask()` activates only when an observer is
1960
+ * present at the time the ask starts (`_streamingEnabled` — either
1961
+ * the legacy `onToken` callback is set or `ctx.events` has been
1962
+ * allocated). If you allocate `ctx.events` AFTER a `ctx.ask()` has
1963
+ * begun, that in-flight ask will not stream `token` /
1964
+ * `partial_object` events at all (the agent loop went through
1965
+ * `provider.chat` instead of `provider.stream`). Subsequent asks
1966
+ * will stream normally — the gate is re-checked per ask. The
1967
+ * unambiguous pattern is `const events = ctx.events;` on the first
1968
+ * line of the handler.
1969
+ *
1970
+ * Auto-termination: `emitEvent` calls `_eventBus._finish()` after
1971
+ * emitting `workflow_end` or `error`, so iterators terminate
1972
+ * cleanly with `done: true`. For ad-hoc contexts that never run a
1973
+ * workflow (no terminal event), pass `signal: AbortSignal.timeout(...)`
1974
+ * to `runtime.createContext` (the bus auto-disposes on abort) or
1975
+ * call `ctx.disposeEvents()` when done observing.
1976
+ */
1977
+ get events(): AxlEventBus;
1978
+ /** Manually finish the `ctx.events` bus, terminating any active
1979
+ * iterators with `done: true`. Idempotent. Useful for ad-hoc contexts
1980
+ * from `runtime.createContext()` that never emit a `workflow_end` /
1981
+ * `error` terminal — observers iterating `for await (const e of
1982
+ * ctx.events)` would otherwise hang. Workflow-driven contexts
1983
+ * (`runtime.execute` / `runtime.stream`) terminate automatically and
1984
+ * don't need this.
1985
+ *
1986
+ * Also removes the constructor-registered abort listener so a
1987
+ * long-lived `signal` reused across many ad-hoc contexts doesn't
1988
+ * accumulate listeners. */
1989
+ disposeEvents(): void;
1990
+ /** True if any observer wants per-token streaming for asks started
1991
+ * from this context: either the legacy `onToken` callback is set
1992
+ * (e.g., `runtime.stream()` plumbs a sentinel) OR `ctx.events` has
1993
+ * been allocated (a workflow-handler consumer subscribed before
1994
+ * the ask). `ctx.ask` reads this to decide whether to enter the
1995
+ * streaming code path; the gate is re-checked per ask, so a
1996
+ * consumer subscribing AFTER the first ask started still gets
1997
+ * streaming on the next one. The check `_busRef.current !== undefined`
1998
+ * is a one-way flag — `_finish` does not unset the reference, so
1999
+ * once an observer was present, every subsequent ask streams. */
2000
+ private get _streamingEnabled();
1605
2001
  constructor(init: WorkflowContextInit);
1606
2002
  /**
1607
2003
  * Create a child context for nested agent invocations (e.g., agent-as-tool).
@@ -1646,7 +2042,8 @@ declare class WorkflowContext<TInput = unknown> {
1646
2042
  private executeAgentCall;
1647
2043
  /**
1648
2044
  * Push the final assistant message into session history, preserving providerMetadata
1649
- * (e.g., Gemini thought signatures needed for multi-turn reasoning context).
2045
+ * (e.g., Gemini thought signatures needed for multi-turn reasoning context) and
2046
+ * stamping the agent name for observability + future per-agent scoping.
1650
2047
  */
1651
2048
  private pushAssistantToSessionHistory;
1652
2049
  private buildToolDefs;
@@ -1891,27 +2288,38 @@ declare function workflow<TInput extends z.ZodType, TOutput extends z.ZodType =
1891
2288
  /**
1892
2289
  * A streamable workflow execution.
1893
2290
  *
1894
- * Extends Node's Readable and implements `AsyncIterable<AxlEvent>`. Supports
1895
- * `.on()` events, `for-await-of`, the `.text` iterator (root-only tokens),
1896
- * the `.lifecycle` iterator (structural events only), and `.pipe()`.
2291
+ * Extends Node's `Readable` and implements `AsyncIterable<AxlEvent>` via a
2292
+ * composed `AxlEventBus`. Supports `.on()` events, `for-await-of`, the
2293
+ * `.text` iterator (root-only tokens), the `.lifecycle` iterator (structural
2294
+ * events only), the `.textByAsk` iterator (per-ask token chunks), and `.pipe()`.
1897
2295
  *
1898
2296
  * The wire carries `AxlEvent` directly — there is no per-stream synthesized
1899
2297
  * shape. Consumers narrow on `event.type` and use `AskScoped` fields
1900
2298
  * (`askId`, `parentAskId`, `depth`) for routing/filtering.
1901
- */
1902
- declare class AxlStream extends Readable {
1903
- private bus;
1904
- /**
1905
- * Per-ask token buffers split into "in-progress" and "committed"
1906
- * halves so `fullText` only includes tokens from attempts that
1907
- * actually won. Scoping is per-`askId` so concurrent root-level asks
1908
- * (`ctx.parallel`, `ctx.spawn`, `ctx.race`, `ctx.map`) don't
1909
- * interleave each other's tokens — each branch's chunks stay
1910
- * contiguous, and a `pipeline(failed)` on one branch only discards
1911
- * THAT branch's in-progress buffer (previously: shared buffer caused
1912
- * a failure on one branch to discard a peer's in-flight successful
1913
- * tokens). Insertion order in the Maps reflects which ask emitted
1914
- * its first token first, which is what `fullText` joins on.
2299
+ *
2300
+ * The underlying `AxlEventBus` is shared with `ctx.events` — both surfaces
2301
+ * expose the same iterator/EventEmitter contract over the same `AxlEvent`
2302
+ * union. AxlStream layers on top: Readable backpressure shape, terminal
2303
+ * `done`/`error` synthesis, `.pipe()`, and per-ask `fullText` token
2304
+ * accounting (which is stream-specific because the runtime emits the
2305
+ * structured `pipeline` events that drive commit/discard).
2306
+ */
2307
+ declare class AxlStream extends Readable implements AsyncIterable<AxlEvent> {
2308
+ /** Composed event bus — owns iterator queue, EventEmitter gating, and
2309
+ * curated views (`.text`, `.lifecycle`, `.textByAsk`). Private; the
2310
+ * public surface is via the delegating methods below. */
2311
+ private readonly events;
2312
+ /**
2313
+ * Per-ask token buffers split into "in-progress" and "committed" halves
2314
+ * so `fullText` only includes tokens from attempts that actually won.
2315
+ * Scoping is per-`askId` so concurrent root-level asks
2316
+ * (`ctx.parallel`, `ctx.spawn`, `ctx.race`, `ctx.map`) don't interleave
2317
+ * each other's tokens — each branch's chunks stay contiguous, and a
2318
+ * `pipeline(failed)` on one branch only discards THAT branch's
2319
+ * in-progress buffer (previously: shared buffer caused a failure on
2320
+ * one branch to discard a peer's in-flight successful tokens).
2321
+ * Insertion order in the Maps reflects which ask emitted its first
2322
+ * token first, which is what `fullText` joins on.
1915
2323
  *
1916
2324
  * On `pipeline(status: 'committed')` for an ask, that ask's
1917
2325
  * in-progress entry flushes to its committed entry. On
@@ -1925,52 +2333,54 @@ declare class AxlStream extends Readable {
1925
2333
  private attemptByAsk;
1926
2334
  private committedByAsk;
1927
2335
  private result;
1928
- private finished;
1929
2336
  readonly promise: Promise<unknown>;
1930
- private eventQueue;
1931
- private waiters;
1932
- constructor();
1933
- /** Wire-format event names callers can subscribe to via `.on(name, fn)`.
1934
- * Derived from the canonical `AXL_EVENT_TYPES` tuple — adding a new
1935
- * variant in `types.ts` automatically extends the subscribable set. */
1936
- private static readonly STREAM_EVENTS;
1937
- on(event: string, handler: (...args: unknown[]) => void): this;
1938
- off(event: string, handler: (...args: unknown[]) => void): this;
2337
+ private resolvePromise;
2338
+ private rejectPromise;
2339
+ constructor(options?: EventStreamOptions);
2340
+ on<T extends AxlEventType>(event: T, handler: (e: AxlEventOf<T>) => void): this;
2341
+ on(event: string, handler: (...args: any[]) => void): this;
2342
+ off<T extends AxlEventType>(event: T, handler: (e: AxlEventOf<T>) => void): this;
2343
+ off(event: string, handler: (...args: any[]) => void): this;
1939
2344
  [Symbol.asyncIterator](): {
1940
2345
  next: () => Promise<IteratorResult<AxlEvent>>;
2346
+ return: () => Promise<IteratorResult<AxlEvent>>;
1941
2347
  [Symbol.asyncIterator](): /*elided*/ any;
1942
- [Symbol.asyncDispose](): Promise<void>;
2348
+ [Symbol.asyncDispose]: () => Promise<void>;
1943
2349
  };
1944
2350
  get text(): AsyncIterable<string>;
1945
- /**
1946
- * Iterator over `{ askId, text }` pairs — one emission per token chunk,
1947
- * tagged with the ask frame that produced it. Complements `.text`
1948
- * (root-only stream): consumers building a split-pane UI that shows
1949
- * each sub-agent's output in its own lane can group by `askId`
1950
- * without iterating the raw stream and hand-filtering on `event.type`.
1951
- *
1952
- * Nested and root tokens both flow through. The `agent` field names
1953
- * the producing agent for UI labelling; it's undefined when the token
1954
- * was emitted outside any ask (rare — synthesized test fixtures).
1955
- */
1956
2351
  get textByAsk(): AsyncIterable<{
1957
2352
  askId: string;
1958
2353
  agent?: string;
1959
2354
  text: string;
1960
2355
  }>;
1961
- /**
1962
- * Iterator over structural lifecycle events only — skips per-token chatter
1963
- * and progressive partial_object emissions. Useful for waterfall UIs and
1964
- * any consumer that wants the "what happened" timeline without per-chunk
1965
- * noise. Renamed from `.steps` in the unified-event-model migration
1966
- * because these are events, not pipeline steps.
1967
- */
1968
2356
  get lifecycle(): AsyncIterable<AxlEvent>;
2357
+ /** Coalescing iterator over `partial_object` events — yields the latest
2358
+ * object payload per `askId`. Designed for streaming-structured-output
2359
+ * UIs: when the producer outpaces the consumer, intermediate snapshots
2360
+ * are silently superseded and the next `.next()` await sees only the
2361
+ * most recent state per ask. Listener-based, so does NOT race with the
2362
+ * main `for await (const e of stream)` iterator. See
2363
+ * `AxlEventBus.partialObjects` for full semantics. The yielded
2364
+ * `CoalescedPartialObject` shape is shared with the bus so the two
2365
+ * surfaces can never drift on the `attempt` field. */
2366
+ get partialObjects(): AsyncIterable<CoalescedPartialObject>;
1969
2367
  pipe<T extends NodeJS.WritableStream>(destination: T, options?: {
1970
2368
  end?: boolean;
1971
2369
  }): T;
1972
- /** Push an event onto the stream. Called by the runtime. */
2370
+ /** Push an event onto the stream. Called by the runtime.
2371
+ *
2372
+ * Side-effect ordering: `accountForToken` (fullText buffers) →
2373
+ * `events._push` (bus.emit + iterator waiter/queue) → `Readable.push`
2374
+ * (object-mode buffer for `.pipe()` consumers and `'data'` listeners).
2375
+ * Pre-refactor the order was `bus.emit → Readable.push → waiter/queue`;
2376
+ * the new order resolves async iterators before Readable.push. The
2377
+ * difference is invisible to all in-tree consumers — `.pipe()` here
2378
+ * routes through the bus's `'token'` listener (line 129), and existing
2379
+ * tests pass — but it's intentional rather than accidental. */
1973
2380
  _push(event: AxlEvent): void;
2381
+ /** Token accumulation and pipeline commit/discard accounting for
2382
+ * `fullText`. Extracted from `_push` so the bus delegation reads cleanly. */
2383
+ private accountForToken;
1974
2384
  /**
1975
2385
  * Signal successful completion.
1976
2386
  *
@@ -2017,6 +2427,18 @@ type SessionOptions = {
2017
2427
  /**
2018
2428
  * A stateful conversation session.
2019
2429
  * Persists message history across multiple interactions.
2430
+ *
2431
+ * **Concurrency contract:** `send`, `stream`, `end`, and `fork` are
2432
+ * serialized per session id within one runtime instance. Two
2433
+ * `runtime.session('x')` handles share the same lock (it lives on
2434
+ * `AxlRuntime`, not the `Session` instance). `history()` and
2435
+ * `handoffs()` bypass the lock and return the last persisted snapshot.
2436
+ *
2437
+ * **⚠️ Cross-process locking is NOT provided.** Multiple Node workers
2438
+ * behind a load balancer hitting the same Redis-backed `sessionId`
2439
+ * will still race. Pin sessions to workers (sticky routing) or use
2440
+ * distinct ids per request. See `docs/api-reference.md` →
2441
+ * Sessions → Concurrency.
2020
2442
  */
2021
2443
  declare class Session {
2022
2444
  private sessionId;
@@ -2026,13 +2448,36 @@ declare class Session {
2026
2448
  private options;
2027
2449
  constructor(sessionId: string, runtime: AxlRuntime, store: StateStore, options?: SessionOptions);
2028
2450
  get id(): string;
2029
- send(workflowName: string, input: unknown): Promise<unknown>;
2030
- stream(workflowName: string, input: unknown): Promise<AxlStream>;
2451
+ send(workflowName: string, input: unknown, options?: {
2452
+ signal?: AbortSignal;
2453
+ events?: EventStreamOptions;
2454
+ }): Promise<unknown>;
2455
+ private sendImpl;
2456
+ /** Read history + summary, apply maxMessages limit (with optional
2457
+ * summarization), and push the new user message. Returns the mutable
2458
+ * `history` array (which `executeAgentCall` will append the assistant
2459
+ * reply to in-place) and the `metadata` payload to forward to
2460
+ * `runtime.execute()` / `runtime.stream()`. Shared by `sendImpl` and
2461
+ * `streamImpl` so future fixes (e.g., to summarization wiring) land in
2462
+ * one place. */
2463
+ private prepareHistory;
2464
+ /** Push the assistant reply (if `executeAgentCall` didn't already) and
2465
+ * persist. Shared by `sendImpl` and `streamImpl`. The fallback assistant
2466
+ * push has no agent context and so leaves `agent` undefined — see the
2467
+ * `ChatMessage.agent` field doc. */
2468
+ private commitHistory;
2469
+ stream(workflowName: string, input: unknown, options?: {
2470
+ signal?: AbortSignal;
2471
+ events?: EventStreamOptions;
2472
+ }): Promise<AxlStream>;
2473
+ private streamImpl;
2031
2474
  history(): Promise<ChatMessage[]>;
2032
2475
  /** Get the handoff history for this session. */
2033
2476
  handoffs(): Promise<HandoffRecord[]>;
2034
2477
  end(): Promise<void>;
2035
- fork(newId: string): Promise<Session>;
2478
+ fork(newId: string, options?: {
2479
+ overwrite?: boolean;
2480
+ }): Promise<Session>;
2036
2481
  }
2037
2482
 
2038
2483
  /**
@@ -2059,6 +2504,17 @@ type ExecuteOptions = {
2059
2504
  * Useful for in-process testing and ad-hoc invocations where you don't want to poll
2060
2505
  * `runtime.getPendingDecisions()` and call `runtime.resolveDecision()`. */
2061
2506
  awaitHumanHandler?: (options: AwaitHumanOptions) => Promise<HumanDecision>;
2507
+ /** External AbortSignal. When fired, aborts the workflow exactly as
2508
+ * `runtime.abort(executionId)` would. Lets callers use the standard
2509
+ * JS cancellation pattern (e.g., AbortController from a UI's "stop"
2510
+ * button) without having to track the execution id. */
2511
+ signal?: AbortSignal;
2512
+ /** Configuration for the workflow context's `ctx.events` bus and the
2513
+ * `AxlStream` returned by `runtime.stream()`. Both consume the same
2514
+ * `EventStreamOptions` (queue cap + overflow policy). When omitted,
2515
+ * defaults are `maxQueued: 10_000` and `onOverflow:
2516
+ * 'drop-oldest-non-terminal'`. */
2517
+ events?: EventStreamOptions;
2062
2518
  };
2063
2519
  /**
2064
2520
  * Shape of events forwarded to an eval `onProgress` callback. Mirrors
@@ -2101,6 +2557,10 @@ type CreateContextOptions = {
2101
2557
  }, meta: CallbackMeta) => void;
2102
2558
  /** Handler for tool approval requests. Called when an agent invokes a tool with requireApproval. */
2103
2559
  awaitHumanHandler?: (options: AwaitHumanOptions) => Promise<HumanDecision>;
2560
+ /** Configuration for the lazy `ctx.events` bus on the returned context.
2561
+ * Defaults: `maxQueued: 10_000`, `onOverflow:
2562
+ * 'drop-oldest-non-terminal'`. */
2563
+ events?: EventStreamOptions;
2104
2564
  };
2105
2565
  /**
2106
2566
  * The main entry point for executing Axl workflows.
@@ -2124,6 +2584,13 @@ declare class AxlRuntime extends EventEmitter {
2124
2584
  private spanManager;
2125
2585
  private historicalExecutions;
2126
2586
  private historicalExecutionsLoadPromise;
2587
+ /** Per-session in-process serialization. Each entry is a chain of pending
2588
+ * work for that session id — `Session.send` / `Session.stream` await the
2589
+ * prior entry before reading history, so concurrent calls produce a
2590
+ * sequenced conversation log instead of last-writer-wins corruption.
2591
+ * Cross-process locking is NOT provided; use distinct session ids when
2592
+ * multiple processes share a store. */
2593
+ private sessionLocks;
2127
2594
  private evalHistory;
2128
2595
  private evalHistoryLoadPromise;
2129
2596
  /** Resolved cap on `ExecutionInfo.events` per execution. Cached on
@@ -2171,8 +2638,9 @@ declare class AxlRuntime extends EventEmitter {
2171
2638
  * Side effects on `execInfo`: sets `status` / `completedAt` /
2172
2639
  * `duration` / `result` (or `error`) before emitting `workflow_end`.
2173
2640
  * On throw: emits `workflow_end({status: 'failed', aborted?})` and
2174
- * persists before rethrowing. `abortControllers.delete` runs in
2175
- * `finally` regardless of outcome.
2641
+ * persists before rethrowing. `abortControllers.delete` and
2642
+ * `cleanupAbortForwarder` (if provided) both run in `finally`
2643
+ * regardless of outcome.
2176
2644
  *
2177
2645
  * Caller responsibilities:
2178
2646
  * - Allocate `executionId` and register it in `abortControllers` map
@@ -2182,6 +2650,10 @@ declare class AxlRuntime extends EventEmitter {
2182
2650
  * - Pass `span` if the call site is inside a tracer span — this
2183
2651
  * helper sets `axl.workflow.cost` / `.duration` attributes on
2184
2652
  * success.
2653
+ * - Pass `cleanupAbortForwarder` (the cleanup fn returned from
2654
+ * `forwardAbortSignal`) so the listener on the user's external
2655
+ * signal is removed when the workflow completes — see the
2656
+ * helper's docstring for the leak this prevents.
2185
2657
  */
2186
2658
  private runWorkflowBody;
2187
2659
  /**
@@ -2265,7 +2737,27 @@ declare class AxlRuntime extends EventEmitter {
2265
2737
  stream(name: string, input: unknown, options?: ExecuteOptions): AxlStream;
2266
2738
  /** Create or resume a session. */
2267
2739
  session(id: string, options?: SessionOptions): Session;
2268
- /** Gracefully shut down the runtime, aborting in-flight executions and closing state stores and MCP servers. */
2740
+ /** @internal Serialize work per session id within this runtime instance.
2741
+ * Subsequent calls await the prior task's settlement (success or failure)
2742
+ * before running. Used by `Session.send` / `Session.stream` / `end` /
2743
+ * `fork` to eliminate read-modify-write races on `StateStore.saveSession`
2744
+ * (and on `delete`/`getSession` ordering vs in-flight saves).
2745
+ *
2746
+ * ⚠️ NOT REENTRANT. Calling this from inside an `fn` already running
2747
+ * under `_serializeSession(sameId, ...)` will deadlock — the inner
2748
+ * call awaits the outer's settlement, the outer awaits the inner's
2749
+ * return. `Session.fork` deliberately calls with TWO different ids
2750
+ * (source and target) and never the same id twice. Any future caller
2751
+ * must follow the same rule. */
2752
+ _serializeSession<T>(id: string, fn: () => Promise<T>): Promise<T>;
2753
+ /** Gracefully shut down the runtime, aborting in-flight executions and
2754
+ * closing state stores and MCP servers. Drains in-flight per-session
2755
+ * work (Session.send / Session.stream) before closing the state store.
2756
+ *
2757
+ * This is a drain of EXISTING work, not a barrier against new calls —
2758
+ * callers should stop accepting work (e.g., close their HTTP server)
2759
+ * before invoking `shutdown()`. A `Session.send` invoked after the
2760
+ * drain snapshot is taken can still race a closed state store. */
2269
2761
  shutdown(): Promise<void>;
2270
2762
  /** Abort a running execution by its ID. */
2271
2763
  abort(executionId: string): void;
@@ -3231,4 +3723,4 @@ declare class NoopSpanManager implements SpanManager {
3231
3723
  */
3232
3724
  declare function createSpanManager(config?: TelemetryConfig): Promise<SpanManager>;
3233
3725
 
3234
- export { AXL_EVENT_TYPES, type Agent, type AgentCallEndData, type AgentCallInfo, type AgentCallParams, type AgentCallStartData, type AgentConfig, AnthropicProvider, type AnyWorkflow, type AskOptions, type AskScoped, type AwaitHumanData, type AwaitHumanOptions, type AwaitHumanResolvedData, type AxlConfig, AxlError, type AxlEvent, type AxlEventBase, type AxlEventOf, type AxlEventType, AxlRuntime, AxlStream, BudgetExceededError, type BudgetOptions, type BudgetResult, CONFIG_CANDIDATES, COST_BEARING_LEAF_TYPES, type CallbackMeta, type ChatMessage, type ChatOptions, type CheckpointEventData, type CreateContextOptions, type DelegateData, type DelegateOptions, type Effort, type EmbedResult, type EmbedUsage, type Embedder, type EvalExecuteWorkflow, type EvalHistoryEntry, type ExecuteOptions, type ExecutionInfo, type ExecutionState, GeminiProvider, type GuardrailBlockHandler, type GuardrailData, GuardrailError, type GuardrailResult, type GuardrailsConfig, type HandoffDescriptor, type HandoffRecord, type HandoffReturnData, type HandoffStartData, type HumanDecision, InMemoryVectorStore, type InputGuardrail, type MapOptions, MaxTurnsError, McpManager, type McpServer, type McpServerConfig, type McpToolDefinition, type McpToolResult, type MemoryConfig, type MemoryEventData, MemoryManager, MemoryStore, NoConsensus, NoopSpanManager, OpenAIEmbedder, OpenAIProvider, OpenAIResponsesProvider, type OutputGuardrail, type OutputValidator, type PendingDecision, type Provider, type ProviderAdapter, ProviderRegistry, type ProviderResponse, QuorumNotMet, REDACTED, REDACTION_RULES, type RaceOptions, type RecallOptions, type RecallResult, RedisStore, type RememberOptions, type RememberResult, type ResolvedThinkingOptions, type Result, type RetryPolicy, SQLiteStore, type SchemaCheckData, Session, type SessionOptions, type SpanHandle, type SpanManager, type SpawnOptions, SqliteVectorStore, type StateStore, type StreamChunk, type TelemetryConfig, TimeoutError, type Tool, type ToolApprovalData, type ToolCallData, type ToolCallMessage, type ToolCallStartData, type ToolChoice, type ToolConfig, ToolDenied, type ToolDeniedData, type ToolHooks, type ValidateData, type ValidateResult, ValidationError, type VectorEntry, type VectorResult, type VectorStore, type VerifyData, VerifyError, type VerifyOptions, type VerifyRetry, type VoteOptions, type Workflow, type WorkflowConfig, WorkflowContext, type WorkflowContextInit, type WorkflowEndData, type WorkflowStartData, agent, createSpanManager, defineConfig, eventCostContribution, expandGlob, extractJson, findConfig, importModule, isCostBearingLeaf, isRootLevel, needsTsxLoader, parsePartialJson, pickDefault, pickExport, redactEvent, registerConditions, resolveRuntime, resolveThinkingOptions, tool, workflow, zodToJsonSchema };
3726
+ export { AXL_EVENT_TYPES, type Agent, type AgentCallEndData, type AgentCallInfo, type AgentCallParams, type AgentCallStartData, type AgentConfig, AnthropicProvider, type AnyWorkflow, type AskOptions, type AskScoped, type AwaitHumanData, type AwaitHumanOptions, type AwaitHumanResolvedData, type AxlConfig, AxlError, type AxlEvent, type AxlEventBase, AxlEventBus, type AxlEventOf, type AxlEventType, AxlRuntime, AxlStream, BudgetExceededError, type BudgetOptions, type BudgetResult, CONFIG_CANDIDATES, COST_BEARING_LEAF_TYPES, type CallbackMeta, type ChatMessage, type ChatOptions, type CheckpointEventData, type CoalescedPartialObject, type CreateContextOptions, type DelegateData, type DelegateOptions, type Effort, type EmbedResult, type EmbedUsage, type Embedder, type EvalExecuteWorkflow, type EvalHistoryEntry, type EventStreamOptions, EventStreamOverflowError, type ExecuteOptions, type ExecutionInfo, type ExecutionState, GeminiProvider, type GuardrailBlockHandler, type GuardrailData, GuardrailError, type GuardrailResult, type GuardrailsConfig, type HandoffDescriptor, type HandoffRecord, type HandoffReturnData, type HandoffStartData, type HumanDecision, InMemoryVectorStore, type InputGuardrail, type MapOptions, MaxTurnsError, McpManager, type McpServer, type McpServerConfig, type McpToolDefinition, type McpToolResult, type MemoryConfig, type MemoryEventData, MemoryManager, MemoryStore, NoConsensus, NoopSpanManager, OpenAIEmbedder, OpenAIProvider, OpenAIResponsesProvider, type OutputGuardrail, type OutputValidator, type PendingDecision, type Provider, type ProviderAdapter, ProviderRegistry, type ProviderResponse, QuorumNotMet, REDACTED, REDACTION_RULES, type RaceOptions, type RecallOptions, type RecallResult, RedisStore, type RememberOptions, type RememberResult, type ResolvedThinkingOptions, type Result, type RetryPolicy, SQLiteStore, type SchemaCheckData, Session, type SessionOptions, type SpanHandle, type SpanManager, type SpawnOptions, SqliteVectorStore, type StateStore, type StreamChunk, type TelemetryConfig, TimeoutError, type Tool, type ToolApprovalData, type ToolCallData, type ToolCallMessage, type ToolCallStartData, type ToolChoice, type ToolConfig, ToolDenied, type ToolDeniedData, type ToolHooks, type ValidateData, type ValidateResult, ValidationError, type VectorEntry, type VectorResult, type VectorStore, type VerifyData, VerifyError, type VerifyOptions, type VerifyRetry, type VoteOptions, type Workflow, type WorkflowConfig, WorkflowContext, type WorkflowContextInit, type WorkflowEndData, type WorkflowStartData, agent, createSpanManager, defineConfig, eventCostContribution, expandGlob, extractJson, findConfig, importModule, isCostBearingLeaf, isRootLevel, needsTsxLoader, parsePartialJson, pickDefault, pickExport, redactEvent, registerConditions, resolveRuntime, resolveThinkingOptions, tool, workflow, zodToJsonSchema };