Come eseguire SandBase Harness sul tuo server
Scopri come eseguire SandBase Harness v0.3.2 su una VPS: installazione taggata, YAML degli agent, server MCP, modalità sandbox e Anthropic SDK sul tuo host.
Cosa si ottiene eseguendo in proprio il runtime degli agent SandBase
Eseguire in proprio il runtime degli agent SandBase significa eseguire SandBase Harness su un server di tua proprietà, in modo che sessioni, credenziali, memoria e audit trail vengano archiviati sul tuo disco anziché su quello di terzi. È un servizio Node. Rimane 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 degli agent gestiti in hosting. Questo rende interessante il runtime in entrambe le direzioni: puoi scrivere codice basato su Anthropic SDK e configurare il relativo baseURL per usare il tuo server, quindi spostare in seguito lo stesso codice su un deployment in hosting.
SandBase Harness non include un modello. Ne utilizza uno. Ad agosto 2026 supporta endpoint OpenAI, Anthropic e compatibili con OpenAI; questo include gateway self-hosted e provider come DeepSeek V4. Devi comunque 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 i requisiti minimi obbligatori indicati dal progetto.
git, oltre a una chiave API per il provider del modello che intendi utilizzare.- Docker, ma solo se vuoi sandbox containerizzate separate per ogni sessione.
Ubuntu 24.04 distribuisce 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 -vnode -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 su PATH. Rimuovilo prima di continuare, perché la compilazione utilizza il valore di 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 include tutto ciò che è stato aggiunto anche solo 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 buildUsare npm ci, non npm install. ci installa le versioni esatte registrate nel lockfile sottoposto a commit, quindi l'albero locale corrisponde a quello testato dai manutentori. npm install può risolvere versioni più recenti; in questo modo un tag bloccato smette di essere tale senza segnalarlo.
Creare ora un workspace. Il workspace è una directory separata che contiene i file dell'agente e tutto lo stato di runtime. Mantenerlo al di fuori del checkout del codice sorgente 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 startinit scrive 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; la procedura è descritta più avanti. Per ora, raggiungere la console tramite SSH:
ssh -N -L 3000:127.0.0.1:3000 you@your-serverIl percorso node .../dist/index.js è lungo e scomodo da digitare, 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
Il progetto lo specifica nella propria documentazione di installazione: 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 componente non correlato al runtime desiderato. Installa il progetto dal sorgente GitHub contrassegnato con il relativo tag finché i maintainer non annunciano un pacchetto ufficiale con scope. Non si tratta di una semplice nota nella storia del progetto: v0.3.1 serve principalmente a sostituire la procedura rapida precedente basata su npm con il percorso bloccato sul sorgente contrassegnato.
Imposta il workspace su un provider di modelli
init scrive .managed-agents/config.yaml. Un unico provider è configurato per l'intero workspace; i singoli agent scelgono quindi gli ID dei modelli specifici.
model:
provider: openai
api_key: ${OPENAI_API_KEY}
storage:
metadata:
provider: sqlite
options: {}
artifacts:
provider: local
options:
base_path: filesIl modulo ${OPENAI_API_KEY} acquisisce il valore dall'ambiente del processo. In questo modo la chiave non viene salvata nel file di configurazione né in alcun backup di quel 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.envApri il file in un editor e aggiungi una riga: OPENAI_API_KEY=sk-.... Le chiavi dei provider devono essere inserite qui. I secret usati da un agent durante una sessione devono invece essere archiviati nei vault delle credenziali del runtime. Si tratta di un problema diverso, con un impatto potenziale diverso; prima di incollare un token di produzione in uno dei due punti, consulta mantieni i secret fuori dagli agent AI.
L’YAML dell’agent: mcp_servers, tools e policy di autorizzazione
Gli agenti sono definiti come file YAML nella directory agents/ del workspace. Questa è la parte del runtime in cui passerai effettivamente più tempo. Le chiavi risultano più chiare dopo aver scritto manualmente un semplice ciclo dell’agent, perché ognuna controlla un aspetto che altrimenti dovresti implementare nel codice: il prompt di sistema, l’elenco degli strumenti e il controllo eseguito prima dell’avvio di uno strumento.
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-commanderCaricalo e verifica che sia stato importato:
sandbase reload
sandbase list
sandbase chat agent_assistant --message "hello"reload importa lo YAML iniziale in SQLite. list dovrebbe ora visualizzare l’agent 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; quindi qui funziona qualsiasi servizio già operativo, inclusi i server MCP ospitati sullo stesso VPS del runtime. La ricerca sul Web è spesso il primo strumento che si tende a usare. Prima di configurarne uno, conviene leggere come fornire a un agente la propria istanza SearXNG, perché uno strumento che restituisce pagine scritte da terzi inserisce testo non attendibile direttamente nel contesto del modello. Il primo collegamento più prudente ha la forma opposta: un endpoint in sola lettura sui dati che già possiedi. È ciò che openGym espone accanto allo stesso tracker degli allenamenti, consentendo all'agente di rispondere a domande sulla cronologia degli allenamenti senza poterla modificare.
Dichiarare un server non assegna automaticamente i suoi strumenti all’agent. A questo serve l’elenco tools, tramite una voce mcp_toolset il cui mcp_server_name corrisponde a name. Se l’agent si comporta come se gli strumenti MCP non esistessero, confronta queste due stringhe carattere per carattere prima di cercare altrove.
agent_toolset_20260401 è l’insieme di strumenti integrato. Il suffisso con la data identifica una versione dello schema. In questo modo, un agent vincolato a quella versione mantiene le definizioni degli strumenti per cui è stato scritto. default_config definisce la policy per ogni strumento dell’insieme, mentre ogni voce sotto configs sovrascrive la configurazione di uno strumento in base al nome, bash nell’esempio.
permission_policy è il punto in cui un runtime offre un controllo superiore a una semplice chiamata al modello. always_ask sospende la sessione e attende l’approvazione di una persona prima di eseguire la chiamata. always_allow consente l’esecuzione. Impostare bash su always_ask significa che l’agent non può eseguire un comando shell senza che tu possa prima vedere il comando esatto. È lo stesso controllo che useresti per eseguire Claude Code in sicurezza su un VPS. Se utilizzi anche DeepSeek Harness, gli stessi controlli sono disponibili come add-on anziché come chiavi YAML, e i plugin che limitano i budget e controllano le chiamate agli strumenti sono l’equivalente più vicino a questo blocco.
Le tre modalità sandbox e quando utilizzare 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 è adeguata quando siete l'unico utente e l'agente legge soltanto file di vostra proprietà. Non fornisce isolamento. Una chiamata agli strumenti che elimina file elimina i vostri file, mentre una chiamata che legge /etc/sandbase/runtime.env legge la vostra 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 filesystem separato, di un proprio limite di memoria e di una propria quota CPU; il container viene rimosso insieme alla sessione. Passate a questa modalità non appena un agente esegue codice che non avete scritto voi. Il costo è che l'utente del runtime deve poter accedere al socket Docker; inoltre, appartenere al gruppo docker equivale ad avere privilegi root sull'host. I container per sessione hanno la stessa struttura delle sandbox self-hosted per agenti con un container per esecuzione, quindi il ragionamento su ciò che un processo evaso potrebbe raggiungere si applica anche in questo caso.
kubernetes esegue il carico di lavoro della sessione come pod e lo gestisce tramite kubectl exec e kubectl cp. L'immagine del runtime deve includere kubectl e il relativo ServiceAccount deve disporre, tramite RBAC (role-based access control), dell'autorizzazione a creare, eliminare, ottenere, elencare e monitorare i pod nel namespace di destinazione, oltre che al subresource exec. Questa modalità richiede la configurazione necessaria soltanto se disponete già di un cluster.
Perché il runtime è associato a 127.0.0.1?
Perché viene avviato senza autenticazione. Il runtime abilita l'autenticazione tramite bearer token quando esiste almeno una chiave API, mentre una nuova init non ne crea nessuna. In questo modo, associarlo a 0.0.0.0 con le impostazioni predefinite esporrebbe su Internet un runtime dell'agente non autenticato, con strumenti shell e la chiave del provider.
Quando vuoi renderlo raggiungibile, lascia invariato l'indirizzo di bind e fai altre due cose.
Per prima cosa, abilita l'autenticazione. Imposta MANAGED_AGENTS_API_KEY nel file di ambiente del servizio oppure crea una chiave con POST /v1/api-keys. Il comando restituisce un campo secret_key una sola volta e non lo mostra più. I client inviano quindi Authorization: Bearer <key> in ogni richiesta. Una chiave rappresenta un'identità condivisa. Se invece vuoi un agente isolato separato per ogni membro del team, mantenendo le chiavi dei provider in un unico gateway, OneCLI è progettato per questo modello.
In secondo luogo, configura un reverse proxy davanti al runtime e termina lì TLS (transport layer security). Per progettazione, il runtime fornisce HTTP in chiaro 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. Quando il buffering è attivo, nginx trattiene la risposta finché il buffer non si riempie. Di conseguenza, la console non mostra nulla mentre l'agente lavora e visualizza tutto in un'unica volta al termine. proxy_read_timeout 3600s è importante perché il valore predefinito è 60 secondi. Se lo stream rimane inattivo per più di un minuto, il proxy lo chiude durante l'esecuzione di un turno e l'errore può sembrare un arresto anomalo del runtime.
Nel firewall, apri 22 e 443. Lascia chiusa la porta 3000, perché il proxy la raggiunge tramite loopback e nessun componente esterno al server deve accedervi.
Indirizzare l'SDK Anthropic al proprio server
Il runtime implementa una superficie /v1 compatibile con CMA, quindi un client dell'SDK Anthropic 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. Servono per eseguire senza modifiche il codice scritto per una distribuzione ospitata.
La compatibilità è ampia, 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 lacune, inclusi gli strumenti personalizzati lato client, che richiedono ancora la registrazione con nome oltre l'attuale protocollo dei risultati degli eventi.
Anche il protocollo HTTP semplice funziona correttamente 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 è un flusso di eventi che continua ad arrivare. Se la connessione si interrompe, riprendere dall'ultimo evento ricevuto invece di riprodurre nuovamente 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 laptop. Gli eventi vengono persistiti sul server, quindi il client riproduce un log invece di conservare l'unica copia.
Dove risiedono su disco credenziali, memoria e audit trail
Tutto ciò che appartiene al runtime si trova in .managed-agents/ nell'area di lavoro.
.managed-agents/
├── config.yaml
├── data.db
├── logs/runtime.log
├── files/
├── skills/
├── snapshots/
└── sandbox/data.dbcontiene i metadati SQLite: agenti, sessioni, voci del vault delle credenziali, voci dell'archivio della memoria e chiavi API.files/contiene i byte dei file caricati eskills/contiene i pacchetti di skill caricati.snapshots/contiene le snapshot dell'area di lavoro delle sessioni, mentresandbox/contiene le directory di lavoro delle sessioni in modalità locale.logs/runtime.logè il primo punto da controllare quando qualcosa non produce alcun risultato senza segnalare errori.
I vault delle credenziali sono gruppi di secret. Ciascun gruppo viene aggiunto con un auth_type, ad esempio environment_variable, e associato a una sessione tramite vault_ids durante la creazione della sessione. Gli archivi della 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 è la differenza fondamentale rispetto a una semplice chiamata al modello: il runtime conserva lo stato tra le sessioni e registra ciò che è avvenuto.
Poiché si tratta di un'unica 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 sandbaseArresta 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. Il problema emergerebbe proprio quando il backup serve. Se preferisci mantenere gli agent YAML in git e lo stato in una posizione separata, la documentazione di deployment consente di fissare la posizione dello stato con --data-dir su start.
Il ripristino segue la procedura inversa: effettua il checkout dello stesso tag su un sistema appena installato, decomprimi l'archivio nell'area di lavoro e avvia il servizio. La chiave del provider non è inclusa nell'archivio se hai usato la forma ${OPENAI_API_KEY}. Conservala quindi in un luogo a cui potrai ancora accedere.
Eseguilo tramite 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/sandbaseSalva questo contenuto 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.targetL'esempio di distribuzione del progetto usa un binario managed-agents in PATH. Un'installazione dal codice sorgente contrassegnato non crea questo binario, quindi ExecStart esegue node direttamente 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/dashboardUn 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 determinante, perché un processo avviato manualmente termina al riavvio successivo.
Cosa può non funzionare e quale messaggio verrà visualizzato
npm run build viene terminato senza errori da npm. Su un VPS da 1 GB, la compilazione TypeScript viene interrotta dall'out-of-memory killer del kernel, che registra l'evento nel log del kernel anziché in npm. Confermalo con journalctl -k | grep -i "out of memory", che stampa una riga contenente il nome del processo node terminato. Aggiungi una partizione o un file di 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 utilizza già la porta. sudo ss -lntp | grep 3000 ne identifica il processo. Arresta quel processo oppure avvia il runtime con --port 3001 e aggiorna il proxy.
Il dashboard non si carica dal laptop. È il comportamento previsto, perché il runtime è associato all'interfaccia di loopback. Usa il tunnel SSH indicato sopra oppure completa la configurazione del reverse proxy. Non correggere il problema con --host 0.0.0.0, perché l'autenticazione resta disabilitata 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. Considera però 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 i permessi 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 chiave API. L'autenticazione si attiva 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 compaiono mai in una sessione. Confronta il mcp_server_name nel blocco tools con il name in mcp_servers. Verifica quindi 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
Posso eseguire SandBase Harness senza una chiave OpenAI o Anthropic?
Sì, se disponi di un endpoint compatibile con OpenAI. Il runtime supporta provider OpenAI, Anthropic e compatibili con OpenAI, quindi funziona anche un server locale che implementa l'API OpenAI. Imposta il provider dell'area di lavoro in .managed-agents/config.yaml e configura api_key e l'endpoint in modo che puntino a tale server. Il runtime non include un proprio modello, quindi un servizio deve rispondere alle richieste.
È sicuro esporre il runtime su una porta pubblica?
Non con la configurazione predefinita. Il runtime è in ascolto su 127.0.0.1:3000 e viene avviato con l'autenticazione disabilitata; la soluzione non consiste nel cambiare l'indirizzo di bind. Crea una chiave API oppure imposta MANAGED_AGENTS_API_KEY per abilitare l'autenticazione tramite bearer token. Poi configura nginx o Caddy davanti al runtime per gestire TLS e mantieni chiusa la porta 3000 nel firewall, in modo che l'unico percorso di 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/ nell'area di lavoro. Contiene config.yaml, il database SQLite data.db con agenti, sessioni, voci del vault delle credenziali e voci della memoria, oltre ai file caricati, ai pacchetti delle skill e alle snapshot delle sessioni. Arresta il servizio prima di copiarla, così SQLite non viene modificato durante la creazione dell'archivio. Le chiavi API dei provider referenziate come ${OPENAI_API_KEY} non sono incluse nel backup, quindi conservale separatamente.
Perché clonare il tag v0.3.2 invece di main?
Un tag identifica un albero del repository immutabile, 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 della guida e la sua esecuzione. Il progetto avverte inoltre che il pacchetto managed-agents non qualificato su npm non corrisponde a questo progetto, quindi npx managed-agents installa un componente non correlato. La release v0.3.1 serve principalmente a sostituire la procedura rapida di npm con il percorso basato sul codice sorgente del tag bloccato.