Repository navigation
Proposal: Add SDK idempotency keys for safe memory write retries #1627
Description
Proposal: Add SDK idempotency keys for safe memory write retries
Problem
packages/tools/src/ai-sdk.ts:addMemoryTool and documentAddTool (and the same in packages/tools/src/openai/tools.ts, claude-memory.ts) call client.add({ content, containerTags }) with no idempotency signal. If the network retries (the supermemory client retries 2× on 5XX/network), or the caller retries, the backend creates:
- duplicate memories,
- duplicate embeddings,
- duplicate writes.
There is no Idempotency-Key header and no client helper to keep the same key across retries. Offline sync and batching also need a stable per-content key.
Impact
- Duplicate memories on retry (common on mobile/flaky networks)
- No safe retry primitive for SDK consumers
- Blocks offline queue (no stable key to dedupe)
Proposal — Phase A (SDK-only, mergeable now)
Generate the key in the SDK and attach it as Idempotency-Key header. No backend change required in this phase — the header is optional and ignored until the backend honors it. The SDK is already correct for safe retries.
Idempotency-Key = SHA256(
content +
'|' + containerTags.sort().join(',') +
'|' + minuteBucket // Math.floor(Date.now()/60000)
)
minuteBucketmakes the key stable within the same minute (retries share it) but rotates soon after, preventing indefinite dedupe.SHA-256is available via Web Crypto (and Node 20+crypto.subtle) — already a repo dependency for other hashing.
SDK flow:
const key = await generateIdempotencyKey(content, containerTags)
await client.add({ content, containerTags }, { headers: { "Idempotency-Key": key } })Phase A files:
- New
packages/tools/src/shared/idempotency.ts—generateIdempotencyKey(content, containerTags, now?),buildIdempotencyHeaders(...) - Wire
addMemoryTool+documentAddToolinpackages/tools/src/ai-sdk.ts(andopenai/tools.tsfor parity) to attach the header via the secondRequestOptionsarg - Unit tests in
packages/tools(vitest already exists)
Out of scope for this PR:
- Backend honoring the key (Phase B — server-side dedupe store)
- Custom
userIdin the hash (Phase B — if backend needs it, it can hashapiKeyserver-side) - Batch idempotency
Questions for maintainers
Is the backend already capable of accepting Idempotency-Key, or would this require backend support? This PR keeps the header optional so it is useful even before the backend honors it (stable key for caller-side retry dedupe, foundation for Phase B).
Verification
Vitest in packages/tools — 10 scenarios: same content+tags+minute → same key, different content → different key, different tags → different key, minute rollover → different key, custom now injection, header builder, retry helper reuses same key, empty content edge, containerTags order independence, concurrent callers share key.
Non-goals (Phase A)
- No backend change
- No persistent storage of keys
- No cross-process coordination
- No batch/bulk idempotency
Activity
linear-code commentedon Aug 30, 2026
ENG-1233
feat(tools): add SDK idempotency keys for safe memory write retries
feat(tools): add SDK idempotency keys for safe memory write retries
feat(tools): add SDK idempotency keys for safe memory write retries
84dnnvbdvp-debug commentedon Sep 23, 2026
There’s an idempotency boundary worth pinning before Phase A: the key should identify the logical write, rather than derive from payload + the current minute.
With the proposed hash, two intentional identical writes with the same content/tags inside one minute get the same key and would be collapsed once the backend honors it. Conversely, the same logical write retried across a minute boundary gets a different key and can duplicate. Those are opposite failure modes caused by using time/content as operation identity.
I’d make the SDK accept or generate an opaque per-operation key once, then reuse that exact key across automatic transport retries and expose/preserve it for caller or offline retries. On the server side, enforcement can scope the key to the caller + operation, persist/replay the first result, and reject reuse of the same key with a different payload.
Also, an ignored header by itself cannot make retries idempotent; the server-side enforcement is what creates that guarantee. So until Phase B exists, I’d describe Phase A as establishing the client contract / plumbing rather than making retries safe.
A useful regression matrix would be:
That keeps operation idempotency separate from content deduplication.
rishibalanagu0108 commentedon Oct 16, 2026
Hi! I’d be interested in working on this, but I’d like to confirm the intended scope before implementing it.
Would you accept a client-side PR that adds idempotency-key plumbing to
@supermemory/toolsbefore the backend enforces those keys? If so, should the SDK generate one opaque key per logical write and allow callers to supply/reuse it across retries, rather than deriving it from content and a time bucket?I can keep the change limited to the existing write paths and add regression tests proving that automatic retries preserve the same key. Please let me know whether backend support already exists or whether you would prefer to defer the SDK work until it does.