Hoppa till innehållet
AI-grafen
F· AI engineeringminnessystem· ca 90 min· volatil — kontrolleras ofta· verifierad 2026-09-21

Minne och integritet

Kunna designa minnen som respekterar radering, samtycke och dataminimering.

Förkunskaper

Intuition

Ett agentminne är en personuppgiftssamling som växer av sig själv. Det gör det till en av de känsligaste delarna i ett AI-system.

Fyra frågor som måste besvaras innan minnet byggs:

FrågaVarför
Vad sparas — och vad sparas inte?dataminimering
Hur länge?lagringsminimering
Kan användaren se och ändra det?rättelse och öppenhet
Vad händer vid radering?rätten att bli glömd

Den svåraste är den sista. Att radera en rad i databasen är enkelt. Men minnet kan ha spridit sig: till sammanfattningar, till embeddingar, till loggar, till en modell som finjusterats på det.

Grundprincipen är därför att designa för radering från början — inte att lösa det när begäran kommer.

Formellt

Var en personuppgift kan hamna, och hur svårt den är att få bort:

PlatsRadering
Minnespostenlätt
Sammanfattningarkräver omgenerering utan den posten
Embeddingar i indexlätt om id:t är spårbart, annars svårt
Samtalsloggarlätt om de har gallringstid
Audit-loggarska inte raderas — men får inte innehålla mer än nödvändigt
Backupertekniskt svårt; hanteras med gallringsrutin
Finjusterade modellvikteri praktiken omöjligt

Sista raden är skälet till att man inte ska finjustera på användarnas minnen. Gör man det blir radering ett löfte man inte kan hålla.

Designprinciper som fungerar:

  1. Spara referenser, inte kopior. Låt sammanfattningar peka på källminnen så att de kan genereras om när något raderas.
  2. Kategorisera vid skrivning. Varje minne får en typ (preferens, mål, faktum, känsligt) och en gallringstid som följer av typen.
  3. Filtrera vid skrivning, inte vid läsning. Det som aldrig sparas kan inte läcka.
  4. Spårbart id genom hela kedjan — från minnespost till embedding till sammanfattning.
  5. Radering är en operation, inte en manuell process. Den ska gå att köra och verifiera.

Vad som aldrig ska sparas i ett agentminne:

KategoriVarför
Personnummer, adresser, kontaktuppgifter till tredje partinte nödvändigt för funktionen
Hälsouppgiftersärskild kategori enligt GDPR art. 9
Uppgifter om andra personerden registrerade är inte användaren
Lösenord och nycklaruppenbart
Känsliga uppgifter om barnskärpta krav

Öppenhet i praktiken. Användaren ska kunna se en lista över vad systemet minns, i läsbar form, och kunna ta bort enskilda poster. Det är både ett GDPR-krav och bra design: ett minne som inte går att korrigera blir obehagligt så snart det innehåller något fel.

Samtycke. Ett minne som sparas över sessioner bör vara ett aktivt val, inte förvalt — särskilt för barn. Och det ska gå att stänga av utan att tjänsten blir oanvändbar.

Kod

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

class Typ(Enum):
    PREFERENS = ("preferens", 365)
    MAL = ("mål", 365)
    SVARIGHET = ("svårighet", 180)
    FAKTUM = ("faktum", 90)
    SESSION = ("session", 1)

    def __init__(self, etikett, gallringsdagar):
        self.etikett = etikett
        self.gallringsdagar = gallringsdagar

FORBJUDNA = {
    "personnummer": r"\b(19|20)?\d{6}[-+]?\d{4}\b",
    "e-post": r"\b[\w.+-]+@[\w-]+\.[\w.]+\b",
    "telefon": r"\b0[\d\s-]{7,12}\b",
    "adress": r"\b\w+(gatan|vägen|gränd)\s+\d+\b",
}

def far_sparas(text: str) -> tuple[bool, list[str]]:
    traffar = [namn for namn, m in FORBJUDNA.items() if re.search(m, text, re.I)]
    return (not traffar), traffar

@dataclass
class Minne:
    id: str
    anvandare: str
    text: str
    typ: Typ
    skapad: float
    kallor: list[str] = field(default_factory=list)   # id:n detta härletts ur

    def utgangen(self, nu=None):
        nu = nu or time.time()
        return nu - self.skapad > self.typ.gallringsdagar * 86400

class Minnesbank:
    def __init__(self, vektorindex):
        self.minnen: dict[str, Minne] = {}
        self.index = vektorindex

    def skriv(self, anvandare, text, typ: Typ, kallor=None):
        ok, traffar = far_sparas(text)
        if not ok:
            return {"sparat": False, "anledning": f"innehåller {', '.join(traffar)}"}
        mid = str(uuid.uuid4())
        self.minnen[mid] = Minne(mid, anvandare, text, typ, time.time(), kallor or [])
        self.index.upsert(id=mid, vector=badda_in(text),
                          payload={"anvandare": anvandare, "typ": typ.etikett})
        return {"sparat": True, "id": mid}

    def radera(self, mid: str) -> dict:
        """Raderar posten OCH allt som härletts ur den."""
        borttagna = []
        ko = [mid]
        while ko:
            m = ko.pop()
            if m not in self.minnen:
                continue
            # allt som pekar på m måste också bort eller genereras om
            ko += [k for k, v in self.minnen.items() if m in v.kallor]
            del self.minnen[m]
            self.index.delete(id=m)
            borttagna.append(m)
        return {"borttagna": borttagna, "antal": len(borttagna)}

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

    def gallra(self) -> dict:
        nu = time.time()
        utgangna = [k for k, v in self.minnen.items() if v.utgangen(nu)]
        for i in utgangna:
            del self.minnen[i]
            self.index.delete(id=i)
        return {"gallrade": len(utgangna)}

    def visa_for_anvandare(self, anvandare: str):
        """Läsbar lista — användaren ska kunna se och ta bort enskilda poster."""
        return sorted(
            [{"id": m.id, "text": m.text, "typ": m.typ.etikett,
              "sparat": time.strftime("%Y-%m-%d", time.localtime(m.skapad)),
              "gallras": time.strftime("%Y-%m-%d", time.localtime(
                  m.skapad + m.typ.gallringsdagar * 86400))}
             for m in self.minnen.values() if m.anvandare == anvandare],
            key=lambda d: d["sparat"], reverse=True)

# Verifiera raderingen — annars är den ett löfte utan täckning
def verifiera_radering(bank, anvandare):
    kvar_minnen = [m for m in bank.minnen.values() if m.anvandare == anvandare]
    kvar_index = bank.index.count(filter={"anvandare": anvandare})
    return {"minnen_kvar": len(kvar_minnen), "index_kvar": kvar_index,
            "ok": len(kvar_minnen) == 0 and kvar_index == 0}

kallor-fältet är det som gör radering genomförbar. Utan spårning av vad ett minne härletts ur går en raderad uppgift att återfinna i en sammanfattning, och raderingen är inte verklig.

Behärskning innebär

  • Designar minnen med gallring och radering
  • Tillämpar dataminimering på vad som sparas
  • Gör minnet synligt och redigerbart för användaren

Logga in för att göra övningarna och bygga upp din behärskning.

Källor

Alla källor och licenser