9683cf280b5f57c48d2fcbbf327ac308d81258b5
The SSE proxy was buffering the entire response body with a 30s read timeout, causing 504s on the OpenCode /global/event stream. Add a streaming path that detects SSE requests (by Accept header or /event path) and returns a StreamingResponse with no read timeout. Also fix the make try target to poll the health endpoint for Docker readiness and wait for the container to reach running status before opening the browser.
Lovdata Chat Interface
A container-per-session architecture for Norwegian legal research. Each user session gets an isolated OpenCode container connected to the external Lovdata MCP server, which provides 15+ tools for searching Norwegian laws, provisions, and cross-references.
Architecture
Users → Caddy (reverse proxy) → Session Manager (FastAPI)
↓
Docker-in-Docker daemon
↓ ↓ ↓
[OC 1] [OC 2] [OC 3] ← OpenCode containers
↓ ↓ ↓
Lovdata MCP Server (external)
LLM APIs (OpenAI/Anthropic/Google)
| Component | Purpose |
|---|---|
| Session Manager | FastAPI service managing OpenCode container lifecycles |
| OpenCode Containers | Isolated chat environments with MCP integration |
| Lovdata MCP Server | External Norwegian legal research (laws, provisions, cross-references) |
| Caddy | Reverse proxy with dynamic session-based routing |
| PostgreSQL | Session persistence across restarts |
| Docker-in-Docker | TLS-secured Docker daemon for container management |
Session Manager Components
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
logging_config.py → Structured JSON logging with context
Quick Start
-
Set up environment variables:
cp .env.example .env # Edit .env with your API keys and MCP server URL -
Start the services:
docker-compose up --build -
Create a session:
curl http://localhost/api/sessions -X POST -
Access the chat interface at the URL returned in step 3.
Development
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
POST /api/sessions # Create new session
GET /api/sessions # List all sessions
GET /api/sessions/{id} # Get session info
DELETE /api/sessions/{id} # Delete session
POST /api/cleanup # Manual cleanup
GET /api/health # Health check
Running Locally (without Docker)
cd session-manager
pip install -r requirements.txt
uvicorn main:app --reload --host 0.0.0.0 --port 8000
Testing
Test scripts live in docker/scripts/ and are self-contained:
python docker/scripts/test-docker-service.py
python docker/scripts/test-async-docker.py
python docker/scripts/test-resource-limits.py
python docker/scripts/test-session-auth.py
python docker/scripts/test-database-persistence.py
python docker/scripts/test-container-health.py
python docker/scripts/test-http-connection-pool.py
python docker/scripts/test-host-ip-detection.py
python docker/scripts/test-structured-logging.py
Building the OpenCode Image
make build MCP_SERVER=http://your-lovdata-server:8001
make run # Run interactively
make clean # Clean up
Environment Configuration
Required 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)
OPENAI_API_KEY=...
ANTHROPIC_API_KEY=...
GOOGLE_API_KEY=...
Security
Docker socket: Default setup uses socket mounting (/var/run/docker.sock). For production, enable TLS:
cd docker && DOCKER_ENV=production ./scripts/generate-certs.sh
./scripts/setup-docker-tls.sh
Session isolation:
- Each session gets a dedicated container
- Resource limits: 4GB RAM, 1 CPU core per container
- Max 3 concurrent sessions (configurable via
resource_manager.py) - Auto-cleanup after 60 minutes inactivity
- Token-based session authentication
Further Documentation
CLAUDE.md— AI assistant guidance for working with this codebaseLOW_PRIORITY_IMPROVEMENTS.md— Backlog of non-critical improvementsdocs/project-analysis.md— Detailed architectural analysisdocker/*.md— Implementation docs for individual components
Description
Languages
Python
84.6%
Shell
12.6%
JavaScript
1.7%
Makefile
0.6%
Dockerfile
0.5%