❯ AI · Platform · 2026
Groundwork
An AI analytics platform where every number traces to a query result — the model writes SQL, executes it, and narrates over facts, never from context.
The problem
Analytics tools have a trust problem that no feature can patch: the model answers from whatever happened to be in context, so a confident "revenue is up 12%" arrives with no way to check it. For someone making a decision off that number, that is not a quality bug. The product is not working.
The approach
The core decision was to take the model out of the number path. The model writes SQL; a disposable DuckDB engine executes it against the uploaded data; the model then narrates only what the query returned, never data-in-context. A single Go backend owns the whole run — a durable, resumable run/step DAG in Postgres with leases, heartbeats, idempotency keys, and LISTEN/NOTIFY — and streams progress to the UI over SSE. Go earns its place here: one binary, static typing across a concurrency-heavy orchestrator, and a first-party Claude SDK. DuckDB earns its place because it is embeddable and disposable per session, which is what makes scale-to-zero practical.
Architecture
- Analytics App (Next.js)
- Go API (chi)
- Run/Step DAG (Postgres)
- Ingest Pipeline
- Grounded-Answer Loop
- DuckDB Engine
- Claim Provenance
- Egress Audit
FLOWS
- Analytics App (Next.js)submit runGo API (chi)
- Go API (chi)SSE progressAnalytics App (Next.js)
- Go API (chi)uploadIngest Pipeline
- Go API (chi)orchestrateRun/Step DAG (Postgres)
- Run/Step DAG (Postgres)stepGrounded-Answer Loop
- Grounded-Answer Loopexecute SQLDuckDB Engine
- Grounded-Answer Loopbind claimsClaim Provenance
- Grounded-Answer LoopauditEgress Audit
Uploads (CSV, Excel, Parquet, SQLite) enter through an ingest pipeline: type inference with per-type fit-rate confidence badges, PII detection at ingest, and multi-file relationship inference with confidence scoring. Nothing runs against the data until the user confirms what was inferred.
A question creates a run, and the DAG walks it step by step. Leases let a crashed worker's step be re-run elsewhere; idempotency keys stop double execution; LISTEN/NOTIFY wakes the worker without polling. Inside the grounded-answer loop, verification is layered: a determinism cache, static SQL analysis before execution, post-execution reconciliation, and claim-level provenance that binds every number in the narrative to backing cells.
The stack is deliberately scale-to-zero: Fly.io per-session engine processes, Neon Postgres, Cloudflare R2 for Parquet storage. The web app is Next.js with first-party SVG chart rendering and Auth.js JWE cookie auth, so no charting library sits between the data and the reader.
Security & privacy
The egress audit is the guardrail that makes "every number traces to a query result" enforceable rather than aspirational. Every outbound narrative is checked: if a claim cannot be bound to a backing cell, the response is dropped and the run is marked failed. There is no path where an unverifiable number reaches the user.
Before any request goes to the model, a shared payload builder strips columns flagged as PII at ingest, so the model never sees data it was not allowed to see. Deleting a workspace deletes everything — files, runs, cached claims — in one cascade, because for an analytics product a half-deleted workspace is worse than no delete at all.
Key decisions
Model writes SQL, engine executes
The model's opinions stop at the query; the numbers come from the engine. This is what makes the answers auditable.
Disposable DuckDB per session
No warm warehouse to pay for between sessions; the engine spins up with the run and dies with it.
Fail-closed egress audit
If a response cannot be traced to a query result, it is blocked, not logged and shipped.
Layered verification, not a single check
The determinism cache, static analysis, and post-execution reconciliation each catch a different failure class.
One binary owns orchestration
The DAG, leases, and SSE live in one process instead of a queue plus a worker fleet.
What I'd do differently
The orchestrator started as a queue-like runner and became a DAG later, so early runs were written twice while leases were being tuned. Shipping the lease and idempotency design from the first commit would have saved a migration. I would also invest in the ingest confirmation UX earlier — confidence scores are only useful when a user can see why two files were paired.