Як розгорнути SandBase Harness на власному VPS
Інструкція для SandBase Harness v0.3.2: встановлення за тегом, YAML агента, MCP-сервери, режими sandbox і підключення Anthropic SDK до власного VPS.
Що ви отримуєте, розгортаючи runtime агента SandBase на власній інфраструктурі
Self-hosting runtime агента SandBase означає запуск SandBase Harness на сервері, яким ви керуєте. Сесії, облікові дані, пам’ять і журнали аудиту зберігаються на вашому диску, а не на чужій інфраструктурі. Це Node-сервіс. Він слухає 127.0.0.1:3000, надає /v1 HTTP API і вебконсоль, а стан зберігає в SQLite поруч із файлами агента.
Структура /v1 API відповідає Claude Managed Agents (CMA) — керованому API для агентів, що працює в хостed-середовищі. Саме це робить runtime цікавим у двох сценаріях: можна писати код на основі Anthropic SDK і вказати в його baseURL власний сервер, а пізніше перенести той самий код у хостед-розгортання.
SandBase Harness не містить моделі. Він викликає модель. Станом на August 2026 він підтримує OpenAI, Anthropic і OpenAI-сумісні endpoint-и. Це охоплює self-hosted шлюзи та провайдерів, таких як DeepSeek V4. Вам все одно потрібен API key або локальний сервер, який підтримує OpenAI API.
Що потрібно підготувати
- VPS з Ubuntu 24.04 і щонайменше 2 GB оперативної пам’яті. Збірка TypeScript є найважчим етапом інсталяції.
- Node.js 22 або новішої версії та npm 10 або новішої версії. Це жорсткі мінімальні вимоги, зазначені проєктом.
git, а також API key постачальника моделі, яку ви плануєте використовувати.- Docker, але лише якщо потрібні окремі container sandbox для кожної сесії.
Ubuntu 24.04 постачається з Node 18.19 у власному репозиторії. Це нижче за мінімальну вимогу, тому встановлюйте Node з NodeSource.
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs git
node -v
npm -vnode -v має вивести v22 або новішу версію, а npm -v — 10 або новішу версію. Якщо node -v і далі виводить v18.19.1, пакет дистрибутива все ще встановлений і має пріоритет у PATH. Видаліть його перед продовженням, оскільки збірка використовує ту версію node, яку shell знаходить першою.
Встановлення SandBase з тегу v0.3.2
Встановлюйте з тегу, а не з гілки, яка змінюється. Звичайний клон main містить стан, оновлений навіть годину тому, і наведені нижче ключі конфігурації можуть йому не відповідати. Станом на 16 August 2026 поточним є тег v0.3.2.
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Використовуйте npm ci, а не npm install. ci встановлює точні версії, записані у фіксованому lockfile, тому ваше дерево відповідає дереву, яке перевірили мейнтейнери. npm install може вибрати новіші версії. Через це зафіксований тег непомітно перестає бути зафіксованим.
Тепер створіть workspace. Workspace — це окремий каталог, у якому зберігаються файли агента та весь runtime state. Якщо розмістити його за межами checkout із вихідним кодом, ви зможете отримати новий тег, не змінюючи свої дані.
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 записує каталог .managed-agents/ у workspace. start запускає консоль на http://127.0.0.1:3000/dashboard, а API — на http://127.0.0.1:3000/v1. Поки що жоден із них недоступний із вашого ноутбука. Це правильно, і нижче це буде описано докладніше. Наразі підключайтеся до консолі через SSH:
ssh -N -L 3000:127.0.0.1:3000 you@your-serverДовгий шлях node .../dist/index.js швидко набридає, тому задайте йому ім’я.
alias sandbase='node /opt/sandbase/sandbase-harness/dist/index.js'Наведені нижче команди записано як sandbase <command> на цій основі.
Не встановлюйте його з npm
У власній документації з інсталяції проєкт прямо зазначає: пакет managed-agents без scope, доступний у npm, не належить цьому проєкту. Тому npx managed-agents і npm install -g managed-agents завантажують щось, що не має стосунку до потрібного вам runtime. Поки maintainers не оголосять про офіційний пакет зі scope, встановлюйте проєкт із позначеного тегом джерельного коду на GitHub. Це не другорядна примітка в історії проєкту: v0.3.1 переважно призначений для заміни старого швидкого запуску через npm на шлях із фіксованим джерельним кодом із тегом.
Прив’яжіть робочий простір до постачальника моделей
init записує .managed-agents/config.yaml. Для всього робочого простору налаштовується один постачальник, а окремі агенти потім вибирають конкретні ідентифікатори моделей.
model:
provider: openai
api_key: ${OPENAI_API_KEY}
storage:
metadata:
provider: sqlite
options: {}
artifacts:
provider: local
options:
base_path: filesФорма ${OPENAI_API_KEY} отримує значення із середовища процесу, тому ключ не зберігається у файлі конфігурації та в жодній резервній копії цього файлу. Додайте його до файлу середовища, який може читати лише root, оскільки systemd читає EnvironmentFile= від імені root, перш ніж скинути привілеї.
sudo install -d -m 750 /etc/sandbase
sudo touch /etc/sandbase/runtime.env
sudo chmod 600 /etc/sandbase/runtime.envВідкрийте цей файл у редакторі та додайте один рядок: OPENAI_API_KEY=sk-.... Ключі постачальників потрібно зберігати тут. Секрети, які агент використовує під час сеансу, натомість зберігаються у сховищах облікових даних runtime. Це інша проблема з іншим радіусом наслідків, і перед вставленням production-токена в будь-яке з цих місць варто прочитати матеріал про зберігання секретів поза AI-агентами.
YAML агента: mcp_servers, tools і політики дозволів
Агенти визначаються у YAML-файлах у каталозі робочої області agents/. Саме з цією частиною середовища виконання ви працюватимете найчастіше.
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Завантажте файл і перевірте, що його імпортовано:
sandbase reload
sandbase list
sandbase chat agent_assistant --message "hello"reload імпортує початковий YAML у SQLite. list тепер має вивести агента з ідентифікатором. Якщо list його не показує, файл не було розібрано, а причина записана в .managed-agents/logs/runtime.log.
mcp_servers оголошує кінцеві точки MCP (model context protocol). type: url означає, що середовище виконання обмінюється даними через HTTP із сервером, який працює в іншому місці. Тому тут можна використовувати будь-який уже налаштований сервер, зокрема MCP-сервери, розміщені на тому самому VPS, що й середовище виконання.
Оголошення сервера не передає його інструменти агенту. Це робить список tools через запис mcp_toolset, у якому mcp_server_name має збігатися з наведеним вище name. Якщо агент поводиться так, ніби інструментів MCP не існує, спочатку порівняйте ці два рядки посимвольно.
agent_toolset_20260401 — це вбудований набір інструментів. Суфікс із датою є версією схеми. Тому агент, зафіксований на цій версії, використовує визначення інструментів, для яких його було написано. default_config задає політику для всіх інструментів у наборі, а кожен запис у configs перевизначає її для окремого інструмента за іменем, bash у прикладі.
permission_policy — це те, що дає середовищу виконання перевагу над простим викликом моделі. always_ask призупиняє сеанс і чекає, доки людина схвалить виклик перед його виконанням. always_allow дозволяє виконати його. Якщо встановити bash у always_ask, агент не зможе виконати команду shell, доки ви спочатку не побачите точну команду. Це такий самий контроль, який ви застосували б під час безпечного запуску Claude Code на VPS.
Три режими sandbox і коли кожен із них підходить
Виклики інструментів, які виконують код, запускаються всередині sandbox. Backend вибирається для кожного середовища через sandbox_provider в об’єкті config середовища або в консолі: Settings, потім Sandbox. Середовища створюються через API за адресою POST /v1/environments.
local запускає код як дочірній процес runtime на хості від імені того самого користувача, що й runtime. Це режим за замовчуванням. Він підходить, якщо ви єдиний користувач, а агент лише читає файли, які вам належать. Це не ізоляція. Виклик інструмента, який видаляє файли, видалить ваші файли. Виклик інструмента, який читає /etc/sandbase/runtime.env, прочитає ключ вашого провайдера.
docker запускає окремий контейнер для кожної сесії.
{
"sandbox_provider": "docker",
"image": "node:22-slim",
"resources": { "memory": "1g", "cpu": 1 }
}Сесія отримує власну файлову систему, власне обмеження пам’яті та власну частку CPU. Контейнер видаляється разом із сесією. Перемикайтеся на цей режим одразу, щойно агент починає виконувати код, якого ви не писали. Недолік полягає в тому, що користувач runtime повинен мати доступ до Docker socket, а членство в групі docker еквівалентне root на хості. Контейнери для окремих сесій мають таку саму структуру, як sandbox для self-hosted agent з одним контейнером на кожен запуск, тому міркування про те, до чого може отримати доступ процес після виходу за межі ізоляції, застосовні тут без змін.
kubernetes запускає робоче навантаження сесії як pod і керує ним за допомогою kubectl exec та kubectl cp. У runtime image має бути наявний kubectl, а його ServiceAccount повинен мати дозволи RBAC (role-based access control) для створення, видалення, отримання, перелічення та моніторингу pod у цільовому namespace, а також доступ до subresource exec. Цей режим виправданий лише в тому разі, якщо ви вже використовуєте кластер.
Чому runtime прив’язаний до 127.0.0.1?
Тому що він запускається з вимкненою автентифікацією. runtime вмикає автентифікацію bearer-токеном, коли існує принаймні один API key, а новий init не створює жодного. Прив’язка до 0.0.0.0 за такого значення за замовчуванням відкрила б у публічному інтернеті runtime агента без автентифікації, який має доступ до shell-інструментів і вашого ключа провайдера.
Тому, якщо runtime має бути доступним ззовні, не змінюйте адресу прив’язки та виконайте ще дві дії.
По-перше, увімкніть автентифікацію. Задайте MANAGED_AGENTS_API_KEY у файлі змінних середовища сервісу або створіть ключ за допомогою POST /v1/api-keys. Команда один раз повертає поле secret_key і більше його не показує. Після цього клієнти надсилають Authorization: Bearer <key> у кожному запиті.
По-друге, розмістіть reverse proxy перед runtime і завершуйте TLS (transport layer security) на ньому. runtime за задумом обслуговує звичайний HTTP і очікує, що сертифікатами керуватиме інший компонент.
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;
}
}Два з цих рядків потрібні не для оформлення. proxy_buffering off важливий, оскільки сеанси передаються через server-sent events (SSE). Якщо буферизацію ввімкнено, nginx утримує відповідь, доки його буфер не заповниться. Тому консоль нічого не показує, поки агент працює, а потім виводить усе одразу. proxy_read_timeout 3600s важливий, оскільки значення за замовчуванням становить 60 секунд. Якщо потік не передає даних довше хвилини, proxy закриває його посеред виконання запиту, і це виглядає так, ніби runtime аварійно завершив роботу.
У firewall відкрийте 22 і 443. Порт 3000 залиште закритим, оскільки proxy підключається до нього через loopback, а зовнішній доступ до нього не потрібен.
Використовуйте Anthropic SDK зі своїм сервером
Runtime реалізує поверхню /v1, сумісну з CMA, тому клієнту Anthropic SDK потрібно змінити лише одне поле.
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'
});Він також приймає beta-заголовки, які надсилають клієнти Claude Managed Agents: anthropic-beta: managed-agents-2026-04-01 і anthropic-beta: agent-memory-2026-07-22. Для локального runtime вони необов’язкові. Вони дають змогу запускати тут код, написаний для hosted deployment, без змін.
Сумісність майже повна, але не абсолютна. Перш ніж вважати певну поверхню доступною, прочитайте docs/api-matrix.md у checkout. Там проєкт описує власні обмеження, зокрема client-side custom tools, для яких наразі потрібна іменована реєстрація поверх поточного event-result protocol.
Звичайний HTTP працює так само добре. Це найшвидший спосіб перевірити, чи runtime працює:
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}'Справний runtime постійно надсилає потік подій. Якщо з’єднання розривається, відновіть його з останньої отриманої події, а не відтворюйте весь turn повторно:
curl -N http://127.0.0.1:3000/v1/sessions/SESSION_ID/events/stream \
-H "Last-Event-ID: EVENT_ID"Саме цей resumable stream дає змогу сесії пережити закриття ноутбука. Події зберігаються на сервері, тому клієнт відтворює журнал, а не зберігає єдину його копію.
Де на диску зберігаються облікові дані, пам’ять і журнали аудиту
Усе, чим керує runtime, зберігається в .managed-agents/ у workspace.
.managed-agents/
├── config.yaml
├── data.db
├── logs/runtime.log
├── files/
├── skills/
├── snapshots/
└── sandbox/data.db— це метадані SQLite: агенти, сесії, записи сховища облікових даних, записи сховища пам’яті та API keys.files/містить байти завантажених файлів, аskills/— завантажені пакети skills.snapshots/містить знімки workspace сесій, аsandbox/— робочі каталоги сесій у local mode.logs/runtime.log— перше місце, яке слід перевірити, якщо щось мовчки не виконується.
Vaults облікових даних — це групи секретів. Кожен секрет додається за допомогою auth_type, наприклад environment_variable, а до сесії підключається через vault_ids під час її створення. Сховища пам’яті містять іменовані записи, які підключаються до сесії як memory_store із власним параметром доступу та інструкціями. Обидва типи даних зберігаються в data.db. Саме це відрізняє цей підхід від прямого виклику моделі: runtime зберігає контекст між сесіями та записує результати роботи.
Оскільки все зберігається в одному каталозі, створюйте резервну копію всього каталогу.
sudo systemctl stop sandbase
sudo tar czf /root/sandbase-$(date +%F).tgz -C /opt/sandbase/workspace .managed-agents
sudo systemctl start sandbaseСпочатку зупиніть сервіс. Копіювання бази даних SQLite під час запису runtime може створити файл, який не відкриється під час відновлення. Ви можете виявити це саме в день, коли резервна копія знадобиться. Якщо ви хочете зберігати YAML-файли агентів у git, а стан — окремо, документація з розгортання підтримує фіксацію розташування стану за допомогою --data-dir у start.
Відновлення виконується у зворотному порядку: перевірте той самий tag на чистому сервері, розпакуйте архів у workspace і запустіть сервіс. Якщо ви використали форму ${OPENAI_API_KEY}, ключ провайдера не входить до архіву. Зберігайте його в іншому безпечному місці.
Запускайте через systemd
Виділіть для середовища виконання окремого користувача, щоб виклик інструмента в режимі локальної ізольованої оболонки не давав йому діяти від вашого імені.
sudo adduser --system --group --no-create-home --home /opt/sandbase sandbase
sudo chown -R sandbase:sandbase /opt/sandbaseЗбережіть це як /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У власному прикладі розгортання проєкту викликається бінарний файл managed-agents у PATH. Встановлення з позначеного вихідного коду не створює його, тому ExecStart натомість виконує node для зібраної точки входу.
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Успішний результат — active (running) від status і 200 від curl. Якщо результат інший, спочатку прочитайте journalctl -u sandbase -n 50, а потім .managed-agents/logs/runtime.log. enable --now — важлива половина, оскільки процес, запущений вручну, завершується після наступного перезавантаження.
Що може вийти з ладу та яке повідомлення ви побачите
npm run build завершується без помилки від npm. На VPS із 1 GB компіляцію TypeScript припиняє kernel out-of-memory killer. Він записує повідомлення до kernel log, а не до npm. Перевірте це за допомогою journalctl -k | grep -i "out of memory". Команда виведе рядок із назвою процесу node, який було завершено. Додайте swap або виконайте збірку на потужнішому інстансі та скопіюйте dist/.
Error: listen EADDRINUSE: address already in use 127.0.0.1:3000. Інший процес уже використовує порт. Команда sudo ss -lntp | grep 3000 покаже, який саме. Зупиніть цей процес або запустіть runtime з параметром --port 3001 і оновіть proxy.
Dashboard не завантажується на вашому ноутбуці. Це очікувана поведінка, оскільки runtime прив’язаний до loopback. Використайте SSH tunnel вище або завершіть налаштування reverse proxy. Не виправляйте це за допомогою --host 0.0.0.0, оскільки authentication вимкнена, доки не створено key.
Docker sandboxes завершується з помилкою permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock. Користувач sandbase не входить до групи docker. Виправте це за допомогою sudo usermod -aG docker sandbase і перезапустіть service. Врахуйте, які права ви надали: ця група має права root на host, тому це частково нівелює перевагу використання runtime під окремим користувачем.
Kubernetes sandboxes завершується з помилкою Error from server (Forbidden). ServiceAccount не має дозволів для pod або subresource exec. Перевірте це безпосередньо за допомогою kubectl auth can-i create pods/exec -n <namespace>. Команда повертає yes або no.
Кожен запит повертає 401 після додавання API key. Authentication вмикається, щойно існує перший key, і застосовується як до console, так і до API. Надішліть Authorization: Bearer <key>. Якщо key втрачено, створіть новий, оскільки secret_key повертається лише один раз і не зберігається у формі, придатній для читання.
Інструменти MCP server ніколи не з’являються в session. Перевірте mcp_server_name у блоці tools і порівняйте його з name у mcp_servers. Потім перевірте, чи може runtime звернутися до URL безпосередньо із server, за допомогою curl -i <url>. MCP server типу URL є network dependency, а VPS розпізнає імена та маршрутизує network traffic інакше, ніж ваш ноутбук.
FAQ
Чи можна запускати SandBase Harness без ключа OpenAI або Anthropic?
Так, якщо у вас є endpoint, сумісний з OpenAI. Runtime підтримує провайдерів OpenAI, Anthropic і сумісних з OpenAI, тому локальний сервер, який працює з OpenAI API, підходить. Укажіть провайдера workspace у .managed-agents/config.yaml і задайте для нього api_key та endpoint. Runtime не містить власної моделі, тому якийсь сервіс має обробляти ці виклики.
Чи безпечно відкривати runtime на публічному порту?
Ні, не в установленій конфігурації. Він прив’язаний до 127.0.0.1:3000 і запускається з вимкненою автентифікацією. Зміна адреси прив’язки не вирішує проблему. Створіть API key або задайте MANAGED_AGENTS_API_KEY, щоб увімкнути автентифікацію за bearer token. Потім розмістіть nginx або Caddy перед runtime для TLS і закрийте порт 3000 у firewall, щоб єдиним шляхом доступу був proxy.
У чому різниця між sandbox local, Docker і Kubernetes?
local запускає код інструментів як дочірній процес runtime на host, з правами користувача runtime і без ізоляції. docker створює для кожної сесії окремий container із власною файловою системою, обмеженням пам’яті та часткою CPU і видаляє його після завершення сесії. kubernetes запускає сесію як pod і керує ним через kubectl exec. Для цього в образі runtime потрібен kubectl, а в цільовому namespace — RBAC для pod і субресурсу exec.
Що саме потрібно резервувати?
Каталог .managed-agents/ у workspace. У ньому містяться config.yaml, база даних SQLite data.db з агентами, сесіями, записами сховища облікових даних і записами пам’яті, а також завантажені файли, пакети skill і знімки сесій. Перед копіюванням зупиніть service, щоб SQLite не записувала дані під час створення архіву. API keys провайдерів, на які посилаються як на ${OPENAI_API_KEY}, не входять до backup, тому зберігайте їх окремо.
Чому слід клонувати tag v0.3.2, а не main?
Tag містить фіксоване дерево, тому ключі конфігурації та CLI-команди, описані в документації, відповідатимуть отриманій версії. main змінюється, а ключ конфігурації можуть перейменувати між моментом написання інструкції та її виконанням. Проєкт також попереджає, що пакет managed-agents без scope у npm не є цим проєктом, тому npx managed-agents встановлює сторонній пакет. Release v0.3.1 переважно потрібен для заміни швидкого запуску через npm на шлях зі встановленням із зафіксованого джерела за tag.