Protocol Compatibility

RelataDB speaks 8 compatibility doors plus 5 native protocols (13 wire surfaces total) from one binary and one governed store. Any existing client library that speaks one of the compat doors works without the Relata SDK.

All compat doors bind to 127.0.0.1 and share one credential: RELATA_BEARER_TOKEN. Doors auto-enable when RELATA_BEARER_TOKEN is set (any tier); an explicit RELATA_<DOOR>_ENABLE=true|false overrides. With no token and no explicit enable, doors stay off by default (an unauthenticated port is never auto-exposed). pgwire refuses to start without a token. On non-free profiles, an explicit =true without a token fails closed, and RELATA_TENANCY_MODE=multi refuses the tenant-less shared-token doors (use pgwire, which carries per-connection org).

Cross-protocol consistency: write via any door and read the same data back over SQL, pgwire, or any other surface. ACL, org isolation, and the audit log apply on every door uniformly. See the cross-door data visibility table below for the exact type mapping.

Protocol matrix

DoorPort env var (default)ReadWriteStored as
S3 (AWS / boto3 / rclone / MinIO)RELATA_S3_PORT (9191)✅ GET / LIST / HEAD✅ PUT / DELETES3Object, S3Bucket
Postgres + pgvectorRELATA_PG_PORT (5433)✅ SELECT✅ INSERT / COPY / UPDATE / DELETEUser-defined type
ClickHouse HTTPRELATA_CLICKHOUSE_PORT (8123)✅ SELECTINSERT FORMAT JSONEachRowTable name = type
ClickHouse native TCPRELATA_CH_NATIVE_PORT (9000)✅ SELECTRead-only
Neo4j HTTP CypherRELATA_NEO4J_PORT (7474)✅ MATCH / RETURN✅ CREATE / MERGECypher label = type
Neo4j BoltRELATA_BOLT_PORT (7687)✅ MATCH / RETURN✅ CREATE / MERGECypher label = type
Redis RESPRELATA_REDIS_PORT (6379)✅ GET / KEYS✅ SET / DELKvEntry
MongoDB wireRELATA_MONGO_PORT (27017)✅ find / count✅ insert / update / deleteMongoDocument

Native Relata protocols (always on):

DoorDefault portNotes
HTTP REST9090/query, /ingest, /search, /memory/*, /mcp, /health, /status, /metrics, /types, /specs, /sparql, /watch/stream
gRPC50051gRPC door
Arrow Flight8815Zero-copy columnar streaming; enable with RELATA_FLIGHT_ENABLE=true
MCP/mcp on HTTPModel Context Protocol tools
SPARQL/sparql on HTTPSingle Basic Graph Pattern over KnowledgeTriple + optional LIMIT

Cross-door data visibility

All 13 wire surfaces read and write through one governed ObjectStore. Data written through any door is immediately queryable through any other door — the same rows, the same types, the same governance (ACL, provenance, audit, tenant isolation).

Two type patterns

User-defined types (HTTP, ClickHouse, Neo4j/Bolt, pgwire, Flight): you choose the type name. Ingest via ClickHouse → query via Neo4j → read via pgwire. Same type, same rows.

Door-specific types (S3, Redis, Mongo): the door maps to a fixed internal type. The data is in the store and queryable via SQL from any door — through the internal type name.

Type mapping table

Write viaStored as typeExample writeRead via another door
HTTP /ingestUser-defined (Person)POST /ingest?object_type=PersonSELECT * FROM Person via pgwire; MATCH (n:Person) via Neo4j
ClickHouse INSERTTable name = type (Person)INSERT INTO Person FORMAT JSONEachRowSELECT * FROM Person via HTTP; MATCH (n:Person) via Bolt
Neo4j/Bolt CREATECypher label = type (Person)CREATE (n:Person {id:'x'})SELECT * FROM Person via ClickHouse; COPY Person FROM STDIN via pgwire
pgwire COPY/INSERTUser-defined (Person)COPY Person FROM STDIN CSVSELECT * FROM Person via HTTP; HYBRID_SEARCH FROM Person
Arrow Flight do_putTable from Arrow schemaFlightClient.do_put("Person", batches)SELECT * FROM Person via any door
Redis SETKvEntrySET foo barSELECT value FROM KvEntry WHERE _pk = 'foo' via HTTP
Redis PUBLISHPubSubMessagePUBLISH ch helloSELECT message FROM PubSubMessage WHERE channel = 'ch' via HTTP
Mongo insertOneMongoDocumentdb.users.insertOne({name:'x'})SELECT * FROM MongoDocument WHERE collection = 'users' via HTTP
S3 PUTS3Object + S3Bucketaws s3 cp file s3://cases/keySELECT key, size FROM S3Object WHERE bucket = 'cases' via HTTP

Concrete cross-door examples

Ingest via ClickHouse, query via HTTP:

# Write through ClickHouse
curl -X POST http://127.0.0.1:8123/ -H "X-ClickHouse-Key: $TOKEN" \
  --data-binary "INSERT INTO Person FORMAT JSONEachRow" \
  --data-binary '{"id":"ch1","name":"from-clickhouse","company":"Acme"}'
 
# Read through HTTP SQL — same row, same type
curl -X POST http://127.0.0.1:9090/query -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sql":"SELECT name FROM Person WHERE company = '\''Acme'\''","purpose":"analytics"}'

Write via Neo4j Bolt, query via pgwire:

# Write through Bolt
from neo4j import GraphDatabase
d = GraphDatabase.driver("bolt://127.0.0.1:7687", auth=("neo4j", "$TOKEN"))
with d.session() as s:
    s.run("CREATE (n:Person {id:'neo1', name:'Alice'})")
# Read through psql — same row, same type
psql -h 127.0.0.1 -p 5433 -U relata -c "SELECT * FROM Person WHERE id = 'neo1'"

Ingest via HTTP, search via Neo4j graph:

# Ingest Person + CallEvent via HTTP
curl -X POST http://127.0.0.1:9090/ingest?object_type=CallEvent -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/x-ndjson" \
  -d '{"id":"c1","caller":"+14155550100","callee":"+14155550101"}'
 
# Query via HTTP PATHS_BETWEEN (graph operator over the same data)
curl -X POST http://127.0.0.1:9090/query -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sql":"PATHS_BETWEEN('\''+14155550100'\'','\''+14155550101'\'', MAX_HOPS => 3)","purpose":"analytics"}'

Write via Redis, read via HTTP SQL:

redis-cli -h 127.0.0.1 -p 6379 -a $TOKEN SET config:timeout "30"
SELECT value FROM KvEntry WHERE _pk = 'config:timeout';
-- → "30"

Start the doors

export RELATA_BEARER_TOKEN=<your-strong-token>
 
RELATA_S3_PORT=9191 \
RELATA_PG_PORT=5433 \
RELATA_CLICKHOUSE_PORT=8123 \
RELATA_NEO4J_PORT=7474 \
RELATA_REDIS_PORT=6379 \
RELATA_MONGO_PORT=27017 \
relata serve

A single end-to-end smoke test for all six compat doors: scripts/protocol_smoke_test.py.

S3 — boto3 / aws CLI / rclone / MinIO client

Supported operations: ListBuckets, CreateBucket / HeadBucket / DeleteBucket, ListObjectsV2, GetBucketLocation, PutObject / GetObject / DeleteObject / HeadObject, and multipart upload.

import boto3
from botocore.config import Config
 
s3 = boto3.client(
    "s3",
    endpoint_url="http://127.0.0.1:9191",
    aws_access_key_id="change-me",
    aws_secret_access_key="unused",
    config=Config(signature_version="s3v4", s3={"addressing_style": "path"}),
)
 
s3.create_bucket(Bucket="cases")
s3.put_object(Bucket="cases", Key="exhibit-1.txt", Body=b"hello world")
body = s3.get_object(Bucket="cases", Key="exhibit-1.txt")["Body"].read()
print(body)

When RELATA_S3_SECRET_KEY is set the door requires verified SigV4 and rejects plaintext bearer auth.

Read S3 objects over SQL

SELECT key, size, content_hash FROM S3Object WHERE bucket = 'cases';

Limits

  • Buckets must be empty to delete.
  • Multipart parts are in-memory only (lost on restart).
  • ETag is SHA-256.
  • Object bodies ≥ RELATA_S3_BLOB_THRESHOLD_MB (default 4 MiB) spill to the content-addressed blob store.

Postgres + pgvector — psql / psycopg2 / LangChain PGVector

psql -h 127.0.0.1 -p 5433 -U relata relata
# password = RELATA_BEARER_TOKEN
CREATE EXTENSION vector;
CREATE TABLE docs (id text PRIMARY KEY, embedding vector(3));
INSERT INTO docs VALUES ('a', '[1,0,0]'), ('b', '[0.9,0.1,0]');
 
-- Cosine KNN — auto-routed to Relata's HNSW index
SELECT id FROM docs ORDER BY embedding <=> '[0.9,0.1,0]' LIMIT 2;

INSERT / UPDATE / DELETE and ordinary SELECT work. GUI clients (TablePlus, DBeaver, pgAdmin, DataGrip) connect and browse schema via the catalog intercept.

KNN operatorMetricNote
&lt;=>cosine distancePreferred — ANN index is cosine-only
&lt;->L2 distanceMetric-correct via over-fetch + re-rank
&lt;#>negative inner productMetric-correct via over-fetch + re-rank

pgwire is fail-closed: refuses to start when RELATA_BEARER_TOKEN is unset.

ClickHouse — HTTP or native TCP

# HTTP
curl -X POST "http://127.0.0.1:8123/?query=SELECT+1" \
  -H "X-ClickHouse-Key: change-me"
 
curl -X POST http://127.0.0.1:8123/ \
  -H "X-ClickHouse-Key: change-me" \
  --data-binary "SELECT name FROM Person FORMAT JSONEachRow"
from clickhouse_driver import Client
 
ch = Client(host="127.0.0.1", port=9000, password="change-me")
rows = ch.execute("SELECT name FROM Person LIMIT 5")
print(rows)

Read + write. The door routes governed SELECTs through the planner and accepts INSERT INTO <table> FORMAT JSONEachRow writes that go through the same governed pipeline (ACL, ontology, WAL, provenance, audit) as HTTP /ingest.

# Write
curl -X POST "http://127.0.0.1:8123/" \
  -H "X-ClickHouse-Key: change-me" \
  --data-binary "INSERT INTO Person FORMAT JSONEachRow" \
  --data-binary '{"id":"ch1","name":"from-clickhouse"}'
 
# Read back over HTTP SQL (cross-door)
curl -X POST http://127.0.0.1:9090/query \
  -H "Authorization: Bearer change-me" \
  -H "Content-Type: application/json" \
  -d '{"sql":"SELECT * FROM Person WHERE id = '\''ch1'\''","purpose":"analytics"}'

Neo4j — HTTP Cypher or Bolt

# HTTP Cypher
curl -X POST http://neo4j:change-me@127.0.0.1:7474/db/neo4j/tx/commit \
  -H "Content-Type: application/json" \
  -d '{"statements":[{"statement":"MATCH (n) RETURN n LIMIT 5"}]}'
from neo4j import GraphDatabase
 
driver = GraphDatabase.driver("bolt://127.0.0.1:7687", auth=("neo4j", "change-me"))
with driver.session() as s:
    print(s.run("MATCH (n) RETURN n LIMIT 5").data())

Typed labels (n:Person) map to governed object types. Read and write Cypher supportedCREATE / MERGE go through the governed write door (run_protocol_cypher_write) with the same ACL, ontology validation, WAL, and audit as HTTP /ingest.

Cypher subset supported:

  • MATCH / OPTIONAL MATCH / WITH (read)
  • CREATE / MERGE (write — superseded by governed upsert)
  • Relationship path patterns including bounded -[r*1..5]->
  • Single-identifier RETURN [AS alias]
  • Whitelisted property predicates
# Write via Bolt, read back over SQL (cross-door)
from neo4j import GraphDatabase
driver = GraphDatabase.driver("bolt://127.0.0.1:7687", auth=("neo4j", "change-me"))
with driver.session() as s:
    s.run("CREATE (n:Person {id:'neo1', name:'from-cypher'})")
-- Read the same node over HTTP SQL
SELECT * FROM Person WHERE id = 'neo1';

Redis — any RESP client

redis-cli -h 127.0.0.1 -p 6379 -a change-me SET foo bar
redis-cli -h 127.0.0.1 -p 6379 -a change-me GET foo

Governed keys persist as KvEntry rows. Read back over SQL:

SELECT key, value FROM KvEntry WHERE key = 'foo';

Not supported: MULTI/EXEC, BLPOP, scripting, cluster commands. Pub/Sub (PUBLISH / SUBSCRIBE) persists messages as governed PubSubMessage rows so subscribers can replay on reconnect.

MongoDB — any Mongo wire client

const { MongoClient } = require("mongodb");
const c = new MongoClient("mongodb://localhost:27017", {
  auth: { username: "relata", password: "change-me" },
});
const db = c.db("cases");
await db.collection("exhibits").insertOne({ _id: "ex1", body: "hello" });
console.log(await db.collection("exhibits").findOne({ _id: "ex1" }));

Governed docs persist as MongoDocument rows. Read back over SQL:

SELECT * FROM MongoDocument WHERE collection = 'exhibits';
LimitDetail
AuthSCRAM-SHA-256 only
maxWireVersion17
Transactions / change streamsNot supported
$push / $pull / $unsetNot supported
Nested equalityVia flattened columns only

Arrow Flight — zero-copy streaming

# Enable on the server
RELATA_FLIGHT_ENABLE=true relata serve

Connect any Arrow Flight client (Python pyarrow.flight, etc.) to grpc://localhost:8815:

import pyarrow.flight as fl
client = fl.connect("grpc://localhost:8815")
reader = client.do_get(fl.FlightDescriptor.for_command(
    b"PURPOSE 'analytics' SELECT * FROM Person"))
for batch in reader:
    print(batch.data.num_rows, "rows")

Arrow IPC format — no JSON intermediate, no string serialisation. Use for high-throughput columnar reads.

SPARQL

# GET
curl "http://localhost:9090/sparql?query=SELECT+%3Fs+%3Fp+%3Fo+WHERE+%7B+%3Fs+%3Fp+%3Fo+%7D+LIMIT+10" \
  -H "Authorization: Bearer $RELATA_BEARER_TOKEN"
 
# POST
curl -X POST http://localhost:9090/sparql \
  -H "Content-Type: application/sparql-query" \
  -H "Authorization: Bearer $RELATA_BEARER_TOKEN" \
  -d "SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 10"

Single Basic Graph Pattern over KnowledgeTriple. Optional LIMIT. Both GET and POST verify bearer auth.

Security notes

  • All doors bind to 127.0.0.1.
  • All doors share RELATA_BEARER_TOKEN.
  • pgwire is fail-closed (refuses to start without a token).
  • All other doors default to open dev mode when the token is unset.
  • Egress filtering applies uniformly on every door.
  • Cross-protocol reads and writes go through the same planner with the same ACL, org isolation, and audit chain.

See also