AI agents
Anthropic's Fable-retentie: legaltech-incident herbeleefd
De ochtend dat het Fable-retentiebeleid van Anthropic veranderde, had een Rotterdamse legaltech vier uur om een purge-job te herschrijven die stilletjes niets meer deed.

07:14 op een woensdag. De head of platform bij een legaltech-SaaS van 23 mensen in Rotterdam opent Hacker News in bed, ziet de kop Anthropic vereist 30 dagen dataretentie voor Fable en Mythos, en zijn duim staat stil. Hun product, een contract-review-agent die door middelgrote Nederlandse advocatenkantoren wordt gebruikt, sluist al veertien maanden lang advocaat-cliënt-transcripties door de zero-retention enterprise tier van Anthropic. Hun data protection addendum met elke klant zegt, op schrift, dat geen enkele transcript-content bij een externe verwerker wordt opgeslagen na de duur van de call. Om 09:00 heeft het bedrijf een incident-channel, om 11:00 een conceptmelding voor twee van hun grotere kantoren, en om 17:30 een herschreven purge-job in productie.
Zo zag die dag er werkelijk uit, en zo zou de on-call engineer het volgende maand anders aanpakken.
ZDR als dragende aanname
Het product is klein. Drie backend-engineers, één frontend, twee ML, en een CTO die nog steeds elke Postgres-migration nakijkt. Ze gebruiken een agent-loop om concept-contracten te lezen, de advocaat in een chatvenster vervolgvragen te stellen, en een geannoteerde redline te produceren. Elk gesprek loopt 40 tot 90 turns voordat het eindigt.
Zero data retention bij Anthropic was dragend voor drie dingen:
- De DPA die ze met elk advocatenkantoor tekenen. Artikel 4 noemt Anthropic als sub-verwerker en stelt dat geen prompt- of response-content server-side wordt bewaard na de call.
- Het architectuurdiagram in hun SOC 2 Type II audit, op dit moment onder review.
- Hun interne purge-job, die alleen transcripties uit hun eigen Postgres hoefde te verwijderen nadat de zaak werd afgesloten. De job ging ervan uit dat de externe kant al een no-op was.
Veertien maanden lang klopte dat allemaal. Tot het niet meer klopte.
Wat de aankondiging veranderde
De HN-thread werd in het eerste uur door de helft van de legaltech-oprichters die we kennen verkeerd gelezen. De wijziging is smaller dan de kop suggereert: hij geldt voor twee specifieke nieuwere modelfamilies en is bedoeld om abuse-monitoring op agentic tooling te ondersteunen. Diezelfde week stond er een aparte voorpagina-thread waarin cybersecurity-onderzoekers luidkeels klaagden dat de guardrails op Fable te strak zaten voor legitieme red-teaming, wat de beleidschurn alleen maar chaotischer maakte voor iedereen die door koppen scrolde.
Die smalle scope bespaarde het Rotterdamse team een slechtere woensdag. Het bespaarde ze niet de hele woensdag. Hun laatste sprint had de redline-agent op een Fable-preview gemigreerd omdat tool-use-prestaties op PDF-tabellen merkbaar beter waren, en de migratie stond voor 60% uitgerold achter een feature flag. Ongeveer 31% van het live traffic raakte al een model dat vanaf de update van die ochtend transcript-content 30 dagen zou bewaren.
De relevante clausule in hun klant-DPA was de standaard Artikel 28 AVG-verwerkerstaal, die de verplichting bij de verantwoordelijke (de legaltech) legt om retentiebeloftes door te schuiven naar elke sub-verwerker. Een policy-pagina van een leverancier is geen eenzijdige patch op die contracten.
De purge-job die stilletjes een no-op was
Hier is de functie die elke nacht om 02:00 Amsterdamse tijd liep, licht ontdaan:
def purge_closed_matters(db, anthropic_client):
closed = db.fetch("""
SELECT matter_id FROM matters
WHERE status = 'closed'
AND closed_at < now() - interval '90 days'
""")
for m in closed:
db.execute(
"DELETE FROM transcripts WHERE matter_id = %s",
(m["matter_id"],),
)
# third-party side: ZDR, nothing to do
log.info("purge complete", count=len(closed))
Die laatste comment is de hele bug. Hij klopte in maart 2025, klopte door vijftien sprint reviews heen, en klopte niet meer om 06:00 op de ochtend van de aankondiging. De job had geen idee van een third-party transcript-identifier omdat het nooit nodig was geweest om er één te bewaren. De API van Anthropic geeft een response id terug bij elk bericht, maar het team had ze op de SDK-grens laten vallen.
Daaruit volgden twee problemen. Eén: voor elk gesprek dat naar het Fable-model was gerouteerd, hadden ze geen manier om op te sommen welke externe records bestonden. Twee: zelfs als ze de ids hadden, vereist het feitelijke delete-endpoint de workspace admin key, die alleen op de laptop van de CTO stond. De privacydocumentatie van Anthropic zelf beschrijft de delete-on-request-mogelijkheid, maar je moet er wel op aangesloten zijn om hem te gebruiken.
De incident-tijdlijn van vier uur
Het team begon niet met de rewrite. Ze begonnen met de vraag richting klant: "Moeten we iemand mailen voor de lunch?" De CTO liep de tijdlijn terug. De feature flag had vier dagen aangestaan. Twee van hun twaalf klanten hadden traffic dat door het nieuwe model liep. Eén was het grotere kantoor dat op dat moment een renewal overwoog. De beslissing was om beide te informeren, in duidelijke taal, voordat iemand het op HN las.
De conceptmail ging om 11:42 uit. Hij zei: we ontdekten een onbedoeld retentievenster op een deel van recent traffic; we hebben het betrokken traffic teruggerold naar het legacy model; we zullen de externe records binnen het 30-dagen-venster verwijderen en dat schriftelijk bevestigen. Geen juridisch slagen om de arm, geen "uit voorzorg"-toneel. Beide kantoren reageerden diezelfde middag. Geen van beiden klaagde. Eén vroeg om de verwijderingsbevestiging in hun Q3 vendor review.
Tegen de tijd dat die mail uitging, had het platformteam de feature flag al uitgezet en 100% van de inference teruggerouteerd naar het oudere model. Het cijfer van 31% klopte, behulpzaam genoeg, op het moment van de rollback, omdat de flag percentage-based was in plaats van tenant-based.
De purge-job herschrijven
De rewrite moest drie dingen doen die de oude job niet deed: de third-party response ids vastleggen op het moment van schrijven, ze naast de matter opslaan, en het delete-endpoint aanroepen met juiste auth op het moment van purge.
Vastleggen gebeurt op de SDK-grens. Elk bericht dat de agent stuurt of ontvangt schrijft nu zijn response id in een kleine tabel:
CREATE TABLE third_party_transcript_refs (
id bigserial PRIMARY KEY,
matter_id uuid NOT NULL REFERENCES matters(id),
provider text NOT NULL,
provider_msg_id text NOT NULL,
model text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
purged_at timestamptz
);
CREATE INDEX ON third_party_transcript_refs (matter_id)
WHERE purged_at IS NULL;
De agent-loop schrijft één rij per bericht. De purge-job heeft nu iets om op te sommen:
def purge_closed_matters(db, providers):
closed = db.fetch("""
SELECT m.id AS matter_id
FROM matters m
WHERE m.status = 'closed'
AND m.closed_at < now() - interval '90 days'
""")
for m in closed:
refs = db.fetch("""
SELECT id, provider, provider_msg_id
FROM third_party_transcript_refs
WHERE matter_id = %s
AND purged_at IS NULL
""", (m["matter_id"],))
for ref in refs:
client = providers[ref["provider"]]
try:
client.delete_message(ref["provider_msg_id"])
except ProviderNotFound:
pass # already aged out, treat as success
db.execute("""
UPDATE third_party_transcript_refs
SET purged_at = now()
WHERE id = %s
""", (ref["id"],))
db.execute(
"DELETE FROM transcripts WHERE matter_id = %s",
(m["matter_id"],),
)
log.info("purge complete", matters=len(closed))
De provider-map injecteert een andere client voor elke modelfamilie, dus een toekomstige tweede sub-verwerker (of de stap terug naar een no-retention tier zodra er één is) vereist geen nieuwe rewrite. De ProviderNotFound-tak is belangrijk: als een record al uit het 30-dagen-venster is verlopen, geeft de delete-call 404 terug, en dat is het succesgeval, niet het faalgeval.
Twee extra's gingen diezelfde middag live. Een dagelijkse job slaat alarm als het aantal purged_at IS NULL-rijen ouder dan 30 dagen niet nul is. En de workspace admin key staat nu in de secret store, met gelogde break-glass-toegang, zodat de purge-job onbemand kan draaien.
Drie lessen uit één ochtend
Geen van deze is nieuw. Ze zijn allemaal makkelijker te negeren dan om naar te handelen.
Eén: "de leverancier regelt het" is een architectuuraanname met een halfwaardetijd. ZDR was echt, de afhankelijkheid van het team was verstandig, en het beleid veranderde toch op een manier die hun toestemming niet vereiste. Overal waar je data-flow-diagram een "hier wordt niets opgeslagen"-annotatie heeft, wil je op zijn minst een stub van de code die de dag opvangt dat die annotatie niet meer klopt.
Twee: agentic features worden vaker achter percentage-flags uitgerold dan achter tenant-flags, omdat percentage-flags makkelijker te schrijven zijn. Percentage-flags zijn de verkeerde granulariteit voor elk traffic dat onder per-klant-contracten valt. Het team was al twee sprints van plan dit te herstellen. De ochtend van de aankondiging was de ochtend waarop ze stopten met van plan zijn.
Drie: de juiste eerste zet bij zo'n incident is de klantmail, niet de code-aanpassing. De code-aanpassing heb je in de hand; hoe de klant ervaart hoe je je gedraagt bij een verrassing niet. Stuur 'm voordat HN op het kantoor wordt gelezen.
Als jouw DPA zegt dat een sub-verwerker niets opslaat, bouw dan toch het purge-pad dat dat bewijst, voordat de leverancier van gedachten verandert.
Een audit van 30 minuten voor vandaag
- Grep je codebase op "ZDR", "zero retention", of elke comment die eindigt op "nothing to do". Elk daarvan is een toekomstig incident.
- Open je DPA-template. Lijst elke sub-verwerker op wiens retentiegedrag je op schrift beweert. Vind voor elk het productie-code-pad dat op verzoek bij die verwerker zou verwijderen. Bestaat dat niet, dan is dat het volgende ticket.
- Controleer dat elke feature flag die een sub-verwerker-route bestuurt tenant-scoped is, niet percentage-scoped. Als je niet binnen vijf minuten kunt antwoorden op "welke klantdata heeft deze provider deze week geraakt", heb je een vorm-probleem met je flags.
- Bevestig dat de workspace-admin-credentials voor elke AI-leverancier door een onbemande job kunnen worden gebruikt, niet alleen door een laptop. Verplaats ze naar je secret store.
- Schrijf in één alinea op wie klanten als eerste mailt als een sub-verwerker midden in een sprint zijn beleid wijzigt. Pin het in het incident-channel.
Dit alles vraagt geen sprint. Het vraagt een middag en de bereidheid om te ontdekken dat een van je annotaties nu een leugen is.
Toen wij de contract-review-agent bouwden voor een Nederlandse legaltech-klant, liepen we precies hier tegenaan: een purge-job die stilletjes aannam dat een sub-verwerker nooit iets zou opslaan, en een feature flag op de verkeerde hoogte. We hebben uiteindelijk de third-party id-capture in de SDK-grens zelf gezet, zodat een vierde of vijfde modelfamilie later toevoegen een config-wijziging is in plaats van weer een noodgeval. Datzelfde patroon zien we in elk AI-agents-project dat we opleveren: het verwijderpad zit in de architectuur, niet als voetnoot in de DPA.
Eén concrete actie voor vanmiddag: open de SDK-wrapper van je agent en bevestig dat je de response id van de provider bij elk bericht opslaat. Doe je dat niet, dan is dat een patch van 20 regels, en dat is het verschil tussen woensdagochtend een ongemak en woensdagochtend een klantgesprek.
Kern
Als jouw DPA zegt dat een sub-verwerker niets opslaat, bouw dan toch het purge-pad dat dat bewijst, voordat de leverancier dat voor je verandert.
FAQ
Geldt de nieuwe 30-dagen-retentie voor elk Anthropic-model?
Nee. De aankondiging was beperkt tot specifieke nieuwere modelfamilies die voor agentic tooling worden gebruikt. Oudere modellen die in productie vastgepind staan vielen buiten de scope, en daarom was rollback een werkbare eerste zet.
Wat is het praktische verschil tussen ZDR en een 30-dagen-retentievenster?
ZDR betekent dat de leverancier geen prompt- of response-content bewaart na de duur van de call. Een 30-dagen-venster betekent dat transcripties versleuteld worden opgeslagen, op verzoek te verwijderen zijn, en daarna automatisch worden opgeruimd na 30 dagen.
Moet ik standaard elk third-party AI-record verwijderen?
Alleen waar je DPA of lokale wetgeving dat vereist. Bouw het verwijderpad zodat de keuze bij jou ligt, en dwing het dan af bij afgesloten zaken of op klantverzoek, in plaats van een blanco sweep te draaien.
Is percentage-based feature flagging altijd verkeerd voor AI-features?
Niet altijd, maar wel zodra het traffic onder per-klant-contracten valt. Tenant-scoped flags zijn de juiste hoogte voor routeringsbeslissingen die een sub-verwerker raken.
Hoe ziet het eerste uur na een beleidswijziging bij een sub-verwerker eruit?
Zet de betrokken route uit, identificeer welke klanten erop zaten, en schrijf een mail in duidelijke taal voordat je code aanpast. De rewrite kan een uur wachten. Hoe de klant het nieuws voor het eerst leest niet.