RelataDB SDK Cookbook

One page. Every method. One story — Operation Shadow Ledger, a $2.8 M embezzlement investigation — runs through the whole thing as the connective tissue, but each numbered section is self-contained and every code block is copy-pasteable.

Every captured output on this page is real — taken from a live relata serve process. The scenario is fictional; the data shapes, query patterns, and governance are production-grade.

What you'll build

4 suspects · 4 wire transfers ($3.77 M) · 3 phone records · 3 case documents
                                  ↓
            ┌──────────────────────────────────────────────────┐
            │             RelataDB (one binary)                  │
            │   ingest → index → query → search → recall         │
            │      → resolve → trace → justify → audit           │
            └──────────────────────────────────────────────────┘
                                  ↓
   RelataClient · IngestClient · Memory · McpClient · AuditClient · Namespace

In a DIY stack this would be Postgres + Elasticsearch + Pinecone + Neo4j + mem0 + Splunk + an identity-resolution ETL + Kafka glue — 8+ services. Here it's one binary.


Setup

pip install relata-sdk httpx
relata serve  # starts on localhost:9090

Imports every snippet below assumes:

from relata import (
    RelataClient, IngestClient, Memory, McpClient,
    AuditClient, Namespace, QueryResult,
)

A single client carries auth, tenant, and the default purpose so you don't repeat yourself on every call:

client = RelataClient(
    "http://localhost:9090",
    bearer_token="perftoken",
    purpose="analytics",
)

PURPOSE is optional but never invisible. Omit it and the server records purpose: null in the audit log; set it once on the client and every query, ingest, and MCP call is tagged. Governance travels in the substrate.


1. Connect & Verify

Before anything else, confirm the server is up, what profile it's running, and what build you're talking to.

client.health()
# HealthResponse(status="ok", profile="free", node_id="node-7f3a...")
 
client.status()
# StatusResponse(profile="free", role="coordinator",
#                query_quota=QueryQuota(cost_remaining=1000000, ...))
 
client.ready()
# ReadyReport(is_ready=True, checks={
#   "storage": True, "wal": True, "ingest_queue": True, ...})
 
client.version()
# VersionInfo(version="2.0.0", commit="a1b2c3d", build_profile="release")
 
client.stats()
# Stats(records=11, states=11, snapshot_rows=11, log_leaves=4, tokens=0)
 
client.list_modules()
# {"modules": ["aml", "maritime", "sanctions", "sigma"]}
MethodWhat it returnsUse it for
health()liveness + profile + node idload-balancer probe
status()profile, role, query quotapre-flight a batch job
ready()9-condition readiness reportKubernetes readinessProbe
version()build-infomigration / capability checks
stats()engine-wide countshealth dashboards
list_modules()installed extension packsfeature negotiation

Async mirrors exist for every method in this page (ahealth(), astatus(), aready(), aversion(), astats()). The a prefix is the only difference.


2. Define Your Schema

RelataDB is ontology-driven: types are data, not DDL. Register them at runtime, evolve them online, branch them.

client.register_type(
    "Person",
    description="A natural person under investigation",
    owner="fraud-team",
    properties={
        "id":      {"type": "text"},
        "name":    {"type": "text"},
        "email":   {"type": "text"},
        "phone":   {"type": "text"},
        "role":    {"type": "text"},
        "company": {"type": "text"},
        "risk":    {"type": "text"},
    },
)
# {"created": True, "name": "Person"}

List and inspect what you registered:

client.list_types()
# {"types": [{"name": "Person", "rows": 0}, {"name": "Transaction", "rows": 0}, ...]}
 
client.type_detail("Person")
# {"name": "Person", "owner": "fraud-team", "rows": 4,
#  "properties": {"id": {...}, "name": {...}, ...}}

Evolve the schema without downtime — add / drop / rename / retype:

# Add a `sanctions_status` column to every existing Person row.
client.schema_alter("Person", "add", "sanctions_status", col_type="text")
 
# Rename `risk` → `risk_band`.
client.schema_alter("Person", "rename", "risk", new_column="risk_band")
 
# Retype a column.
client.schema_alter("Person", "retype", "phone", col_type="text")

Register a typed edge for graph traversal (ADR-007):

client.register_edge_type("Person", "Transaction", "AUTHORIZED")
# {"from_type": "Person", "to_type": "Transaction", "label": "AUTHORIZED"}
 
client.list_edge_types()
# {"edges": [{"from_type": "Person", "to_type": "Transaction",
#             "label": "AUTHORIZED"}, ...]}

Remove a type when it's no longer needed (admin token required):

client.deregister_type("ExperimentalType")
# {"deleted": True, "name": "ExperimentalType"}
Migrate a whole ontology in one governed call

ontology_migrate registers type specs, link types, and property constraints together so a SHACL-consistent ontology lands atomically:

client.ontology_migrate({
    "types": [
        {"name": "Person",       "properties": {"name": {"type": "text"}}},
        {"name": "Transaction",  "properties": {"amount": {"type": "float"}}},
    ],
    "links": [
        {"from": "Person", "to": "Transaction", "label": "AUTHORIZED"},
    ],
})

And register SmartIngest enrichment rules so custom identifiers get auto-detected alongside the 76 built-in canonical types:

client.enrichment_rules({
    "rules": [
        {"name": "internal_acct", "pattern": r"ACME-\d{6}",
         "canonical_kind": "account_number"},
    ],
})

3. Ingest Data

Four shapes: NDJSON bulk, JSON upsert/skip, CSV, and document. All route through IngestClient and all trigger SmartIngest (76 canonical identifier types auto-detected on the way in).

ingest = IngestClient.from_client(client)

NDJSON bulk — the fast path

ingest.bulk("Person", [
    {"id": "alice", "name": "Alice Chen",   "email": "alice.chen@acmecorp.com",
     "phone": "+14155550100", "role": "CFO",      "company": "Acme Corp",   "risk": "HIGH"},
    {"id": "bob",   "name": "Bob Smith",    "email": "bob@shellco.io",
     "phone": "+14155550101", "role": "Director",  "company": "ShellCo Ltd", "risk": "HIGH"},
    {"id": "carla", "name": "Carla Nunez",  "email": "carla.nunez@acmecorp.com",
     "phone": "+34666123456", "role": "Accountant","company": "Acme Corp",   "risk": "LOW"},
    {"id": "david", "name": "David Kim",    "email": "d.kim@offshore.bn",
     "phone": "+822012345678","role": "Nominee",   "company": "Pacific Trust","risk": "MEDIUM"},
])
{"rows_queued": 4, "rows_rejected": 0, "task_id": "itsk_019fe254-3647-...", "connector": "direct", "errors": []}

Under the hood: SmartIngest scanned every field and auto-detected 4 phone numbers (E.164) and 4 emails (RFC 5322). They're now in the IdentityIndex — linkable across sources with no detection code on your side.

The money trail

ingest.bulk("Transaction", [
    {"id": "tx1", "from_account": "Acme Corp",         "to_account": "Pacific Trust 7742",
     "amount": 2300000, "currency": "USD", "date": "2026-01-15", "authorized_by": "Alice Chen"},
    {"id": "tx2", "from_account": "Pacific Trust 7742","to_account": "ShellCo Ltd",
     "amount": 850000,  "currency": "USD", "date": "2026-01-22", "authorized_by": "David Kim"},
    {"id": "tx3", "from_account": "ShellCo Ltd",       "to_account": "CASH",
     "amount": 120000,  "currency": "USD", "date": "2026-02-01", "authorized_by": "Bob Smith"},
    {"id": "tx4", "from_account": "Acme Corp",         "to_account": "Pacific Trust 7742",
     "amount": 500000,  "currency": "USD", "date": "2026-02-10", "authorized_by": "Alice Chen"},
])

Case documents (rich text for BM25)

ingest.bulk("CaseDoc", [
    {"id": "whistleblower", "title": "Whistleblower Complaint by Carla Nunez",
     "body": "I am writing to report suspected embezzlement by CFO Alice Chen. Over three "
             "months, Alice authorized four wire transfers totaling $2.8 million from Acme "
             "Corp to an offshore account at Pacific Trust Bank held by David Kim. The money "
             "was then moved to ShellCo Ltd, directed by Bob Smith."},
    {"id": "sar", "title": "Suspicious Activity Report - Pacific Trust Bank",
     "body": "Account 7742 held by David Kim received $2.8 million from Acme Corp. Funds "
             "were rapidly moved to ShellCo Ltd and partially withdrawn as cash. Pattern "
             "consistent with money laundering layering."},
    {"id": "news", "title": "Acme Corp CFO Under Scrutiny",
     "body": "Federal investigators examine whether CFO Alice Chen orchestrated a $2.8 "
             "million embezzlement through offshore accounts. A whistleblower complaint "
             "triggered the probe. Bob Smith of ShellCo denied involvement."},
])

JSON upsert / skip — conflict resolution

# Re-ingest with on_conflict='upsert' → update existing rows by id.
ingest.bulk("Person", [
    {"id": "alice", "risk": "CRITICAL", "sanctions_status": "under_review"},
], on_conflict="upsert")
 
# 'skip' keeps the existing row untouched if the id already exists.
ingest.bulk("Person", [
    {"id": "alice", "risk": "LOW"},
], on_conflict="skip")  # alice's risk stays CRITICAL

CSV ingest — bulk from a file

csv_text = """id,from_account,to_account,amount,currency,date
tx5,ShellCo Ltd,CASH,80000,USD,2026-02-05
tx6,Acme Corp,Pacific Trust 7742,300000,USD,2026-02-08
"""
ingest.bulk_csv("Transaction", csv_text)
# {"rows_queued": 2, "rows_rejected": 0, ...}

Document ingest — datagrep-extractor envelope

chunks = '{"chunk_id":"c1","text":"SAR filed on Pacific Trust account 7742"}\n' \
         '{"chunk_id":"c2","text":"Alice Chen authorized 4 transfers totaling $2.8M"}'
manifest = '{"source":"sar.pdf","extractor":"dgrep-v1","chunks":2}'
 
client.ingest_document(chunks, manifest)
# IngestDocumentResponse(report_id="rep_019fe2...", chunks_ingested=2,
#                        warnings=[], queue_depth=0)
Streaming, CDR, OTLP — the long tail of ingest shapes
MethodShapeUse it for
ingest.bulk("T", rows, detect_packs="network,financial")NDJSON + detector overrideper-call SmartIngest pack selection
ingest.ingest_iter("T", generator, batch_size=500)streaming iteratorO(batch_size) memory for huge CSVs
ingest.ingest_cdr(rows)CSV via /ingest/cdrcall-detail records (caller/callee/tower)
ingest.otlp_traces(payload) / otlp_logs(...) / otlp_metrics(...)OTLP/JSONOpenTelemetry ingest
ingest.media_status(task_id)pollmultipart media upload progress
# Stream a million rows without holding them all in memory:
def row_gen():
    for i in range(1_000_000):
        yield {"id": f"r{i}", "amount": i}
 
total = ingest.ingest_iter("Transaction", row_gen(), batch_size=1000)
# → 1_000_000

4. Query

SQL is the primary query language. RelataDB extends it with bi-temporal, graph, identity, and search operators — all reachable through query().

Plain SQL

result = client.query("SELECT name, role, company FROM Person WHERE risk = 'HIGH'")
for row in result:
    print(row["name"], row["role"])
{"data": [
  {"name": "Alice Chen", "role": "CFO",      "company": "Acme Corp"},
  {"name": "Bob Smith",  "role": "Director", "company": "ShellCo Ltd"}
]}

Aggregates

client.query("SELECT SUM(amount) FROM Transaction")
# {"data": [{"SUM(amount)": 3770000}]}
 
client.query(
    "SELECT from_account, COUNT(*), SUM(amount) "
    "FROM Transaction GROUP BY from_account"
)
{"data": [
  {"from_account": "Acme Corp",           "COUNT(*)": 2, "SUM(amount)": 2800000},
  {"from_account": "Pacific Trust 7742",  "COUNT(*)": 1, "SUM(amount)": 850000},
  {"from_account": "ShellCo Ltd",         "COUNT(*)": 1, "SUM(amount)": 120000}
]}

$3.77 million moved. The trail: Acme → offshore → shell company → cash.

Parameterized query (no SQL injection)

result = client.query_params(
    "SELECT name, role FROM Person WHERE risk = $1 AND company = $2",
    ["HIGH", "Acme Corp"],
)
# ?-placeholders are rewritten to $1, $2, … automatically:
client.query_params("SELECT * FROM Person WHERE id = ?", ["alice"])

Typed select helper (fluent builder)

result = (
    client.select("name", "risk")
         .from_("Person")
         .where("risk = 'HIGH'")
         .order_by("name")
         .limit(10)
         .execute()
)

Arrow IPC (zero-copy, large result sets)

tbl = client.query_arrow("SELECT * FROM Transaction LIMIT 1000")
df = tbl.to_pandas()   # requires pyarrow

Federated multi-query

client.multi_search({
    "queries": [
        {"query": "alice",       "type": "Person",      "limit": 5},
        {"query": "embezzlement","type": "CaseDoc",     "limit": 5},
        {"query": "pacific trust","type": "Transaction","limit": 5},
    ],
})
# {"results": [<SearchResponse>, <SearchResponse>, ...],
#  "processing_time_ms": 7.2}

GraphQL

client.graphql("""
  query {
    Person(where: { risk: { _eq: "HIGH" } }, limit: 10) {
      id name role company
    }
  }
""")

SPARQL

client.sparql("""
  PREFIX rel: <https://relata.io/ns#>
  SELECT ?s ?o WHERE { ?s rel:authorizedBy ?o } LIMIT 5
""")
MethodWireReturns
query(sql)POST /queryQueryResult (iterable)
query_params(sql, params)POST /query (positional binds)QueryResult
query_arrow(sql)POST /query/arrowpyarrow.Table
multi_search(queries)POST /multi-searchdict
graphql(q)POST /graphqldata field
sparql(q)POST /sparqldict

Three ways to search: the dedicated search() (BM25, faceted, highlighted), the hybrid SQL operator, and the typed namespace handle.

POST /search — BM25 with facets & highlights

res = client.search(
    "alice chen", "Person",
    limit=10,
    facets=["company", "risk"],
    highlight=True,
    filters={"company": "Acme Corp"},
    matching_strategy="all",
)
for hit in res.hits:
    print(hit.score, hit.fields["name"])
# 8.42 Alice Chen
{
  "hits": [{"score": 8.42, "fields": {"name": "Alice Chen", ...}}],
  "total": 1,
  "estimated_total_hits": 1,
  "facets": {"company": {"Acme Corp": 1}, "risk": {"HIGH": 1}},
  "processing_time_ms": 2.1
}

HYBRID_SEARCH — fused BM25 + vector (via SQL)

client.query(
    "HYBRID_SEARCH FROM CaseDoc "
    "QUERY 'embezzlement offshore transfers' LIMIT 3"
)
{"rows": 3, "data": [
  {"title": "Whistleblower Complaint by Carla Nunez", "_score": 12.84},
  {"title": "Acme Corp CFO Under Scrutiny",           "_score": 9.21},
  {"title": "Suspicious Activity Report",             "_score": 7.55}
]}

All three documents found, ranked by relevance — no Elasticsearch, no external service.

Weighted fusion — [graph, bm25, vector]

# Pure BM25 (keyword precision, no semantic fuzziness):
client.query(
    "HYBRID_SEARCH FROM CaseDoc QUERY 'ShellCo shell company' "
    "LIMIT 3 WEIGHTS 0.0 1.0 0.0"
)
 
# Balanced BM25 + vector via the search() door (set metric or weights to
# trigger the hybrid channel — #2672):
client.search(
    "embezzlement offshore", "CaseDoc",
    metric="cosine", weights=[0.0, 0.5, 0.5],
)

The WEIGHTS triple is [graph, bm25, vector]. Setting any one to 1.0 and the others to 0.0 gives you single-channel mode.


6. Resolve Identities

SmartIngest already linked every phone, email, IBAN, IMEI… on the way in. These six operators query that index.

detect_identities — pull identifiers out of free text

client.detect_identities(
    "Contact Alice at alice.chen@acmecorp.com or +14155550100. "
    "Wire to Pacific Trust account 7742, IBAN GB29NWBK60161331926819."
)
# {"data": [
#   {"kind": "email",  "value": "alice.chen@acmecorp.com"},
#   {"kind": "phone",  "value": "+14155550100"},
#   {"kind": "iban",   "value": "GB29NWBK60161331926819"}, ...]}

resolve_ids / identity_cluster — "who does this belong to?"

# The SQL operator — one query, no detection code:
client.query("LOOKUP_IDENTITY '+14155550100'")
# {"rows": 1, "data": [{"id": "alice", "name": "Alice Chen",
#                       "phone": "+14155550100", "risk": "HIGH"}]}
 
# The typed SDK helper — resolve to the full identity cluster:
client.identity_cluster("+14155550100")
# {"data": [{"id": "alice", "match_kind": "phone",
#            "cluster": [{"kind":"email","value":"alice.chen@acmecorp.com"},
#                         {"kind":"phone","value":"+14155550100"}, ...]}]}
 
client.resolve_ids("+14155550100", mode="canonical")
# Returns the single best canonical match.

same_identity — predicate

client.same_identity("+14155550100", "alice.chen@acmecorp.com")
# True  — both resolve to Alice.
 
client.same_identity("+14155550100", "bob@shellco.io")
# False

fuse_identities / split_identities — ontological merge

# Merge two records that are actually the same person:
client.fuse_identities("alice", "a.chen.duplicate")
# Writes an IdentityLink with link_type='fused'; returns the merged cluster.
 
# Undo a mistaken fuse:
client.split_identities("alice", "a.chen.duplicate")

76 canonical types are auto-detected: phones (E.164), emails (RFC 5322), IBANs (ISO 13616), IMEIs (GSM), MMSIs (maritime), VINs (vehicles), passport numbers, BTC addresses, URLs, MAC addresses, and 65 more.

MethodSQL operatorReturns
detect_identities(text)DETECT_IDENTITIES($1)list of {kind, value}
resolve_ids(v, mode=)RESOLVE_IDENTITY($1[, MODE => …])matched rows
identity_cluster(v)RESOLVE_IDENTITY($1, MODE => 'cluster')full cluster
same_identity(a, b)SAME_IDENTITY($1, $2)bool
fuse_identities(a, b)FUSE_IDENTITIES($1, $2)merged cluster
split_identities(a, b)SPLIT_IDENTITIES($1, $2)unmerged clusters

7. Trace Graphs

Twelve graph operators — reachable as typed SDK methods or as SQL operators. The story: trace the money from Acme to cash.

Create the edges first

client.create_link("AUTHORIZED", "alice", "Person", "tx1", "Transaction")
client.create_link("AUTHORIZED", "david", "Person", "tx2", "Transaction")
client.create_link("AUTHORIZED", "bob",   "Person", "tx3", "Transaction")
client.create_link("AUTHORIZED", "alice", "Person", "tx4", "Transaction")
# {"link_name": "AUTHORIZED", "source_id": "alice", ...}

Shortest path

client.graph_shortest_path("alice", "tx3")
# {"path": ["alice", "tx1", "Pacific Trust 7742", "tx2", "ShellCo Ltd", "tx3"],
#  "hops": 5}
 
client.graph_dijkstra("Transaction", "tx1", "tx3")  # weighted variant

Traversal

client.graph_traverse("alice", direction="out", max_depth=3, limit=50)
# {"nodes": [{"id": "tx1"}, {"id": "tx4"}, ...],
#  "edges": [{"from": "alice", "to": "tx1", "label": "AUTHORIZED"}, ...]}

Centrality & community detection

client.graph_pagerank("Person", damping=0.85, max_iter=100)
# {"data": [{"id": "alice", "score": 0.42},
#           {"id": "bob",   "score": 0.21}, ...]}
 
client.graph_community("Person")
# {"communities": [{"id": 0, "members": ["alice", "bob", "david"]}, ...]}
 
client.graph_scc("Person")                # strongly connected components
client.graph_triangle_count("Transaction") # graph density / cohesion
client.graph_link_predict("Person")
# Predicts missing relationships: [{"from": "alice", "to": "david", "score": 0.78}, ...]
 
client.graph_node_similarity("Person", "alice")
# {"data": [{"id": "bob", "score": 0.91}, {"id": "david", "score": 0.64}]}
All 12 graph operators
MethodSQL operatorFinds
graph_shortest_path(src, dst)GET /graph/shortest-pathshortest path (HTTP door, supports max_hops)
graph_dijkstra(type, src, dst)GRAPH_DIJKSTRA(...)weighted shortest path
graph_traverse(src, depth=)GET /graph/traverseBFS traversal
graph_pagerank(type)GRAPH_PAGERANK(...)centrality
graph_community(type)GRAPH_COMMUNITY(...)Louvain/label-prop communities
graph_scc(type)GRAPH_SCC(...)strongly connected components (fraud rings)
graph_cycles(type)GRAPH_CYCLES(...)cycle detection
graph_triangle_count(type)TRIANGLE_COUNT(...)cohesion
graph_node_similarity(type, node)GRAPH_NODE_SIMILARITY(...)similar entities
graph_link_predict(type)GRAPH_LINK_PREDICT(...)missing edges
list_edge_types()GET /types/edgesregistered edges
register_edge_type(from, to, label)POST /types/edgesregister an edge
create_link(name, src, srcT, dst, dstT)POST /linkscreate an edge instance

8. Investigate

One-click entity investigation plus twelve domain-specific operators (financial crime, maritime, telecom, geospatial). The SDK methods wrap governed SQL operators; the MCP tools wrap the same operators for agents.

Investigate entity (composite profile)

mcp = McpClient.from_client(client)
 
mcp.investigate_entity("Person", "alice")
# {"profile": {...}, "timeline": [...], "connections": [...],
#  "risk": {"score": 0.92, "factors": ["sanctions_proximity", ...]}}

Sanctions screening

client.sanctions_screen("Alice Chen")
# {"data": []}  — no hits yet
 
mcp.screen_sanctions("Alice Chen", threshold=0.85)
# {"rows": 0}

Beneficial ownership chain

client.beneficial_ownership_chain("Pacific Trust 7742", max_depth=6)
# {"chain": [{"account": "Pacific Trust 7742", "holder": "David Kim"},
#            {"holder": "David Kim", "nominee_for": "Bob Smith"}, ...]}

Crypto trace, wire reconstruction, hawala

client.crypto_trace("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb")
# {"hops": [{"from": "0x742d...", "to": "0x9ab1...", "amount": 12.5}, ...]}
 
client.wire_reconstruction("Pacific Trust 7742", tolerance_pct=5.0)
# {"chain": [{"account": "...", "in": 2800000, "out": 850000}, ...]}
 
client.hawala_trace("alice", max_hops=5)
# {"network": [{"node": "alice", "linked_to": ["hawaladar_1", ...]}, ...]}

Geospatial

client.geofence("POINT(-97.74 30.27)", target_type="MovementEvent")
# {"data": [{"id": "m1", "lat": 30.27, "lon": -97.74}, ...]}
 
client.crime_pattern_cluster("downtown_austin")
# {"clusters": [{"centroid": [...], "events": 23}, ...]}

Telecom: burner & convoy detection

client.burner_detect(max_age_days=30, max_calls=3)
# {"burners": [{"phone": "+14155550199", "age_days": 12, "calls": 2}, ...]}
 
client.convoy_detect(radius_m=200, time_tol_secs=300, min_points=3)
# {"convoys": [{"members": ["veh_1", "veh_2", "veh_3"], "window": [...]}]}

Maritime

client.vessel_track(538005644, window_secs=86400)        # MMSI → AIS track
client.dark_fleet_detect(max_gap_hours=24)               # AIS gaps ("going dark")
client.vessel_to_vessel_transfer(proximity_nm=0.5,
                                  time_window_minutes=120)
# {"transfers": [{"vessel_a": 538..., "vessel_b": 636..., ...}]}
Full investigation operator table
MethodDomainWhat it finds
sanctions_screen(name)compliancesanctions-list hits (fuzzy threshold)
beneficial_ownership_chain(party)complianceultimate beneficial owner
crypto_trace(entity)financialcryptocurrency fund flow
wire_reconstruction(account)financialwire-transfer chain
hawala_trace(seed)financialinformal value-transfer network
geofence(area)geospatialentities within a geographic fence
crime_pattern_cluster(area)geospatialspatial crime clusters
burner_detect(...)telecomburner phone numbers
convoy_detect(...)telecom/transportentities traveling together
dark_fleet_detect(...)maritimevessels with AIS gaps
vessel_track(mmsi)maritimeAIS position reports
vessel_to_vessel_transfer(...)maritimeship-to-ship transfers

9. Agent Memory

Memory is the mem0-style high-level surface over the governed /memory/* verbs (ADR-144). Every belief is bi-temporal, provenance-tracked, and governable.

mem = Memory("http://localhost:9090",
             purpose="agent-notes",
             bearer_token="perftoken",
             session_id="shadow-ledger")

add — store a belief

mid1 = mem.add("Alice Chen authorized $2.3M wire to Pacific Trust account 7742 on Jan 15.",
               confidence=0.95, memory_class="episodic")
mid2 = mem.add("Bob Smith directs ShellCo Ltd, received $850K from the offshore account.")
mid3 = mem.add("Carla Nunez blew the whistle — four fraudulent transfers, $2.8M total.")
mid4 = mem.add("Classic laundering: placement → layering → integration.",
               memory_class="procedural")
# mid1 = "019fe254-3647-77fc-..."

One call stored a bi-temporal row (valid_from/valid_to + system_from/system_to), linked it to the session, scored it with confidence, and hash-chained it to the provenance graph. No extra tables, no vector store setup.

add_batch — high-throughput write

ids = mem.add_batch([
    "The wire transfer matches a known fraud pattern: rapid offshore movement.",
    {"content": "Customer #1234 has no prior history with this beneficiary.",
     "confidence": 0.8, "memory_class": "semantic"},
    "Beneficiary account opened 2 days before the transfer request.",
])
# ids = ["019fe255-...", "019fe256-...", "019fe257-..."]

search — recall ranked by relevance × recency × confidence

hits = mem.search("How much money was transferred?", top_k=2)
ScoreMemory
1.0000Classic laundering: placement → layering → integration.
0.6444Carla Nunez blew the whistle — four fraudulent transfers, $2.8M total.
mem.search("Who is the whistleblower?")
# [{"content": "Carla Nunez blew the whistle...", "score": 1.0, ...}]
 
mem.search("What is ShellCo?")
# [{"content": "Bob Smith directs ShellCo Ltd...", "score": 1.0, ...}]

search_detailed — observe the ADR-145 retrieval-quality knobs

envelope = mem.search_detailed(
    "money transfer",
    top_k=5,
    min_confidence=0.5,            # CONFIDENCE
    recency_half_life_secs=86400,  # RECENCY
    budget_tokens=2048,            # BUDGET
    stability_days=30.0,           # FORGETTING_CURVE
    cancel_threshold=0.2,          # CANCEL_WHEN
)
# envelope = {"rows": [...],
#             "recall_cost_tokens": 412,   # BUDGET running total
#             "cancelled": False}          # CANCEL_WHEN short-circuit
mem.associate(mid1, mid2, relation="same_investigation")
# {"from_id": mid1, "to_id": mid2, "relation": "same_investigation"}

episodes — list sessions

mem.episodes(session_id="shadow-ledger")
# [{"id": "ep_1", "session_id": "shadow-ledger",
#   "summary": "Operation Shadow Ledger investigation", ...}]

justify — provenance chain

mem.justify(mid1)
# {"found": True,
#  "provenance": {"prov_hex": "a3f8b2c1...",
#                  "source": "memory:remember",
#                  "timestamp": "2026-08-08T16:20:14Z"}}

When the regulator asks "why did the agent flag this transaction?", you have the answer — every belief is traceable.

update / resolve / summarise / forget

new_id = mem.update(mid1, "UPDATED: Alice authorized $2.3M — confirmed by 2 sources.")
# Old belief is superseded, not deleted. Bi-temporal history preserves it.
 
mem.resolve(new_id)   # follow the supersession chain to canonical head
# {"id": new_id, "content": "UPDATED: ...", "supersedes": [mid1]}
 
mem.summarise([mid1, mid2, mid3], summary_content="Three findings on Shadow Ledger.")
# {"id": "summ_...", "content": "Three findings on Shadow Ledger."}
 
mem.forget(mid4)      # governed retention-policy retract (not a hard delete)
# {"memory_item_id": mid4, "policy": "soft_delete",
#  "forget_at_ns": 1789234560000000000}
The full Memory surface (15 methods)
MethodVerbPurpose
add(content, ...)rememberstore a belief, return its id
add_batch(items)remember_batchbulk write, return ids in order
search(query, top_k=)recallranked retrieval
search_detailed(query, ...)recallfull envelope with cost/cancelled
batch_search(queries)recall×Nmultiple queries merged
get(memory_id)recognizesingle fetch, or None
update(id, content)consolidatesupersede an old belief
forget(memory_id)forgetgoverned retention retract
associate(src, dst, rel)associatetyped link between memories
episodes(session_id=)episodes_inlist sessions
justify(memory_id)justifyPROV-O provenance chain
resolve(memory_id)resolvefollow supersession to canonical head
summarise(ids)summarisesummary belief from sources
get(memory_id)recognizesingle fetch
close()close the HTTP pool

10. MCP Tools (69 governed agent tools)

McpClient is the typed Python surface over the server's 69 MCP tools — the same tools Claude / Cursor / Cline get when you point them at RelataDB.

mcp = McpClient.from_client(client)
 
mcp.initialize()    # handshake
tools = mcp.list_tools()
# tools = [{"name": "query_knowledge", ...}, {"name": "recall", ...}, ...]
# len(tools) == 69

Connect Claude directly:

claude mcp add relata http://localhost:9090/mcp \
  --header "Authorization: Bearer perftoken"

Knowledge & query

mcp.query_knowledge("SELECT name, risk FROM Person WHERE risk = 'HIGH'",
                    purpose="analytics")
 
mcp.search_knowledge("embezzlement offshore", purpose="analytics", top_k=5)
 
mcp.explain_policy("SELECT * FROM Person", purpose="analytics")
# Shows the ACL / org-isolation policy that would apply, without executing.
 
mcp.suggest_extensions()
# Lists extension packs and their data availability.

Entity discovery

mcp.list_entity_types()
mcp.get_entities("Person", filters={"risk": "HIGH"}, limit=10)
mcp.search_entities("alice", entity_types=["Person"])
mcp.get_domain_summary("financial")
# {"counts": {"Person": 4, "Transaction": 4, ...}, "freshness": {...}}

Case & investigation

mcp.get_entity_profile("alice", purpose="analytics")
mcp.get_timeline("alice", purpose="analytics",
                 since_ns=1736899200000000000)   # since 2025-01-15
mcp.find_connections("alice", purpose="analytics", limit=50)
mcp.get_relationships(subject="alice", predicate="AUTHORIZED",
                      purpose="analytics")
mcp.investigate_entity("Person", "alice")
mcp.find_threats("ProcessEvent")
mcp.add_case_note("case-2026-001",
                  "Alice authorized 4 transfers to Pacific Trust 7742.",
                  author="investigator-1")
mcp.get_case_summary("case-2026-001", purpose="analytics")

Identity

mcp.lookup_identity("+14155550100", purpose="analytics")
mcp.resolve_entity_identity("alice", purpose="analytics")

Memory (the 10 cognitive verbs, reachable via MCP too)

mid = mcp.remember("Alice authorized $2.3M wire on Jan 15.", purpose="agent-notes")
mcp.remember_batch([{"content": "ShellCo received $850K."},
                    {"content": "Carla is the whistleblower."}],
                   purpose="agent-notes")
mcp.recall("whistleblower", purpose="agent-notes", top_k=3)
mcp.recognize(mid, purpose="agent-notes")
mcp.justify(mid, purpose="agent-notes")
mcp.consolidate(mid, "UPDATED: confirmed by 2 sources.", purpose="agent-notes")
mcp.forget(mid, retain_days=90, purpose="agent-notes")
mcp.remember_procedure("fraud-agent", "SAR_FILING",
                       "1. Screen. 2. Document. 3. File SAR within 30 days.",
                       purpose="agent-notes")
mcp.recall_procedure("fraud-agent", name="SAR_FILING", purpose="agent-notes")
mcp.associate(mid, mid2, relation="same_case", purpose="agent-notes")
mcp.resolve(mid, purpose="agent-notes")
mcp.summarise(ids=[mid, mid2], purpose="agent-notes")
mcp.episodes_in("shadow-ledger", purpose="agent-notes")

Natural language → SQL

mcp.nl_query("show all high risk persons", purpose="analytics")
# {"dialect": "sql", "sql": "SELECT * FROM Person WHERE risk = 'HIGH'",
#  "rows": [...]}
 
mcp.nl_query("who is alice connected to",
             purpose="analytics", max_sub_questions=2)
# {"dialect": "cypher", "decomposed": True,
#  "sub_results": [{"dialect": "sql", ...}, {"dialect": "cypher", ...}]}

Detection rules & jobs

mcp.import_sigma("""
title: Suspicious Large Wire Transfer
status: experimental
logsource:
  product: relata
  service: Transaction
detection:
  selection:
    amount: 1000000
  condition: selection
level: high
""", purpose="security")
# {"rule_id": "019fe24e-e333-...", "name": "Suspicious Large Wire Transfer",
#  "status": "active"}
 
mcp.list_rules()
mcp.create_rule("high_value_wire",
                "SELECT * FROM Transaction WHERE amount > 1000000",
                severity="high", purpose="security")
mcp.list_jobs()
mcp.schedule_job("high_value_wire")
mcp.job_status()

Workflows

mcp.list_workflows()
mcp.run_workflow("sar_filing_workflow")
mcp.workflow_status("<run_id>")

Graph via MCP

mcp.detect_communities("Person", purpose="analytics")
mcp.rank_key_nodes("Person", metric="pagerank", purpose="analytics")
mcp.hub_authority("Person", purpose="analytics")
mcp.find_scc("Person", purpose="analytics")
mcp.predict_links("Person", from_id="alice", purpose="analytics")
mcp.paths_between("alice", "tx3", max_hops=4, purpose="analytics")
mcp.list_link_types()

Financial-crime via MCP

mcp.trace_crypto("0x742d...", max_hops=5, purpose="analytics")
mcp.beneficial_ownership("alice", max_depth=6, purpose="analytics")
mcp.reconstruct_wire("Pacific Trust 7742", tolerance_pct=5.0)
mcp.trace_hawala("alice", max_hops=5)
mcp.screen_sanctions("Alice Chen", purpose="compliance_review")
mcp.geofence(30.27, -97.74, radius_m=1000, purpose="analytics")

RAG & multimodal

mcp.rag_store_answer("Who authorized the transfers?", "Alice Chen.",
                     source_ids=["tx1", "tx4"], purpose="rag")
mcp.rag_store_elements([{"type": "fact", "text": "ShellCo is a front."}],
                       purpose="rag")
mcp.ingest_document(chunks_jsonl=chunks, manifest_json=manifest,
                    purpose="rag")
mcp.hybrid_search("CaseDoc", "embezzlement", top_k=5, purpose="analytics")
mcp.similar_multimodal("MediaEmbedding", "img_42",
                       modality="image", purpose="investigation")
mcp.search_video_frames("frame_1", top_k=20, purpose="security_incident")
mcp.ingest_media("MediaImage", bytes_b64="...", modality="image")

Ops

mcp.server_health()
mcp.job_status()
mcp.metrics()
mcp.aggregate_stats("Transaction", agg="SUM", column="amount")
mcp.get_audit_trail(principal_filter="investigator-1", limit=100)
The remaining MCP tools (gist)
ToolWhat it does
find_in_social_corpus(object_type, text_query=, user=)search ingested social-media corpus
face_match(probe_id, threshold=)GATED (ADR-155) — match a probe face
erase_subject(subject, reason=)GDPR Art. 17 crypto-shred erasure
metrics() / server_health() / job_status()ops observability

11. Media & Biometrics

Four governed SQL operators for face search, image similarity, perceptual-hash matching, and DNS-tunnel detection. (The MCP face_match tool is gated behind ADR-155 — these SQL-reachable operators are the unblocked path.)

client.face_search(
    "gallery-1", [0.12, -0.34, 0.56, ...],   # 128-dim probe embedding
    k=5, threshold=0.6, purpose="investigation",
)
# QueryResult: columns entity_id, gallery_id, score, modality (ranked desc)
 
client.match_pdq(
    "corpus-1", "a1b2c3d4e5f67890...",        # PDQ hex hash
    threshold=0.9, purpose="investigation",
)
# QueryResult: entity_id, media_type, corpus_id, score, matcher
 
client.similar_image(
    "media-42", threshold=0.6, index="ncmec",
    purpose="investigation",
)
# QueryResult: near-duplicate images, ranked desc
 
client.dns_tunnel_detect("ws-finance-01", purpose="security")
# {"data": [{"domain": "exfil.bad.tld", "entropy": 7.8, "count": 1242}]}

12. Governance

Erasure, sessions, and export — the compliance surface.

GDPR Art. 17 erasure — irreversible crypto-shred

client.erase_subject("alice", reason="gdpr-art17-request")
# {"subject": "alice", "shredded": True,
#  "rows_affected": 4, "vectors_removed": 1, "blobs_removed": 0,
#  "receipt": {"signed": "..."}}

Session management (draft → review → commit)

# Stage some writes, review them, then commit or discard.
client.session_diff("session-shadow-ledger")
# {"changes": [{"type": "Person", "id": "alice", "op": "upsert", ...}]}
 
client.session_commit("session-shadow-ledger")
# {"committed": True, "rows": 4, "commit_hash": "f3a2c8b1..."}
 
# Or throw them away:
client.session_discard("session-shadow-ledger")
# {"discarded": True}

Data export

client.export_data("Transaction", format="json")
# {"type": "Transaction", "format": "json",
#  "rows": [...], "exported_at": "2026-08-08T..."}
 
client.export_data("Person", format="csv")
Enrichment rules & ontology migration (the governance long tail)
# Register custom SmartIngest enrichment rules.
client.enrichment_rules({
    "rules": [{"name": "internal_acct", "pattern": r"ACME-\d{6}",
               "canonical_kind": "account_number"}],
})
 
# Governed SHACL ontology migration.
client.ontology_migrate({
    "types": [{"name": "SanctionsHit",
               "properties": {"name": {"type": "text"},
                              "list": {"type": "text"}}}],
})

13. Branches & Namespaces

Schema branches for "what-if" analysis; namespace handles for the search-developer shape.

Schema branches (copy-on-write forks)

client.create_schema_branch("hypothesis-bribery", from_branch="main")
# {"created": True, "branch": "hypothesis-bribery", "source": "main"}
 
# Ingest alternative theories into the branch, run queries, compare.
# Then either merge or delete:
client.delete_schema_branch("hypothesis-bribery")
# {"deleted": True}
docs = client.namespace("CaseDoc")
 
# Schemaless upsert (POST /ingest/auto — auto-creates the type if absent).
docs.write([
    {"id": "memo1", "title": "Bribery hypothesis",
     "body": "Pacific Trust may be a kickback conduit.", "status": "draft"},
], schema={"title": "text", "body": "text"})
 
# Typed ranked search — text compiles to BM25; filters narrow server-side.
res = docs.query(
    text="bribery kickback",
    match_column="title",
    filters=[{"field": "status", "op": "eq", "value": "draft"}],
    limit=5,
)
for row in res:
    print(row["title"])
 
docs.get("memo1")           # point lookup
docs.delete_all()           # governed bi-temporal tombstone every row
docs.branch_from("CaseDoc") # T10 copy-on-write namespace branch

One client, one connection pool, reused across every namespace. Governance (auth, tenant, purpose) travels in the substrate.


14. Cluster Ops

Four methods for multi-node deployments (RELATA_PROFILE=cluster).

client.cluster_nodes()
# [ClusterNode(id="node-7f3a", role="coordinator", addr="10.0.0.1:9090",
#              partitions=[0,1,2], state="healthy"),
#  ClusterNode(id="node-9b2c", role="reader",      addr="10.0.0.2:9090", ...)]
 
client.cluster_topology()
# {"nodes": [...], "partitions": [{"id": 0, "primary": "node-7f3a",
#                                  "replicas": ["node-9b2c"]}], "roles": {...}}
 
client.cluster_rebalance()
# {"rebalanced": True, "partitions_moved": 3, "duration_ms": 1240}
 
client.cluster_drain("node-9b2c")   # evacuate for maintenance
# {"drained": True, "node": "node-9b2c", "partitions_relocated": 3}

15. System Ops

SSE event stream, webhooks, and the high-throughput wire protocols.

Observe stream (live SSE)

for event in client.observe_stream():
    print(event["kind"], event["level"], event["message"])
# query INFO  SELECT name FROM Person (rows=4, latency_ms=2.1)
# ingest INFO bulk Person rows=4
# ...

Requires RELATA_OBSERVE_STREAM=on server-side. A connection drop ends the generator cleanly rather than raising.

Webhooks

client.register_webhook(
    "https://hooks.slack.com/services/...",
    event_types=["ingest.completed", "alert.triggered"],
)
# {"id": "wh_019fe2...", "url": "https://...", "event_types": [...]}
 
client.list_webhooks()
# {"webhooks": [{"id": "wh_019fe2...", ...}]}
 
client.delete_webhook("wh_019fe2...")
# {"deleted": True}

Arrow Flight (zero-copy columnar over gRPC)

tbl = client.query_flight(
    "SELECT * FROM Transaction LIMIT 1000", purpose="analytics",
)
df = tbl.to_pandas()
# Requires RELATA_FLIGHT_ENABLE=true (port 8815); pyarrow only, no grpcio.

Plain gRPC (RelataQuery.Execute)

result = client.query_grpc("SELECT * FROM Person LIMIT 1000")
# Same QueryResult shape as query(); requires grpcio
# (pip install relata-sdk[grpc]).
 
result = client.query_grpc_stream("SELECT * FROM Transaction")
# Server-streaming variant — same shape, frame-by-frame.

16. Audit & Provenance

The audit chain is hash-chained and tamper-evident. A chain_valid: False response means tampering — escalate immediately.

from relata import AuditClient
 
audit = AuditClient.from_client(client)
 
audit.count()
# AuditCountResponse(count=47, entries=47,
#                    chain_valid=True, chain_head="f3a2c8b1...")

chain_valid: True — every query, ingest, and MCP call is recorded and provably unmodified.

# Paginated entries with filters:
audit.entries(principal="investigator-1",
              purpose="analytics",
              limit=5)
# {"entries": [{"ts_ns": ..., "principal": "investigator-1",
#               "action": "query", "sql": "SELECT ...",
#               "decision": "allow", "request_id": "req_..."}, ...],
#  "next_cursor": "...", "chain_valid": True}
 
audit.find_by_request_id("req_abc123")
# Single entry, or None

Court-grade PDF + signed receipts

pdf_bytes = audit.export_pdf("case-2026-001", template="default")
Path("shadow-ledger.pdf").write_bytes(pdf_bytes)
 
receipt = audit.sign_receipt({
    "case_id": "case-2026-001",
    "subject": "alice",
    "action": "gdpr_erasure",
})
# {"signed": True, "signature": "...", "signed_at": "..."}

17. Temporal & Provenance

Every row carries four timestamps: valid_from, valid_to (when the fact was true in the real world) and system_from, system_to (when RelataDB knew it).

AS OF — point-in-time queries

# Valid time: what was true on Jan 20?
client.query("SELECT COUNT(*) FROM Person AS OF '2026-01-20T00:00:00Z'")
 
# System time: what did we KNOW on Jan 20?
client.query("SELECT COUNT(*) FROM Person AS OF SYSTEM TIME '2026-01-20T00:00:00Z'")

WITH PROVENANCE — every row carries its chain

result = client.query("SELECT name, company FROM Person WITH PROVENANCE")
# Each row includes prov_hex, source, commit_hash, timestamp.

Bi-temporal via the fluent builder

result = (
    client.select("Person")
         .where("risk = 'HIGH'")
         .as_of("2026-01-20")
         .with_provenance()
         .limit(20)
         .execute()
)

The DIY alternative (for contrast)

ComponentDIY stackRelataDB
Relational storePostgres✅ built-in
Full-text searchElasticsearch / Typesense✅ custom BM25 (WAND, 12-language stemmers)
Vector storePinecone / pgvector✅ custom HNSW + DiskANN
Graph databaseNeo4j✅ CSR + PLL
Memory layermem0 / custom✅ 15 methods, governed
Audit logSplunk / custom✅ hash-chained, tamper-evident
Identity resolutionCustom ETL✅ SmartIngest (76 types)
Agent toolsCustom MCP server✅ 69 tools, governed
ETL glueKafka / Fivetran✅ not needed (one store)
Total services8+1

Summary: 100% SDK Coverage Table

Every public method of RelataClient (+ the companion clients), which section shows it, and the recipe it belongs to.

#MethodClient§Recipe
1health()RelataClient1liveness probe
2status()RelataClient1profile + quota
3stats()RelataClient1dashboard counts
4version()RelataClient1build info
5ready()RelataClient1readiness probe
6list_modules()RelataClient1installed packs
7register_type(name, **)RelataClient2register a type
8deregister_type(name)RelataClient2remove a type
9list_types()RelataClient2list all types
10type_detail(name)RelataClient2type details
11schema_alter(name, action, col, **)RelataClient2online ALTER
12register_edge_type(from, to, label)RelataClient2register edge
13list_edge_types()RelataClient2list edges
14ontology_migrate(schema)RelataClient2SHACL migration
15enrichment_rules(rules)RelataClient2custom detectors
16bulk(type, rows)IngestClient3NDJSON bulk
17bulk(type, rows, on_conflict='upsert')IngestClient3JSON upsert
18bulk(type, rows, on_conflict='skip')IngestClient3skip existing
19bulk_csv(type, csv_text)IngestClient3CSV ingest
20ingest_iter(type, iter, batch_size)IngestClient3streaming
21ingest_cdr(rows)IngestClient3call-detail records
22otlp_traces(payload) / otlp_logs / otlp_metricsIngestClient3OpenTelemetry
23ingest_document(chunks, manifest)RelataClient3datagrep doc
24query(sql)RelataClient4SQL SELECT
25query_params(sql, params)RelataClient4parameterized
26query_arrow(sql)RelataClient4Arrow IPC
27select(*cols).execute()RelataClient4fluent builder
28multi_search(queries)RelataClient4federated
29graphql(query)RelataClient4GraphQL
30sparql(query)RelataClient4SPARQL
31search(query, type, **)RelataClient5POST /search
32query("HYBRID_SEARCH ...")RelataClient5fused search
33query("HYBRID_SEARCH ... WEIGHTS")RelataClient5weighted
34detect_identities(text)RelataClient6detect from text
35resolve_ids(value, mode=)RelataClient6resolve identity
36identity_cluster(value)RelataClient6full cluster
37same_identity(a, b)RelataClient6predicate
38fuse_identities(a, b)RelataClient6merge
39split_identities(a, b)RelataClient6unmerge
40graph_shortest_path(src, dst)RelataClient7shortest path
41graph_traverse(src, depth=)RelataClient7BFS traversal
42graph_community(type)RelataClient7Louvain
43graph_pagerank(type)RelataClient7centrality
44graph_scc(type)RelataClient7SCC (fraud rings)
45graph_cycles(type)RelataClient7cycle detection
46graph_link_predict(type)RelataClient7missing edges
47graph_node_similarity(type, node)RelataClient7similar entities
48graph_triangle_count(type)RelataClient7cohesion
49graph_dijkstra(type, src, dst)RelataClient7weighted path
50create_link(name, src, sT, dst, dT)RelataClient7create edge
51sanctions_screen(name)RelataClient8sanctions hit
52beneficial_ownership_chain(party)RelataClient8UBO trace
53crypto_trace(entity)RelataClient8crypto flow
54wire_reconstruction(account)RelataClient8wire chain
55hawala_trace(seed)RelataClient8hawala network
56geofence(fence)RelataClient8geo-fence
57burner_detect(**)RelataClient8burner phones
58convoy_detect(**)RelataClient8convoys
59crime_pattern_cluster(area)RelataClient8crime clusters
60dark_fleet_detect(**)RelataClient8AIS gaps
61vessel_track(mmsi)RelataClient8AIS track
62vessel_to_vessel_transfer(**)RelataClient8STS transfers
63face_search(gallery, embedding)RelataClient11face k-NN
64similar_image(media_ref)RelataClient11near-dup images
65match_pdq(corpus, hash)RelataClient11PDQ hash match
66dns_tunnel_detect(entity)RelataClient11DNS tunneling
67add(content, **)Memory9remember
68add_batch(items)Memory9bulk remember
69search(query, top_k=)Memory9recall
70search_detailed(query, **)Memory9recall + knobs
71batch_search(queries)Memory9multi-recall
72get(memory_id)Memory9recognize
73update(id, content)Memory9consolidate
74forget(memory_id)Memory9retention retract
75associate(src, dst, rel)Memory9link memories
76episodes(session_id=)Memory9list episodes
77justify(memory_id)Memory9provenance chain
78resolve(memory_id)Memory9canonical head
79summarise(ids)Memory9summary belief
80list_tools() / call_tool / initializeMcpClient10MCP core
81query_knowledge / search_knowledge / explain_policyMcpClient10knowledge
82list_entity_types / get_entities / search_entitiesMcpClient10discovery
83get_domain_summary / find_in_social_corpusMcpClient10domain/social
84lookup_identity / resolve_entity_identityMcpClient10identity
85get_entity_profile / get_timeline / find_connectionsMcpClient10entity dossier
86get_relationships / add_case_note / get_audit_trailMcpClient10case/audit
87get_case_summary / investigate_entity / find_threatsMcpClient10investigation
88remember / recall / recognize / justify / consolidate / forgetMcpClient10memory verbs
89remember_procedure / recall_procedure / associate / resolve / summarise / episodes_inMcpClient10memory long tail
90remember_batch / nl_queryMcpClient10batch + NL
91rag_store_answer / rag_store_elements / ingest_documentMcpClient10RAG
92hybrid_search / similar_multimodal / search_video_framesMcpClient10retrieval
93ingest_media / face_match (gated)McpClient10media
94paths_between / detect_communities / rank_key_nodesMcpClient10graph
95hub_authority / find_scc / predict_links / list_link_typesMcpClient10graph long tail
96trace_crypto / beneficial_ownership / reconstruct_wire / trace_hawala / screen_sanctions / geofenceMcpClient10fincrime
97import_sigma / list_rules / create_rule / list_jobs / schedule_job / job_statusMcpClient10detection
98list_workflows / run_workflow / workflow_statusMcpClient10workflows
99aggregate_stats / server_health / metrics / erase_subjectMcpClient10ops/governance
100erase_subject(subject, reason)RelataClient12GDPR erasure
101session_commit(id) / session_diff(id) / session_discard(id)RelataClient12session mgmt
102export_data(type, format=)RelataClient12data export
103cluster_nodes()RelataClient14list nodes
104cluster_topology()RelataClient14topology
105cluster_rebalance()RelataClient14rebalance
106cluster_drain(node_id)RelataClient14drain
107create_schema_branch(name, from)RelataClient13create branch
108delete_schema_branch(name)RelataClient13delete branch
109namespace(name).query(**) / .write(rows) / .get(id) / .delete_all() / .branch_from()Namespace13retrieval surface
110observe_stream()RelataClient15SSE events
111register_webhook(url, ...) / list_webhooks() / delete_webhook(id)RelataClient15webhooks
112query_flight(sql)RelataClient15Arrow Flight
113query_grpc(sql) / query_grpc_stream(sql)RelataClient15gRPC query
114audit_count() / AuditClient.count()RelataClient / AuditClient16audit count
115AuditClient.entries(**) / .find_by_request_id(...)AuditClient16audit entries
116AuditClient.export_pdf(case) / .sign_receipt(payload)AuditClient16PDF + receipt

76 RelataClient methods · 15 Memory methods · 69 MCP tools · 100% covered.

~30 lines of code per recipe. Zero external services. One binary.



Every captured response on this page was taken from a live RelataDB server. No mockups.