Files
opus-agent/CLAUDE.md
T
Adriano Dal Pastro 5d78d865a4 fix: correct Docker setup, SDK async API, and docs
- Dockerfile: use explicit uvicorn command (uv run start fails without package mode)
- docker-compose: mount claude-auth volume to /root/.claude (worker runs as root)
- worker: update to Claude Code SDK async iterable API (query() returns iterator, not array)
- main.py: fix docs_url from /doc to /docs
- README: correct login instructions (exec not run --rm), add production URLs
- Add CLAUDE.md with full project documentation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:35:44 +00:00

4.8 KiB

OpusAgent

Self-hosted REST API proxy to Claude models via Claude Code SDK, leveraging a Claude Pro/Max subscription.

Architecture

Two Docker containers behind Traefik reverse proxy:

Client → Traefik (HTTPS) → FastAPI (Python, :8000) ↔ Node.js Worker (:3001, internal)
                                    ↓                         ↓
                              SQLite (WAL)           Claude Code SDK
  • fastapi-service: API REST, auth, rate limiting, async queue processing
  • claude-worker: Express server wrapping @anthropic-ai/claude-code SDK

Key Paths

Area Path
FastAPI entrypoint src/backend/main.py
Config (Pydantic Settings) src/backend/config.py
Database layer (aiosqlite) src/backend/db/database.py
API models + envelope src/backend/models/api.py
Routes src/backend/api/routes/{health,topics,requests,sessions}.py
Auth + rate limiter src/backend/api/dependencies.py
Worker HTTP client src/backend/services/worker_client.py
Queue processor src/backend/services/queue.py
Node.js worker worker/index.js
Tests tests/
Design spec (Italian) docs/superpowers/specs/
Implementation plan docs/superpowers/plans/

Tech Stack

  • Python 3.11+: FastAPI, uvicorn, aiosqlite, httpx, slowapi, pydantic-settings
  • Node.js 20: Express, @anthropic-ai/claude-code SDK (v1.0.128+, async iterable API)
  • Package manager: uv (Python), npm (Node.js)
  • Database: SQLite with WAL mode, foreign keys enabled
  • Infra: Docker Compose, Traefik v3 (TLS via Let's Encrypt)

Database Schema

Three tables in SQLite:

  • topics: id (UUID), name (unique), system_prompt, summary, model (nullable), timestamps
  • requests: id (UUID), session_id, topic_id (FK), prompt, result, error, model, summarize (bool), status (pending/processing/completed/failed), timestamps
  • closed_sessions: session_id (PK), closed_at

API Endpoints

All require X-Api-Key header. All responses use envelope: {success, data, error}.

  • GET /api/health — health check (DB + worker)
  • POST|GET /api/topics, GET|PUT|DELETE /api/topics/{id} — topic CRUD
  • POST /api/topics/{id}/summarize — regenerate complete topic summary
  • POST /api/requests (202) — submit prompt (async queue), GET|DELETE /api/requests[/{id}]
  • GET /api/sessions, DELETE /api/sessions/{id} — session management

Processing Flow

  1. POST /api/requests → 202 Accepted (queued)
  2. Background loop picks oldest pending, calls worker POST /process
  3. Worker runs Claude Code SDK (query()) with optional session resume
  4. Result stored in DB; optional incremental summary appended to topic
  5. Client polls GET /api/requests/{id} for result

Model resolution: request-level > topic-level > CLAUDE_MODEL env var.

Commands

# Development
uv sync --all-groups
uv run dev                          # uvicorn with --reload

# Production
docker compose up -d                # both services
docker compose exec claude-worker npx claude login  # first-time auth (must run on active container, NOT run --rm)

# Tests
uv run pytest -v

# Logs
docker compose logs -f fastapi-service
docker compose logs -f claude-worker

Environment Variables (.env)

Variable Default Purpose
API_KEY (required) Auth key for all endpoints
API_HOST 0.0.0.0 Server bind address
API_PORT 8000 Server port
WORKER_URL http://claude-worker:3001 Internal worker address
DB_PATH /data/opus-agent.db SQLite database path
CLAUDE_MODEL claude-sonnet-4-6 Default Claude model
RATE_LIMIT_PER_MINUTE 10 Rate limit per API key
RATE_LIMIT_PER_HOUR 100 Rate limit per API key

Design Decisions

  • Sequential processing: one request at a time (FIFO), no concurrency — simplifies Claude Code SDK session handling
  • Async Python: FastAPI + aiosqlite for non-blocking I/O while queue runs in background
  • SQLite + WAL: sufficient for single-writer pattern, no external DB needed
  • Envelope pattern: consistent {success, data, error} on all responses
  • Session = conversation: session_id maps to Claude Code SDK resume option
  • Worker isolation: not exposed to host, only reachable from fastapi-service via Docker internal network
  • Claude auth volume: credentials stored at /root/.claude (not /home/node/.claude) because the worker process runs as root inside the container
  • SDK async iteration: query() returns an async iterable (not a promise); iterate with for await...of and extract the message with type === "result"

Deployment

Domain: opus-agent.tielogic.xyz → Traefik → fastapi-service:8000

Traefik config: /opt/docker/traefik/dynamic.yml (file provider, auto-reload).