EKOS's Key Invariant says AI consumes knowledge through the Runtime only, never
raw systems directly. RFC 0056 is the first place that line gets crossed on purpose: ClickHouse
schema compiled into the ledger like any other source, plus ekos clickhouse
ask — an LLM-built, SELECT-only, redacted, ledger-audited live query. Every screen on this
page is real output from a real ClickHouse container, unedited, including the queries that failed.
ekos clickhouse askAI systems consume reconstructed knowledge through the Runtime only; they never touch raw enterprise systems directly.
A repo-wide read confirmed it, before design started: every MCP tool, the AiRuntime, and every connector in this codebase only ever touched the
local ledger — even crates/simulation, the closest precedent.
RFC 0056 treats the live-query capability as an explicit, scoped, audited
exception to that invariant, not a quiet extension of it: SELECT-only, redacted on
the way out, and every call recorded as evidence — never a silent second path around the
Runtime.
$ ekos build Build complete. Connectors re-scanned: 7 $ ekos recover clickhouse-analyzer complete tables=2 Recover complete. ClickHouse tables analysed: 2 $ ekos resolve && ekos compile && ekos commit Commit complete. Objects written: 2 Evidence records: 2
ClickHouseObserver reads ClickHouse's stock HTTP interface
(system.tables/system.columns, no
native driver) and ClickHouseAnalyzerPass compiles every table
into a real ObjectKind::Table KIR object — the exact
build.rs/recover.rs wiring the RFC 0012
Snowflake/Oracle scaffolds never got. No LLM call in this stage, no new invariant surface.
Object: ekos_test.orders (Table) Properties: source_system: "clickhouse" engine: "MergeTree" columns: [{"data_type":"UInt64","name":"order_id"}, {"data_type":"String","name":"customer_name"}, {"data_type":"Decimal(10, 2)","name":"amount"}, {"data_type":"String","name":"status"}, {"data_type":"DateTime","name":"created_at"}] Evidence: [100%] clickhouse:ekos_test.orders — "table ekos_test.orders observed via ClickHouse system.tables/system.columns"
This is the same ObjectKind::Table kind file-based SQL
DDL already compiles to — not a new Custom("ClickHouseTable") kind. A
ClickHouse orders table and a Postgres orders
table with overlapping columns become real cross-system identity candidates for free, with zero
new exclusion-list code in crates/identity.
Stage 03 grounds the LLM in the real compiled schema
from Stage 1, never a guess. Stage 04 parses the generated SQL with
sqlparser's real ClickHouseDialect and hard-
rejects anything that isn't exactly one Statement::Query — no
INSERT/DROP, no multi-statement batches, a
LIMIT injected into the parsed AST if missing. Only SQL that
sqlparser itself parsed and re-emitted ever reaches ClickHouse.
$ ekos clickhouse ask --json "how many orders have status shipped?"
{
"sql": "SELECT COUNT(order_id) FROM ekos_test.orders WHERE status = 'shipped' LIMIT 1000",
"rows": [{ "COUNT(order_id)": "3" }],
"audit_event_id": "be4f1866-7fe2-475e-aaf5-32566cbd1d0d"
}
Seed data: 5 orders, status shipped on rows 1, 3, 5 —
3 is the correct answer, verified against the fixture that was actually
inserted, not assumed correct because the pipeline didn't error.
$ ekos clickhouse ask "list the 3 highest-amount orders, showing customer_name and amount"
Error: query execution failed: api error 404: Code: 47. DB::Exception:
Unknown expression identifier `name` in scope SELECT name, amount FROM
(... WHERE status = 'completed') ... (UNKNOWN_IDENTIFIER)
The model invented a status = 'completed' filter — that
value never existed in the real data — and referenced name instead of
the aliased customer_name. It's still exactly one well-formed
SELECT, so the safety gate has nothing to reject; ClickHouse's real
schema does the rejecting instead. No row returned, no ledger entry written, no confident wrong
answer — a plain error surfaced straight through.
SELECT entry_type, count(*) FROM entries GROUP BY entry_type; event | 3 evidence | 6 object | 3
3 event rows for the 3 queries that actually succeeded against
ClickHouse — the 2 that errored (a bad join, the hallucinated filter above) left no trace,
because record_query_event only runs after execution succeeds.
6 evidence rows: 2 table evidences from Stage 1, 3 query-SQL
evidences from Stage 2, 1 unrelated file evidence. Row data itself is never ledgered — only
the fact that a query ran, its SQL, and a content-hash of the result.
tools/list -> contains ekos_clickhouse_query: true
tools/call -> {"COUNT(order_id)": "3"}
tools/list -> contains ekos_clickhouse_query: false
tools/call -> isError: true
"ekos_clickhouse_query is disabled -- set
[clickhouse].enable-mcp-query = true"
The gate is enforced twice, independently: hidden from
tools/list so a well-behaved agent never sees it, and re-checked inside
call_tool so a client calling it by name directly — without ever having
listed it — is still rejected.
# compile the schema once $ export EKOS_CLICKHOUSE_URL=http://localhost:8123 $ export EKOS_CLICKHOUSE_DATABASE=analytics $ ekos build && ekos recover # ask a real question, live, redacted, audited $ ekos clickhouse ask "how many orders shipped last week?"