
Product
PHP and Composer Support Is Now in Beta
Socket’s PHP and Composer support is now in Beta for all customers, with PHP reachability analysis generally available.
attestari
Advanced tools
The auditable memory layer for AI agents — provenance, bi-temporal recall, and provable deletion.
The auditable memory layer for AI agents. Give your agent long-term memory — like any memory layer — except every fact carries a receipt (where it came from, when), it runs on plain Postgres, the audit trail is tamper-evident, and any user's data can be provably deleted with a signed certificate.
from attestari import Memory
mem = Memory()
mem.add("Hi, I'm Alice. I live in Toronto and I work at Acme.",
subject_id="alice", valid_from="2021-06-01")
mem.add("Update: I moved to Berlin and I now work at Globex.",
subject_id="alice", valid_from="2026-01-01") # supersedes Acme + Toronto
mem.answer("where does the user work", subject_id="alice") # -> "Globex" (latest)
mem.answer("where did the user live", subject_id="alice",
as_of="2022-01-01") # -> "Toronto" (time-travel)
cert = mem.forget("alice") # right-to-be-forgotten -> a signed deletion certificate
No database, no API key, no model download required to run that — the core engine has zero dependencies.
Hosted memory is a black box: you can't see where a "memory" came from, you can't cleanly delete one user's data, and you can't prove the history wasn't altered. For a bank, hospital, or insurer — and under GDPR / the EU AI Act — that's a dealbreaker. Attestari is the neutral, self-hostable layer that fixes exactly that.
| Attestari | Typical memory layer | |
|---|---|---|
| Runs on plain Postgres (no graph DB) | ✅ | ✗ (needs Neo4j / a vector service) |
| Provenance on every fact | ✅ | partial |
| Bi-temporal ("what did it know on date D?") | ✅ | ✗ |
| Provable deletion + certificate (GDPR) | ✅ | ✗ |
| Tamper-evident audit trail (hash chain) | ✅ | ✗ |
| Works across model vendors | ✅ | usually locked to one |
The last two rows are the moat. Deletion you can prove: each user's data is
encrypted with their own key; forget() destroys the key, so the content is
unrecoverable — while an immutable log and a signed certificate remain as proof.
A history you can verify: every event is hash-linked, so verify_audit()
catches any edit, insert, or delete — and the proof survives deletion.
as_of any past instant; corrections
supersede old facts without erasing them, so history is always reconstructable.forget(subject_id) crypto-shreds a user's data and
returns a signed DeletionCertificate; content backups and replicas are
covered (key storage needs its own backup policy — see
the threat model).verify_audit() detects
any edit/insert/delete, and the proof survives crypto-shred.conflicts(), not silently dropped.git clone https://github.com/attestari/attestari && cd attestari
python examples/spike.py # zero-dep end-to-end loop
python examples/agent_with_memory.py # the "give an agent memory" pattern
You'll see facts change over time, a bi-temporal query answer differently "as of"
different dates, a provenance trace back to the source, and a forget() that
issues a certificate. No install, no API key, no database.
One facade, Memory, covers the whole surface:
from attestari import Memory
mem = Memory() # zero-dep, in-memory (tests, demos)
# mem = Memory.local() # durable in one local SQLite file — zero infrastructure
# mem = Memory.postgres() # durable on Postgres + pgvector (production service)
# --- write -------------------------------------------------------------
fact_ids = mem.add(
"I moved to Berlin and I now work at Globex.",
subject_id="alice", # whose memory this is
valid_from="2026-01-01", # when it became true (defaults to now)
source_ref="chat:msg-42", # where it came from (for provenance)
)
# --- read --------------------------------------------------------------
mem.search("where does the user work", subject_id="alice") # ranked SearchResults
mem.answer("where does the user work", subject_id="alice") # -> "Globex"
mem.answer("where did the user live", subject_id="alice",
as_of="2022-01-01") # time travel: recall as-of a past date
mem.timeline(subject_id="alice") # full bi-temporal history
mem.get_provenance(fact_ids[0]) # source episode + span
mem.conflicts(subject_id="alice") # surfaced conflicts
# --- govern ------------------------------------------------------------
report = mem.verify_audit() # AuditReport — is the hash chain intact?
cert = mem.forget("alice") # DeletionCertificate — provable erasure
| Method | Returns | What it does |
|---|---|---|
add(text, *, subject_id, valid_from=…, source_ref=…) | list[str] | Ingest a message; extract, dedup, and supersede facts. |
search(query, *, subject_id, as_of=…, limit=5) | list[SearchResult] | Hybrid retrieval with an optional time filter. |
answer(query, **kwargs) | str | None | The single top object for a query. |
timeline(*, subject_id) | list[Edge] | Every fact for a subject, live and superseded. |
get_provenance(fact_id) | Provenance | None | Trace a fact to its source episode + span. |
conflicts(*, subject_id=None) | list[dict] | Conflicts resolved by predicate cardinality. |
resolve_entities(names=None, *, auto=True) | ResolutionResult | Merge duplicate entities (reversible). |
forget(subject_id) | DeletionCertificate | Crypto-shred a subject; return proof. |
verify_audit(deep=False) | AuditReport | Verify the tamper-evident hash chain; deep=True also catches silent edits to event content. |
Three storage tiers, one engine — every guarantee (audit chain, crypto-shred, deep verification, time travel) holds on all three:
| Tier | Storage | For | Setup |
|---|---|---|---|
Memory() | in-memory | tests, demos, determinism | none |
Memory.local() | one SQLite file (~/.attestari/attestari.db) | a personal agent, MCP, prototypes — durable, single-process | none (stdlib) |
Memory.postgres() | Postgres + pgvector | production: concurrent access, indexed hybrid search | one container |
Durable with zero infrastructure (survives restarts; nothing to install or run):
from attestari import Memory
mem = Memory.local() # or Memory.local("path/to/agent.db")
Durable, on Postgres + pgvector (one container, no graph DB):
ATTESTARI_PG_PORT=5433 docker compose up -d # applies the schema on first boot
pip install -e ".[postgres,embeddings]"
export ATTESTARI_DATABASE_URL=postgresql://attestari:attestari@localhost:5433/attestari
Already have a Postgres (managed or local)? The schema ships inside the pip package — no clone needed:
python -m attestari.initdb postgresql://user:pass@host:5432/db # idempotent
from attestari import Memory
mem = Memory.postgres() # durable; materialized projections + pgvector + full-text search
As a REST API + visual console:
pip install -e ".[server]"
uvicorn attestari.server:app # API at /v1/*, the memory-graph console at /
As an MCP server (any agent — Claude, frameworks — can use it) — exposes
add_memory / search_memory / get_provenance / forget_subject over stdio.
Register it in your MCP client's config (e.g. Claude Desktop's
claude_desktop_config.json); the client launches the process for you:
{
"mcpServers": {
"attestari": {
"command": "attestari-mcp",
"args": [],
"env": {
"ATTESTARI_SQLITE_PATH": "~/.attestari/attestari.db",
"ANTHROPIC_API_KEY": "sk-ant-...",
"ATTESTARI_KEK": "base64-kek-here"
}
}
}
}
Only command/args are required. Durable by default (memories go to the local
SQLite file, so they survive app restarts); the env block is where per-server
config lives — add ATTESTARI_DATABASE_URL to use Postgres instead of SQLite,
ANTHROPIC_API_KEY to upgrade extraction to Claude, ATTESTARI_KEK to enable
crypto-shred. To run it standalone (e.g. to debug): attestari-mcp (or
python -m attestari.mcp). Without a local install, MCP clients can spawn it
straight from PyPI: uvx --from "attestari[server]" attestari-mcp.
From TypeScript — the TS client talks to the REST API, so start the server
first (see above; it defaults to http://localhost:8000). Then see
clients/ts (@attestari/client), a thin typed client mirroring the
Memory surface.
With LangChain: see clients/langchain (attestari-langchain)
— a AttestariRetriever (recall facts with provenance) and AttestariChatMessageHistory
(drop-in memory for RunnableWithMessageHistory) for any chain or agent.
With real Claude extraction (instead of the zero-dep deterministic extractor):
pip install -e ".[anthropic]"
export ANTHROPIC_API_KEY=sk-ant-...
python examples/spike.py --llm anthropic
Enable crypto-shred deletion (turn forget() from a logical delete into
cryptographic erasure). Encryption is opt-in via a root key-encryption key
(KEK); with none set, forget() still works but only drops the data from reads.
Mint a KEK once and set it in the environment:
pip install -e ".[crypto]"
export ATTESTARI_KEK=$(python -c "from attestari.crypto import generate_kek; print(generate_kek())")
Now each subject's PII is encrypted at rest under a per-subject key, and
forget() destroys that key — the ciphertext is unrecoverable, while the audit
proof survives. With the KEK set, the DeletionCertificate is also signed
(HMAC-SHA256 under a KEK-derived key); anyone holding the KEK can verify it
offline — verify_certificate(cert, kek) — and a certificate with any altered
field fails. Without a KEK, forget() is a logical delete and the certificate
is issued unsigned. Keep the KEK out of the database and its backups (env
var or a KMS) — storing it next to the data defeats the shred. See the backup
boundary in docs/the-moat.md.
Deploying for real. A production checklist:
Memory.postgres() (concurrent access); apply the schema with
python -m attestari.initdb "$ATTESTARI_DATABASE_URL". Memory.local() (SQLite) is
single-process — great for one agent or an MCP server, not a shared service.uvicorn attestari.server:app --host 0.0.0.0 --port 8000 --workers 4 behind a
reverse proxy; put your own auth in front (the API ships without auth).ANTHROPIC_API_KEY (extraction auto-upgrades
to Claude) and install [embeddings] for real semantic vectors.ATTESTARI_KEK from a KMS/secrets manager as an env var — never
bake it into an image or the DB. Back the keyring table up on a separate,
short-retention policy (or rotate the KEK) so a restored data backup can't
resurrect a shredded subject — see docs/the-moat.md.edge, entity) as well —
they hold plaintext fact text for retrieval and are fully rebuildable from the
(ciphertext) event log, so backing them up only weakens the shred..env file automatically — export the vars
(or use your orchestrator's secret injection) before starting the process.uvicorn attestari.server:app serves:
| Method & path | Purpose |
|---|---|
GET /healthz | Liveness check. |
POST /v1/add | Ingest a message. |
GET /v1/search | Hybrid retrieval (q, subject_id, as_of, limit). |
GET /v1/timeline | Full bi-temporal history for a subject. |
GET /v1/provenance/{fact_id} | Trace a fact to its source. |
POST /v1/forget/{subject_id} | Provable deletion → certificate. |
GET /v1/conflicts | Surfaced conflicts. |
GET /v1/audit/verify | Verify the audit hash chain. |
GET /v1/graph | The memory graph (for the console). |
GET / | The visual graph console. |
Environment variables (all optional — the engine runs with none of them):
| Variable | Effect |
|---|---|
ATTESTARI_DATABASE_URL | Postgres DSN; the server/MCP use Postgres instead of local SQLite. |
ATTESTARI_SQLITE_PATH | Where Memory.local()-backed server/MCP keep the SQLite file (default ~/.attestari/attestari.db). |
ATTESTARI_KEK | Root key-encryption key; turns on crypto-shred deletion. |
ATTESTARI_PG_PORT | Host port for the bundled docker compose Postgres (default 5432). |
ATTESTARI_WRAP_UPSTREAM | Base URL of a memory service to govern; mounts the /v1/wrap/* endpoints (unset = no wrap routes). |
ATTESTARI_WRAP_UPSTREAM_TOKEN | Sent to the upstream as Authorization: Bearer …. |
ATTESTARI_WRAP_*_PATH | Override the upstream paths — ADD, SEARCH, DELETE, GET_ALL (defaults /add, /search, /delete, /get_all). Set GET_ALL empty to disable the post-delete read-back. |
ANTHROPIC_API_KEY | Enables Claude fact extraction — the server/MCP upgrade from the regex extractor automatically. |
ATTESTARI_EXTRACTOR_MODEL | Override the extraction model (default claude-opus-4-8). |
Install extras (pip install -e ".[extra]"):
| Extra | Adds |
|---|---|
postgres | psycopg + pgvector — the durable event store. |
embeddings | sentence-transformers — real semantic embeddings. |
crypto | cryptography — crypto-shred deletion. |
server | FastAPI + uvicorn + MCP — the REST server and MCP server. |
anthropic | The Anthropic SDK — Claude fact extraction. |
dev | pytest + ruff — tests and linting. |

Don't trust the bullet points — break the properties and watch them get caught.
This runs with no database, no API key, no model download, and is self-verifying
(every claim ends in an assert; it crashes if any property is false):
python examples/prove_the_moat.py # tamper -> caught · crypto-shred -> unrecoverable · time-travel
It adversarially proves all three differentiators: a silently rewritten fact is
caught at the exact seq by verify_audit(deep=True); a crypto-shredded
subject's ciphertext is provably unrecoverable while the audit proof survives;
and a corrected fact is queryable in the past without erasing history. (Runs on a
bare clone; pip install "attestari[crypto]" upgrades claim 2 from logical erasure to
cryptographic crypto-shred.) See
docs/the-moat.md for the threat model and honest boundaries.
For the full crypto-shred against a real encrypted Postgres row:
ATTESTARI_DATABASE_URL=postgresql://attestari:attestari@localhost:5433/attestari \
python examples/audit_and_forget_demo.py # audit -> trace -> forget -> PROVE
It shows: a fact traced to its source, a subject forgotten, the raw row confirmed to be unreadable ciphertext, recall returning nothing — and the audit chain still valid after the erasure.
You don't have to replace your memory layer to get an audit trail. wrap() puts
Attestari in front of the client you already use:
from attestari.wrap import wrap
governed = wrap(mem0_client) # or zep, or your own
governed.add("I live in Berlin.", subject_id="u1") # recorded, then stored
governed.search("where do I live", subject_id="u1") # straight through
receipt = governed.forget("u1") # both stores + proof
receipt.complete # True only if BOTH halves succeeded
receipt.certificate # Attestari's signed deletion certificate
receipt.downstream_verified # True = we read the store back and it was empty
receipt.downstream_error # what the wrapped store said, if it refused
governed.verify_audit(deep=True) # the chain still holds
Not on Python? Point your app at the Attestari server instead of at your memory service and get the same guarantees over REST:
ATTESTARI_WRAP_UPSTREAM=https://memory.internal uvicorn attestari.server:app
POST /v1/wrap/add {"text": …, "subject_id": …} # recorded, then forwarded
POST /v1/wrap/search {"query": …, "subject_id": …} # passthrough
POST /v1/wrap/forget/{id} # both stores + evidence
A partial erasure returns 409, not 200 — a caller checking only the status
code must never read a half-completed deletion as success. The routes appear
only when an upstream is configured. See attestari.wrap_http for the small
JSON contract the upstream is expected to speak (stdlib-only, no new deps).
It doesn't take the delete call's word for it. After deleting, forget()
reads the subject back out of the wrapped store. A store that returns
{"deleted": true} and keeps the rows is caught right there —
downstream_verified is False, complete is False, and the discrepancy
goes into the audit chain. If the adapter has no read-back operation,
downstream_verified is None rather than True: "nobody checked" and
"checked and clean" are different claims, and only one of them is evidence.
Writes are recorded in the tamper-evident chain before being passed downstream;
reads pass through untouched (retrieval is why you kept your store); forget()
deletes downstream, crypto-shreds Attestari's copy, and records what the
downstream store actually did — including failure. Method names and the subject
keyword are configurable via Adapter, so this works against a bare vector
store too.
What wrapping does and doesn't prove. Attestari can't cryptographically shred data inside someone else's service — it doesn't hold their keys. A wrapped deployment proves the deletion was requested, that the downstream delete was called and what it returned, that Attestari's own copy is unrecoverable, and that none of that was altered afterwards. That's an auditable deletion record across both systems, not crypto-shred everywhere: a wrapped store is only as erasable as its own delete endpoint is honest, and wrapping turns that endpoint's behaviour into evidence instead of a promise. For full cryptographic erasure, the data has to live in Attestari itself.
The people who have to answer for an AI system's memory get their own docs in auditor/ — a plain-language one-pager (what's guaranteed, what isn't, how to check it yourself), an EU AI Act Art. 12 mapping, and a GDPR Art. 17 note on cryptographic erasure and the deployment policies it depends on.
Any deployment can produce a dated snapshot of its own verifiable state:
attestari evidence --deep --out ./evidence # EVIDENCE.md + evidence.json
The bundle carries the audit-chain result and head hash, an erasure register with every request re-checked against the current ledger, and the retained deletion certificates. Every claim is re-derived from the live ledger when the bundle is generated — it's evidence because you can regenerate it, with read access and no cooperation from whoever runs the system.
attestari verify --deep # re-check the chain, re-hashing content
attestari verify --user u_123 # confirm one subject's erasure; non-zero exit if not
The source of truth is an append-only event log; everything you query (the knowledge graph, the vector index, the keyword index) is a projection you can rebuild from it. That's why audit, time-travel, provenance, and provable deletion fall out of the design instead of being bolted on.
src/attestari/ the engine — events, store, projection, retrieve, memory,
crypto (shred), audit (hash chain), predicates, resolver
src/attestari/server.py, console.py FastAPI REST API + the graph console
src/attestari/mcp.py the MCP server
src/attestari/cli.py, evidence.py `attestari verify` + the evidence bundle
src/attestari/wrap.py, wrap_http.py govern an existing memory layer (Mem0,
Zep, or any HTTP service) instead of replacing it
auditor/ the auditor pack — DPO one-pager, AI Act + GDPR mappings
examples/ runnable demos — start with spike.py
eval/ quality + retrieval-latency harness
clients/ts/ the TypeScript SDK (@attestari/client)
clients/langchain/ the LangChain integration (attestari-langchain)
src/attestari/db/schema.sql the Postgres bi-temporal schema
docker-compose.yml Postgres + pgvector
The engine and its differentiators — verifiable deletion, tamper-evident audit, bi-temporal provenance, Postgres-native retrieval — are built and tested (122 tests; Postgres p95 ≈ 1 ms).
Apache-2.0. The core stays permissively licensed; the hosted cloud and enterprise/governance features are the commercial layer.
FAQs
The auditable memory layer for AI agents — provenance, bi-temporal recall, and provable deletion.
We found that attestari demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Product
Socket’s PHP and Composer support is now in Beta for all customers, with PHP reachability analysis generally available.

Product
Socket is bringing experimental protection to Firefox, scanning 97,000+ extensions in Mozilla's official directory for malware and risky updates.

Research
/Security News
Three compromised Rust crates pulled in a malicious dependency that downloaded and executed cross-platform malware during Cargo builds.