Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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_id→topics.id(molti a uno)requests.session_idraggruppa logicamente richieste della stessa conversazione- Un topic può avere zero o più richieste
- Una sessione è definita implicitamente dal
session_idcondiviso 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
- Il client invia
POST /api/requestscon prompt e topic_id - FastAPI valida la richiesta, verifica API key, controlla rate limit
- Crea record in SQLite con
status: "pending", restituisce202 Acceptedcon ID - Il client riceve l'ID e può fare polling su
GET /api/requests/{id} - Il worker loop (nel FastAPI service) prende la richiesta pending più vecchia
- Aggiorna status a
processing - Carica il
system_promptdal topic associato - Chiama il worker Node.js via HTTP:
POST http://claude-worker:3001/process - Il worker Node.js esegue la richiesta tramite Claude Code SDK
- Il worker restituisce il risultato
- FastAPI aggiorna il record:
status: "completed",result: "..." - Se
summarize: true: il worker genera un riassunto dello scambio e lo appende alsummarydel topic - 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.
- Prima richiesta:
POST /api/requests {prompt, topic_id}senza session_id - Il sistema genera un UUID come
session_id, lo salva nel record - La risposta include il
session_idgenerato - Richieste successive: il client passa lo stesso
session_idricevuto - Il worker Node.js usa il session_id per mantenere il contesto con Claude Code
(flag
--resumeo equivalente SDK) - 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
- Il client chiama
POST /api/topics/{id}/summarize - FastAPI raccoglie tutte le richieste
completeddel topic - Compone un payload con tutti gli scambi (prompt + risultato)
- Chiama il worker per generare un riassunto complessivo
- Sostituisce il campo
summarydel topic con il nuovo riassunto - Restituisce il summary aggiornato
5.4 Flusso summary incrementale
- Richiesta con
summarize: trueviene completata - Dopo aver salvato il risultato, il worker genera un riassunto in 1-2 frasi dello scambio
- Il riassunto viene appeso al
summarydel 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:
- Request
model— se la richiesta specifica un modello, usa quello - Topic
model— se il topic ha un modello configurato, usa quello - Default globale —
CLAUDE_MODELdal.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-7claude-sonnet-4-6claude-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 Requestscon headerRetry-AftereX-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-Keyobbligatorio su tutti gli endpoint - Chiave configurata in
.env - Risposta
403 Forbiddense mancante o non valida
Isolamento Worker
- Il worker Node.js non è esposto all'esterno (
expose, nonports) - 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
- Clona il repository e copia
.env.examplein.env, compila i valori - Avvia solo il worker per autenticare Claude Code:
docker compose run --rm claude-worker npx claude login - Segui il flusso di autenticazione nel browser
- Le credenziali vengono salvate nel volume
claude-auth - Avvia il sistema completo:
docker compose up -d - 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
failedcon 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.