6.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Architecture Overview
Container-per-session architecture for Norwegian legal research chat interface:
- session-manager: FastAPI service managing OpenCode container lifecycles (one per user session)
- OpenCode containers: Isolated chat environments with MCP integration
- Lovdata MCP server: External Norwegian legal research server (15+ tools for law search, provisions, cross-references)
- Caddy: Reverse proxy with dynamic session-based routing
Session Manager Components
The session-manager is built with a layered architecture:
main.py → FastAPI endpoints, session lifecycle orchestration
docker_service.py → Docker abstraction layer (testable, mockable)
async_docker_client.py → Async Docker operations
database.py → PostgreSQL session persistence with asyncpg
session_auth.py → Token-based session authentication
container_health.py → Health monitoring and auto-recovery
resource_manager.py → CPU/memory limits, throttling
http_pool.py → Connection pooling for container HTTP requests
host_ip_detector.py → Docker host IP detection (host.docker.internal fallback)
logging_config.py → Structured JSON logging with context
Key design patterns:
- Dependency injection: SessionManager receives DockerService via constructor (enables testing with MockDockerService)
- Service abstraction: Clean separation between business logic (main.py) and infrastructure (docker_service.py)
- Async-first: All I/O operations use asyncio (Docker, HTTP, database)
- Database persistence: Sessions survive manager restarts via PostgreSQL
MCP Integration
OpenCode containers connect to two MCP servers:
- lovdata MCP server: Norwegian legal tools (configured via
MCP_SERVERenv var) - sequential-thinking: Local MCP for reasoning (optional)
Configuration: config_opencode/opencode.jsonc
Skills: config_opencode/skills/norwegian-legal-queries/
Development Commands
Running the stack
# Start all services (session-manager, docker-daemon, caddy)
docker-compose up --build
# Start in background
docker-compose up -d --build
# View logs
docker-compose logs -f session-manager
# Stop services
docker-compose down
Session management (API)
# Create session
curl http://localhost/api/sessions -X POST
# List sessions
curl http://localhost/api/sessions
# Get session info
curl http://localhost/api/sessions/{session_id}
# Delete session
curl http://localhost/api/sessions/{session_id} -X DELETE
# Manual cleanup
curl http://localhost/api/cleanup -X POST
# Health check
curl http://localhost/api/health
Running session-manager locally (without Docker)
cd session-manager
# Install dependencies
pip install -r requirements.txt
# Run directly (requires Docker socket or TLS connection)
uvicorn main:app --reload --host 0.0.0.0 --port 8000
Testing
Test scripts in docker/scripts/:
# Test Docker service abstraction
python docker/scripts/test-docker-service.py
# Test async Docker operations
python docker/scripts/test-async-docker.py
# Test resource limits
python docker/scripts/test-resource-limits.py
# Test session authentication
python docker/scripts/test-session-auth.py
# Test database persistence
python docker/scripts/test-database-persistence.py
# Test container health monitoring
python docker/scripts/test-container-health.py
# Test HTTP connection pooling
python docker/scripts/test-http-connection-pool.py
# Test host IP detection
python docker/scripts/test-host-ip-detection.py
# Test structured logging
python docker/scripts/test-structured-logging.py
All test scripts are self-contained and can run independently.
Building the OpenCode image
# Build with custom MCP server
make build MCP_SERVER=http://your-lovdata-server:8001
# Run container interactively
make run
# Clean up image
make clean
Environment Configuration
Required environment variables (see .env.example):
MCP_SERVER=http://localhost:8001 # External Lovdata MCP server URL
# Docker TLS (if using TLS instead of socket)
DOCKER_TLS_VERIFY=1
DOCKER_CERT_PATH=/etc/docker/certs
DOCKER_HOST=tcp://host.docker.internal:2376
# Optional LLM keys (at least one required for chat functionality)
OPENAI_API_KEY=...
ANTHROPIC_API_KEY=...
GOOGLE_API_KEY=...
Security
Current setup uses Docker socket mounting (/var/run/docker.sock) which grants root access. TLS-based Docker API access is implemented but not enabled by default.
To enable TLS (recommended for production):
# Generate certificates
cd docker
DOCKER_ENV=production ./scripts/generate-certs.sh
# Configure Docker daemon
./scripts/setup-docker-tls.sh
# Update docker-compose.yml to use TLS instead of socket
Session isolation:
- Each session gets dedicated container
- Resource limits: 4GB RAM, 1 CPU core per container
- Max 3 concurrent sessions (configurable in resource_manager.py)
- Auto-cleanup after 60 minutes inactivity
Session Data Persistence
Sessions are stored in PostgreSQL with the following schema:
- Session ID, container ID, status, timestamps
- Survives session-manager restarts
- Health monitoring tracks container state
Session working directories: ./sessions/ (bind-mounted into containers)
Container Health Monitoring
Automatic health checks run every 30 seconds:
- Restart unhealthy containers (max 3 attempts)
- Mark failed sessions for cleanup
- Track health history for debugging
Implementation Documentation
Detailed implementation guides in docker/:
ASYNC_DOCKER_IMPLEMENTATION.md- Async Docker clientCONTAINER_HEALTH_MONITORING_IMPLEMENTATION.md- Health checksDATABASE_PERSISTENCE_IMPLEMENTATION.md- Session databaseHOST_IP_IMPLEMENTATION.md- Host IP detectionHTTP_CONNECTION_POOLING_IMPLEMENTATION.md- HTTP poolingRESOURCE_LIMITS_IMPLEMENTATION.md- Resource managementSESSION_AUTHENTICATION_IMPLEMENTATION.md- Auth tokensSTRUCTURED_LOGGING_IMPLEMENTATION.md- JSON logging
Common Pitfalls
-
MCP_SERVER must point to external server: The lovdata-ai MCP server is NOT part of this stack, configure URL in
.env -
Docker socket permissions: Session-manager needs access to Docker socket or TLS certificates
-
Port conflicts: Session-manager uses ports 8000, Caddy uses 80/443, docker-daemon uses 2376
-
Container cleanup: Failed containers may linger, use
/api/cleanupto force cleanup -
Resource exhaustion: Default limit is 3 concurrent sessions, increase in resource_manager.py if needed
-
Database connection: PostgreSQL connection configured in database.py, defaults to localhost:5432