Files
opus-agent/docs/superpowers/specs/2026-05-25-opus-agent-design.md
2026-05-25 18:26:20 +02:00

20 KiB

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_idtopics.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.

// 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",
    "model": "claude-sonnet-4-6",
    "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.

// 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",
    "session_id": "a1b2c3d4-...",
    "status": "pending",
    "created_at": "2026-05-25T10:05:00Z"
  },
  "error": null
}

GET /api/requests/{id}

Restituisce stato e risultato (quando disponibile).

// 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?",
    "model": null,
    "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

Il session_id è generato dal server. Il client non deve inventarne uno.

  1. Prima richiesta: POST /api/requests {prompt, topic_id} senza session_id
  2. Il sistema genera un UUID come session_id, lo salva nel record
  3. La risposta include il session_id generato
  4. Richieste successive: il client passa lo stesso session_id ricevuto
  5. Il worker Node.js usa il session_id per mantenere il contesto con Claude Code (flag --resume o equivalente SDK)
  6. Ogni richiesta nella sessione condivide la storia della conversazione

Per one-shot: il client non invia session_id (o lo invia null). Il record viene creato senza session_id e la richiesta è indipendente.

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

// 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

// 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

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 globaleCLAUDE_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)

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)

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

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:

# 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:
    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:
    docker compose up -d
    
  6. Verifica con GET /api/health

Rinnovo credenziali

Se le credenziali Claude Code scadono:

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.