Architecture
Eneo follows a modern microservices architecture with clean separation of concerns, designed for scalability, maintainability, and deployment flexibility.
High-Level Overview
Core Components
Frontend (SvelteKit)
Technology Stack:
- Framework: SvelteKit with TypeScript
- Styling: Tailwind CSS
- State Management: Svelte stores
- HTTP Client: Fetch API with type-safe wrappers
Responsibilities:
- User interface and user experience
- Client-side routing and navigation
- Form handling and validation
- Real-time updates via SSE and WebSockets
- State management and caching
Key Features:
- Server-side rendering (SSR) for SEO
- Hot module replacement for development
- Type-safe API client generation
- Responsive design
- Dark mode support
Directory Structure:
frontend/apps/client/
├── src/
│ ├── routes/ # SvelteKit routes
│ ├── lib/ # Shared utilities
│ │ ├── api/ # API client
│ │ ├── stores/ # State stores
│ │ └── components/ # Reusable components
│ └── app.html # HTML template
└── package.jsonBackend (FastAPI)
Technology Stack:
- Framework: FastAPI (Python 3.11+)
- ORM: SQLAlchemy 2.0
- Validation: Pydantic v2
- Authentication: JWT tokens, OIDC
- API Documentation: OpenAPI/Swagger
Responsibilities:
- RESTful API endpoints
- Business logic and validation
- Authentication and authorization
- Database operations
- AI provider integration
- WebSocket connections
Architecture Pattern:
- Domain-Driven Design (DDD)
- Repository Pattern for data access
- Service Layer for business logic
- Dependency Injection for modularity
Directory Structure:
backend/
├── app/
│ ├── main.py # Application entry point
│ ├── api/ # API endpoints
│ │ ├── routes/ # Route handlers
│ │ └── dependencies.py # DI dependencies
│ ├── core/ # Core functionality
│ │ ├── config.py # Configuration
│ │ ├── security.py # Auth & security
│ │ └── database.py # DB connection
│ ├── models/ # SQLAlchemy models
│ ├── schemas/ # Pydantic schemas
│ ├── services/ # Business logic
│ │ ├── ai/ # AI integrations
│ │ ├── embeddings/ # Vector embeddings
│ │ └── documents/ # Document processing
│ └── worker.py # Background worker
└── tests/Key Design Patterns:
Repository Pattern:
class UserRepository:
def __init__(self, db: Session):
self.db = db
def get(self, user_id: int) -> User:
return self.db.query(User).filter(User.id == user_id).first()
def create(self, user_data: UserCreate) -> User:
user = User(**user_data.dict())
self.db.add(user)
self.db.commit()
return userService Layer:
class AIService:
def __init__(self, user_repo: UserRepository, ai_provider: AIProvider):
self.user_repo = user_repo
self.ai_provider = ai_provider
async def generate_response(self, prompt: str, user_id: int) -> str:
user = self.user_repo.get(user_id)
# Business logic here
return await self.ai_provider.generate(prompt)Database (PostgreSQL + pgvector)
Technology:
- Database: PostgreSQL 13+
- Extension: pgvector for vector similarity search
- Migrations: Alembic
Schema Design:
Core Tables:
users- User accounts and profilesorganizations- Organization/tenant dataspaces- Collaborative workspacesassistants- AI assistant configurationsconversations- Chat conversationsmessages- Individual messagesdocuments- Uploaded documentsdocument_chunks- Chunked document text with embeddings
Key Relationships:
organizations (1) ─── (N) users
organizations (1) ─── (N) spaces
spaces (1) ─── (N) assistants
spaces (1) ─── (N) documents
users (1) ─── (N) conversations
conversations (1) ─── (N) messages
documents (1) ─── (N) document_chunksVector Search:
-- Example vector similarity search
SELECT id, content,
1 - (embedding <=> query_embedding) as similarity
FROM document_chunks
WHERE 1 - (embedding <=> query_embedding) > 0.7
ORDER BY embedding <=> query_embedding
LIMIT 5;Indexes:
- B-tree indexes on foreign keys
- IVFFlat index on vector embeddings
- GiST indexes for full-text search
Durable Object Content
Durable content has one PostgreSQL control plane and one explicit byte backend per record:
| PostgreSQL control plane | Byte backend |
|---|---|
| Content identity and lifecycle state | Bounded postgres_inline payload or private object_store object |
| Canonical SHA-256, exact size, and media type | Payload bytes only |
| Concrete authorization references, holds, and retention | Backend-specific transport/deletion state |
| Audit and bounded reconciliation/query facts | No authorization, routing, or product policy |
Inline bytes, control state, and the first owner reference commit atomically. Object-store intent and the first reference commit before remote work. Eneo verifies the PostgreSQL-owned SHA-256 before returning full or range responses, and a bounded reconciler resolves process and network failures.
Object storage is optional. The default reference deployment is inline-capable without another service. Administrators can enable an Eneo-built and attested SeaweedFS profile or use external endpoints such as MinIO through one vendor-neutral contract.
Read Object Content Architecture for the complete lifecycle, integrity, reconciliation, binding, and image publication design. The Object Content Storage guide covers deployment and operations.
Worker Service (ARQ today)
Technology:
- Queue: Redis-based ARQ (Async Redis Queue)
- Language: Python with async/await
- Concurrency: Configurable worker pool
Responsibilities:
- Document processing (PDF, Word, etc.)
- Web crawling and content extraction
- Embedding generation
- Background tasks and scheduled jobs
Object-content reconciliation is implemented as a queue-neutral async task. Only the current registration and schedule adapter depends on ARQ. A future worker change therefore does not alter the object store, lifecycle, or database contract, and Eneo avoids a speculative generic queue framework in the meantime.
Task Types:
Document Processing:
@worker.task
async def process_document(ctx, document_id: int):
# Extract text from document
text = await extract_text(document_id)
# Chunk the text
chunks = chunk_text(text)
# Generate embeddings
embeddings = await generate_embeddings(chunks)
# Store in database
await store_chunks(document_id, chunks, embeddings)Web Crawling:
@worker.task
async def crawl_website(ctx, url: str, max_depth: int):
async with Crawler() as crawler:
pages = await crawler.crawl(url, max_depth=max_depth)
for page in pages:
await process_page_content(page)Audit Logging
Audit logging captures security- and compliance-relevant actions across tenants. Logs are persisted in PostgreSQL, exports are handled by the worker, and retention is enforced by a daily purge job.
See Audit Logging for implementation details, retention hierarchy, and export options.
Redis (Cache & Queue)
Responsibilities:
- Session storage
- Task queue for ARQ
- Caching frequently accessed data
- Rate limiting counters
- WebSocket pub/sub
Cache Strategy:
- Session data: 24-hour TTL
- API responses: 5-minute TTL
- Embeddings: 1-hour TTL
- User preferences: No expiration
Reverse Proxy (Traefik)
Responsibilities:
- SSL/TLS termination
- Load balancing
- Request routing
- Automatic certificate management (Let’s Encrypt)
- Health checks
Configuration:
# Route HTTP to HTTPS
- "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
# Backend routing
- "traefik.http.routers.backend.rule=Host(`eneo.example.com`) && PathPrefix(`/api`)"
- "traefik.http.routers.backend.tls.certresolver=myresolver"
# Frontend routing
- "traefik.http.routers.frontend.rule=Host(`eneo.example.com`)"
- "traefik.http.routers.frontend.tls.certresolver=myresolver"Data Flow
User Authentication Flow
User → Frontend → Backend → Database
↓
Generate JWT
↓
Response ← Frontend ← UserAI Chat Flow
User types message
↓
Frontend sends POST /api/chat
↓
Backend validates & authenticates
↓
Retrieve relevant documents (RAG)
↓
Generate prompt with context
↓
Stream to AI Provider (OpenAI/Claude)
↓
Stream response back via SSE
↓
Frontend displays streaming text
↓
Save conversation to databaseDocument Upload Flow
- User uploads file via
POST /api/v1/files/ - Backend saves metadata to
InfoBlobstable and enqueues processing task - Worker picks up task from Redis (ARQ)
- Processing pipeline: extract text → chunk (200 tokens) → generate embeddings
- Store chunks and vectors in
InfoBlobChunks(pgvector) - Update job status to
COMPLETEorFAILED - Frontend polls or receives WebSocket notification
Security Architecture
Authentication Layers
-
Session-based (default)
- JWT tokens stored in httpOnly cookies
- CSRF protection
- Token refresh mechanism
-
OIDC (optional)
- Integration with enterprise IdP
- Automatic user provisioning
- Single sign-on (SSO)
Authorization Model
Role-Based Access Control (RBAC):
- Global Roles: Admin, User
- Space Roles: Owner, Editor, Viewer
- Resource-level: Per-assistant, per-document permissions
Permission Checks:
def check_permission(user: User, resource: Space, action: str) -> bool:
membership = get_membership(user.id, resource.id)
return membership.role.has_permission(action)Data Security
- At Rest: Database encryption (optional)
- In Transit: TLS 1.3 everywhere
- Secrets: Environment variables, never in code
- API Keys: Hashed storage, regular rotation
Scalability Considerations
Horizontal Scaling
Stateless Services:
- Frontend and Backend are stateless
- Can run multiple instances behind load balancer
- Session state in Redis (shared)
Scaling Strategy:
# Docker Compose scaling
docker compose up -d --scale backend=3 --scale worker=2Database Scaling
Read Replicas:
- Primary for writes
- Replicas for reads
- Connection pooling (PgBouncer)
Partitioning:
- Partition large tables by date
- Separate hot and cold data
Caching Strategy
Multi-Level Cache:
- Browser cache (static assets)
- CDN cache (public content)
- Redis cache (dynamic data)
- Application cache (in-memory)
Deployment Architectures
Single Server (Small Organizations)
All services on one server with Docker Compose:
- Frontend, Backend, Worker
- PostgreSQL, Redis
- Traefik
Suitable for: < 100 users
Multi-Server (Medium Organizations)
Separate servers for different roles:
- Web Server: Frontend + Backend
- Database Server: PostgreSQL
- Worker Server: Background processing
- Cache Server: Redis
Suitable for: 100-1000 users
Kubernetes (Large Organizations)
Fully orchestrated with K8s:
- Auto-scaling pods
- Service discovery
- Rolling updates
- Health checks and self-healing
Suitable for: 1000+ users
Monitoring & Observability
Logging
Structured Logging:
{
"timestamp": "2025-09-30T10:30:45Z",
"level": "INFO",
"service": "backend",
"user_id": "user-123",
"request_id": "req-456",
"message": "AI request completed",
"duration_ms": 1250
}Metrics
Key Metrics:
- Request rate and latency
- Error rates (4xx, 5xx)
- Database query performance
- AI provider response times
- Worker queue depth
Health Checks
Endpoints:
GET /api/health- Basic healthGET /api/health/db- Database connectivityGET /api/health/redis- Redis connectivityGET /api/health/ready- Readiness probe
Technology Decisions
Why SvelteKit?
- Lightweight and fast
- Excellent developer experience
- Built-in SSR and routing
- Smaller bundle sizes than React
Why FastAPI?
- Modern Python async framework
- Automatic OpenAPI documentation
- Type safety with Pydantic
- High performance (comparable to Node.js)
Why PostgreSQL + pgvector?
- Reliable and mature RDBMS
- Native vector support (pgvector)
- Excellent performance
- ACID compliance for data integrity
Why ARQ?
- Python-native async task queue
- Simple and reliable
- Redis-backed for speed
- Cron-like scheduling support
Future Architecture Plans
Planned Enhancements
- Kubernetes Native: Helm charts for K8s deployment
- Multi-Region: Geographic distribution
- Plugin System: Extensibility via plugins
- GraphQL API: Alternative to REST
- Event Sourcing: Audit trail improvements
- Microservices: Further service decomposition
Additional Resources
- Technical Diagrams: Architecture diagrams on GitHub
- API Documentation: OpenAPI Specification
- Database Schema: Schema documentation
- Contributing Guide: Development guidelines