Reference

Architecture

How the pieces fit — the React app's five sections, the Fastify server's core and eight modules around its single query choke point, one native DuckDB engine per workspace, and the Drizzle metadata store.

┌──────────────── React 19 + Vite + Tailwind v4 (two themes, one kit) ────────────────┐
│ Home      #/                  Agent Home: one prompt, Needs you, Continue            │
│ Library   #/library           dashboards · Mosaic dashboards · notebooks (Stories,   │
│                               Canvas) · saved queries · apps · briefs · missions     │
│           editors             SQL workbench #/library/queries · notebook · grid and  │
│                               Mosaic dashboard · data app — each an EditorPage       │
│ Data      #/data/tables       tables, views and files; a table's page (overview,     │
│                               columns, checks, lineage & access); Prepare editor     │
│           #/data/sources      connections and folders · #/data/pipelines syncs,      │
│                               streams, reverse ETL · #/data/models dbt ·             │
│                               #/data/metrics semantic layer and monitors             │
│ Activity  #/activity          needs you (approvals, failing checks, insights) · runs │
│                               · alerts and snapshots · comments                      │
│ Settings  #/settings/<page>   Profile · Workspace · People · AI · Developers · Admin │
│ ⌘K        every command, object, table, page and setting; "Ask about this" in "..."  │
└──────────────┬────────────────────────────────────────────────────────────────────────┘
               │ REST · WebSocket (rows; one shared live-event stream) · SSE (missions, MCP) · Streamable HTTP
┌──────────────▼────────────────────────────────────────────────────────────────────────┐
│ Fastify 5 (TypeScript strict) — a core and eight modules (src/modules/<name>)         │
│  core: config · metadata store · engines · cipher · auth (local, OIDC, SCIM, tokens)  │
│        workspaces · QueryService (authz → SQL guard → access policies → HITL → audit) │
│        result cache · JobRunner (one jobs table) · RunStore (one runs table)          │
│  Sources     connections table + drivers · cloud · databases · lakehouse · connectors │
│  Pipelines   syncs · streams · CDC · reverse ETL · orchestration                      │
│  Modelling   lineage · dbt · semantic layer · quality · insights · Prepare            │
│  Analysis    workbench · saved queries · dashboards · Mosaic · notebooks · comments · │
│              Library · builder · Canvas · exports · search                            │
│  Delivery    data apps (subprocess · docker · k8s, own origin) · alerts · snapshots · │
│              embeds · query endpoints · style guide                                   │
│  AI          model settings · inline actions (assist) · agent runtime (missions,      │
│              scheduled missions, briefs) · A2A · CLI · OAuth · Slack · Teams          │
│  Governance  teams · access policies · approvals · Activity · audit export · SCIM ·   │
│              tags · PII · watches                                                     │
│  Platform    cluster · notification channels · quotas · usage · templates ·           │
│              Postgres protocol · revisions · Git · live events                        │
│  Tool registry (94 tools) → MCP (100 with the agent's own · 4 resources · 6 prompts)  │
│               + REST façade /api/agent/v1/tools + OpenAPI 3.0                         │
├───────────────────────────────────────────────────────────────────────────────────────┤
│ EngineManager ─ one DuckDB instance per workspace (LRU + idle TTL); cluster: a lease  │
│  filesystem jail (Node) + allowed_directories / enable_external_access + lock_config  │
├───────────────────────────────────────────────────────────────────────────────────────┤
│ Metadata store (Drizzle): SQLite by default · PostgreSQL via DATABASE_URL             │
│  jobs · runs · connections · approvals · counters · exports · agent_* · ai_usage · …  │
└───────────────────────────────────────────────────────────────────────────────────────┘

The one path every query takes#

Whether it comes from the workbench, a WebSocket stream, a dashboard widget, an MCP tool or the REST façade, SQL goes through QueryService:

  1. Access — WorkspaceService.get(principal, workspace, minRole) resolves the effective role (owner, direct grant, team grant; admins from the UI) — 404 for non-members, 403 for insufficient roles.
  2. Authorization — scope (read / write / admin), platform role, workspace role, access policies (row filters and column masks rewritten into the statement), and the human-in-the-loop gate for agents.
  3. Cache — for read-only, deterministic statements a key built from the referenced files' size:mtime and the workspace data epoch is checked first.
  4. Guard — the lexer classifies statements and rewrites path literals into the jail; extension and setting policies apply.
  5. Engine — a fresh DuckDB connection with a timeout (interrupt() on cancel), row caps and cell truncation.
  6. Audit — one row per execution with actor, workspace, SQL, duration, status; a mutation also moves the data epoch and notifies members over the live feed.

ZAANIX Agent#

The agent runtime lives in the server's AI module (modules/ai/agent-runtime): a decision engine that picks context and tools without a model, a context engine that ranks what the workspace holds within a token budget, and the model. It runs as the person — reads call the services with their identity, tools go through the same tool registry as MCP with the agent as the actor — so every call meets the QueryService choke point above, and changes wait for approval. Missions, tasks, briefs, memories and held calls live in the metadata store, so a restart picks up where it was. Inline AI actions (POST /api/agent/assist) are single model calls through the same governance. See ZAANIX Agent.

Jobs, runs and connections#

One JobRunner and one jobs table schedule everything — syncs, alerts, snapshots, dbt, quality suites, monitors, reverse syncs, watches, scheduled missions and the system's own housekeeping — and run each due job exactly once, also in a cluster. Every run of every kind is a row of one runs table, read by each feature in its own shape and by Activity › Runs all together. Every stored connection (secrets, cloud, lakehouse, database, connector) is a row of one connections table, its credentials encrypted with the row id as associated data. Nothing a feature needs is kept only in a process's memory: exports, rate counters, single-use tokens and orchestration waits are in the metadata store too.

Cluster mode#

Several nodes can share one PostgreSQL metadata store and a ReadWriteMany data directory. A lease names the node that opens each workspace's DuckDB file; other nodes forward that workspace's queries to it, and scheduled work is claimed by one node at a time. See Running ZAANIX for a team.

Engines#

EngineManager keeps one DuckDBInstance per workspace, LRU-bounded (max_cached_engines) and idle-evicted (engine_idle_ttl_seconds). Each engine starts with memory/thread/temp settings, loads allow-listed extensions, applies the owner's cloud and lakehouse secrets and ATTACHes catalogs, then locks the configuration. Secrets and attachments can be re-applied to a running engine without losing in-memory tables; a :memory: engine that restarts moves the workspace epoch so cached results over its (now gone) tables are not served.

Project layout#

packages/server/src
  config/ db/ engine/ security/     loader, Drizzle schemas and migrations, the jail and SQL guard, crypto
  context.ts · services/            the core: auth, workspaces, query, cache, jobs, runs, counters, audit
  modules/<name>/                   sources · pipelines · modelling · analysis · delivery · ai · governance · platform
                                    each with its services, routes/ and tools.ts, registered through modules/kernel.ts
  agent/ · mcp/                     the tool registry (one list for MCP and REST), OpenAPI, the MCP server
  observability/                    pino, prom-client, OpenTelemetry, the live event bus
packages/web/src
  app/                              routes (five sections; every old hash redirects), settingsNav
  components/ui · page · data       the kit, the three page templates (List, Object, Editor), result grids
  features/                         agent · library · data · activity · settings · workspace · notebooks · dashboards · …
  store/ · lib/ · theme/            zustand stores, one shared live-event socket, Light and Dark tokens
packages/agent-cli                  zaanix-agent, served by the server
packages/sdk-python                 the zaanix Python SDK

Tests#

More than 820 tests: the server's 649 boot real DuckDB engines and the MCP server over every transport, serve real Iceberg tables through a mock REST catalog, emulate Databricks, warehouses, SaaS and Google APIs, run migrations on SQLite and PostgreSQL, and pin every route and tool; the web app's 170 cover routes and redirects, the kit, the page templates and the themes' contrast. Browser checks drive the real UI (60+ scenarios, a visual QA of both themes, inline AI and no-model walks), and scripts/smoke.mjs verifies a running instance or the Docker image.