SSD Nodes Learn 🎉 VPS da $5.50/mese
Guide Matt ConnorDi Matt Connor

Come fare il self-hosting del runtime SandBase

Esegui SandBase Harness v0.3.2 sulla tua VPS: installazione con tag, agent YAML, server MCP, modalità sandbox e SDK Anthropic puntato al tuo server.

Cosa si ottiene con il self-hosting del runtime degli agent SandBase

Fare il self-hosting del runtime degli agent SandBase significa eseguire SandBase Harness su un server di propria gestione, in modo che sessioni, credenziali, memoria e audit trail vengano salvati sul proprio disco invece che su quello di terzi. È un servizio Node. Resta in ascolto su 127.0.0.1:3000, espone un'API HTTP /v1 e una console web, e conserva il proprio stato in SQLite accanto ai file degli agent.

L'API /v1 ricalca Claude Managed Agents (CMA), l'API per agent gestiti in hosting. Questo rende il runtime interessante in entrambe le direzioni: è possibile scrivere codice usando l'SDK Anthropic e impostare baseURL sul proprio server, quindi trasferire in seguito lo stesso codice a una distribuzione in hosting.

SandBase Harness non include un modello. Ne utilizza uno. Ad agosto 2026 supporta endpoint OpenAI, Anthropic e compatibili con OpenAI, inclusi gateway self-hosted e provider come DeepSeek V4. È comunque necessario fornire una chiave API oppure un server locale che esponga l'API OpenAI.

Cosa serve prima di iniziare

  • Una VPS con Ubuntu 24.04 e almeno 2 GB di RAM. La compilazione TypeScript è il passaggio più pesante dell’installazione.
  • Node.js 22 o versione successiva e npm 10 o versione successiva. Sono entrambi requisiti minimi vincolanti dichiarati dal progetto.
  • git e una API key per il provider del modello che intendi utilizzare.
  • Docker, ma solo se vuoi sandbox container per singola sessione.

Ubuntu 24.04 include Node 18.19 nel proprio repository. Questa versione è inferiore al requisito minimo, quindi installa Node tramite NodeSource.

curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs git
node -v
npm -v

node -v dovrebbe restituire v22 o una versione successiva, mentre npm -v dovrebbe restituire 10 o una versione successiva. Se node -v restituisce ancora v18.19.1, il pacchetto della distribuzione è ancora installato e ha la precedenza in PATH. Rimuovilo prima di continuare, perché la compilazione utilizza il node trovato dalla shell.

Installare SandBase dal tag v0.3.2

Installare da un tag, mai da un branch soggetto a modifiche. Un clone bare di main fornisce tutto ciò che è stato aggiunto un'ora fa e le chiavi di configurazione riportate di seguito potrebbero non corrispondere. v0.3.2 è il tag corrente al 16 agosto 2026.

sudo install -d -o "$USER" -g "$USER" /opt/sandbase
cd /opt/sandbase
git clone --branch v0.3.2 --depth 1 https://github.com/sandbaseai/sandbase-harness.git
cd sandbase-harness
npm ci
npm run build

Usare npm ci, non npm install. ci installa le versioni esatte registrate nel lockfile sottoposto a commit, quindi l'albero di lavoro corrisponde a quello testato dai manutentori. npm install può risolvere versioni più recenti; in questo modo un tag bloccato smette di esserlo senza che sia evidente.

Ora creare un workspace. Il workspace è una directory separata che contiene i file dell'agente e tutto lo stato di runtime. Mantenerlo fuori dal checkout dei sorgenti consente di scaricare un tag più recente senza modificare i dati.

mkdir -p /opt/sandbase/workspace
cd /opt/sandbase/workspace
node /opt/sandbase/sandbase-harness/dist/index.js init
node /opt/sandbase/sandbase-harness/dist/index.js start

init crea una directory .managed-agents/ nel workspace. start avvia la console su http://127.0.0.1:3000/dashboard e l'API su http://127.0.0.1:3000/v1. Nessuno dei due servizi è ancora raggiungibile dal laptop, ed è il comportamento corretto; questo aspetto viene trattato più avanti. Per ora, accedere alla console tramite SSH:

ssh -N -L 3000:127.0.0.1:3000 you@your-server

Il lungo percorso node .../dist/index.js diventa scomodo, quindi assegnargli un nome.

alias sandbase='node /opt/sandbase/sandbase-harness/dist/index.js'

I comandi riportati di seguito sono scritti come sandbase <command> sulla base di questa configurazione.

Non installarlo da npm

La documentazione di installazione del progetto lo specifica chiaramente: il pacchetto managed-agents senza scope disponibile su npm non appartiene a questo progetto. Di conseguenza, npx managed-agents e npm install -g managed-agents scaricano un pacchetto non correlato al runtime desiderato. Installalo dal codice sorgente taggato su GitHub finché i manutentori non annunciano un pacchetto ufficiale con scope. Non si tratta di una semplice nota nella storia del progetto: v0.3.1 esiste principalmente per sostituire la procedura rapida precedente basata su npm con il percorso basato sul codice sorgente taggato e fissato.

Indicare il provider del modello per il workspace

init scrive .managed-agents/config.yaml. Per l'intero workspace si configura un solo provider; i singoli agent scelgono poi gli ID dei modelli specifici.

model:
  provider: openai
  api_key: ${OPENAI_API_KEY}
storage:
  metadata:
    provider: sqlite
    options: {}
  artifacts:
    provider: local
    options:
      base_path: files

Il formato ${OPENAI_API_KEY} legge il valore dall'ambiente del processo. In questo modo la chiave non viene inserita nel file di configurazione né in alcun backup del file. Inseriscila in un file di ambiente leggibile solo da root, perché systemd legge EnvironmentFile= come root prima di ridurre i privilegi.

sudo install -d -m 750 /etc/sandbase
sudo touch /etc/sandbase/runtime.env
sudo chmod 600 /etc/sandbase/runtime.env

Apri il file con un editor e aggiungi una riga: OPENAI_API_KEY=sk-.... Le chiavi dei provider devono essere inserite qui. I secret utilizzati da un agent durante una sessione devono invece essere archiviati nei vault delle credenziali del runtime. Si tratta di un problema diverso, con un diverso raggio d'impatto. Prima di incollare un token di produzione in una delle due posizioni, leggi come mantenere i secret fuori dagli agent AI.

Il file YAML dell’agente: mcp_servers, strumenti e criteri di autorizzazione

Gli agenti sono definiti come file YAML nella directory agents/ del workspace. È la parte del runtime in cui lavorerai effettivamente più a lungo.

name: Incident commander
description: Triages alerts and coordinates response.
model: gpt-4o
system: |-
  You are an on-call incident commander.
mcp_servers:
  - name: sentry
    type: url
    url: https://mcp.sentry.dev/mcp
tools:
  - type: agent_toolset_20260401
    default_config:
      permission_policy: { type: always_ask }
    configs:
      - name: bash
        permission_policy: { type: always_ask }
  - type: mcp_toolset
    mcp_server_name: sentry
metadata:
  template: incident-commander

Caricalo e verifica che sia stato importato:

sandbase reload
sandbase list
sandbase chat agent_assistant --message "hello"

reload importa il file YAML iniziale in SQLite. list dovrebbe ora stampare l’agente con un ID. Se list non lo mostra, il file non è stato analizzato e .managed-agents/logs/runtime.log contiene il motivo.

mcp_servers dichiara gli endpoint MCP (model context protocol). type: url indica che il runtime comunica tramite HTTP con un server eseguito altrove. Pertanto puoi usare qualsiasi server già operativo, inclusi i server MCP ospitati sullo stesso VPS del runtime.

La dichiarazione di un server non assegna automaticamente i relativi strumenti all’agente. A questo serve l’elenco tools, tramite una voce mcp_toolset il cui valore mcp_server_name corrisponde a name. Se l’agente si comporta come se gli strumenti MCP non esistessero, confronta queste due stringhe carattere per carattere prima di verificare qualsiasi altra cosa.

agent_toolset_20260401 è l’insieme di strumenti integrato. Il suffisso con la data identifica una versione dello schema. Un agente vincolato a questa versione mantiene le definizioni degli strumenti per cui è stato configurato. default_config imposta il criterio per ogni strumento dell’insieme, mentre ogni voce in configs sovrascrive quello di un singolo strumento, bash nell’esempio.

permission_policy è ciò che distingue un runtime da una semplice chiamata al modello. always_ask sospende la sessione e attende che una persona approvi la chiamata prima di eseguirla. always_allow la autorizza. Impostare bash su always_ask significa che l’agente non può eseguire un comando shell senza che tu visualizzi prima il comando completo. È lo stesso controllo che useresti per eseguire Claude Code in sicurezza su un VPS.

I tre modalità sandbox e quando usare ciascuna

Le chiamate agli strumenti che eseguono codice vengono eseguite all'interno di una sandbox. Il backend viene scelto per ambiente tramite sandbox_provider nell'oggetto config dell'ambiente oppure in Settings, quindi Sandbox, nella console. Gli ambienti vengono creati tramite API all'indirizzo POST /v1/environments.

local esegue il codice come processo figlio del runtime, sull'host e con l'utente del runtime. È la modalità predefinita ed è adatta quando sei l'unico utente e l'agente legge soltanto file di tua proprietà. Non fornisce isolamento. Una chiamata agli strumenti che elimina file elimina i tuoi file, mentre una chiamata che legge /etc/sandbase/runtime.env legge la chiave del provider.

docker avvia un container per ogni sessione.

{
  "sandbox_provider": "docker",
  "image": "node:22-slim",
  "resources": { "memory": "1g", "cpu": 1 }
}

La sessione dispone di un proprio filesystem, di un proprio limite di memoria e di una propria quota CPU; il container viene rimosso insieme alla sessione. Passa a questa modalità non appena un agente esegue codice che non hai scritto tu. Il costo è che l'utente del runtime deve poter accedere al socket Docker e che l'appartenenza al gruppo docker equivale a root sull'host. I container per sessione hanno la stessa struttura delle sandbox per agenti self-hosted con un container per esecuzione, quindi il ragionamento su ciò che un processo evaso potrebbe raggiungere si applica anche in questo caso, senza modifiche.

kubernetes esegue il carico di lavoro della sessione come pod e lo gestisce con kubectl exec e kubectl cp. L'immagine del runtime deve includere kubectl e il relativo ServiceAccount deve disporre delle autorizzazioni RBAC (role-based access control) per creare, eliminare, ottenere, elencare e monitorare i pod nel namespace di destinazione, oltre che per il subresource exec. Questa modalità richiede la configurazione necessaria solo se disponi già di un cluster.

Perché il runtime è associato a 127.0.0.1?

Perché viene avviato con l'autenticazione disattivata. Il runtime abilita l'autenticazione tramite bearer token quando esiste almeno una API key, mentre una nuova init non ne crea nessuna. In questo caso, associare il runtime a 0.0.0.0 esporrebbe su Internet un runtime dell'agente non autenticato, con strumenti shell e la chiave del provider.

Quando è necessario renderlo raggiungibile, quindi, lasciare invariato l'indirizzo di bind ed eseguire altre due operazioni.

Per prima cosa, abilitare l'autenticazione. Impostare MANAGED_AGENTS_API_KEY nel file delle variabili d'ambiente del servizio oppure creare una chiave con POST /v1/api-keys. Il comando restituisce un campo secret_key una sola volta e non lo mostra più. I client devono quindi inviare Authorization: Bearer <key> a ogni richiesta.

In secondo luogo, configurare un reverse proxy davanti al runtime e terminare lì TLS (transport layer security). Il runtime serve HTTP in chiaro per progettazione e si aspetta che un altro componente gestisca i certificati.

server {
    listen 443 ssl;
    server_name agents.example.com;

    ssl_certificate     /etc/letsencrypt/live/agents.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/agents.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Connection "";
        proxy_buffering off;
        proxy_read_timeout 3600s;
    }
}

Due di quelle righe non sono decorative. proxy_buffering off è importante perché le sessioni usano server-sent events (SSE) per lo streaming. Con il buffering attivo, nginx trattiene la risposta finché il buffer non è pieno. Di conseguenza, la console non mostra nulla mentre l'agente lavora e visualizza tutto solo alla fine. proxy_read_timeout 3600s è importante perché il valore predefinito è 60 secondi. Uno stream che rimane inattivo per più di un minuto viene quindi chiuso dal proxy durante un turno, facendo sembrare che il runtime sia andato in crash.

Nel firewall, aprire le porte 22 e 443. Lasciare chiusa la porta 3000, perché il proxy la raggiunge tramite loopback e nessun componente esterno al server deve poterla raggiungere.

Indirizzare Anthropic SDK verso il proprio server

Il runtime espone una superficie /v1 compatibile con CMA, quindi un client Anthropic SDK può comunicare con esso modificando un solo campo.

import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({
  apiKey: process.env.MANAGED_AGENTS_API_KEY ?? 'local-dev-key',
  baseURL: 'http://127.0.0.1:3000'
});

Accetta anche gli header beta inviati dai client Claude Managed Agents, anthropic-beta: managed-agents-2026-04-01 e anthropic-beta: agent-memory-2026-07-22. In un runtime locale sono facoltativi. Sono disponibili per consentire l'esecuzione invariata del codice scritto per una distribuzione gestita.

La compatibilità è elevata, ma non completa. Prima di presumere che una determinata funzionalità sia disponibile, leggere docs/api-matrix.md nel checkout. Il progetto documenta lì le proprie limitazioni, inclusi gli strumenti personalizzati lato client, che richiedono ancora la registrazione con nome rispetto all'attuale protocollo dei risultati degli eventi.

Anche HTTP semplice funziona senza problemi ed è il modo più rapido per verificare che il runtime sia attivo:

curl -N -X POST http://127.0.0.1:3000/v1/sessions/SESSION_ID/messages \
  -H "Content-Type: application/json" \
  -d '{"content": "Hello", "stream": true}'

Una risposta corretta è costituita da un flusso continuo di eventi. Se la connessione si interrompe, riprendere dall'ultimo evento ricevuto invece di ripetere l'intero turno:

curl -N http://127.0.0.1:3000/v1/sessions/SESSION_ID/events/stream \
  -H "Last-Event-ID: EVENT_ID"

Questo flusso ripristinabile consente alla sessione di sopravvivere alla chiusura del portatile. Gli eventi vengono salvati sul server, quindi il client riproduce un log invece di conservare l'unica copia.

Dove risiedono su disco credenziali, memoria e registri di audit

Tutto ciò che appartiene al runtime si trova sotto .managed-agents/ nell'area di lavoro.

.managed-agents/
├── config.yaml
├── data.db
├── logs/runtime.log
├── files/
├── skills/
├── snapshots/
└── sandbox/
  • data.db contiene i metadati SQLite: agenti, sessioni, voci del vault delle credenziali, voci dell'archivio di memoria e chiavi API.
  • files/ contiene i byte dei file caricati e skills/ contiene i pacchetti di skill caricati.
  • snapshots/ contiene le snapshot dell'area di lavoro delle sessioni, mentre sandbox/ contiene le directory di lavoro delle sessioni in modalità locale.
  • logs/runtime.log è il primo punto da controllare quando qualcosa non produce alcun effetto senza segnalare errori.

I vault delle credenziali sono gruppi di secret. Ogni gruppo viene aggiunto con un auth_type, ad esempio environment_variable, e associato a una sessione tramite vault_ids quando la sessione viene creata. Gli archivi di memoria contengono voci con nome che vengono montate in una sessione come memory_store, con impostazioni di accesso e istruzioni proprie. Entrambi risiedono in data.db. Questa è esattamente la differenza rispetto a una semplice chiamata al modello: il runtime conserva le informazioni tra le sessioni e registra ciò che è accaduto.

Poiché si tratta di una sola directory, esegui il backup dell'intera directory.

sudo systemctl stop sandbase
sudo tar czf /root/sandbase-$(date +%F).tgz -C /opt/sandbase/workspace .managed-agents
sudo systemctl start sandbase

Arresta prima il servizio. Copiare un database SQLite mentre il runtime vi sta scrivendo può produrre un file che non sarà possibile aprire durante il ripristino. Potresti accorgertene proprio quando ti serve. Se preferisci mantenere gli YAML degli agenti in git e lo stato in un'altra posizione, la documentazione del deployment supporta l'impostazione permanente della posizione dello stato tramite --data-dir su start.

Il ripristino segue il procedimento inverso: esegui il checkout dello stesso tag su un sistema nuovo, decomprimi l'archivio nell'area di lavoro e avvia il servizio. Se hai usato la forma ${OPENAI_API_KEY}, la chiave del provider non si trova nell'archivio. Conservala quindi in un luogo a cui avrai ancora accesso.

Eseguilo con systemd

Assegna al runtime un utente dedicato, in modo che una chiamata allo strumento in modalità sandbox locale non possa operare come il tuo utente.

sudo adduser --system --group --no-create-home --home /opt/sandbase sandbase
sudo chown -R sandbase:sandbase /opt/sandbase

Salva il file come /etc/systemd/system/sandbase.service.

[Unit]
Description=SandBase Harness runtime
After=network-online.target

[Service]
User=sandbase
Group=sandbase
WorkingDirectory=/opt/sandbase/workspace
EnvironmentFile=/etc/sandbase/runtime.env
ExecStart=/usr/bin/node /opt/sandbase/sandbase-harness/dist/index.js start --host 127.0.0.1 --port 3000
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

L'esempio di deployment del progetto richiama un binario managed-agents in PATH. Un'installazione da sorgenti con tag non lo crea, quindi ExecStart esegue node sull'entry point compilato.

sudo systemctl daemon-reload
sudo systemctl enable --now sandbase
sudo systemctl status sandbase
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/dashboard

Un risultato corretto è active (running) da status e 200 da curl. Per qualsiasi altro risultato, leggi prima journalctl -u sandbase -n 50 e poi .managed-agents/logs/runtime.log. enable --now è la parte importante, perché un processo avviato manualmente termina al riavvio successivo.

Cosa si rompe e quale messaggio viene visualizzato

npm run build viene terminato senza errori da npm. Su un VPS da 1 GB, la compilazione TypeScript viene interrotta dal kernel out-of-memory killer, che registra l'evento nel log del kernel anziché in npm. Verificalo con journalctl -k | grep -i "out of memory", che stampa una riga contenente il nome del processo node terminato. Aggiungi lo swap oppure esegui la compilazione su un'istanza più grande e copia dist/.

Error: listen EADDRINUSE: address already in use 127.0.0.1:3000. Un altro processo sta già usando la porta. sudo ss -lntp | grep 3000 ne indica il nome. Arresta quel processo oppure avvia il runtime con --port 3001 e aggiorna il proxy.

La dashboard non viene caricata dal laptop. È il comportamento previsto, perché il runtime è associato all'interfaccia di loopback. Usa il tunnel SSH precedente oppure completa la configurazione del reverse proxy. Non risolvere il problema con --host 0.0.0.0, perché l'autenticazione resta disattivata finché non esiste una chiave.

Le sandbox Docker non funzionano con permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock. L'utente sandbase non appartiene al gruppo docker. Correggi il problema con sudo usermod -aG docker sandbase e riavvia il servizio, tenendo presente cosa hai autorizzato: quel gruppo equivale a root sull'host, quindi annulla in parte il motivo per cui hai assegnato al runtime un utente dedicato.

Le sandbox Kubernetes non funzionano con Error from server (Forbidden). Al ServiceAccount mancano le autorizzazioni sui pod o sul subresource exec. Verificalo direttamente con kubectl auth can-i create pods/exec -n <namespace>, che restituisce yes o no.

Ogni richiesta restituisce 401 dopo l'aggiunta di una API key. L'autenticazione viene attivata quando esiste la prima chiave e si applica sia alla console sia all'API. Invia Authorization: Bearer <key>. Se hai perso la chiave, creane un'altra, perché secret_key viene restituito una sola volta e non viene memorizzato in forma leggibile.

Gli strumenti di un server MCP non vengono mai visualizzati in una sessione. Confronta mcp_server_name nel blocco tools con name in mcp_servers, quindi verifica che il runtime possa raggiungere l'URL direttamente dal server con curl -i <url>. Un server MCP di tipo URL è una dipendenza di rete; un VPS risolve i nomi e instrada il traffico in modo diverso dal laptop.

FAQ

È possibile eseguire SandBase Harness senza una chiave OpenAI o Anthropic?

Sì, se si dispone di un endpoint compatibile con OpenAI. Il runtime supporta provider OpenAI, Anthropic e compatibili con OpenAI, quindi funziona anche un server locale che espone l'API OpenAI. Impostare il provider del workspace in .managed-agents/config.yaml e configurare api_key e l'endpoint affinché puntino a quel server. Il runtime non include un modello proprio, quindi un servizio deve rispondere alle chiamate.

È sicuro esporre il runtime su una porta pubblica?

Non con la configurazione predefinita. Il runtime è in ascolto su 127.0.0.1:3000 e avvia l'autenticazione disabilitata; la soluzione non consiste nel cambiare l'indirizzo di bind. Creare una API key oppure impostare MANAGED_AGENTS_API_KEY per abilitare l'autenticazione tramite bearer token. Quindi configurare nginx o Caddy davanti al runtime per gestire TLS e mantenere chiusa la porta 3000 nel firewall, in modo che l'unico accesso passi dal proxy.

Qual è la differenza tra le sandbox local, Docker e Kubernetes?

local esegue il codice degli strumenti come processo figlio del runtime sull'host, con i permessi dell'utente del runtime e senza isolamento. docker assegna a ogni sessione un container dedicato, con filesystem, limite di memoria e quota CPU propri, e lo rimuove al termine della sessione. kubernetes esegue la sessione come pod e la gestisce tramite kubectl exec, che richiede kubectl all'interno dell'immagine del runtime e autorizzazioni RBAC sui pod, oltre al subresource exec nel namespace di destinazione.

Che cosa è necessario includere esattamente nel backup?

La directory .managed-agents/ nel workspace. Contiene config.yaml, il database SQLite data.db con agenti, sessioni, voci del credential vault e voci della memoria, oltre ai file caricati, ai pacchetti skill e agli snapshot delle sessioni. Arrestare il servizio prima di copiarla, in modo che SQLite non venga scritto durante la creazione dell'archivio. Le API key dei provider referenziate come ${OPENAI_API_KEY} non sono incluse nel backup; archiviarle separatamente.

Perché clonare il tag v0.3.2 invece di main?

Un tag identifica un tree fisso, quindi le chiavi di configurazione e i comandi CLI descritti sono quelli effettivamente disponibili. main cambia nel tempo e una chiave di configurazione può essere rinominata tra la pubblicazione di una guida e il momento in cui la si esegue. Il progetto avverte inoltre che il pacchetto managed-agents senza scope su npm non è questo progetto, quindi npx managed-agents installa un componente non correlato. La release v0.3.1 esiste soprattutto per sostituire il quick start npm con il percorso basato sul codice sorgente del tag fissato.