Repository navigation
Backend Architecture
Canonical source:
docs/BACKEND_ARCHITECTURE.md— this page is rendered from it on every publish; edit the source.
Date: 2026-04-07 Scope: Backend services, data flow, queues, persistence, eventing, APIs, adapters, health, runtime
| Service | Port | Database | Status | Key Finding |
|---|---|---|---|---|
| auth-service | 4001 | PostgreSQL claw_auth | REAL | JWT + refresh rotation, RBAC (3 roles), user CRUD, password management. Missing: device session tracking |
| chat-service | 4002 | PostgreSQL claw_chat | REAL | Thread/message CRUD, context assembly, execution with fallback chain. Missing: token streaming, contextPackIds on thread |
| connector-service | 4003 | PostgreSQL claw_connectors | REAL | 5 provider adapters (OpenAI, Anthropic, Gemini, Bedrock, DeepSeek), health check, model sync, encrypted config |
| routing-service | 4004 | PostgreSQL claw_routing | REAL | 7 routing modes, Ollama-assisted AUTO routing, heuristic fallback, decision persistence. Missing: capability matching |
| memory-service | 4005 | PostgreSQL claw_memory (pgvector) | PARTIAL | CRUD works. Auto-extraction is a STUB (just logs). Internal endpoints for context retrieval work |
| file-service | 4006 | PostgreSQL claw_files | PARTIAL | Upload, validation, chunking (JSON/CSV/MD/text) work. NO vector embeddings, NO semantic search |
| audit-service | 4007 | MongoDB claw_audit | REAL | 10 event types handled, usage ledger with token/cost tracking, severity levels |
| ollama-service | 4008 | PostgreSQL claw_ollama | REAL | Model pull, list, generate, health check, role assignment (ROUTER/FALLBACK/REASONING/CODING). Roles are NOT consumed by routing |
| health-service | 4009 | None | REAL | Pings all services, aggregates status (healthy/degraded/unhealthy), used by Docker + frontend |
| client-logs-service | 4010 | MongoDB claw_client_logs | REAL | Batched ingestion from frontend, TTL 30 days, structured JSON, searchable |
| server-logs-service | 4011 | MongoDB claw_server_logs | REAL | RabbitMQ-driven ingestion from all backend services, Elasticsearch-ready schema |
Frontend → Nginx (4000) → Service endpoints
→ Auth guard (JWT validation on every request)
Message flow:
POST /chat-messages → chat-service creates USER message
→ publishes message.created (RabbitMQ)
→ routing-service receives message.created
→ calls Ollama router (AUTO mode) or uses heuristic
→ publishes message.routed
→ chat-service receives message.routed
→ ContextAssemblyManager.assemble():
- fetches memories from memory-service (HTTP)
- fetches file chunks from file-service (HTTP)
- builds prompt with token budgeting
→ ChatExecutionManager.execute():
- calls Ollama or cloud provider
- tries fallback chain on failure
→ stores ASSISTANT message
→ emits SSE completion event
→ publishes message.completed (RabbitMQ)
→ audit-service records usage + audit log
→ memory-service receives (but extraction is a STUB)
| # | Gap | Impact | Remediation |
|---|---|---|---|
| B1 | Memory extraction is a STUB | AI never learns from conversations | Implement extraction in handleMessageCompleted using Ollama |
| B2 | No background job processing | File processing and memory extraction block request threads | Add BullMQ for async jobs |
| B3 | RabbitMQ nack discards messages | Message loss on any handler failure | Add dead-letter exchange + retry queue |
| B4 | No contextPackIds on ChatThread | Context packs can't be attached to threads | Add schema field + migration |
| B5 | Ollama role assignments not consumed by routing | Role system is decorative | Wire routing to query assigned router model |
| # | Gap | Impact | Remediation |
|---|---|---|---|
| B6 | No vector embeddings for files | Can't do semantic file retrieval, only full-chunk injection | Add pgvector embedding column + embedding generation |
| B7 | No capability-based routing | Can't filter models by supportsVision/supportsTools | Add capability check before route selection |
| B8 | No token-by-token streaming | Users wait for full response | Implement chunked streaming from providers |
| B9 | No device session tracking | Can't manage active sessions per device | Add device fingerprint to Session model |
| B10 | No circuit breaker for provider calls | One failing provider can cascade failures | Add circuit breaker pattern |
| # | Gap | Impact |
|---|---|---|
| B11 | No request correlation from frontend to backend | Can't trace a user action across services |
| B12 | No feature flags / system settings consumed at runtime | Can't toggle features without redeployment |
| B13 | No database backup automation | Data loss risk |
auth-service (PostgreSQL claw_auth)
-
User— id, email, username, passwordHash, role (ADMIN/OPERATOR/USER), status (ACTIVE/INACTIVE/SUSPENDED), mustChangePassword, languagePreference, appearancePreference -
Session— id, userId, refreshToken, expiresAt, createdAt -
SystemSetting— id, key (unique), value, description
chat-service (PostgreSQL claw_chat)
-
ChatThread— id, userId, title, routingMode, lastProvider, lastModel, preferredProvider, preferredModel, isPinned, isArchived, systemPrompt, temperature, maxTokens -
ChatMessage— id, threadId, role (USER/ASSISTANT/SYSTEM/TOOL), content, provider, model, routingMode, routerModel, usedFallback, inputTokens, outputTokens, estimatedCost, latencyMs, feedback, metadata (JSON) -
MessageAttachment— id, messageId, fileId, type
connector-service (PostgreSQL claw_connectors)
-
Connector— id, name, provider (OPENAI/ANTHROPIC/GEMINI/AWS_BEDROCK/DEEPSEEK/OLLAMA), status, authType, encryptedConfig, baseUrl, region, isEnabled, defaultModelId -
ConnectorModel— id, connectorId, provider, modelKey, displayName, lifecycle, supportsStreaming, supportsTools, supportsVision, supportsAudio, supportsStructuredOutput, maxContextTokens -
ConnectorHealthEvent— id, connectorId, status, latencyMs, errorMessage, checkedAt -
ModelSyncRun— id, connectorId, status, modelsFound, modelsAdded, modelsRemoved, startedAt, completedAt, errorMessage
routing-service (PostgreSQL claw_routing)
-
RoutingDecision— id, messageId, threadId, selectedProvider, selectedModel, routingMode, confidence, reasonTags, privacyClass, costClass, fallbackProvider, fallbackModel -
RoutingPolicy— id, name, routingMode, priority, config (JSON), isActive
memory-service (PostgreSQL claw_memory, pgvector enabled)
-
MemoryRecord— id, userId, type (SUMMARY/FACT/PREFERENCE/INSTRUCTION), content, sourceThreadId, sourceMessageId, isEnabled -
ContextPack— id, userId, name, description, scope -
ContextPackItem— id, contextPackId, type, content, fileId, sortOrder
file-service (PostgreSQL claw_files)
-
File— id, userId, filename, mimeType, sizeBytes, storagePath, ingestionStatus (PENDING/PROCESSING/COMPLETED/FAILED) -
FileChunk— id, fileId, chunkIndex, content
ollama-service (PostgreSQL claw_ollama)
-
LocalModel— id, name, family, parameterSize, quantization, sizeBytes, format, digest -
PullJob— id, modelName, status, progress, error, startedAt, completedAt -
LocalModelRoleAssignment— id, modelId, role (ROUTER/FALLBACK_CHAT/REASONING/CODING), assignedAt
audit-service (MongoDB claw_audit)
-
AuditLog— userId, action, entityType, entityId, severity (LOW/MEDIUM/HIGH/CRITICAL), details, ipAddress, createdAt -
UsageLedger— userId, resourceType, action, quantity, unit, metadata, createdAt
client-logs-service (MongoDB claw_client_logs)
-
ClientLog— level, message, component, action, userId, route, userAgent, metadata, createdAt (TTL 30 days)
server-logs-service (MongoDB claw_server_logs)
-
ServerLog— level, message, serviceName, module, controller, action, requestId, traceId, userId, threadId, messageId, provider, latencyMs, errorCode, errorStack, metadata
| Model | Missing Field | Purpose |
|---|---|---|
| Session | deviceId, deviceName, ipAddress | Device session tracking |
| ChatThread | contextPackIds String[] | Attach context packs to threads |
| FileChunk | embedding Vector(384) | Semantic search via pgvector |
| MemoryRecord | embedding Vector(384) | Semantic memory retrieval |
POST /api/v1/auth/login — { email, password } → { tokens, user }
POST /api/v1/auth/refresh — { refreshToken } → { tokens }
POST /api/v1/auth/logout — (authenticated) → 204
GET /api/v1/auth/me — (authenticated) → user profile
POST /api/v1/users — (admin) create user
GET /api/v1/users — (admin) list users
GET /api/v1/users/:id — (admin) get user
PATCH /api/v1/users/:id — (admin) update user
PATCH /api/v1/users/me/password — change own password
PATCH /api/v1/users/me/preferences — update language/appearance
POST /api/v1/chat-threads — create thread (title, routingMode, systemPrompt, temperature, maxTokens, preferredProvider, preferredModel)
GET /api/v1/chat-threads — list threads (paginated, filterable)
GET /api/v1/chat-threads/:id — get thread
PATCH /api/v1/chat-threads/:id — update thread
DELETE /api/v1/chat-threads/:id — delete thread (cascades messages)
POST /api/v1/chat-messages — send message (threadId, content, routingMode?, provider?, model?, fileIds?)
GET /api/v1/chat-messages/thread/:threadId — list messages (cursor-paginated: before, limit)
GET /api/v1/chat-messages/:id — get message
POST /api/v1/chat-messages/:id/regenerate — regenerate response
PATCH /api/v1/chat-messages/:id/feedback — set feedback
SSE /api/v1/chat-messages/stream/:threadId — stream completion events
POST /api/v1/connectors — create connector
GET /api/v1/connectors — list connectors
GET /api/v1/connectors/:id — get connector
PATCH /api/v1/connectors/:id — update connector
DELETE /api/v1/connectors/:id — delete connector
POST /api/v1/connectors/:id/test — test health
POST /api/v1/connectors/:id/sync — sync models
GET /api/v1/connectors/:id/models — list models
GET /api/v1/internal/connectors/config?provider=X — (internal) get decrypted config
POST /api/v1/routing/evaluate — evaluate route (routingMode, message, forcedModel?, forcedProvider?)
POST /api/v1/routing/policies — create policy
GET /api/v1/routing/policies — list policies
PATCH /api/v1/routing/policies/:id — update policy
DELETE /api/v1/routing/policies/:id — delete policy
GET /api/v1/routing/decisions/:threadId — get routing history for thread
POST /api/v1/memories — create memory
GET /api/v1/memories — list memories (paginated, filterable by type)
GET /api/v1/memories/:id — get memory
PATCH /api/v1/memories/:id — update memory
DELETE /api/v1/memories/:id — delete memory
PATCH /api/v1/memories/:id/toggle — toggle enabled/disabled
POST /api/v1/context-packs — create context pack
GET /api/v1/context-packs — list packs
GET /api/v1/context-packs/:id — get pack with items
PATCH /api/v1/context-packs/:id — update pack
DELETE /api/v1/context-packs/:id — delete pack
POST /api/v1/context-packs/:id/items — add item
DELETE /api/v1/context-packs/:id/items/:itemId — remove item
GET /api/v1/internal/memories/for-context?userId=X&limit=N — (internal) get enabled memories
GET /api/v1/internal/context-packs/:id/items — (internal) get pack items
POST /api/v1/files — upload file (base64 content)
GET /api/v1/files — list files
GET /api/v1/files/:id — get file metadata
DELETE /api/v1/files/:id — delete file + chunks
GET /api/v1/files/:id/chunks — get file chunks
GET /api/v1/internal/files/:id/chunks — (internal) get chunks for context assembly
GET /api/v1/ollama/models — list installed models
POST /api/v1/ollama/pull — pull model from registry
POST /api/v1/ollama/assign-role — assign model to role
POST /api/v1/ollama/generate — (public) generate text
GET /api/v1/ollama/health — (public) health check
GET /api/v1/ollama/runtimes — list runtimes
GET /api/v1/health — (public) aggregated health status
GET /api/v1/audits — (admin) list audit logs
GET /api/v1/audits/stats — (admin) audit statistics
GET /api/v1/usage — (admin) usage entries
GET /api/v1/usage/summary — (admin) usage summary
GET /api/v1/usage/cost — (admin) cost summary
GET /api/v1/usage/latency — (admin) latency summary
- Name:
claw.events(topic exchange, durable)
| Event Pattern | Publisher | Subscribers | Payload |
|---|---|---|---|
message.created |
chat-service | routing-service | messageId, threadId, userId, content, routingMode, forcedProvider?, forcedModel? |
message.routed |
routing-service | chat-service | messageId, threadId, selectedProvider, selectedModel, routingMode, fallbackProvider?, fallbackModel? |
message.completed |
chat-service | audit-service, memory-service | messageId, threadId, assistantMessageId, provider, model, inputTokens, outputTokens, latencyMs |
thread.created |
chat-service | — | threadId, userId |
user.login |
auth-service | audit-service | userId, email |
user.logout |
auth-service | audit-service | userId |
connector.created |
connector-service | audit-service | connectorId, provider, name, userId |
connector.updated |
connector-service | audit-service | connectorId, changes |
connector.deleted |
connector-service | audit-service | connectorId, userId |
connector.synced |
connector-service | audit-service, routing-service | connectorId, modelsFound, modelsAdded |
connector.health_checked |
connector-service | audit-service, routing-service | connectorId, status, latencyMs |
routing.decision_made |
routing-service | audit-service | threadId, selectedProvider, selectedModel, confidence, reasonTags |
memory.extracted |
memory-service | audit-service | memoryId, userId, type |
file.uploaded |
file-service | — | fileId, userId, filename |
file.chunked |
file-service | — | fileId, chunkCount |
log.server |
all backend services | server-logs-service | structured log payload |
- No dead-letter exchange (DLX)
- No retry queue
- Failed messages are nack'd without requeue (discarded)
- No message TTL
These are things that LOOK complete but are NOT:
| Smell | Where | What's Wrong |
|---|---|---|
| Memory extraction handler | memory-service handleMessageCompleted | Just logs "Received message.completed" — no extraction logic |
| Ollama role assignments | ollama-service LocalModelRoleAssignment | Schema exists, CRUD exists, but routing-service never queries roles |
| Routing policies | routing-service RoutingPolicy | Full CRUD, but evaluateRoute() never reads policies |
| MessageAttachment model | chat-service schema | Model defined, but never populated — messages don't track file associations |
| Context pack thread attachment | chat-service | No contextPackIds field on ChatThread — packs can't be linked |
| SSE streaming | chat-service ChatStreamController | Only emits "done" event, no token-by-token chunks |
| File embedding | file-service FileChunk | pgvector extension enabled in memory-service but NOT in file-service — no embedding column |
Goal: Make handleMessageCompleted actually extract facts/preferences/instructions from conversations using Ollama.
Changes:
- memory-service AppConfig: Add
OLLAMA_SERVICE_URL - memory-service: New
MemoryExtractionManagerthat calls Ollama with extraction prompt - memory-service handleMessageCompleted: Call extraction manager, store extracted memories
- Extraction prompt: Ask Ollama to return JSON array of
{ type, content }extracted from the assistant response - Zod validate Ollama response, discard invalid entries
- Publish
memory.extractedevent for each extracted memory
env changes: Add OLLAMA_SERVICE_URL to memory-service AppConfig, .env, .env.example, install scripts
Changes:
- chat-service Prisma: Add
contextPackIds String[]to ChatThread - chat-service DTOs: Add
contextPackIdsto create/update thread DTOs - chat-service handleMessageRouted: Pass
thread.contextPackIdsto ContextAssemblyManager - Frontend: Add context pack selector in thread settings
- Frontend types: Add
contextPackIdsto ChatThread type
Changes:
- routing-service AppConfig: Already has OLLAMA_SERVICE_URL
- routing-service: On startup, fetch ROUTER role model from ollama-service
- OllamaRouterManager: Use the assigned router model instead of config default
- Add internal endpoint to ollama-service:
GET /api/v1/internal/ollama/router-model
Changes:
- shared-rabbitmq: Add DLX (dead-letter exchange) setup on channel creation
- shared-rabbitmq: Add retry logic with exponential backoff (3 retries, then DLQ)
- shared-rabbitmq: Add message TTL (24 hours)
- Add
RABBITMQ_RETRY_COUNTandRABBITMQ_RETRY_DELAY_MSto .env
Changes:
- routing-service: Before finalizing route, query connector-service for model capabilities
- If message contains image/file references, filter models without supportsVision
- If routing to a model that doesn't support the required capability, try next in fallback chain
- Add internal endpoint:
GET /api/v1/internal/connectors/model-capabilities?provider=X&model=Y
- JWT auth with refresh rotation: REAL
- RBAC with 3 roles (ADMIN, OPERATOR, USER): REAL
- Rate limiting (100 req/min configurable): REAL
- Helmet security headers: REAL
- Zod input validation: REAL (all DTOs)
- Prisma ORM (no raw SQL): REAL
- Secret encryption (AES-256-GCM for connector configs): REAL
- Log redaction (passwords, tokens, API keys): REAL
- CORS with configurable origins: REAL
- No HTTPS/TLS termination at Nginx (dev-only HTTP)
- No request signing between services (internal endpoints are @Public)
- No IP allowlisting for internal endpoints
- No audit log for admin actions (user create, role change)
- RabbitMQ message loss on handler failure (nack without requeue)
- No circuit breaker for HTTP calls to providers
- All file processing is synchronous (blocks request thread)
- No database connection pooling configuration visible
- 7 PostgreSQL instances (~200MB RAM each, ~1.4GB total)
- Ollama router adds 1-5s latency per AUTO-routed message
- No caching of connector configs (fetched on every cloud provider call)
- No caching of Ollama router model assignment
| Service | Test Files | Tests | Coverage |
|---|---|---|---|
| auth-service | 8 | 77 | Guards, JWT, DTOs, auth manager, user service |
| chat-service | 5 | 55 | DTOs, messages service, threads service |
| connector-service | 4 | 46 | DTOs, manager, service |
| routing-service | 3 | 29 | Routing manager, routing service |
| memory-service | 4 | 48 | Memory service, context packs service |
| file-service | 3 | 22 | File processing, files service |
| ollama-service | 3 | 12 | Ollama manager (env-dependent failure) |
| audit-service | 4 | 22 | Event manager, services |
| health-service | 1 | 1 | Smoke test only |
| client-logs-service | 0 | 0 | NO TESTS |
| server-logs-service | 0 | 0 | NO TESTS |
| Total | 35 | 312 |
- Integration tests (cross-service event flow)
- E2E tests (Playwright infrastructure exists but empty)
- API contract tests
- Load/performance tests
- Failure mode tests (provider timeout, RabbitMQ down, DB down)
| Gate | Status | Blocking? |
|---|---|---|
| Typecheck passes (0 errors) | PASS | No |
| Lint passes (0 errors) | PASS | No |
| Build succeeds | PASS | No |
| 312 unit tests pass | PASS | No |
| Rate limiting enabled | PASS | No |
| Helmet security headers | PASS | No |
| Memory extraction real | FAIL (stub) | YES |
| Context packs attachable to threads | FAIL (no schema field) | YES |
| Ollama roles consumed by routing | FAIL (unused) | YES |
| RabbitMQ retry/DLQ | FAIL (messages discarded) | YES for production |
| E2E tests | FAIL (none exist) | YES for production |
| CI/CD pipeline | PASS (GitHub Actions) | No |
| Requirement | Status |
|---|---|
| Context assembled before execution | REAL — ContextAssemblyManager fetches memories + file chunks |
| Routing decisions persisted | REAL — RoutingDecision table stores every decision |
| Every assistant message has provider/model | REAL — stored on ChatMessage record |
| File ingestion is real | PARTIAL — chunking works, no embeddings |
| Memory extraction is async and auditable | STUB — handler just logs |
| Logs emitted on every critical transition | REAL — StructuredLogger used throughout |
| Fallback paths explicit and safe | REAL — fallback chain with multiple providers |
| Background jobs for heavy operations | MISSING — all synchronous |
| Dead letter handling | MISSING — failed messages discarded |
| Circuit breaker for providers | MISSING — no circuit breaker |
ClawAI Wiki · source-controlled under wiki/ · generated/mirrored from the current repository knowledge layer.
ClawAI Wiki
Product
- Product-Vision
- Flagship-Features
- Business-Overview
- Requirements-Register
- Drift-Log
- Runtime-Managed-Connector-Providers
- Threads
- ADR-Index
Architecture
- System-at-a-Glance
- Architecture-Map
- System-Architecture
- Message-Flow
- End-to-End-Data-Flow
- Routing-Engine
- Event-Bus
- Security-Architecture
Platform
- Backend-Services
- Frontend-Architecture
- Shared-Packages
- Database-Reference
- API-Reference
- Provider-Catalog
- Memory-and-Context-Architecture
- File-Support-Architecture
- Research-and-Web-Crawling
Runtime & Ops
- Docker-Guide
- Nginx-Reference
- CI-CD-Pipeline
- Logging-and-Observability
- Operational-Runbooks
- Troubleshooting
Quality
Engineering OS
- AI-Native-Engineering-OS
- Generated-Manifests
- Rules-Catalog
- Skills-Catalog
- Context-Layer
- Engineering-Memory
- Tooling-and-Automation
- Scripts-and-Operations
- ESLint-and-Architecture-Enforcement
- Extended-Work-Skills
Agents
Reference