Skip to content

feat(tools): SDK-level cross-source memory deduplication - #1531

Open
Dhravya wants to merge 1 commit into
feat/sdk-playgroundfrom
sdk-dedup/tools-ts
Open

feat(tools): SDK-level cross-source memory deduplication#1531
Dhravya wants to merge 1 commit into
feat/sdk-playgroundfrom
sdk-dedup/tools-ts

Conversation

@Dhravya

@Dhravya Dhravya commented Aug 18, 2026

Copy link
Copy Markdown
Member

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


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.

Reviewed by Cursor Bugbot for commit 2fa2e0d. Bugbot is set up for automated code reviews on this repo. Configure here.

Move profile deduplication into the SDK middleware. Facts are normalized
(strip leading date, trim, collapse whitespace, casefold) and deduplicated
in static > dynamic > search priority within each request, then injected as
one owned <supermemory> block that replaces the previous block instead of
accumulating. Dedup is mode-aware so query-mode search results are not
dropped against an omitted profile. No global/browser Set: request-local
only, safe for concurrent requests and Cloudflare Workers.

Covers AI SDK, OpenAI Chat/Responses, Mastra, and VoltAgent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@claude

claude Bot commented Aug 18, 2026

Copy link
Copy Markdown

Claude finished @Dhravya's task in 2m 2s —— View job


Code Review Complete

  • Loading PR diff and context
  • Analyzing code changes for bugs and security issues
  • Reviewing cross-file dependencies
  • Submitting review

Result: No issues found — this looks good to ship.

The SDK-level deduplication implementation is solid:

  • Request isolation is correct (no global state)
  • Context block regex is safe from ReDoS
  • Normalization preserves original strings while deduping on normalized keys
  • Edge cases (empty memories, multiple system messages) handled correctly
  • Good test coverage for the new behavior

Score: 10/10

Dhravya commented Aug 18, 2026

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more


How to use the Graphite Merge Queue

Add the label Main to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Request isolation is correct — The seenMemories Set in deduplicateMemories() is created fresh per call, and the injected flag in the middleware map() callbacks is closure-scoped per request. No global state that could cause cross-user leakage.

  2. 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.

  3. Normalization logic is soundnormalizeMemoryFact() 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.

  4. 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.

  5. Good test coverage — Tests verify context replacement (not accumulation), cross-user isolation, and normalized deduplication.

Score: 10/10

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
supermemory-mcp 2fa2e0d Aug 18 2026, 03:19 PM

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
supermemory-app 2fa2e0d Commit Preview URL

Branch Preview URL
Aug 18 2026, 03:19 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants