@axlsdk/axl 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -274,96 +274,114 @@ type RaceOptions<T = unknown> = {
274
274
  };
275
275
  /** Execution status */
276
276
  type ExecutionStatus = 'running' | 'completed' | 'failed' | 'waiting';
277
- /** Trace event type. See `TraceEvent` for per-type data shapes. */
278
- type TraceEventType = 'agent_call' | 'tool_call' | 'verify' | 'handoff' | 'delegate' | 'tool_denied' | 'tool_approval' | 'log' | 'workflow_start' | 'workflow_end' | 'guardrail' | 'schema_check' | 'validate';
279
- /** Data shape for `agent_call` trace events. Populated on every LLM call (pass or fail). */
280
- type AgentCallTraceData = {
277
+ /**
278
+ * Canonical list of `AxlEvent.type` discriminators. Single source of truth —
279
+ * derive `AxlEventType` from this tuple and use it to validate stream filters,
280
+ * redaction tables, and exhaustiveness assertions.
281
+ */
282
+ declare const AXL_EVENT_TYPES: readonly ["workflow_start", "workflow_end", "ask_start", "ask_end", "agent_call_start", "agent_call_end", "token", "tool_call_start", "tool_call_end", "tool_approval", "tool_denied", "delegate", "handoff_start", "handoff_return", "pipeline", "partial_object", "verify", "log", "memory_remember", "memory_recall", "memory_forget", "checkpoint_save", "checkpoint_replay", "await_human", "await_human_resolved", "guardrail", "schema_check", "validate", "done", "error"];
283
+ /** Discriminator union derived from `AXL_EVENT_TYPES`. */
284
+ type AxlEventType = (typeof AXL_EVENT_TYPES)[number];
285
+ /** Resolved model parameters sent to the provider for an LLM call. */
286
+ type AgentCallParams = {
287
+ temperature?: number;
288
+ maxTokens?: number;
289
+ effort?: Effort;
290
+ thinkingBudget?: number;
291
+ includeThoughts?: boolean;
292
+ toolChoice?: ToolChoice;
293
+ stop?: string[];
294
+ };
295
+ /**
296
+ * Data shape for `agent_call_start` events — the **request** side of the call.
297
+ * Everything here is known at the moment the call is dispatched, before the
298
+ * provider responds. Consumers can render "what's being asked" without waiting
299
+ * for completion.
300
+ */
301
+ type AgentCallStartData = {
281
302
  /** Original user prompt passed to `ctx.ask()`. Does not include retry feedback or tool results. */
282
303
  prompt: string;
283
- /** Final LLM response content for this turn. */
284
- response: string;
285
304
  /** Resolved system prompt (after evaluating dynamic system selectors). */
286
305
  system?: string;
287
- /** Reasoning/thinking content returned by the provider, when available. */
288
- thinking?: string;
289
306
  /** Resolved model parameters sent to the provider for this call. */
290
- params?: {
291
- temperature?: number;
292
- maxTokens?: number;
293
- effort?: Effort;
294
- thinkingBudget?: number;
295
- includeThoughts?: boolean;
296
- toolChoice?: ToolChoice;
297
- stop?: string[];
298
- };
307
+ params?: AgentCallParams;
299
308
  /** 1-indexed iteration of the tool-calling loop for this `ctx.ask()` call. */
300
- turn?: number;
309
+ turn: number;
301
310
  /** When set, this call is a retry triggered by a failed gate check on the previous turn. */
302
311
  retryReason?: 'schema' | 'validate' | 'guardrail';
312
+ /** Names of tools exposed to the model on this call. Empty/omitted when no tools are bound. */
313
+ toolNames?: string[];
303
314
  /** Full ChatMessage[] sent to the provider this turn. Only populated when `trace.level === 'full'`. */
304
315
  messages?: ChatMessage[];
305
316
  };
306
- /** Data shape for `guardrail` trace events. */
307
- type GuardrailTraceData = {
308
- guardrailType: 'input' | 'output';
309
- blocked: boolean;
310
- reason?: string;
311
- /** 1-indexed attempt count (output guardrails only). */
312
- attempt?: number;
313
- /** Maximum attempts allowed before the guardrail throws. */
314
- maxAttempts?: number;
315
- /** The exact corrective message about to be injected into the conversation — only set when
316
- * this check failed and a retry is happening. Gives users visibility into what the LLM sees
317
- * between retry attempts. */
318
- feedbackMessage?: string;
317
+ /**
318
+ * Data shape for `agent_call_end` events — the **response** side of the call.
319
+ * Populated when the provider returns (success or recoverable failure). The
320
+ * companion request-side payload lives on the matching `agent_call_start`.
321
+ *
322
+ * Pair invariant: every `agent_call_start` is followed by exactly one
323
+ * `agent_call_end`, even on provider error. On the error path `response` is
324
+ * empty and `error` carries the provider's message; cost/tokens/duration are
325
+ * still emitted (top-level on the event) when partial usage is available.
326
+ */
327
+ type AgentCallEndData = {
328
+ /** Final LLM response content for this turn. Empty string on error. */
329
+ response: string;
330
+ /** Reasoning/thinking content returned by the provider, when available. */
331
+ thinking?: string;
332
+ /** 1-indexed iteration of the tool-calling loop. Mirrors the matching `agent_call_start.data.turn`. */
333
+ turn: number;
334
+ /** Mirrors `agent_call_start.data.retryReason` so cost-attribution consumers
335
+ * reading `agent_call_end` (cost lives here) can bucket without joining. */
336
+ retryReason?: 'schema' | 'validate' | 'guardrail';
337
+ /** Provider error message when the call threw (network failure, 4xx/5xx,
338
+ * abort, etc). Mutually exclusive with `response` content. Subject to
339
+ * `config.trace.redact` (vendor errors can echo prompt text). */
340
+ error?: string;
319
341
  };
320
- /** Data shape for `schema_check` trace events. Emitted on every schema parse (pass or fail). */
321
- type SchemaCheckTraceData = {
322
- valid: boolean;
323
- reason?: string;
324
- attempt: number;
325
- maxAttempts: number;
326
- feedbackMessage?: string;
342
+ /** Data shape for `tool_call_end` events. */
343
+ type ToolCallData = {
344
+ args: unknown;
345
+ result: unknown;
346
+ callId?: string;
327
347
  };
328
- /** Data shape for `validate` trace events (post-schema business rule validation). */
329
- type ValidateTraceData = {
330
- valid: boolean;
331
- reason?: string;
332
- attempt: number;
333
- maxAttempts: number;
334
- feedbackMessage?: string;
348
+ /** Data shape for `tool_call_start` events. */
349
+ type ToolCallStartData = {
350
+ args: unknown;
335
351
  };
336
- /** Data shape for `tool_approval` trace events. Emitted by the approval gate on both outcomes. */
337
- type ToolApprovalTraceData = {
352
+ /** Data shape for `tool_approval` events. Emitted by the approval gate on both outcomes. */
353
+ type ToolApprovalData = {
338
354
  approved: boolean;
339
355
  args: unknown;
340
356
  reason?: string;
341
357
  };
342
- /** Data shape for `tool_call` trace events. */
343
- type ToolCallTraceData = {
344
- args: unknown;
345
- result: unknown;
358
+ /** Data shape for `tool_denied` events. Emitted when the LLM names a tool the agent doesn't expose. */
359
+ type ToolDeniedData = {
360
+ args?: unknown;
361
+ reason?: string;
346
362
  callId?: string;
347
363
  };
348
- /** Data shape for `handoff` trace events. */
349
- type HandoffTraceData = {
364
+ /** Data shape for `handoff_start` events (always emitted, pre-transition). */
365
+ type HandoffStartData = {
366
+ source: string;
350
367
  target: string;
351
368
  mode: 'oneway' | 'roundtrip';
352
- /** Wall-clock ms from handoff_to_X tool call to target agent completion.
353
- * Always emitted — the event only fires at the terminal point of the
354
- * handoff, so we always have a measurement. */
355
- duration: number;
356
- /** Source agent that initiated the handoff (mirrors the event's `agent` field
357
- * for convenience — lets consumers query the handoff chain without
358
- * stitching together event.agent and data.target). */
359
- source?: string;
360
369
  /** The `message` arg the source agent passed when invoking `handoff_to_X`
361
- * (roundtrip mode only). Gives observability into *why* the source chose
362
- * to delegate. Subject to `config.trace.redact`. */
370
+ * (roundtrip mode only). Subject to `config.trace.redact`. */
363
371
  message?: string;
364
372
  };
365
- /** Data shape for `delegate` trace events. */
366
- type DelegateTraceData = {
373
+ /** Data shape for `handoff_return` events (roundtrip-only, post-return).
374
+ * The returned value itself is observable via the target ask's
375
+ * `ask_end.outcome`; this event marks the control transfer back to the
376
+ * source agent and carries the round-trip duration. */
377
+ type HandoffReturnData = {
378
+ source: string;
379
+ target: string;
380
+ /** Wall-clock ms from `handoff_start` emission to control returning. */
381
+ duration: number;
382
+ };
383
+ /** Data shape for `delegate` events. */
384
+ type DelegateData = {
367
385
  candidates: string[];
368
386
  /** Set when the decision is known at emission time (single-agent short-circuit). */
369
387
  selected?: string;
@@ -372,20 +390,45 @@ type DelegateTraceData = {
372
390
  /** Why this delegate was emitted: 'routed' (multi-agent) or 'single_candidate'. */
373
391
  reason: 'routed' | 'single_candidate';
374
392
  };
375
- /** Data shape for `verify` trace events. */
376
- type VerifyTraceData = {
393
+ /** Data shape for `verify` events. */
394
+ type VerifyData = {
377
395
  attempts: number;
378
396
  passed: boolean;
379
397
  lastError?: string;
380
398
  };
381
- /** Data shape for `workflow_start` trace events. Emitted once per workflow execution. */
382
- type WorkflowStartTraceData = {
399
+ /** Data shape for legacy `guardrail` events. Replaced by `pipeline` in PR 2. */
400
+ type GuardrailData = {
401
+ guardrailType: 'input' | 'output';
402
+ blocked: boolean;
403
+ reason?: string;
404
+ attempt?: number;
405
+ maxAttempts?: number;
406
+ feedbackMessage?: string;
407
+ };
408
+ /** Data shape for legacy `schema_check` events. Replaced by `pipeline` in PR 2. */
409
+ type SchemaCheckData = {
410
+ valid: boolean;
411
+ reason?: string;
412
+ attempt: number;
413
+ maxAttempts: number;
414
+ feedbackMessage?: string;
415
+ };
416
+ /** Data shape for legacy `validate` events. Replaced by `pipeline` in PR 2. */
417
+ type ValidateData = {
418
+ valid: boolean;
419
+ reason?: string;
420
+ attempt: number;
421
+ maxAttempts: number;
422
+ feedbackMessage?: string;
423
+ };
424
+ /** Data shape for `workflow_start` events. Emitted once per workflow execution. */
425
+ type WorkflowStartData = {
383
426
  /** The validated input passed to the workflow handler. */
384
427
  input: unknown;
385
428
  };
386
- /** Data shape for `workflow_end` trace events. Emitted once per workflow execution
429
+ /** Data shape for `workflow_end` events. Emitted once per workflow execution
387
430
  * on completion, failure, or cancellation. Distinguish cancellation via `aborted`. */
388
- type WorkflowEndTraceData = {
431
+ type WorkflowEndData = {
389
432
  status: 'completed' | 'failed';
390
433
  duration: number;
391
434
  /** Workflow return value. Present on `status: 'completed'`. */
@@ -396,78 +439,328 @@ type WorkflowEndTraceData = {
396
439
  * or consumer disconnect on streaming workflows). */
397
440
  aborted?: boolean;
398
441
  };
399
- /** Common fields carried by every `TraceEvent` regardless of `type`. */
400
- type TraceEventBase = {
442
+ /** Data shape for `checkpoint_save` / `checkpoint_replay` events.
443
+ * Emitted by `ctx.checkpoint(name, fn)` — `save` on first execution,
444
+ * `replay` when a saved value short-circuits the function call. */
445
+ type CheckpointEventData = {
446
+ /** Stable, caller-supplied identifier under which the checkpoint is
447
+ * stored. Internal auto-checkpoints from ask/spawn/race/parallel/map
448
+ * use names prefixed with `__auto/<primitive>/`. */
449
+ name: string;
450
+ };
451
+ /** Data shape for `await_human` events — emitted when execution suspends
452
+ * for a human decision via `ctx.awaitHuman()`. The pending side of the
453
+ * pair; `await_human_resolved` follows when the decision arrives. */
454
+ type AwaitHumanData = {
455
+ /** Optional human-facing prompt describing the decision needed. */
456
+ prompt?: string;
457
+ /** Channel routing the decision (e.g., 'slack', 'email', custom). */
458
+ channel?: string;
459
+ };
460
+ /** Data shape for `await_human_resolved` events — paired terminal of an
461
+ * `await_human` request. Carries the `HumanDecision` returned to the workflow. */
462
+ type AwaitHumanResolvedData = {
463
+ channel?: string;
464
+ decision: HumanDecision;
465
+ };
466
+ /** Data shape for `memory_remember` / `memory_recall` / `memory_forget` events. */
467
+ type MemoryEventData = {
468
+ scope: string;
469
+ key?: string;
470
+ /** Result count for `recall` (number of vectors returned). */
471
+ count?: number;
472
+ /** Embedder cost for semantic recall/remember. Mirrored at the top-level
473
+ * `cost` on the event so cost rails (`trackExecution`) pick it up. */
474
+ cost?: number;
475
+ /** Embedder usage detail (tokens / model). */
476
+ usage?: {
477
+ tokens?: number;
478
+ cost?: number;
479
+ model?: string;
480
+ };
481
+ /** True when this memory op called the semantic recall path (vs. key-only). */
482
+ embed?: boolean;
483
+ /** True when a key-only recall returned a value / semantic call path. */
484
+ semantic?: boolean;
485
+ /** True when a key-only recall returned a value. */
486
+ hit?: boolean;
487
+ /** Result count for `recall` (alias for `count` retained for back-compat). */
488
+ resultCount?: number;
489
+ /** Error message when the operation failed on the partial-failure path. */
490
+ error?: string;
491
+ };
492
+ /** Common fields carried by every `AxlEvent` regardless of `type`. */
493
+ type AxlEventBase = {
401
494
  executionId: string;
495
+ /** Monotonic per-execution step counter, shared across nested asks via ALS. */
402
496
  step: number;
497
+ /** Wall-clock ms. */
403
498
  timestamp: number;
499
+ /** Workflow this event belongs to. Auto-stamped by `emitEvent` from
500
+ * `this.workflowName` when defined; callers may override. */
404
501
  workflow?: string;
502
+ /** Optional emitting-agent name. Variants that always have an agent
503
+ * (e.g., `agent_call_start/end`) redeclare it as required so consumers
504
+ * narrowing on those variants get a non-optional `agent`. Single-point
505
+ * events that may or may not have an agent (`handoff`, gate events,
506
+ * `log`) keep the optional. */
405
507
  agent?: string;
406
- promptVersion?: string;
508
+ /** Optional model URI — set on agent-related events; ignored on others. */
407
509
  model?: string;
510
+ /** Optional prompt version stamped from `agent._config.version`. */
511
+ promptVersion?: string;
512
+ /**
513
+ * Cost (USD) contributed by this event.
514
+ *
515
+ * Two DIFFERENT semantics ship on this field and consumers must know
516
+ * which they're reading:
517
+ *
518
+ * - **Leaf cost** (`agent_call_end`, `tool_call_end`, `memory_remember`,
519
+ * `memory_recall`): the authoritative charge for this single
520
+ * provider call / tool invocation / embedder call. Summing these
521
+ * across an execution gives the true spend.
522
+ *
523
+ * - **Per-ask rollup** (`ask_end`): the SUM of leaf costs emitted
524
+ * within this ask's frame, EXCLUDING nested asks (which roll up
525
+ * into their own `ask_end`). Spec/16 decision 10.
526
+ *
527
+ * **If you write your own accumulator, DO NOT do `total += event.cost`
528
+ * across all event types — you'll double-count every ask** because the
529
+ * leaves AND the rollup both carry `cost`. Use the exported
530
+ * `eventCostContribution(event)` helper from `@axlsdk/axl` instead:
531
+ * it encapsulates the "skip ask_end, finite-check, leaf-only" rule in
532
+ * one place so your accumulator stays in lockstep with the built-in
533
+ * `runtime.trackExecution`, `ExecutionInfo.totalCost`, and Studio's
534
+ * cost aggregator.
535
+ *
536
+ * Other variants may stamp `cost` optionally to flow into cost rails
537
+ * (e.g., memory ops mirror `usage.cost` here). Review UX-8.
538
+ */
408
539
  cost?: number;
540
+ /** Token counts. Required-by-narrowing on `agent_call_end`; optional on
541
+ * any event that wishes to mirror an aggregate. Scope is agent prompt /
542
+ * completion / reasoning tokens ONLY — embedder tokens live in
543
+ * `memory_*.data.usage.tokens` and are deliberately NOT summed into
544
+ * this field (different pricing, different model, different category). */
409
545
  tokens?: {
410
546
  input?: number;
411
547
  output?: number;
412
548
  reasoning?: number;
413
549
  };
550
+ /** Duration in ms (set on `_end` variants and a few single-point events). */
414
551
  duration?: number;
415
- /** When set, this event was emitted from a child context spawned by a tool
416
- * handler. The value is the `callId` of the outer `tool_call` that invoked
417
- * the tool. Lets consumers reconstruct agent-as-tool call graphs by
418
- * joining nested events to their parent `tool_call`. Undefined on top-level
419
- * events. */
420
- parentToolCallId?: string;
552
+ };
553
+ /** Fields on every event that originates within a specific `ctx.ask()` call. */
554
+ type AskScoped = {
555
+ askId: string;
556
+ /** Absent on root ask. */
557
+ parentAskId?: string;
558
+ /** 0 = root ask; +1 per nested ask. */
559
+ depth: number;
560
+ /** Emitting agent's name. Absent on `ask_start` (pre-resolution) and on
561
+ * events that predate agent resolution. */
562
+ agent?: string;
563
+ };
564
+ /**
565
+ * Meta carried alongside callback invocations (`onToken`, `onAgentStart`,
566
+ * `onToolCall`) so consumers can group/route by ask.
567
+ *
568
+ * Note: `agent` is **required** here (the callback is always invoked
569
+ * inside an ALS frame that has the agent name already resolved). On the
570
+ * event side (`AskScoped.agent`) the field is optional — events emitted
571
+ * before agent resolution (e.g., `ask_start` is fired before the
572
+ * dynamic agent selector runs) can land without it.
573
+ */
574
+ type CallbackMeta = {
575
+ askId: string;
576
+ parentAskId?: string;
577
+ depth: number;
578
+ agent: string;
421
579
  };
422
580
  /**
423
- * Trace event. A discriminated union over `type` — consumers that narrow via
424
- * `type` get statically-typed access to `data` and event-specific fields.
425
- * When adding a new event type, extend this union AND the emitter in
426
- * `WorkflowContext.emitTrace()` together so the compiler catches drift.
581
+ * Unified event union. Replaces the old `TraceEvent` (rich, persisted) and
582
+ * `StreamEvent` (lean, wire) by emitting a single rich event from one site
583
+ * and consuming the same shape on both rails.
584
+ *
585
+ * - Streaming consumers iterate `AxlStream` (an `AsyncIterable<AxlEvent>`).
586
+ * - Non-streaming consumers read `ExecutionInfo.events: AxlEvent[]`.
587
+ *
588
+ * Tree reconstruction: group ask-scoped events by `askId`, parent-link via
589
+ * `parentAskId`, sort by `step`, render by `depth`. Tokens (high-volume) and
590
+ * `partial_object` events are stream-only — never persisted to
591
+ * `ExecutionInfo.events`.
592
+ *
593
+ * When adding a new variant, extend `AXL_EVENT_TYPES` AND the emitter in
594
+ * `WorkflowContext.emitEvent()` together so the compiler catches drift; the
595
+ * exhaustiveness fixture in `__tests__/axl-event-exhaustive.test-d.ts` will
596
+ * also fail until the new case is handled.
427
597
  */
428
- type TraceEvent = (TraceEventBase & {
429
- type: 'agent_call';
430
- data?: AgentCallTraceData;
431
- }) | (TraceEventBase & {
432
- type: 'tool_call';
598
+ type AxlEvent = (AxlEventBase & {
599
+ type: 'workflow_start';
600
+ workflow: string;
601
+ data: WorkflowStartData;
602
+ }) | (AxlEventBase & {
603
+ type: 'workflow_end';
604
+ workflow: string;
605
+ data: WorkflowEndData;
606
+ }) | (AxlEventBase & AskScoped & {
607
+ type: 'ask_start';
608
+ prompt: string;
609
+ }) | (AxlEventBase & AskScoped & {
610
+ type: 'ask_end';
611
+ /** Discriminated outcome — narrow on `outcome.ok`. Ask-internal throws
612
+ * surface here, NOT via the workflow-level `error` event. */
613
+ outcome: {
614
+ ok: true;
615
+ result: unknown;
616
+ } | {
617
+ ok: false;
618
+ error: string;
619
+ };
620
+ /** Sum of `agent_call_end.cost` + `tool_call_end.cost` WITHIN THIS ASK,
621
+ * excluding nested asks. Nested asks contribute to their own ask_end. */
622
+ cost: number;
623
+ duration: number;
624
+ }) | (AxlEventBase & AskScoped & {
625
+ type: 'agent_call_start';
626
+ agent: string;
627
+ model: string;
628
+ /** 1-indexed tool-calling loop iteration within the ask. */
629
+ turn: number;
630
+ /** Request-side payload — prompt, system, params, messages, retry context. */
631
+ data: AgentCallStartData;
632
+ }) | (AxlEventBase & AskScoped & {
633
+ type: 'agent_call_end';
634
+ agent: string;
635
+ model: string;
636
+ /** Authoritative turn-level cost. */
637
+ cost: number;
638
+ duration: number;
639
+ tokens?: {
640
+ input?: number;
641
+ output?: number;
642
+ reasoning?: number;
643
+ };
644
+ /** Response-side payload — response text, thinking. */
645
+ data: AgentCallEndData;
646
+ }) | (AxlEventBase & AskScoped & {
647
+ type: 'token';
648
+ data: string;
649
+ }) | (AxlEventBase & AskScoped & {
650
+ type: 'tool_call_start';
433
651
  tool: string;
434
- data?: ToolCallTraceData;
435
- }) | (TraceEventBase & {
652
+ callId: string;
653
+ data: ToolCallStartData;
654
+ }) | (AxlEventBase & AskScoped & {
655
+ type: 'tool_call_end';
656
+ tool: string;
657
+ callId: string;
658
+ duration: number;
659
+ cost?: number;
660
+ data: ToolCallData;
661
+ }) | (AxlEventBase & AskScoped & {
436
662
  type: 'tool_approval';
437
663
  tool: string;
438
- data?: ToolApprovalTraceData;
439
- }) | (TraceEventBase & {
664
+ callId?: string;
665
+ data: ToolApprovalData;
666
+ }) | (AxlEventBase & AskScoped & {
440
667
  type: 'tool_denied';
441
668
  tool: string;
442
- data?: unknown;
443
- }) | (TraceEventBase & {
669
+ callId?: string;
670
+ data?: ToolDeniedData;
671
+ }) | (AxlEventBase & AskScoped & {
672
+ type: 'delegate';
673
+ data: DelegateData;
674
+ }) | (AxlEventBase & {
675
+ type: 'handoff_start';
676
+ fromAskId: string;
677
+ toAskId: string;
678
+ sourceDepth: number;
679
+ targetDepth: number;
680
+ data: HandoffStartData;
681
+ }) | (AxlEventBase & {
682
+ type: 'handoff_return';
683
+ fromAskId: string;
684
+ toAskId: string;
685
+ sourceDepth: number;
686
+ targetDepth: number;
687
+ data: HandoffReturnData;
688
+ }) | (AxlEventBase & AskScoped & {
689
+ type: 'pipeline';
690
+ status: 'start';
691
+ stage: 'initial' | 'schema' | 'validate' | 'guardrail';
692
+ attempt: number;
693
+ maxAttempts: number;
694
+ }) | (AxlEventBase & AskScoped & {
695
+ type: 'pipeline';
696
+ status: 'failed';
697
+ stage: 'schema' | 'validate' | 'guardrail';
698
+ attempt: number;
699
+ maxAttempts: number;
700
+ /** Feedback message about to be injected into the conversation. */
701
+ reason: string;
702
+ }) | (AxlEventBase & AskScoped & {
703
+ type: 'pipeline';
704
+ status: 'committed';
705
+ /** The stage of the most recent `pipeline(start)` — `'initial'` when
706
+ * the ask committed on the first pass, otherwise the gate that last
707
+ * retried before commit. Lets consumers tell "committed cleanly"
708
+ * from "committed after a schema/validate/guardrail retry". */
709
+ stage: 'initial' | 'schema' | 'validate' | 'guardrail';
710
+ /** The final successful attempt. */
711
+ attempt: number;
712
+ maxAttempts: number;
713
+ }) | (AxlEventBase & AskScoped & {
714
+ type: 'partial_object';
715
+ attempt: number;
716
+ /** DeepPartial<T>; consumers cast at the render site. */
717
+ data: {
718
+ object: unknown;
719
+ };
720
+ }) | (AxlEventBase & AskScoped & {
721
+ type: 'verify';
722
+ data: VerifyData;
723
+ }) | (AxlEventBase & Partial<AskScoped> & {
444
724
  type: 'guardrail';
445
- data?: GuardrailTraceData;
446
- }) | (TraceEventBase & {
725
+ data?: GuardrailData;
726
+ }) | (AxlEventBase & Partial<AskScoped> & {
447
727
  type: 'schema_check';
448
- data?: SchemaCheckTraceData;
449
- }) | (TraceEventBase & {
728
+ data?: SchemaCheckData;
729
+ }) | (AxlEventBase & Partial<AskScoped> & {
450
730
  type: 'validate';
451
- data?: ValidateTraceData;
452
- }) | (TraceEventBase & {
453
- type: 'delegate';
454
- data?: DelegateTraceData;
455
- }) | (TraceEventBase & {
456
- type: 'handoff';
457
- data?: HandoffTraceData;
458
- }) | (TraceEventBase & {
459
- type: 'verify';
460
- data?: VerifyTraceData;
461
- }) | (TraceEventBase & {
731
+ data?: ValidateData;
732
+ }) | (AxlEventBase & Partial<AskScoped> & {
462
733
  type: 'log';
463
- data?: unknown;
464
- }) | (TraceEventBase & {
465
- type: 'workflow_start';
466
- data?: WorkflowStartTraceData;
467
- }) | (TraceEventBase & {
468
- type: 'workflow_end';
469
- data?: WorkflowEndTraceData;
734
+ data: unknown;
735
+ }) | (AxlEventBase & Partial<AskScoped> & {
736
+ type: 'memory_remember' | 'memory_recall' | 'memory_forget';
737
+ data: MemoryEventData;
738
+ }) | (AxlEventBase & Partial<AskScoped> & {
739
+ type: 'checkpoint_save' | 'checkpoint_replay';
740
+ data: CheckpointEventData;
741
+ }) | (AxlEventBase & Partial<AskScoped> & {
742
+ type: 'await_human';
743
+ data: AwaitHumanData;
744
+ }) | (AxlEventBase & Partial<AskScoped> & {
745
+ type: 'await_human_resolved';
746
+ data: AwaitHumanResolvedData;
747
+ }) | (AxlEventBase & {
748
+ type: 'done';
749
+ data: {
750
+ result: unknown;
751
+ };
752
+ }) | (AxlEventBase & Partial<AskScoped> & {
753
+ type: 'error';
754
+ data: {
755
+ message: string;
756
+ name?: string;
757
+ code?: string;
758
+ };
470
759
  });
760
+ /** Convenience: extract the union member matching a given `type` discriminator. */
761
+ type AxlEventOf<T extends AxlEventType> = Extract<AxlEvent, {
762
+ type: T;
763
+ }>;
471
764
  /** Result of a guardrail check. */
472
765
  type GuardrailResult = {
473
766
  block: boolean;
@@ -509,7 +802,10 @@ type ExecutionInfo = {
509
802
  executionId: string;
510
803
  workflow: string;
511
804
  status: ExecutionStatus;
512
- steps: TraceEvent[];
805
+ /** Full event timeline. Tokens and `partial_object` events are NOT persisted
806
+ * here (stream-only); aggregate `tokens: { input, output, reasoning? }`
807
+ * on `agent_call_end` is the persisted token representation. */
808
+ events: AxlEvent[];
513
809
  totalCost: number;
514
810
  startedAt: number;
515
811
  completedAt?: number;
@@ -517,57 +813,26 @@ type ExecutionInfo = {
517
813
  result?: unknown;
518
814
  error?: string;
519
815
  };
520
- type StreamEvent = {
521
- type: 'token';
522
- data: string;
523
- } | {
524
- type: 'tool_call';
525
- name: string;
526
- args: unknown;
527
- callId?: string;
528
- } | {
529
- type: 'tool_result';
530
- name: string;
531
- result: unknown;
532
- callId?: string;
533
- } | {
534
- type: 'tool_approval';
535
- name: string;
536
- args: unknown;
537
- approved: boolean;
538
- reason?: string;
539
- } | {
540
- type: 'agent_start';
541
- agent: string;
542
- model?: string;
543
- } | {
544
- type: 'agent_end';
545
- agent: string;
546
- cost?: number;
547
- duration?: number;
548
- } | {
549
- type: 'handoff';
550
- source: string;
551
- target: string;
552
- mode?: 'oneway' | 'roundtrip';
553
- } | {
554
- type: 'step';
555
- step: number;
556
- data: unknown;
557
- } | {
558
- type: 'done';
559
- data: unknown;
560
- } | {
561
- type: 'error';
562
- message: string;
563
- };
564
- /** Record of an agent handoff event (persisted in session metadata). */
816
+ /** Record of an agent handoff event (persisted in session metadata).
817
+ *
818
+ * `duration` semantics:
819
+ * - `oneway`: target's full ask duration (start-to-completion).
820
+ * - `roundtrip`: full round-trip wall-clock (handoff_start → handoff_return),
821
+ * includes the time to push the result back into the source's
822
+ * conversation. Both measurements are populated by the
823
+ * runtime once the corresponding event fires; if the target
824
+ * never completes (workflow aborted mid-handoff), `duration`
825
+ * stays undefined.
826
+ *
827
+ * `toAskId` is the askId of the target frame — lets consumers correlate
828
+ * the record to the target's `ask_end` event in the trace stream. */
565
829
  type HandoffRecord = {
566
830
  source: string;
567
831
  target: string;
568
832
  mode: 'oneway' | 'roundtrip';
569
833
  timestamp: number;
570
834
  duration?: number;
835
+ toAskId?: string;
571
836
  };
572
837
  /** Information about a completed agent call, emitted via onAgentCallComplete. */
573
838
  type AgentCallInfo = {
@@ -707,12 +972,8 @@ type ExecutionState = {
707
972
  * Redis (production).
708
973
  */
709
974
  interface StateStore {
710
- saveCheckpoint(executionId: string, step: number, data: unknown): Promise<void>;
711
- getCheckpoint(executionId: string, step: number): Promise<unknown | null>;
712
- getLatestCheckpoint(executionId: string): Promise<{
713
- step: number;
714
- data: unknown;
715
- } | null>;
975
+ saveCheckpoint(executionId: string, name: string, data: unknown): Promise<void>;
976
+ getCheckpoint(executionId: string, name: string): Promise<unknown | null>;
716
977
  saveSession(sessionId: string, history: ChatMessage[]): Promise<void>;
717
978
  getSession(sessionId: string): Promise<ChatMessage[]>;
718
979
  deleteSession(sessionId: string): Promise<void>;
@@ -905,6 +1166,27 @@ type StateConfig = {
905
1166
  sqlite?: {
906
1167
  path: string;
907
1168
  };
1169
+ /**
1170
+ * Maximum number of events retained in `ExecutionInfo.events` per
1171
+ * execution. Token and partial_object events are already excluded
1172
+ * from the array (high-volume, stream-only); this cap bounds the
1173
+ * remaining structural events (`agent_call_*`, `tool_call_*`, gate
1174
+ * events, pipeline, etc).
1175
+ *
1176
+ * Pathological workloads (e.g., 50 nested asks × 20-turn tool loops)
1177
+ * can otherwise accumulate tens of thousands of events totalling
1178
+ * hundreds of MB before the terminal `done` event fires. When the
1179
+ * cap is hit, further events are dropped from the array and a single
1180
+ * `log` event with `data.event === 'events_truncated'` is appended
1181
+ * recording the truncation. The trace channel (`runtime.on('trace')`)
1182
+ * and WS broadcast continue to receive every event — only the
1183
+ * in-memory `ExecutionInfo.events` array is bounded.
1184
+ *
1185
+ * Default: 50_000. Set to `Infinity` to disable the cap (legacy
1186
+ * behavior; only safe for short-lived executions). Must be a positive
1187
+ * integer or `Infinity`.
1188
+ */
1189
+ maxEventsPerExecution?: number;
908
1190
  };
909
1191
  /** Global defaults */
910
1192
  type DefaultsConfig = {
@@ -1200,13 +1482,17 @@ type WorkflowContextInit = {
1200
1482
  config: AxlConfig;
1201
1483
  providerRegistry: ProviderRegistry;
1202
1484
  sessionHistory?: ChatMessage[];
1203
- onTrace?: (event: TraceEvent) => void;
1204
- onToken?: (token: string) => void;
1485
+ onTrace?: (event: AxlEvent) => void;
1486
+ /** Per-token streaming callback. `meta` carries `askId`/`parentAskId`/
1487
+ * `depth`/`agent` so consumers can route or filter (e.g., `meta.depth === 0`
1488
+ * for root-only chat UIs). */
1489
+ onToken?: (token: string, meta: CallbackMeta) => void;
1490
+ /** Pre-execution tool-call callback. `meta` carries the ask correlation. */
1205
1491
  onToolCall?: (call: {
1206
1492
  name: string;
1207
1493
  args: unknown;
1208
1494
  callId?: string;
1209
- }) => void;
1495
+ }, meta: CallbackMeta) => void;
1210
1496
  pendingDecisions?: Map<string, (d: HumanDecision) => void>;
1211
1497
  budgetContext?: {
1212
1498
  totalCost: number;
@@ -1233,13 +1519,26 @@ type WorkflowContextInit = {
1233
1519
  onAgentStart?: (info: {
1234
1520
  agent: string;
1235
1521
  model: string;
1236
- }) => void;
1522
+ }, meta: CallbackMeta) => void;
1237
1523
  /** Callback fired after each ctx.ask() completes (once per ask invocation). */
1238
1524
  onAgentCallComplete?: (call: AgentCallInfo) => void;
1239
- /** Set by `createChildContext(parentToolCallId)` — stamped on every trace event
1240
- * emitted from this child so consumers can join nested agent calls back to
1241
- * the outer tool call that spawned them. */
1242
- parentToolCallId?: string;
1525
+ /** Internal: shared auto-checkpoint counters. Set by `createChildContext`
1526
+ * so parent + child share the same number space, preventing checkpoint
1527
+ * store key collisions across nested `WorkflowContext` instances.
1528
+ *
1529
+ * - `byAgent` — per-agent counter for `ctx.ask` auto-checkpoints; each
1530
+ * agent's first ask gets `0`, second gets `1`, etc. Composed name:
1531
+ * `__auto/<agent>/ask/<n>`. Reads naturally as "orchestrator's first
1532
+ * ask, second ask…" instead of a global ordinal.
1533
+ * - `root` — counter for `ctx.spawn` / `race` / `parallel` / `map`
1534
+ * wrappers, which don't have an agent context (they wrap arbitrary
1535
+ * fn callbacks). Composed name: `__auto/<primitive>/<n>`.
1536
+ *
1537
+ * Root contexts default to a fresh `{ byAgent: new Map(), root: 0 }`. */
1538
+ autoCheckpointCounters?: {
1539
+ byAgent: Map<string, number>;
1540
+ root: number;
1541
+ };
1243
1542
  };
1244
1543
  /**
1245
1544
  * The central coordination object for all Axl primitives.
@@ -1264,8 +1563,34 @@ declare class WorkflowContext<TInput = unknown> {
1264
1563
  private pendingDecisions?;
1265
1564
  private budgetContext?;
1266
1565
  private stateStore?;
1267
- private stepCounter;
1268
- private checkpointCounter;
1566
+ /** Root step counter for this execution. Inherited by every ctx.ask()
1567
+ * frame so all events from this WorkflowContext (root + nested) share
1568
+ * a single monotonic counter, even when concurrent branch primitives
1569
+ * fire asks before any single parent ask exists. Per-instance so
1570
+ * separate executions don't cross-talk. */
1571
+ private stepRefRoot;
1572
+ /**
1573
+ * Auto-checkpoint counters for internal `_checkpoint` callers. Held by
1574
+ * reference so child contexts created via `createChildContext` share
1575
+ * the same number space — otherwise a tool handler's nested `ctx.ask()`
1576
+ * (which auto-checkpoints) would collide with the parent's
1577
+ * auto-checkpoints in the state store. See `WorkflowContextInit
1578
+ * .autoCheckpointCounters` for the per-agent vs root split.
1579
+ * User-facing `ctx.checkpoint(name, fn)` requires an explicit name and
1580
+ * does NOT touch these counters — it goes straight to the store under
1581
+ * the caller-supplied key.
1582
+ */
1583
+ private autoCheckpointCounters;
1584
+ /** Idempotency guards for `workflow_start` / `workflow_end` emission.
1585
+ * Both `runtime.execute()` and `runtime.stream()` have paths where a
1586
+ * post-emit side-effect (checkpoint deletion, state-store persistence)
1587
+ * can throw AFTER `_emitWorkflowEnd({status: 'completed'})` has already
1588
+ * fired — the outer catch would then fire `_emitWorkflowEnd({status:
1589
+ * 'failed'})` for a second time. These flags make both emitters
1590
+ * single-fire so consumers never see paired-then-conflicting terminal
1591
+ * events for one execution. */
1592
+ private _workflowStartEmitted;
1593
+ private _workflowEndEmitted;
1269
1594
  private signal?;
1270
1595
  private summaryCache?;
1271
1596
  private workflowName?;
@@ -1277,27 +1602,46 @@ declare class WorkflowContext<TInput = unknown> {
1277
1602
  private awaitHumanHandler?;
1278
1603
  private onAgentStart?;
1279
1604
  private onAgentCallComplete?;
1280
- private parentToolCallId?;
1281
1605
  constructor(init: WorkflowContextInit);
1282
1606
  /**
1283
1607
  * Create a child context for nested agent invocations (e.g., agent-as-tool).
1284
1608
  * Shares: budget tracking, abort signals, trace emission, provider registry,
1285
1609
  * state store, span manager, memory manager, MCP manager, config,
1286
- * awaitHuman handler, pending decisions, tool overrides.
1287
- * Isolates: session history, step counter, streaming callbacks (onToken, onAgentStart, onToolCall).
1610
+ * awaitHuman handler, pending decisions, tool overrides, AND the
1611
+ * streaming callbacks (onToken / onAgentStart / onToolCall).
1612
+ * Isolates: session history.
1613
+ *
1614
+ * Streaming callbacks now propagate into nested asks because every callback
1615
+ * invocation carries `meta.askId`/`meta.parentAskId`/`meta.depth` so
1616
+ * consumers that want root-only behavior can filter on `meta.depth === 0`
1617
+ * instead of relying on the runtime to drop nested events. This is the
1618
+ * "nested ask visibility" fix from spec §3.2.
1288
1619
  *
1289
- * @param parentToolCallId - The `callId` of the outer `tool_call` that
1290
- * spawned this child. When set, every trace event emitted by the child
1291
- * will carry this id as `parentToolCallId`, so consumers can join nested
1292
- * agent calls back to the outer tool call that invoked them.
1620
+ * The shared step counter lives in `askStorage` (ALS), so there is no
1621
+ * per-context counter to pass through anymore. Nested events are
1622
+ * correlated to their parent via `parentAskId` (set on every nested ask
1623
+ * by ALS frame allocation) — the deprecated `parentToolCallId` field
1624
+ * was removed in 0.16.0.
1293
1625
  */
1294
- createChildContext(parentToolCallId?: string): WorkflowContext;
1626
+ createChildContext(): WorkflowContext;
1295
1627
  /**
1296
1628
  * Resolve the current abort signal.
1297
1629
  * Branch-scoped signals (from race/spawn/map/budget) in AsyncLocalStorage
1298
1630
  * take priority over the instance-level signal.
1299
1631
  */
1300
1632
  private get currentSignal();
1633
+ /**
1634
+ * Build a `CallbackMeta` for the current ask frame. Used at every
1635
+ * `onToken`/`onToolCall`/`onAgentStart` call site so consumers can
1636
+ * route or filter by ask correlation (`meta.depth === 0` for root
1637
+ * chat UIs, etc.).
1638
+ *
1639
+ * If the call is somehow outside an ask frame (e.g., a programmatic
1640
+ * `tool.run()` test harness), falls back to a synthetic meta keyed off
1641
+ * `executionId` so the type contract holds — consumers see `depth: 0`
1642
+ * and can ignore those events.
1643
+ */
1644
+ private currentCallbackMeta;
1301
1645
  ask<T = string>(agent: Agent, prompt: string, options?: AskOptions<T>): Promise<T>;
1302
1646
  private executeAgentCall;
1303
1647
  /**
@@ -1314,16 +1658,49 @@ declare class WorkflowContext<TInput = unknown> {
1314
1658
  /**
1315
1659
  * Execute a function with checkpoint-replay semantics.
1316
1660
  *
1317
- * On first execution, runs `fn()`, saves the result, and returns it.
1318
- * On replay (resume after restart), returns the saved result without re-executing.
1319
- * This prevents duplicate side effects (double API calls, double refunds, etc.).
1661
+ * On first execution, runs `fn()`, saves the result under `name`, and
1662
+ * returns it. On replay (resume after restart), returns the saved result
1663
+ * without re-executing. This prevents duplicate side effects (double API
1664
+ * calls, double refunds, etc.).
1665
+ *
1666
+ * `name` must be a stable, caller-supplied identifier — the same call
1667
+ * site MUST pass the same name across runs of the same execution for
1668
+ * replay to work. Names are scoped to a single execution and must be
1669
+ * unique within it; reusing a name (e.g. inside a loop) gives
1670
+ * last-write-wins behavior. For loops, compose names from the iterator:
1671
+ * `ctx.checkpoint(`process-${id}`, fn)`.
1672
+ *
1673
+ * Names starting with `__auto/` are reserved for runtime-internal
1674
+ * auto-checkpointing of `ctx.ask` / spawn / race / parallel / map.
1320
1675
  */
1321
- checkpoint<T>(fn: () => Promise<T>): Promise<T>;
1676
+ checkpoint<T>(name: string, fn: () => Promise<T>): Promise<T>;
1322
1677
  /**
1323
- * Internal checkpoint implementation shared by both the public checkpoint()
1324
- * and the automatic checkpointing in ask/spawn/race/parallel/map.
1678
+ * Internal checkpoint implementation shared by both the public
1679
+ * `checkpoint(name, fn)` and the automatic checkpointing in
1680
+ * ask/spawn/race/parallel/map. Internal callers pass an `__auto/...`
1681
+ * name composed by `_autoCheckpointName`.
1682
+ *
1683
+ * `agent` is the originating agent for ask auto-checkpoints — passing
1684
+ * it explicitly stamps `event.agent` on the trace events even though
1685
+ * the emit fires before/after `fn()` (where the ask's ALS frame
1686
+ * either doesn't exist yet or has already torn down). Without this,
1687
+ * post-fn `checkpoint_save` rows show no agent in the trace UI.
1325
1688
  */
1326
1689
  private _checkpoint;
1690
+ /** Generate a stable auto-checkpoint name for an internal primitive.
1691
+ *
1692
+ * For `ctx.ask`, the originating agent is known and gets a per-agent
1693
+ * counter — the resulting name reads as "orchestrator's first ask"
1694
+ * (`__auto/orchestrator-agent/ask/0`) instead of a global ordinal.
1695
+ * For `spawn` / `race` / `parallel` / `map`, no agent context is
1696
+ * available (those wrap arbitrary fn callbacks), so the name uses
1697
+ * the root counter: `__auto/spawn/0`, `__auto/race/3`, etc.
1698
+ *
1699
+ * All counters live on a shared ref passed through `createChildContext`,
1700
+ * so a tool handler's nested `ctx.ask()` (which runs in a child
1701
+ * WorkflowContext) can never collide with the parent's auto-names in
1702
+ * the state store. */
1703
+ private _autoCheckpointName;
1327
1704
  spawn<T>(n: number, fn: (index: number) => Promise<T>, options?: SpawnOptions): Promise<Result<T>[]>;
1328
1705
  private _spawnImpl;
1329
1706
  vote<T>(results: Result<T>[], options: VoteOptions<T>): T | Promise<T>;
@@ -1377,9 +1754,16 @@ declare class WorkflowContext<TInput = unknown> {
1377
1754
  * @internal
1378
1755
  */
1379
1756
  private _accumulateBudgetCost;
1380
- /** @internal */
1757
+ /** @internal — idempotent; no-ops on second+ call within one ctx. */
1381
1758
  _emitWorkflowStart(input: unknown): void;
1382
- /** @internal */
1759
+ /** @internal — idempotent; no-ops on second+ call within one ctx.
1760
+ * Protects against the post-emit-side-effect double-fire (reviewer
1761
+ * bug B1): `runtime.execute()` / `runtime.stream()` emit
1762
+ * `workflow_end(completed)`, then call `deleteCheckpoints` /
1763
+ * `persistExecution`. If either throws, the outer catch would
1764
+ * otherwise fire a second `workflow_end(failed)` with conflicting
1765
+ * status. First-wins semantics: the completed event stands, the
1766
+ * inner side-effect failure propagates as a normal thrown error. */
1383
1767
  _emitWorkflowEnd(info: {
1384
1768
  status: 'completed' | 'failed';
1385
1769
  duration: number;
@@ -1416,10 +1800,10 @@ declare class WorkflowContext<TInput = unknown> {
1416
1800
  /**
1417
1801
  * Internal emitter input — intentionally loose so call sites don't need to
1418
1802
  * build a perfectly-narrowed discriminated-union member. The resulting
1419
- * `TraceEvent` (exported type) remains strict, and TypeScript narrows it at
1803
+ * `AxlEvent` (exported type) remains strict, and TypeScript narrows it at
1420
1804
  * consumer sites via the `type` discriminator.
1421
1805
  */
1422
- private emitTrace;
1806
+ private emitEvent;
1423
1807
  }
1424
1808
 
1425
1809
  /** Retry policy for tool handlers */
@@ -1487,6 +1871,15 @@ type Workflow<TInput extends z.ZodType = z.ZodType, TOutput extends z.ZodType =
1487
1871
  readonly outputSchema: TOutput | undefined;
1488
1872
  readonly handler: (ctx: WorkflowContext<z.infer<TInput>>) => Promise<z.infer<TOutput>>;
1489
1873
  };
1874
+ /**
1875
+ * Bivariant-parameter alias for `Workflow<any, any>`. Function parameters
1876
+ * are contravariant under strict types, so `Workflow<TInput=MsgType>` won't
1877
+ * satisfy a `Workflow<unknown, unknown>` slot. The standard escape hatch is
1878
+ * to widen to `any`. This alias gives that workaround a name so call sites
1879
+ * (e.g. `AxlRuntime.register`, `AxlTestRuntime.WorkflowLike.handler`) don't
1880
+ * each independently re-derive it.
1881
+ */
1882
+ type AnyWorkflow = Workflow<any, any>;
1490
1883
  /**
1491
1884
  * Define a named workflow with Zod-validated input/output and an async handler.
1492
1885
  * Register workflows with `AxlRuntime.register()` to execute them.
@@ -1498,37 +1891,112 @@ declare function workflow<TInput extends z.ZodType, TOutput extends z.ZodType =
1498
1891
  /**
1499
1892
  * A streamable workflow execution.
1500
1893
  *
1501
- * Extends Node's Readable and implements AsyncIterable<StreamEvent>.
1502
- * Supports .on() events, for-await-of, .text iterator, and .pipe().
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()`.
1897
+ *
1898
+ * The wire carries `AxlEvent` directly — there is no per-stream synthesized
1899
+ * shape. Consumers narrow on `event.type` and use `AskScoped` fields
1900
+ * (`askId`, `parentAskId`, `depth`) for routing/filtering.
1503
1901
  */
1504
1902
  declare class AxlStream extends Readable {
1505
1903
  private bus;
1506
- private tokens;
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.
1915
+ *
1916
+ * On `pipeline(status: 'committed')` for an ask, that ask's
1917
+ * in-progress entry flushes to its committed entry. On
1918
+ * `pipeline(status: 'failed')` or `ask_end({ok:false})` for an ask,
1919
+ * that ask's in-progress entry is discarded. Spec/16 §4.3.
1920
+ *
1921
+ * Tokens emitted outside any ask (synthesized test fixtures with no
1922
+ * `askId`) fall back to an empty-string sentinel key — preserves the
1923
+ * single-buffer behavior for that legacy case.
1924
+ */
1925
+ private attemptByAsk;
1926
+ private committedByAsk;
1507
1927
  private result;
1508
1928
  private finished;
1509
1929
  readonly promise: Promise<unknown>;
1510
1930
  private eventQueue;
1511
1931
  private waiters;
1512
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. */
1513
1936
  private static readonly STREAM_EVENTS;
1514
1937
  on(event: string, handler: (...args: unknown[]) => void): this;
1515
1938
  off(event: string, handler: (...args: unknown[]) => void): this;
1516
1939
  [Symbol.asyncIterator](): {
1517
- next: () => Promise<IteratorResult<StreamEvent>>;
1940
+ next: () => Promise<IteratorResult<AxlEvent>>;
1518
1941
  [Symbol.asyncIterator](): /*elided*/ any;
1519
1942
  [Symbol.asyncDispose](): Promise<void>;
1520
1943
  };
1521
1944
  get text(): AsyncIterable<string>;
1522
- get steps(): AsyncIterable<StreamEvent>;
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
+ get textByAsk(): AsyncIterable<{
1957
+ askId: string;
1958
+ agent?: string;
1959
+ text: string;
1960
+ }>;
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
+ get lifecycle(): AsyncIterable<AxlEvent>;
1523
1969
  pipe<T extends NodeJS.WritableStream>(destination: T, options?: {
1524
1970
  end?: boolean;
1525
1971
  }): T;
1526
- /** Push a stream event. Called by the runtime. */
1527
- _push(event: StreamEvent): void;
1528
- /** Signal successful completion. */
1529
- _done(result: unknown): void;
1530
- /** Signal an error. */
1531
- _error(error: Error): void;
1972
+ /** Push an event onto the stream. Called by the runtime. */
1973
+ _push(event: AxlEvent): void;
1974
+ /**
1975
+ * Signal successful completion.
1976
+ *
1977
+ * `executionId` is required — the runtime must allocate it before
1978
+ * calling `stream()` so terminal events always carry a real id.
1979
+ * Previously the default-empty parameter surfaced blank executionIds
1980
+ * on error paths that threw before `execInfo` was assigned (review S4).
1981
+ */
1982
+ _done(result: unknown, executionId: string): void;
1983
+ /** Signal an error. `executionId` is required for the same reason as
1984
+ * `_done`: terminal events must carry a real id even when the
1985
+ * failure happens before any real trace event fires (review S4). */
1986
+ _error(error: Error, executionId: string): void;
1987
+ /** Concatenated root-only text from committed attempts plus the
1988
+ * current in-flight attempt(s). Retried (gate-rejected) attempts
1989
+ * are excluded — see spec/16 §4.3. Reading mid-attempt returns the
1990
+ * in-progress text; reading after `pipeline(committed)` (which
1991
+ * fires before `done`) returns the canonical winning text.
1992
+ *
1993
+ * With concurrent root-level asks (`ctx.parallel`, `ctx.spawn`,
1994
+ * `ctx.race`, `ctx.map`), each branch's tokens are scoped per-`askId`
1995
+ * and emitted contiguously in the order each ask first started
1996
+ * emitting tokens. A `pipeline(failed)` or `ask_end({ok:false})` on
1997
+ * one branch only discards THAT branch's in-progress buffer —
1998
+ * sibling branches are unaffected. For UIs that want each sub-agent
1999
+ * in its own lane, prefer `textByAsk`. */
1532
2000
  get fullText(): string;
1533
2001
  }
1534
2002
 
@@ -1597,8 +2065,23 @@ type CreateContextOptions = {
1597
2065
  signal?: AbortSignal;
1598
2066
  /** Prior conversation history for multi-turn eval testing. */
1599
2067
  sessionHistory?: ChatMessage[];
1600
- /** Token streaming callback. */
1601
- onToken?: (token: string) => void;
2068
+ /** Token streaming callback. `meta` carries `askId`/`parentAskId`/`depth`/`agent`
2069
+ * so consumers can route or filter by ask correlation (e.g.,
2070
+ * `meta.depth === 0` for root-only chat UIs). */
2071
+ onToken?: (token: string, meta: CallbackMeta) => void;
2072
+ /** Fires when an agent invokes a tool. `meta` carries the same ask
2073
+ * correlation shape as `onToken`. */
2074
+ onToolCall?: (call: {
2075
+ name: string;
2076
+ args: unknown;
2077
+ callId?: string;
2078
+ }, meta: CallbackMeta) => void;
2079
+ /** Fires when an agent begins processing a turn. `meta` carries the
2080
+ * same ask correlation shape as `onToken`. */
2081
+ onAgentStart?: (info: {
2082
+ agent: string;
2083
+ model?: string;
2084
+ }, meta: CallbackMeta) => void;
1602
2085
  /** Handler for tool approval requests. Called when an agent invokes a tool with requireApproval. */
1603
2086
  awaitHumanHandler?: (options: AwaitHumanOptions) => Promise<HumanDecision>;
1604
2087
  };
@@ -1626,6 +2109,10 @@ declare class AxlRuntime extends EventEmitter {
1626
2109
  private historicalExecutionsLoadPromise;
1627
2110
  private evalHistory;
1628
2111
  private evalHistoryLoadPromise;
2112
+ /** Resolved cap on `ExecutionInfo.events` per execution. Cached on
2113
+ * the instance so per-event onTrace handlers don't reach into
2114
+ * `this.config.state` on every emission. */
2115
+ private readonly maxEventsPerExecution;
1629
2116
  constructor(config?: AxlConfig);
1630
2117
  /**
1631
2118
  * Whether `config.trace.redact` is enabled on this runtime. A narrow
@@ -1654,13 +2141,39 @@ declare class AxlRuntime extends EventEmitter {
1654
2141
  /** Get the MCP manager (if initialized). */
1655
2142
  getMcpManager(): McpManager | undefined;
1656
2143
  private createStateStore;
2144
+ /**
2145
+ * Run a workflow body inside an ALREADY-CONSTRUCTED `WorkflowContext`
2146
+ * with full lifecycle invariants: emit `workflow_start` → run handler
2147
+ * → parse output → emit `workflow_end` → delete checkpoints → persist.
2148
+ *
2149
+ * Shared between `execute()` and `stream()`. Centralizes the
2150
+ * "start iff end" pairing invariant so a future emit path
2151
+ * (e.g., `resumeExecution`) can inherit correct behavior by routing
2152
+ * through this helper. Also factors out the AbortError detection.
2153
+ *
2154
+ * Side effects on `execInfo`: sets `status` / `completedAt` /
2155
+ * `duration` / `result` (or `error`) before emitting `workflow_end`.
2156
+ * On throw: emits `workflow_end({status: 'failed', aborted?})` and
2157
+ * persists before rethrowing. `abortControllers.delete` runs in
2158
+ * `finally` regardless of outcome.
2159
+ *
2160
+ * Caller responsibilities:
2161
+ * - Allocate `executionId` and register it in `abortControllers` map
2162
+ * BEFORE calling this helper (so early-throw paths still have a
2163
+ * correlation id available for terminal events).
2164
+ * - Construct `execInfo` and `ctx` before calling.
2165
+ * - Pass `span` if the call site is inside a tracer span — this
2166
+ * helper sets `axl.workflow.cost` / `.duration` attributes on
2167
+ * success.
2168
+ */
2169
+ private runWorkflowBody;
1657
2170
  /**
1658
2171
  * Persist a completed/failed execution to the state store (fire-and-forget)
1659
2172
  * and move it from the active executions map to the historical cache.
1660
2173
  */
1661
2174
  private persistExecution;
1662
2175
  /** Register a workflow with the runtime. */
1663
- register(workflow: Workflow<any, any>): void;
2176
+ register(workflow: AnyWorkflow): void;
1664
2177
  /** Register standalone tools for Studio introspection and direct testing. */
1665
2178
  registerTool(...tools: Tool<any, any>[]): void;
1666
2179
  /** Register standalone agents for Studio playground and introspection. */
@@ -1716,7 +2229,7 @@ declare class AxlRuntime extends EventEmitter {
1716
2229
  * When `true`, populate `EvalItem.traces` on every item (success + failure
1717
2230
  * paths). Forwards to `runEval({ captureTraces: true })`, which wraps each
1718
2231
  * item's execution in `runtime.trackExecution({ captureTraces: true })`.
1719
- * Verbose-mode `agent_call.data.messages` snapshots are stripped from
2232
+ * Verbose-mode `agent_call_start.data.messages` snapshots are stripped from
1720
2233
  * captured traces to keep memory bounded.
1721
2234
  */
1722
2235
  captureTraces?: boolean;
@@ -1841,11 +2354,11 @@ declare class AxlRuntime extends EventEmitter {
1841
2354
  * need embedder token counts, subscribe to `runtime.on('trace', ...)` and
1842
2355
  * read `data.usage.tokens` on `memory_remember` / `memory_recall` events.
1843
2356
  *
1844
- * Pass `{ captureTraces: true }` to also collect the raw `TraceEvent[]` observed
2357
+ * Pass `{ captureTraces: true }` to also collect the raw `AxlEvent[]` observed
1845
2358
  * during `fn()`. This is opt-in because it keeps every event in memory for the
1846
2359
  * duration of the call — useful for eval per-item capture, debugging, and test
1847
2360
  * assertions, but overhead grows with trace volume. When enabled, verbose-mode
1848
- * `agent_call.data.messages` snapshots are omitted from captured events (still
2361
+ * `agent_call_start.data.messages` snapshots are omitted from captured events (still
1849
2362
  * broadcast via onTrace) to keep memory bounded — callers who need the full
1850
2363
  * verbose snapshot should subscribe to `runtime.on('trace', ...)` directly.
1851
2364
  *
@@ -1856,7 +2369,7 @@ declare class AxlRuntime extends EventEmitter {
1856
2369
  }): Promise<{
1857
2370
  result: T;
1858
2371
  cost: number;
1859
- traces?: TraceEvent[];
2372
+ traces?: AxlEvent[];
1860
2373
  metadata: {
1861
2374
  models: string[];
1862
2375
  modelCallCounts?: Record<string, number>;
@@ -1891,8 +2404,8 @@ declare class AxlRuntime extends EventEmitter {
1891
2404
  * The emit('trace', event) call happens before this method is called, so
1892
2405
  * programmatic subscribers always receive events regardless of trace config.
1893
2406
  */
1894
- private outputTraceEvent;
1895
- private logTraceEvent;
2407
+ private outputAxlEvent;
2408
+ private logAxlEvent;
1896
2409
  /**
1897
2410
  * Append a handoff record to session metadata.
1898
2411
  * Note: The read-modify-write is not atomic. Concurrent handoffs in the same
@@ -1901,8 +2414,177 @@ declare class AxlRuntime extends EventEmitter {
1901
2414
  * cross-execution concurrency on the same session, which is unlikely.
1902
2415
  */
1903
2416
  private appendHandoffRecord;
2417
+ /**
2418
+ * Patch `duration` on the handoff record matching `toAskId` (the target
2419
+ * frame's askId, which the runtime stamps on `handoff_start` and which
2420
+ * appears as `askId` on the target's `ask_end`).
2421
+ *
2422
+ * Called from two places per handoff:
2423
+ * - target's `ask_end` — first-write source for ONEWAY (no return event).
2424
+ * - `handoff_return` — overwrites for ROUNDTRIP with the round-trip
2425
+ * wall-clock figure (intentional: round-trip
2426
+ * duration is the more useful metric for the
2427
+ * source-paused-for-N-ms semantic).
2428
+ *
2429
+ * No-op if no record matches `toAskId` (e.g., stream subscribed mid-
2430
+ * handoff, or session metadata got wiped) — `duration` stays undefined,
2431
+ * which is the documented default for `HandoffRecord`.
2432
+ */
2433
+ private updateHandoffDuration;
1904
2434
  }
1905
2435
 
2436
+ /**
2437
+ * Shared helpers for working with `AxlEvent` streams.
2438
+ *
2439
+ * Consumers writing their own accumulators / reducers reach for these so
2440
+ * they don't have to re-derive spec invariants at every call site. The
2441
+ * core runtime, AxlTestRuntime, and Studio's cost reducer ALL use these —
2442
+ * keeping one source of truth for each invariant.
2443
+ */
2444
+
2445
+ /**
2446
+ * Variants that directly carry an authoritative cost charge from the
2447
+ * provider (or embedder). `ask_end` is a per-ask ROLLUP of these leaves
2448
+ * — counting it alongside them would double-charge. Any future
2449
+ * cost-emitting variant MUST be added here AND NOT be a rollup.
2450
+ *
2451
+ * Treated as a `Set` for O(1) membership checks; exported `as const`
2452
+ * tuple so exhaustiveness fixtures can cross-check against
2453
+ * `AXL_EVENT_TYPES`.
2454
+ */
2455
+ declare const COST_BEARING_LEAF_TYPES: readonly ["agent_call_end", "tool_call_end", "memory_remember", "memory_recall"];
2456
+ /**
2457
+ * Cost contribution of `event` to a running total.
2458
+ *
2459
+ * Returns the event's `cost` if it's a cost-bearing leaf; 0 otherwise.
2460
+ * The key invariant (spec/16 decision 10): `ask_end` events carry a
2461
+ * per-ask ROLLUP of leaf costs that already passed through consumer
2462
+ * accumulators, so they contribute 0 here to avoid double-counting.
2463
+ *
2464
+ * Use this at every place you'd write `total += event.cost`:
2465
+ *
2466
+ * for (const ev of info.events) total += eventCostContribution(ev);
2467
+ *
2468
+ * Consumers who want the authoritative per-ask rollup read
2469
+ * `ask_end.cost` directly — that field is populated whether or not
2470
+ * the leaf events were summed separately. Spec decision 10.
2471
+ */
2472
+ declare function eventCostContribution(event: AxlEvent): number;
2473
+ /**
2474
+ * True when the event is a cost-bearing leaf (contributes directly to
2475
+ * totals) — used by internal emitters that need to know whether to
2476
+ * bump per-frame askCost rollups.
2477
+ *
2478
+ * Distinct from `eventCostContribution(e) > 0`: an `agent_call_end`
2479
+ * with `cost: 0` is still a leaf (produced by a free / cached turn),
2480
+ * just one that happens to contribute zero.
2481
+ */
2482
+ declare function isCostBearingLeaf(event: AxlEvent): boolean;
2483
+ /**
2484
+ * True when the event originates from the root ask (`depth === 0`)
2485
+ * or has no ask correlation at all (workflow / done / error / log).
2486
+ *
2487
+ * Used by consumers that want "just the chat bubble" tokens or
2488
+ * "just the root ask" visuals. Nested-ask UIs drop this filter.
2489
+ *
2490
+ * `depth` is coerced through `?? 0` so events without the field
2491
+ * (out-of-ask lifecycle events, synthesized terminals) are treated
2492
+ * as root-level — they're never at depth ≥ 1.
2493
+ */
2494
+ declare function isRootLevel(event: AxlEvent): boolean;
2495
+
2496
+ /**
2497
+ * Tolerant JSON parser for progressive structured-output streaming.
2498
+ *
2499
+ * Accepts partial / truncated JSON and returns the longest parseable prefix
2500
+ * as a structured value. Used by `ctx.ask({ schema })` while streaming so
2501
+ * consumers can render fields as they arrive — see `partial_object` AxlEvent.
2502
+ *
2503
+ * Scope (intentional minimum):
2504
+ * - Single-pass tokenizer + recursive-descent state machine.
2505
+ * - Only TRAILING truncation is tolerated. Mid-document malformed input
2506
+ * throws. (A consumer rendering progressive output sees one well-formed
2507
+ * subtree at a time, never bogus shape changes.)
2508
+ * - Supports objects, arrays, strings (with `\"` `\\` `\n` `\t` `\r` `\b`
2509
+ * `\f` `\/` `\uXXXX`), numbers (int/float/exp/negative), `true` /
2510
+ * `false` / `null`.
2511
+ * - Rejects: trailing commas, comments, unquoted keys.
2512
+ *
2513
+ * Truncation recovery:
2514
+ * - Unterminated string → return content parsed so far.
2515
+ * - Unterminated number → return valid prefix if it's a complete number;
2516
+ * drop the key/value if the prefix is malformed
2517
+ * (`-`, `1.`, `1e` → drop).
2518
+ * - Unclosed `[` → array with parsed elements.
2519
+ * - Unclosed `{` → object with parsed pairs. A key without a value
2520
+ * is dropped.
2521
+ * - `,` or `:` at EOF → ignored; the last complete value wins.
2522
+ *
2523
+ * Zero runtime deps. ~250 LOC.
2524
+ */
2525
+ /** Parse a JSON-shaped string that may be truncated mid-stream. */
2526
+ declare function parsePartialJson(input: string): unknown;
2527
+
2528
+ /**
2529
+ * Per-variant redaction rules for `AxlEvent`.
2530
+ *
2531
+ * `config.trace.redact` is an observability-boundary filter that scrubs
2532
+ * user/LLM content from events before they reach consumers. Two layers
2533
+ * historically applied this filter independently:
2534
+ *
2535
+ * 1. Core: `WorkflowContext.emitEvent()` — emit-time scrub before `onTrace`.
2536
+ * 2. Studio: `redactStreamEvent()` — wire-boundary scrub before WS broadcast
2537
+ * and again at REST serialization (`redactExecutionInfo`).
2538
+ *
2539
+ * Both layers walked the same `AxlEvent` discriminated union, with separate
2540
+ * if/else and switch/case ladders. Adding a new variant required updating
2541
+ * both. They had drifted: WS missed `workflow_start`/`workflow_end`,
2542
+ * `log`, and the full `memory_*` walk that core applied. Defense-in-depth
2543
+ * also broke for runtimes that flipped `redact` after emitting events —
2544
+ * REST reads of stored `ExecutionInfo.events` would leak fields the WS
2545
+ * layer didn't know to scrub.
2546
+ *
2547
+ * `REDACTION_RULES` is the single source of truth: a `Record<AxlEventType,
2548
+ * RuleFor<...>>` so adding a new variant to `AXL_EVENT_TYPES` without a
2549
+ * corresponding entry is a typecheck error. Every rule is pure (no
2550
+ * mutation), so the same rule runs at emit time and at the wire boundary
2551
+ * with identical results.
2552
+ *
2553
+ * Numeric/structural fields (`cost`, `tokens`, `duration`, `askId`,
2554
+ * `parentAskId`, `depth`, `agent`, `executionId`, `step`, `timestamp`,
2555
+ * `callId`, `tool`, `model`, `workflow`) are NEVER scrubbed — they're
2556
+ * non-PII observability metadata and load-bearing for cost rails. Rules
2557
+ * preserve these by spreading `{ ...event }` and only overriding the
2558
+ * specific user-content field(s) for that variant.
2559
+ */
2560
+
2561
+ declare const REDACTED = "[redacted]";
2562
+ /** Type-safe rule signature: takes a specific event variant, returns the same variant. */
2563
+ type RuleFor<T extends AxlEventType> = (event: AxlEventOf<T>) => AxlEventOf<T>;
2564
+ /**
2565
+ * Per-variant rule table. Adding a new variant to `AXL_EVENT_TYPES`
2566
+ * without a corresponding entry here is a typecheck error — the
2567
+ * `Record<AxlEventType, ...>` mapped type forces exhaustiveness.
2568
+ *
2569
+ * For variants that ship no user content today (`delegate`,
2570
+ * `handoff_return`, `checkpoint_*`), the rule is `passthrough` — but
2571
+ * that's still an explicit, reviewed decision rather than a
2572
+ * silent omission.
2573
+ */
2574
+ declare const REDACTION_RULES: {
2575
+ [K in AxlEventType]: RuleFor<K>;
2576
+ };
2577
+ /**
2578
+ * Apply the redaction rule for `event.type` and return a new event with
2579
+ * user-content fields scrubbed. Pure: never mutates the input.
2580
+ *
2581
+ * Caller responsibility: only invoke when redaction is enabled. The rules
2582
+ * unconditionally scrub — they don't gate on `config.trace.redact`. Both
2583
+ * `WorkflowContext.emitEvent` (core) and `redactStreamEvent` (Studio)
2584
+ * check the flag before calling this function.
2585
+ */
2586
+ declare function redactEvent(event: AxlEvent): AxlEvent;
2587
+
1906
2588
  /** Base error class for all Axl errors */
1907
2589
  declare class AxlError extends Error {
1908
2590
  readonly code: string;
@@ -2160,12 +2842,8 @@ declare class MemoryStore implements StateStore {
2160
2842
  private executionHistory;
2161
2843
  private evalHistory;
2162
2844
  constructor();
2163
- saveCheckpoint(executionId: string, step: number, data: unknown): Promise<void>;
2164
- getCheckpoint(executionId: string, step: number): Promise<unknown | null>;
2165
- getLatestCheckpoint(executionId: string): Promise<{
2166
- step: number;
2167
- data: unknown;
2168
- } | null>;
2845
+ saveCheckpoint(executionId: string, name: string, data: unknown): Promise<void>;
2846
+ getCheckpoint(executionId: string, name: string): Promise<unknown | null>;
2169
2847
  saveSession(sessionId: string, history: ChatMessage[]): Promise<void>;
2170
2848
  getSession(sessionId: string): Promise<ChatMessage[]>;
2171
2849
  deleteSession(sessionId: string): Promise<void>;
@@ -2209,13 +2887,44 @@ declare class MemoryStore implements StateStore {
2209
2887
  declare class SQLiteStore implements StateStore {
2210
2888
  private db;
2211
2889
  constructor(filePath: string);
2890
+ /**
2891
+ * Idempotent schema migration. Tracks version via `PRAGMA user_version`
2892
+ * so reopens skip applied steps. Wrapped in `BEGIN IMMEDIATE` so
2893
+ * concurrent `SQLiteStore` constructors serialize at the file level
2894
+ * and rollback on failure leaves the DB in a clean pre-migration
2895
+ * state.
2896
+ *
2897
+ * Migration history:
2898
+ * - v1 (spec/16): rename `execution_history.steps` → `events`. The
2899
+ * JS-level `ExecutionInfo.steps` was renamed to `.events` in PR 1
2900
+ * commit 1; this brings the on-disk schema in line.
2901
+ * - v2 (named checkpoints): rename `checkpoints.step` (INTEGER) →
2902
+ * `checkpoints.name` (TEXT). The runtime now requires
2903
+ * `ctx.checkpoint(name, fn)` so the per-context numeric counter is
2904
+ * gone — names are caller-supplied stable identifiers. Existing
2905
+ * numeric step values become stringified names ("0", "1", …) to
2906
+ * preserve replay continuity for in-flight executions across the
2907
+ * upgrade boundary.
2908
+ *
2909
+ * Applied BEFORE `initTables()` so the subsequent `CREATE TABLE
2910
+ * IF NOT EXISTS` runs against the post-migration column name. Fresh
2911
+ * installs just create the table with `events` directly and bump
2912
+ * `user_version` to the current version.
2913
+ *
2914
+ * TOCTOU guard (review B-3): the initial `user_version` read is
2915
+ * informational only — a concurrent process can complete the
2916
+ * migration between that read and our `BEGIN IMMEDIATE`. We
2917
+ * re-read `user_version` inside the transaction and short-circuit
2918
+ * if it's already at target, so the non-idempotent half (writes
2919
+ * that went beyond a simple ALTER) never double-applies under a
2920
+ * race. The current v0→v1 step IS idempotent via the column-
2921
+ * presence check, but future migrations may not be, and the
2922
+ * transactional re-read costs nothing.
2923
+ */
2924
+ private migrate;
2212
2925
  private initTables;
2213
- saveCheckpoint(executionId: string, step: number, data: unknown): Promise<void>;
2214
- getCheckpoint(executionId: string, step: number): Promise<unknown | null>;
2215
- getLatestCheckpoint(executionId: string): Promise<{
2216
- step: number;
2217
- data: unknown;
2218
- } | null>;
2926
+ saveCheckpoint(executionId: string, name: string, data: unknown): Promise<void>;
2927
+ getCheckpoint(executionId: string, name: string): Promise<unknown | null>;
2219
2928
  saveSession(sessionId: string, history: ChatMessage[]): Promise<void>;
2220
2929
  getSession(sessionId: string): Promise<ChatMessage[]>;
2221
2930
  deleteSession(sessionId: string): Promise<void>;
@@ -2274,12 +2983,8 @@ declare class RedisStore implements StateStore {
2274
2983
  private execHistorySetKey;
2275
2984
  private evalHistoryKey;
2276
2985
  private evalHistorySetKey;
2277
- saveCheckpoint(executionId: string, step: number, data: unknown): Promise<void>;
2278
- getCheckpoint(executionId: string, step: number): Promise<unknown | null>;
2279
- getLatestCheckpoint(executionId: string): Promise<{
2280
- step: number;
2281
- data: unknown;
2282
- } | null>;
2986
+ saveCheckpoint(executionId: string, name: string, data: unknown): Promise<void>;
2987
+ getCheckpoint(executionId: string, name: string): Promise<unknown | null>;
2283
2988
  saveSession(sessionId: string, history: ChatMessage[]): Promise<void>;
2284
2989
  getSession(sessionId: string): Promise<ChatMessage[]>;
2285
2990
  deleteSession(sessionId: string): Promise<void>;
@@ -2369,4 +3074,4 @@ declare class NoopSpanManager implements SpanManager {
2369
3074
  */
2370
3075
  declare function createSpanManager(config?: TelemetryConfig): Promise<SpanManager>;
2371
3076
 
2372
- export { type Agent, type AgentCallInfo, type AgentCallTraceData, type AgentConfig, AnthropicProvider, type AskOptions, type AwaitHumanOptions, type AxlConfig, AxlError, AxlRuntime, AxlStream, BudgetExceededError, type BudgetOptions, type BudgetResult, type ChatMessage, type ChatOptions, type CreateContextOptions, type DelegateOptions, type DelegateTraceData, type Effort, type EmbedResult, type EmbedUsage, type Embedder, type EvalHistoryEntry, type ExecuteOptions, type ExecutionInfo, type ExecutionState, GeminiProvider, type GuardrailBlockHandler, GuardrailError, type GuardrailResult, type GuardrailTraceData, type GuardrailsConfig, type HandoffDescriptor, type HandoffRecord, type HandoffTraceData, type HumanDecision, InMemoryVectorStore, type InputGuardrail, type MapOptions, MaxTurnsError, McpManager, type McpServer, type McpServerConfig, type McpToolDefinition, type McpToolResult, type MemoryConfig, MemoryManager, MemoryStore, NoConsensus, NoopSpanManager, OpenAIEmbedder, OpenAIProvider, OpenAIResponsesProvider, type OutputGuardrail, type OutputValidator, type PendingDecision, type Provider, type ProviderAdapter, ProviderRegistry, type ProviderResponse, QuorumNotMet, type RaceOptions, type RecallOptions, type RecallResult, RedisStore, type RememberOptions, type RememberResult, type ResolvedThinkingOptions, type Result, type RetryPolicy, SQLiteStore, type SchemaCheckTraceData, Session, type SessionOptions, type SpanHandle, type SpanManager, type SpawnOptions, SqliteVectorStore, type StateStore, type StreamChunk, type StreamEvent, type TelemetryConfig, TimeoutError, type Tool, type ToolApprovalTraceData, type ToolCallMessage, type ToolCallTraceData, type ToolChoice, type ToolConfig, ToolDenied, type ToolHooks, type TraceEvent, type TraceEventType, type ValidateResult, type ValidateTraceData, ValidationError, type VectorEntry, type VectorResult, type VectorStore, VerifyError, type VerifyOptions, type VerifyRetry, type VerifyTraceData, type VoteOptions, type Workflow, type WorkflowConfig, WorkflowContext, type WorkflowContextInit, type WorkflowEndTraceData, type WorkflowStartTraceData, agent, createSpanManager, defineConfig, extractJson, resolveThinkingOptions, tool, workflow, zodToJsonSchema };
3077
+ 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, 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 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, extractJson, isCostBearingLeaf, isRootLevel, parsePartialJson, redactEvent, resolveThinkingOptions, tool, workflow, zodToJsonSchema };