Long-term memory on disk is a knowledge-base exfil cache. Encrypt at rest, redact at write time, and gate recall by classification — so a stolen laptop or poisoned entry cannot quietly own the agent’s history.
This is Part 13 and the close of Phase 4: Architectural Best Practices. Part 11 hardened the wire; Part 12 hardened the bytes you schedule. This post answers the residual question: when the agent’s “brain” lives as Obsidian vaults or JSONL on disk, what stops a stolen disk, a curious roommate process, or a delayed-action poison write from owning everything the agent ever knew?
The Vault That Synced Itself
An edge laptop runs a ClawQL coding agent. Memory lives in ~/.ClawQL/memory/ as JSONL plus an Obsidian vault. Full-disk encryption was “coming next sprint.” The machine is lost in a rideshare. The thief mounts the SSD and grep -r api_key. Years of customer notes, pasted secrets the redactor never saw, and a poison note an injected README wrote last month wait patiently for the next session restore from backup.
Cluster cousin: memory bucket without Object Lock, shared prefix, plaintext objects. Same knowledge base, prettier URL.
Parts 4–7 protect the live process. Memory outlives the process. Unencrypted persistence is a crown jewel with a longer TTL than any JWT.
Memory as a Delayed-Action Surface
Poisoning vectors from the curriculum:
| Vector | When harm appears | Mitigation |
|---|---|---|
| Malicious tool/user write | Future retrieval | Redact + injection-pattern block at write |
| RAG / document recall poison | Later sessions | Classify at ingest; scrub before store (Part 14) |
| Inter-agent fabricated result | Downstream agent trust | mTLS + message crypto (Part 11) + write-time gates |
| Post-write modification | Silent history rewrite | Append-only + Merkle + WORM |
| Disk / bucket theft | Offline | Encryption at rest + key not beside ciphertext |
Classification taxonomy (travels with every entry):
| Level | Rule of thumb |
|---|---|
| public | Safe externally |
| internal | Org-only; never public memory / undeclared external APIs |
| confidential | Need-to-know roles; HITL for external disclosure |
| secret | Never persist; never externalize; destroy with session |
Agents become classifiers the moment they process data. Make that explicit architecture — not accidental logging.
The Architecture Pattern: Data-at-Rest Protection
Encrypt the store (directory / volume / object SSE + app-level where keys must leave the host). Redact at write — Presidio placeholders; block credentials/secret class outright. Tag classification and residency at ingestion; enforce on every recall and tool egress. Append-only semantics (supersedes create new entries; no in-place edits of trusted history). Integrity via Merkle chain / checksums; scheduled verify; fail closed on read if broken. Keys in Vault transit (per-subject where GDPR erasure requires crypto-shredding) — never in the JSONL header.
Encrypting the Directory
Laptop / edge:
Keep memory under a dedicated path (~/.ClawQL/memory, Obsidian vault inside) — never the whole $HOME as the trust root.
macOS: FileVault for the volume and an app-level key (Keychain / passphrase unlock) wrapping the vault directory so a logged-in browser compromise is not automatic vault plaintext.
Linux: fscrypt on the memory directory or an encrypted loop/LVM volume; unlock via user session or agent supervisor — not a world-readable key file beside the data.
# Linux sketch
sudo fscrypt encrypt ~/.ClawQL/memory
# Unlock policy: passphrase or unlocking key fetched from agent supervisor / TPM
App-level envelope when OS crypto alone is not enough:
const redacted = await presidio.redact(entry);
assertNoSecrets(redacted); // reject entire write if credential/secret class
const classified = tagClassification(redacted, ingestHints);
const ciphertext = await vault.transit.encrypt(subjectKeyId, classified.payload);
await appendOnlyStore.commit({
...classified.meta,
ciphertext,
prevRoot,
});
Never store Vault tokens in the memory file for convenience.
Redaction and Classification at Write
Presidio at every memory write, log emit, and external tool payload:
Replace PII with [REDACTED:EMAIL_ADDRESS]-style placeholders. Credentials / secret class → reject the write; do not partially redact. Store classification and residencyRegion as metadata the client cannot override. Gateway recall predicate: tenantId + maxClassification — server-side only.
Poison patterns blocked at write: instruction-injection phrases (ignore previous instructions, system prompt:, override:, …). Rate-limit memory writes (>50/60s → throttle) so poison storms show up in Part 9.
Integrity: Append-Only, Merkle, WORM
Cluster / server memory:
hash_n = SHA-256(entry_content + entry_metadata + prev_root)
Updates = new entry with supersedes: <oldHash>; originals remain. Object Lock COMPLIANCE (or equivalent) for retention. Integrity job every ~15 minutes; on failure: block all memory reads and page — serving a tampered store is worse than an outage.
GDPR erasure vs WORM: delete per-subject transit keys (crypto-shred); ciphertext stays, personal data becomes unattributable. Confirm requests before irreversible delete.
Local JSONL: at minimum HMAC or signature chain with a key outside the directory; refuse load if the chain breaks.
Residency and Sync Traps
EU/confidential entries only in EU buckets; Panguard blocks memory_write that would mis-reside. Obsidian Sync / iCloud / Dropbox: if the vault holds internal+, either disable sync, use zero-knowledge sync you’ve actually verified, or keep secrets out of the synced tree entirely. Analytics/dev: synthetic or pseudonymized copies — never prod memory mounts in engineering laptops “to reproduce a bug.”
How This Joins Parts 6, 8, and 11
| Layer | Role for memory |
|---|---|
| Part 6 FIM | Deny unexpected openat of vault paths by foreign UIDs |
| Part 8 | MEMORY events with traceId; never log raw entry bodies |
| Part 9 | Burst memory_write / cross-class recall attempts |
| Part 11 | mTLS + ACLs so peer agents cannot inject poison writes |
| Part 12 | Signed agent builds still need encrypted disks underneath |
FIM without encryption protects integrity of paths while leaving confidentiality to luck. Encryption without redaction stores secrets beautifully.
Honest Failure Modes
Unlock UX. Agents that cannot unlock memory at boot will reinvent plaintext caches. Pair unlock with the supervisor, not a sticky note in the repo.
Performance. Redaction and encrypt add latency; batch carefully but never skip on “trusted internal” traffic.
False sense of FileVault. Full-disk encryption stops thief-with-screwdriver, not malware running as the logged-in user. App-level keys + Part 6 still matter.
Merkle without blocking reads. “Alert only” on integrity fail is how you serve lies. Fail closed.
Obsidian plugins. Third-party plugins are another supply chain (Part 12) reading the same vault — allowlist harshly.
Getting Started
Inventory memory locations (JSONL, Obsidian, object prefixes) and who can read them today. Encrypt local dirs (fscrypt/FileVault + app wrap) and enable SSE + KMS for buckets. Put Presidio on the write path; reject secrets; tag classification and residency. Enforce classification on recall in the gateway; add canary tests. Turn on append-only + integrity verify with fail-closed reads (server) / HMAC chain (local). Emit MEMORY security events (Part 8 schema) without bodies; alert on write storms (Part 9).
Part 14: sanitize untrusted text before reason so infrastructure is not the only line against injection.
Companion: DevSecOps-boilerplate · Seatbelt essay. Docs: Memory poisoning prevention · Classification / residency · Secrets at rest.
