Gen — Containers (L2)¶
The Gen slice of the platform-level container diagram, with Gen-specific notes.
Gen Web App¶
- Tech: Next.js 15.1, React 19, TypeScript 5.
- AI:
@ai-sdk/openai+@ai-sdk/react(useChathook); 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-kitfor 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 withjson/httpfs/spatial/polyglot/nanoarrow/h3extensions). Each query gets its own scratch schema; cleanup is unconditional infinally. - OpenAPI: served at
/swagger; consumed byaxion.gen.webvia 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
/hangfiredashboard, heavy-binary host (Tippecanoe lives here, not in the API image). - Defaults:
WorkerCount = 2, queues["default", "critical"], server namegen-worker-{MachineName}. Bound fromHangfire:config. - Jobs registered in
Worker/Program.csas scoped services: TableProfileRefreshJob— refreshtable_profile_cachefrom the connector.MapTilesGenerationJob— pages source rows, runs Tippecanoe (-Z14 -z16for points,-zgotherwise), uploads PMTiles totiles/{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:
- 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.
- 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.
- 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. |