Attestari

io.github.attestari由 GitHub 账号 attestari 发布v0.0.8更新于 Oct 24, 2026

Auditable memory for AI agents: provable deletion, tamper-evident audit, time-travel recall.

发布方自己的服务:Attestari

概览

AI 生成的概览

为 AI 代理提供可审计的长期记忆:记录带来源的事实,支持按历史时点回溯、防篡改审计与可证明删除。

可以做什么
  • 为某个主体添加记忆,自动抽取、去重并让新事实取代旧事实
  • 按主体检索并回答问题,可指定过去某个日期作为查询时点
  • 把某条事实追溯到其来源消息及字符区间
  • 查看主体的完整双时态时间线与已暴露的冲突
  • 校验哈希链审计记录,并在启用时擦除主体数据并出具签名的删除证书
适用场景
适合需要长期记住人物或实体信息、且必须说明每条事实来源、还原某历史时点的真实情况、或能证明用户数据已被删除的场景。更偏向受监管或自托管的部署,而非简单的笔记存储。
运行要求
以 stdio 方式在本地运行,通常由 MCP 客户端通过 uvx 从 PyPI 包启动,或使用已安装的命令。默认无需任何配置,记忆保存在本地 SQLite 文件中。可选环境变量:ATTESTARI_SQLITE_PATH 指定文件位置,ATTESTARI_DATABASE_URL 改用带 pgvector 的 Postgres,ANTHROPIC_API_KEY 启用 Claude 事实抽取,ATTESTARI_KEK 启用静态加密,ATTESTARI_MCP_ALLOW_FORGET 开放擦除工具。
安装前请注意
ATTESTARI_KEK 是根加密密钥,必须与数据库及其备份分开保存,否则擦除将失效。擦除不可逆且默认关闭;开启 ATTESTARI_MCP_ALLOW_FORGET 后代理可删除主体数据,该工具被标记为破坏性操作并需两次调用确认。即使擦除后,subject_id、谓词、时间戳和 source_ref 等字段仍可读,应使用假名化 ID 且勿在 source_ref 中写入个人数据。设置 ANTHROPIC_API_KEY 会把记忆文本发送给 Anthropic 做抽取。

安装

在 SourceWeft 中

  1. 打开 控制台中的 Attestari,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

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] [python] [tests] [deps]

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

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.

AttestariTypical memory layer
Runs on plain Postgres (no graph DB)✅✗ (needs Neo4j / a vector service)
Provenance on every fact✅partial
Bi-temporal ("what was true 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. as_of filters on valid time (when a fact was true). System time (when Attestari recorded it) is stored on every fact, but there's no query on it yet.
  • 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).
  • 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. Merges are recorded, but reads don't follow them yet: search() and timeline() still see each surface form separately.
  • Hybrid retrieval — semantic (pgvector) ⊕ keyword (full-text), 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 attestaripython examples/spike.py              # zero-dep end-to-end looppython 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 SearchResultsmem.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 datemem.timeline(subject_id="alice")                                    # full bi-temporal historymem.get_provenance(fact_ids[0])                                     # source episode + spanmem.conflicts(subject_id="alice")                                   # surfaced conflicts
# --- govern ------------------------------------------------------------report = mem.verify_audit()          # AuditReport — is the hash chain intact?cert   = mem.forget("alice")         # DeletionCertificate — provable erasure
MethodReturnsWhat 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 | NoneThe single top object for a query.
timeline(*, subject_id)list[Edge]Every fact for a subject, live and superseded.
get_provenance(fact_id)Provenance | NoneTrace a fact to its source episode + span.
conflicts(*, subject_id=None)list[dict]Conflicts resolved by predicate cardinality.
resolve_entities(names=None, *, auto=True)ResolutionResultMerge duplicate entities (reversible).
forget(subject_id)DeletionCertificateCrypto-shred a subject; return proof.
verify_audit(deep=False)AuditReportVerify 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:

TierStorageForSetup
Memory()in-memorytests, demos, determinismnone
Memory.local()one SQLite file (~/.attestari/attestari.db)a personal agent, MCP, prototypes — durable, single-processnone (stdlib)
Memory.postgres()Postgres + pgvectorproduction: concurrent access, indexed hybrid searchone container

Durable with zero infrastructure (survives restarts; nothing to install or run):

python
from attestari import Memorymem = 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 bootpip 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 Memorymem = 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 over stdio, plus forget_subject if you opt in (below). 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.

Erasure over MCP is opt-in. An agent can be steered by text it reads, and erasure can't be undone, so forget_subject is only offered when the env block sets "ATTESTARI_MCP_ALLOW_FORGET": "1". It is then flagged destructive (clients that honour tool annotations ask before running it) and takes two calls: the first previews what would be erased and returns a manifest_hash; the second passes that hash back as confirm_manifest_hash, and erases only if the subject's records still match the preview. Leave it off unless the agent is meant to erase people; Memory.forget() and POST /v1/forget are unaffected.

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

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.

What forget() covers, and what it leaves: it erases the episodes recorded under that subject_id and the facts extracted from them. A fact about the person that was extracted from someone else's message lives in that other subject's scope and survives. Encryption covers message text and fact values only; each event's other fields stay readable after an erasure — subject_id, a fact's subject and predicate (with Claude extraction the subject can be a person's name), scope ids, timestamps, character spans (which reveal a value's length) and source_ref. So use opaque, pseudonymous subject_ids and keep personal data out of source_ref. An erased subject_id stays closed: add() for it raises ForgottenSubjectError and stores nothing (POST /v1/add returns 409, and the MCP tool refuses), so record someone who comes back under a new id. Postgres keeps a register of certificates; Memory() and Memory.local() hand the certificate back once from forget(), so store it yourself there.

Deploying for real. A production checklist:

  • Storage: use Memory.postgres() (concurrent access); apply the schema with python -m attestari.initdb "$ATTESTARI_DATABASE_URL", and run it again after every upgrade (it's idempotent; until then, writes stop with that instruction and reads keep working). 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).
  • Writes for one subject: on 0.0.7 and earlier, serialise them in your app. Two add() calls for the same subject at the same moment can each supersede the same old value, leaving two live values for a single-valued predicate. Later releases decide supersession under the store's write lock, so concurrent writes are safe.
  • Writes after an erasure: on 0.0.7 and earlier, check is_forgotten(subject_id) before add(). With a KEK set, a write for a forgotten subject makes every later read of the log fail with InvalidTag, for every subject. To recover, delete that subject's row from the keyring table. Later releases refuse the write instead.
  • 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.
  • 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. After a restore without them, the first connection rebuilds them.
  • 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 & pathPurpose
GET /healthzLiveness check.
POST /v1/addIngest a message.
GET /v1/searchHybrid retrieval (q, subject_id, as_of, limit).
GET /v1/timelineFull 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/conflictsSurfaced conflicts.
GET /v1/audit/verifyVerify the audit hash chain.
GET /v1/graphThe memory graph (for the console).
GET /The visual graph console.

Configuration

Environment variables (all optional — the engine runs with none of them):

VariableEffect
ATTESTARI_DATABASE_URLPostgres DSN; the server/MCP use Postgres instead of local SQLite.
ATTESTARI_SQLITE_PATHWhere Memory.local()-backed server/MCP keep the SQLite file (default ~/.attestari/attestari.db).
ATTESTARI_KEKRoot key-encryption key; turns on crypto-shred deletion.
ATTESTARI_MCP_ALLOW_FORGET1 offers the forget_subject tool on the MCP server (off by default; two calls when on).
ATTESTARI_PG_PORTHost port for the bundled docker compose Postgres (default 5432).
ATTESTARI_WRAP_UPSTREAMBase URL of a memory service to govern; mounts the /v1/wrap/* endpoints (unset = no wrap routes).
ATTESTARI_WRAP_UPSTREAM_TOKENSent to the upstream as Authorization: Bearer ….
ATTESTARI_WRAP_*_PATHOverride 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_KEYEnables Claude fact extraction — the server/MCP upgrade from the regex extractor automatically.
ATTESTARI_EXTRACTOR_MODELOverride the extraction model (default claude-opus-4-8).

Install extras (pip install -e ".[extra]"):

ExtraAdds
postgrespsycopg + pgvector — the durable event store.
embeddingssentence-transformers — real semantic embeddings.
cryptocryptography — crypto-shred deletion.
serverFastAPI + uvicorn + MCP — the REST server and MCP server.
anthropicThe Anthropic SDK — Claude fact extraction.
devpytest + 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]

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 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 storedgoverned.search("where do I live", subject_id="u1")       # straight throughreceipt = governed.forget("u1")                           # both stores + proof
receipt.complete             # True only if BOTH halves succeededreceipt.certificate          # Attestari's signed deletion certificatereceipt.downstream_verified  # True = we read the store back and it was emptyreceipt.downstream_error     # what the wrapped store said, if it refusedgoverned.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 forwardedPOST /v1/wrap/search       {"query": …, "subject_id": …}  # passthroughPOST /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/ — 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:

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

  • The Attestari Atlas — the whole engine on one illustrated page: every write, read and erase, the audit chain you can tamper with in the browser, where it fits, and its sharp edges. Written for 0.0.7.
  • ARCHITECTURE.md — the design, end to end.
  • 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, resolversrc/attestari/server.py, console.py   FastAPI REST API + the graph consolesrc/attestari/mcp.py   the MCP serversrc/attestari/cli.py, evidence.py     `attestari verify` + the evidence bundlesrc/attestari/wrap.py, wrap_http.py   govern an existing memory layer (Mem0,                    Zep, or any HTTP service) instead of replacing itauditor/            the auditor pack — DPO one-pager, AI Act + GDPR mappingsexamples/           runnable demos — start with spike.pyeval/               quality + retrieval-latency harnessclients/ts/         the TypeScript SDK (@attestari/client)clients/langchain/  the LangChain integration (attestari-langchain)src/attestari/db/schema.sql       the Postgres bi-temporal schemadocker-compose.yml  Postgres + pgvector

Docs & contributing

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.

来源:README.md,提交 4bb79df

工具

0
SourceWeft 尚未在沙箱中启动过该软件包,因此其工具未知。

版本历史

3
  1. v0.0.8最新Oct 9, 2026
  2. v0.0.7Oct 9, 2026
  3. v0.0.6Sep 16, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分

其他 Attestari 集成