commit dcdbae211ca45a6a0f42938d66628e1bc481f279 Author: AdrianoDev Date: Mon May 25 18:25:03 2026 +0200 Aggiunta spec design OpusAgent Servizio self-hosted su VPS per interazione programmatica con modelli Anthropic tramite Claude Code SDK e abbonamento Pro/Max. Co-Authored-By: Claude Opus 4.7 (1M context) diff --git a/docs/superpowers/specs/2026-05-25-opus-agent-design.md b/docs/superpowers/specs/2026-05-25-opus-agent-design.md new file mode 100644 index 0000000..03f648e --- /dev/null +++ b/docs/superpowers/specs/2026-05-25-opus-agent-design.md @@ -0,0 +1,668 @@ +# OpusAgent — Design Specification + +> Servizio self-hosted che consente l'interazione programmatica con i modelli +> Anthropic attraverso Claude Code SDK, sfruttando un abbonamento Claude +> Pro/Max esistente. Il servizio espone un'API REST asincrona con gestione +> degli argomenti, coda di richieste sequenziale, rate limiting e generazione +> automatica di riassunti. + +--- + +## 1. Panoramica + +OpusAgent è un servizio a due componenti, deployato via Docker su una VPS +Hostinger, che funge da intermediario tra software client e i modelli +Anthropic. Invece di richiedere API key a consumo, il servizio sfrutta +un'istanza autenticata di Claude Code che opera con l'abbonamento Pro/Max +dell'utente. + +### Obiettivi + +- Consentire ai propri software di comunicare con Claude in modo programmatico +- Utilizzare l'abbonamento Claude Pro/Max esistente, evitando costi API a consumo +- Organizzare le interazioni per argomento con system prompt dedicati +- Supportare sia richieste one-shot che conversazioni multi-turn +- Accumulare conoscenza tramite riassunti incrementali e completi +- Garantire sicurezza con autenticazione API key e rate limiting + +### Utenti + +Uso esclusivamente personale: l'operatore e i suoi software. + +--- + +## 2. Architettura + +Il sistema è composto da due container Docker che comunicano via HTTP sulla +rete interna Docker, con persistenza su SQLite in un volume condiviso. + +``` +┌─────────────┐ ┌──────────────────┐ ┌──────────────────┐ +│ Client │ REST │ FastAPI │ HTTP │ Node.js Worker │ +│ Software │───────▶│ (Python) │───────▶│ (Claude Code │ +│ │◀───────│ Queue + Auth │◀───────│ SDK) │ +└─────────────┘ │ Rate Limit │ └────────┬─────────┘ + └────────┬──────────┘ │ + │ ┌─────▼─────────┐ + ┌────▼────┐ │ ~/.claude/ │ + │ SQLite │ │ (auth volume) │ + │ (coda) │ └───────────────┘ + └─────────┘ +``` + +### Componenti + +| Componente | Tecnologia | Ruolo | +|------------|------------|-------| +| **fastapi-service** | Python 3.11+, FastAPI, SQLite | API REST pubblica, autenticazione, rate limiting, gestione coda | +| **claude-worker** | Node.js, @anthropic-ai/claude-code | Processamento richieste via Claude Code SDK, gestione sessioni | +| **SQLite** | Volume Docker condiviso | Persistenza coda richieste e argomenti | +| **Claude Auth** | Volume Docker dedicato | Credenziali Claude Code (solo worker) | + +### Principi architetturali + +- Il worker non è esposto all'esterno: comunica solo via rete Docker interna +- Processamento sequenziale: una richiesta alla volta (FIFO) +- Il FastAPI service è l'unico punto di ingresso +- Nessuna dipendenza da NATS: HTTP interno sufficiente per due servizi +- Integrazione Traefik per reverse proxy (ultima fase del deploy) + +--- + +## 3. Data Model + +### Tabella `topics` + +Contiene gli argomenti con i rispettivi system prompt. Ogni argomento +rappresenta un contesto di conversazione distinto. + +| Campo | Tipo | Vincoli | Descrizione | +|-------|------|---------|-------------| +| `id` | TEXT (UUID) | PK | Identificativo unico | +| `name` | TEXT | NOT NULL, UNIQUE | Nome breve (es. "coding", "analisi") | +| `system_prompt` | TEXT | NOT NULL | System prompt inviato a Claude per questo argomento | +| `summary` | TEXT | DEFAULT "" | Riassunto accumulato delle interazioni | +| `created_at` | TEXT (ISO 8601) | NOT NULL | Timestamp di creazione | +| `model` | TEXT | NULLABLE | Modello Claude per questo topic (override del default globale) | +| `updated_at` | TEXT (ISO 8601) | NOT NULL | Timestamp ultima modifica | + +### Tabella `requests` + +Contiene le richieste in coda, in elaborazione e completate. + +| Campo | Tipo | Vincoli | Descrizione | +|-------|------|---------|-------------| +| `id` | TEXT (UUID) | PK | Identificativo unico | +| `session_id` | TEXT | NULLABLE | Raggruppa richieste multi-turn. Null = one-shot | +| `topic_id` | TEXT (UUID) | FK → topics.id, NOT NULL | Argomento di riferimento | +| `prompt` | TEXT | NOT NULL | Il prompt da inviare a Claude | +| `model` | TEXT | NULLABLE | Override modello per questa richiesta (prevale su topic e default) | +| `summarize` | INTEGER | DEFAULT 0 | 1 = genera riassunto e lo appende al topic | +| `status` | TEXT | NOT NULL, DEFAULT "pending" | `pending`, `processing`, `completed`, `failed` | +| `result` | TEXT | NULLABLE | Risposta di Claude | +| `error` | TEXT | NULLABLE | Messaggio di errore (se status = failed) | +| `created_at` | TEXT (ISO 8601) | NOT NULL | Timestamp di creazione | +| `completed_at` | TEXT (ISO 8601) | NULLABLE | Timestamp di completamento | + +### Relazioni + +- `requests.topic_id` → `topics.id` (molti a uno) +- `requests.session_id` raggruppa logicamente richieste della stessa conversazione +- Un topic può avere zero o più richieste +- Una sessione è definita implicitamente dal `session_id` condiviso tra richieste + +--- + +## 4. API REST + +Tutti gli endpoint richiedono l'header `X-Api-Key` con la chiave configurata +nel `.env`. Risposte nel formato envelope standard: +`{success: bool, data: any, error: {code, message} | null}`. + +### 4.1 Topics + +#### `POST /api/topics` + +Crea un nuovo argomento. + +```json +// Request +{ + "name": "coding", + "system_prompt": "Sei un esperto sviluppatore Python e TypeScript...", + "summary": "Argomento per domande di programmazione", + "model": "claude-sonnet-4-6" +} + +// Response 201 +{ + "success": true, + "data": { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "coding", + "system_prompt": "Sei un esperto sviluppatore Python e TypeScript...", + "summary": "Argomento per domande di programmazione", + "created_at": "2026-05-25T10:00:00Z", + "updated_at": "2026-05-25T10:00:00Z" + }, + "error": null +} +``` + +#### `GET /api/topics` + +Lista tutti gli argomenti. + +#### `GET /api/topics/{id}` + +Dettaglio di un argomento. + +#### `PUT /api/topics/{id}` + +Aggiorna un argomento (name, system_prompt, summary). + +#### `DELETE /api/topics/{id}` + +Elimina un argomento. Fallisce se ci sono richieste pending/processing. + +#### `POST /api/topics/{id}/summarize` + +Rigenera il summary completo del topic analizzando tutte le richieste +completate. Invia l'elenco degli scambi (prompt + risultato) a Claude Code +e sostituisce il campo `summary` con il nuovo riassunto complessivo. + +### 4.2 Requests + +#### `POST /api/requests` + +Invia una nuova richiesta alla coda. + +```json +// Request +{ + "prompt": "Come implemento un middleware di autenticazione JWT in FastAPI?", + "topic_id": "550e8400-e29b-41d4-a716-446655440000", + "session_id": null, + "model": null, + "summarize": true +} + +// Response 202 +{ + "success": true, + "data": { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "status": "pending", + "created_at": "2026-05-25T10:05:00Z" + }, + "error": null +} +``` + +#### `GET /api/requests/{id}` + +Restituisce stato e risultato (quando disponibile). + +```json +// Response (completata) +{ + "success": true, + "data": { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "session_id": null, + "topic_id": "550e8400-e29b-41d4-a716-446655440000", + "prompt": "Come implemento un middleware di autenticazione JWT in FastAPI?", + "summarize": true, + "status": "completed", + "result": "Per implementare un middleware JWT in FastAPI...", + "error": null, + "created_at": "2026-05-25T10:05:00Z", + "completed_at": "2026-05-25T10:05:45Z" + }, + "error": null +} +``` + +#### `GET /api/requests` + +Lista richieste con filtri opzionali: `?status=pending&topic_id=xxx&session_id=yyy`. + +#### `DELETE /api/requests/{id}` + +Cancella una richiesta con status `pending`. Non è possibile cancellare +richieste in `processing`, `completed` o `failed`. + +### 4.3 Sessions + +#### `GET /api/sessions` + +Lista sessioni attive con conteggio richieste per ciascuna. + +#### `DELETE /api/sessions/{id}` + +Chiude una sessione. Le richieste existing restano, ma non si possono +aggiungere nuove richieste a questa sessione. + +### 4.4 System + +#### `GET /api/health` + +Health check del servizio. Verifica: +- Stato FastAPI +- Connettività al worker (HTTP ping) +- Accesso al database SQLite + +--- + +## 5. Flusso di Processamento + +### 5.1 Flusso richiesta standard + +1. Il client invia `POST /api/requests` con prompt e topic_id +2. FastAPI valida la richiesta, verifica API key, controlla rate limit +3. Crea record in SQLite con `status: "pending"`, restituisce `202 Accepted` con ID +4. Il client riceve l'ID e può fare polling su `GET /api/requests/{id}` +5. Il worker loop (nel FastAPI service) prende la richiesta pending più vecchia +6. Aggiorna status a `processing` +7. Carica il `system_prompt` dal topic associato +8. Chiama il worker Node.js via HTTP: `POST http://claude-worker:3001/process` +9. Il worker Node.js esegue la richiesta tramite Claude Code SDK +10. Il worker restituisce il risultato +11. FastAPI aggiorna il record: `status: "completed"`, `result: "..."` +12. Se `summarize: true`: il worker genera un riassunto dello scambio + e lo appende al `summary` del topic +13. Il client ottiene il risultato al prossimo polling + +### 5.2 Flusso multi-turn + +1. Prima richiesta: `POST /api/requests {prompt, topic_id}` (senza session_id) +2. Il sistema genera un `session_id` e lo restituisce nella risposta +3. Richieste successive: `POST /api/requests {prompt, topic_id, session_id: "..."}` +4. Il worker Node.js usa il session_id per mantenere il contesto con Claude Code + (flag `--resume` o equivalente SDK) +5. Ogni richiesta nella sessione condivide la storia della conversazione + +### 5.3 Flusso summary completo + +1. Il client chiama `POST /api/topics/{id}/summarize` +2. FastAPI raccoglie tutte le richieste `completed` del topic +3. Compone un payload con tutti gli scambi (prompt + risultato) +4. Chiama il worker per generare un riassunto complessivo +5. Sostituisce il campo `summary` del topic con il nuovo riassunto +6. Restituisce il summary aggiornato + +### 5.4 Flusso summary incrementale + +1. Richiesta con `summarize: true` viene completata +2. Dopo aver salvato il risultato, il worker genera un riassunto + in 1-2 frasi dello scambio +3. Il riassunto viene appeso al `summary` del topic con timestamp: + `[2026-05-25] Descrizione dello scambio.` + +--- + +## 6. Node.js Worker + +### Responsabilità + +Il worker è un server HTTP interno minimale con un'unica responsabilità: +eseguire richieste tramite Claude Code SDK e restituire il risultato. + +### Endpoint interni + +| Metodo | Path | Descrizione | +|--------|------|-------------| +| `POST` | `/process` | Processa una richiesta Claude Code | +| `POST` | `/summarize` | Genera riassunto di uno o più scambi | +| `GET` | `/health` | Health check del worker | + +### Payload `/process` + +```json +// Request +{ + "prompt": "Come implemento JWT?", + "system_prompt": "Sei un esperto sviluppatore...", + "model": "claude-sonnet-4-6", + "session_id": null +} + +// Response +{ + "success": true, + "result": "Per implementare JWT in FastAPI...", + "session_id": "generated-or-existing-id" +} +``` + +### Payload `/summarize` + +```json +// Request +{ + "exchanges": [ + {"prompt": "Come faccio X?", "result": "Si fa così..."}, + {"prompt": "E se devo Y?", "result": "In quel caso..."} + ], + "mode": "incremental" +} + +// Response +{ + "success": true, + "summary": "[2026-05-25] Discusso come implementare X e gestire il caso Y." +} +``` + +### Claude Code SDK Usage + +```javascript +import { claude } from '@anthropic-ai/claude-code'; + +const result = await claude({ + prompt: request.prompt, + systemPrompt: request.system_prompt, + model: request.model, + // Per multi-turn: resume dalla sessione + ...(request.session_id && { resume: request.session_id }), + // Output in formato testo, non interattivo + print: true, +}); +``` + +--- + +## 7. Selezione Modello + +Il modello Claude da utilizzare si risolve con priorità a cascata: + +1. **Request `model`** — se la richiesta specifica un modello, usa quello +2. **Topic `model`** — se il topic ha un modello configurato, usa quello +3. **Default globale** — `CLAUDE_MODEL` dal `.env` + +Questa gerarchia consente di avere un default ragionevole (es. `claude-sonnet-4-6`) +con la flessibilità di usare modelli diversi per argomenti specifici (es. Opus +per analisi complesse) o per singole richieste (es. Haiku per domande semplici). + +### Modelli supportati + +I valori accettati corrispondono agli identificativi dei modelli Claude: +- `claude-opus-4-7` +- `claude-sonnet-4-6` +- `claude-haiku-4-5-20251001` + +La validazione accetta qualsiasi stringa che inizi con `claude-` per +compatibilità con modelli futuri. + +--- + +## 8. Rate Limiting + +Implementato con `slowapi` (wrapper di `limits` per FastAPI). + +### Configurazione + +``` +# .env +RATE_LIMIT_PER_MINUTE=10 +RATE_LIMIT_PER_HOUR=100 +``` + +### Comportamento + +- Limiti applicati per API key +- Quando superati: risposta `429 Too Many Requests` con header + `Retry-After` e `X-RateLimit-Remaining` +- I limiti si applicano solo a `POST /api/requests` (invio richieste) +- Gli endpoint di lettura (GET) e gestione topics non sono limitati + +--- + +## 9. Sicurezza + +### Autenticazione API + +- Header `X-Api-Key` obbligatorio su tutti gli endpoint +- Chiave configurata in `.env` +- Risposta `403 Forbidden` se mancante o non valida + +### Isolamento Worker + +- Il worker Node.js non è esposto all'esterno (`expose`, non `ports`) +- Raggiungibile solo dalla rete Docker interna +- Nessuna autenticazione sul worker (rete fidata interna) + +### Credenziali Claude Code + +- Conservate in volume Docker dedicato (`claude-auth`) +- Non accessibili dal container FastAPI +- Autenticazione manuale una tantum alla prima configurazione + +### Integrazione Traefik + +- Sarà implementata come ultima fase del deploy +- Il servizio FastAPI sarà raggiungibile tramite reverse proxy Traefik +- HTTPS via Let's Encrypt tramite Traefik +- Labels Docker per il routing automatico + +--- + +## 10. Configurazione + +### .env + +``` +# Server +API_HOST=0.0.0.0 +API_PORT=8000 + +# Sicurezza +API_KEY= + +# Rate Limiting +RATE_LIMIT_PER_MINUTE=10 +RATE_LIMIT_PER_HOUR=100 + +# Worker +WORKER_URL=http://claude-worker:3001 + +# Database +DB_PATH=/data/opus-agent.db + +# Claude Code (worker) +CLAUDE_MODEL=claude-sonnet-4-6 +``` + +### Variabili condivise + +Entrambi i container ricevono lo stesso `.env` via `env_file`. +Il FastAPI service usa le variabili di server, sicurezza, rate limit e DB. +Il worker usa `CLAUDE_MODEL` e il path DB per eventuali accessi diretti. + +--- + +## 11. Struttura Progetto + +``` +opus-agent/ +├── src/ +│ └── backend/ +│ ├── __init__.py +│ ├── main.py # Entry point FastAPI +│ ├── config.py # Pydantic Settings +│ ├── api/ +│ │ ├── __init__.py +│ │ ├── dependencies.py # API Key auth + rate limiting +│ │ └── routes/ +│ │ ├── __init__.py +│ │ ├── topics.py # CRUD topics + summarize +│ │ ├── requests.py # CRUD requests +│ │ ├── sessions.py # Lista/chiudi sessioni +│ │ └── health.py # Health check +│ ├── models/ +│ │ ├── __init__.py +│ │ └── api.py # Pydantic models request/response +│ ├── db/ +│ │ ├── __init__.py +│ │ ├── database.py # SQLite connection + init schema +│ │ └── models.py # Schema tabelle +│ └── services/ +│ ├── __init__.py +│ ├── queue.py # Logica coda (pick pending, update status) +│ └── worker_client.py # Client HTTP per chiamare il worker +│ +├── worker/ +│ ├── index.js # Server HTTP + Claude Code SDK +│ ├── package.json +│ └── Dockerfile +│ +├── tests/ +│ ├── __init__.py +│ ├── conftest.py +│ ├── test_api_topics.py +│ ├── test_api_requests.py +│ └── test_services.py +│ +├── Dockerfile # Backend Python +├── docker-compose.yml +├── pyproject.toml +├── .env.example +├── .gitignore +└── README.md +``` + +--- + +## 12. Docker + +### Dockerfile Backend (Python) + +```dockerfile +FROM python:3.11-slim AS base +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv +WORKDIR /app +COPY pyproject.toml uv.lock ./ +RUN uv sync --frozen --no-dev +COPY src/ src/ +EXPOSE 8000 +CMD ["uv", "run", "start"] +``` + +### Dockerfile Worker (Node.js) + +```dockerfile +FROM node:20-slim +RUN npm install -g @anthropic-ai/claude-code +WORKDIR /app +COPY package.json ./ +RUN npm ci --production +COPY index.js ./ +EXPOSE 3001 +CMD ["node", "index.js"] +``` + +### docker-compose.yml + +```yaml +services: + fastapi-service: + build: + context: . + dockerfile: Dockerfile + ports: + - "${API_PORT:-8000}:8000" + env_file: .env + volumes: + - opus-data:/data + depends_on: + claude-worker: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", + "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health')"] + interval: 10s + timeout: 5s + retries: 3 + + claude-worker: + build: + context: ./worker + dockerfile: Dockerfile + expose: + - "3001" + env_file: .env + volumes: + - opus-data:/data + - claude-auth:/home/node/.claude + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3001/health"] + interval: 10s + timeout: 5s + retries: 3 + +volumes: + opus-data: + claude-auth: +``` + +### Integrazione Traefik (ultima fase) + +Nella configurazione finale, le porte dirette verranno sostituite da +labels Traefik per il routing automatico: + +```yaml +# Da aggiungere a fastapi-service +labels: + - "traefik.enable=true" + - "traefik.http.routers.opus-agent.rule=Host(`opus.tuodominio.com`)" + - "traefik.http.routers.opus-agent.tls.certresolver=letsencrypt" + - "traefik.http.services.opus-agent.loadbalancer.server.port=8000" +``` + +--- + +## 13. Setup Iniziale + +### Prima installazione sulla VPS + +1. Clona il repository e copia `.env.example` in `.env`, compila i valori +2. Avvia solo il worker per autenticare Claude Code: + ```bash + docker compose run --rm claude-worker npx claude login + ``` +3. Segui il flusso di autenticazione nel browser +4. Le credenziali vengono salvate nel volume `claude-auth` +5. Avvia il sistema completo: + ```bash + docker compose up -d + ``` +6. Verifica con `GET /api/health` + +### Rinnovo credenziali + +Se le credenziali Claude Code scadono: +```bash +docker compose run --rm claude-worker npx claude login +docker compose restart claude-worker +``` + +--- + +## 14. Limiti e Vincoli Noti + +- **Throughput**: Una richiesta alla volta. Il tempo di risposta dipende dalla + complessità del prompt e dal carico sull'abbonamento Claude. +- **Rate limit abbonamento**: Claude Pro/Max ha limiti d'uso propri. + Se l'abbonamento raggiunge il suo limite, le richieste falliranno con + un errore specifico. Il servizio marca queste come `failed` con il + messaggio d'errore di Claude. +- **Sessioni multi-turn**: La persistenza delle sessioni dipende dal + comportamento del Claude Code SDK. Se il SDK non supporta nativamente + il resume di sessioni, si implementa passando lo storico come contesto. +- **Volume credenziali**: Le credenziali Claude Code nel volume Docker + possono scadere. Serve rinnovo manuale periodico. +- **SQLite concorrenza**: Con un solo writer (il queue processor), non ci + sono problemi di locking. Se in futuro si volessero più worker paralleli, + servirebbe migrare a PostgreSQL o usare WAL mode.