SSD Nodes Learn 🎉 VPS desde $5.50/mes
Guías Matt ConnorPor Matt Connor

Cómo alojar SandBase Harness en su propio VPS

Ejecute SandBase Harness v0.3.2 en su VPS con instalación fijada, YAML de agentes, servidores MCP, modos sandbox y el SDK de Anthropic apuntando a su servidor.

Qué obtiene al alojar usted mismo el runtime de agentes de SandBase

Alojar usted mismo el runtime de agentes de SandBase significa ejecutar SandBase Harness en un servidor que administra, de modo que las sesiones, las credenciales, la memoria y los registros de auditoría se almacenan en su disco y no en el de un tercero. Es un servicio de Node. Escucha en 127.0.0.1:3000, ofrece una API HTTP /v1 y una consola web, y guarda su estado en SQLite junto a los archivos de sus agentes.

La API /v1 sigue el modelo de Claude Managed Agents (CMA), la API de agentes administrados alojada. Esto hace que este runtime sea útil en ambos sentidos: puede escribir código con el SDK de Anthropic y apuntar su baseURL a su propio servidor, y después trasladar el mismo código a una implementación alojada.

SandBase Harness no incluye un modelo. Lo invoca. En agosto de 2026 admite OpenAI, Anthropic y endpoints compatibles con OpenAI, lo que incluye gateways alojados por usted y proveedores como DeepSeek V4. Aun así, debe proporcionar una clave de API o un servidor local que implemente la API de OpenAI.

Qué necesita antes de empezar

  • Un VPS con Ubuntu 24.04 y al menos 2 GB de RAM. La compilación de TypeScript es el paso más pesado de la instalación.
  • Node.js 22 o posterior y npm 10 o posterior. Son los requisitos mínimos estrictos indicados por el proyecto.
  • git, además de una clave de API del proveedor del modelo que vaya a utilizar.
  • Docker, pero sólo si quiere contenedores aislados por sesión.

Ubuntu 24.04 incluye Node 18.19 en su propio repositorio. Esta versión es inferior al mínimo requerido, por lo que debe instalar Node desde 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 debe mostrar v22 o posterior, y npm -v debe mostrar 10 o posterior. Si node -v todavía muestra v18.19.1, el paquete de la distribución sigue instalado y tiene prioridad en PATH. Elimínelo antes de continuar, porque la compilación se ejecuta con el node que encuentre el shell.

Instalar SandBase desde la etiqueta v0.3.2

Instale desde una etiqueta, nunca desde una rama que pueda cambiar. Un clon simple de main obtiene todo lo incorporado hace una hora, y las claves de configuración siguientes pueden no coincidir con ese contenido. v0.3.2 es la etiqueta actual a 16 August 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

Use npm ci, no npm install. ci instala las versiones exactas registradas en el archivo de bloqueo confirmado, por lo que el árbol local coincide con el árbol que probaron los mantenedores. npm install puede resolver versiones más recientes. Por eso una etiqueta fijada puede dejar de estar fijada sin avisar.

Ahora cree un workspace. El workspace es un directorio independiente que contiene los archivos del agente y todo el estado de ejecución. Mantenerlo fuera del checkout del código fuente permite extraer una etiqueta más reciente sin modificar los datos.

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 escribe un directorio .managed-agents/ en el workspace. start inicia la consola en http://127.0.0.1:3000/dashboard y la API en http://127.0.0.1:3000/v1. Ninguna de las dos es accesible todavía desde su portátil, lo cual es correcto y se explica más adelante. Acceda ahora a la consola mediante SSH:

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

Esa ruta larga de node .../dist/index.js resulta incómoda, así que asígnele un nombre.

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

Los comandos siguientes están escritos como sandbase <command> sobre esa base.

No lo instale desde npm

El proyecto lo indica en su propia documentación de instalación: el paquete managed-agents sin ámbito que aparece en npm no pertenece a este proyecto. Por tanto, npx managed-agents y npm install -g managed-agents descargan algo que no está relacionado con el runtime que necesita. Instálelo desde el código fuente etiquetado de GitHub hasta que los mantenedores anuncien un paquete oficial con ámbito. No se trata de una nota menor en la historia del proyecto: v0.3.1 existe principalmente para sustituir el inicio rápido antiguo de npm por la ruta fijada al código fuente etiquetado.

Apunte el espacio de trabajo a un proveedor de modelos

init escribe .managed-agents/config.yaml. Se configura un proveedor para todo el espacio de trabajo y, después, cada agente elige IDs de modelo concretos.

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

El formulario ${OPENAI_API_KEY} toma el valor del entorno del proceso. Por tanto, la clave queda fuera del archivo de configuración y de todas las copias de seguridad que haga de ese archivo. Guárdela en un archivo de entorno que sólo root pueda leer, porque systemd lee EnvironmentFile= como root antes de eliminar privilegios.

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

Abra ese archivo en un editor y añada una línea, OPENAI_API_KEY=sk-.... Las claves de los proveedores deben ir aquí. Los secretos que usa un agente durante una sesión deben guardarse en los almacenes de credenciales del entorno de ejecución. Es un problema distinto, con un alcance de impacto diferente. Consulte mantener los secretos fuera de los agentes de IA antes de pegar un token de producción en cualquiera de los dos lugares.

El YAML del agente: mcp_servers, tools y políticas de permisos

Los agentes se definen como archivos YAML en el directorio agents/ del workspace. Esta es la parte del runtime en la que realmente pasará la mayor parte del tiempo.

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

Cárguelo y compruebe que se haya importado:

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

reload importa el YAML inicial en SQLite. list debería mostrar ahora el agente con un ID. Si list no lo muestra, el archivo no se analizó y .managed-agents/logs/runtime.log contiene el motivo.

mcp_servers declara los endpoints de MCP (model context protocol). type: url significa que el runtime se comunica por HTTP con un servidor que se ejecuta en otro lugar. Por tanto, aquí funciona cualquier servidor que ya administre, incluidos los servidores MCP alojados en el mismo VPS que el runtime.

Declarar un servidor no entrega sus herramientas al agente. La lista tools se encarga de eso mediante una entrada mcp_toolset cuyo mcp_server_name coincide con el name anterior. Si el agente se comporta como si las herramientas de MCP no existieran, compare esas dos cadenas carácter por carácter antes de buscar en otro lugar.

agent_toolset_20260401 es el conjunto de herramientas integrado. El sufijo con fecha es una versión del esquema. Por tanto, un agente fijado a esa versión conserva las definiciones de herramientas para las que fue escrito. default_config establece la política para todas las herramientas del conjunto, y cada entrada de configs sustituye la política de una herramienta por su nombre, bash en el ejemplo.

permission_policy es donde un runtime ofrece una ventaja frente a una llamada directa al modelo. always_ask pausa la sesión y espera a que una persona apruebe la llamada antes de ejecutarla. always_allow permite ejecutarla. Establecer bash en always_ask significa que el agente no puede ejecutar un comando de shell sin que usted vea primero el comando exacto. Es el mismo control que aplicaría al ejecutar Claude Code de forma segura en un VPS.

Los tres modos de sandbox y cuándo usar cada uno

Las llamadas a herramientas que ejecutan código se realizan dentro de un sandbox. El backend se elige por entorno, mediante sandbox_provider en el objeto config del entorno, o en la consola, en Settings y después Sandbox. Los entornos se crean mediante la API en POST /v1/environments.

local ejecuta el código como un proceso hijo del runtime, en el host y con el usuario del propio runtime. Es el modo predeterminado y resulta razonable mientras usted sea el único usuario y el agente sólo lea archivos de su propiedad. No proporciona aislamiento. Una llamada a una herramienta que elimine archivos elimina sus archivos, y una llamada que lea /etc/sandbase/runtime.env lee la clave del proveedor.

docker inicia un contenedor por sesión.

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

La sesión obtiene su propio sistema de archivos, su propio límite de memoria y su propia cuota de CPU. El contenedor se elimina con la sesión. Cambie a este modo en cuanto un agente ejecute código que usted no haya escrito. El coste es que el usuario del runtime necesita acceso al socket de Docker, y pertenecer al grupo docker equivale a tener root en el host. Los contenedores por sesión tienen la misma estructura que los sandboxes autohospedados de agentes con un contenedor por ejecución, por lo que el análisis de lo que podría alcanzar un proceso que escapara se aplica aquí sin cambios.

kubernetes ejecuta la carga de trabajo de la sesión como un pod y la gestiona con kubectl exec y kubectl cp. La imagen del runtime necesita tener kubectl instalado, y su ServiceAccount necesita permisos de RBAC (control de acceso basado en roles) para crear, eliminar, obtener, listar y monitorizar pods en el namespace de destino, además del subrecurso exec. Este modo sólo compensa el trabajo de configuración si ya ejecuta un clúster.

¿Por qué el runtime está enlazado a 127.0.0.1?

Porque se inicia con la autenticación desactivada. El runtime activa la autenticación mediante bearer token cuando existe al menos una clave de API, y un init nuevo no crea ninguna. Enlazar 0.0.0.0 con ese valor predeterminado expondría en Internet un runtime de agente sin autenticación, con herramientas de shell y la clave del proveedor.

Por tanto, cuando necesite que sea accesible, mantenga intacta la dirección de enlace y haga otras dos cosas.

Primero, active la autenticación. Defina MANAGED_AGENTS_API_KEY en el archivo de entorno del servicio o cree una clave con POST /v1/api-keys. Este comando devuelve un campo secret_key una sola vez y no vuelve a mostrarlo. Después, los clientes envían Authorization: Bearer <key> en cada solicitud.

Segundo, coloque un reverse proxy delante y termine TLS (seguridad de la capa de transporte) allí. El runtime sirve HTTP sin cifrar por diseño y espera que otro componente gestione los certificados.

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;
    }
}

Dos de esas líneas no son decorativas. proxy_buffering off es importante porque las sesiones se transmiten mediante eventos enviados por el servidor (SSE). Si el buffering está activado, nginx retiene la respuesta hasta que se llena su búfer. Por eso, la consola no muestra nada mientras trabaja el agente y después muestra todo de golpe. proxy_read_timeout 3600s también es importante porque el valor predeterminado es de 60 segundos. Si una transmisión permanece inactiva más de un minuto, el proxy la cierra durante un turno y el fallo parece un bloqueo del runtime.

En el firewall, abra 22 y 443. Mantenga cerrado 3000, porque el proxy accede a ese puerto mediante loopback y nada externo debe acceder al servidor.

Indique al SDK de Anthropic su propio servidor

El runtime implementa una interfaz con forma de CMA /v1, por lo que un cliente del SDK de Anthropic puede comunicarse con él cambiando 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'
});

También acepta las cabeceras beta que envían los clientes de Claude Managed Agents, anthropic-beta: managed-agents-2026-04-01 y anthropic-beta: agent-memory-2026-07-22. Son opcionales frente a un runtime local. Existen para que el código escrito para una implementación alojada se ejecute aquí sin cambios.

La compatibilidad es amplia, pero no total. Lea docs/api-matrix.md en el checkout antes de dar por disponible una interfaz, porque el proyecto documenta allí sus propias carencias, incluidas las herramientas personalizadas del lado del cliente, que todavía necesitan un registro con nombre por encima del protocolo actual de resultados de eventos.

HTTP plano funciona igual de bien y es la forma más rápida de comprobar que el runtime está activo:

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 respuesta correcta es un flujo de eventos que sigue llegando. Si la conexión se interrumpe, reanude desde el último evento recibido en lugar de repetir todo el turno:

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

Este flujo reanudable permite que una sesión sobreviva al cierre del portátil. Los eventos se conservan en el servidor, por lo que el cliente reproduce un registro en lugar de mantener la única copia.

Dónde se almacenan en disco las credenciales, la memoria y los registros de auditoría

Todo lo que administra el runtime se encuentra en .managed-agents/ dentro del workspace.

.managed-agents/
├── config.yaml
├── data.db
├── logs/runtime.log
├── files/
├── skills/
├── snapshots/
└── sandbox/
  • data.db es la base de datos SQLite de metadatos: agentes, sesiones, entradas del almacén de credenciales, entradas del almacén de memoria y API keys.
  • files/ contiene los bytes de los archivos cargados y skills/ contiene los paquetes de skills cargados.
  • snapshots/ contiene las instantáneas del workspace de las sesiones, y sandbox/ contiene los directorios de trabajo de las sesiones en modo local.
  • logs/runtime.log es el primer lugar que debe revisar cuando algo no hace nada de forma silenciosa.

Los almacenes de credenciales son grupos de secretos. Cada uno se añade con un auth_type, como environment_variable, y se asocia a una sesión mediante vault_ids al crearla. Los almacenes de memoria contienen entradas con nombre que se montan en una sesión como memory_store, con su propia configuración de acceso e instrucciones. Ambos se encuentran en data.db. Esta es precisamente la diferencia con una llamada directa al modelo: el runtime conserva información entre sesiones y registra lo ocurrido.

Como todo está en un único directorio, haga una copia de seguridad del directorio completo.

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

Detenga primero el servicio. Copiar una base de datos SQLite mientras el runtime escribe en ella puede generar un archivo que no se pueda abrir al restaurarlo. Es posible que sólo lo descubra el día que necesite la copia. Si prefiere mantener el YAML de los agentes en git y el estado en otra ubicación, la documentación de despliegue permite fijar la ubicación del estado mediante --data-dir en start.

La restauración se realiza en orden inverso: consulte el mismo tag en un equipo nuevo, descomprima el archivo en el workspace e inicie el servicio. La clave del proveedor no está en el archivo si utilizó la forma ${OPENAI_API_KEY}, así que guárdela en un lugar al que todavía tenga acceso.

Ejecutarlo con systemd

Asigne al entorno de ejecución un usuario propio para que una llamada a una herramienta en modo sandbox local no pueda actuar como usted.

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

Guarde esto como /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

El ejemplo de despliegue del proyecto llama a un binario managed-agents en PATH. Una instalación desde el código fuente etiquetado no crea uno, por lo que ExecStart ejecuta node directamente contra el punto de entrada compilado.

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 resultado correcto es active (running) de status y 200 de curl. Para cualquier otro resultado, lea primero journalctl -u sandbase -n 50 y después .managed-agents/logs/runtime.log. enable --now es la mitad importante, porque un proceso iniciado manualmente desaparece tras el siguiente reinicio.

Qué falla y qué mensaje verá

npm run build se termina sin mostrar errores de npm. En un VPS de 1 GB, el kernel detiene la compilación de TypeScript mediante el mecanismo de falta de memoria. El mensaje se registra en el log del kernel, no en npm. Confírmelo con journalctl -k | grep -i "out of memory", que muestra una línea con el proceso node terminado. Añada swap, o compile en una instancia más grande y copie dist/.

Error: listen EADDRINUSE: address already in use 127.0.0.1:3000. Otro proceso ya utiliza el puerto. sudo ss -lntp | grep 3000 indica cuál es. Detenga ese proceso o inicie el runtime con --port 3001 y actualice el proxy.

El dashboard no se carga desde el portátil. Este comportamiento es intencionado porque el runtime está enlazado a loopback. Use el túnel SSH anterior o termine de configurar el reverse proxy. No lo corrija con --host 0.0.0.0, porque la autenticación está desactivada hasta que exista una clave.

Los sandboxes de Docker fallan con permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock. El usuario sandbase no pertenece al grupo docker. Corríjalo con sudo usermod -aG docker sandbase y reinicie el servicio. Tenga en cuenta lo que ha concedido: ese grupo equivale a root en el host, por lo que elimina parte del aislamiento que justificaba asignar al runtime su propio usuario.

Los sandboxes de Kubernetes fallan con Error from server (Forbidden). Al ServiceAccount le faltan permisos sobre pods o sobre el subrecurso exec. Compruébelo directamente con kubectl auth can-i create pods/exec -n <namespace>, que responde yes o no.

Todas las peticiones devuelven 401 después de añadir una clave de API. La autenticación se activa cuando existe la primera clave y se aplica tanto a la consola como a la API. Envíe Authorization: Bearer <key>. Si ha perdido la clave, cree otra, porque secret_key se devuelve una sola vez y no se almacena en un formato legible.

Las herramientas de un servidor MCP nunca aparecen en una sesión. Compruebe que el mcp_server_name del bloque tools coincida con el name de mcp_servers. Después, verifique que el runtime pueda acceder a la URL desde el propio servidor mediante curl -i <url>. Un servidor MCP de tipo URL es una dependencia de red. Un VPS resuelve nombres y encamina el tráfico de forma distinta a un portátil.

FAQ

¿Puedo ejecutar SandBase Harness sin una clave de OpenAI o Anthropic?

Sí, si tiene un endpoint compatible con OpenAI. El runtime admite proveedores de OpenAI, Anthropic y compatibles con OpenAI, por lo que funciona un servidor local que implemente la API de OpenAI. Configure el proveedor del workspace en .managed-agents/config.yaml y apunte api_key y el endpoint a él. El runtime no incluye ningún modelo propio, por lo que algún servicio debe responder a las llamadas.

¿Es seguro exponer el runtime en un puerto público?

No con la instalación predeterminada. Se enlaza a 127.0.0.1:3000 y se inicia con la autenticación desactivada. La solución no consiste en usar otra dirección de enlace. Cree una clave de API o establezca MANAGED_AGENTS_API_KEY para activar la autenticación mediante bearer token. Después, coloque nginx o Caddy delante para TLS y mantenga cerrado el puerto 3000 en el firewall, de modo que el único acceso sea a través del proxy.

¿Cuál es la diferencia entre los sandboxes local, Docker y Kubernetes?

local ejecuta el código de las herramientas como un proceso hijo del runtime en el host, con los permisos del usuario del runtime y sin aislamiento. docker proporciona a cada sesión su propio contenedor, con su propio sistema de archivos, límite de memoria y cuota de CPU, y lo elimina cuando termina la sesión. kubernetes ejecuta la sesión como un pod y lo gestiona mediante kubectl exec, que necesita kubectl dentro de la imagen del runtime y RBAC para los pods, además del subrecurso exec en el namespace de destino.

¿Qué debo incluir exactamente en la copia de seguridad?

El directorio .managed-agents/ del workspace. Contiene config.yaml, la base de datos SQLite data.db con los agentes, las sesiones, las entradas del almacén de credenciales y las entradas de memoria, además de los archivos subidos, los paquetes de skills y las instantáneas de las sesiones. Detenga el servicio antes de copiarlo para que SQLite no se escriba durante la creación del archivo. Las claves de API de los proveedores referenciadas como ${OPENAI_API_KEY} no están dentro de la copia de seguridad, por lo que debe almacenarlas por separado.

¿Por qué clonar la etiqueta v0.3.2 en lugar de main?

Una etiqueta identifica un árbol fijo, por lo que las claves de configuración y los comandos CLI que consulta son los que realmente obtiene. main cambia, y una clave de configuración puede cambiar de nombre entre el momento en que se escribe una guía y el momento en que se ejecuta. El proyecto también advierte que el paquete managed-agents sin ámbito en npm no pertenece a este proyecto, por lo que npx managed-agents instala algo no relacionado. La versión v0.3.1 existe principalmente para sustituir ese inicio rápido de npm por el procedimiento basado en el código fuente fijado a una etiqueta.