Skip to content

Proposal: Add SDK idempotency keys for safe memory write retries #1627

Open

Description

@Sravanjangam

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)
)
  • minuteBucket makes the key stable within the same minute (retries share it) but rotates soon after, preventing indefinite dedupe.
  • SHA-256 is 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 + documentAddTool in packages/tools/src/ai-sdk.ts (and openai/tools.ts for parity) to attach the header via the second RequestOptions arg
  • 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 userId in the hash (Phase B — if backend needs it, it can hash apiKey server-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

  1. linear-code commented on Aug 30, 2026

    @linear-code
  2. added 3 commits that reference this issue on Aug 30, 2026
    a84003f
    71be386
    9960935
  3. 84dnnvbdvp-debug commented on Sep 23, 2026

    @84dnnvbdvp-debug

    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:

    1. Same logical operation retried before and after a minute boundary → one backend effect, same result replayed.
    2. Two intentional identical writes close together with distinct keys → two effects.
    3. Same key reused with a different payload → conflict/rejection, not silent aliasing.
    4. An automatic transport retry preserves the exact original key.

    That keeps operation idempotency separate from content deduplication.

  4. rishibalanagu0108 commented on Oct 16, 2026

    @rishibalanagu0108
    Contributor

    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/tools before 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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions