The zero-dependency, single-binary system of record, private AI runtime, and API/LLM gateway — install it, query it, wire it to your AI and your tools, run it in production. Every snippet below is copyable, and every section is filterable from the sidebar; track 6 covers the project itself — releases, CI, roadmap and contributing. Where Centauri does not do something, the docs say so.
Everything below ships inside the single centauri executable — there is nothing separate to download. Pick a tab to see how that piece works, how to start it, and (where it competes with something you know) an honest comparison.
The engine — a system of record that can prove its history
Every fact is stored once with two clocks (when it became true, when you learned it), a cause, a source and a confidence. Nothing is updated or deleted: a change is a new superseding fact, and a SHA-256 chain seals every record so centauri verify re-proves the whole history. That one data model does the jobs of a document store, a vector database, a search engine, a graph and an audit ledger.
centauri serve -data my.log -token secret
centauri serve -data my.log -pg-addr :5432 # + Postgres wire for BI tools
Centauri is the flight recorder beside your operational database, not a Postgres replacement. Coming from Oracle? Centauri internals vs Oracle →
Studio, Console, App, Dashboard and the Book
Five browser workspaces are embedded in the binary with go:embed, so a server is its own front end. Centauri Studio (/studio) is the AI-first IDE: object explorer, causal-graph view, edit-as-a-fact, SQL auto-routing and the ✨ Copilot. The Console (/console) is the suite home where you mint gateway keys and watch usage. The App (/app) is the plain-English Ask / Add / Recent screen for non-technical users. The Dashboard (/) holds the query bar, Genesis, the AI panel and Vision upload; the CeQL Book (/ceql) is the textbook. centauri desktop opens the Console in a native-feeling app window (Edge or Chrome --app= mode) and falls back to your browser.
centauri desktop # opens the Console app window; manages Ollama
open-studio.bat # Windows: jump straight to /studio
http://localhost:7771/studio http://localhost:7771/console http://localhost:7771/app
Centauri Local AI — your own private AI, on your hardware
centauri desktop detects your hardware, installs Ollama if needed, pulls a model trio sized to your machine (chat, embeddings, vision), registers them as facts and turns on auto-embed. From then on every fact embeds itself; SEARCH ranks by meaning as well as keywords; ASK answers in plain English with citations to the source facts; ENRICH asset:* USING vision reads images and PDFs into searchable facts. Nothing leaves the machine and there is no per-token bill. A cloud boost (GLM-5.2, GPT-5.5, Claude) exists — off by default and clearly labeled, because with it your questions leave the machine.
centauri desktop # zero-step: detect, install, pull, register
centauri setup vision -install # add PDF rendering + vision models
centauri serve -ai auto # headless server with the appliance
Centauri Gateway — the API & LLM gateway whose config is a database
The product in Kong's category. centauri gateway is a reverse proxy whose routes, API keys and per-request audit are bi-temporal facts in a Centauri log: config time-travels (AS OF 'last tuesday 9am'), a key is revoked with RETIRE and its history stays, and the request audit is sealed in the hash chain so centauri verify proves nothing was edited. Auth by API key, OIDC JWT or mTLS; a built-in client-credentials token issuer; per-route / per-key / per-IP rate limits; programmable CePL request policies and webhook callouts; OpenAI-compatible LLM routes with SSE streaming, provider failover and per-key token metering.
centauri gateway -data gateway.log -addr :8080 -admin :7771
PUT route:api FACET config SET prefix='/api/', upstream='http://127.0.0.1:9000', auth='key', rate_per_min=120, audit='meta'
FACTS OF route:api FACET config AS OF 'last tuesday 9am'
Centauri Gateway vs Kong, Apigee, AWS API Gateway & Tyk
Centauri Gateway
Kong (OSS + Konnect)
Google Apigee
AWS API Gateway
Tyk
Config model
bi-temporal factsRETIRE, never delete
YAML / Postgres / KonnectGitOps versioning
proxies with revisions
console / CloudFormationstages + deployments
files, Redis or dashboard
Config time travel
✓ one AS OF query
✗ reconstruct from git
~ revision rollback
~ redeploy old stage
✗
Request audit trail
tamper-evidenthash chain, verify; metadata only
~ plugin logging, not verifiable
✓ managed analytics + audit logs
~ CloudWatch + CloudTrail + Athena
~ Tyk Pump
AI / LLM routing
✓ SSE, failover, pinning, token metering as facts
✓ AI-gateway plugin suite
~
~ assemble with Bedrock/Lambda
~
Authentication
~ keys, OIDC JWT, mTLS, client-credentials issuer; no human login flows
✓ full OAuth2/OIDC suites
✓ built-in OAuth2 server
✓ IAM, Cognito, authorizers
✓
Rate limiting
✓ route / key / IP
✓ sliding window, clustered
✓ quotas, spike arrest
✓ usage plans
✓
Middleware / plugins
~ built-ins + CePL policies + webhooks; no marketplace
✓ hundreds, custom in Lua/Go/JS/Py
✓ rich policy catalog
~ mapping templates + Lambda
✓
Kubernetes ingress / mesh
✗ runs in k8s as a plain deployment
✓ Ingress Controller + Kuma
~
~
✓ Tyk Operator
Throughput class
~ Go stdlib proxy; ~26k req/s on 2 cores; not for six-figure RPS
✓ NGINX/OpenResty
✓ Google scale
✓ serverless scale
✓ Go, published benchmarks
Deployment weight
one binaryconfig + audit in one file
~ data + control plane, Postgres or DB-less
~ managed SaaS
✓ fully managed
~ gateway + Redis
Scale-out / HA
~ N read-only followers of one control plane; no multi-DC
✓ clustering, hybrid, multi-region
✓
✓
✓ multi-DC in paid tiers
Software cost
$0 foreverMIT License
≈$1,260+/yr per service (Plus)enterprise ≈$30–50k+/yr
≈$4,380+/yr per env/region
$0 base≈$1–3.50 per 1M calls
OSS gateway free; paid dashboard/tiers
Honest summary: Kong wins on raw throughput, plugin ecosystem, Kubernetes ingress and OAuth2 IdP flows; Apigee on monetization and analytics; AWS is cheapest per call at scale, but AWS-locked. Centauri's edge is config time travel, a provable audit, LLM metering as queryable facts, and $0. Pricing is indicative mid-2026 list pricing — verify with each vendor.
MCP server, SDKs and connectors — get in and out without lock-in
AI agents are first-class clients: centauri mcp exposes 20 typed tools (plus one per stored procedure) over the Model Context Protocol, so Claude Desktop, Claude Code, Cursor or any MCP host query, write and trace lineage with no SQL strings. Applications use the zero-dependency Python, Go and JavaScript SDKs over the REST/JSON API; pipelines use the Airbyte source and destination or raw CDC at /v1/changes; BI tools use the Postgres wire. Everything is the same API underneath.
Deploy and scale — a laptop, a $5 VPS, Docker, Kubernetes or Render
Production is the same binary with a token and HTTPS in front. Scale reads with centauri follow replicas; scale writes with serve -shards N; add automatic failover with serve -ha-lease; keep history beyond RAM with sealed segments, the lazy index and an S3-compatible cold tier. One click deploys to Render; infra/k8s.yaml deploys to Kubernetes; gateways scale out with -control-follow.
Nothing matches that filter. Try a shorter term — or open the CeQL Book for the full language reference.
Track 1
Getting started & installation
One static binary, no config files, no cluster, no DBA. Pick an install path, start it, and you have the engine, the dashboards, the CeQL textbook, the REST API and the MCP server. Default HTTP port is 7771; the gateway's data plane defaults to :8080; data is one file (centauri.log, or ~/.config/Centauri/ / %APPDATA%\Centauri for centauri desktop).
install.sh detects Linux or Darwin and amd64 or arm64, downloads the matching release binary from GitHub Releases, and moves it to /usr/local/bin/centauri (using sudo only if that directory isn't writable). It fetches one static file and nothing else goes through a shell. Pin a release with CENTAURI_VERSION.
curl -fsSL https://centauridb.com/install.sh | sh
# pin a version
CENTAURI_VERSION=v0.3.0 curl -fsSL https://centauridb.com/install.sh | sh
# then
centauri desktop
Manual fallback: download centauri-linux-amd64, centauri-linux-arm64, centauri-darwin-amd64 or centauri-darwin-arm64 from the latest release and chmod +x it.
centauri-windows-setup.exe (built from installer/centauri.iss) puts centauri.exe and the .bat launchers in C:\Program Files\Centauri, adds a Start Menu entry that runs centauri desktop, and an optional desktop icon. Your facts live in %APPDATA%\Centauri, so uninstalling never touches data. Prefer portable? Unzip centauri-windows-amd64.zip anywhere and double-click run-centauri.bat.
On first run you're asked Set up your private local AI now? [Y/n]. Press Enter and Centauri installs Ollama if needed and downloads the model trio once; when it's done you'll see ai: your private AI is ready — ASK and SEARCH now run on local models, nothing leaves this machine.
The Dockerfile is a two-stage build (golang:1.22-alpine → alpine:3.20) with the binary at /usr/local/bin/centauri, VOLUME /data, port 7771, and ENTRYPOINT ["centauri"], so any subcommand works as the command. The admin token comes from CENTAURI_TOKEN.
Go 1.22+ and nothing else: go.mod has an empty require block and the project treats that as a feature. centauri seed fills a log with synthetic retail price-change data (-skus, -stores, -changes) so there's something to query immediately.
go build -o centauri ./cmd/centauri
./centauri seed && ./centauri serve
# the same checks CI runs (CLAUDE.md)
go vet ./... && go test ./...
cd sdk/python && python -m unittest discover -s tests
Launch modes — desktop, serve, gateway and the launchers#
centauri desktop is the one-click experience: data in your profile, opens the Console in an app window or your browser, manages Ollama, AI on by default. centauri serve is headless (AI is opt-in with -ai auto). centauri gateway is the reverse proxy. Windows ships the same modes as double-click launchers; all assume port 7771 and %APPDATA%\Centauri\centauri.log (the gateway uses gateway.log on 8080).
Launcher
What it does
run-centauri.bat
start Centauri (desktop) + open the app; args seed, build, stop, vision
start-all.bat
everything: database + dashboard + Studio + local AI setup + browser
start-db.bat
the database only (no AI): dashboard, Studio, CeQL book, full API
start-ai.bat
switch the local AI on for a running engine via POST /v1/ai/enable {"tier":"auto"}
start-gateway.bat
the API gateway on :8080 (proxy + dashboard + Console on one port); key <name> mints a key and exits
usage: centauri <command> [flags]
Commands
desktop one-click start: data in your profile, the Centauri home opens in an app window (or your browser)
serve HTTP/JSON API — writes, queries, SSE watch, log shipping
gateway reverse proxy: routes/keys/audit live in a Centauri store (bi-temporal)
mcp Model Context Protocol server on stdio (connect AI agents)
shell interactive CeQL REPL (psql-style; \h for meta commands)
seed populate with synthetic price-change demo data
demo seed|clear a curated multi-domain example database (demo.log)
setup setup vision [-install] — ready a local AI (Ollama + PDF renderer)
follow replicate a primary's log into a read-only follower
sync bidirectional, echo-safe sync with a peer (-primary <url>); run on both
verify recompute the tamper-evidence hash chain over a log file
doctor read-only health check (chain, checkpoint, lock); safe on a live DB
backup copy a database to -to <file> and verify the copy's chain
archive seal a log into compressed, tamper-evident segments: -to <dir>
seal roll an archive's tail into a new segment: -data <archive-dir>
merge reconcile diverged copies: merge -to merged.log a.log b.log
export run a CeQL query and print/write results in a chosen format
Common flags
-data <path> log file to use (default centauri.log)
-addr <:port> listen address (serve) (default :7771)
-token <tok> HTTP API bearer token (or set $CENTAURI_TOKEN)
-lazy keep payloads on disk so data can exceed RAM (serve/desktop)
-ollama desktop: auto-start a local Ollama & stop it on exit (default on)
-format <fmt> table | csv | jsonl | json | yaml | plain (export)
-to <file> output file ('-' or omitted = stdout) (export/backup/merge)
Also present (not in the banner): retention, compact, archive-push, tablespace-demo. Notable serve flags: -read-token, -lazy-index, -tls-cert/-tls-key, -max-concurrency, -query-timeout, -max-concurrency-per-db, -log-format/-log-level, -group-commit, -shards, -checkpoint-every, -auto-seal-mb, -s3-endpoint/-s3-bucket/-s3-region/-s3-prefix, -oidc-issuer/-oidc-audience/-oidc-jwks/-oidc-write-scope, -pg-addr, -ha-lease/-ha-id/-ha-addr/-ha-ttl, -ai/-ai-yes. Each is explained in the track that uses it.
Writes go to POST /v1/append with an events array; each event names a subject, a facet, a type (OBSERVED, CORRECTION…), a provenance (SYSTEM_FEED, HUMAN_ENTRY, SCAN_VERIFIED, AI_INFERRED), a confidence and a value. Reads are plain GETs; /v1/query runs CeQL as ?q= or a JSON body. With -token set, add Authorization: Bearer to every call.
centauri shell is a psql-style CeQL REPL (it takes the writer lock so it won't race a running server): \d lists subjects; \schemas, \stats, \slots, \timing, \x, \format are the meta commands; \q quits. centauri export -q "…" prints a query as table / csv / jsonl / json / yaml / plain. centauri demo seed builds a curated multi-domain demo.log; demo clear removes it — the only "erase" in the product, and it only touches that disposable file.
Every server process embeds its UIs with go:embed — nothing to deploy separately. The dashboard has the 🏗 Build my database button (Genesis), the AI panel, 📎 Vision upload, the ✨ plain-English helper and an honesty table. Studio is the AI-first IDE (object explorer, causal-graph view, edit-as-a-fact; SQL auto-routed). The Console is the suite home where you mint gateway API keys in the browser. In serve -lazy-index mode the root path serves the Tablespace Console instead: storage inspector, one-click Verify, query console, cache metrics.
http://localhost:7771/ dashboard (Genesis, AI panel, Vision, query bar)
http://localhost:7771/ceql the CeQL Book — the canonical language reference
http://localhost:7771/studio Centauri Studio (AI-first IDE)
http://localhost:7771/app simple app: Ask / Add / Recent
http://localhost:7771/console the Console: whole suite on one screen; mint API keys
http://localhost:7771/ Tablespace Console — when started with serve -lazy-index
Open paths that need no token: the pages above, GET /v1/version, /livez, /readyz, /metrics, and (with -ha-lease) GET /v1/ha.
CeQL is the native language: time, cause and trust are syntax. A read-only SQL SELECT subset and a PostgreSQL wire listener exist for people and tools that think in SQL; a plain-English helper exists for everyone else. The full textbook is the CeQL Book — this track is the working reference.
No tables, rows or documents. A subject is anything facts can be about (item:100001/store:4001, toy:robot), created the first time a fact mentions it. A fact is an immutable statement with two clocks (effective time — when it became true; recorded time — when the database learned it), a provenance, a confidence and causal links. A facet is which system's view the fact belongs to (source, register, pdt, shelf). A new fact on the same subject + facet supersedes the old one, which is kept forever: current state is just the newest layer of history. Name subjects like paths so wildcards behave like directories.
FACTS OF toy:robot -- what's true now
PUT toy:robot SET price_cents=500, color='silver'
HISTORY OF toy:robot -- the whole story
Patterns use *. List fields to narrow output, including the built-in rank column for stable row numbers when paging. WHERE sees your value fields and the metadata (subject, facet, type, provenance, namespace, trust, effective, recorded, pending, superseded). Operators: = != > >= < <= IN LIKE MATCHES EXISTS AND OR NOT ( ); dotted paths reach nested values. String-equality filters use an automatic per-field secondary index; EXPLAIN shows when it's probed.
FACTS [fields] OF subject-pattern
[FACET f] [AS OF t] [AS KNOWN AT t] [WHERE expr]
[GROUP BY key] [ORDER BY key [DESC]] [LIMIT n] [OFFSET n]
[WHY [DEPTH n]]
FACTS subject, price_cents, trust OF item:*/store:4001 ORDER BY price_cents DESC LIMIT 10
FACTS rank, subject, price_cents OF item:*/store:4001 ORDER BY price_cents DESC LIMIT 10 OFFSET 10
FACTS OF item:*/store:4001 WHERE trust >= 0.8 AND price_cents > 700
FACTS OF acme:* WHERE namespace = 'acme'
Insert and update are the same act: PUT appends a fact that supersedes the prior one. CORRECT is the same but typed CORRECTION. RETIRE supersedes with {retired: true} — there is no DELETE, on purpose. PUT accepts FACET, TYPE, EFFECTIVE (future- or back-dated), CONFIDENCE, SCHEMA (validated write), PROVENANCE and REF (lineage). CeQL SET takes scalars only; nested arrays/maps go through POST /v1/append or an SDK. Writes need the admin token, an OIDC token with the write scope, or a scoped token granted write.
PUT toy:robot SET price_cents=450, color='red'
PUT toy:robot SET price_cents=400 EFFECTIVE '2026-07-01' -- future-dated
CORRECT toy:robot SET price_cents=445 -- typed CORRECTION
RETIRE toy:robot -- supersedes with {retired: true}
RETIRE route:api FACET config -- retire one facet
Two clauses because every fact has two clocks. AS OF t asks what was true at t. AS KNOWN AT t asks what the database believed at t — facts learned later are invisible even if they describe earlier times. Both together is the audit query: "on March 1, what did we think March 15 would be?" Times are forgiving: YESTERDAY, TODAY, NOW-7d, 10 DAYS AGO, 'yesterday 2pm CST', ISO dates, RFC3339, or raw UnixMicro. Aggregation, SEARCH, CONTEXT, PROFILE and the topology operators all accept these clauses.
FACTS OF item:100001/store:4001 AS OF '2026-03-15' AS KNOWN AT '2026-03-01'
FACTS OF toy:robot AS OF YESTERDAY
FACTS OF toy:robot AS OF NOW-7d
FACTS OF toy:robot AS OF 'yesterday 2pm CST'
CONTEXT FOR toy:robot AS KNOWN AT '2026-03-01' -- decision replay
FACTS region, SUM(units) OF sku:* GROUP BY region AS KNOWN AT '2026-03-01'
Causes are first-class links. Add WHY to any FACTS query and each result carries its causal chain; WHY <event-id> DEPTH n traces one event backwards, EFFECTS <event-id> forwards. MATCH finds every causal path between two subject patterns, walking outbound (CAUSES) or inbound (CAUSED BY), optionally restricted by VIA to one link type (TRIGGERED, SUPERSEDES, CORRECTS, DISTRIBUTED_AS, ACTIVATED_BY, ENRICHED_FROM). The HTTP equivalent of a trace is GET /v1/trace?event=<id>&direction=cause|effect.
FACTS OF toy:robot WHY DEPTH 3
WHY 0193fa2e-77c1… DEPTH 6 -- what led to this event
EFFECTS 0193fa2e-77c1… -- what this event led to
MATCH item:* CAUSES register:* VIA TRIGGERED DEPTH 3
MATCH item:100001/store:4001 CAUSED BY * VIA CORRECTS
MATCH intent:* CAUSES pdt:* DEPTH 5 LIMIT 50
Facets let different systems hold different beliefs about one subject, and reconciliation becomes a query. DISAGREE ON <field> lists subjects whose facets disagree, with a trust-ranked suggested winner. PENDING <facet> OLDER THAN n DAYS finds facts that were distributed but never activated ("wedges"); POST /v1/activate closes one. CONTEXT FOR <subject> returns in one call what an agent needs to reason: current facts, recent history, causal chains, disagreements with a suggested resolution, pending wedges, enrichments, schemas and a confidence summary — rebuildable as of any past moment with AS KNOWN AT.
DISAGREE ON price_cents
PENDING pdt OLDER THAN 21 DAYS
CONTEXT FOR toy:robot
CONTEXT FOR toy:robot AS KNOWN AT '2026-03-01'
Because nothing is erased, a rollback appends superseding reversion facts that restore each subject to a chosen past point and records the rollback itself as an auditable event. AS OF still sees the pre-rollback world, HISTORY grows rather than shrinks, and a rollback is itself reversible. SNAPSHOT 'name' is a durable fact (it survives the session, unlike SQL SAVEPOINT); DIFF previews the delta first.
SNAPSHOT 'before-import'
DIFF OF toy:* BETWEEN '2026-03-01' AND '2026-03-15'
ROLLBACK -- undo the last commit
ROLLBACK TO SNAPSHOT 'before-import'
ROLLBACK OF toy:* TO '2026-03-15'
Honest scope: there is no interactive multi-statement BEGIN…COMMIT MVCC transaction. Writes are single-fact or batch appends; a /v1/append with several events is atomic within one log.
Aggregates run over current facts and respect time travel: COUNT(*) COUNT(f) COUNT(DISTINCT f) APPROX_COUNT_DISTINCT(f) SUM AVG MIN MAX MEDIAN STDDEV LISTAGG. COUNT(DISTINCT) is exact; APPROX_COUNT_DISTINCT uses HyperLogLog (internal/ceql/hll.go) with a fixed ~16 KB of memory and ≈0.8% error regardless of cardinality. MEDIAN, STDDEV (population) and LISTAGG carry the Oracle names.
FACTS facet, COUNT(*), AVG(price_cents), MAX(price_cents)
OF item:*/store:4001 GROUP BY facet
HAVING COUNT(*) > 5 AND AVG(price_cents) > 300
FACTS APPROX_COUNT_DISTINCT(region) OF item:*
FACTS MEDIAN(price_cents) OF item:*/store:4001
FACTS facet, LISTAGG(kind) OF item:*/store:4001 GROUP BY facet
FACTS facet, AVG(price_cents) OF item:* AS KNOWN AT NOW-2d GROUP BY facet
Honest limit: aggregates run over current facts, not history. To roll up HISTORY results, export and sum client-side.
Optional, versioned and append-only: DEFINE SCHEMA creates a new version; facts written under v1 stay valid against v1. Types are number string bool any; constraints REQUIRED MIN MAX UNIT; a field can carry a description. Write with SCHEMA <id> to validate — a rejection names the bad field. Over HTTP: POST /v1/schema, GET /v1/schema.
DEFINE SCHEMA price (
price_cents number REQUIRED MIN 1 UNIT 'cents',
kind string
) TITLE 'A retail price'
PUT toy:car SET price_cents=300 SCHEMA price -- validated
SCHEMAS -- list all (latest versions)
SCHEMA price -- every version of one
Not today: referential field types (parent ref(item)) and foreign-key constraints are roadmap. The recommended integrity pattern is a CePL procedure gateway — see modeling hierarchies.
A WATCH never finishes: it streams every matching fact the moment it commits. In the dashboard it feeds the LIVE panel; over HTTP it is the GET /v1/watch SSE stream (filterable by facet and type); in the Python SDK it's for e in db.watch(...). Streaming endpoints (/v1/watch, /v1/changes, /v1/log) are exempt from admission limits.
WATCH ALL
WATCH toy:robot
WATCH ALL FACET pdt TYPE DISTRIBUTED
SEARCH '<text>' OF <pattern> ranks by Okapi BM25 computed in a single pass in pure Go (internal/ceql/bm25.go) — bi-temporal like everything else. It blends with vectors two ways: SIMILAR TO <event> ALPHA a mixes BM25 with embedding similarity to a reference event (ALPHA 1.0 = pure keyword, 0.0 = pure semantic, default 0.5); and with a local embedder registered, SEARCH embeds the query text itself and blends meaning into the ranking. The final order folds in recency, trust (confidence × provenance) and causal centrality, and every hit returns its signal breakdown. SIMILAR TO <event-id> TOP k MIN score is pure vector search. Without an embedder SEARCH is plain BM25 and never errors. SEARCH also indexes AI-derived text (vision descriptions, summaries).
SEARCH 'late markdown' OF item:* LIMIT 10
SEARCH 'markdown' OF item:* SIMILAR TO 0193fa2e-77c1 ALPHA 0.5
SEARCH 'penny' OF item:* AS KNOWN AT '2026-03-01'
SIMILAR TO 0193fa2e-77c1… TOP 5 MIN 0.7
Four stdlib-only operators (internal/ceql/topology.go) answer questions about the shape of data. SHAPE OF <pattern> ON <fields> computes persistent homology of a value cloud (Betti-0 clusters, Betti-1 loops, Betti-2 voids with MAXDIM 2); WINDOW n STRIDE m builds a time-delay embedding so a periodic signal becomes a loop; ON EMBEDDING METRIC cosine analyses stored vectors. CONSISTENCY OF <subject> ON <field> [EPS n] treats the facets observing a subject as a sheaf: a global section exists iff they agree. CYCLES finds H1 of the causal graph — lineage must be acyclic, so any cycle is a data-integrity alarm. DRIFT … BUCKETS n is a regime-change detector. All accept AS OF / AS KNOWN AT.
SHAPE OF item:*/store:4001 ON price_cents
SHAPE OF item:* ON price_cents, trust, av_cost MAXDIM 2
SHAPE OF item:100001/store:4001 ON price_cents WINDOW 12 STRIDE 2
SHAPE OF item:* ON EMBEDDING METRIC cosine
CONSISTENCY OF item:100001/store:4001 ON price_cents EPS 5
CYCLES IN CAUSES OF item:100001/store:4001
DRIFT OF item:*/store:4001 ON price_cents BUCKETS 6
Nodes are subjects and parent pointers are ordinary facts, so a hierarchy is bi-temporal: a reclass is one PUT, and January reports re-run under January's hierarchy. Denormalize ancestry onto leaf facts (dept, class, region) so roll-ups are one GROUP BY that time-travels. Facets carry each system's belief about a leaf, so DISAGREE and PENDING replace reconciliation batches. Integrity is schemas plus CePL procedure gateways: applications call procedures; only admins use raw PUT. Full write-up: docs/modeling-hierarchies.md.
PUT class:3204 SET name='Slim Fit', parent='dept:320'
PUT sku:100001-RED-32/store:4001 FACET source
SET units=12, cost_cents=900, price_cents=1999,
dept='dept:320', class='class:3204', region='region:4'
SCHEMA sku_loc_status
FACTS dept, SUM(units), AVG(price_cents) OF sku:* GROUP BY dept
FACTS OF class:3204 AS OF '2026-01-15' -- which dept was it in, in January?
PROFILE OF <pattern> describes the data (fields, types, coverage, ranges, top values) as of any moment. SUBJECTS LIKE … LIMIT n lists subjects; STATS returns store counters. EXPLAIN <query> returns the access path plus the JSON AST without running; EXPLAIN ANALYZE runs it and reports rows and timing (reads only). Agents can submit the JSON AST directly to /v1/query — also the escape hatch for strings that can't be embedded in CeQL literals, which have no escapes.
PROFILE OF item:*
PROFILE OF item:* AS KNOWN AT '2026-03-01'
SUBJECTS LIKE item:*4001 LIMIT 50
STATS
EXPLAIN FACTS OF toy:robot WHY
EXPLAIN ANALYZE FACTS OF item:*
For people and LLMs who think in SELECT, a lean SQL subset (internal/ceql/sql.go) transpiles to CeQL and runs through the same executor (GET /v1/sql?q= or POST /v1/sql {"q"}; Studio auto-routes SQL). Supported: SELECT * | col, AGG(col) with COUNT SUM AVG MIN MAX; FROM <name> maps to namespace <name>:*, FROM facts to all subjects, a quoted FROM 'item:1' to an exact subject or pattern; WHERE with = != <> < <= > >= IN LIKE AND OR NOT (); GROUP BY, HAVING, ORDER BY, LIMIT n [OFFSET m]; time travel with AS OF / AS KNOWN AT, with SQL:2011 FOR SYSTEM_TIME AS OF as a synonym. It rejects what it doesn't support rather than mis-answering.
SELECT * FROM sku WHERE category = 'beverage' AND price_cents > 1000 ORDER BY price_cents DESC LIMIT 5
SELECT category, AVG(price_cents) FROM facts GROUP BY category HAVING COUNT(*) > 3
SELECT * FROM 'item:1' FOR SYSTEM_TIME AS OF 'yesterday'
curl "localhost:7771/v1/sql?q=SELECT%20*%20FROM%20sku%20LIMIT%2010"
curl -X POST localhost:7771/v1/sql -H 'content-type: application/json' \
-d '{"q": "SELECT category, COUNT(*) FROM facts GROUP BY category"}'
What SQL is NOT here: read-only — only SELECT; writes use CeQL. No joins (in SQL or CeQL), no subqueries, no window functions, no SQL COUNT(DISTINCT) yet.
Postgres wire protocol: psql, JDBC, DBeaver, BI tools#
Start serve with -pg-addr :5432 and Centauri also speaks the PostgreSQL frontend/backend protocol v3 (internal/pgwire, stdlib only), so psql, JDBC/ODBC clients and BI tools connect directly and run the same read-only SELECT subset. Both the simple and the extended (Parse/Bind/Describe/Execute) sub-protocols are implemented, so prepared statements with parameters work in JDBC and psycopg default modes.
-- inside psql
SELECT * FROM sku WHERE category='beverage' LIMIT 10;
SELECT * FROM 'item:1' FOR SYSTEM_TIME AS OF 'yesterday';
Honest scope: every column is returned as TEXT (OID 25); authentication is the server token sent as a cleartext password with any username and sslmode=disable — put TLS or network controls in front; no ?db= routing (the default database is served); writes are rejected. It is a protocol adapter over the transpiler, not a Postgres.
The dashboard query bar accepts plain English and the ✨ helper translates it (internal/ceql/natural.go): deterministic rules first — no model, no API key, shows you the CeQL — then the local LLM for the long tail when one is registered. Model output is re-parsed before it's offered, so a suggestion is never invalid CeQL. When a query fails to parse the same helper offers a "did you mean".
curl -X POST localhost:7771/v1/assist -H 'content-type: application/json' \
-d '{"text": "what was the price of item:100001/store:4001 yesterday at 2pm CST"}'
Beyond the admin (-token) and read-only (-read-token) tokens, POST /v1/acl registers a token confined to subject prefixes (only its SHA-256 is stored, as an acl:<hash> fact). It may run CeQL only over subjects within its prefixes, only via /v1/query, and may write only if granted; mask lists value fields redacted ("***") in that token's results — the store is never mutated. Deny-by-default for anything broader. A CePL procedure plus a scoped token is the safe surface for an agent.
The engine embeds no model and stays zero-dependency. AI features orchestrate models you run yourself — through Ollama over plain HTTP — and every feature degrades gracefully when no model is registered. Model choice is itself a fact: versioned, queryable, auditable.
How local AI works — and what is not in the binary#
Orchestration — retrieval, hybrid BM25+vector ranking, RAG answering, enrichment, model selection — lives in stdlib Go (internal/ai). Model weights (2–20 GB) do not. Ollama is the default runtime (OpenAI-compatible; LocalAI and vLLM also work). A model is registered as a plain fact (model:<name>, facet config). With no suitable model registered, the non-LLM path answers and queries never break.
PUT model:chat FACET config SET endpoint='http://localhost:11434/v1/chat/completions',
kind='chat', model='qwen3:14b'
-ai auto (default for desktop; opt-in on serve) detects total memory and picks a tier; small | balanced | max force one. Each tier is a chat + embedder + vision trio pulled via Ollama. The table is config, not a contract (internal/ai/presets.go).
Tier
Hardware
Chat
Embeddings
Vision
small
~8 GB RAM, CPU-only OK
gemma3:4b
nomic-embed-text
gemma3:4b
balanced
~12–16 GB GPU
qwen3:14b
bge-m3
gemma3:12b
max
24 GB+ GPU
glm-4.7-flash
bge-m3
gemma3:27b
glm-4.7-flash is a 30B MoE / 3B-active, MIT-licensed model with a 200K context, ~19 GB at Q4 — the strongest open model that fits a 24 GB GPU.
centauri desktop # turnkey: auto-detect, auto-install, auto-pull
centauri serve -ai auto # server: detect hardware + provision
centauri serve -ai small # force a tier: small | balanced | max
centauri serve -ai auto -ai-yes # skip the one-time consent prompt (unattended)
Ollama provisioning — what happens in the background#
With AI mode on, startup is instant and provisioning runs in the background: detect the tier from total memory; install Ollama if missing (winget on Windows, brew on macOS; on Linux it prints the one-line installer and never pipes curl|sh automatically); start Ollama (stopping on exit only the instance it started; -ollama=false opts out; serve never auto-manages Ollama); pull the tier's models, skipping any already present; register them as model:* facts and turn on auto-embed. Everything is idempotent. The one-time opt-out is recorded in .centauri-ai-optout next to the data file. If Ollama can't be installed automatically, Centauri still runs; AI lights up once you install it and restart.
GET /v1/ai/status # progress; where answers are computed (local / cloud / none)
POST /v1/ai/enable {"tier":"auto"} # turn on fully-local AI (auto | small | balanced | max)
POST /v1/ai/local {"tier":"auto"} # switch back to local — the reversible off-switch
All /v1/ai/* endpoints are admin-token-only, including the GET.
With local AI enabled, every new fact embeds itself in the background right after it commits — both ingest paths — so data is instantly SEARCH / SIMILAR / ASK-able. Embeddings are ordinary enrichment facts and never touch the original write's hash chain. ENRICH <pattern> USING <model> [ON <field>] [AS <kind>] (internal/ceql/enrich.go) runs a registered model over matching events from the query itself and stores the result as a superseding, provenanced enrichment fact; re-runs skip what's done, so inference is cached in the log. POST /v1/feedback {event, score, note} stores an append-only rating in [-1, 1] that nudges future retrieval — no weights change.
PUT model:embed FACET config SET
endpoint='http://localhost:11434/v1/embeddings', kind='embedding', model='nomic-embed-text'
ENRICH item:* USING embed -- embeddings → vector index
ENRICH ticket:* USING summarize ON body AS summary
ENRICH asset:* USING vision -- describe images / PDF pages
ASK '<question>' answers from the built-in knowledge base first (Q&A live as kb:<slug> facts, ranked by the same BM25 ranker as SEARCH). With a chat model registered, a question the KB can't answer retrieves your most relevant facts (hybrid BM25 + vector), has the local LLM answer using only those, and returns grounded: true, the answer, and sources (event ids) so every answer is checkable. Without a chat model, ASK records the question as a kb_gap:<slug> fact instead of guessing — an agent can later answer it by appending a kb: fact. Because knowledge is facts, FACTS OF kb:* AS KNOWN AT … tells you what the assistant would have said last month.
ASK 'how much does the widget cost'
ASK 'summarize the history of item:1'
ASK 'why do these prices disagree'
FACTS OF kb_gap:* -- 1. what couldn't we answer?
PUT kb:does-it-scale FACET knowledge -- 2. an agent teaches the answer
SET question='does it scale?', answer='Single-node, in-RAM working set…'
ASK 'does it scale?' -- 3. now answered from the database
Upload an image or PDF (POST /v1/assets or the dashboard's 📎 panel); pages are rendered locally, a vision model describes them, and the description is embedded into the vector index — so SEARCH 'E-3 200A panel' finds the drawing. Two helpers can't be bundled: Ollama (multimodal model + embedder) and a PDF rasteriser (poppler pdftoppm or ImageMagick magick; plain images need neither). centauri setup vision -install installs what's missing via your OS package manager and pulls the models; drop -install to just check (exits non-zero when something is missing). Status: GET /v1/vision/status; fetch a stored asset with GET /v1/assets/{sha}.
OS
What setup vision -install installs
Windows
winget install Ollama.Ollama + ImageMagick.ImageMagick + ArtifexSoftware.GhostScript (poppler also works)
macOS
brew install ollama + brew install poppler
Linux
apt-get install poppler-utils; Ollama via its one-line installer
SEARCH 'E-3 200A panel' OF asset:*
FACTS OF asset:*
SIMILAR TO <asset-event-id> TOP 5
Windows gotcha: the bare convert command is the System32 filesystem tool, not ImageMagick; Centauri ignores it and uses pdftoppm or magick.
The cloud boost — GLM-5.2, GPT-5.5, Claude, custom#
Out of the box Centauri is 100% local. Optionally, paste a provider key into the dashboard AI panel (or POST /v1/ai/cloud) to switch chat to a cloud model. Providers: zai (GLM-5.2, the default), openai (GPT-5.5), anthropic (Claude), or custom (any OpenAI-compatible server, key optional — a LAN Ollama box works keyless). Each provider keeps its own key in its own 0600 file next to the data file; the key never enters the log. Embeddings and vision stay local either way.
Read before you turn it on: it is off by default; when on, your prompts and the retrieved facts leave your machine and are processed by the provider under its terms; it costs money (per token); it is clearly marked and reversible via POST /v1/ai/local. GLM-5.2 cannot run locally — its weights are 200+ GB; local tiers top out at glm-4.7-flash.
-- registering a remote model by hand: the secret is named, never stored
PUT model:summarize FACET config SET endpoint='https://api.openai.com/v1/chat/completions',
kind='chat', model='gpt-4o-mini', auth_env='OPENAI_KEY'
PUT model:cloud FACET config SET endpoint='https://api.z.ai/api/paas/v4/chat/completions',
kind='chat', model='glm-5.2', auth_file='/path/to/zai.key', timeout_secs='600'
Describe your scenario in plain language and Centauri (internal/architect) interviews you and builds the entire database — schemas, CePL procedures, watches, starter queries — then stores the conversation as blueprint:* facts so the database forever remembers why it exists. Two paths: a description with an adaptive interview, or paste CREATE TABLE DDL (the RDBMS import path). POST /v1/architect/plan with {description, answers} returns questions until none remain, then a blueprint; POST /v1/architect/apply (admin) builds it into a new named environment (<env>.log next to the data file — the server must host a data directory). The dashboard's 🏗 button and the centauri_genesis MCP tool drive the same engine.
curl -X POST localhost:7771/v1/architect/plan -H 'content-type: application/json' \
-d '{"description":"a small bakery tracking orders, suppliers and daily waste","answers":{}}'
# → {"questions":[...]} … answer them, resend with "answers" filled … → {"blueprint":{...}}
curl -X POST localhost:7771/v1/architect/apply -H 'Authorization: Bearer <admin>' \
-H 'content-type: application/json' -d '{"description":"...","answers":{...}}'
# RDBMS import path
curl -X POST localhost:7771/v1/architect/plan -H 'content-type: application/json' \
-d '{"ddl":"CREATE TABLE item (id INT PRIMARY KEY, name TEXT, dept_id INT);","answers":{}}'
CePL (internal/proc) is a deliberately tiny near-English language inside the database: a procedure is stored as a versioned proc:<name> fact, run by name, and every run returns a step-by-step trace. Statements: PROCEDURE name(p1, p2) … END, LET x = FIRST FACTS OF … (binds a CeQL read), LET y = <expression>, WHEN <guard>: <step> (IS MISSING / IS PRESENT), PUT / CORRECT / RETIRE with ${var} holes, FAIL 'msg', RETURN value. Substitution is injection-safe: templates are re-parsed as CeQL after filling, numbers render bare, a string glued to a word accepts only token-safe characters and never *, and anything else becomes a quoted literal. HISTORY OF proc:x shows every version; writes carry REF 'proc:x' lineage; every stored procedure is auto-exposed over MCP as a typed proc_<name> tool.
PROCEDURE duty_estimate(item, units)
LET rate = FIRST FACTS OF hts:${item} FACET assess
WHEN rate IS MISSING: FAIL 'no duty rate on file for ${item}'
LET cost = FIRST FACTS OF cost:${item}
WHEN cost IS MISSING: FAIL 'no average cost for ${item}'
LET duty = cost.av_cost * units * rate.comp_rate
PUT duty:${item} SET duty_amt=${duty}, units=${units} REF 'proc:duty_estimate'
RETURN duty
END
# define (admin): body is {"source": "<procedure text>"}
curl -X POST localhost:7771/v1/proc -H 'Authorization: Bearer <admin>' \
-H 'content-type: application/json' -d '{"source":"PROCEDURE …\n…\nEND"}'
# run
curl -X POST localhost:7771/v1/proc/run -H 'Authorization: Bearer <admin>' \
-H 'content-type: application/json' \
-d '{"name":"duty_estimate","args":{"item":"100001","units":3}}'
RUN duty_estimate WITH item='100001', units=3 -- from the query bar / shell
HISTORY OF proc:duty_estimate -- every version ever deployed
Honest scope: no loops, no nesting, no body access — heavier logic belongs in an SDK.
MCP server — Claude Desktop, Claude Code, Cursor, agents#
centauri mcp runs a Model Context Protocol server over stdio against a local log (internal/mcp), keeping stdout clean. It exposes 20 typed tools — centauri_context, centauri_current, centauri_asof, centauri_history, centauri_trace, centauri_pending, centauri_disagreements, centauri_similar, centauri_by_ref, centauri_append, centauri_activate, centauri_enrich, centauri_put_schema, centauri_get_schemas, centauri_ceql, centauri_define_procedure, centauri_run_procedure, centauri_genesis, centauri_subjects, centauri_stats — plus one auto-generated proc_<name> tool per stored procedure. Any MCP host uses the same mcpServers block: Claude Desktop (claude_desktop_config.json), Claude Code (.mcp.json or claude mcp add), Cursor (.cursor/mcp.json).
centauri mcp opens the log file directly rather than going through HTTP, so point it at its own -data file or stop the server first — two processes appending to one log is not a supported configuration.
Non-MCP hosts (OpenAI, Gemini, Grok, Mistral, local runners) use a single centauri_query tool that forwards CeQL to POST /v1/query — the CeQL Book (section 14½) shows the pattern, including OpenAI(base_url="http://localhost:11434/v1") for Ollama.
Track 4
Enterprise storage, HA & the API/LLM gateway
Oracle-grade memory for what happened, when, why, and how much to trust it — in one free binary. This track covers the storage engine's integrity and tiering, write scaling, failover, replication, governance, authentication, and the gateway whose control plane is the database itself.
The store is an append-only JSONL log with in-memory indexes (internal/store). On open it replays the log (shortcut by centauri.checkpoint); -checkpoint-every N seconds writes that recovery checkpoint while serving so a crash replays only seconds of tail. Every committed line is covered by a per-line SHA-256 chain (integrity.go). centauri verify recomputes it and prints the chain head — record it somewhere external; a future verify reproducing it proves the history is byte-for-byte intact. backup copies the committed log (safe while serving) and verifies the copy's chain head equals the source's. -lazy keeps payloads on disk so data can exceed RAM while the index still fits.
centauri archive -to <dir> seals a log into compressed (typically 5–10×), tamper-evident segments plus a manifest.json. Each segment carries a Merkle root (internal/segment/merkle.go; O(log n) inclusion proofs) and zone maps (min/max times, namespace prefixes, field stats) so queries skip segments that can't match; stats and chain heads stay uncompressed in the manifest, so pruning and verify need no decompression. Serving an archive dir replays sealed segments plus an appendable tail; -auto-seal-mb N seals the tail once it passes N MB (Oracle-style log switch) and centauri seal does it by hand, crash-safe via one atomic manifest switch. compact merges consecutive segments offline without erasing. verify -fast scrubs per-segment Merkle roots in parallel. Sealed segments support per-segment AES-256-GCM crypto-erasure — destroy the key, keep the chain.
The lazy index — serving bigger-than-RAM, read-only#
serve -lazy-index -data <archive-dir> opens an archive with the disk-backed index (internal/store/lazyindex.go): one streaming pass keeps only the current fact per (subject, facet) in RAM, so memory scales with live subjects, not total events. history/asof stream zone-map-pruned segments from disk through an LRU of decompressed segments; a Merkle-validated pointer checkpoint (lazy.ckpt) makes restart replay only the tail. It honours -read-token, native TLS, admission limits, and serves the Tablespace Console at / plus /livez, /readyz, /metrics.
Honest limit: read-only by design (writes use a normal serve). No persisted secondary index for arbitrary cold WHERE: equality over current state is indexed (/v1/lookup), cold history/range predicates are pruned scans; vector similarity, causal centrality and recency/trust weighting need the full in-RAM index.
internal/objstore provides a segment store with a local-dir backend and an S3-compatible backend (AWS S3, MinIO, Cloudflare R2, Backblaze B2) using stdlib net/http and a hand-rolled AWS Signature V4 signer — no SDK. centauri archive-push uploads a sealed archive; -pull restores it; serve -lazy-index -s3-endpoint … -s3-bucket … reads segments from the bucket on demand, Merkle-verifies each one (untrusted storage is safe) and LRU-caches them. GET/PUT/byte counters appear on /metrics as centauri_objstore_*. Credentials come only from AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
Flag
Meaning
-s3-endpoint
S3-compatible endpoint, e.g. https://s3.us-east-1.amazonaws.com or http://localhost:9000
-s3-bucket
bucket name
-s3-region
region for SigV4 signing (default us-east-1)
-s3-prefix
key prefix within the bucket (default centauri)
-pull
archive-push: download from the bucket instead of uploading
Honest status: the object store is a read-only cold tier — writes and sealing still target local disk. SigV4 interop is unit-tested against a mock S3; smoke-test it against your MinIO or real S3 before relying on it. GCS and Azure signers are design-only.
Writes are single-writer per log by design — the per-line hash chain is sequential. Two levers: -group-commit coalesces concurrent appends into one fsync per batch on a single chain; serve -shards N (with -data a directory) partitions subjects across N independent shard logs by FNV-1a hash of the subject — each its own chain, lock and committer — written in parallel (~N× throughput). Routed: /v1/append, /v1/current, /v1/history, /v1/asof, /v1/subjects (union), /v1/shards, /v1/query for a concrete subject, and fan-out (internal/ceql/fanout.go) for wildcard FACTS/HISTORY/SEARCH.
Not in sharded mode: cross-shard atomic transactions, cross-shard causal links/trace, cross-shard aggregates/GROUP BY; SEARCH ranking is approximate across shards. Use single-store serve for those.
serve -ha-lease <shared dir> -ha-addr <url> runs lease-based leader election (internal/ha): the node holding the lease is the writable primary; the rest start read-only and replicate from it; when the primary's lease lapses a replica acquires it and promotes itself — no operator action. Split-brain is prevented by a monotonic epoch fencing token plus self-fencing: a primary stops accepting writes the instant it can't confirm a live lease. GET /v1/ha reports role/epoch/leader. The bundled lease store uses atomic O_EXCL creation on a shared POSIX filesystem (NFS / EBS multi-attach / k8s shared volume); for multi-AZ without shared storage, plug in any CAS-capable LeaseStore (one interface, two methods).
Flag
Meaning
-ha-lease
shared lease directory enabling election
-ha-id
this node's id (default hostname)
-ha-addr
advertised base URL replicas follow when this node leads
-ha-ttl
lease TTL in seconds (default 10)
-interval
replication poll interval (default 2s)
# node A and node B, same shared lease dir
centauri serve -data a.log -addr :7771 -ha-lease /mnt/shared/centauri-lease -ha-id a -ha-addr http://a.internal:7771 -token secret
centauri serve -data b.log -addr :7771 -ha-lease /mnt/shared/centauri-lease -ha-id b -ha-addr http://b.internal:7771 -token secret
curl http://a.internal:7771/v1/ha
Honest limit: still single-writer at any moment; multi-writer clustering does not exist. Assumes bounded clock skew.
centauri follow -primary <url> keeps a live read-only replica by polling GET /v1/log and ingesting raw bytes, so the follower's hash chain is byte-identical to the primary's; add -addr to serve a read-only API from it. centauri sync -primary <peer> is bidirectional, record-level and echo-safe: it pulls the peer's /v1/changes from a per-peer replication slot, ingests facts it doesn't hold (dedup by event id), and advances the slot — run it on both ends. Multi-master on the same subject converges because "current" is a deterministic function of the facts. merge produces one canonical re-chained log offline.
centauri follow -data replica.log -primary https://db.example.com -token secret -addr :7772
centauri sync -data laptop.log -primary http://desktop:7771 -token secret # and the mirror on the peer
centauri merge -to merged.log a.log b.log && centauri verify -data merged.log
Honest scope: sync promises the same set of facts, not byte-identical logs. Push mode / pairing and a browser replica are not built.
Change data capture is GET /v1/changes?from=<cursor> (internal/store/cdc.go): it returns events, a cursor (byte offset) and caught_up; resume from the cursor and you never miss or duplicate a fact. Durable replication slots make this resumable server-side: ?slot=<name> resumes from the slot's confirmed position, POST /v1/changes/ack confirms progress, GET /v1/slots lists them; retention is gated by the minimum slot cursor so no consumer misses changes. /v1/watch is the push alternative (SSE; internal/store/watch.go); /v1/log is the raw byte feed replication uses.
centauri retention -pattern '<glob>' -older-than N (internal/retention) previews which subjects would be retired — dry-run by default; -apply RETIREs them by appending retired:true corrections so queries skip stale facts. History is kept, the chain stays intact, and the log does not shrink. A hold:<name> fact with a subject pattern places matching subjects under a legal hold that retention skips — and the engine enforces it: any RETIRE of a held subject is rejected on every originating write path (CeQL, raw append, sharded) until the hold is lifted; replication is exempt so replicas mirror the primary.
PUT hold:litigation-2026 SET pattern='order:2025-*', active=true
RETIRE hold:litigation-2026 -- lift the hold
Not yet: a stored-policy scheduler inside the binary (schedule the -apply form externally) and a crypto-erase retention action.
Authentication: tokens, scoped tokens, field masking, OIDC#
serve gates every /v1/* route behind -token (admin, env CENTAURI_TOKEN) and -read-token (read-only, env CENTAURI_READ_TOKEN); dashboards, /v1/version, probes and /metrics stay open. Scoped tokens (POST /v1/acl) confine a principal to subject prefixes and can mask fields. Enterprise SSO: -oidc-issuer (plus -oidc-audience, -oidc-jwks, -oidc-write-scope) accepts bearer JWTs from any OIDC provider (Okta, Azure AD, Auth0, Keycloak, Google), verifying RS256/384/512 against the provider's JWKS (fetched over HTTPS, cached, refreshed on rotation) plus exp/nbf/iss/aud (internal/oidc). A valid token grants read; write requires the configured scope matched against scope/roles/groups.
Honest scope: Centauri validates the IdP's tokens; it does not run an identity provider for people. No role hierarchies, LDAP/SAML, or SSO-claim-to-ACL-scope mapping yet; OIDC is token-only on serve -lazy-index.
-tls-cert/-tls-key terminate HTTPS natively on serve, desktop, -lazy-index and the gateway's proxy listener — no reverse proxy required (Caddy/nginx in front is fine; bind to localhost if so). Admission control: -max-concurrency (HTTP 429 over the cap), -query-timeout seconds (HTTP 503), -max-concurrency-per-db for a per-tenant cap; streaming endpoints and health/metrics are exempt. -log-format json -log-level info emits one slog line per request with method, path, status, bytes, duration and an X-Request-ID (honoured inbound, echoed back). /livez, /readyz and Prometheus /metrics exist on every serve mode and the gateway.
A server whose -data sits in a directory can host several named environments (<name>.log beside the default); clients pick one with ?db=<name> (SDKs: WithDatabase / { database } / Airbyte database). GET /v1/databases lists them; POST /v1/databases (admin) creates one; Genesis apply creates a fresh environment per blueprint. The Postgres wire listener has no ?db= routing — it serves the default database.
centauri gateway (internal/gateway, stdlib httputil) turns a Centauri log into a reverse proxy whose entire control plane — routes, API keys, request audit — lives in the database as facts. Config therefore time-travels (AS OF, AS KNOWN AT, HISTORY OF route:api) and the audit is hash-chained and verify-able. Data plane default :8080; -admin :7771 serves the normal dashboard/API over the same store; -admin-inline serves it on the proxy port for unmatched paths (single-port deploys). A route is the current fact of route:<name> facet config; a key is gwkey:<sha256> (the plaintext is printed once and never stored). Mint keys offline with -newkey <name> or live in the Console (/console → Keys & Access).
FACTS OF route:api FACET config AS OF 'last tuesday 9am'
FACTS OF route:api FACET config AS KNOWN AT '2026-08-01'
HISTORY OF route:api
RETIRE gwkey:<sha256> FACET config -- revoke a key; history stays
Maps and arrays (providers, add_request_headers, allow_ips…) must be written through POST /v1/append or an SDK, because CeQL SET takes scalars only. Parsing is fail-closed: an unknown auth value, a bad CIDR or glob, a broken webhook URL or a missing policy procedure rejects the whole route (its paths 404) — a typo can never mean "open".
Field
Type
Meaning
prefix
string
path prefix that selects this route (/api/)
upstream
string
base URL to proxy to (non-LLM routes)
strip_prefix
bool
remove prefix before forwarding
auth
string
"none", "key", or "oidc"; anything else rejects the route
rate_per_min
number
per-route requests/minute; absent/0 = unlimited
timeout_ms
number
upstream timeout
audit
string
"meta" = one audit fact per request (metadata only); "none"
llm
bool
OpenAI-compatible LLM route with provider failover
providers
array
LLM: {endpoint, model, auth_env, auth_file} tried in order
oidc_issuer, oidc_audience
string
required on OIDC routes
oidc_jwks_url
string
explicit JWKS when the issuer has no discovery doc
add_request_headers, add_response_headers
map
middleware header sets
remove_request_headers
array
headers deleted before forwarding
cors
bool
permissive CORS + OPTIONS preflight short-circuit (before auth)
allow_ips, deny_ips
array
CIDR allow/deny (deny wins)
max_body_bytes
number
413 above this
block_path_patterns
array
path.Match globs answered 403
policy
string
stored CePL procedure run per request
authz_webhook
string
URL POSTed per request; non-2xx = 403
webhook_fail_open
bool
allow when the webhook is unreachable (default fail closed)
retired
bool
set by RETIRE; route stops matching
Key fields: name, routes (prefixes it may call; empty = all), rate_per_min (per-key budget), retired. Middleware order per request: CORS → auth → rate limit → header add/remove → IP filters → body cap → path blocks → authorization webhook → CePL policy → proxy.
auth='key' requires a minted gateway key as a bearer token. auth='oidc' validates the caller's JWT with the same verifier serve uses, forwards X-Auth-Subject: <sub> (any client-supplied value of that header is stripped), and records the JWT sub as the audit key; the bearer passes through to the upstream unless you add "remove_request_headers": ["Authorization"]. -mtls-ca <pem> (mtls.go, with -tls-cert/-tls-key) requires every connection on the proxy listener to present a client certificate signed by that CA — requests without one die at the TLS handshake; the cert CN becomes the audit key when nothing else named the caller. Listener-wide, not per-route.
PUT route:api FACET config SET prefix='/api/', upstream='http://127.0.0.1:9000', auth='oidc', oidc_issuer='https://login.example.com', oidc_audience='my-api'
Token issuer: POST /gw/token (client-credentials only)#
The gateway can issue short-lived service tokens so machine callers pass auth='oidc' routes without an external IdP (issuer.go). Every gateway key doubles as an OAuth2 client: client_id is the key's name, client_secret is the key. The token is an RS256 JWT with iss = -issuer, sub = key name, aud = -token-aud (default centauri), exp after -token-ttl seconds (default 900), signed by an RSA-2048 key generated on first use and stored as PEM next to the log (issuer.key, mode 0600) — never in the log, because the log can never erase it. The public half is served at /.well-known/jwks.json and /.well-known/openid-configuration. Every issuance and failure is audited on gw:_tokens. RETIRE gwkey:… stops new tokens immediately; issued ones live out their TTL.
Rate limiting (ratelimit.go) is per route (rate_per_min) and per key on top. For programmable logic a route names a stored CePL procedure in policy (policy.go); it runs after auth, rate limit and built-in middleware, immediately before proxying, receiving any subset of method, path, key (resolved caller name, or '' when anonymous) and ip. Its RETURN decides: 'allow'/true proceeds; 'deny'/'deny:<message>'/false is 403; a record is honoured as {"allow", "status", "message", "add_headers"}; anything else, a runtime error, or FAIL is 403 fail-closed. Policies are parsed once per config load and hot-reload when the procedure fact changes; denials are audited even on audit:'none' routes. Measured cost ≈2 µs per request. authz_webhook POSTs {method, path, key, ip, headers} (safe subset only, never the credential) to your service; anything but 2xx, or the 2-second timeout, is 403 fail-closed unless webhook_fail_open.
PUT route:api FACET config SET prefix='/api/', upstream='http://127.0.0.1:9000', auth='key', policy='office_rules'
PROCEDURE office_rules(method, path, key, ip)
-- writes only from the ci key; reads for everyone
WHEN method != 'GET' AND key != 'ci': RETURN 'deny:only ci may write here'
RETURN 'allow'
END
PUT route:api FACET config SET prefix='/api/', upstream='http://127.0.0.1:9000', auth='key', authz_webhook='https://authz.internal/check'
Honest scope: CePL policies are guards plus CeQL reads/writes — no loops, no body access, no request transformation. A plugin marketplace: no.
Set llm: true and a providers list (llm.go); the gateway speaks the OpenAI-compatible chat-completions shape and fails over in order on unreachable or 5xx providers. Each provider entry pins the model — the gateway forces it into the forwarded request so callers can't choose an expensive model. Secrets never live in facts: auth_env names an env var, auth_file a key file; local servers like Ollama are keyless. When a provider reports usage, the audit fact records model, prompt_tokens, completion_tokens, so per-key token spend is a query. stream: true passes SSE through, flushed per chunk, with stream_options.include_usage injected so the trailing usage chunk lands token counts.
Honest scope: failover applies until the first byte; a provider failure mid-stream ends the stream early, properly terminated, with truncated: true in the audit fact.
With audit: "meta" each request appends a fact on subject gw:<route> facet gateway (audit.go) carrying method, path, status, ms, key (name, never the secret), upstream, bytes_out, and for LLM routes model and token counts — bodies are never recorded. FACTS OF gw:* is the latest request per route; HISTORY OF gw:<route> is the full log. /metrics is on the admin plane. Audit grows the log: retire old audit facts with centauri retention -pattern 'gw:*', or set audit: 'none' on hot routes. Scale-out: run N gateways with -control-follow https://primary:7771 -control-token …; each follows the primary's control log read-only and writes audit to its own local log (-audit-data).
HISTORY OF gw:chat FACET gateway ORDER BY recorded DESC LIMIT 100
HISTORY OF gw:chat WHERE status >= 500 -- every 5xx, forever
FACTS OF route:api FACET config AS OF 5 minutes ago
Honest limits: the control plane stays single-writer with no leader election; no cross-gateway audit merge; no multi-DC; not a Kubernetes ingress controller; ~26k req/s measured on a 2-core dev VM — NGINX and Kong still win at the edge. See the gateway comparison.
Track 5
SDKs, CDC streams & connectors
Three zero-dependency SDKs, two Airbyte connectors, and the production recipes. Everything talks to the same REST/JSON API and SSE streams — the SDKs are convenience, not a second protocol.
sdk/python is zero-dependency, Python 3.8+. Centauri.launch() finds the binary, starts a server (optionally on a given data= log) and connects; Centauri(url=…, token=…) connects to a running one. Insert and update are the same call (db.add). Errors say what happened and what to do next (CentauriConnectionError, CentauriAPIError with .status/.message). db.api.get/post/stream reaches any endpoint the SDK hasn't wrapped. Use a with block and the launched server stops automatically.
cd sdk/python
pip install -e .
from centauri import Centauri
db = Centauri.launch() # finds & starts the server for you
db.add("toy:robot", {"price_cents": 500}) # save your first fact
print(db.get("toy:robot")) # read it back
You want to…
Call
Start a local server from Python / connect to one
Centauri.launch() / Centauri(url=…, token=…)
Save a fact; save many atomically; bulk-load CSV/JSON
sdk/python/examples/ holds five runnable scripts, each under a minute (and TUTORIAL.md walks through them). All use Centauri.launch(data="tutorial.log") except 04, which connects to the already-running server.
# 01_hello_centauri.py — start, save, update, read current and history
db = Centauri.launch(data="tutorial.log")
db.add("toy:robot", {"price_cents": 500, "color": "silver"})
db.add("toy:robot", {"price_cents": 450, "color": "silver"}) # update — old kept!
print("Current:", [e.value for e in db.get("toy:robot")])
print("History:", [e.value for e in db.history("toy:robot")])
db.stop()
# 02_time_travel.py — back-dated write, then at() and double time travel
db.add("toy:rocket", {"price_cents": 900}, effective=yesterday)
db.add("toy:rocket", {"price_cents": 700}) # effective now
db.at("toy:rocket", yesterday)
db.at("toy:rocket", now, known=yesterday) # as known then
# 03_pump_csv.py — bulk-load a CSV with a 'subject' column
csv_file.write_text("subject,price_cents,kind\ntoy:car,300,REGULAR\ntoy:boat,550,REGULAR\ntoy:kite,120,SALE\n")
count = db.pump(csv_file)
print(f"Pumped {count} facts!")
# 04_watch_live.py — SSE watch loop; run 01 or 03 in another terminal
db = Centauri() # connect to the already-running server
for event in db.watch():
print(f" NEW: {event.subject} {event.value} (facet={event.facet})")
sdk/go is a thin zero-dependency client (stdlib net/http + encoding/json) with the same surface as Python: Add, Get, History, At(subject, atMicros, knownMicros), Context, Query (any CeQL), DefineProcedure, Run, and Changes(cursor). Options: WithToken, WithDatabase, WithHTTPClient.
c.DefineProcedure(`PROCEDURE reprice(item, pct)
LET cur = FIRST FACTS OF ${item}
WHEN cur IS MISSING: FAIL 'unknown ${item}'
LET newp = cur.price_cents * pct / 100
PUT ${item} SET price_cents=${newp}
RETURN newp
END`)
c.Run("reprice", map[string]any{"item": "toy:robot", "pct": 90})
evs, cursor, caughtUp, _ := c.Changes(0)
sdk/js/centauri.mjs is a zero-dependency fetch-based ESM client for Node 18+ and the browser. Methods: add, append(events), get, history, at(subject, at, known), context, stats, subjects, query(ceql), defineProcedure(source), run(name, args), changes(from). Options { token, database }. Inside a JS template literal the CePL ${…} holes must be escaped as \${…}.
import { Centauri } from "@centauri/client"; // or "./centauri.mjs"
const db = new Centauri("http://localhost:7771", { token: "secret" });
await db.add("toy:robot", { price_cents: 500 });
const facts = await db.get("toy:robot");
const hist = await db.history("toy:robot");
const bundle = await db.context("toy:robot");
const res = await db.query("FACTS OF item:* WHERE region='EU' WHY");
await db.defineProcedure(`PROCEDURE reprice(item, pct)
LET cur = FIRST FACTS OF \${item}
WHEN cur IS MISSING: FAIL 'unknown \${item}'
LET newp = cur.price_cents * pct / 100
PUT \${item} SET price_cents=\${newp}
RETURN newp
END`);
await db.run("reprice", { item: "toy:robot", pct: 90 });
const { events, cursor, caught_up } = await db.changes(0);
All three SDKs wrap GET /v1/changes as changes(cursor) returning the events, the byte-offset cursor to resume from, and a caught-up flag; Python additionally wraps /v1/watch as a generator. Persist the cursor — or use a server-side slot with POST /v1/changes/ack — and you get exactly-once-per-fact delivery across restarts. This is the same mechanism centauri sync and the Airbyte source ride.
# Python: resumable polling with the escape hatch
page = db.api.get("/v1/changes", **{"from": cursor})
for ev in page["events"]: ...
cursor = page["cursor"]
Airbyte destination — land anything into Centauri#
connectors/airbyte/destination_centauri is a standalone pure-stdlib Python program speaking the Airbyte Protocol over stdin/stdout — no Airbyte CDK, nothing linked into the binary. Each Airbyte RECORD becomes one fact via POST /v1/append: subject <stream>:<key> (the primary_key field if configured, else a per-stream counter), facet from config, type OBSERVED, value = the full record. STATE messages are acknowledged only after the preceding records are durably appended, which is what makes resumable syncs safe. Commands: spec, check, write.
connectors/airbyte/source_centauri exposes one stream, facts. read pages GET /v1/changes?from=<cursor>, emits one RECORD per event, and emits the byte-offset cursor as Airbyte STATE, so the next sync resumes exactly. Sync modes: full_refresh and incremental (source_defined_cursor: true). Each record's data is the full event: event_id, subject, facet, type, value, effective_time, recorded_time, provenance, confidence. Config: centauri_url (required), token, database. Commands: spec, check, discover, read.
The dashboard ships inside the binary, so going to production is running the binary somewhere public with HTTPS in front. From docs/deploy.md:
Always set -token (and ideally -read-token) — without one, anyone with the URL owns your data.
Terminate TLS natively (-tls-cert/-tls-key) or behind Caddy/nginx bound to localhost.
Take periodic centauri backup snapshots off the box and record the chain head somewhere separate — tamper evidence needs an external anchor.
Set -max-concurrency, -query-timeout and -max-concurrency-per-db; scrape /metrics; probe /livez and /readyz; emit -log-format json.
# Recipe 1 — systemd unit on a ~$5 VPS, Caddy in front
ExecStart=/usr/local/bin/centauri serve -data /var/lib/centauri/centauri.log -addr 127.0.0.1:7771 -token CHANGE_ME -read-token CHANGE_ME_TOO
infra/k8s.yaml deploys Centauri on Kubernetes as a normal workload (it is not an ingress controller — no IngressClass, Gateway API or CRDs). It defines a Secretcentauri-token, a centauri-dbService on 7771 and a StatefulSet with replicas: 1 and a 1Gi volumeClaimTemplate — the store is single-writer, so never raise replicas; run read replicas with centauri follow. The gateway is a Deployment (replicas: 1, strategy Recreate) with its own PVC, a LoadBalancer on 80 → 8080, args gateway -data /data/gateway.log -addr :8080 -admin-inline, and probes on /livez and /readyz. Build and push your own image first; no public image is assumed. Scale gateways horizontally with -control-follow.
render.yaml is a Render Blueprint (Deploy to Render) that creates two Docker web services on the starter plan: centauri (health check /, 1 GB disk at /data, CENTAURI_TOKEN auto-generated) and centauri-gateway (health check /livez, gateway -data /data/gateway.log -addr :7771 -admin-inline, its own disk and token). Render exposes one port per service, which is why the gateway runs -admin-inline: proxied routes win and any unmatched path serves the token-gated admin dashboard. Mint keys in the Console, since the writer lock blocks -newkey while running.
-- the demo flow, from the query bar
PUT route:api FACET config SET prefix='/api/', upstream='https://your-backend', auth='none', rate_per_min=60
HISTORY OF gw:api FACET gateway
FACTS OF route:api FACET config AS OF 5 minutes ago
Every serve mode exposes the same three unauthenticated endpoints: /livez, /readyz and Prometheus /metrics (process + API counters, store counters and build info on normal serve; segment-cache gauges on lazy; centauri_objstore_* with an object store; never fact data). GET /v1/version is the deploy stamp. Structured request logs carry X-Request-ID. GET /v1/integrity exposes the live chain state, and /v1/verify (lazy mode) recomputes Merkle roots and the chain online with no downtime.
How Centauri is built, released and steered: the release assets, the CI and AI-assisted maintenance workflows, the benchmark discipline, the roadmap, the invariants every contribution must respect, and the design documents behind each subsystem.
Pushing a version tag runs .github/workflows/release.yml, which builds six static binaries, the Windows installer, and a checksum file, then publishes a GitHub Release. Asset names are stable, so /releases/latest/download/<asset> always points at the newest release. Every component — engine, dashboards, Studio, Console, App, CeQL Book, gateway, MCP server, local-AI orchestration — is inside the one binary; there is nothing else to download.
SDKs and connectors are source in the repository (sdk/python via pip install -e ., sdk/go as a Go module path, sdk/js/centauri.mjs as an ESM file, connectors/airbyte as Docker builds). Publishing the Python SDK to PyPI is on the roadmap; nothing is published to PyPI or npm today.
Every pull request runs ci.yml (go vet, go test ./..., the Python SDK tests) and claude-review.yml, an automatic code review by Claude on non-draft PRs. perf.yml benchmarks the PR's base and head and compares them with benchstat, warning on possible regressions; it also runs weekly on main as a drift check. sync.yml keeps the two canonical→copy pairs identical on every push (internal/api/ceql.html → docs/ceql.html; docs/kb.json → internal/assistant/kb.json), so the embedded textbook and the assistant's knowledge never drift from the website.
# the same gate, locally
go vet ./... && go test ./...
cd sdk/python && python -m unittest discover -s tests
go test -bench . -benchmem ./internal/store/
Three nights a week (dream.yml, Mon/Wed/Fri 06:00 UTC, or on demand from the Actions tab), Claude reviews the codebase, picks one item from ROADMAP.md top-to-bottom — or one real bug it finds — implements it with tests, and opens a pull request. CI gates it and a human merges, always: auto-merge is explicitly out of scope. Humans steer what gets built by editing the roadmap. Items are kept small enough for one PR, and the roadmap lists what is deliberately never dreamed about: multi-writer clustering, third-party Go dependencies, and rewrites of working subsystems without a human-filed issue.
ROADMAP.md
## Now (v0.4) # prioritized, picked top to bottom
## Next
## Always welcome # dream-sized: NL rules, catalog entries, Genesis packs, tests
## Explicitly out of scope
Centauri ships a reproducible benchmark suite rather than baking numbers into the docs — run it on your own hardware and data shape. Core-path benchmarks (internal/store/core_bench_test.go) cover Append, Current and AsOf; the lazy/archive suite (lazyindex_bench_test.go) isolates each win in pairs: HistoryCold vs HistoryCached (segment-cache), AsOfCold vs AsOfCached (zone-map pruning), SearchCold vs SearchResident, and OpenCold vs OpenFromCheckpoint (fast restart). bench/baseline.txt holds a committed baseline (from deploy.bat) that the Perf workflow compares against; the gateway's own BenchmarkPolicyOverhead measures the per-request cost of a CePL policy.
go test -bench . -benchmem ./internal/store/
go test -bench Policy -benchmem ./internal/gateway/
# bench/baseline.txt — one developer machine (windows/arm64), for drift detection only
BenchmarkAppend-12 ~19–36 µs/op 46 allocs/op
BenchmarkCurrent-12 ~0.4–0.8 µs/op 4 allocs/op
BenchmarkAsOf-12 ~0.5–0.75 µs/op 4 allocs/op
Honest limits: these measure single-process paths, not concurrent load; the lazy path has no persisted secondary index, so an arbitrary WHERE over cold history the zone maps can't prune is still a scan. The gateway's ~26k req/s figure is from a 2-core dev VM.
From ROADMAP.md, as of this page. Now (v0.4): encryption at rest + crypto-erasure end to end (the segment-level AES-256-GCM primitive and legal holds are shipped; per-subject keys for the live log are open); an HNSW/ANN index for SIMILAR beyond brute-force cosine; cross-subject joins in CeQL (FACTS OF a:* JOIN b:* ON …, nested-loop first); a CePL step-debugger in the dashboard; Windows/macOS code-signing docs. Next: referential field types in schemas (parent ref(item:*)); the rest of the governance pack (named roles, PII classification tags, scheduled data-quality checks); derived facts (DERIVE summary:<x> AS FACTS … GROUP BY …, incrementally maintained); window frames; spatial fields; stemming for MATCHES; PyPI publishing for the Python SDK.
Shipped since the list was first written (kept for honesty): sharded writes, group commit, tablespaces + S3 offload + auto-seal, the lazy index, CDC with slots, HA failover, log shipping, read-only SQL + Postgres wire, retention + legal holds, scoped tokens + masking, OIDC, admission control, backup/verify, the local-AI appliance, and the gateway.
CLAUDE.md is the guide for every contributor, human or AI. Five invariants; violating one is a rejected PR:
Nothing is ever erased. No code path may mutate or delete committed log bytes. Update = append a superseding fact; delete = RETIRE.
Replay determinism. Every replay-visible state change goes through store.apply(); new index state must also be checkpointed and reset.
Write-then-apply. Memory changes only after bytes are durably written; events before their supersession markers.
The hash chain covers every committed line identically in commit, replay and raw ingest.
Zero Go dependencies. The empty require block in go.mod is a feature.
A new CeQL capability is complete only with parser + executor + tests, a section in the CeQL Book, an entry in the autocomplete catalog (its examples must parse — the catalog test enforces this), and, if user-facing, a dashboard surface: features without one don't exist for users. Documentation honesty is policy — comparison tables state what Centauri does not do. Ideas go to issues (bug and feature templates); accepted features get implemented.
go vet ./... && go test ./...
cd sdk/python && python -m unittest discover -s tests
# then open a PR — Claude reviews it, the Perf gate runs, a human merges
The docs/ folder keeps the reasoning, not just the result. Design notes: own-your-data storage (your data never lives on our infrastructure), vision, tablespaces, segmentation & Merkle proofs, sync, object-store backend. Status matrices: enterprise readiness (the live ✓/partial/✗ table), plus dated review snapshots (audit-updated-capabilities.md, enterprise-deployment-gaps.md, production-suggestions.md, product-positioning-audit.md). Messaging: positioning.md is the source of truth for how Centauri is described, and db-engines-submission.md is the DB-Engines listing request.
License: the MIT License — OSI-approved, permissive, free for any use including commercial, no copyleft. Copyright JacobLabs LLC and contributors. There is no paid edition: the binary you download is everything. Related pages on this site: how it works, internals vs Oracle, gateway comparison, showcase.
Honesty is policy
What Centauri does NOT do
Every line below is stated in the repository docs. Comparison tables say what Centauri does not do, and a ✗ is never inflated to a ✓.
Query & data model
Not a general SQL database. The SQL surface is a read-only SELECT subset; writes use CeQL.
No JOINs — in SQL or CeQL. No subqueries, window functions, or SQL COUNT(DISTINCT) yet.
The Postgres wire listener returns every column as TEXT, authenticates with the token as a cleartext password, has no ?db= routing, and rejects non-SELECT.
No multi-statement BEGIN…COMMIT MVCC transactions. No foreign-key constraints (ref types are roadmap). No cost-based optimizer.
CeQL aggregates run over current facts, not history. CeQL strings have no escapes — use the JSON AST for awkward values.
Write path & scale
Single writer per log. Sharded writes, read replicas and HA failover exist; multi-writer clustering and high-contention multi-writer OLTP do not.
Sharded mode: no cross-shard atomic transactions, causal links/trace, or aggregates.
The working set lives in RAM; the tablespace/archive tier serves bigger-than-memory data read-only, with no persisted index for arbitrary cold WHERE; vector similarity and causal weighting need the in-RAM index.
Object store: read-only cold tier; writes and sealing still target local disk; GCS/Azure signers are design-only.
Sync: no push mode/pairing, no browser/edge replica; sync promises the same set of facts, not byte-identical logs.
Security & operations
No Vault / KMS / cloud Secrets Manager integration — secrets are env vars or a 0600 key file.
At-rest encryption covers sealed segments only; the hot tail, manifest and checkpoint are not encrypted — use disk/volume encryption. End-to-end encryption of the live log is planned.
No hot certificate reload; no mTLS on serve (gateway proxy listener only); no OpenTelemetry trace spans.
OIDC validates the IdP's tokens; Centauri is not an identity provider for people. No role hierarchies, LDAP/SAML, or claim-to-ACL mapping.
Retention RETIREs — the log file never shrinks. No in-binary policy scheduler and no crypto-erase retention action yet.
centauri verify proves integrity only against an externally recorded chain head.
AI
The engine bundles no model; AI features orchestrate an external runtime (Ollama) and an external PDF renderer — optional add-ons, not built in.
GLM-5.2 is cloud-only (200+ GB weights), off by default, sends prompts and retrieved facts off-machine, and costs money. Local tiers top out at glm-4.7-flash.
Without a chat model, ASK answers only from kb:* facts and logs gaps — it does not guess. Without an embedder, SEARCH is pure BM25.
"Learning" means accumulating facts, not retraining weights. No drag-and-drop spreadsheet import for non-technical users yet.
Gateway
Not a Kubernetes ingress controller. Not a plugin ecosystem — a fixed middleware set plus CePL policies and webhook callouts; no third-party plugin API or marketplace.
CePL policies cannot transform requests or responses. Token issuing is client_credentials only.
The control plane is single-writer with no leader election; no cross-gateway audit merge; no multi-DC; no commercial support.
~26k req/s on a 2-core dev VM; NGINX and Kong still win at the edge. SSE failover applies only until the first byte. Bodies are never recorded in audit facts.
Source documents for every claim on this page: docs/ in the repository — quickstart.md, sql.md, ai-features.md, local-ai.md, vision-setup.md, gateway.md, design-tablespaces.md, design-segmentation.md, object-store-backend.md, design-sync.md, modeling-hierarchies.md, deploy.md, performance.md, enterprise-readiness.md — plus the SDK and connector READMEs.