Python SDK
⏱ Running in 5 minutes — new here? Jump to the quickstart for install → first query → first agent memory, or copy the Quick start block below.
pip install relata-sdkand you’re ready.
The official Python SDK for RelataDB. Sync + async surfaces, Pydantic models, fluent query builder, agent-memory client, and 12 typed v1.1 clients that mirror the server’s REST surface.
- Source:
sdks/python/(sdks/python/relata/) - PyPI:
relata-sdk - Current version:
0.2.0 - Python: 3.11+
- Runtime deps:
httpx >= 0.27,pydantic >= 2.6
Install
pip install relata-sdk
# or
uv add relata-sdkQuick start
from relata import RelataClient
with RelataClient("http://localhost:9090", purpose="analytics") as client:
result = client.query("SELECT * FROM Person WHERE name LIKE 'Ahmed%' LIMIT 10")
for row in result:
print(row)Agent memory in three lines
Memory is a Mem0-style surface over the governed /memory/* verbs — purpose + ACL stay on by default.
from relata import Memory
with Memory("http://localhost:9090", purpose="agent-notes") as m:
mem_id = m.add("Alice prefers dark mode") # store
hits = m.search("ui preferences", top_k=5) # recall (confidence × recency × relevance)
m.forget(mem_id) # governed retract, not a hard deleteFor async apps, AsyncMemory has the same surface: async with AsyncMemory(...) as m: await m.add(...) ; await m.search(...).
RelataClient — the main client
RelataClient(
base_url: str,
*,
bearer_token: str | None = None,
purpose: str | None = None,
timeout: float = 30.0,
tenant: str | None = None, # X-Organization-Id (multi-tenant)
acting_as: str | None = None, # X-Acting-As (delegation)
delegated_by: str | None = None, # X-Delegated-By
headers: dict[str, str] | None = None,
max_retries: int = 0,
retry_backoff_secs: float = 0.5,
)Core methods
| Method | Returns | Description |
|---|---|---|
query(sql, *, purpose) | QueryResult | Execute a SQL query |
select(*cols) | QueryBuilder | Start a fluent query (see below) |
health() | HealthResponse | Liveness check |
status() | StatusResponse | Profile, role, query quota |
stats() | Stats | Engine counts (records / states / snapshot_rows / log_leaves / tokens) |
version() | VersionInfo | Build info (version, commit, profile, schema_version) |
ready() | ReadyReport | 9-condition readiness report |
audit_count() | AuditCountResponse | Audit entry count + chain validity |
cluster_nodes() | list[ClusterNode] | List cluster nodes |
close() / __enter__ / __exit__ | — | Context-manager lifecycle |
Every method has an async mirror (aquery, ahealth, astats, aready, aclose, …).
Purpose is mandatory (with a default escape hatch)
Every query must declare a purpose registered in the tenant’s PurposeRegistry ("analytics", "audit", "compliance_review", …). Set a default on the client or override per-call:
client = RelataClient("http://localhost:9090", purpose="analytics")
result = client.query("SELECT COUNT(*) FROM Person", purpose="audit") # overrideAuthentication
client = RelataClient(
"https://relata.example.com",
bearer_token="your-token", # required when server has RELATA_BEARER_TOKEN set
purpose="analytics",
)Without a token the SDK runs as the built-in api-user principal — fine for local dev, not for production multi-tenant.
Multi-tenant + delegation headers
client = RelataClient(
url,
bearer_token=token,
tenant="org-acme", # X-Organization-Id
acting_as="user-bob", # X-Acting-As
delegated_by="user-alice", # X-Delegated-By
)QueryBuilder — fluent SQL
result = (
client.select("Person")
.where("nationality = 'IN'")
.where("age > 25") # multiple .where() → AND
.as_of("2025-01-01") # bi-temporal point-in-time
.with_provenance() # PROV-O columns
.order_by("name ASC")
.limit(20, after="cursor") # keyset pagination
.execute()
)| Method | Description |
|---|---|
.select(*cols) / .from_(table) | Projection / FROM |
.where(condition) | Append AND predicate (validated — SQL-injection stopgap) |
.as_of(timestamp) | Bi-temporal AS OF |
.with_provenance() | Append WITH PROVENANCE |
.purpose(p) | Override purpose for this query |
.limit(n[, after="cursor"]) | LIMIT, optional keyset pagination (LIMIT n AFTER 'cursor') |
.since(cursor) | Incremental reads (WHERE system_from > cursor) |
.offset(n) | OFFSET |
.order_by(*cols) | ORDER BY |
.paths_between(src, dst, max_hops) | PATHS_BETWEEN graph operator |
.match_face(probe, candidate, threshold) | MATCH_FACE biometric operator |
.lookup_identity(value) | LOOKUP_IDENTITY operator |
.hybrid_score(text) | HYBRID_SCORE ranking |
.raw(sql) | Raw SQL escape hatch |
.sql() | Return assembled SQL without executing |
.execute() / .aexecute() | Run sync / async |
v1.1 typed clients — from_client(client)
Each module inherits the parent client’s auth, tenant, and purpose context, so governance stays consistent across the surface. Every client has an async mirror (Async*).
| Module | Class | Surface |
|---|---|---|
relata.memory | Memory / AsyncMemory | Mem0-style add / search / forget over governed /memory/* |
relata.governance | GovernanceClient | Rules, retention (holds + WORM), breakglass, alerts, DSAR |
relata.mcp | McpClient | 22 typed MCP tool wrappers + generic call_tool |
relata.a2a | A2AClient | A2A tasks + LangGraph checkpoints + agent card |
relata.audit | AuditClient | Audit entries (filtered/paginated) + signed receipts + PDF export |
relata.identity | IdentityClient | Identity label/uncertainty + lookup tables + ERASE SUBJECT |
relata.objects | ObjectClient | Typed upsert + batch via /ingest?object_type= |
relata.ingest | IngestClient | Bulk NDJSON + CSV + media status |
relata.vectors | VectorClient | KNN + hybrid search + similar-to (SQL-backed) |
relata.s3 | S3Client | boto3 / httpx / aiobotocore wrapper for the S3 protocol door |
relata.system | SystemClient | LLM config + test + jobs status |
relata.streaming | StreamingClient | NDJSON row streams + SSE consumers (watch/alerts) + Arrow IPC |
relata.tenants | TenantAdminClient | Tenant CRUD + quota + sharing agreements + platform admin |
Example — typed client chain
from relata import RelataClient, McpClient, GovernanceClient
with RelataClient(url, bearer_token=token, purpose="compliance") as client:
mcp = McpClient.from_client(client)
gov = GovernanceClient.from_client(client)
profile = mcp.get_entity_profile("person-123", purpose="compliance")
holds = gov.list_legal_holds()
sigma = gov.import_sigma(open("rules.yml").read())Response models
| Model | Fields |
|---|---|
QueryResult | rows, query_id, elapsed_ms, row_count — iterable, truthy-when-non-empty |
HealthResponse | status, profile, node_id |
StatusResponse | profile, role, query_quota |
AuditCountResponse | entries, chain_valid |
ClusterNode | node_id, role, url |
VersionInfo | version, commit, profile, schema_version, features |
Stats | records, states, snapshot_rows, log_leaves, tokens |
ReadyReport | is_ready, status, reason, detail |
QueryResult is iterable, indexable, and truthy:
result = client.query("SELECT * FROM Person LIMIT 10")
for row in result: # iterate
print(row["name"])
first = result.rows[0] # index
if not result: # truthy when non-empty
print("empty")
print(f"{len(result)} rows in {result.elapsed_ms} ms")Error handling
from relata.exceptions import (
PurposeError, # missing or unregistered purpose (HTTP 400)
QuotaError, # per-principal cost quota exhausted (HTTP 429)
AuthError, # invalid or missing bearer token (HTTP 401)
ForbiddenError, # Cedar ACL denied (HTTP 403)
NotFoundError, # (HTTP 404)
ConflictError, # (HTTP 409)
ValidationError, # (HTTP 422)
RateLimitedError, # (HTTP 429 with retry_after)
ConnectionError, # server unreachable
ServerError, # 5xx
RelataError, # base class — catch-all
)
try:
result = client.query("SELECT * FROM Person")
except PurposeError as exc:
print(f"Fix: set purpose= on client or per query. {exc}")
except QuotaError as exc:
print(f"Quota exhausted — reduce query scope. {exc}")
except AuthError as exc:
print(f"Auth failed — check bearer_token. {exc}")
except ConnectionError as exc:
print(f"Cannot reach server. {exc}")Every error is RFC 7807 problem+json-aware: code, type_url, retryable, request_id are all exposed as attributes. The X-Request-ID header is auto-generated per request, and the server’s response ID is stamped on exceptions.
Async support
import asyncio
from relata import RelataClient
async def main():
async with RelataClient("http://localhost:9090", purpose="analytics") as client:
result = await client.aquery("SELECT * FROM Person LIMIT 5")
async for row in result:
print(row)
asyncio.run(main())Custom object types
RelataDB is ontology-governed — unknown types are rejected on both ingest and read (fail-closed). Register custom types at runtime:
client._sync.post("/types", {
"name": "AgentTask",
"fields": [
{"name": "task_id", "type": "string"},
{"name": "status", "type": "string"},
],
})
client._sync.post("/ingest?object_type=AgentTask",
{"task_id": "t-1", "status": "done"})For ACL access in strict mode, grant via env var: RELATA_ACL_GRANT=AgentTask:read+write.
Agent-framework adapters
Drop-in governed memory backends for the major agent frameworks. Each adapter wraps Memory, so purpose + ACL stay on. None imports its framework, so they load with or without it installed.
| Framework | Import | Shape |
|---|---|---|
| LangChain | relata_adapters.langchain.RelataMemory | BaseMemory |
| LlamaIndex | relata_adapters.llamaindex.RelataMemory | BaseMemory |
| CrewAI | relata_adapters.crewai.RelataStorage | Storage |
| AutoGen v0.2 | relata_adapters.autogen.RelataMemory | Memory (async) |
| AG2 (v0.4+) | relata_adapters.ag2.RelataAG2Memory | MemoryProtocol |
| Pydantic-AI | relata_adapters.pydantic_ai.RelataMemoryBackend | memory backend |
| smolagents | relata_adapters.smolagents.RelataTool | tool callable |
| LangGraph | relata_langgraph.RelataCheckpointer | checkpointer |
| Auto-detect | relata_adapters.registry.get_memory_adapter() | picks the right class |
from relata_adapters.langchain import RelataMemory
mem = RelataMemory(base_url="http://localhost:9090", purpose="agent")
# chain = ConversationChain(llm=..., memory=mem)Deployment profiles
| Profile | Use case | Start command |
|---|---|---|
lite | Embedded / single-process / dev | RELATA_PROFILE=lite relata serve |
server | Single-node production | RELATA_PROFILE=server relata serve |
cluster | Multi-node distributed (alpha) | RELATA_PROFILE=cluster relata serve |
The SDK works identically across all three.
Examples
The examples/ directory ships runnable workflows:
| File | Demonstrates |
|---|---|
basic_query.py | Getting started, health check, quota check |
analytics.py | Full analytics workflow: identity → cases → graph → provenance |
face_search.py | MATCH_FACE operator, threshold comparison, CCTV temporal search |
graph_traversal.py | PATHS_BETWEEN, temporal network shift, hub detection |
audit.py | Chain verification, audit summary, anomaly detection |
memory_quickstart.py | Memory add / search / forget in 20 lines |
License
AGPL-3.0-only. See the root LICENSE file.