@axlsdk/axl 0.14.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,26 +274,493 @@ type RaceOptions<T = unknown> = {
274
274
  };
275
275
  /** Execution status */
276
276
  type ExecutionStatus = 'running' | 'completed' | 'failed' | 'waiting';
277
- /** Trace event */
278
- type TraceEvent = {
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 = {
302
+ /** Original user prompt passed to `ctx.ask()`. Does not include retry feedback or tool results. */
303
+ prompt: string;
304
+ /** Resolved system prompt (after evaluating dynamic system selectors). */
305
+ system?: string;
306
+ /** Resolved model parameters sent to the provider for this call. */
307
+ params?: AgentCallParams;
308
+ /** 1-indexed iteration of the tool-calling loop for this `ctx.ask()` call. */
309
+ turn: number;
310
+ /** When set, this call is a retry triggered by a failed gate check on the previous turn. */
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[];
314
+ /** Full ChatMessage[] sent to the provider this turn. Only populated when `trace.level === 'full'`. */
315
+ messages?: ChatMessage[];
316
+ };
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;
341
+ };
342
+ /** Data shape for `tool_call_end` events. */
343
+ type ToolCallData = {
344
+ args: unknown;
345
+ result: unknown;
346
+ callId?: string;
347
+ };
348
+ /** Data shape for `tool_call_start` events. */
349
+ type ToolCallStartData = {
350
+ args: unknown;
351
+ };
352
+ /** Data shape for `tool_approval` events. Emitted by the approval gate on both outcomes. */
353
+ type ToolApprovalData = {
354
+ approved: boolean;
355
+ args: unknown;
356
+ reason?: string;
357
+ };
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;
362
+ callId?: string;
363
+ };
364
+ /** Data shape for `handoff_start` events (always emitted, pre-transition). */
365
+ type HandoffStartData = {
366
+ source: string;
367
+ target: string;
368
+ mode: 'oneway' | 'roundtrip';
369
+ /** The `message` arg the source agent passed when invoking `handoff_to_X`
370
+ * (roundtrip mode only). Subject to `config.trace.redact`. */
371
+ message?: string;
372
+ };
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 = {
385
+ candidates: string[];
386
+ /** Set when the decision is known at emission time (single-agent short-circuit). */
387
+ selected?: string;
388
+ /** Router model used for multi-agent routing. */
389
+ routerModel?: string;
390
+ /** Why this delegate was emitted: 'routed' (multi-agent) or 'single_candidate'. */
391
+ reason: 'routed' | 'single_candidate';
392
+ };
393
+ /** Data shape for `verify` events. */
394
+ type VerifyData = {
395
+ attempts: number;
396
+ passed: boolean;
397
+ lastError?: string;
398
+ };
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 = {
426
+ /** The validated input passed to the workflow handler. */
427
+ input: unknown;
428
+ };
429
+ /** Data shape for `workflow_end` events. Emitted once per workflow execution
430
+ * on completion, failure, or cancellation. Distinguish cancellation via `aborted`. */
431
+ type WorkflowEndData = {
432
+ status: 'completed' | 'failed';
433
+ duration: number;
434
+ /** Workflow return value. Present on `status: 'completed'`. */
435
+ result?: unknown;
436
+ /** Error message. Present on `status: 'failed'`. */
437
+ error?: string;
438
+ /** True when the failure was an `AbortError` (user cancellation, budget hard_stop,
439
+ * or consumer disconnect on streaming workflows). */
440
+ aborted?: boolean;
441
+ };
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 = {
279
494
  executionId: string;
495
+ /** Monotonic per-execution step counter, shared across nested asks via ALS. */
280
496
  step: number;
281
- type: 'agent_call' | 'tool_call' | 'verify' | 'handoff' | 'delegate' | 'tool_denied' | 'log' | 'workflow_start' | 'workflow_end' | 'guardrail' | 'validate';
497
+ /** Wall-clock ms. */
498
+ timestamp: number;
499
+ /** Workflow this event belongs to. Auto-stamped by `emitEvent` from
500
+ * `this.workflowName` when defined; callers may override. */
282
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. */
283
507
  agent?: string;
284
- tool?: string;
285
- promptVersion?: string;
508
+ /** Optional model URI — set on agent-related events; ignored on others. */
286
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
+ */
287
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). */
288
545
  tokens?: {
289
546
  input?: number;
290
547
  output?: number;
291
548
  reasoning?: number;
292
549
  };
550
+ /** Duration in ms (set on `_end` variants and a few single-point events). */
293
551
  duration?: number;
294
- data?: unknown;
295
- timestamp: number;
296
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;
579
+ };
580
+ /**
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.
597
+ */
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';
651
+ tool: string;
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 & {
662
+ type: 'tool_approval';
663
+ tool: string;
664
+ callId?: string;
665
+ data: ToolApprovalData;
666
+ }) | (AxlEventBase & AskScoped & {
667
+ type: 'tool_denied';
668
+ tool: string;
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> & {
724
+ type: 'guardrail';
725
+ data?: GuardrailData;
726
+ }) | (AxlEventBase & Partial<AskScoped> & {
727
+ type: 'schema_check';
728
+ data?: SchemaCheckData;
729
+ }) | (AxlEventBase & Partial<AskScoped> & {
730
+ type: 'validate';
731
+ data?: ValidateData;
732
+ }) | (AxlEventBase & Partial<AskScoped> & {
733
+ type: 'log';
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
+ };
759
+ });
760
+ /** Convenience: extract the union member matching a given `type` discriminator. */
761
+ type AxlEventOf<T extends AxlEventType> = Extract<AxlEvent, {
762
+ type: T;
763
+ }>;
297
764
  /** Result of a guardrail check. */
298
765
  type GuardrailResult = {
299
766
  block: boolean;
@@ -335,7 +802,10 @@ type ExecutionInfo = {
335
802
  executionId: string;
336
803
  workflow: string;
337
804
  status: ExecutionStatus;
338
- 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[];
339
809
  totalCost: number;
340
810
  startedAt: number;
341
811
  completedAt?: number;
@@ -343,57 +813,26 @@ type ExecutionInfo = {
343
813
  result?: unknown;
344
814
  error?: string;
345
815
  };
346
- type StreamEvent = {
347
- type: 'token';
348
- data: string;
349
- } | {
350
- type: 'tool_call';
351
- name: string;
352
- args: unknown;
353
- callId?: string;
354
- } | {
355
- type: 'tool_result';
356
- name: string;
357
- result: unknown;
358
- callId?: string;
359
- } | {
360
- type: 'tool_approval';
361
- name: string;
362
- args: unknown;
363
- approved: boolean;
364
- reason?: string;
365
- } | {
366
- type: 'agent_start';
367
- agent: string;
368
- model?: string;
369
- } | {
370
- type: 'agent_end';
371
- agent: string;
372
- cost?: number;
373
- duration?: number;
374
- } | {
375
- type: 'handoff';
376
- source: string;
377
- target: string;
378
- mode?: 'oneway' | 'roundtrip';
379
- } | {
380
- type: 'step';
381
- step: number;
382
- data: unknown;
383
- } | {
384
- type: 'done';
385
- data: unknown;
386
- } | {
387
- type: 'error';
388
- message: string;
389
- };
390
- /** 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. */
391
829
  type HandoffRecord = {
392
830
  source: string;
393
831
  target: string;
394
832
  mode: 'oneway' | 'roundtrip';
395
833
  timestamp: number;
396
834
  duration?: number;
835
+ toAskId?: string;
397
836
  };
398
837
  /** Information about a completed agent call, emitted via onAgentCallComplete. */
399
838
  type AgentCallInfo = {
@@ -533,12 +972,8 @@ type ExecutionState = {
533
972
  * Redis (production).
534
973
  */
535
974
  interface StateStore {
536
- saveCheckpoint(executionId: string, step: number, data: unknown): Promise<void>;
537
- getCheckpoint(executionId: string, step: number): Promise<unknown | null>;
538
- getLatestCheckpoint(executionId: string): Promise<{
539
- step: number;
540
- data: unknown;
541
- } | null>;
975
+ saveCheckpoint(executionId: string, name: string, data: unknown): Promise<void>;
976
+ getCheckpoint(executionId: string, name: string): Promise<unknown | null>;
542
977
  saveSession(sessionId: string, history: ChatMessage[]): Promise<void>;
543
978
  getSession(sessionId: string): Promise<ChatMessage[]>;
544
979
  deleteSession(sessionId: string): Promise<void>;
@@ -651,9 +1086,49 @@ interface VectorStore {
651
1086
  delete(ids: string[]): Promise<void>;
652
1087
  close?(): Promise<void>;
653
1088
  }
1089
+ /**
1090
+ * Usage info reported by an embedder for a single `embed()` call.
1091
+ *
1092
+ * Mirrors the provider `usage` namespace — nested rather than flat so
1093
+ * future additions (latency, cache-hit info, rate-limit headers) extend
1094
+ * this type without breaking the `Embedder` return shape.
1095
+ *
1096
+ * All fields are optional so embedders without cost/pricing knowledge
1097
+ * (e.g. local models, self-hosted) can still conform to the interface.
1098
+ */
1099
+ type EmbedUsage = {
1100
+ /** Total input tokens consumed. */
1101
+ tokens?: number;
1102
+ /** Cost in USD, computed from tokens × per-model pricing. */
1103
+ cost?: number;
1104
+ /** The underlying model identifier (e.g. "text-embedding-3-small"). */
1105
+ model?: string;
1106
+ };
1107
+ /**
1108
+ * Result of an `Embedder.embed()` call.
1109
+ *
1110
+ * Always contains `vectors`. `usage` is optional so embedders can omit
1111
+ * it entirely (non-OpenAI, local, test fakes).
1112
+ */
1113
+ type EmbedResult = {
1114
+ vectors: number[][];
1115
+ usage?: EmbedUsage;
1116
+ };
654
1117
  /** Embedder interface for converting text to vectors. */
655
1118
  interface Embedder {
656
- embed(texts: string[]): Promise<number[][]>;
1119
+ /**
1120
+ * Embed one or more texts into vectors.
1121
+ *
1122
+ * @param texts Input strings to embed.
1123
+ * @param signal Optional `AbortSignal` — when triggered, the underlying
1124
+ * network call should abort. Passed through from
1125
+ * `ctx.remember` / `ctx.recall` so user cancellation and
1126
+ * hard_stop budget aborts propagate to the embedder fetch.
1127
+ * Impls are free to ignore it (the call just runs to
1128
+ * completion like before), but OpenAIEmbedder honors it
1129
+ * via `fetchWithRetry`.
1130
+ */
1131
+ embed(texts: string[], signal?: AbortSignal): Promise<EmbedResult>;
657
1132
  readonly dimensions: number;
658
1133
  }
659
1134
  /** Memory configuration. */
@@ -691,6 +1166,27 @@ type StateConfig = {
691
1166
  sqlite?: {
692
1167
  path: string;
693
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;
694
1190
  };
695
1191
  /** Global defaults */
696
1192
  type DefaultsConfig = {
@@ -911,6 +1407,28 @@ declare class McpManager {
911
1407
  get serverCount(): number;
912
1408
  }
913
1409
 
1410
+ /**
1411
+ * Result of a `MemoryManager.remember()` call.
1412
+ *
1413
+ * Exposes embedder `usage` so callers (WorkflowContext) can attribute
1414
+ * cost to the current execution scope and surface it in trace events.
1415
+ * `usage` is absent when no embedding happened (embed option not set,
1416
+ * or no vector store configured).
1417
+ */
1418
+ type RememberResult = {
1419
+ usage?: EmbedUsage;
1420
+ };
1421
+ /**
1422
+ * Result of a `MemoryManager.recall()` call.
1423
+ *
1424
+ * Wraps the original return shape (key-value payload or vector search
1425
+ * results) with optional embedder `usage`. Semantic recall populates
1426
+ * `usage` when the embedder reported it; key-value lookups never do.
1427
+ */
1428
+ type RecallResult = {
1429
+ data: unknown | VectorResult[] | null;
1430
+ usage?: EmbedUsage;
1431
+ };
914
1432
  /**
915
1433
  * Coordinates key-value memory and vector store for semantic search.
916
1434
  * All key-value operations delegate to the StateStore.
@@ -927,12 +1445,18 @@ declare class MemoryManager {
927
1445
  * Store a key-value pair in memory.
928
1446
  * If a vector store and embedder are configured and embed is true,
929
1447
  * also embeds the value for semantic search.
1448
+ *
1449
+ * Returns `{ usage }` when embedding happened, carrying the embedder's
1450
+ * cost/token reporting out to the caller for cost attribution.
930
1451
  */
931
- remember(key: string, value: unknown, stateStore: StateStore, sessionId: string | undefined, options?: RememberOptions): Promise<void>;
1452
+ remember(key: string, value: unknown, stateStore: StateStore, sessionId: string | undefined, options?: RememberOptions, signal?: AbortSignal): Promise<RememberResult>;
932
1453
  /**
933
1454
  * Recall a value from memory by key, or perform semantic search if query is provided.
1455
+ *
1456
+ * Always returns `{ data }`; semantic recalls additionally include
1457
+ * `usage` from the embedder (when the embedder reported it).
934
1458
  */
935
- recall(key: string, stateStore: StateStore, sessionId: string | undefined, options?: RecallOptions): Promise<unknown | VectorResult[] | null>;
1459
+ recall(key: string, stateStore: StateStore, sessionId: string | undefined, options?: RecallOptions, signal?: AbortSignal): Promise<RecallResult>;
936
1460
  /** Delete a memory entry. If a vector store is configured, also removes the embedding. */
937
1461
  forget(key: string, stateStore: StateStore, sessionId: string | undefined, options?: {
938
1462
  scope?: 'session' | 'global';
@@ -958,13 +1482,17 @@ type WorkflowContextInit = {
958
1482
  config: AxlConfig;
959
1483
  providerRegistry: ProviderRegistry;
960
1484
  sessionHistory?: ChatMessage[];
961
- onTrace?: (event: TraceEvent) => void;
962
- 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. */
963
1491
  onToolCall?: (call: {
964
1492
  name: string;
965
1493
  args: unknown;
966
1494
  callId?: string;
967
- }) => void;
1495
+ }, meta: CallbackMeta) => void;
968
1496
  pendingDecisions?: Map<string, (d: HumanDecision) => void>;
969
1497
  budgetContext?: {
970
1498
  totalCost: number;
@@ -991,9 +1519,26 @@ type WorkflowContextInit = {
991
1519
  onAgentStart?: (info: {
992
1520
  agent: string;
993
1521
  model: string;
994
- }) => void;
1522
+ }, meta: CallbackMeta) => void;
995
1523
  /** Callback fired after each ctx.ask() completes (once per ask invocation). */
996
1524
  onAgentCallComplete?: (call: AgentCallInfo) => void;
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
+ };
997
1542
  };
998
1543
  /**
999
1544
  * The central coordination object for all Axl primitives.
@@ -1018,8 +1563,34 @@ declare class WorkflowContext<TInput = unknown> {
1018
1563
  private pendingDecisions?;
1019
1564
  private budgetContext?;
1020
1565
  private stateStore?;
1021
- private stepCounter;
1022
- 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;
1023
1594
  private signal?;
1024
1595
  private summaryCache?;
1025
1596
  private workflowName?;
@@ -1036,8 +1607,21 @@ declare class WorkflowContext<TInput = unknown> {
1036
1607
  * Create a child context for nested agent invocations (e.g., agent-as-tool).
1037
1608
  * Shares: budget tracking, abort signals, trace emission, provider registry,
1038
1609
  * state store, span manager, memory manager, MCP manager, config,
1039
- * awaitHuman handler, pending decisions, tool overrides.
1040
- * 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.
1619
+ *
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.
1041
1625
  */
1042
1626
  createChildContext(): WorkflowContext;
1043
1627
  /**
@@ -1046,6 +1630,18 @@ declare class WorkflowContext<TInput = unknown> {
1046
1630
  * take priority over the instance-level signal.
1047
1631
  */
1048
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;
1049
1645
  ask<T = string>(agent: Agent, prompt: string, options?: AskOptions<T>): Promise<T>;
1050
1646
  private executeAgentCall;
1051
1647
  /**
@@ -1062,16 +1658,49 @@ declare class WorkflowContext<TInput = unknown> {
1062
1658
  /**
1063
1659
  * Execute a function with checkpoint-replay semantics.
1064
1660
  *
1065
- * On first execution, runs `fn()`, saves the result, and returns it.
1066
- * On replay (resume after restart), returns the saved result without re-executing.
1067
- * 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.
1068
1675
  */
1069
- checkpoint<T>(fn: () => Promise<T>): Promise<T>;
1676
+ checkpoint<T>(name: string, fn: () => Promise<T>): Promise<T>;
1070
1677
  /**
1071
- * Internal checkpoint implementation shared by both the public checkpoint()
1072
- * 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.
1073
1688
  */
1074
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;
1075
1704
  spawn<T>(n: number, fn: (index: number) => Promise<T>, options?: SpawnOptions): Promise<Result<T>[]>;
1076
1705
  private _spawnImpl;
1077
1706
  vote<T>(results: Result<T>[], options: VoteOptions<T>): T | Promise<T>;
@@ -1099,6 +1728,49 @@ declare class WorkflowContext<TInput = unknown> {
1099
1728
  private _mapImpl;
1100
1729
  awaitHuman(options: AwaitHumanOptions): Promise<HumanDecision>;
1101
1730
  private _awaitHumanImpl;
1731
+ /**
1732
+ * Compose an `AbortSignal` for a memory operation's underlying embedder
1733
+ * fetch. Combines the parent context signal (user cancellation,
1734
+ * `runtime.execute({ signal })`) with the budget hard_stop abort so
1735
+ * that either cause correctly aborts an in-flight embed call.
1736
+ *
1737
+ * Returns `undefined` if there's nothing to cancel on — the embedder
1738
+ * runs without a signal in that case, matching its prior behavior.
1739
+ *
1740
+ * @internal
1741
+ */
1742
+ private _composeMemorySignal;
1743
+ /**
1744
+ * Accumulate a cost amount into the active `budgetContext` and trip the
1745
+ * `exceeded` flag if we've crossed the limit. On `hard_stop` policy,
1746
+ * also fires the abort controller so in-flight operations cancel.
1747
+ *
1748
+ * Called from every code path that spends money: the `agent_call` loop,
1749
+ * semantic memory operations (`ctx.remember({embed:true})`, `ctx.recall({query})`),
1750
+ * and any future cost-emitting primitive. Centralizing the logic here
1751
+ * means `ctx.budget({ limit, policy })` accurately enforces the limit
1752
+ * across ALL cost sources — not just agent calls.
1753
+ *
1754
+ * @internal
1755
+ */
1756
+ private _accumulateBudgetCost;
1757
+ /** @internal — idempotent; no-ops on second+ call within one ctx. */
1758
+ _emitWorkflowStart(input: unknown): void;
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. */
1767
+ _emitWorkflowEnd(info: {
1768
+ status: 'completed' | 'failed';
1769
+ duration: number;
1770
+ result?: unknown;
1771
+ error?: string;
1772
+ aborted?: boolean;
1773
+ }): void;
1102
1774
  log(event: string, data?: unknown): void;
1103
1775
  /**
1104
1776
  * Store a value in memory, scoped to the current session (default) or globally.
@@ -1125,7 +1797,13 @@ declare class WorkflowContext<TInput = unknown> {
1125
1797
  * @param options - Optional: schema, routerModel, metadata, retries
1126
1798
  */
1127
1799
  delegate<T = string>(agents: Agent[], prompt: string, options?: DelegateOptions<T>): Promise<T>;
1128
- private emitTrace;
1800
+ /**
1801
+ * Internal emitter input — intentionally loose so call sites don't need to
1802
+ * build a perfectly-narrowed discriminated-union member. The resulting
1803
+ * `AxlEvent` (exported type) remains strict, and TypeScript narrows it at
1804
+ * consumer sites via the `type` discriminator.
1805
+ */
1806
+ private emitEvent;
1129
1807
  }
1130
1808
 
1131
1809
  /** Retry policy for tool handlers */
@@ -1193,6 +1871,15 @@ type Workflow<TInput extends z.ZodType = z.ZodType, TOutput extends z.ZodType =
1193
1871
  readonly outputSchema: TOutput | undefined;
1194
1872
  readonly handler: (ctx: WorkflowContext<z.infer<TInput>>) => Promise<z.infer<TOutput>>;
1195
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>;
1196
1883
  /**
1197
1884
  * Define a named workflow with Zod-validated input/output and an async handler.
1198
1885
  * Register workflows with `AxlRuntime.register()` to execute them.
@@ -1204,37 +1891,112 @@ declare function workflow<TInput extends z.ZodType, TOutput extends z.ZodType =
1204
1891
  /**
1205
1892
  * A streamable workflow execution.
1206
1893
  *
1207
- * Extends Node's Readable and implements AsyncIterable<StreamEvent>.
1208
- * 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.
1209
1901
  */
1210
1902
  declare class AxlStream extends Readable {
1211
1903
  private bus;
1212
- 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;
1213
1927
  private result;
1214
1928
  private finished;
1215
1929
  readonly promise: Promise<unknown>;
1216
1930
  private eventQueue;
1217
1931
  private waiters;
1218
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. */
1219
1936
  private static readonly STREAM_EVENTS;
1220
1937
  on(event: string, handler: (...args: unknown[]) => void): this;
1221
1938
  off(event: string, handler: (...args: unknown[]) => void): this;
1222
1939
  [Symbol.asyncIterator](): {
1223
- next: () => Promise<IteratorResult<StreamEvent>>;
1940
+ next: () => Promise<IteratorResult<AxlEvent>>;
1224
1941
  [Symbol.asyncIterator](): /*elided*/ any;
1225
1942
  [Symbol.asyncDispose](): Promise<void>;
1226
1943
  };
1227
1944
  get text(): AsyncIterable<string>;
1228
- 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>;
1229
1969
  pipe<T extends NodeJS.WritableStream>(destination: T, options?: {
1230
1970
  end?: boolean;
1231
1971
  }): T;
1232
- /** Push a stream event. Called by the runtime. */
1233
- _push(event: StreamEvent): void;
1234
- /** Signal successful completion. */
1235
- _done(result: unknown): void;
1236
- /** Signal an error. */
1237
- _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`. */
1238
2000
  get fullText(): string;
1239
2001
  }
1240
2002
 
@@ -1275,6 +2037,25 @@ declare class Session {
1275
2037
 
1276
2038
  type ExecuteOptions = {
1277
2039
  metadata?: Record<string, unknown>;
2040
+ /** Handler for tool approval requests. When provided, tools with `requireApproval` resolve
2041
+ * via this handler instead of suspending the execution and registering a pending decision.
2042
+ * Useful for in-process testing and ad-hoc invocations where you don't want to poll
2043
+ * `runtime.getPendingDecisions()` and call `runtime.resolveDecision()`. */
2044
+ awaitHumanHandler?: (options: AwaitHumanOptions) => Promise<HumanDecision>;
2045
+ };
2046
+ /**
2047
+ * Shape of events forwarded to an eval `onProgress` callback. Mirrors
2048
+ * `@axlsdk/eval`'s `EvalProgressEvent` so runtime consumers can type their
2049
+ * callbacks without importing the optional peer dep.
2050
+ */
2051
+ type EvalProgressEventShape = {
2052
+ type: 'item_done';
2053
+ itemIndex: number;
2054
+ totalItems: number;
2055
+ } | {
2056
+ type: 'run_done';
2057
+ totalItems: number;
2058
+ failures: number;
1278
2059
  };
1279
2060
  type CreateContextOptions = {
1280
2061
  metadata?: Record<string, unknown>;
@@ -1284,8 +2065,23 @@ type CreateContextOptions = {
1284
2065
  signal?: AbortSignal;
1285
2066
  /** Prior conversation history for multi-turn eval testing. */
1286
2067
  sessionHistory?: ChatMessage[];
1287
- /** Token streaming callback. */
1288
- 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;
1289
2085
  /** Handler for tool approval requests. Called when an agent invokes a tool with requireApproval. */
1290
2086
  awaitHumanHandler?: (options: AwaitHumanOptions) => Promise<HumanDecision>;
1291
2087
  };
@@ -1313,7 +2109,25 @@ declare class AxlRuntime extends EventEmitter {
1313
2109
  private historicalExecutionsLoadPromise;
1314
2110
  private evalHistory;
1315
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;
1316
2116
  constructor(config?: AxlConfig);
2117
+ /**
2118
+ * Whether `config.trace.redact` is enabled on this runtime. A narrow
2119
+ * boolean getter is preferred over exposing the full config because:
2120
+ * (a) `Readonly<AxlConfig>` is shallow so the config would be mutable
2121
+ * at runtime through sub-objects, subverting any compliance guarantee;
2122
+ * (b) observability consumers like Studio only need the boolean, not
2123
+ * the whole config tree; (c) if future observability config needs to
2124
+ * expand, each new flag gets its own narrow getter.
2125
+ *
2126
+ * Studio's server-side redaction helpers (executions, memory, sessions,
2127
+ * evals, decisions, tools, playground, workflows) all route through
2128
+ * this accessor to decide whether to scrub response payloads.
2129
+ */
2130
+ isRedactEnabled(): boolean;
1317
2131
  /**
1318
2132
  * Initialize MCP servers configured in the config.
1319
2133
  * Call this before executing workflows that use MCP tools.
@@ -1327,13 +2141,39 @@ declare class AxlRuntime extends EventEmitter {
1327
2141
  /** Get the MCP manager (if initialized). */
1328
2142
  getMcpManager(): McpManager | undefined;
1329
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;
1330
2170
  /**
1331
2171
  * Persist a completed/failed execution to the state store (fire-and-forget)
1332
2172
  * and move it from the active executions map to the historical cache.
1333
2173
  */
1334
2174
  private persistExecution;
1335
2175
  /** Register a workflow with the runtime. */
1336
- register(workflow: Workflow<any, any>): void;
2176
+ register(workflow: AnyWorkflow): void;
1337
2177
  /** Register standalone tools for Studio introspection and direct testing. */
1338
2178
  registerTool(...tools: Tool<any, any>[]): void;
1339
2179
  /** Register standalone agents for Studio playground and introspection. */
@@ -1381,6 +2221,18 @@ declare class AxlRuntime extends EventEmitter {
1381
2221
  /** Run a registered eval by name. */
1382
2222
  runRegisteredEval(name: string, options?: {
1383
2223
  metadata?: Record<string, unknown>;
2224
+ /** Called after each dataset item completes (execution + scoring). */
2225
+ onProgress?: (event: EvalProgressEventShape) => void;
2226
+ /** Abort signal — checked before starting each item. */
2227
+ signal?: AbortSignal;
2228
+ /**
2229
+ * When `true`, populate `EvalItem.traces` on every item (success + failure
2230
+ * paths). Forwards to `runEval({ captureTraces: true })`, which wraps each
2231
+ * item's execution in `runtime.trackExecution({ captureTraces: true })`.
2232
+ * Verbose-mode `agent_call_start.data.messages` snapshots are stripped from
2233
+ * captured traces to keep memory bounded.
2234
+ */
2235
+ captureTraces?: boolean;
1384
2236
  }): Promise<unknown>;
1385
2237
  /** Get all execution info (running + completed + historical). */
1386
2238
  getExecutions(): Promise<ExecutionInfo[]>;
@@ -1457,6 +2309,10 @@ declare class AxlRuntime extends EventEmitter {
1457
2309
  concurrency?: number;
1458
2310
  budget?: string;
1459
2311
  metadata?: Record<string, unknown>;
2312
+ }, options?: {
2313
+ onProgress?: (event: EvalProgressEventShape) => void;
2314
+ signal?: AbortSignal;
2315
+ captureTraces?: boolean;
1460
2316
  }): Promise<unknown>;
1461
2317
  /**
1462
2318
  * Compare two evaluation results to detect regressions and improvements.
@@ -1484,14 +2340,43 @@ declare class AxlRuntime extends EventEmitter {
1484
2340
  * Returns cost (same as `trackCost`) plus metadata extracted from trace events:
1485
2341
  * models (unique URIs), tokens (input/output/reasoning sums), and agent call count.
1486
2342
  *
2343
+ * ## Cost vs tokens semantics
2344
+ *
2345
+ * - `cost` is the full aggregate across EVERY event with a top-level
2346
+ * `event.cost` set: agent calls, tool calls, semantic memory ops, etc.
2347
+ * This is the number to reconcile against your provider bill.
2348
+ *
2349
+ * - `metadata.tokens` is narrowly scoped to **agent** prompt/completion/
2350
+ * reasoning tokens. Embedder tokens from semantic `ctx.remember({embed:true})`
2351
+ * / `ctx.recall({query})` are deliberately NOT summed here — they're a
2352
+ * different category (input-only, different pricing, different model).
2353
+ * Conflating them would make "prompt tokens" misleading in the UI. If you
2354
+ * need embedder token counts, subscribe to `runtime.on('trace', ...)` and
2355
+ * read `data.usage.tokens` on `memory_remember` / `memory_recall` events.
2356
+ *
2357
+ * Pass `{ captureTraces: true }` to also collect the raw `AxlEvent[]` observed
2358
+ * during `fn()`. This is opt-in because it keeps every event in memory for the
2359
+ * duration of the call — useful for eval per-item capture, debugging, and test
2360
+ * assertions, but overhead grows with trace volume. When enabled, verbose-mode
2361
+ * `agent_call_start.data.messages` snapshots are omitted from captured events (still
2362
+ * broadcast via onTrace) to keep memory bounded — callers who need the full
2363
+ * verbose snapshot should subscribe to `runtime.on('trace', ...)` directly.
2364
+ *
1487
2365
  * Works with both `createContext()` and `execute()` calls inside `fn`.
1488
2366
  */
1489
- trackExecution<T>(fn: () => Promise<T>): Promise<{
2367
+ trackExecution<T>(fn: () => Promise<T>, options?: {
2368
+ captureTraces?: boolean;
2369
+ }): Promise<{
1490
2370
  result: T;
1491
2371
  cost: number;
2372
+ traces?: AxlEvent[];
1492
2373
  metadata: {
1493
2374
  models: string[];
1494
2375
  modelCallCounts?: Record<string, number>;
2376
+ /**
2377
+ * Agent token totals only — does not include embedder tokens from
2378
+ * semantic memory operations. See the method-level JSDoc above.
2379
+ */
1495
2380
  tokens: {
1496
2381
  input: number;
1497
2382
  output: number;
@@ -1519,8 +2404,8 @@ declare class AxlRuntime extends EventEmitter {
1519
2404
  * The emit('trace', event) call happens before this method is called, so
1520
2405
  * programmatic subscribers always receive events regardless of trace config.
1521
2406
  */
1522
- private outputTraceEvent;
1523
- private logTraceEvent;
2407
+ private outputAxlEvent;
2408
+ private logAxlEvent;
1524
2409
  /**
1525
2410
  * Append a handoff record to session metadata.
1526
2411
  * Note: The read-modify-write is not atomic. Concurrent handoffs in the same
@@ -1529,8 +2414,177 @@ declare class AxlRuntime extends EventEmitter {
1529
2414
  * cross-execution concurrency on the same session, which is unlikely.
1530
2415
  */
1531
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;
1532
2434
  }
1533
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
+
1534
2588
  /** Base error class for all Axl errors */
1535
2589
  declare class AxlError extends Error {
1536
2590
  readonly code: string;
@@ -1788,12 +2842,8 @@ declare class MemoryStore implements StateStore {
1788
2842
  private executionHistory;
1789
2843
  private evalHistory;
1790
2844
  constructor();
1791
- saveCheckpoint(executionId: string, step: number, data: unknown): Promise<void>;
1792
- getCheckpoint(executionId: string, step: number): Promise<unknown | null>;
1793
- getLatestCheckpoint(executionId: string): Promise<{
1794
- step: number;
1795
- data: unknown;
1796
- } | null>;
2845
+ saveCheckpoint(executionId: string, name: string, data: unknown): Promise<void>;
2846
+ getCheckpoint(executionId: string, name: string): Promise<unknown | null>;
1797
2847
  saveSession(sessionId: string, history: ChatMessage[]): Promise<void>;
1798
2848
  getSession(sessionId: string): Promise<ChatMessage[]>;
1799
2849
  deleteSession(sessionId: string): Promise<void>;
@@ -1837,13 +2887,44 @@ declare class MemoryStore implements StateStore {
1837
2887
  declare class SQLiteStore implements StateStore {
1838
2888
  private db;
1839
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;
1840
2925
  private initTables;
1841
- saveCheckpoint(executionId: string, step: number, data: unknown): Promise<void>;
1842
- getCheckpoint(executionId: string, step: number): Promise<unknown | null>;
1843
- getLatestCheckpoint(executionId: string): Promise<{
1844
- step: number;
1845
- data: unknown;
1846
- } | null>;
2926
+ saveCheckpoint(executionId: string, name: string, data: unknown): Promise<void>;
2927
+ getCheckpoint(executionId: string, name: string): Promise<unknown | null>;
1847
2928
  saveSession(sessionId: string, history: ChatMessage[]): Promise<void>;
1848
2929
  getSession(sessionId: string): Promise<ChatMessage[]>;
1849
2930
  deleteSession(sessionId: string): Promise<void>;
@@ -1902,12 +2983,8 @@ declare class RedisStore implements StateStore {
1902
2983
  private execHistorySetKey;
1903
2984
  private evalHistoryKey;
1904
2985
  private evalHistorySetKey;
1905
- saveCheckpoint(executionId: string, step: number, data: unknown): Promise<void>;
1906
- getCheckpoint(executionId: string, step: number): Promise<unknown | null>;
1907
- getLatestCheckpoint(executionId: string): Promise<{
1908
- step: number;
1909
- data: unknown;
1910
- } | null>;
2986
+ saveCheckpoint(executionId: string, name: string, data: unknown): Promise<void>;
2987
+ getCheckpoint(executionId: string, name: string): Promise<unknown | null>;
1911
2988
  saveSession(sessionId: string, history: ChatMessage[]): Promise<void>;
1912
2989
  getSession(sessionId: string): Promise<ChatMessage[]>;
1913
2990
  deleteSession(sessionId: string): Promise<void>;
@@ -1946,7 +3023,7 @@ declare class OpenAIEmbedder implements Embedder {
1946
3023
  baseUrl?: string;
1947
3024
  dimensions?: number;
1948
3025
  });
1949
- embed(texts: string[]): Promise<number[][]>;
3026
+ embed(texts: string[], signal?: AbortSignal): Promise<EmbedResult>;
1950
3027
  }
1951
3028
 
1952
3029
  /**
@@ -1997,4 +3074,4 @@ declare class NoopSpanManager implements SpanManager {
1997
3074
  */
1998
3075
  declare function createSpanManager(config?: TelemetryConfig): Promise<SpanManager>;
1999
3076
 
2000
- export { type Agent, type AgentCallInfo, 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 Effort, type Embedder, type EvalHistoryEntry, type ExecutionInfo, type ExecutionState, GeminiProvider, type GuardrailBlockHandler, GuardrailError, type GuardrailResult, type GuardrailsConfig, type HandoffDescriptor, type HandoffRecord, 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, RedisStore, type RememberOptions, type ResolvedThinkingOptions, type Result, type RetryPolicy, SQLiteStore, Session, type SessionOptions, type SpanHandle, type SpanManager, type SpawnOptions, SqliteVectorStore, type StateStore, type StreamChunk, type StreamEvent, type TelemetryConfig, TimeoutError, type Tool, type ToolCallMessage, type ToolChoice, type ToolConfig, ToolDenied, type ToolHooks, type TraceEvent, type ValidateResult, ValidationError, type VectorEntry, type VectorResult, type VectorStore, VerifyError, type VerifyOptions, type VerifyRetry, type VoteOptions, type Workflow, type WorkflowConfig, WorkflowContext, type WorkflowContextInit, 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 };