You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(memory): add provider-aware OM idle activation (#16663)
This adds `activateAfterIdle: 'auto'` for observational memory so idle
activations can line up better with the actor model's prompt-cache
behavior. The resolver uses the current provider/model plus provider
options like OpenAI `promptCacheRetention`, and falls back to a short 5
minute TTL when it can't tell.
```ts
memory: new Memory({
options: {
observationalMemory: {
model: 'openai/gpt-5.5',
activateAfterIdle: 'auto',
},
},
})
```
This also updates MastraCode to use `auto`, adds a small idle counter
above the input so it's easier to see when idle activation should
happen, and combines back-to-back OM activation markers into one line.
Verified with the focused memory/core/MastraCode tests, typecheck,
prettier, and lint.
With `"auto"`, Mastra chooses an idle activation TTL from the active
model provider:
| Provider | Auto TTL |
| - | - |
| Anthropic, OpenRouter, unknown providers, xAI | 5 minutes |
| DeepSeek | 1 hour |
| Google Gemini | 24 hours |
| Groq | 2 hours |
| OpenAI with `providerOptions.openai.promptCacheRetention: "24h"` | 1
hour (safer cause 24h means up to 24h) |
| OpenAI with `providerOptions.openai.promptCacheRetention: "in_memory"`
| 5 minutes |
| OpenAI `gpt-4*`, `gpt-5`, `gpt-5-*`, `gpt-5.1*`, `gpt-5.2*`,
`gpt-5.3*`, and `gpt-5.4*` | 5 minutes |
| Other OpenAI models | 1 hour |
to test this out I also added an idle time counter in mc
<img width="511" height="169" alt="Screenshot 2026-05-15 at 2 12 46 PM"
src="https://github.com/user-attachments/assets/9fb2b6f8-9451-40e1-ab5e-d083614dc664"
/>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## ELI5
This PR makes observational memory's "wake-up after being idle" setting
smart: set it to 'auto' and Mastra picks a timeout based on the active
model/provider instead of always using 5 minutes. The TUI shows how long
the session has been idle and collapses multiple memory-activation
messages into one.
## Overview
Introduce provider-aware idle activation for Observational Memory via a
new activateAfterIdle: 'auto' option. When configured, a resolver
computes a provider- and model-specific TTL (with sensible fallbacks)
and the runtime uses that resolved TTL for activation checks and emitted
activation markers.
## Key Changes
### TTL resolution & types
- New resolver and mapping:
packages/memory/src/processors/observational-memory/activation-ttl.ts
- Provider/model-to-TTL mapping (high-level):
- Anthropic, OpenRouter, xAI, unknown → 5 minutes
- DeepSeek → 1 hour
- Google Gemini → 24 hours
- Groq → 2 hours
- OpenAI: respects providerOptions.openai.promptCacheRetention when
present (e.g., "24h" → conservative 1 hour, "in_memory" → 5 minutes);
specific short-retention OpenAI model patterns (gpt-4*, gpt-5, gpt-5-*,
gpt-5.1*–gpt-5.4*) → 5 minutes; other OpenAI models → 1 hour
- New exported functions: resolveActivationTTL(...) and
resolveAutoActivationTTL(...)
- Types updated to accept 'auto' and carry resolved values:
- packages/core/src/memory/types.ts
- packages/memory/src/processors/observational-memory/types.ts (adds
ResolvedActivationTTL)
### Observational Memory integration
- parseActivationTTL preserves literal 'auto' and activation-time
resolution uses resolveActivationTTL:
-
packages/memory/src/processors/observational-memory/observational-memory.ts
-
packages/memory/src/processors/observational-memory/reflector-runner.ts
- When TTL-triggered activation occurs, activation markers emit the
resolved numeric TTL so clients display the actual timeout.
### ProviderOptions flow (plumbing)
- Ensure providerOptions are available and merged where needed so auto
resolution can read provider behavior:
- packages/core/src/agent/agent.ts,
packages/core/src/agent/agent-legacy.ts
- Durable execution: packages/core/src/agent/durable/* (types,
serialization, LLM execution step)
- LLM layer: MastraLLMVNext.getProviderOptions()
(packages/core/src/llm/model/model.loop.ts)
- Workflow step merging:
packages/core/src/loop/workflows/agentic-execution/llm-execution-step.ts
### MastraCode UI & UX
- Idle counter: IdleCounterComponent shows "X … idle" above input after
1 minute of inactivity and formatIdleDuration added
(mastracode/src/tui/components/idle-counter.ts).
- Idle timer lifecycle and seeding at startup:
mastracode/src/tui/mastra-tui.ts; new state fields (idleCounter,
idleStartedAt, lastRenderedMessageAt).
- OM activation marker collapsing: consecutive observation activations
are combined into one marker with aggregated tokens and activationCount
(mastracode/src/tui/handlers/om.ts,
mastracode/src/tui/components/om-marker.ts).
- MastraCode now configures observationalMemory.activateAfterIdle:
'auto' (mastracode/src/agents/memory.ts).
- Activation markers show resolved numeric TTL when available.
### Tests
- TTL resolution tests:
packages/memory/src/processors/observational-memory/__tests__/activation-ttl.test.ts
- Observational memory API emits resolved TTL in markers:
packages/memory/src/processors/observational-memory/__tests__/observational-memory-api.test.ts
- Memory config serialization:
packages/core/src/memory/memory-config.test.ts
- ProviderOptions forwarding:
packages/core/src/agent/__tests__/per-model-fallback-settings.test.ts
- MastraCode UI tests: idle-counter.test.ts, om-marker.test.ts,
om.test.ts, render-messages.test.ts
### Docs & release notes
- Docs updated to document activateAfterIdle: 'auto' and provider-to-TTL
mapping:
- docs/src/content/en/docs/memory/observational-memory.mdx
- docs/src/content/en/reference/memory/observational-memory.mdx (option
type signatures updated)
- Changesets: .changeset/brave-caches-observe.md and
.changeset/quiet-walls-rest.md
## Notes for reviewers / behavior highlights
- 'auto' is preserved in serialized configs; runtime resolves to a
numeric TTL for activation checks and emitted markers.
- Default fallback when provider/model is unknown is 5 minutes.
- OpenAI handling prefers promptCacheRetention when provided via
providerOptions and uses a conservative mapping.
- This PR touches runtime resolution, types, durable serialization,
provider-options plumbing, and MastraCode TUI rendering; tests were
added across core, memory, and MastraCode.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/mastra-ai/mastra/pull/16663?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
Added `activateAfterIdle: "auto"` for Observational Memory early activation.
7
+
8
+
Mastra can now choose an idle activation timeout from the active model provider's prompt cache behavior. OpenAI also respects `providerOptions.openai.promptCacheRetention` when available.
Updated MastraCode to use provider-aware Observational Memory idle activation.
6
+
7
+
MastraCode now sets `activateAfterIdle: "auto"`, shows an idle-time counter above the input after one minute of inactivity, and combines back-to-back OM activation markers into a single line.
Copy file name to clipboardExpand all lines: docs/src/content/en/docs/memory/observational-memory.mdx
+19-6Lines changed: 19 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -104,7 +104,7 @@ const memory = new Memory({
104
104
options: {
105
105
observationalMemory: {
106
106
model: 'google/gemini-2.5-flash',
107
-
activateAfterIdle: '5m',
107
+
activateAfterIdle: 'auto',
108
108
activateOnProviderChange: true,
109
109
},
110
110
},
@@ -495,30 +495,43 @@ Reflection works similarly — the Reflector runs in the background when observa
495
495
|`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`). |
496
496
|`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. |
497
497
|`observation.blockAfter`|`1.2`| Safety threshold as a multiplier of `messageTokens`. At `1.2`, synchronous observation is forced at 36k tokens (1.2 × 30k). Only matters if buffering can't keep up. |
498
-
|`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`, or duration strings like `"5m"` or `"1hr"`. Set this to your prompt cache TTL if you want activation to happen before the next cold prompt. |
498
+
|`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"`, or `"auto"` for a provider-aware prompt cache TTL. |
499
499
|`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. |
500
500
|`reflection.bufferActivation`|`0.5`| When to start background reflection. `0.5` means reflection begins when observations reach 50% of the `observationTokens` threshold. |
|`reflection.blockAfter`|`1.2`| Safety threshold for reflection, same logic as observation. |
504
504
505
-
If you're relying on prompt caching, set `activateAfterIdle` to match your 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.
505
+
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.
506
+
507
+
With `"auto"`, Mastra chooses an idle activation TTL from the active model provider:
With a 5-minute prompt cache TTL, this activates buffered observations after 5 minutes of inactivity so the next uncached prompt uses compressed observations instead of a larger raw message window. If you prefer, `300_000` works the same way.
532
+
With `"auto"`, this activates buffered observations based on the active provider's prompt cache behavior so the next uncached prompt uses compressed observations instead of a larger raw message window. If you prefer a fixed 5-minute TTL, use `"5m"` or `300_000`.
520
533
521
-
Changing model or providers mid-thread will invalidate the prompt cache. If your agent can switch between providers or models mid-thread, `activateOnProviderChange: true` forces buffered observations to activate before the new provider runs. That avoids sending a large raw window to a provider that can't reuse the previous prompt cache.
534
+
Changing models or providers mid-thread will invalidate the prompt cache. If your agent can switch between providers or models mid-thread, `activateOnProviderChange: true` forces buffered observations to activate before the new provider runs. That avoids sending a large raw window to a provider that can't reuse the previous prompt cache.
Copy file name to clipboardExpand all lines: docs/src/content/en/reference/memory/observational-memory.mdx
+6-6Lines changed: 6 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -66,9 +66,9 @@ OM performs thresholding with fast local token estimation. Text uses `tokenx`, a
66
66
},
67
67
{
68
68
name: 'activateAfterIdle',
69
-
type: 'number | string | false',
69
+
type: 'number | string | false | "auto"',
70
70
description:
71
-
'Time before buffered observations are forced to activate after inactivity, even before `observation.messageTokens` is reached. Accepts a numeric millisecond value such as `300_000`, duration strings like `"5m"` or `"1hr"`, or `false` to disable inherited observation idle activation. Reflections do not inherit this setting. Use `reflection.activateAfterIdle` to opt reflections into idle activation.',
71
+
'Time before buffered observations are forced to activate after 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 `false` to disable inherited observation idle activation. Reflections do not inherit this setting. Use `reflection.activateAfterIdle` to opt reflections into idle activation.',
72
72
isOptional: true,
73
73
},
74
74
{
@@ -204,9 +204,9 @@ OM performs thresholding with fast local token estimation. Text uses `tokenx`, a
204
204
},
205
205
{
206
206
name: 'activateAfterIdle',
207
-
type: 'number | string | false',
207
+
type: 'number | string | false | "auto"',
208
208
description:
209
-
'Time before buffered observations are forced to activate after inactivity. Accepts milliseconds, a duration string, or `false`. If unset, the top-level `activateAfterIdle` value is used for observations. Set `false` to disable the top-level idle setting for observations.',
209
+
'Time before buffered observations are forced to activate after inactivity. Accepts milliseconds, a duration string, `"auto"` for a provider-aware prompt cache TTL, or `false`. If unset, the top-level `activateAfterIdle` value is used for observations. Set `false` to disable the top-level idle setting for observations.',
210
210
isOptional: true,
211
211
},
212
212
{
@@ -305,9 +305,9 @@ OM performs thresholding with fast local token estimation. Text uses `tokenx`, a
305
305
},
306
306
{
307
307
name: 'activateAfterIdle',
308
-
type: 'number | string | false',
308
+
type: 'number | string | false | "auto"',
309
309
description:
310
-
'Time before buffered reflections are forced to activate after inactivity. Accepts milliseconds, a duration string, or `false`. Reflections do not inherit top-level `activateAfterIdle`; set this explicitly to opt reflections into idle activation.',
310
+
'Time before buffered reflections are forced to activate after inactivity. Accepts milliseconds, a duration string, `"auto"` for a provider-aware prompt cache TTL, or `false`. Reflections do not inherit top-level `activateAfterIdle`; set this explicitly to opt reflections into idle activation.',
0 commit comments