@mastra/oracledb 0.5.1-alpha.0 → 0.5.2-alpha.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.
@@ -0,0 +1,102 @@
1
+ //#region src/memory/index.d.ts
2
+ /**
3
+ * Shared rules for observational memory lifecycle writes. Every storage adapter applies
4
+ * these inside its own atomic section (lock, transaction, or conditional update), so the
5
+ * decisions are identical across adapters.
6
+ */
7
+ /**
8
+ * Observational memory columns that lifecycle writes depend on. Storage adapters merge these
9
+ * into the table schema they get from `@mastra/core`, so the columns exist even when the
10
+ * installed core predates them.
11
+ */
12
+ declare const OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS: {
13
+ readonly supersededBy: {
14
+ readonly type: 'text';
15
+ readonly nullable: true;
16
+ };
17
+ };
18
+ /** `schema` plus {@link OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS}; `undefined` when `schema` is. */
19
+ declare function withObservationalMemoryLifecycleColumns<T extends object>(schema: T | undefined): (T & typeof OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS) | undefined;
20
+ /**
21
+ * Result of `commitActiveObservations`.
22
+ */
23
+ interface UpdateActiveObservationsResult {
24
+ /** Whether the observations were written. */
25
+ applied: boolean;
26
+ /**
27
+ * Why nothing was written: `retired` — the target record was superseded by a newer
28
+ * generation; `conflict` — `expectedActiveObservations` no longer matches the stored text.
29
+ */
30
+ reason?: 'retired' | 'conflict';
31
+ }
32
+ /**
33
+ * Result of `appendBufferedObservations`.
34
+ *
35
+ * The append targets the head generation: when `input.id` was superseded, the chunk is
36
+ * appended to the current head instead. The append is skipped (`persisted: false`) when the
37
+ * head already holds a chunk with the same `cycleId`, or when the chunk is wholly covered by
38
+ * the head's cursor. Chunks carry `lastObservedAt = max message time + 1ms`, so a chunk is
39
+ * wholly covered iff `head.lastObservedAt >= chunk.lastObservedAt - 1ms`.
40
+ */
41
+ interface UpdateBufferedObservationsResult {
42
+ /** Whether the chunk was stored. */
43
+ persisted: boolean;
44
+ /** The record the chunk was written to (or would have been written to). */
45
+ recordId: string;
46
+ }
47
+ /** The fields of an observational memory record that decide head order. */
48
+ interface ObservationalMemoryHeadOrderFields {
49
+ generationCount: number;
50
+ createdAt: Date | string;
51
+ id: string;
52
+ }
53
+ /**
54
+ * Canonical head order: `generationCount DESC, createdAt ASC, id ASC`.
55
+ * Returns a negative number when `a` sorts before `b` (i.e. `a` is the better head candidate).
56
+ */
57
+ declare function compareObservationalMemoryHeadOrder(a: ObservationalMemoryHeadOrderFields, b: ObservationalMemoryHeadOrderFields): number;
58
+ /**
59
+ * A buffered chunk stores `lastObservedAt = max message time + 1ms`. It is wholly covered
60
+ * (every message it observed is already at or before the cursor) iff
61
+ * `cursor >= chunk.lastObservedAt - 1ms`.
62
+ */
63
+ declare function isBufferedChunkCoveredByCursor(chunkLastObservedAt: Date | string, cursor: Date | string | null | undefined): boolean;
64
+ /** The later of two cursors; `undefined` only when both are absent. */
65
+ declare function maxObservationCursor(a: Date | string | null | undefined, b: Date | string | null | undefined): Date | undefined;
66
+ interface ReflectionTextPlan {
67
+ /** `equal`: stored text equals the snapshot. `append`: observations were appended after the snapshot. */
68
+ mode: 'equal' | 'append';
69
+ observations: string;
70
+ tokenCount: number;
71
+ }
72
+ /**
73
+ * Decide the new generation's text when a reflection built from `snapshot` commits against
74
+ * the stored record.
75
+ *
76
+ * - Stored text equals the snapshot: the reflection replaces it.
77
+ * - Stored text extends the snapshot (activation only appends): the appended tail is kept
78
+ * after the reflection, and its tokens are added.
79
+ * - Anything else (a non-append rewrite): `null` — the reflection must not be applied.
80
+ */
81
+ declare function planReflectionGenerationText(input: {
82
+ storedObservations: string;
83
+ storedObservationTokenCount: number;
84
+ snapshotObservations: string;
85
+ snapshotObservationTokenCount: number;
86
+ reflection: string;
87
+ tokenCount: number;
88
+ }): ReflectionTextPlan | null;
89
+ /**
90
+ * Backs `updateActiveObservations` in adapters that implement `commitActiveObservations`.
91
+ * Callers of `updateActiveObservations` (which resolves `void`) can't see a commit result, so a
92
+ * write that was not applied throws instead of resolving as if it had committed.
93
+ */
94
+ declare function assertActiveObservationsApplied(result: UpdateActiveObservationsResult, id: string): void;
95
+ /**
96
+ * Whether the stored text can still accept a reflection built from `snapshot`
97
+ * (equal, or only appended to since).
98
+ */
99
+ declare function isAppendOnlySince(storedObservations: string, snapshotObservations: string): boolean;
100
+ //#endregion
101
+ export { OBSERVATIONAL_MEMORY_LIFECYCLE_COLUMNS, ObservationalMemoryHeadOrderFields, ReflectionTextPlan, UpdateActiveObservationsResult, UpdateBufferedObservationsResult, assertActiveObservationsApplied, compareObservationalMemoryHeadOrder, isAppendOnlySince, isBufferedChunkCoveredByCursor, maxObservationCursor, planReflectionGenerationText, withObservationalMemoryLifecycleColumns };
102
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -3,7 +3,7 @@ name: mastra-oracledb
3
3
  description: Documentation for @mastra/oracledb. Use when working with @mastra/oracledb APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/oracledb"
6
- version: "0.5.1-alpha.0"
6
+ version: "0.5.2-alpha.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.5.1-alpha.0",
2
+ "version": "0.5.2-alpha.0",
3
3
  "package": "@mastra/oracledb",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -76,7 +76,7 @@ console.log(secondResponse.text)
76
76
 
77
77
  The `resource` identifies the user or entity, while the `thread` identifies the conversation. Reuse both values to continue the same conversation. Observational Memory processes the stored history as it grows and replaces older messages with observations when its activation conditions are met.
78
78
 
79
- The agent now has humanlike long-term memory that persists across conversations. Setting `observationalMemory: true` uses `google/gemini-2.5-flash` by default. To use a different model, pass it in the config object:
79
+ The agent now has humanlike long-term memory that persists across conversations. Setting `observationalMemory: true` uses automatic model selection. To use a specific model, pass it in the config object:
80
80
 
81
81
  ```typescript
82
82
  const memory = new Memory({
@@ -98,7 +98,17 @@ See [configuration options](https://mastra.ai/reference/memory/observational-mem
98
98
  >
99
99
  > For an AI SDK example, see [Using Mastra Memory](https://mastra.ai/integrations/agentic-ui/ai-sdk-ui).
100
100
 
101
- > **Note:** OM currently only supports `@mastra/pg`, `@mastra/libsql`, `@mastra/mysql`, `@mastra/mongodb`, `@mastra/convex`, and `@mastra/oracledb` storage adapters. It uses background agents for managing memory. When no model is set, the default model is `google/gemini-2.5-flash`.
101
+ > **Note:** OM currently only supports `@mastra/pg`, `@mastra/libsql`, `@mastra/mysql`, `@mastra/mongodb`, `@mastra/convex`, and `@mastra/oracledb` storage adapters. It uses background agents for managing memory. When no model is set, the default is `model: 'auto'`.
102
+
103
+ ### Automatic models in Mastra Code and Factory
104
+
105
+ Mastra Code and Factory surface the same automatic selection as a per-role **Auto** option in their settings.
106
+
107
+ An automatic Observer or Reflector follows the active main model provider. Mastra Code uses that provider's built-in low-cost Observational Memory model when one is configured. For custom providers and providers without a built-in choice, it uses the active main model. The effective model is resolved when the role runs, so changing the main model doesn't rewrite the saved automatic selection.
108
+
109
+ Select an explicit model to pin one role independently. Reset that role to **Auto** to resume following the main model provider. Existing saved concrete model IDs remain explicit until you reset them.
110
+
111
+ Factory stores automatic intent separately from the effective model shown in settings. A missing provider credential can mark that effective model unavailable, but it doesn't replace the saved selection or prevent Factory from starting.
102
112
 
103
113
  ## Temporal gap markers
104
114
 
@@ -458,7 +468,7 @@ With default settings, the context window doesn't grow unbounded. It oscillates
458
468
  3. **Repeat**: History grows from \~6k back toward 30k and shrinks again. Each cycle appends to the observation log, which grows much more slowly than raw history.
459
469
  4. **Observations reach 40k**: The Reflector creates a smaller log from the current observations and any earlier reflections.
460
470
 
461
- In the normal buffered cycle, raw history oscillates between roughly 6k and 30k tokens. The observation log stays around 40k tokens, however long the conversation runs. These are activation thresholds rather than hard caps, so history can grow past the threshold whenever background buffering doesn't keep pace. Above `blockAfter` (default `1.2`, \~36k tokens) activation is allowed to overshoot the retention target instead of activating fewer chunks. It doesn't drain the buffer, and with the default settings it removes the same amount of history as below the threshold. Reflection falls back to a synchronous run above its own `blockAfter` (\~48k tokens).
471
+ In the normal buffered cycle, raw history oscillates between roughly 6k and 30k tokens. The observation log stays around 40k tokens, however long the conversation runs. These are activation thresholds rather than hard caps, so history can grow past the threshold whenever background buffering doesn't keep pace. Between the threshold and `blockAfter` (default `1.2`, \~36k tokens), buffering keeps running in the background and the next ready chunk activates without blocking the agent. At `blockAfter`, a synchronous observation runs if no buffered chunk activates. Reflection falls back to a synchronous run above its own `blockAfter` (\~48k tokens).
462
472
 
463
473
  With [`shareTokenBudget`](https://mastra.ai/reference/memory/observational-memory) enabled, the two budgets pool together. While the observation log is small, message history can expand into the unused observation space (up to \~70k tokens with the defaults) before observation triggers. It then shrinks as observations accumulate.
464
474
 
@@ -570,11 +580,30 @@ The progress bars update live while the agent is observing or reflecting, showin
570
580
 
571
581
  ## Models
572
582
 
573
- The Observer and Reflector run in the background. Any model that works with Mastra's [model routing](https://mastra.ai/models) (`provider/model`) can be used. When no model is set, the default model is `google/gemini-2.5-flash`.
583
+ The Observer and Reflector run in the background. Any model that works with Mastra's [model routing](https://mastra.ai/models) (`provider/model`) can be used. When no model is set, the default is `model: 'auto'`.
584
+
585
+ Automatic selection uses `google/gemini-2.5-flash` when `GOOGLE_GENERATIVE_AI_API_KEY` is configured. Otherwise, it selects a low-cost model for the active main model's provider, then falls back to the active main model. Observer and Reflector resolve independently before each invocation.
586
+
587
+ Automatic selection reads the main model's `provider/model` ID. Models created from a plain model string carry that ID, and a dynamic model function can label a custom model by returning `{ model, id }`. When the ID is unknown, such as a model with a custom URL, API key, headers, or gateway, automatic selection reuses the main model itself because a sibling model isn't known to share that route. Pass a concrete model ID to the role to override either behavior.
588
+
589
+ Use `autoModels` to change the model picked for a provider, and `resolveModel` to create the picked model with your own credentials or routing:
590
+
591
+ ```typescript
592
+ const memory = new Memory({
593
+ options: {
594
+ observationalMemory: {
595
+ autoModels: { openai: 'openai/gpt-5-nano' },
596
+ resolveModel: (modelId, { requestContext }) => createRoutedModel(modelId, requestContext),
597
+ },
598
+ },
599
+ })
600
+ ```
601
+
602
+ `resolveAutoModelId(mainModelId, { autoModels })` from `@mastra/memory` returns the model automatic selection would pick, which is useful for showing the effective model in a settings UI.
574
603
 
575
604
  Mastra recommends using a model that has a large context window (128K+ tokens) and is fast enough to run in the background without slowing down your actions.
576
605
 
577
- If you're unsure which model to use, start with the default `google/gemini-2.5-flash`. We've also successfully tested `openai/gpt-5-mini`, `anthropic/claude-haiku-4-5`, `deepseek/deepseek-reasoner`, `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash`, `xai/grok-4-1-fast`, `qwen3`, and `glm-4.7`.
606
+ To pin a model instead, set a concrete model ID. We've also successfully tested `openai/gpt-5-mini`, `anthropic/claude-haiku-4-5`, `deepseek/deepseek-reasoner`, `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash`, `xai/grok-4-1-fast`, `qwen3`, and `glm-4.7`.
578
607
 
579
608
  ```typescript
580
609
  const memory = new Memory({
@@ -767,23 +796,25 @@ When message tokens reach the `messageTokens` threshold, buffered chunks activat
767
796
 
768
797
  Async buffered Observer calls don't generate continuation hints because delayed hints can be stale by activation time. When buffered chunks activate, any previously stored suggested response and current task are cleared. The main agent receives the compressed observations without those hints.
769
798
 
770
- When message production outpaces the Observer, the `blockAfter` safety threshold allows activation to overshoot the retention target instead of using fewer chunks. Activation still uses no more chunks than needed to reach the target, and the default settings remain unaffected. A synchronous observation runs when the `messageTokens` threshold is reached and activating buffered chunks doesn't bring pending tokens back under it. For example, this happens when a large tool result arrives after buffering has stopped. Buffered activation usually preserves a minimum remaining context (the smaller of \~1k tokens or the configured retention floor), but a single buffered chunk that covers the whole pending window still activates and can leave less.
799
+ When message production outpaces the Observer, history can pass `messageTokens` before a buffered chunk is ready. Between `messageTokens` and `blockAfter`, Mastra keeps buffering in the background and activates the chunk on a later step, so the agent doesn't wait on the Observer. A synchronous observation runs if pending tokens still reach `blockAfter` after buffered activation, for example when a large tool result leaves an uncovered tail. Above `blockAfter`, activation may also overshoot the retention target instead of using fewer chunks. Buffered activation usually preserves a minimum remaining context (the smaller of \~1k tokens or the configured retention floor), but a single buffered chunk that covers the whole pending window still activates and can leave less.
800
+
801
+ At `blockAfter`, activation waits a bounded time for a buffered observation that's still being produced so it activates with the rest. If that takes longer, Mastra observes the remaining messages synchronously. A late observation whose messages are all already observed is then dropped instead of stored. Activations triggered by the idle TTL or a model change don't wait.
771
802
 
772
803
  Reflection works similarly: the Reflector runs in the background when observations reach a fraction of the reflection threshold.
773
804
 
774
805
  ### Settings
775
806
 
776
- | Setting | Default | What it controls |
777
- | ------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
778
- | `observation.bufferTokens` | `0.2` | How often to buffer. `0.2` means every 20% of `messageTokens`. With the default 30k threshold, that's roughly every 6k tokens. Can also be an absolute token count (e.g. `5000`). |
779
- | `observation.bufferActivation` | `0.8` | How aggressively to clear the message window on activation. `0.8` means remove enough messages to keep only 20% of `messageTokens` remaining. Lower values keep more message history. |
780
- | `observation.blockAfter` | `1.2` | Safety net if buffering can't keep up. Values from 1 up to (but not including) 100 multiply `messageTokens`. For example, `1.2` creates a threshold of 36k tokens (1.2 × 30k), above which activation may overshoot the retention target rather than use fewer chunks. Values of 100 or more are absolute token counts (e.g. `50_000`) and must be greater than `messageTokens`. |
781
- | `activateAfterIdle` | none | Forces buffered observations to activate after a period of inactivity, even before `observation.messageTokens` is reached. Accepts a numeric millisecond value such as `300_000`, duration strings like `"5m"` or `"1hr"`, `"auto"` for a provider-aware prompt cache TTL, or an object of per-provider TTLs such as `{ default: 'auto', anthropic: '1h' }`. |
782
- | `activateOnProviderChange` | `false` | Forces buffered observations to activate when the next step uses a different `provider/model` than the one that produced the latest assistant step. Use this when switching providers or models would invalidate prompt cache reuse. |
783
- | `reflection.bufferActivation` | `0.5` | When to start background reflection. `0.5` means reflection begins when observations reach 50% of the `observationTokens` threshold. |
784
- | `reflection.activateAfterIdle` | none | Opts buffered reflections into idle activation. Reflections don't inherit top-level `activateAfterIdle`. |
785
- | `reflection.activateOnProviderChange` | `false` | Opts buffered reflections into provider-change activation. Reflections don't inherit top-level `activateOnProviderChange`. |
786
- | `reflection.blockAfter` | `1.2` | Safety threshold for reflection. Same value format as observation (absolute values must be greater than `observationTokens`), but above it reflection runs synchronously when no buffered reflection is ready to activate. |
807
+ | Setting | Default | What it controls |
808
+ | ------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
809
+ | `observation.bufferTokens` | `0.2` | How often to buffer. `0.2` means every 20% of `messageTokens`. With the default 30k threshold, that's roughly every 6k tokens. Can also be an absolute token count (e.g. `5000`). |
810
+ | `observation.bufferActivation` | `0.8` | How aggressively to clear the message window on activation. `0.8` means remove enough messages to keep only 20% of `messageTokens` remaining. Lower values keep more message history. |
811
+ | `observation.blockAfter` | `1.2` | Safety net if buffering can't keep up. Values from 1 up to (but not including) 100 multiply `messageTokens`. For example, `1.2` creates a threshold of 36k tokens (1.2 × 30k). Below it, only background buffering and activation run. Observation runs synchronously if pending tokens still reach it after buffered activation. Activation may also overshoot the retention target. Values of 100 or more are absolute token counts (e.g. `50_000`) and must be greater than `messageTokens`. Set `1` to observe synchronously at `messageTokens`. |
812
+ | `activateAfterIdle` | none | Forces buffered observations to activate after a period of inactivity, even before `observation.messageTokens` is reached. Accepts a numeric millisecond value such as `300_000`, duration strings like `"5m"` or `"1hr"`, `"auto"` for a provider-aware prompt cache TTL, or an object of per-provider TTLs such as `{ default: 'auto', anthropic: '1h' }`. |
813
+ | `activateOnProviderChange` | `false` | Forces buffered observations to activate when the next step uses a different `provider/model` than the one that produced the latest assistant step. Use this when switching providers or models would invalidate prompt cache reuse. |
814
+ | `reflection.bufferActivation` | `0.5` | When to start background reflection. `0.5` means reflection begins when observations reach 50% of the `observationTokens` threshold. |
815
+ | `reflection.activateAfterIdle` | none | Opts buffered reflections into idle activation. Reflections don't inherit top-level `activateAfterIdle`. |
816
+ | `reflection.activateOnProviderChange` | `false` | Opts buffered reflections into provider-change activation. Reflections don't inherit top-level `activateOnProviderChange`. |
817
+ | `reflection.blockAfter` | `1.2` | Safety threshold for reflection. Same value format as observation (absolute values must be greater than `observationTokens`), but above it reflection runs synchronously when no buffered reflection is ready to activate. |
787
818
 
788
819
  If you're relying on prompt caching, set `activateAfterIdle` to `"auto"` or to a specific cache TTL. That way, once a thread has been idle long enough for the cache to expire, the next request can activate buffered observations first and send a smaller compressed context window.
789
820
 
@@ -880,9 +911,9 @@ const memory = new Memory({
880
911
  })
881
912
  ```
882
913
 
883
- - `previousObserverTokens: 2000` → default. Keeps \~2k tokens of recent observations.
914
+ - `previousObserverTokens: false` → default. Sends the full previous observations.
915
+ - `previousObserverTokens: 2000` → keeps \~2k tokens of recent observations.
884
916
  - `previousObserverTokens: 0` → omit previous observations completely.
885
- - `previousObserverTokens: false` → disable truncation and keep full previous observations.
886
917
 
887
918
  ## Hooks
888
919
 
@@ -95,29 +95,32 @@ Set the `retention` field on the store config.
95
95
 
96
96
  Each domain specifies its age-prunable tables and the timestamp column that anchors comparison, chosen so `maxAge` matches the meaning of the data. Append-only logs use creation time, live state uses last activity, and jobs or runs use completion time so in-flight work isn't pruned.
97
97
 
98
- | Domain | Table key | Anchor column | `maxAge` measures |
99
- | ----------------- | ------------------ | ---------------- | ---------------------------------------------------------------- |
100
- | `memory` | `threads` | `createdAt` | Thread age |
101
- | `memory` | `messages` | `createdAt` | Message age |
102
- | `memory` | `resources` | `createdAt` | Resource age |
103
- | `threadState` | `threadState` | `updatedAt` | Inactivity: state for still-active threads survives |
104
- | `observability` | `spans` | `startedAt` | Span age |
105
- | `observability` | `metrics` | `timestamp` | Metric event age (vNext only) |
106
- | `observability` | `logs` | `timestamp` | Log event age (vNext only) |
107
- | `observability` | `scores` | `timestamp` | Score event age (vNext only) |
108
- | `observability` | `feedback` | `timestamp` | Feedback event age (vNext only) |
109
- | `scores` | `scorers` | `createdAt` | Score record age |
110
- | `workflows` | `workflowSnapshot` | `updatedAt` | Inactivity, suspended or long-running workflows survive |
111
- | `backgroundTasks` | `backgroundTasks` | `completedAt` | Time since completion, in-flight tasks (`NULL`) are never pruned |
112
- | `experiments` | `experiments` | `completedAt` | Time since completion, running experiments are never pruned |
113
- | `notifications` | `notifications` | `createdAt` | Notification age |
114
- | `harness` | `sessions` | `createdAt` | Session record age |
115
- | `schedules` | `triggers` | `actual_fire_at` | Fire-history age (epoch-ms column) |
98
+ | Domain | Table key | Anchor column | `maxAge` measures |
99
+ | ----------------- | ------------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------- |
100
+ | `memory` | `threads` | `createdAt` | Thread age |
101
+ | `memory` | `messages` | `createdAt` | Message age |
102
+ | `memory` | `resources` | `createdAt` | Resource age |
103
+ | `memory` | `runFences` | `retiredAt` | Time since the durable agent execution holding the fence settled. Fences of running executions (`NULL`) are never pruned |
104
+ | `threadState` | `threadState` | `updatedAt` | Inactivity: state for still-active threads survives |
105
+ | `observability` | `spans` | `startedAt` | Span age |
106
+ | `observability` | `metrics` | `timestamp` | Metric event age (vNext only) |
107
+ | `observability` | `logs` | `timestamp` | Log event age (vNext only) |
108
+ | `observability` | `scores` | `timestamp` | Score event age (vNext only) |
109
+ | `observability` | `feedback` | `timestamp` | Feedback event age (vNext only) |
110
+ | `scores` | `scorers` | `createdAt` | Score record age |
111
+ | `workflows` | `workflowSnapshot` | `updatedAt` | Inactivity, suspended or long-running workflows survive |
112
+ | `workflows` | `runOwnership` | `updatedAt` | Inactivity. A running durable agent execution renews its claim every 10 seconds, so keep `maxAge` well above its 30-second lease |
113
+ | `backgroundTasks` | `backgroundTasks` | `completedAt` | Time since completion, in-flight tasks (`NULL`) are never pruned |
114
+ | `experiments` | `experiments` | `completedAt` | Time since completion, running experiments are never pruned |
115
+ | `notifications` | `notifications` | `createdAt` | Notification age |
116
+ | `harness` | `sessions` | `createdAt` | Session record age |
117
+ | `schedules` | `triggers` | `actual_fire_at` | Fire-history age (epoch-ms column) |
116
118
 
117
119
  > **Note:**
118
120
  >
119
121
  > - The memory `observational_memory` table has no timestamp anchor, so it can't be age-pruned and isn't a valid retention key.
120
122
  > - Experiments prune as whole units: an aged experiment's result rows are deleted together with it (results cascade with their parent), so a run is never left partially deleted. Retention doesn't have a separate `results` key.
123
+ > - `runOwnership` and `runFences` hold the run fencing records of durable agents, one small record per run in each. If the `runOwnership` `maxAge` is too short, a running execution can lose its run, but an execution that lost a run still can't write. A fence is retired only when its execution settles, so the fence of a run that crashed and was never recovered is never pruned.
121
124
  > - For `schedules`, the growth table is the fire history (`schedule_triggers`, one row per fire): schedule definitions are config and aren't pruned.
122
125
  > - On PostgreSQL, timestamp anchors use the timezone-aware mirror columns (for example `createdAtZ`, `completedAtZ`).
123
126
  > - DuckDB observability stores append-only events for all five signals. Its `spans` policy uses the event `timestamp` column rather than `startedAt`.