Form a belief.
The agent forms a claim; the engine stamps confidence and evidence pointers at record time and returns a belief id.
See the CALL →Your agent recalls a customer risk score from Tuesday. On Wednesday the underlying evidence is retracted. On Thursday an auditor asks: what did the agent actually see when it approved the loan? InvariantDB answers with one Cypher line — because valid time and recorded time are first-class query verbs, and every belief carries lineage.
Not a log line to grep, not a snapshot to rehydrate, not an event bus to replay. A belief is an addressable node with a claim, a confidence, and its evidence. Time is a query verb. Revisions are edges, not overwrites.
AT VALID for the world; AT RECORDED for the engine's knowledge.Every graph exposes a JSON-RPC endpoint. Any Model Context Protocol client — Claude Desktop, an SDK, your own agent — can call it. The engine advertises fourteen tools on tools/list. The workhorse is cypher_query; every other tool is a shortcut around the equivalent CALL db.* procedure.
# One URL per graph. JSON-RPC 2.0 over HTTPS. POST https://your-tenant.invariantdb.com/graphs/{name}/mcp Authorization: Bearer $INVARIANTDB_TOKEN Content-Type: application/json # The transport announces itself as # "invariantdb-mcp" on the initialize call.
{
"mcpServers": {
"invariantdb": {
"command": "npx",
"args": ["invariantdb-mcp"],
"env": {
"INVARIANTDB_ENDPOINT":
"https://your-tenant.invariantdb.com/graphs/agent/mcp",
"INVARIANTDB_TOKEN": "..."
}
}
}
}
cypher_query rejects a top-level atTime argument on purpose. Time travel belongs inside the Cypher body — put AT VALID or AT RECORDED on the MATCH pattern.
Every proc-wrapper tool dispatches to the equivalent CALL db.* form, so callers can drop to raw Cypher at any point without giving up the MCP contract.
cypher_queryRun any Cypher against the bound graph. Returns columns + rows.search_fulltextBM25 search over a (label, property). Top-k hits with score.search_vectorVector search over a (label, property). Top-k hits with score.get_node_by_strFetch one node by its string id.get_edge_by_strFetch one edge by its string id.engine_infoMetadata for the bound graph: current version + auth summary.record_episodeAppend one event to an agent's session (episodic memory).replay_episodesReplay a session in time order — last N events.belief_lineageWalk every version of a belief back through its evidence.property_provenanceFull history + provenance for one node property.derived_fromFollow derivation edges to trace a value to its sources.acl_eventsReplay ACL audit events — who tried, what, when, denied?subject_exportGDPR Art. 20 per-subject export — every node/edge/version.destroy_subject_keyGDPR Art. 17 crypto-shred with a signed audit-chain receipt.Belief revision in four Cypher lines. Yield columns match the runtime procedure catalog exactly — every snippet is copy-runnable against a fresh graph.
The agent forms a claim; the engine stamps confidence and evidence pointers at record time and returns a belief id.
See the CALL →Contradicting evidence arrives. The old belief is superseded, not overwritten. The successor gets its own id — the chain stays intact.
See the CALL →Walk the lineage backward, filter by createdAt, and you have proof of what the agent could have known on any given day.
See the CALL →CALL db.recordBelief( "belief-99", "customer:c-77 risk = LOW", 0.82, ["episode-482", "episode-491"] ) YIELD beliefId RETURN beliefId;
CALL db.reviseBelief( "belief-99", "customer:c-77 risk = HIGH", 0.91, "address-mismatch document arrived 2026-03-04" ) YIELD oldBeliefId, newBeliefId, revisedAt RETURN oldBeliefId, newBeliefId, revisedAt;
CALL db.beliefLineage("belief-99", 3) YIELD beliefId, claim, confidence, createdAt, supersededAt, depth, evidence RETURN beliefId, claim, confidence, depth ORDER BY depth DESC;
// What did the agent know about c-77 on March 5? CALL db.beliefLineage("belief-99", 3) YIELD beliefId, claim, createdAt WHERE createdAt <= datetime("2026-03-05T00:00:00Z") RETURN beliefId, claim, createdAt ORDER BY createdAt DESC;
Agents fetch context by keyword and by embedding — then follow edges from whatever they found. BM25 for lexical hits, vector search for semantic proximity, Cypher expansion for the neighbourhood around each hit.
Point gds.fulltextSearch at a (label, property) pair and a query string. Yield gives node id, score, the full node record, and the human-facing string id.
CALL gds.fulltextSearch("Note", "body", "address mismatch", 20) YIELD nodeId, score, node, strId RETURN strId, score, node.body ORDER BY score DESC;
Pass the embedded query vector to gds.vectorSearch. Yield is intentionally narrow — just node id and score — so you can pipe straight into a MATCH to enrich or expand.
CALL gds.vectorSearch("Doc", "embedding", $queryVector, 10) YIELD nodeId, score MATCH (d:Doc) WHERE id(d) = nodeId RETURN d.title, score ORDER BY score DESC;
Every agent has a session log. db.recordEpisode appends one event; db.replayEpisodes hands the last N back in time order — kind, timestamp, text, and any payload the agent attached.
CALL db.recordEpisode("session-1", "observation", "customer address does not match KYC document") YIELD eventId, ts RETURN eventId, ts; CALL db.replayEpisodes("session-1", 50) YIELD eventId, kind, ts, text, payload RETURN kind, ts, text ORDER BY ts;
Every write is stamped with the actor, source, reason, request id, and confidence. Ask any property for its story — you get the full version history, provenance attached.
MATCH (c:Customer {strId: "c-77"}) CALL db.propertyProvenance(id(c), "risk") YIELD value, validFrom, validTo, version, txnAt, actor, source, reason, requestId, confidence RETURN version, value, actor, reason, txnAt ORDER BY version;
Every fact carries two intervals. Attach a clock verb to the pattern and the whole query travels there — edges included, indexes included, deletes reversed if the delete came later.
When was the fact true out in reality? Reprice a portfolio at last quarter's prices. Reread a customer's permissions at consent grant.
AT VALID '2026-03-05T00:00:00Z'When did the engine learn the fact? Reconstruct what the agent could have known before a bug fix. Prove there was no future-info leakage into training.
AT RECORDED '2026-03-04T09:00:00Z'// What did the agent's memory look like at record-time // March 4, as of what the world knew at valid-time March 5? MATCH (c:Customer {strId: "c-77"})-[:HAS_BELIEF]->(b:Belief) AT VALID "2026-03-05T00:00:00Z" AT RECORDED "2026-03-04T09:00:00Z" RETURN b.claim, b.confidence, b.createdAt; // Every mutation in a bitemporal window — // useful for driving downstream projections or // verifying that an agent's writes stayed inside // a policy budget. CALL db.changes($fromNanos, $toNanos) YIELD kind, op, entity_id, label, txn_at, applied_seq RETURN kind, op, label, txn_at ORDER BY applied_seq;
GDPR Article 17 and Article 20 are query verbs, not tickets in a queue. Erase a subject in place and prove it happened; export everything a subject touched; verify the ACL audit chain hasn't been tampered with.
The subject's encryption key is destroyed. Every ciphertext that mentioned them becomes unreadable in place. History structure survives.
CALL db.destroySubjectKey("customer:c-77") YIELD subjectId, destroyedAtNanos RETURN subjectId, destroyedAtNanos;
A signed receipt with anchor hashes that lets an auditor confirm the destroy happened — without ever seeing what was destroyed.
CALL db.proveErasure("customer:c-77") YIELD receiptId, destroyedAt, anchorHistorySeq, anchorHistoryHash, ownChainHash, chainStatus RETURN receiptId, chainStatus;
Every node, edge, and version history the subject appears in — typed and shaped for direct download or handoff to the requester.
CALL db.subjectExport("customer:c-77") YIELD type, id, kind, payload RETURN type, id, kind, payload;
Tamper-evidence check on the ACL audit log. If any entry has been rewritten between event id A and event id B, the call flags it — and tells you where.
CALL db.verifyAclChain() YIELD status, entries, brokenAtEventId, reason RETURN status, entries, brokenAtEventId, reason;
Who tried to read what, when, and whether the engine allowed it. Filter by principal, since-id, or limit.
CALL db.aclEvents(0, "agent-42", 100) YIELD eventId, ts, principal, label, property, action, denied, reason RETURN ts, principal, action, denied ORDER BY eventId DESC;
Walk a value backwards through the DERIVED_FROM edges. RAG citation chains, inference trees, document lineage — all in one traversal.
CALL db.derivedFrom("belief-99", 5) YIELD strId, depth, kind RETURN strId, depth, kind ORDER BY depth;
InvariantDB serves an llms.txt index and a full plain-text corpus. Both are stable, versioned, and safe to include in a training or retrieval pipeline — they contain only the capability-level narrative and public API surface. /llms.txt is the H1 + bulleted section index; /llms-full.txt is the concatenated corpus of every page linked from it. Both are declared in <link rel="alternate" type="text/plain"> so crawlers can discover them from any page on the site.
Free tier includes an MCP endpoint, a graph, and enough audit chain to run the demos on this page against your own data.