feat(memory): add OM-managed working memory#18654
Conversation
🦋 Changeset detectedLatest commit: 3a3e149 The changes in this PR will be included in the next version bump. This PR includes changesets to release 25 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
WalkthroughThis PR adds an ChangesObserver-managed working memory
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
PR triageLinked issue check skipped for core contributor @TylerBarnes. PR complexity score
Applied label: Changed test gateChanged tests passed against the base branch; they should fail before the PR code is applied. Label: |
331daf4 to
7fe1030
Compare
5d3d2b8 to
298e17e
Compare
298e17e to
298d8e0
Compare
7fe1030 to
830b573
Compare
298d8e0 to
230d9a5
Compare
b223ad4 to
c7de507
Compare
c504e9b to
78777fc
Compare
c7de507 to
de1166b
Compare
78777fc to
616b2bf
Compare
## Summary
Adds the observational memory extractor pipeline.
Extractors let OM capture structured values alongside observations,
persist selected values into thread metadata, and carry extraction
failures through the OM lifecycle. Extractor instructions and schemas
can be static or resolved from runtime context, so extraction can adapt
to the active agent and thread.
```ts
observationalMemory: {
enabled: true,
observation: {
extract: [
new Extractor({
slug: 'priority',
name: 'Priority',
schema: z.enum(['low', 'medium', 'high']),
instructions: 'Extract the user priority when it changes.',
}),
],
},
}
```
## Test plan
- `pnpm --filter ./packages/memory test:unit -- --run
src/processors/observational-memory/__tests__/extractor.test.ts
src/processors/observational-memory/__tests__/observational-memory-api.test.ts
--reporter=dot --bail 1`
- `pnpm --filter ./packages/memory check`
- Stack rebase verified linear against #18652
## Stack
- Previous: #18652
- Next: #18654
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
ELI5: This PR makes “observational memory” not only remember what
happened, but also automatically pull out specific structured facts
(like a user’s priority), save them on the thread, and reuse them later.
It also adds an `Extractor` API so you can define how those facts are
detected, validated, and what happens when extraction fails.
### Summary
- Added a public `Extractor` API for Observational Memory with:
- **inline (XML/tag-based)** and **structured (Zod/schema-based)**
extraction modes
- slug validation, runtime-resolved/dynamic `instructions` and optional
`schema`
- per-extractor `onExtracted` hooks to normalize/transform values and
record failures safely.
- Extended Observational Memory configuration with extraction pipelines:
- `observationalMemory.observation.extract` and
`observationalMemory.reflection.extract`
- built-in extractors (current task, suggested response, thread title)
composed with user extractors.
- Implemented extraction end-to-end across observer/reflector:
- prompt generation includes extractor output sections and optional
“prior extracted values”
- parsing supports extracting values from model output while stripping
extractor sections
- structured extraction retries/fallback behavior is handled, and
extracted values are validated/merged.
- Persisted extracted values into thread observational memory metadata
under `threadOMMetadata.extracted`, including carry-forward of prior
extracted values into subsequent observer/reflector runs.
- Propagated extraction failures through the OM lifecycle (without
blocking successful extractions):
- added `extractionFailures` alongside `extractedValues` in OM end
markers and marker/telemetry payloads.
- Updated exports/types and documentation:
- new/extended types for `extractedValues` and `extractionFailures`
across OM configs, runners, and storage
- added Observational Memory docs sections for “Extractors” plus an
“Extractor API” reference.
- Added tests covering extractor logic and Observational Memory
persistence across:
- extractor unit behavior (inline/structured parsing, retries, hooks,
prompt composition)
- observational-memory API behavior (observe/reflect/buffer) and thread
metadata persistence
- marker schema changes and additional observational-memory lifecycle
assertions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Mastra Code (crof/glm-5.2) <noreply@mastra.ai>
616b2bf to
3d64e85
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
packages/core/src/memory/mock.ts (1)
238-242: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMockMemory.listTools still ignores
readOnlyunlike the realMemory.listTools.The real
Memory.listTools(packages/memory/src/index.ts) gates tool creation onworkingMemoryConfig?.enabled && workingMemoryConfig.agentManaged !== false && !mergedConfig.readOnly. This mock's negated condition only coversenabled/agentManaged, soMockMemorywill still exposeupdateWorkingMemorywhenreadOnlyis true, diverging from production behavior in tests that rely on the mock.♻️ Proposed fix to align with real Memory.listTools
- if (!mergedConfig.workingMemory?.enabled || mergedConfig.workingMemory.agentManaged === false) { + if ( + !mergedConfig.workingMemory?.enabled || + mergedConfig.workingMemory.agentManaged === false || + mergedConfig.readOnly + ) { return {}; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/memory/mock.ts` around lines 238 - 242, MockMemory.listTools is still missing the readOnly gate, so it can expose updateWorkingMemory when the real Memory.listTools would not. Update the conditional in MockMemory.listTools to match Memory.listTools behavior by also checking mergedConfig.readOnly before returning the tool map, keeping the mock aligned with the production logic in the listTools method.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/memory/integration-tests/src/om-extracted-metadata.test.ts`:
- Around line 183-268: The mocked model output in this working-memory
observation test is missing the existing profile field, so the merge assertion
will not preserve the previous value. Update the mock in the observe flow used
by WorkingMemoryExtractor/doGenerate to include the full merged JSON for
“working-memory”, including profile.name along with the new location and
preferences, so getWorkingMemory() can assert the merged state correctly.
In
`@packages/memory/src/processors/observational-memory/__tests__/extractor.test.ts`:
- Around line 192-225: The test name and expectation are inconsistent with the
intended behavior in WorkingMemoryExtractor/onExtracted: once OM metadata is no
longer returned, this case should verify that the extractor does not persist
working-memory metadata in the hook result. Update the assertion in this test to
expect result.values to be undefined or omitted, and keep the title aligned with
the behavior verified by applyExtractorHooks and resolveExtractors.
In
`@packages/memory/src/processors/observational-memory/working-memory-extractor.ts`:
- Around line 71-93: The `onExtracted` hook in `working-memory-extractor` is
persisting working memory and then returning `current`, which causes
`applyExtractorHooks` to keep a duplicate `working-memory` value in
`extractedValues`. Make this hook side-effect-only by keeping the
`updateWorkingMemory` call and changing the return so it yields `undefined`
after persistence, while preserving the existing early exits for
`isSchemaWorkingMemory` and empty `workingMemory`.
---
Nitpick comments:
In `@packages/core/src/memory/mock.ts`:
- Around line 238-242: MockMemory.listTools is still missing the readOnly gate,
so it can expose updateWorkingMemory when the real Memory.listTools would not.
Update the conditional in MockMemory.listTools to match Memory.listTools
behavior by also checking mergedConfig.readOnly before returning the tool map,
keeping the mock aligned with the production logic in the listTools method.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2bca477b-095d-4b26-a60e-7860e834c25c
📒 Files selected for processing (13)
.changeset/honest-llamas-dress.mddocs/src/content/en/docs/memory/observational-memory.mdxdocs/src/content/en/docs/memory/working-memory.mdxdocs/src/content/en/reference/memory/observational-memory.mdxpackages/core/src/memory/mock.tspackages/core/src/memory/types.tspackages/memory/integration-tests/src/om-extracted-metadata.test.tspackages/memory/src/index.test.tspackages/memory/src/index.tspackages/memory/src/processors/observational-memory/__tests__/extractor.test.tspackages/memory/src/processors/observational-memory/index.tspackages/memory/src/processors/observational-memory/types.tspackages/memory/src/processors/observational-memory/working-memory-extractor.ts
Co-Authored-By: Mastra Code (crof/glm-5.2) <noreply@mastra.ai>
Adds a WorkingMemoryExtractor that lets Observational Memory update working memory through the normal extractor pipeline. The extractor reads the active working memory template or schema, writes updates through the memory instance, and does not duplicate the value under OM extracted metadata. Also adds injectTools: boolean (default true) to BaseWorkingMemory config. Set to false when OM extractors or another path owns working memory updates, preventing the working memory tool from being injected into the main agent. Co-Authored-By: Mastra Code (crof/glm-5.2) <noreply@mastra.ai>
Move the unreleased working memory extraction controls to the final agentManaged/manageWorkingMemory API. This keeps the working memory extractor branch from introducing the discarded injectTools name and documents the managed working memory defaults. Co-Authored-By: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
Addresses #18654 (comment) Co-Authored-By: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
Summary
Adds a built-in WorkingMemoryExtractor and a simpler OM-managed working memory setup.
With
manageWorkingMemory, OM can extract working-memory updates from observations and apply them after the turn. The sugar defaults working memory to prompt-cache-friendly state signals and disables direct agent tool management unless the user opts back in.Users who still want both paths can opt in explicitly:
Test plan
pnpm --filter ./packages/memory test:unit -- --run src/processors/observational-memory/__tests__/extractor.test.ts src/processors/observational-memory/__tests__/observational-memory-api.test.ts --reporter=dot --bail 1pnpm --filter ./packages/memory checkStack
ELI5
This PR helps Observational Memory automatically “catch” what changed in your app’s working memory as it watches the conversation, then apply those updates for the agent after the turn. So the agent doesn’t have to remember to call the working-memory tool every time—and the default setup is tuned to stay friendly to prompt caching.
Summary
WorkingMemoryExtractorand re-exported it from the Observational Memory module.observationalMemory.observation.manageWorkingMemoryto let Observational Memory extract working-memory updates and apply them after the turn.workingMemory.agentManagedto control whether the main agent still receives working-memory tool/instructions (agentManageddefaults totruewhen not overridden).manageWorkingMemory: truewithworkingMemory.enabled: true), defaults change to:workingMemory.agentManaged: false(OM applies updates instead of the agent)workingMemory.useStateSignals: true(prompt-cache-friendly)workingMemory.agentManaged: truepreserves the “agent also manages it” path.listTools()/getSystemMessage()/merged-config behavior under the new options.manageWorkingMemory,agentManaged, anduseStateSignals).