feat(tools): SDK-level cross-source memory deduplication - #1531
Conversation
|
Claude finished @Dhravya's task in 1m 35s —— View job Code Review Complete
Overview: Moves memory deduplication from the playground UI into SDK middleware, implementing request-scoped context block replacement across all TypeScript integrations (AI SDK, OpenAI Chat/Responses, Mastra, VoltAgent). Issues found: None — this looks good to ship. Details: The implementation is solid:
Score: 10/10 |
There was a problem hiding this comment.
Overview: Moves memory deduplication from the playground UI into the SDK middleware, implementing request-scoped context block replacement across all TypeScript SDK integrations (AI SDK, OpenAI, Mastra, VoltAgent).
Issues found: None — this looks good to ship.
Details:
The implementation is solid:
-
Request isolation is correct — The
seenMemoriesSet indeduplicateMemories()is created fresh per call, and theinjectedflag in the middlewaremap()callbacks is closure-scoped per request. No global state that could cause cross-user leakage. -
Context block regex is safe — The pattern
/<supermemory context="user-memories" readonly>[\s\S]*?<\/supermemory>/uses non-greedy matching with fixed anchors, avoiding ReDoS concerns. The specific attribute match (context="user-memories" readonly) ensures user-authored supermemory tags with different attributes won't be stripped. -
Normalization logic is sound —
normalizeMemoryFact()strips date prefixes, collapses whitespace, and lowercases for comparison while preserving the original memory string for output. The priority order (static > dynamic > search) is maintained correctly. -
Edge cases handled — Empty memories return early without injecting content. Multiple system messages get the context injected into only the first one, with stale blocks stripped from subsequent ones.
-
Good test coverage — Tests verify context replacement (not accumulation), cross-user isolation, and normalized deduplication.
Score: 10/10
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
supermemory-mcp | d0f53b0 | Sep 01 2026, 06:19 AM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
supermemory-app | d0f53b0 | Commit Preview URL Branch Preview URL |
Sep 01 2026, 06:22 AM |
93f92cd to
4e04a0d
Compare
2fa2e0d to
be97748
Compare
There was a problem hiding this comment.
Overview: Moves memory deduplication from the playground UI into SDK middleware, implementing request-scoped context block replacement across all TypeScript integrations (AI SDK, OpenAI Chat/Responses, Mastra, VoltAgent).
Issues found: None — this looks good to ship.
Details:
The implementation is solid:
-
Request isolation is correct — The
seenMemoriesSet indeduplicateMemories()is created fresh per call, and thememoryCachein each middleware context is per-request/per-user. No global state that could cause cross-user leakage in Cloudflare Worker isolates or concurrent requests. -
Context block regex is safe — The pattern
/<supermemory context="user-memories" readonly>[\s\S]*?<\/supermemory>/guses non-greedy matching with fixed anchors, avoiding ReDoS. The specific attribute match ensures user-authored supermemory tags with different attributes won't be stripped. -
Tag injection prevented —
escapeMemoryContextDelimiters()escapes<and>in memory content, preventing injection attacks where memory text containing</supermemory>could break out of the SDK-owned block. -
Mode-aware deduplication handles edge cases — In "query" mode, the profile isn't injected, so search results correctly skip deduplication against the profile. A memory appearing in both profile and search results survives in search results, preventing legitimate context from disappearing.
-
Normalization logic is sound —
normalizeMemoryFact()strips[YYYY-MM-DD]date prefixes,[recent]tags, collapses whitespace, and lowercases for comparison while preserving the original memory string for output. -
Good test coverage — Tests verify block replacement (stale → fresh), no accumulation (single
<supermemory>tag), cross-user isolation, normalized deduplication variants, and query mode search result preservation.
Score: 10/10
Merge activity
|
be97748 to
ec6c159
Compare
8a78b11 to
e1c3122
Compare
e1c3122 to
ff2d0cf
Compare
## Stack Context This stack moves memory deduplication **out of the playground UI and into the SDKs themselves**, so every integration injects a single, deduplicated, self-replacing memory block. Three PRs: 1. **`sdk-dedup/tools-ts`** (this PR) — TypeScript SDK core + integrations 2. `sdk-dedup/python` — Python SDKs 3. `sdk-dedup/playground` — playground debug view reflects the SDK-owned block ## What? Move profile deduplication into the SDK middleware for the TypeScript tools package. - Facts are normalized (strip leading `[YYYY-MM-DD]`, trim, collapse whitespace, casefold) and deduplicated in **`static > dynamic > search`** priority within a single request. - The result is injected as one **owned `<supermemory>` block** that *replaces* the previous block instead of accumulating a new one each turn. - Dedup is **mode-aware**: in query mode, search results are not dropped against a profile that isn't being injected. - Deduplication is **request-local** — no global/browser `Set`. Safe for multiple users, concurrent requests, and Cloudflare Worker isolates. Covers AI SDK, OpenAI (Chat + Responses), Mastra, and VoltAgent. New `shared/memory-context.ts` owns the block-replacement logic. ## Why? The earlier "conversation-scoped deduplication" was only a playground browser `Set` — a UI debug affordance that did not change what the SDK sent to the model, and would have been unsafe as server-side global state. Real cross-source dedup belongs in the SDK, applied fresh per stateless model request. ## Testing - `bun run test` in `packages/tools`: 145 passed (the one failing suite, `claude-memory.test.ts`, is a pre-existing broken import unrelated to this change). 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes how system prompts and instructions are built across all TypeScript integrations; behavior is well-covered by unit tests but incorrect strip/replace logic could drop or duplicate context in production prompts. > > **Overview** > Moves **cross-source memory deduplication** and **owned prompt injection** into `@supermemory/tools` so every integration sends one deduplicated memory block per request instead of growing context each turn. > > **Deduplication:** Facts are normalized via `normalizeMemoryFact` (strip `[YYYY-MM-DD]`, trim, collapse whitespace, lowercase) and deduplicated with **static → dynamic → search** priority. `deduplicateMemoriesForMode` keeps search hits in **query** mode when the profile is not injected. > > **Owned `<supermemory>` block:** New `shared/memory-context.ts` wraps memories in `<supermemory context="user-memories" readonly>`, strips stale blocks, and **replaces** prior SDK context while preserving caller system instructions. Applied in AI SDK (`injectMemoriesIntoParams`), OpenAI Chat/Responses middleware, Mastra input processor (`wrapMemoryContext`), and VoltAgent hooks. > > **Tests:** Unit coverage for block replacement (with-supermemory, OpenAI, VoltAgent), Mastra wrapper tag assertion, normalized dedup variants, and concurrent `containerTag` isolation. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2fa2e0d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
ec6c159 to
d035932
Compare
## Stack Context This stack moves memory deduplication **out of the playground UI and into the SDKs themselves**, so every integration injects a single, deduplicated, self-replacing memory block. Three PRs: 1. **`sdk-dedup/tools-ts`** (this PR) — TypeScript SDK core + integrations 2. `sdk-dedup/python` — Python SDKs 3. `sdk-dedup/playground` — playground debug view reflects the SDK-owned block ## What? Move profile deduplication into the SDK middleware for the TypeScript tools package. - Facts are normalized (strip leading `[YYYY-MM-DD]`, trim, collapse whitespace, casefold) and deduplicated in **`static > dynamic > search`** priority within a single request. - The result is injected as one **owned `<supermemory>` block** that *replaces* the previous block instead of accumulating a new one each turn. - Dedup is **mode-aware**: in query mode, search results are not dropped against a profile that isn't being injected. - Deduplication is **request-local** — no global/browser `Set`. Safe for multiple users, concurrent requests, and Cloudflare Worker isolates. Covers AI SDK, OpenAI (Chat + Responses), Mastra, and VoltAgent. New `shared/memory-context.ts` owns the block-replacement logic. ## Why? The earlier "conversation-scoped deduplication" was only a playground browser `Set` — a UI debug affordance that did not change what the SDK sent to the model, and would have been unsafe as server-side global state. Real cross-source dedup belongs in the SDK, applied fresh per stateless model request. ## Testing - `bun run test` in `packages/tools`: 145 passed (the one failing suite, `claude-memory.test.ts`, is a pre-existing broken import unrelated to this change). 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes how system prompts and instructions are built across all TypeScript integrations; behavior is well-covered by unit tests but incorrect strip/replace logic could drop or duplicate context in production prompts. > > **Overview** > Moves **cross-source memory deduplication** and **owned prompt injection** into `@supermemory/tools` so every integration sends one deduplicated memory block per request instead of growing context each turn. > > **Deduplication:** Facts are normalized via `normalizeMemoryFact` (strip `[YYYY-MM-DD]`, trim, collapse whitespace, lowercase) and deduplicated with **static → dynamic → search** priority. `deduplicateMemoriesForMode` keeps search hits in **query** mode when the profile is not injected. > > **Owned `<supermemory>` block:** New `shared/memory-context.ts` wraps memories in `<supermemory context="user-memories" readonly>`, strips stale blocks, and **replaces** prior SDK context while preserving caller system instructions. Applied in AI SDK (`injectMemoriesIntoParams`), OpenAI Chat/Responses middleware, Mastra input processor (`wrapMemoryContext`), and VoltAgent hooks. > > **Tests:** Unit coverage for block replacement (with-supermemory, OpenAI, VoltAgent), Mastra wrapper tag assertion, normalized dedup variants, and concurrent `containerTag` isolation. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2fa2e0d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
ff2d0cf to
b01d2b6
Compare
d035932 to
d0f53b0
Compare
The base branch was changed.

Stack Context
This stack moves memory deduplication out of the playground UI and into the SDKs themselves, so every integration injects a single, deduplicated, self-replacing memory block. Three PRs:
sdk-dedup/tools-ts(this PR) — TypeScript SDK core + integrationssdk-dedup/python— Python SDKssdk-dedup/playground— playground debug view reflects the SDK-owned blockWhat?
Move profile deduplication into the SDK middleware for the TypeScript tools package.
[YYYY-MM-DD], trim, collapse whitespace, casefold) and deduplicated instatic > dynamic > searchpriority within a single request.<supermemory>block that replaces the previous block instead of accumulating a new one each turn.Set. Safe for multiple users, concurrent requests, and Cloudflare Worker isolates.Covers AI SDK, OpenAI (Chat + Responses), Mastra, and VoltAgent. New
shared/memory-context.tsowns the block-replacement logic.Why?
The earlier "conversation-scoped deduplication" was only a playground browser
Set— a UI debug affordance that did not change what the SDK sent to the model, and would have been unsafe as server-side global state. Real cross-source dedup belongs in the SDK, applied fresh per stateless model request.Testing
bun run testinpackages/tools: 145 passed (the one failing suite,claude-memory.test.ts, is a pre-existing broken import unrelated to this change).🤖 Generated with Claude Code
Note
Medium Risk
Changes how system prompts and instructions are built across all TypeScript integrations; behavior is well-covered by unit tests but incorrect strip/replace logic could drop or duplicate context in production prompts.
Overview
Moves cross-source memory deduplication and owned prompt injection into
@supermemory/toolsso every integration sends one deduplicated memory block per request instead of growing context each turn.Deduplication: Facts are normalized via
normalizeMemoryFact(strip[YYYY-MM-DD], trim, collapse whitespace, lowercase) and deduplicated with static → dynamic → search priority.deduplicateMemoriesForModekeeps search hits in query mode when the profile is not injected.Owned
<supermemory>block: Newshared/memory-context.tswraps memories in<supermemory context="user-memories" readonly>, strips stale blocks, and replaces prior SDK context while preserving caller system instructions. Applied in AI SDK (injectMemoriesIntoParams), OpenAI Chat/Responses middleware, Mastra input processor (wrapMemoryContext), and VoltAgent hooks.Tests: Unit coverage for block replacement (with-supermemory, OpenAI, VoltAgent), Mastra wrapper tag assertion, normalized dedup variants, and concurrent
containerTagisolation.Reviewed by Cursor Bugbot for commit 2fa2e0d. Bugbot is set up for automated code reviews on this repo. Configure here.