@axlsdk/axl 0.17.6 → 0.17.7

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
@@ -855,6 +855,14 @@ type ExecutionInfo = {
855
855
  duration: number;
856
856
  result?: unknown;
857
857
  error?: string;
858
+ /** Caller-supplied metadata threaded verbatim from `ExecuteOptions.metadata`
859
+ * (or `runtime.stream()`'s `options.metadata`). Persisted as part of the
860
+ * `ExecutionInfo` snapshot so callers have a stable, queryable surface for
861
+ * tags like `userId`, `tenantId`, or correlation ids without parsing the
862
+ * event timeline. Filter via `runtime.getExecutions()` + an in-process
863
+ * `.filter()`; secondary indexes on this field are intentionally not
864
+ * provided — `StateStore` is a persistence boundary, not a query engine. */
865
+ metadata?: Record<string, unknown>;
858
866
  };
859
867
  /** Record of an agent handoff event (persisted in session metadata).
860
868
  *
@@ -1447,6 +1455,30 @@ interface StateStore {
1447
1455
  getExecution?(executionId: string): Promise<ExecutionInfo | null>;
1448
1456
  /** List recent executions (most recent first). */
1449
1457
  listExecutions?(limit?: number): Promise<ExecutionInfo[]>;
1458
+ /**
1459
+ * Delete an execution from history by ID. Returns `true` if an entry was
1460
+ * deleted, `false` if the ID didn't exist. Used for GDPR right-to-be-
1461
+ * forgotten and operator-driven cleanup. Symmetric to `deleteEvalResult`.
1462
+ *
1463
+ * **Custom store implementors:** This delete must be **total** —
1464
+ * sweep every per-execution surface your store maintains. The runtime's
1465
+ * `runtime.deleteExecution` does NOT call separate `deleteCheckpoints` /
1466
+ * `deleteExecutionState` / `finalizeStreamingEvents` after this; one
1467
+ * call should clean EVERYTHING owned by the executionId. The built-in
1468
+ * stores remove:
1469
+ * - the canonical execution-history row and its indexes,
1470
+ * - all checkpoints for the id,
1471
+ * - the suspended-state row + its pending-set membership,
1472
+ * - the streaming-event buffer + its in-flight-ids membership,
1473
+ * - any pending awaitHuman decision row.
1474
+ * Skipping any of these leaks PII through `recoverIncompleteStreams()`
1475
+ * or `getPendingDecisions()`, breaking the GDPR delete contract.
1476
+ *
1477
+ * For multi-table SQL stores: wrap the deletes in a transaction. For
1478
+ * Redis-style stores: use MULTI/EXEC. Idempotency is required —
1479
+ * deleting an unknown id returns `false` without throwing.
1480
+ */
1481
+ deleteExecution?(executionId: string): Promise<boolean>;
1450
1482
  /** Save an eval result to history. */
1451
1483
  saveEvalResult?(entry: EvalHistoryEntry): Promise<void>;
1452
1484
  /** List eval history entries (most recent first). */
@@ -1455,6 +1487,31 @@ interface StateStore {
1455
1487
  deleteEvalResult?(id: string): Promise<boolean>;
1456
1488
  /** List all session IDs (used by Studio session browser). */
1457
1489
  listSessions?(): Promise<string[]>;
1490
+ /**
1491
+ * Append a batch of events to the streaming buffer for an execution.
1492
+ * Should be idempotent w.r.t. the executionId being new vs existing
1493
+ * (first call also registers the id in the "in-flight" index used by
1494
+ * `listStreamingExecutions`).
1495
+ */
1496
+ appendStreamingEvents?(executionId: string, events: AxlEvent[]): Promise<void>;
1497
+ /**
1498
+ * Finalize a streaming execution — delete its buffer + un-register it
1499
+ * from the in-flight index. Called by the runtime after the canonical
1500
+ * `executionHistory` blob has been written, so the streaming buffer
1501
+ * is no longer the source of truth for this execution.
1502
+ */
1503
+ finalizeStreamingEvents?(executionId: string): Promise<void>;
1504
+ /**
1505
+ * List execution IDs that have a streaming buffer but no corresponding
1506
+ * completed `executionHistory` — i.e., runs whose process died mid-flight.
1507
+ * The runtime calls this from `recoverIncompleteStreams()`.
1508
+ */
1509
+ listStreamingExecutions?(): Promise<string[]>;
1510
+ /**
1511
+ * Read the events accumulated in the streaming buffer for an execution.
1512
+ * Returns `[]` if no buffer exists.
1513
+ */
1514
+ getStreamingEvents?(executionId: string): Promise<AxlEvent[]>;
1458
1515
  close?(): Promise<void>;
1459
1516
  deleteCheckpoints?(executionId: string): Promise<void>;
1460
1517
  }
@@ -1632,6 +1689,44 @@ type StateConfig = {
1632
1689
  * integer or `Infinity`.
1633
1690
  */
1634
1691
  maxEventsPerExecution?: number;
1692
+ /**
1693
+ * When to persist execution events to the state store.
1694
+ *
1695
+ * - `'terminal'` (default, back-compat) — events are buffered in memory
1696
+ * and written to `executionHistory` only at the terminal `done`/`error`
1697
+ * event. Fastest path; if the process crashes mid-run, the events are
1698
+ * lost. Fine for development and short-running workflows.
1699
+ *
1700
+ * - `'streaming'` — events are batched and written to a streaming buffer
1701
+ * throughout the run (via `StateStore.appendStreamingEvents`), then the
1702
+ * buffer is finalized when the canonical `executionHistory` is saved at
1703
+ * terminal exit. If the process crashes mid-run, call
1704
+ * `runtime.recoverIncompleteStreams()` on the next process to reconstruct
1705
+ * the partial `ExecutionInfo` from the streaming buffer (status: `'failed'`,
1706
+ * error: `'process terminated'`). Use this in production when you need to
1707
+ * know "what did the workflow do up to the crash."
1708
+ *
1709
+ * Excluded from streaming flush even in `'streaming'` mode: `token`,
1710
+ * `partial_object`, `string_delta` (high-volume, reconstructable from
1711
+ * the persisted `agent_call_end.data`).
1712
+ */
1713
+ persist?: 'terminal' | 'streaming';
1714
+ /**
1715
+ * Number of events buffered before flushing to the streaming store.
1716
+ * Default `100`. Set to `1` for per-event flush (highest durability,
1717
+ * one Redis round-trip per emit — adds latency proportional to RTT).
1718
+ *
1719
+ * Only meaningful when `persist === 'streaming'`.
1720
+ */
1721
+ streamingBatchSize?: number;
1722
+ /**
1723
+ * Max milliseconds between flushes when the batch hasn't filled up.
1724
+ * Default `1000`. Together with `streamingBatchSize`, bounds the
1725
+ * "events lost on crash" window to `min(batchSize-of-events, interval-of-time)`.
1726
+ *
1727
+ * Only meaningful when `persist === 'streaming'`.
1728
+ */
1729
+ streamingBatchInterval?: number;
1635
1730
  };
1636
1731
  /** Global defaults */
1637
1732
  type DefaultsConfig = {
@@ -2762,6 +2857,30 @@ declare class AxlRuntime extends EventEmitter {
2762
2857
  * `events` field — keeps the log to one line per row instead of one per
2763
2858
  * rebuild tick. */
2764
2859
  private warnedMalformedExecutions;
2860
+ /** Streaming-mode flusher. `undefined` when persist === 'terminal'
2861
+ * (back-compat default — no buffering overhead, no batching timer). */
2862
+ private streamingFlusher?;
2863
+ /** Execution IDs that are eligible for streaming-buffer durability —
2864
+ * populated by `execute()` / `stream()`. `createContext()` flows are
2865
+ * deliberately NOT added: ad-hoc contexts (tool tests, Studio playground,
2866
+ * evals) have no terminal `persistExecution` path that would finalize the
2867
+ * streaming buffer, so allowing them to write would leave phantom orphan
2868
+ * buffers that `recoverIncompleteStreams()` later mis-recovers as failed
2869
+ * executions. Membership is removed in `persistExecution` once the
2870
+ * canonical save + finalize chain has been scheduled. */
2871
+ private streamableExecutionIds;
2872
+ /** Execution IDs deleted by `runtime.deleteExecution()` while still in
2873
+ * flight. `persistExecution` skips its save + cache write when the
2874
+ * executionId is in this set — preventing a workflow that completes after
2875
+ * a delete from resurrecting the (intentionally-removed) row. Entries are
2876
+ * removed once the would-be persist has been short-circuited. */
2877
+ private pendingDeletedExecutions;
2878
+ /** In-flight `persistExecution` chains (save → finalize). `shutdown()`
2879
+ * awaits these before closing the StateStore so a workflow whose abort
2880
+ * triggers a persist-during-shutdown isn't racing a closed connection.
2881
+ * The Set is mutated by the chain's own `.finally` so entries clear as
2882
+ * soon as the work settles. */
2883
+ private persistInflight;
2765
2884
  constructor(config?: AxlConfig);
2766
2885
  /**
2767
2886
  * Whether `config.trace.redact` is enabled on this runtime. A narrow
@@ -2824,6 +2943,12 @@ declare class AxlRuntime extends EventEmitter {
2824
2943
  /**
2825
2944
  * Persist a completed/failed execution to the state store (fire-and-forget)
2826
2945
  * and move it from the active executions map to the historical cache.
2946
+ *
2947
+ * Resurrection guard: if `runtime.deleteExecution(id)` was called while
2948
+ * this execution was still running, the id will be in
2949
+ * `pendingDeletedExecutions`. In that case we skip the save AND the
2950
+ * historical-cache write, and finalize the streaming buffer (if any) so
2951
+ * the buffer doesn't outlive the delete.
2827
2952
  */
2828
2953
  private persistExecution;
2829
2954
  /** Register a workflow with the runtime. */
@@ -2932,18 +3057,76 @@ declare class AxlRuntime extends EventEmitter {
2932
3057
  * before invoking `shutdown()`. A `Session.send` invoked after the
2933
3058
  * drain snapshot is taken can still race a closed state store. */
2934
3059
  shutdown(): Promise<void>;
3060
+ /**
3061
+ * Recover executions whose process died mid-run when `state.persist:
3062
+ * 'streaming'` was configured. Synthesizes a partial `ExecutionInfo`
3063
+ * from the streaming buffer for each — `status: 'failed'`, error
3064
+ * `'process terminated'` — registers them in the historical cache so
3065
+ * they show up in `getExecutions()`, persists them via the store's
3066
+ * `saveExecution`, and finalizes the streaming buffer.
3067
+ *
3068
+ * Idempotent — if the streaming buffer is already gone (or
3069
+ * `listStreamingExecutions` is empty), this is a no-op. Returns the
3070
+ * list of recovered `ExecutionInfo`s for caller introspection.
3071
+ *
3072
+ * Wire this into your process startup AFTER `runtime.getExecutions()`
3073
+ * has lazy-loaded the historical cache. Order matters: completed
3074
+ * executions already in the store should not be overwritten by a
3075
+ * stale streaming buffer that the previous process didn't get to
3076
+ * finalize. The implementation checks the historical store before
3077
+ * synthesizing, so a re-run is safe.
3078
+ */
3079
+ recoverIncompleteStreams(): Promise<ExecutionInfo[]>;
2935
3080
  /** Abort a running execution by its ID. */
2936
3081
  abort(executionId: string): void;
2937
3082
  /** Get execution details by ID. */
2938
3083
  getExecution(executionId: string): Promise<ExecutionInfo | undefined>;
2939
3084
  /** Save an eval result to history. */
2940
3085
  saveEvalResult(entry: EvalHistoryEntry): Promise<void>;
3086
+ /**
3087
+ * Delete an execution from history by id. Removes from the active map (if
3088
+ * still running), the historical cache, the configured StateStore, AND
3089
+ * any execution-scoped side state — pending awaitHuman decisions,
3090
+ * resolver maps, streaming buffer registration, in-flight abort controller.
3091
+ * Returns true if an entry was actually removed from any of the three
3092
+ * primary surfaces (active map, historical cache, store).
3093
+ *
3094
+ * In-flight handling: if the execution is still running, this method
3095
+ * **aborts** it (via the registered abort controller) AND marks the id for
3096
+ * skip-on-persist so the workflow's eventual `workflow_end` doesn't
3097
+ * resurrect the row. The workflow itself terminates normally — callers
3098
+ * iterating the returned `AxlStream` will see the abort flow through. If
3099
+ * you need a hard hand-off (delete completes before the workflow tears
3100
+ * down), call `runtime.abort(id)` and `await` the stream/execute promise
3101
+ * before calling this. `streamableExecutionIds` is cleared eagerly so
3102
+ * events emitted during the wind-down don't re-create the streaming
3103
+ * buffer in the store after the deleteExecution-issued DEL.
3104
+ *
3105
+ * Audit trail: emits `execution_deleted` on the runtime's `EventEmitter`
3106
+ * with `{ executionId, wasActive, hadPendingDecision }` so compliance
3107
+ * apps can subscribe via `runtime.on('execution_deleted', ...)` for a
3108
+ * "who deleted what when" log without wrapping this method. Fires
3109
+ * regardless of whether the id was actually present.
3110
+ *
3111
+ * Use cases: GDPR right-to-be-forgotten, operator-driven cleanup of
3112
+ * specific runs (e.g. a workflow that recorded PII the user requested
3113
+ * scrubbed). For bulk eviction by age, use a `RedisStore` TTL instead
3114
+ * (`defaultTtl` / `ttls.executionHistory`).
3115
+ */
3116
+ deleteExecution(id: string): Promise<boolean>;
2941
3117
  /**
2942
3118
  * Delete an eval history entry by id. Removes from in-memory cache and
2943
3119
  * the configured StateStore. Returns true if an entry was actually removed.
2944
3120
  *
2945
3121
  * Ensures lazy-loaded history is loaded first so the in-memory cache and
2946
3122
  * the store can't drift apart on the deletion path.
3123
+ *
3124
+ * Emits `eval_deleted` on the runtime's `EventEmitter` with
3125
+ * `{ id, eval, removed }`. Fires on every call — including attempts
3126
+ * against unknown ids (`removed: false`) — symmetric to `execution_deleted`.
3127
+ * Studio's eval-trends aggregator subscribes to this event so deleted
3128
+ * runs don't linger in the trends panel for up to 5 minutes (until the
3129
+ * next periodic rebuild).
2947
3130
  */
2948
3131
  deleteEvalResult(id: string): Promise<boolean>;
2949
3132
  /** Get eval result history (most recent first). */
@@ -3775,10 +3958,16 @@ declare class MemoryStore implements StateStore {
3775
3958
  saveExecution(execution: ExecutionInfo): Promise<void>;
3776
3959
  getExecution(executionId: string): Promise<ExecutionInfo | null>;
3777
3960
  listExecutions(limit?: number): Promise<ExecutionInfo[]>;
3961
+ deleteExecution(executionId: string): Promise<boolean>;
3778
3962
  saveEvalResult(entry: EvalHistoryEntry): Promise<void>;
3779
3963
  listEvalResults(limit?: number): Promise<EvalHistoryEntry[]>;
3780
3964
  deleteEvalResult(id: string): Promise<boolean>;
3781
3965
  listSessions(): Promise<string[]>;
3966
+ private streamingEvents;
3967
+ appendStreamingEvents(executionId: string, events: AxlEvent[]): Promise<void>;
3968
+ finalizeStreamingEvents(executionId: string): Promise<void>;
3969
+ listStreamingExecutions(): Promise<string[]>;
3970
+ getStreamingEvents(executionId: string): Promise<AxlEvent[]>;
3782
3971
  close(): Promise<void>;
3783
3972
  deleteCheckpoints(executionId: string): Promise<void>;
3784
3973
  private persistAwaitHumanState;
@@ -3815,6 +4004,10 @@ declare class SQLiteStore implements StateStore {
3815
4004
  * numeric step values become stringified names ("0", "1", …) to
3816
4005
  * preserve replay continuity for in-flight executions across the
3817
4006
  * upgrade boundary.
4007
+ * - v3 (ExecutionInfo.metadata): add `execution_history.metadata`
4008
+ * column (TEXT, nullable, JSON-serialized) so caller-supplied
4009
+ * `ExecuteOptions.metadata` round-trips through history. Existing
4010
+ * rows get `NULL`, deserialized as `metadata: undefined`.
3818
4011
  *
3819
4012
  * Applied BEFORE `initTables()` so the subsequent `CREATE TABLE
3820
4013
  * IF NOT EXISTS` runs against the post-migration column name. Fresh
@@ -3849,6 +4042,7 @@ declare class SQLiteStore implements StateStore {
3849
4042
  saveExecution(execution: ExecutionInfo): Promise<void>;
3850
4043
  getExecution(executionId: string): Promise<ExecutionInfo | null>;
3851
4044
  listExecutions(limit?: number): Promise<ExecutionInfo[]>;
4045
+ deleteExecution(executionId: string): Promise<boolean>;
3852
4046
  saveEvalResult(entry: EvalHistoryEntry): Promise<void>;
3853
4047
  listEvalResults(limit?: number): Promise<EvalHistoryEntry[]>;
3854
4048
  deleteEvalResult(id: string): Promise<boolean>;
@@ -3865,6 +4059,115 @@ declare class SQLiteStore implements StateStore {
3865
4059
  deleteCheckpoints(executionId: string): Promise<void>;
3866
4060
  }
3867
4061
 
4062
+ /** Options accepted by {@link RedisStore.create}. */
4063
+ interface RedisStoreOptions {
4064
+ /** Redis connection URL (e.g. `redis://localhost:6379`). Defaults to `redis://localhost:6379`. */
4065
+ url?: string;
4066
+ /**
4067
+ * Prefix prepended to every Redis key written by this store. Defaults to
4068
+ * `'axl:'`. Useful when multiple Axl deployments share a Redis cluster
4069
+ * (e.g. `'axl:prod:'` vs `'axl:staging:'`) or when coexisting with other
4070
+ * applications' keys.
4071
+ *
4072
+ * The prefix is concatenated as-given — no normalization. If you want a
4073
+ * trailing colon, include it. Whitespace and unusual characters are
4074
+ * accepted as-is. Only the empty string is rejected, to prevent accidental
4075
+ * collisions with non-Axl keys.
4076
+ *
4077
+ * Operator note: Redis SCAN/KEYS patterns interpret `*`, `?`, and `[`/`]`
4078
+ * as glob meta-characters. If your prefix contains any of these, the
4079
+ * runtime accepts it (literal SET/HSET/etc. don't use globs and round-
4080
+ * trip cleanly), but operators trying to clean up with
4081
+ * `redis-cli SCAN 0 MATCH '<prefix>*'` will need to escape them. Stick
4082
+ * to alphanumerics, hyphens, underscores, and colons for the best
4083
+ * operator experience.
4084
+ */
4085
+ keyPrefix?: string;
4086
+ /**
4087
+ * Skip the lazy backfill of legacy execution / eval-history entries into
4088
+ * the sorted-set indexes during `RedisStore.create()`. Default `false`
4089
+ * (backfill runs on startup, one MULTI per 500-entry chunk plus two
4090
+ * probes — cheap when there's nothing to do).
4091
+ *
4092
+ * Set to `true` for installs at six-figure execution counts where the
4093
+ * startup cost is unacceptable. Until you call `backfillExecutionIndex()` /
4094
+ * `backfillEvalIndex()` manually (e.g. during a maintenance window),
4095
+ * `listExecutions` / `listEvalResults` fall back to the legacy
4096
+ * SET + N×GET + JS-sort read path — correct but slow at scale.
4097
+ */
4098
+ skipMigration?: boolean;
4099
+ /**
4100
+ * Default TTL in **seconds** applied to every storage category that has
4101
+ * a TTL-capable storage shape. Omitted by default — keys never expire,
4102
+ * matching pre-#3 behavior.
4103
+ *
4104
+ * Per-category overrides via {@link ttls} take precedence. Set a category
4105
+ * to `null` in {@link ttls} to opt that category out of the default.
4106
+ *
4107
+ * Strongly recommended in production: without a TTL every save accumulates
4108
+ * forever and your Redis instance will eventually OOM.
4109
+ *
4110
+ * @example
4111
+ * ```ts
4112
+ * await RedisStore.create({
4113
+ * url: 'redis://localhost:6379',
4114
+ * defaultTtl: 60 * 60 * 24 * 30, // 30 days
4115
+ * });
4116
+ * ```
4117
+ */
4118
+ defaultTtl?: number;
4119
+ /**
4120
+ * Per-category TTL overrides in **seconds**. Use to assign different
4121
+ * lifetimes per category (e.g. short-lived checkpoints, long-lived eval
4122
+ * history). Set a category to `null` to explicitly disable TTL for it
4123
+ * even when {@link defaultTtl} is set. Omitting a category means it falls
4124
+ * back to {@link defaultTtl} (or no TTL if neither is set).
4125
+ *
4126
+ * `pendingDecision` is intentionally NOT in this map — pending decisions
4127
+ * are stored as fields of a shared hash (`axl:decisions`), so a TTL on
4128
+ * that key would expire ALL pending decisions at once, which is almost
4129
+ * never what you want. Resolve decisions individually via
4130
+ * `resolveDecision()` instead.
4131
+ *
4132
+ * Window semantics:
4133
+ * - **Sliding window** (write-only refresh): `memory`, `session`,
4134
+ * `sessionMeta`. Every write resets the TTL, so active *writers* keep
4135
+ * their data and inactive ones forget. Reads do NOT refresh the TTL —
4136
+ * a read-only workload (e.g. agent recalling memories without writing
4137
+ * new ones) eventually ages out. If you need read-activity to count
4138
+ * as "active," issue a no-op `ctx.remember` against a sentinel key
4139
+ * per session turn.
4140
+ * - **Fixed window**: `checkpoint`, `executionState`, `executionHistory`,
4141
+ * `evalHistory`. The TTL is set on the first write and isn't extended
4142
+ * on subsequent writes (via `EXPIRE ... NX`). Designed for runtime-
4143
+ * controlled lifecycles like executions, where the data should age
4144
+ * out on a schedule from creation.
4145
+ */
4146
+ ttls?: {
4147
+ session?: number | null;
4148
+ sessionMeta?: number | null;
4149
+ checkpoint?: number | null;
4150
+ executionState?: number | null;
4151
+ executionHistory?: number | null;
4152
+ evalHistory?: number | null;
4153
+ memory?: number | null;
4154
+ /**
4155
+ * TTL for the `state.persist: 'streaming'` per-execution event buffer
4156
+ * (`exec-events:{id}` list). Safety net for the scenario where the
4157
+ * operator forgets to wire `runtime.recoverIncompleteStreams()` into
4158
+ * startup — without this TTL, orphaned buffers from crashed
4159
+ * processes accumulate indefinitely. Default `null` (no TTL —
4160
+ * matches pre-streaming-mode behavior; explicit opt-in for the
4161
+ * safety net so recovery isn't surprised by buffers that aged out).
4162
+ *
4163
+ * **MUST be longer than your recovery cadence.** If you restart
4164
+ * processes every 12h and the buffer TTL is 1h, any crash whose
4165
+ * recovery hasn't fired within the hour loses its events to TTL
4166
+ * eviction. Recommended floor: 24× your max time-between-restarts.
4167
+ */
4168
+ streamingEvents?: number | null;
4169
+ };
4170
+ }
3868
4171
  /**
3869
4172
  * Redis-backed StateStore using the official `redis` (node-redis) client.
3870
4173
  *
@@ -3876,23 +4179,77 @@ declare class SQLiteStore implements StateStore {
3876
4179
  */
3877
4180
  declare class RedisStore implements StateStore {
3878
4181
  private client;
4182
+ private keyPrefix;
4183
+ private ttls;
3879
4184
  private constructor();
4185
+ /**
4186
+ * Resolve the TTL for a given storage category. Returns `undefined` when
4187
+ * no TTL applies (category is `null` in resolved config, the value is
4188
+ * non-positive — Redis EXPIRE 0 immediately deletes, which is almost
4189
+ * never intended — or the value is non-finite).
4190
+ *
4191
+ * Non-finite values (`NaN`, `±Infinity`) defensively map to `undefined`
4192
+ * rather than being passed to Redis, where they'd surface as a wire-level
4193
+ * protocol error at the worst possible time (first write under load).
4194
+ */
4195
+ private ttlFor;
3880
4196
  /**
3881
4197
  * Create a connected RedisStore instance.
3882
4198
  *
3883
- * @param url - Redis connection URL (e.g. `redis://localhost:6379`). Defaults to `redis://localhost:6379`.
4199
+ * Accepts either a URL string (back-compat) or a {@link RedisStoreOptions}
4200
+ * object. The object form is required to set a custom `keyPrefix`.
4201
+ *
4202
+ * @example
4203
+ * ```ts
4204
+ * // Default prefix 'axl:'
4205
+ * const store = await RedisStore.create('redis://localhost:6379');
4206
+ *
4207
+ * // Custom prefix for shared-cluster deployments
4208
+ * const store = await RedisStore.create({
4209
+ * url: 'redis://localhost:6379',
4210
+ * keyPrefix: 'axl:prod:',
4211
+ * });
4212
+ * ```
4213
+ */
4214
+ static create(options?: string | RedisStoreOptions): Promise<RedisStore>;
4215
+ /**
4216
+ * Backfill the execution-history sorted-set from the legacy ID set.
4217
+ * Idempotent — runs only if the ZSET is missing entries the SET has.
4218
+ * Safe to call manually if you constructed the store with
4219
+ * `skipMigration: true`.
3884
4220
  */
3885
- static create(url?: string): Promise<RedisStore>;
4221
+ backfillExecutionIndex(): Promise<void>;
4222
+ /** Backfill the eval-history sorted-set. Symmetric to `backfillExecutionIndex`. */
4223
+ backfillEvalIndex(): Promise<void>;
4224
+ /**
4225
+ * Shared implementation for both backfill paths. Reads SET members,
4226
+ * batch-MGETs their data blobs in chunks, and ZADDs each into the new
4227
+ * sorted-set inside a single MULTI per chunk.
4228
+ */
4229
+ private backfillHistoryIndex;
3886
4230
  private checkpointKey;
3887
4231
  private sessionKey;
3888
4232
  private sessionMetaKey;
4233
+ private sessionIdsKey;
3889
4234
  private decisionsKey;
3890
4235
  private executionStateKey;
3891
4236
  private pendingExecSetKey;
3892
4237
  private execHistoryKey;
3893
4238
  private execHistorySetKey;
4239
+ /**
4240
+ * Sorted-set index for execution history, scored by `startedAt`. Drives
4241
+ * the O(log N) `listExecutions` fast path; the legacy SET above is kept
4242
+ * as a fallback for users running with `skipMigration: true` before
4243
+ * they've called `backfillExecutionIndex()` manually.
4244
+ */
4245
+ private execHistoryZsetKey;
3894
4246
  private evalHistoryKey;
3895
4247
  private evalHistorySetKey;
4248
+ /** Sorted-set index for eval history, scored by `timestamp`. See above. */
4249
+ private evalHistoryZsetKey;
4250
+ private memoryKey;
4251
+ private streamingEventsKey;
4252
+ private streamingIdsKey;
3896
4253
  saveCheckpoint(executionId: string, name: string, data: unknown): Promise<void>;
3897
4254
  getCheckpoint(executionId: string, name: string): Promise<unknown | null>;
3898
4255
  saveSession(sessionId: string, history: ChatMessage[]): Promise<void>;
@@ -3906,13 +4263,33 @@ declare class RedisStore implements StateStore {
3906
4263
  saveExecutionState(executionId: string, state: ExecutionState): Promise<void>;
3907
4264
  getExecutionState(executionId: string): Promise<ExecutionState | null>;
3908
4265
  listPendingExecutions(): Promise<string[]>;
4266
+ saveMemory(scope: string, key: string, value: unknown): Promise<void>;
4267
+ getMemory(scope: string, key: string): Promise<unknown | null>;
4268
+ getAllMemory(scope: string): Promise<Array<{
4269
+ key: string;
4270
+ value: unknown;
4271
+ }>>;
4272
+ deleteMemory(scope: string, key: string): Promise<void>;
3909
4273
  saveExecution(execution: ExecutionInfo): Promise<void>;
3910
4274
  getExecution(executionId: string): Promise<ExecutionInfo | null>;
3911
4275
  listExecutions(limit?: number): Promise<ExecutionInfo[]>;
4276
+ deleteExecution(executionId: string): Promise<boolean>;
3912
4277
  saveEvalResult(entry: EvalHistoryEntry): Promise<void>;
3913
4278
  listEvalResults(limit?: number): Promise<EvalHistoryEntry[]>;
3914
4279
  deleteEvalResult(id: string): Promise<boolean>;
4280
+ /**
4281
+ * Shared list-by-sorted-set implementation. Uses ZREVRANGEBYSCORE +
4282
+ * MGET when the ZSET is populated (post-migration). Falls back to the
4283
+ * legacy SET + N×GET + JS-sort path when the ZSET is empty or smaller
4284
+ * than the SET (covers users that constructed with `skipMigration: true`
4285
+ * before calling backfill).
4286
+ */
4287
+ private listHistoryByZset;
3915
4288
  listSessions(): Promise<string[]>;
4289
+ appendStreamingEvents(executionId: string, events: AxlEvent[]): Promise<void>;
4290
+ finalizeStreamingEvents(executionId: string): Promise<void>;
4291
+ listStreamingExecutions(): Promise<string[]>;
4292
+ getStreamingEvents(executionId: string): Promise<AxlEvent[]>;
3916
4293
  /** Close the Redis connection. */
3917
4294
  close(): Promise<void>;
3918
4295
  deleteCheckpoints(executionId: string): Promise<void>;