Skip to main content
← All use cases

The scenario

Someone emails asking what you hold about them, and then asks you to delete it. You have a deadline measured in days, and “it is spread across a memory system” is not an answer you can send. The whole thing turns on one earlier decision: whether writes were scoped. If every memory about a person carries their scope, this is a runbook. If not, it is a manual search you cannot certify.

Step 1 — Find everything about them

This is a list, not a search. retrieve ranks by relevance to a query and stops at limit, which is the wrong tool for “everything” — and it has no match-all query (query="*" is refused with 422). list_memories pages the whole scope and tells you the total, so you know when you have all of it. prefer_observations=False matters: by default a note hides the raw facts it was built from, and those facts are data about the person too. A scoped read is strict — it matches only memories carrying that scope, and never untagged ones. That strictness is exactly what you want here: the result is defensible rather than best-effort.

Step 2 — Give them a copy first

The subject is usually entitled to the data before it goes. hits from step 1 is the per-person copy — every row, with its content and dates — so write it to a file before anything is deleted:
If you also want the full-space bundle for your own records, take it from the dashboard first — see Export Your Memory. Full-space export is dashboard-only; there is no API-key endpoint for it.

Step 3 — Delete, one by one, and keep the ids

Deleting a fact also deletes every note built from it, so a note further down hits can already be gone by the time the loop reaches it. That is a 404 memory_not_found, and it is success, not failure — keep it in its own list rather than stopping the loop. Keep both lists. The ids, with a timestamp, are what you attach to your response to the request.

Step 4 — Prove it

Re-running the same scoped query and getting nothing is the evidence. Run it as a test, not by eye.

Evals

  1. Write ten memories for one user and five for another.
  2. Run the runbook for the first. Confirm every id step 1 found ends up in removed or already_gone, and none of them raised anything else.
  3. Confirm the second user’s five are untouched — the most important assertion here, and the one a manual process gets wrong.
  4. Re-run the scoped list and assert empty.

Guardrails

Deletion is permanent and there is no undo. Export first, every time, and have the exported bundle in hand before the first delete_memory call.
  • Unscoped memories are invisible to this process. If you adopted scoping late, memories written before it carry no user_id and a scoped query will never find them — meaning your deletion is incomplete and you will not be told. Scope from day one, or backfill before you promise anything.
  • A shared space needs the same runbook run by its owner. Deleting is owner-only; a visitor cannot erase data from a space they were invited to.
  • Consolidated observations are derived from raw facts, and they go with them. Deleting a fact removes every observation built from it and re-consolidates that observation’s other sources without it. Re-check with a scoped read afterwards anyway — that read, not the delete’s status code, is the evidence you keep.
  • This page is a runbook, not legal advice. What your obligations are, and within what deadline, depends on your jurisdiction and your role as controller or processor.

Built from

Both are complete, runnable scripts: support_bot_scoping.py, user_profiles.py.