Skip to content

Architecture

Enterium is a modular monolith: one FastAPI backend split into bounded contexts with CI-enforced boundaries, plus a React web UI, a set of immutable native pipelines executed in-process, and two small vendored services for publishing and media.

MCP + RESTrender + deployweekly metricsOwner / teamWeb UILLM agentsPlatform APIPostgreSQL+ pgvectorRedisExternal providersLLM · search · images ·storagePublishing hubFleet of static sitesSearch consoles

The API owns all state. External providers are reached only through the integrations context (a single audited channel); published sites are static properties built and deployed by the publishing hub; search metrics flow back in on a schedule.

platform FastAPI, Python 3.12Bounded contextsintegrations · runs ·research · retrieval · quality· publishanalytics · competitors ·cockpit · ops · tickets ·tasks · chatlog · specsNative pipelinesimmutable versioned dirs:collect · generate · localizeShared layersauth · lifecycle engine ·ORM · API routerswebuiReact 19 + TypeScript, Vitecontractsshared OpenAPI + JSONschemaswp_deployerpublishing hub serviceimagegenmedia generation service

The rules that keep it a monolith, not a tangle

Section titled “The rules that keep it a monolith, not a tangle”
  1. A bounded context is a vertical slice of one business capability — its logic, schemas, router and tests live in one folder (platform/app/contexts/<name>/).
  2. Contexts talk only through contracts. A context calls another only via its public contracts.py (typed interface) or an event — never by importing internals, never by querying another context’s tables. This is enforced by an import linter in CI: a violation is a red build, not a review comment.
  3. One table — one owning context. No cross-context SQL joins.
  4. New capability = new module, not new service. A separate service is the exception and needs multiple hard triggers (different runtime, secret-scope isolation, independent scaling). The two vendored services exist precisely because they met those triggers.
  5. The shared kernel stays thin — configuration, database, auth primitives, logging, event plumbing. No business logic.

The full map of contexts with their responsibilities is on Bounded contexts.

There is no permanently-running orchestrator daemon. Production runs on a nightly timer that dispatches the Collect → Generate → publish cycle across the contract fleet; between runs the platform is idle by design (an orchestrator.present: false status is normal — see Interpreting platform status). Every execution is recorded as a pipeline_run with per-step run_step rows, so history is queryable rather than inferred.

Pipelines themselves are native and in-process: each version is an immutable directory of Python steps resolved through the pipeline catalog. Failures are loud — an error in a step fails the run and is recorded in the error surface; there is no silent per-step retry (only the LLM adapter retries transient provider errors internally).