Metadata-Version: 2.5
Name: attestari
Version: 0.0.6
Summary: The auditable memory layer for AI agents — provenance, bi-temporal recall, and provable deletion.
Project-URL: Homepage, https://github.com/attestari/attestari
Project-URL: Repository, https://github.com/attestari/attestari
Project-URL: Issues, https://github.com/attestari/attestari/issues
Project-URL: Documentation, https://github.com/attestari/attestari#readme
Author: Attestari contributors
License: Apache-2.0
License-File: LICENSE
Keywords: agents,ai,audit,compliance,deletion,event-sourcing,gdpr,knowledge-graph,langchain,llm,mcp,memory,provenance,rag
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.40; extra == 'anthropic'
Provides-Extra: crypto
Requires-Dist: cryptography>=42; extra == 'crypto'
Provides-Extra: dev
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: embeddings
Requires-Dist: sentence-transformers>=3.0; extra == 'embeddings'
Provides-Extra: postgres
Requires-Dist: pgvector>=0.2; extra == 'postgres'
Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
Provides-Extra: server
Requires-Dist: fastapi>=0.110; extra == 'server'
Requires-Dist: mcp<2,>=1.0; extra == 'server'
Requires-Dist: uvicorn[standard]>=0.29; extra == 'server'
Description-Content-Type: text/markdown

# Attestari

<!-- mcp-name: io.github.attestari/attestari -->

**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.

![license](https://img.shields.io/badge/license-Apache--2.0-blue)
![python](https://img.shields.io/badge/python-3.11%2B-blue)
![tests](https://img.shields.io/badge/tests-122%20passing-brightgreen)
![deps](https://img.shields.io/badge/core-zero%20dependencies-brightgreen)

```python
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**.

---

## Contents

- [Why it's different](#why-its-different)
- [What you get](#what-you-get)
- [Quickstart (30 seconds)](#quickstart-30-seconds)
- [The Python API](#the-python-api)
- [Run it for real](#run-it-for-real) — Postgres · server + console · MCP · TypeScript
- [REST API](#rest-api)
- [Configuration](#configuration)
- [Provable deletion + tamper-evident audit, in one demo](#provable-deletion--tamper-evident-audit-in-one-demo)
- [Already using Mem0 or Zep? Wrap it](#already-using-mem0-or-zep-wrap-it)
- [For auditors and DPOs](#for-auditors-and-dpos)
- [How it works](#how-it-works)
- [Project layout](#project-layout)
- [Docs & contributing](#docs--contributing)

## Why it's different

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.

## What you get

- **Provenance on every fact** — each memory traces back to the exact source
  message, with a character span, confidence, and timestamps.
- **Bi-temporal time travel** — query memory `as_of` any past instant; corrections
  supersede old facts without erasing them, so history is always reconstructable.
- **Provable deletion** — `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](docs/the-moat.md)).
- **Tamper-evident audit** — a hash-linked event chain; `verify_audit()` detects
  any edit/insert/delete, and the proof survives crypto-shred.
- **Conflict resolution** — single- vs multi-valued predicates; conflicts are
  surfaced via `conflicts()`, not silently dropped.
- **Entity resolution** — merge "the same entity, many surface forms," reversibly.
- **Hybrid retrieval** — semantic (pgvector) ⊕ keyword (full-text) ⊕ graph, with
  the bi-temporal filter built in.
- **Runs anywhere** — zero-dependency in-memory engine, or durable on one Postgres
  + pgvector container. No graph database. Works across model vendors.

## Quickstart (30 seconds)

```bash
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.

## The Python API

One facade, `Memory`, covers the whole surface:

```python
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. |

## Run it for real

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):

```python
from attestari import Memory
mem = Memory.local()      # or Memory.local("path/to/agent.db")
```

**Durable, on Postgres + pgvector** (one container, no graph DB):

```bash
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:

```bash
python -m attestari.initdb postgresql://user:pass@host:5432/db   # idempotent
```
```python
from attestari import Memory
mem = Memory.postgres()   # durable; materialized projections + pgvector + full-text search
```

**As a REST API + visual console:**
```bash
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:

```json
{
  "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`](clients/ts) (`@attestari/client`), a thin typed client mirroring the
`Memory` surface.

**With LangChain:** see [`clients/langchain`](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):
```bash
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:
```bash
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](docs/the-moat.md).

**Deploying for real.** A production checklist:
- **Storage:** use `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.
- **Server:** run under a process manager, e.g.
  `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).
- **Extraction & embeddings:** set `ANTHROPIC_API_KEY` (extraction auto-upgrades
  to Claude) and install `[embeddings]` for real semantic vectors.
- **Keys:** inject `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](docs/the-moat.md).
- **Backups:** exclude the derived projection tables (`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.
- **Secrets:** nothing is read from a `.env` file automatically — export the vars
  (or use your orchestrator's secret injection) before starting the process.

## REST API

`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. |

## Configuration

**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. |

## Provable deletion + tamper-evident audit, in one demo

![Attestari: a subject is forgotten — the raw row becomes unreadable ciphertext, recall returns nothing, and the tamper-evident audit chain still verifies](docs/deletion-demo.gif)

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):

```bash
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](docs/the-moat.md) for the threat model and honest boundaries.

For the full crypto-shred against a real encrypted Postgres row:

```bash
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.

## Already using Mem0 or Zep? Wrap it

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:

```python
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:

```bash
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.

## For auditors and DPOs

The people who have to *answer* for an AI system's memory get their own docs in
**[auditor/](auditor/)** — a plain-language [one-pager](auditor/dpo-one-pager.md)
(what's guaranteed, what isn't, how to check it yourself), an [EU AI Act Art. 12
mapping](auditor/eu-ai-act-article-12.md), and a [GDPR Art. 17
note](auditor/gdpr-article-17.md) on cryptographic erasure and the deployment
policies it depends on.

Any deployment can produce a dated snapshot of its own verifiable state:

```bash
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.

```bash
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
```

## How it works

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.

- [ARCHITECTURE.md](ARCHITECTURE.md) — the design, end to end.
- [LEARN.md](LEARN.md) — the same ideas explained from scratch, for newcomers.

## Project layout

```
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
```

## Docs & contributing

- [ARCHITECTURE.md](ARCHITECTURE.md) — the design
- [auditor/](auditor/) — for DPOs, compliance, and internal audit
- [LEARN.md](LEARN.md) — how Attestari works, from scratch
- [CONTRIBUTING.md](CONTRIBUTING.md) — dev setup, good first issues
- [SECURITY.md](SECURITY.md) — reporting vulnerabilities

## Status

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).

## License

Apache-2.0. The core stays permissively licensed; the hosted cloud and
enterprise/governance features are the commercial layer.
