Torbjørn Lindahl 9683cf280b fix: add SSE streaming proxy and robust make try startup
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.
2026-02-16 00:38:57 +01:00
2026-01-18 23:29:04 +01:00
2026-02-08 20:27:35 +01:00
2026-02-01 19:40:55 +01:00
2026-02-04 19:10:03 +01:00
2026-02-08 20:27:35 +01:00
2026-01-18 22:10:03 +01:00

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

  1. Set up environment variables:

    cp .env.example .env
    # Edit .env with your API keys and MCP server URL
    
  2. Start the services:

    docker-compose up --build
    
  3. Create a session:

    curl http://localhost/api/sessions -X POST
    
  4. 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

Description
No description provided
Readme 277 KiB
Languages
Python 84.6%
Shell 12.6%
JavaScript 1.7%
Makefile 0.6%
Dockerfile 0.5%