Skip to content

Architecture

Nova runs as an 8-service Docker Compose stack. Each service has a single responsibility and communicates over HTTP.

ServicePortRole
orchestrator8000Agent lifecycle, task queue, pipeline execution, MCP tool dispatch, DB migrations
llm-gateway8001Multi-provider model routing via LiteLLM (Anthropic, OpenAI, Ollama, Groq, Gemini, Cerebras, OpenRouter, GitHub, Claude/ChatGPT subscription providers)
memory-service8002Embedding + hybrid semantic/keyword retrieval via pgvector
chat-api8080WebSocket streaming bridge for external clients
dashboard3000 / 5173React admin UI (nginx in production, Vite dev server in development)
postgres5432pgvector-enabled PostgreSQL 16
redis6379State, task queue (BRPOP), rate limiting, session memory
recovery8888Backup/restore, factory reset, service management. Only depends on postgres — stays alive when other services crash.

All communication between services is HTTP. Here’s who calls who:

dashboard ──proxy──▶ orchestrator (/api)
──proxy──▶ llm-gateway (/v1)
──proxy──▶ recovery (/recovery-api)
chat-api ──────────▶ orchestrator (streaming endpoint)
orchestrator ──────▶ llm-gateway (/complete, /stream, /embed)
──────▶ memory-service (/api/v1/memories/*)
──────▶ redis (task queue, state)
recovery ──────────▶ postgres (backup/restore)
──────────▶ Docker API (service management)

The dashboard depends only on the recovery service at startup. It shows a startup screen while other services come online, so users always have visibility into system state.

LayerTechnology
BackendPython, FastAPI, asyncpg, asyncio
FrontendVite, React, TypeScript, Tailwind CSS, TanStack Query
DatabasePostgreSQL 16 + pgvector
QueueRedis (BRPOP task dispatch)
ContainersDocker Compose with hot reload (watch mode)
Model routingLiteLLM (multi-provider abstraction)

The nova-contracts/ package defines the API contract between services using Pydantic models (chat, LLM, memory, orchestrator). Any service satisfying these models is a drop-in replacement. This is a Pydantic-only package with no runtime dependencies on any service.

Nova uses two different database access patterns:

ServiceAccess layerReason
orchestratorRaw asyncpg queriesPerformance-critical task queue operations, no ORM overhead
memory-serviceSQLAlchemy asyncComplex vector queries benefit from ORM expressiveness

Migrations run automatically at orchestrator startup from orchestrator/app/migrations/*.sql. These are pure versioned SQL files that run idempotently — no Alembic.

All tables use UUID primary keys, TIMESTAMPTZ for timestamps, and JSONB for flexible fields.

Each service uses a dedicated Redis database to avoid key collisions:

Redis DBService
0memory-service
1llm-gateway
2orchestrator
3chat-api
  • Raw JSON responses (no { data: ... } wrapper)
  • Admin auth: X-Admin-Secret header
  • API key auth: Authorization: Bearer sk-nova-<hash> or X-API-Key
  • Streaming: Server-Sent Events (SSE) with JSON lines
  • Auto-generated API docs at /docs on each FastAPI service