Skip to content

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

Merged
graphite-app[bot] merged 1 commit into
mainfrom
sdk-dedup/tools-ts
Sep 1, 2026
Merged

feat(tools): SDK-level cross-source memory deduplication#1531
graphite-app[bot] merged 1 commit into
mainfrom
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.

@claude

claude Bot commented Aug 18, 2026

Copy link
Copy Markdown

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


Code Review Complete

  • Fetch PR diff and existing review comments
  • Analyze deduplication logic for correctness
  • Review context block replacement for edge cases
  • Check for security issues (injection, cross-user leakage)
  • Examine test coverage for critical paths
  • Post review summary

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:

  1. Request isolation is correct — The seenMemories Set in deduplicateMemories() is created fresh per call, and the memoryCache in each middleware context is per-request/per-user. No global state that could cause cross-user leakage in Cloudflare Worker isolates or concurrent requests.

  2. Context block regex is safe — The pattern /<supermemory context="user-memories" readonly>[\s\S]*?<\/supermemory>/g uses non-greedy matching with fixed anchors, avoiding ReDoS. The specific attribute match ensures user-authored supermemory tags with different attributes won't be stripped.

  3. Tag injection preventedescapeMemoryContextDelimiters() in memory-context.ts:11-14 escapes < and > in memory content, preventing injection attacks where memory text containing </supermemory> could break out of the SDK-owned block.

  4. Mode-aware deduplication handles edge cases — In "query" mode, the profile isn't injected, so search results correctly skip deduplication against the profile (tools-shared.ts:430-441). A memory appearing in both profile and search results survives in search results, preventing legitimate context from disappearing.

  5. Normalization logic is soundnormalizeMemoryFact() strips [YYYY-MM-DD] date prefixes, [recent] tags, collapses whitespace, and lowercases for comparison while preserving the original memory string for output.

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

Dhravya commented Aug 18, 2026

Copy link
Copy Markdown
Member Author

@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

cloudflare-workers-and-pages Bot commented Aug 18, 2026

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 d0f53b0 Sep 01 2026, 06:19 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 18, 2026

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 d0f53b0 Commit Preview URL

Branch Preview URL
Sep 01 2026, 06:22 AM

@ved015
ved015 force-pushed the feat/sdk-playground branch from 93f92cd to 4e04a0d Compare August 24, 2026 14:43
@ved015
ved015 force-pushed the sdk-dedup/tools-ts branch from 2fa2e0d to be97748 Compare August 24, 2026 16:21
ved015
ved015 previously approved these changes Aug 24, 2026

@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 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:

  1. Request isolation is correct — The seenMemories Set in deduplicateMemories() is created fresh per call, and the memoryCache in each middleware context is per-request/per-user. No global state that could cause cross-user leakage in Cloudflare Worker isolates or concurrent requests.

  2. Context block regex is safe — The pattern /<supermemory context="user-memories" readonly>[\s\S]*?<\/supermemory>/g uses non-greedy matching with fixed anchors, avoiding ReDoS. The specific attribute match ensures user-authored supermemory tags with different attributes won't be stripped.

  3. Tag injection preventedescapeMemoryContextDelimiters() escapes < and > in memory content, preventing injection attacks where memory text containing </supermemory> could break out of the SDK-owned block.

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

  5. Normalization logic is soundnormalizeMemoryFact() strips [YYYY-MM-DD] date prefixes, [recent] tags, collapses whitespace, and lowercases for comparison while preserving the original memory string for output.

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

@graphite-app

graphite-app Bot commented Sep 1, 2026

Copy link
Copy Markdown

Merge activity

@Dhravya
Dhravya force-pushed the feat/sdk-playground branch from 8a78b11 to e1c3122 Compare September 1, 2026 05:58
@graphite-app
graphite-app Bot force-pushed the feat/sdk-playground branch from e1c3122 to ff2d0cf Compare September 1, 2026 06:02
graphite-app Bot pushed a commit that referenced this pull request Sep 1, 2026
## 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 -->
@graphite-app
graphite-app Bot force-pushed the sdk-dedup/tools-ts branch from ec6c159 to d035932 Compare September 1, 2026 06:03
## 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 -->
@graphite-app
graphite-app Bot force-pushed the feat/sdk-playground branch from ff2d0cf to b01d2b6 Compare September 1, 2026 06:12
@graphite-app
graphite-app Bot force-pushed the sdk-dedup/tools-ts branch from d035932 to d0f53b0 Compare September 1, 2026 06:12
@graphite-app
graphite-app Bot changed the base branch from feat/sdk-playground to main September 1, 2026 06:19
@graphite-app
graphite-app Bot dismissed ved015’s stale review September 1, 2026 06:19

The base branch was changed.

@graphite-app
graphite-app Bot merged commit d0f53b0 into main Sep 1, 2026
6 of 7 checks passed
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