DocsEngineeringArchitecture

Architecture

The complete runtime topology and communication model.

Chronoverse separates synchronous domain ownership from asynchronous orchestration. The HTTP gateway serves browser and external clients, gRPC services own domain state, and Kafka workers perform delayed or distributed work.

Runtime topology

HTTP gatewaySessions · CSRF · REST · SSE
gRPC domains
usersworkflowsjobsnotificationsanalytics
Kafka workers
schedulerworkflowexecutionjob logsanalyticsoutbox
Runtime data plane
runtime-agentDocker proxyruntime_nodes
infrastructure
PostgreSQLClickHouseRedisKafkaMeilisearchLGTM
Synchronous domain ownership with asynchronous, replay-safe orchestration.

The production entry point is Nginx. It serves the dashboard, proxies /api/... to the HTTP server, and disables response buffering for live job log routes.

Domain services

Five gRPC services own users, workflows, jobs, notifications, and analytics. They expose ports 50051 through 50055 inside Compose.

Asynchronous plane

Kafka carries workflows, jobs, job_logs, and analytics topics. Scheduling, workflow preparation, container execution, retained log processing, analytics, and outbox publication scale independently within topic and infrastructure constraints.

Persistence plane

  • PostgreSQL: transactional state, runtime nodes, leases, idempotency, outbox rows, and analytics.
  • ClickHouse: retained high-volume job logs.
  • Redis: sessions, cached reads, live log pub/sub, and image-pull locks.
  • Meilisearch: retained log search.
  • Runtime agent: Docker-capable node registration, heartbeat, and capacity metadata.
  • Docker proxy: token-authenticated, method-and-path-allowlisted node-local container lifecycle access.
  • LGTM: OpenTelemetry collection and dashboards.

Design invariant

Workers request state transitions from domain services rather than directly inventing workflow or job state. This keeps replay and ownership rules concentrated at the domain boundary.

Container execution has an additional locality invariant: a container_id is never enough on its own. jobs-service assigns a fresh READY runtime node during claim, persists runtime_node_id and a Docker endpoint snapshot on the job, and workers use that endpoint directly for execution, live logs, cleanup, and expired-lease recovery.

Repository architecture notesdocs/architecture.md