Vayl

Memory that takes a fact back.

Vayl is a self-hosted memory layer for AI agents, over MCP. It reconciles instead of accumulates: a new value supersedes the old, a removal actually retracts, ambiguity is flagged rather than guessed — and every control on the trust layer is verifiable by command.

vayl.db — statement ledger
09:14 remember “We use Redux for state.” state = Redux ACTIVE
11:47 remember “Actually, we moved to Zustand.” state = Zustand SUPERSEDE
14:02 recall “What do we use for state?” → “Zustand” not “Redux, Zustand” history intact — “what did we use first?” still answers “Redux”

Append-only memory serves stale answers.

Two failure modes, shown as output rather than argued in prose. Both stores did their job as designed — the design is the problem.

additive store
> add("We use Zustand for state.")
> add("We switched to Redux. Zustand is out.")
> search("what do we use for state?")

  1. "We use Zustand for state"     0.91  ← stale, ranked first
  2. "We switched to Redux …"       0.86

Every fact is retained; similarity ranking decides the truth. The value you explicitly moved away from comes back first.

graph store
> add("We use Sentry for error monitoring.")
> add("We dropped Sentry last month.")
> search("what do we use for monitoring?")

  USES_MONITORING(org → Sentry)   valid: true  ← never invalidated

Graphs reconcile contradictions well — a new edge invalidates the old one. But a removal creates no new edge, so there is nothing to invalidate against. “We dropped X” leaves X standing.

SUPERSEDE

A real change retires the old value into history and makes the new one current.

RETRACT

A removal with no replacement empties the slot. A tombstone keeps the provenance; recall never returns the retired value.

FLAG

Same slot, same scope, no change signal — genuinely ambiguous. Surfaced for a human, never guessed.

SKIP

Questions, hypotheticals, sarcasm. Not durable facts, never stored as truth.

Mechanism: one LLM call extracts and classifies, the engine applies the outcome. No graph database, no second system of record.

Numbers, with the counter-entries.

Each figure carries the command that reproduces it. The counter-entries are part of the statement, not a footnote.

Silently-wrong at scale

800 fact-updates across 50 users, same model, same synthesizer. Additive memory keeps every version and returns a stale value one lookup in three (32.5%); Vayl retires superseded facts on write, so its active set stays true — and stores 199 rows where an additive store holds 800. See the full benchmark →

python benchmarks/evaluations/scale_bench.py
0%vs 32.5% — additive store, 800 updates
Multi-hop relational — a graph's home turf

Eleven relational queries — ownership chains, transitive dependencies, 3-hop supply — the axis graphs are built for. Vayl's optional graph holds 0% silently-wrong and 8/11 correct (both 3-hop chains), delivered as an SQLite projection, not a required Neo4j deployment. See the full benchmark →

python benchmarks/evaluations/graph_headtohead.py
0%silently-wrong · relational
Retraction — removal without replacement

“We dropped Sentry” must stop being returned. Vayl retires the fact on write — kept in history for audit, gone from the current set. An additive store keeps it searchable, so a stale value can resurface. Measured in the cross-system retract scenarios.

python benchmarks/evaluations/compare_systems.py
retiredvs retained — additive stores
Silently wrong — confident and false

The one number that kills a memory product: returning a stale or wrong value as if it were current. 255 adversarial trials on Claude Haiku. Ambiguity resolves to FLAG, not to a guess.

python benchmarks/evaluations/eval_adversarial.py
0.4%255 adversarial trials
Cost per write

Extract and reconcile against a single SQLite file — no graph pipeline, no Neo4j to operate. Vayl’s writes run ~2.8 s, versus the ~6–9 s typical of a graph pipeline (a graph server plus multi-step edge extraction).

python benchmarks/evaluations/compare_systems.py
~2.8swrite · vs ~6–9s graph pipeline
Counter-entries
Plain contradiction: parity, not a win

On ordinary “X changed to Y” supersession with a capable model, an additive store plus a good reader keeps up. Vayl’s measured edge is removal, honesty under ambiguity, cost, and holding up under sustained churn — not a single clean swap.

python benchmarks/evaluations/compare_systems.py
parityclean supersession, gpt-4o-mini
Reconciliation quality tracks the model

The engine is only as honest as the model reading the input. On the default gpt-5-mini, the messy real-world suite — typos, slang, self-corrections, multi-fact messages across a dozen domains — returns 0% silently-wrong. A weaker model degrades; measure yours before you trust it.

OPENAI_MODEL=gpt-5-mini python benchmarks/evaluations/messy_eval.py
0%messy real-world, gpt-5-mini
Provenance of these numbers

Single-run, author-written suites — not third-party audits. That is exactly why every row above ships with its command: run them before you rely on them.

ls benchmarks/
1 runauthor-written suites

Twenty-nine tools, five families.

The full MCP surface. Every mutation keeps history; every destructive tool is annotated so clients confirm before running it.

Core memory 8 tools Write, reconcile, and read the current truth — plus the history additive stores can’t give you.
rememberstore fact(s); supersedes, retracts, or splits multiple facts per message; carries a source
recallanswer from current memory; says “I don’t know” rather than guessing
recall_relatedmulti-hop questions via the optional entity graph
historyevery value a subject has held, oldest to newest, with status
get_memoryone fact’s structured detail by id
update_memoryaudit-preserving correction — the old value retires to history
forgetguaranteed retraction, retained in history for audit
list_memoriesactive facts plus what was superseded and retracted
Provenance & decisions 3 tools Why does the agent believe this — and why did it act? Answerable even after the facts change.
recall(explain)the exact facts behind an answer: value, source, confidence, what they superseded
record_decisionan immutable, signed snapshot of the beliefs behind an action
explain_decisionreconstruct what was believed at decision time, receipt verified
Safety gates 2 tools Refuse to act on disputed, stale, or low-confidence memory. Reads stay open; actions get gated.
check_before_actSAFE, or BLOCKED with explicit reasons, before an irreversible action
safe_recallanswers only if every supporting fact passes the policy; otherwise withholds and says why
Compliance & proof 9 tools Hard erasure with signed receipts, attestations, and a tamper-evident audit chain — all third-party verifiable.
delete / delete_allhard erasure including history and graph; issues a signed erasure receipt
purge_expiredretention by age — storage-limitation enforcement
export_memorymachine-readable dump for access and portability requests
attestsigned proof of the current value, anchored to the audit head
verify_receiptVALID, or INVALID if any field was edited
verify_auditINTACT, or the exact row where the chain breaks
export_public_keyverify receipts and the chain without the database or the secret
audit_logwho did what, when — encrypted at rest, never wiped by erasure
Admin 8 tools Principals, roles, shared-space policy, edition, and health — the team-deployment surface.
create_principalissue an API key with a role: admin, member, agent, viewer, auditor
list_principals / revoke_principalsee and disable access; revocation is immediate
set_reconcile_policy / get_reconcile_policyshared spaces: RECENCY, AUTHORITY, or REVIEW — a lower-ranked source can’t silently overwrite a higher one
license_statusedition, seats used vs allowed, expiry
stats / healthon-device metrics and a setup diagnostic; nothing leaves the machine
Quickstart

Two minutes to a running memory. Point it at your model provider — your key, your data-residency posture; the database on disk stays encrypted either way.

# install
git clone https://github.com/vayl-dev/vayl && cd vayl
pip install .
// Claude Desktop / Cursor → mcpServers
"vayl": {
  "command": "vayl-mcp",
  "env": {
    "LLM_PROVIDER": "openai",
    "OPENAI_API_KEY": "sk-…",
    "OPENAI_MODEL": "gpt-5-mini",
    "EMBED_BASE_URL": "https://api.openai.com/v1",
    "EMBED_MODEL": "text-embedding-3-small",
    "VAYL_DB": "/absolute/path/vayl.db"
  }
}

The default, and the one the numbers above were measured on: 0% silently-wrong on the messy real-world suite. Encryption at rest and the signed audit chain are on by default.

Verify it. Don’t take the page’s word.

Every claim below is a command and its output, paired with the test that pins the behavior in CI. Adjectives don’t survive an audit; these do.

The audit log is tamper-evident.

Each entry hashes its predecessor and is Ed25519-signed. Edit, delete, or reorder any row and verification names the exact break.

tests/test_audit.py::test_tampering_a_detail_breaks_the_chain
verify_audit
> verify_audit
Audit chain INTACT — 218 entries verified.

# an attacker edits row 2 in the database, then:
> verify_audit
Audit chain BROKEN at seq 2: content tampered
(a row’s stored bytes were altered)

Erasure produces a receipt anyone can check.

Deleting a subject issues a signed receipt — scope, subject, row count, chain anchor. Change any field and the signature fails.

tests/test_receipts.py::test_editing_any_payload_field_invalidates_the_receipt
verify_receipt
> delete subject=alice_salary
Erased 3 record(s). Signed erasure receipt #14 issued.

> verify_receipt 14
VALID — signature verified, payload intact

# edit the receipt’s count from 3 to 0, then:
> verify_receipt 14
INVALID — signature does not match

The data on disk is ciphertext.

Content columns are Fernet-encrypted at rest, on by default. Search the raw database file for a stored value and find nothing.

tests/test_crypto.py::test_data_is_ciphertext_at_rest_but_plaintext_on_read
at rest
$ strings vayl.db | grep 120000
(no matches)

$ sqlite3 vayl.db "SELECT value FROM statements LIMIT 1"
gAAAAABqXGc8t4v…   # ciphertext, not the salary

Third parties verify without the secret.

Receipts, attestations, and the audit chain check out against the public key alone — no database access, no key material, no trust in the operator.

tests/test_receipts.py::test_erasure_receipt_verifies_with_public_key_only
export_public_key
> export_public_key
ed25519: 727b94c14f7abecd188a99a74b5b7ec9…

# on any other machine, with only the key and the receipt:
> receipts.verify(receipt, public_key)
True
self-hostedyour infrastructure, your keys; the vendor never sees data
provider-agnosticany OpenAI-compatible or Anthropic model — your key sets data residency
hard erasuredelete removes history and graph edges, not just the active row
five-role RBACadmin, member, agent, viewer, auditor — fail-closed on every tool
Vault key custodyTransit envelope encryption; unreachable Vault means refuse to start, never run unencrypted
no telemetrythe only outbound call is the LLM endpoint you chose
Full posture and the GDPR / EU AI Act mapping: SECURITY.md · COMPLIANCE.md

Editions.

Community

Apache-2.0 · free · self-hosted
  • reconciling engine — supersede / retract / flag / skip
  • MCP over stdio + HTTP — Claude Desktop, Cursor, Claude Code
  • encryption at rest — on by default
  • signed audit chain, receipts, attestations
  • safety gates, hard erasure, export
  • SQLite backend — single deployment
  • capped seats — 3 principals
Start from the repository

Enterprise

self-hosted license · signed offline, works air-gapped
  • everything in Community, seats uncapped
  • SSO / OIDC — your IdP’s tokens, roles mapped from claims
  • Postgres backend — multi-process, advisory-locked writes
  • HashiCorp Vault key custody — fail-closed
  • multi-tenant isolation seam
  • source-aware shared memory — RECENCY / AUTHORITY / REVIEW
  • support with SLA, compliance pack
Contact for a pilot