# 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: 1. **lovdata MCP server**: Norwegian legal tools (configured via `MCP_SERVER` env var) 2. **sequential-thinking**: Local MCP for reasoning (optional) Configuration: `config_opencode/opencode.jsonc` Skills: `config_opencode/skills/norwegian-legal-queries/` ## Development Commands ### Running the stack ```bash # 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) ```bash # 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) ```bash 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/`: ```bash # 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 ```bash # 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`): ```bash 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): ```bash # 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 client - `CONTAINER_HEALTH_MONITORING_IMPLEMENTATION.md` - Health checks - `DATABASE_PERSISTENCE_IMPLEMENTATION.md` - Session database - `HOST_IP_IMPLEMENTATION.md` - Host IP detection - `HTTP_CONNECTION_POOLING_IMPLEMENTATION.md` - HTTP pooling - `RESOURCE_LIMITS_IMPLEMENTATION.md` - Resource management - `SESSION_AUTHENTICATION_IMPLEMENTATION.md` - Auth tokens - `STRUCTURED_LOGGING_IMPLEMENTATION.md` - JSON logging ## Common Pitfalls 1. **MCP_SERVER must point to external server**: The lovdata-ai MCP server is NOT part of this stack, configure URL in `.env` 2. **Docker socket permissions**: Session-manager needs access to Docker socket or TLS certificates 3. **Port conflicts**: Session-manager uses ports 8000, Caddy uses 80/443, docker-daemon uses 2376 4. **Container cleanup**: Failed containers may linger, use `/api/cleanup` to force cleanup 5. **Resource exhaustion**: Default limit is 3 concurrent sessions, increase in resource_manager.py if needed 6. **Database connection**: PostgreSQL connection configured in database.py, defaults to localhost:5432