Skip to content

Gen — Containers (L2)

The Gen slice of the platform-level container diagram, with Gen-specific notes.

Container Diagram

Gen Web App

  • Tech: Next.js 15.1, React 19, TypeScript 5.
  • AI: @ai-sdk/openai + @ai-sdk/react (useChat hook); tool-calling at /api/agent/stream (Next.js API route acts as a BFF for the LLM provider).
  • Charts: ApexCharts, ECharts, Recharts, Vega/Vega-Lite — the agent picks one based on data shape.
  • Maps: 2GIS MapGL + Deck.gl layers; PMTiles via MapLibre pmtiles:// protocol.
  • DnD: @dnd-kit for dashboard widget layout.
  • i18n: next-intl, with Arabic RTL support.
  • Observability: Langfuse for LLM traces (separate from SigNoz).
  • Routes: /kpi, /sense-library, /login, /dev/test-chat (chat e2e test).
  • Repo: axion.gen.web/frontend/.

Gen API

  • Tech: .NET 10 + ASP.NET Core. REST on 5501, gRPC on 5502.
  • Solution layout:
  • Axion.Gen.Api — controllers, OIDC, Swagger, configuration. Hangfire registered client-only.
  • Axion.Gen.Core — operations (IOperation<TParam,TResult> pattern, same as Sense) and Hangfire job classes.
  • Axion.Gen.Data — EF Core 10 context + entities, big-data connectors, OpenFGA wiring, DuckDB provider.
  • Axion.Gen.Common — shared utilities, authorization helpers (IPermissionService).
  • API surface: dashboards / widgets / dataset CRUD, data-source CRUD with credential encryption, big-data SQL endpoints (/bigdata/query, /bigdata/query/stream, /bigdata/query/parse), maps (PMTiles, hexagons, clusters, filterIds), roles & permissions, agent discovery & chat sessions.
  • Federation engine: in-process DuckDB via DuckDbConnectionProvider (singleton with json/httpfs/spatial/polyglot/nanoarrow/h3 extensions). Each query gets its own scratch schema; cleanup is unconditional in finally.
  • OpenAPI: served at /swagger; consumed by axion.gen.web via Orval (similar to Sense).
  • Repo: axion.gen.backend/src.

Gen Worker

  • Tech: .NET 10 + Hangfire 1.8 (UsePostgreSqlStorage) + Tippecanoe.
  • Role: Hangfire job server, OIDC-gated /hangfire dashboard, heavy-binary host (Tippecanoe lives here, not in the API image).
  • Defaults: WorkerCount = 2, queues ["default", "critical"], server name gen-worker-{MachineName}. Bound from Hangfire: config.
  • Jobs registered in Worker/Program.cs as scoped services:
  • TableProfileRefreshJob — refresh table_profile_cache from the connector.
  • MapTilesGenerationJob — pages source rows, runs Tippecanoe (-Z14 -z16 for points, -zg otherwise), uploads PMTiles to tiles/{datasetId:N}/{tableName}/{mapId:N}.pmtiles.
  • OntologyTilesGenerationJob — produces ontology PMTiles for a dataset.
  • Storage: same Postgres as the API; Hangfire uses its own schema.
  • Repo: axion.gen.backend/src/Axion.Gen.Worker.

Why Gen is a separate backend

The platform could ship Gen's surface as a few extra controllers in Sense API. We split it for three reasons:

  1. Different shape of work. Sense API is request/response tied to a single org's transactional state. Gen API runs federated queries that may take seconds, talks to multiple data sources, and serves a chart-rendering UI. Mixing them complicates everything from the OIDC scope set to the Kestrel timeouts.
  2. Different deploy cadence. Gen evolves with analytics features and the AI agent's tool catalog; Sense evolves with the data-collection state machine. They don't need to ship together.
  3. Different blast radius. A misbehaving Gen federated query (timeout, runaway memory) should never put pressure on Sense's authn/authz path. Process isolation is cheap; coupling failure modes is expensive.

See ADR-0006.

Shared surfaces

Gen reuses, doesn't duplicate:

Concern Shared with Sense
OIDC IdP Same authority, possibly different client.
OpenFGA model Same authorization model — a user's access to Sense data flows through to Gen queries.
Postgres cluster Same physical Postgres (different database).
ClickHouse cluster Same — Gen queries it federated; Sense Worker writes it.
S3 Same — Gen reads Parquet, Sense reads/writes frames + PMTiles.
Observability (SigNoz) Same OTel collector.

Where to go next