{
  "markdown": "# Attestari\n\n<!-- mcp-name: io.github.attestari/attestari -->\n\n**The auditable memory layer for AI agents.** Give your agent long-term memory —\nlike any memory layer — except every fact carries a receipt (where it came from,\nwhen), it runs on plain Postgres, the audit trail is tamper-evident, and any\nuser's data can be **provably deleted** with a signed certificate.\n\n![license](https://img.shields.io/badge/license-Apache--2.0-blue)\n![python](https://img.shields.io/badge/python-3.11%2B-blue)\n![tests](https://img.shields.io/badge/tests-122%20passing-brightgreen)\n![deps](https://img.shields.io/badge/core-zero%20dependencies-brightgreen)\n\n```python\nfrom attestari import Memory\n\nmem = Memory()\nmem.add(\"Hi, I'm Alice. I live in Toronto and I work at Acme.\",\n        subject_id=\"alice\", valid_from=\"2021-06-01\")\nmem.add(\"Update: I moved to Berlin and I now work at Globex.\",\n        subject_id=\"alice\", valid_from=\"2026-01-01\")          # supersedes Acme + Toronto\n\nmem.answer(\"where does the user work\", subject_id=\"alice\")    # -> \"Globex\"  (latest)\nmem.answer(\"where did the user live\",  subject_id=\"alice\",\n           as_of=\"2022-01-01\")                                # -> \"Toronto\" (time-travel)\n\ncert = mem.forget(\"alice\")   # right-to-be-forgotten -> a signed deletion certificate\n```\n\nNo database, no API key, no model download required to run that — the core engine\nhas **zero dependencies**.\n\n---\n\n## Contents\n\n- [Why it's different](#why-its-different)\n- [What you get](#what-you-get)\n- [Quickstart (30 seconds)](#quickstart-30-seconds)\n- [The Python API](#the-python-api)\n- [Run it for real](#run-it-for-real) — Postgres · server + console · MCP · TypeScript\n- [REST API](#rest-api)\n- [Configuration](#configuration)\n- [Provable deletion + tamper-evident audit, in one demo](#provable-deletion--tamper-evident-audit-in-one-demo)\n- [Already using Mem0 or Zep? Wrap it](#already-using-mem0-or-zep-wrap-it)\n- [For auditors and DPOs](#for-auditors-and-dpos)\n- [How it works](#how-it-works)\n- [Project layout](#project-layout)\n- [Docs & contributing](#docs--contributing)\n\n## Why it's different\n\nHosted memory is a black box: you can't see where a \"memory\" came from, you can't\ncleanly delete one user's data, and you can't *prove* the history wasn't altered.\nFor a bank, hospital, or insurer — and under GDPR / the EU AI Act — that's a\ndealbreaker. Attestari is the neutral, self-hostable layer that fixes exactly that.\n\n| | Attestari | Typical memory layer |\n|---|---|---|\n| Runs on plain Postgres (no graph DB) | ✅ | ✗ (needs Neo4j / a vector service) |\n| Provenance on every fact | ✅ | partial |\n| Bi-temporal (\"what did it know on date D?\") | ✅ | ✗ |\n| **Provable deletion + certificate (GDPR)** | ✅ | ✗ |\n| **Tamper-evident audit trail (hash chain)** | ✅ | ✗ |\n| Works across model vendors | ✅ | usually locked to one |\n\nThe last two rows are the moat. **Deletion you can prove:** each user's data is\nencrypted with their own key; `forget()` destroys the key, so the content is\nunrecoverable — while an immutable log and a signed certificate remain as proof.\n**A history you can verify:** every event is hash-linked, so `verify_audit()`\ncatches any edit, insert, or delete — and the proof survives deletion.\n\n## What you get\n\n- **Provenance on every fact** — each memory traces back to the exact source\n  message, with a character span, confidence, and timestamps.\n- **Bi-temporal time travel** — query memory `as_of` any past instant; corrections\n  supersede old facts without erasing them, so history is always reconstructable.\n- **Provable deletion** — `forget(subject_id)` crypto-shreds a user's data and\n  returns a signed `DeletionCertificate`; content backups and replicas are\n  covered (key storage needs its own backup policy — see\n  [the threat model](docs/the-moat.md)).\n- **Tamper-evident audit** — a hash-linked event chain; `verify_audit()` detects\n  any edit/insert/delete, and the proof survives crypto-shred.\n- **Conflict resolution** — single- vs multi-valued predicates; conflicts are\n  surfaced via `conflicts()`, not silently dropped.\n- **Entity resolution** — merge \"the same entity, many surface forms,\" reversibly.\n- **Hybrid retrieval** — semantic (pgvector) ⊕ keyword (full-text) ⊕ graph, with\n  the bi-temporal filter built in.\n- **Runs anywhere** — zero-dependency in-memory engine, or durable on one Postgres\n  + pgvector container. No graph database. Works across model vendors.\n\n## Quickstart (30 seconds)\n\n```bash\ngit clone https://github.com/attestari/attestari && cd attestari\npython examples/spike.py              # zero-dep end-to-end loop\npython examples/agent_with_memory.py  # the \"give an agent memory\" pattern\n```\n\nYou'll see facts change over time, a bi-temporal query answer differently \"as of\"\ndifferent dates, a provenance trace back to the source, and a `forget()` that\nissues a certificate. No install, no API key, no database.\n\n## The Python API\n\nOne facade, `Memory`, covers the whole surface:\n\n```python\nfrom attestari import Memory\n\nmem = Memory()                       # zero-dep, in-memory (tests, demos)\n# mem = Memory.local()               # durable in one local SQLite file — zero infrastructure\n# mem = Memory.postgres()            # durable on Postgres + pgvector (production service)\n\n# --- write -------------------------------------------------------------\nfact_ids = mem.add(\n    \"I moved to Berlin and I now work at Globex.\",\n    subject_id=\"alice\",              # whose memory this is\n    valid_from=\"2026-01-01\",         # when it became true (defaults to now)\n    source_ref=\"chat:msg-42\",        # where it came from (for provenance)\n)\n\n# --- read --------------------------------------------------------------\nmem.search(\"where does the user work\", subject_id=\"alice\")          # ranked SearchResults\nmem.answer(\"where does the user work\", subject_id=\"alice\")          # -> \"Globex\"\nmem.answer(\"where did the user live\",  subject_id=\"alice\",\n           as_of=\"2022-01-01\")                                      # time travel: recall as-of a past date\nmem.timeline(subject_id=\"alice\")                                    # full bi-temporal history\nmem.get_provenance(fact_ids[0])                                     # source episode + span\nmem.conflicts(subject_id=\"alice\")                                   # surfaced conflicts\n\n# --- govern ------------------------------------------------------------\nreport = mem.verify_audit()          # AuditReport — is the hash chain intact?\ncert   = mem.forget(\"alice\")         # DeletionCertificate — provable erasure\n```\n\n| Method | Returns | What it does |\n|---|---|---|\n| `add(text, *, subject_id, valid_from=…, source_ref=…)` | `list[str]` | Ingest a message; extract, dedup, and supersede facts. |\n| `search(query, *, subject_id, as_of=…, limit=5)` | `list[SearchResult]` | Hybrid retrieval with an optional time filter. |\n| `answer(query, **kwargs)` | `str \\| None` | The single top object for a query. |\n| `timeline(*, subject_id)` | `list[Edge]` | Every fact for a subject, live and superseded. |\n| `get_provenance(fact_id)` | `Provenance \\| None` | Trace a fact to its source episode + span. |\n| `conflicts(*, subject_id=None)` | `list[dict]` | Conflicts resolved by predicate cardinality. |\n| `resolve_entities(names=None, *, auto=True)` | `ResolutionResult` | Merge duplicate entities (reversible). |\n| `forget(subject_id)` | `DeletionCertificate` | Crypto-shred a subject; return proof. |\n| `verify_audit(deep=False)` | `AuditReport` | Verify the tamper-evident hash chain; `deep=True` also catches silent edits to event content. |\n\n## Run it for real\n\nThree storage tiers, one engine — every guarantee (audit chain, crypto-shred,\ndeep verification, time travel) holds on all three:\n\n| Tier | Storage | For | Setup |\n|---|---|---|---|\n| `Memory()` | in-memory | tests, demos, determinism | none |\n| `Memory.local()` | one SQLite file (`~/.attestari/attestari.db`) | a personal agent, MCP, prototypes — durable, single-process | none (stdlib) |\n| `Memory.postgres()` | Postgres + pgvector | production: concurrent access, indexed hybrid search | one container |\n\n**Durable with zero infrastructure** (survives restarts; nothing to install or run):\n\n```python\nfrom attestari import Memory\nmem = Memory.local()      # or Memory.local(\"path/to/agent.db\")\n```\n\n**Durable, on Postgres + pgvector** (one container, no graph DB):\n\n```bash\nATTESTARI_PG_PORT=5433 docker compose up -d       # applies the schema on first boot\npip install -e \".[postgres,embeddings]\"\nexport ATTESTARI_DATABASE_URL=postgresql://attestari:attestari@localhost:5433/attestari\n```\n\nAlready have a Postgres (managed or local)? The schema ships **inside the pip\npackage** — no clone needed:\n\n```bash\npython -m attestari.initdb postgresql://user:pass@host:5432/db   # idempotent\n```\n```python\nfrom attestari import Memory\nmem = Memory.postgres()   # durable; materialized projections + pgvector + full-text search\n```\n\n**As a REST API + visual console:**\n```bash\npip install -e \".[server]\"\nuvicorn attestari.server:app   # API at /v1/*, the memory-graph console at /\n```\n\n**As an MCP server** (any agent — Claude, frameworks — can use it) — exposes\n`add_memory / search_memory / get_provenance / forget_subject` over stdio.\nRegister it in your MCP client's config (e.g. Claude Desktop's\n`claude_desktop_config.json`); the client launches the process for you:\n\n```json\n{\n  \"mcpServers\": {\n    \"attestari\": {\n      \"command\": \"attestari-mcp\",\n      \"args\": [],\n      \"env\": {\n        \"ATTESTARI_SQLITE_PATH\": \"~/.attestari/attestari.db\",\n        \"ANTHROPIC_API_KEY\": \"sk-ant-...\",\n        \"ATTESTARI_KEK\": \"base64-kek-here\"\n      }\n    }\n  }\n}\n```\nOnly `command`/`args` are required. Durable by default (memories go to the local\nSQLite file, so they survive app restarts); the `env` block is where per-server\nconfig lives — add `ATTESTARI_DATABASE_URL` to use Postgres instead of SQLite,\n`ANTHROPIC_API_KEY` to upgrade extraction to Claude, `ATTESTARI_KEK` to enable\ncrypto-shred. To run it standalone (e.g. to debug): `attestari-mcp` (or\n`python -m attestari.mcp`). Without a local install, MCP clients can spawn it\nstraight from PyPI: `uvx --from \"attestari[server]\" attestari-mcp`.\n\n**From TypeScript** — the TS client talks to the REST API, so **start the server\nfirst** (see above; it defaults to `http://localhost:8000`). Then see\n[`clients/ts`](clients/ts) (`@attestari/client`), a thin typed client mirroring the\n`Memory` surface.\n\n**With LangChain:** see [`clients/langchain`](clients/langchain) (`attestari-langchain`)\n— a `AttestariRetriever` (recall facts with provenance) and `AttestariChatMessageHistory`\n(drop-in memory for `RunnableWithMessageHistory`) for any chain or agent.\n\n**With real Claude extraction** (instead of the zero-dep deterministic extractor):\n```bash\npip install -e \".[anthropic]\"\nexport ANTHROPIC_API_KEY=sk-ant-...\npython examples/spike.py --llm anthropic\n```\n\n**Enable crypto-shred deletion** (turn `forget()` from a logical delete into\ncryptographic erasure). Encryption is opt-in via a root key-encryption key\n(KEK); with none set, `forget()` still works but only drops the data from reads.\nMint a KEK once and set it in the environment:\n```bash\npip install -e \".[crypto]\"\nexport ATTESTARI_KEK=$(python -c \"from attestari.crypto import generate_kek; print(generate_kek())\")\n```\nNow each subject's PII is encrypted at rest under a per-subject key, and\n`forget()` destroys that key — the ciphertext is unrecoverable, while the audit\nproof survives. With the KEK set, the `DeletionCertificate` is also **signed**\n(HMAC-SHA256 under a KEK-derived key); anyone holding the KEK can verify it\noffline — `verify_certificate(cert, kek)` — and a certificate with any altered\nfield fails. Without a KEK, `forget()` is a logical delete and the certificate\nis issued unsigned. **Keep the KEK out of the database and its backups** (env\nvar or a KMS) — storing it next to the data defeats the shred. See the backup\nboundary in [docs/the-moat.md](docs/the-moat.md).\n\n**Deploying for real.** A production checklist:\n- **Storage:** use `Memory.postgres()` (concurrent access); apply the schema with\n  `python -m attestari.initdb \"$ATTESTARI_DATABASE_URL\"`. `Memory.local()` (SQLite) is\n  single-process — great for one agent or an MCP server, not a shared service.\n- **Server:** run under a process manager, e.g.\n  `uvicorn attestari.server:app --host 0.0.0.0 --port 8000 --workers 4` behind a\n  reverse proxy; put your own auth in front (the API ships without auth).\n- **Extraction & embeddings:** set `ANTHROPIC_API_KEY` (extraction auto-upgrades\n  to Claude) and install `[embeddings]` for real semantic vectors.\n- **Keys:** inject `ATTESTARI_KEK` from a KMS/secrets manager as an env var — never\n  bake it into an image or the DB. Back the `keyring` table up on a separate,\n  short-retention policy (or rotate the KEK) so a restored data backup can't\n  resurrect a shredded subject — see [docs/the-moat.md](docs/the-moat.md).\n- **Backups:** exclude the derived projection tables (`edge`, `entity`) as well —\n  they hold plaintext fact text for retrieval and are fully rebuildable from the\n  (ciphertext) event log, so backing them up only weakens the shred.\n- **Secrets:** nothing is read from a `.env` file automatically — export the vars\n  (or use your orchestrator's secret injection) before starting the process.\n\n## REST API\n\n`uvicorn attestari.server:app` serves:\n\n| Method & path | Purpose |\n|---|---|\n| `GET /healthz` | Liveness check. |\n| `POST /v1/add` | Ingest a message. |\n| `GET /v1/search` | Hybrid retrieval (`q`, `subject_id`, `as_of`, `limit`). |\n| `GET /v1/timeline` | Full bi-temporal history for a subject. |\n| `GET /v1/provenance/{fact_id}` | Trace a fact to its source. |\n| `POST /v1/forget/{subject_id}` | Provable deletion → certificate. |\n| `GET /v1/conflicts` | Surfaced conflicts. |\n| `GET /v1/audit/verify` | Verify the audit hash chain. |\n| `GET /v1/graph` | The memory graph (for the console). |\n| `GET /` | The visual graph console. |\n\n## Configuration\n\n**Environment variables** (all optional — the engine runs with none of them):\n\n| Variable | Effect |\n|---|---|\n| `ATTESTARI_DATABASE_URL` | Postgres DSN; the server/MCP use Postgres instead of local SQLite. |\n| `ATTESTARI_SQLITE_PATH` | Where `Memory.local()`-backed server/MCP keep the SQLite file (default `~/.attestari/attestari.db`). |\n| `ATTESTARI_KEK` | Root key-encryption key; turns on crypto-shred deletion. |\n| `ATTESTARI_PG_PORT` | Host port for the bundled `docker compose` Postgres (default 5432). |\n| `ATTESTARI_WRAP_UPSTREAM` | Base URL of a memory service to govern; mounts the `/v1/wrap/*` endpoints (unset = no wrap routes). |\n| `ATTESTARI_WRAP_UPSTREAM_TOKEN` | Sent to the upstream as `Authorization: Bearer …`. |\n| `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. |\n| `ANTHROPIC_API_KEY` | Enables Claude fact extraction — the server/MCP upgrade from the regex extractor automatically. |\n| `ATTESTARI_EXTRACTOR_MODEL` | Override the extraction model (default `claude-opus-4-8`). |\n\n**Install extras** (`pip install -e \".[extra]\"`):\n\n| Extra | Adds |\n|---|---|\n| `postgres` | `psycopg` + `pgvector` — the durable event store. |\n| `embeddings` | `sentence-transformers` — real semantic embeddings. |\n| `crypto` | `cryptography` — crypto-shred deletion. |\n| `server` | FastAPI + uvicorn + MCP — the REST server and MCP server. |\n| `anthropic` | The Anthropic SDK — Claude fact extraction. |\n| `dev` | pytest + ruff — tests and linting. |\n\n## Provable deletion + tamper-evident audit, in one demo\n\n![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)\n\nDon't trust the bullet points — **break the properties and watch them get caught.**\nThis runs with no database, no API key, no model download, and is self-verifying\n(every claim ends in an `assert`; it crashes if any property is false):\n\n```bash\npython examples/prove_the_moat.py   # tamper -> caught · crypto-shred -> unrecoverable · time-travel\n```\n\nIt adversarially proves all three differentiators: a silently rewritten fact is\n**caught at the exact seq** by `verify_audit(deep=True)`; a crypto-shredded\nsubject's ciphertext is **provably unrecoverable** while the audit proof survives;\nand a corrected fact is queryable in the past without erasing history. (Runs on a\nbare clone; `pip install \"attestari[crypto]\"` upgrades claim 2 from logical erasure to\ncryptographic crypto-shred.) See\n[docs/the-moat.md](docs/the-moat.md) for the threat model and honest boundaries.\n\nFor the full crypto-shred against a real encrypted Postgres row:\n\n```bash\nATTESTARI_DATABASE_URL=postgresql://attestari:attestari@localhost:5433/attestari \\\n    python examples/audit_and_forget_demo.py   # audit -> trace -> forget -> PROVE\n```\nIt shows: a fact traced to its source, a subject forgotten, the raw row confirmed\nto be unreadable ciphertext, recall returning nothing — and the **audit chain\nstill valid** after the erasure.\n\n## Already using Mem0 or Zep? Wrap it\n\nYou don't have to replace your memory layer to get an audit trail. `wrap()` puts\nAttestari in front of the client you already use:\n\n```python\nfrom attestari.wrap import wrap\n\ngoverned = wrap(mem0_client)                              # or zep, or your own\n\ngoverned.add(\"I live in Berlin.\", subject_id=\"u1\")        # recorded, then stored\ngoverned.search(\"where do I live\", subject_id=\"u1\")       # straight through\nreceipt = governed.forget(\"u1\")                           # both stores + proof\n\nreceipt.complete             # True only if BOTH halves succeeded\nreceipt.certificate          # Attestari's signed deletion certificate\nreceipt.downstream_verified  # True = we read the store back and it was empty\nreceipt.downstream_error     # what the wrapped store said, if it refused\ngoverned.verify_audit(deep=True)                          # the chain still holds\n```\n\n**Not on Python?** Point your app at the Attestari server instead of at your\nmemory service and get the same guarantees over REST:\n\n```bash\nATTESTARI_WRAP_UPSTREAM=https://memory.internal uvicorn attestari.server:app\n```\n\n```\nPOST /v1/wrap/add          {\"text\": …, \"subject_id\": …}   # recorded, then forwarded\nPOST /v1/wrap/search       {\"query\": …, \"subject_id\": …}  # passthrough\nPOST /v1/wrap/forget/{id}                                 # both stores + evidence\n```\n\nA partial erasure returns **409**, not 200 — a caller checking only the status\ncode must never read a half-completed deletion as success. The routes appear\nonly when an upstream is configured. See `attestari.wrap_http` for the small\nJSON contract the upstream is expected to speak (stdlib-only, no new deps).\n\n**It doesn't take the delete call's word for it.** After deleting, `forget()`\nreads the subject back out of the wrapped store. A store that returns\n`{\"deleted\": true}` and keeps the rows is caught right there —\n`downstream_verified` is `False`, `complete` is `False`, and the discrepancy\ngoes into the audit chain. If the adapter has no read-back operation,\n`downstream_verified` is `None` rather than `True`: \"nobody checked\" and\n\"checked and clean\" are different claims, and only one of them is evidence.\n\nWrites are recorded in the tamper-evident chain before being passed downstream;\nreads pass through untouched (retrieval is why you kept your store); `forget()`\ndeletes downstream, crypto-shreds Attestari's copy, and records what the\ndownstream store actually did — including failure. Method names and the subject\nkeyword are configurable via `Adapter`, so this works against a bare vector\nstore too.\n\n**What wrapping does and doesn't prove.** Attestari can't cryptographically\nshred data inside someone else's service — it doesn't hold their keys. A wrapped\ndeployment proves the deletion was requested, that the downstream delete was\ncalled and what it returned, that Attestari's own copy is unrecoverable, and\nthat none of that was altered afterwards. That's an *auditable deletion record\nacross both systems*, not crypto-shred everywhere: a wrapped store is only as\nerasable as its own delete endpoint is honest, and wrapping turns that\nendpoint's behaviour into evidence instead of a promise. For full cryptographic\nerasure, the data has to live in Attestari itself.\n\n## For auditors and DPOs\n\nThe people who have to *answer* for an AI system's memory get their own docs in\n**[auditor/](auditor/)** — a plain-language [one-pager](auditor/dpo-one-pager.md)\n(what's guaranteed, what isn't, how to check it yourself), an [EU AI Act Art. 12\nmapping](auditor/eu-ai-act-article-12.md), and a [GDPR Art. 17\nnote](auditor/gdpr-article-17.md) on cryptographic erasure and the deployment\npolicies it depends on.\n\nAny deployment can produce a dated snapshot of its own verifiable state:\n\n```bash\nattestari evidence --deep --out ./evidence   # EVIDENCE.md + evidence.json\n```\n\nThe bundle carries the audit-chain result and head hash, an erasure register\nwith every request re-checked against the current ledger, and the retained\ndeletion certificates. Every claim is **re-derived from the live ledger** when\nthe bundle is generated — it's evidence because you can regenerate it, with read\naccess and no cooperation from whoever runs the system.\n\n```bash\nattestari verify --deep          # re-check the chain, re-hashing content\nattestari verify --user u_123    # confirm one subject's erasure; non-zero exit if not\n```\n\n## How it works\n\nThe source of truth is an **append-only event log**; everything you query (the\nknowledge graph, the vector index, the keyword index) is a **projection** you can\nrebuild from it. That's why audit, time-travel, provenance, and provable deletion\nfall out of the design instead of being bolted on.\n\n- [ARCHITECTURE.md](ARCHITECTURE.md) — the design, end to end.\n- [LEARN.md](LEARN.md) — the same ideas explained from scratch, for newcomers.\n\n## Project layout\n\n```\nsrc/attestari/         the engine — events, store, projection, retrieve, memory,\n                    crypto (shred), audit (hash chain), predicates, resolver\nsrc/attestari/server.py, console.py   FastAPI REST API + the graph console\nsrc/attestari/mcp.py   the MCP server\nsrc/attestari/cli.py, evidence.py     `attestari verify` + the evidence bundle\nsrc/attestari/wrap.py, wrap_http.py   govern an existing memory layer (Mem0,\n                    Zep, or any HTTP service) instead of replacing it\nauditor/            the auditor pack — DPO one-pager, AI Act + GDPR mappings\nexamples/           runnable demos — start with spike.py\neval/               quality + retrieval-latency harness\nclients/ts/         the TypeScript SDK (@attestari/client)\nclients/langchain/  the LangChain integration (attestari-langchain)\nsrc/attestari/db/schema.sql       the Postgres bi-temporal schema\ndocker-compose.yml  Postgres + pgvector\n```\n\n## Docs & contributing\n\n- [ARCHITECTURE.md](ARCHITECTURE.md) — the design\n- [auditor/](auditor/) — for DPOs, compliance, and internal audit\n- [LEARN.md](LEARN.md) — how Attestari works, from scratch\n- [CONTRIBUTING.md](CONTRIBUTING.md) — dev setup, good first issues\n- [SECURITY.md](SECURITY.md) — reporting vulnerabilities\n\n## Status\n\nThe engine and its differentiators — verifiable deletion, tamper-evident audit,\nbi-temporal provenance, Postgres-native retrieval — are **built and tested** (122\ntests; Postgres p95 ≈ 1 ms).\n\n## License\n\nApache-2.0. The core stays permissively licensed; the hosted cloud and\nenterprise/governance features are the commercial layer.\n",
  "bytes": 23656,
  "sha": "572cf23aec579f13b388cb3b107031f4fd33be8f7c9def822d8fb7a673bd7e7b",
  "repo_slug": "attestari/attestari",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_attestari_attestari_0275e91d/readme"
}