Skip to content
AI-grafen
FAI engineeringMemory systems· about 90 min· fast-moving, sources checked often· verified 2026-09-21· EN

Memory and privacy

Be able to design memories that respect deletion, consent and data minimisation.

Prerequisites

Intuition

An agent memory is a collection of personal data that grows by itself. That makes it one of the most sensitive parts of an AI system.

Four questions that have to be answered before the memory is built:

The questionWhy
What is saved — and what is not?data minimisation
For how long?storage minimisation
Can the user see and change it?rectification and openness
What happens on deletion?the right to be forgotten

The hardest is the last one. Deleting a row in the database is simple. But the memory may have spread: into summaries, into embeddings, into logs, into a model that has been fine-tuned on it.

The basic principle is therefore to design for deletion from the start — not to solve it when the request arrives.

Formal

Where a piece of personal data can end up, and how hard it is to remove:

The placeDeletion
The memory entryeasy
Summariesrequires regeneration without that entry
Embeddings in the indexeasy if the id is traceable, otherwise hard
Conversation logseasy if they have a retention time
Audit logsshould not be deleted — but must not contain more than necessary
Backupstechnically hard; handled with a retention routine
Fine-tuned model weightsin practice impossible

The last row is the reason not to fine-tune on the users' memories. If you do, deletion becomes a promise you cannot keep.

Design principles that work:

  1. Save references, not copies. Let summaries point at the source memories so that they can be regenerated when something is deleted.
  2. Categorise on writing. Every memory gets a type (preference, goal, fact, sensitive) and a retention time that follows from the type.
  3. Filter on writing, not on reading. What is never saved cannot leak.
  4. A traceable id through the whole chain — from the memory entry to the embedding to the summary.
  5. Deletion is an operation, not a manual process. It should be possible to run and verify.

What should never be saved in an agent memory:

The categoryWhy
Personal identity numbers, addresses, contact details of third partiesnot necessary for the function
Health dataa special category under GDPR art. 9
Data about other peoplethe data subject is not the user
Passwords and keysobviously
Sensitive data about childrenstricter requirements

Openness in practice. The user should be able to see a list of what the system remembers, in readable form, and be able to remove individual entries. That is both a GDPR requirement and good design: a memory that cannot be corrected becomes unpleasant as soon as it contains something wrong.

Consent. A memory saved across sessions should be an active choice, not a default — particularly for children. And it should be possible to switch off without the service becoming unusable.

Code

import re, time, uuid
from dataclasses import dataclass, field
from enum import Enum

class Type(Enum):
    PREFERENCE = ("preference", 365)
    GOAL = ("goal", 365)
    DIFFICULTY = ("difficulty", 180)
    FACT = ("fact", 90)
    SESSION = ("session", 1)

    def __init__(self, label, retention_days):
        self.label = label
        self.retention_days = retention_days

# The patterns match Swedish formats — adapt them to your own locale
FORBIDDEN = {
    "personal id number": r"\b(19|20)?\d{6}[-+]?\d{4}\b",
    "email": r"\b[\w.+-]+@[\w-]+\.[\w.]+\b",
    "telephone": r"\b0[\d\s-]{7,12}\b",
    "address": r"\b\w+(gatan|vägen|gränd)\s+\d+\b",
}

def may_be_saved(text: str) -> tuple[bool, list[str]]:
    hits = [name for name, m in FORBIDDEN.items() if re.search(m, text, re.I)]
    return (not hits), hits

@dataclass
class Memory:
    id: str
    user: str
    text: str
    type: Type
    created: float
    sources: list[str] = field(default_factory=list)   # the ids this was derived from

    def expired(self, now=None):
        now = now or time.time()
        return now - self.created > self.type.retention_days * 86400

class MemoryBank:
    def __init__(self, vector_index):
        self.memories: dict[str, Memory] = {}
        self.index = vector_index

    def write(self, user, text, type: Type, sources=None):
        ok, hits = may_be_saved(text)
        if not ok:
            return {"saved": False, "reason": f"contains {', '.join(hits)}"}
        mid = str(uuid.uuid4())
        self.memories[mid] = Memory(mid, user, text, type, time.time(), sources or [])
        self.index.upsert(id=mid, vector=embed(text),
                          payload={"user": user, "type": type.label})
        return {"saved": True, "id": mid}

    def delete(self, mid: str) -> dict:
        """Deletes the entry AND everything derived from it."""
        removed = []
        queue = [mid]
        while queue:
            m = queue.pop()
            if m not in self.memories:
                continue
            # everything pointing at m also has to go, or be regenerated
            queue += [k for k, v in self.memories.items() if m in v.sources]
            del self.memories[m]
            self.index.delete(id=m)
            removed.append(m)
        return {"removed": removed, "count": len(removed)}

    def delete_all(self, user: str) -> dict:
        ids = [k for k, v in self.memories.items() if v.user == user]
        for i in ids:
            self.memories.pop(i, None)
            self.index.delete(id=i)
        return {"count": len(ids)}

    def prune(self) -> dict:
        now = time.time()
        expired = [k for k, v in self.memories.items() if v.expired(now)]
        for i in expired:
            del self.memories[i]
            self.index.delete(id=i)
        return {"pruned": len(expired)}

    def show_to_user(self, user: str):
        """A readable list — the user should be able to see and remove individual entries."""
        return sorted(
            [{"id": m.id, "text": m.text, "type": m.type.label,
              "saved": time.strftime("%Y-%m-%d", time.localtime(m.created)),
              "pruned_on": time.strftime("%Y-%m-%d", time.localtime(
                  m.created + m.type.retention_days * 86400))}
             for m in self.memories.values() if m.user == user],
            key=lambda d: d["saved"], reverse=True)

# Verify the deletion — otherwise it is a promise without backing
def verify_deletion(bank, user):
    memories_left = [m for m in bank.memories.values() if m.user == user]
    index_left = bank.index.count(filter={"user": user})
    return {"memories_left": len(memories_left), "index_left": index_left,
            "ok": len(memories_left) == 0 and index_left == 0}

The sources field is what makes deletion feasible. Without tracking what a memory was derived from, a deleted piece of data can be found again in a summary, and the deletion is not real.

Mastery means

  • Designs memories with retention and deletion
  • Applies data minimisation to what is saved
  • Makes the memory visible and editable for the user

Sign in to do the exercises and build your mastery up.

Sources

All the sources and licences