如何在 VPS 自行託管 SandBase Harness v0.3.2
在自己的 VPS 執行 SandBase Harness v0.3.2,涵蓋固定標籤安裝、agent YAML、MCP servers、sandbox 模式,以及將 Anthropic SDK 指向自有主機。
自行託管 SandBase agent runtime 的內容
自行託管 SandBase agent runtime,表示在自有伺服器上執行 SandBase Harness,讓工作階段、憑證、記憶與稽核記錄儲存在自己的磁碟,而不是他人的系統中。它是一項 Node 服務。服務監聽 127.0.0.1:3000,提供 /v1 HTTP API 與 Web 主控台,並將狀態儲存在 agent 檔案旁的 SQLite 中。
/v1 API 的設計參考 Claude Managed Agents (CMA),也就是託管式 managed-agent API。這使此 runtime 在兩方面都具備實用性:你可以使用 Anthropic SDK 撰寫程式,將其 baseURL 指向自己的伺服器,之後再將相同程式碼移至託管部署。
SandBase Harness 不會隨附模型,而是呼叫模型。截至 2026 年 8 月,它支援 OpenAI、Anthropic 與 OpenAI-compatible endpoints,涵蓋自託管 gateway,以及 DeepSeek V4 等供應商。你仍須準備 API key,或提供一部支援 OpenAI API 的本機伺服器。
開始前的必要條件
- 執行 Ubuntu 24.04 的 VPS,至少具備 2 GB RAM。TypeScript 建置是安裝過程中負載最高的步驟。
- Node.js 22 或更新版本,以及 npm 10 或更新版本。這兩者都是專案明定的最低版本。
git,以及你計畫使用之模型供應商的 API key。- Docker,但只有在你需要為每個工作階段建立容器 sandbox 時才需要。
Ubuntu 24.04 自有的套件庫提供 Node 18.19,低於最低版本要求,因此請改用 NodeSource 提供的 Node。
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 上優先取得。請先將它移除再繼續,因為建置會使用 shell 找到的 node。
安裝 SandBase 的 v0.3.2 標籤
請從標籤安裝,絕不要從會變動的分支安裝。對 main 執行 bare clone 會取得一小時前才加入的內容,而下方的設定鍵可能與該版本不相容。截至 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 是存放 agent 檔案與所有執行階段狀態的獨立目錄。將它放在原始碼 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 會在 workspace 中建立 .managed-agents/ 目錄。start 會啟動位於 http://127.0.0.1:3000/dashboard 的主控台,以及位於 http://127.0.0.1:3000/v1 的 API。目前這兩者都無法從你的筆電連線,這是正確的設定,後文會進一步說明。現在先透過 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 安裝
專案自己的安裝文件已明確說明:npm 上可見的未加 scope 的 managed-agents 套件並不是這個專案。因此,npx managed-agents 和 npm install -g managed-agents 取得的是與所需 runtime 無關的內容。在維護者宣布正式的 scoped package 前,請從 GitHub 上標記的來源版本安裝。這不是專案歷史中的小註記:v0.3.1 的主要目的,就是以固定的 tagged-source 路徑取代舊的 npm quick start。
將工作區指定給模型提供者
init 會寫入 .managed-agents/config.yaml。整個工作區會設定一個提供者,個別 agent 再選擇具體的模型 ID。
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 會先以 root 身分讀取 EnvironmentFile=,再降權執行。
sudo install -d -m 750 /etc/sandbase
sudo touch /etc/sandbase/runtime.env
sudo chmod 600 /etc/sandbase/runtime.env使用編輯器開啟該檔案,並加入一行 OPENAI_API_KEY=sk-...。提供者金鑰應放在這裡。agent 在工作階段中使用的秘密,則應改放在 runtime 的認證保存庫中。這是不同的問題,影響範圍也不同。將秘密排除在 AI agent 之外的做法,請參閱 避免將秘密交給 AI agent。在任一處貼入 production token 前,值得先閱讀這篇內容。
代理程式 YAML:mcp_servers、tools 與權限政策
代理程式會定義為工作區 agents/ 目錄中的 YAML 檔案。這是您實際上會花時間處理的執行環境部分。
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 應列出該代理程式及其 ID。若 list 沒有顯示該代理程式,表示檔案未成功解析,原因會寫入 .managed-agents/logs/runtime.log。
mcp_servers 宣告 MCP(model context protocol)端點。type: url 表示執行環境會透過 HTTP 與其他位置執行的伺服器通訊,因此任何您已經運作中的服務都可以在此使用,包括與執行環境位於同一 VPS 上的 MCP 伺服器。
宣告伺服器不會自動將其工具提供給代理程式。tools 清單會透過 mcp_toolset 項目完成此設定,而該項目的 mcp_server_name 必須符合上方的 name。如果代理程式的行為顯示 MCP 工具不存在,請先逐字元比對這兩個字串,再檢查其他地方。
agent_toolset_20260401 是內建工具集。日期尾碼代表 schema 版本,因此固定使用該版本的代理程式會持續使用建立時所依據的工具定義。default_config 會設定工具集中所有工具的政策,而 configs 下的每個項目則會依工具名稱覆寫個別設定;範例中的工具名稱是 bash。
permission_policy 是執行環境優於直接呼叫模型的關鍵。always_ask 會暫停工作階段,等待人工核准後才執行呼叫。always_allow 會直接允許呼叫。將 bash 設為 always_ask,表示代理程式執行 shell 命令前必須先讓您查看完整命令;這與 在 VPS 上安全執行 Claude Code 時所使用的控制方式相同。
The three sandbox modes, and when each one fits
Tool calls that execute code run inside a sandbox. The backend is chosen per environment, through sandbox_provider in the environment's config object, or under Settings then Sandbox in the console. Environments are created over the API at POST /v1/environments.
local runs the code as a child process of the runtime, on the host, as the runtime's own user. It is the default, and it is reasonable while you are the only user and the agent only reads files you own. It is not isolation. A tool call that deletes files deletes your files, and a tool call that reads /etc/sandbase/runtime.env reads your provider key.
docker starts one container per session.
{
"sandbox_provider": "docker",
"image": "node:22-slim",
"resources": { "memory": "1g", "cpu": 1 }
}The session gets its own filesystem, its own memory ceiling and its own CPU share, and the container is removed with the session. Switch to this the moment an agent runs code you did not write. The cost is that the runtime's user needs access to the Docker socket, and membership in the docker group is equivalent to root on the host. Per-session containers are the same shape as self-hosted agent sandboxes with one container per run, so the reasoning about what an escaped process could reach applies here unchanged.
kubernetes runs the session workload as a pod and drives it with kubectl exec and kubectl cp. The runtime image needs kubectl present, and its ServiceAccount needs RBAC (role-based access control) permission to create, delete, get, list and watch pods in the target namespace, plus the exec subresource. This mode is worth the setup only if you already run a cluster.
為什麼 runtime 綁定在 127.0.0.1?
因為它啟動時會關閉驗證。當至少存在一組 API key 時,runtime 才會啟用 bearer-token 驗證,而全新的 init 不會建立任何 API key。若在這個預設設定下綁定到 0.0.0.0,就會把持有 shell 工具和 provider key、但未經驗證的 agent runtime 暴露在公用網際網路上。
因此,當你需要讓它可連線時,保留 bind address 不變,並完成以下兩項設定。
首先,啟用驗證。在服務的環境變數檔案中設定 MANAGED_AGENTS_API_KEY,或使用 POST /v1/api-keys 建立 key。該命令只會回傳一次 secret_key 欄位,之後不會再次顯示。之後,client 必須在每個請求中傳送 Authorization: Bearer <key>。
其次,在前方設定 reverse proxy,並在該處終止 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) 串流;啟用 buffering 時,nginx 會等到 buffer 填滿才傳送回應。因此,agent 執行期間 console 不會顯示內容,最後才一次顯示全部結果。proxy_read_timeout 3600s 也很重要,因為預設值是 60 秒。若串流靜默超過 1 分鐘,proxy 會在一次回合中途關閉連線,錯誤看起來就像 runtime 當機。
在 firewall 上開放 22 和 443。保持 3000 關閉,因為 proxy 會透過 loopback 連線到該埠,主機外部不應有任何連線進入。
將 Anthropic SDK 指向自己的主機
此執行環境提供類似 CMA 的 /v1 介面,因此只需修改一個欄位,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'
});它也接受 Claude Managed Agents 用戶端傳送的 beta 標頭:anthropic-beta: managed-agents-2026-04-01 和 anthropic-beta: agent-memory-2026-07-22。對本機執行環境而言,這些標頭是選用的。加入這些標頭後,針對託管部署撰寫的程式碼即可在此處直接執行。
相容性很高,但並非完全相同。確認介面存在之前,先閱讀 checkout 中的 docs/api-matrix.md,因為專案會在其中記錄自身的缺口,包括用戶端自訂工具;這些工具目前仍需在現有的 event-result protocol 之上進行具名註冊。
純 HTTP 同樣可用,也是最快確認執行環境正常運作的方法:
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}'正常回應會持續傳入事件串流。如果連線中斷,請從上次收到的事件繼續,而不是重播整個回合:
curl -N http://127.0.0.1:3000/v1/sessions/SESSION_ID/events/stream \
-H "Last-Event-ID: EVENT_ID"可恢復的串流讓工作階段即使在筆記型電腦關閉後仍能持續。事件會保存在伺服器上,因此用戶端重播的是日誌,而不是持有唯一的副本。
磁碟上儲存憑證、記憶體與稽核軌跡的位置
執行環境擁有的所有內容都位於工作區的 .managed-agents/ 下。
.managed-agents/
├── config.yaml
├── data.db
├── logs/runtime.log
├── files/
├── skills/
├── snapshots/
└── sandbox/data.db是 SQLite 中繼資料,包含代理程式、工作階段、憑證保存庫項目、記憶體儲存項目與 API 金鑰。files/儲存上傳的檔案內容,skills/儲存上傳的技能套件。snapshots/儲存工作階段工作區快照,sandbox/儲存 local-mode 工作階段的工作目錄。logs/runtime.log是某項功能無聲無息地未執行時,應先檢查的位置。
憑證保存庫是由多個機密組成的集合。每個機密都使用 auth_type 新增,例如 environment_variable,並在建立工作階段時透過 vault_ids 附加至工作階段。記憶體儲存區包含具名項目。您可將這些項目以 memory_store 的形式掛載至工作階段,並為其設定獨立的存取設定與指示。兩者都儲存在 data.db 中。這正是本系統與原始模型呼叫的差異:執行環境會跨工作階段保留記憶,也會記錄發生的事件。
因為所有內容都位於同一個目錄,請將整個目錄一起備份。
sudo systemctl stop sandbase
sudo tar czf /root/sandbase-$(date +%F).tgz -C /opt/sandbase/workspace .managed-agents
sudo systemctl start sandbase先停止服務。執行環境寫入 SQLite 資料庫時複製資料庫,可能會取得無法在還原時開啟的檔案。等到真正需要還原時,才會發現這個問題。如果您希望將代理程式 YAML 儲存在 git 中,並將狀態資料存放於其他位置,部署文件支援在 start 上使用 --data-dir 固定狀態資料的位置。
還原時反向執行相同流程:在新的主機上檢出相同的 tag,將封存檔解開至工作區,然後啟動服務。如果您使用 ${OPENAI_API_KEY} 形式,供應商金鑰不會包含在封存檔中,因此請將金鑰保存於日後仍可取得的位置。
以 systemd 執行
為執行環境建立專用使用者,這樣本機 sandbox 模式中的工具呼叫就無法以你的身分執行。
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專案自己的部署範例會在 PATH 呼叫 managed-agents binary。標記來源安裝不會建立該 binary,因此 ExecStart 會針對建置完成的 entry point 執行 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正常結果應為 status 回傳 active (running),以及 curl 回傳 200。若結果不同,先讀取 journalctl -u sandbase -n 50,再讀取 .managed-agents/logs/runtime.log。enable --now 才是關鍵,因為手動啟動的程序會在下次重新開機後消失。
發生的問題與你會看到的訊息
npm run build 被終止,但 npm 沒有顯示錯誤。 在 1 GB VPS 上,TypeScript 編譯會被 kernel 的 out-of-memory killer 終止。相關訊息會寫入 kernel log,而不是 npm。使用 journalctl -k | grep -i "out of memory" 確認;此命令會列出一行,指出遭終止的 node process。加入 swap,或在較大型的 instance 上建置,再將 dist/ 複製過去。
Error: listen EADDRINUSE: address already in use 127.0.0.1:3000。 其他 process 已占用該 port。sudo ss -lntp | grep 3000 會列出該 process。停止該 process,或使用 --port 3001 啟動 runtime,並更新 proxy。
從你的 laptop 無法載入 dashboard。 這是預期行為,因為 runtime 綁定到 loopback。使用上方的 SSH tunnel,或完成 reverse proxy。不要使用 --host 0.0.0.0 修復,因為在建立 key 前,authentication 會停用。
Docker sandbox 使用 permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock 失敗。 sandbase user 不在 docker group 中。使用 sudo usermod -aG docker sandbase 修正後重新啟動服務,並了解你授予的權限:該 group 在 host 上等同於 root,因此會抵銷部分讓 runtime 使用獨立 user 的安全性理由。
Kubernetes sandbox 使用 Error from server (Forbidden) 失敗。 ServiceAccount 缺少 pod 權限或 exec subresource。直接使用 kubectl auth can-i create pods/exec -n <namespace> 檢查;它會回答 yes 或 no。
加入 API key 後,每個 request 都回傳 401。 建立第一個 key 時會啟用 authentication,且同時套用到 console 與 API。傳送 Authorization: Bearer <key>;如果遺失 key,請重新建立,因為 secret_key 只會回傳一次,且不會以可讀格式儲存。
MCP server 的 tools 永遠不會出現在 session 中。 將 tools 區塊中的 mcp_server_name 與 mcp_servers 中的 name 比對,接著使用 curl -i <url> 檢查 runtime 是否能從 server 本身連到該 URL。URL 類型的 MCP server 是網路相依項目;VPS 解析名稱及路由 network traffic 的方式,與你的 laptop 不同。
FAQ
沒有 OpenAI 或 Anthropic 金鑰時,可以執行 SandBase Harness 嗎?
可以,只要您有 OpenAI 相容的端點。此執行環境支援 OpenAI、Anthropic 與 OpenAI 相容的供應商,因此可使用支援 OpenAI API 的本機伺服器。在 .managed-agents/config.yaml 中設定工作區供應商,並將 api_key 與端點指向該伺服器。此執行環境不內建模型,因此必須有某個服務回應這些呼叫。
將執行環境暴露在公開連接埠上安全嗎?
依預設設定並不安全。它會繫結至 127.0.0.1:3000,且啟動時停用驗證;修正方式不是改用其他繫結位址。請建立 API 金鑰,或設定 MANAGED_AGENTS_API_KEY,以啟用 bearer token 驗證。接著在前方放置 nginx 或 Caddy 處理 TLS,並在防火牆上關閉連接埠 3000,讓外部只能透過代理伺服器進入。
local、Docker 與 Kubernetes sandbox 有何不同?
local 會在主機上將工具程式作為執行環境的子程序執行,使用執行環境使用者的權限,且沒有隔離。docker 會為每個工作階段建立獨立容器,提供專屬檔案系統、記憶體限制與 CPU 配額,並在工作階段結束時移除容器。kubernetes 會將工作階段作為 pod 執行,並透過 kubectl exec 控制;執行環境映像檔中必須包含 kubectl,且目標 namespace 中的 pod 以及 exec 子資源必須具備 RBAC 權限。
我確切需要備份哪些內容?
備份工作區中的 .managed-agents/ 目錄。此目錄包含 config.yaml、儲存 agents、工作階段、憑證保存庫項目與記憶項目的 data.db SQLite 資料庫,以及上傳的檔案、技能套件與工作階段快照。複製前請先停止服務,避免 SQLite 在建立封存檔期間寫入。以 ${OPENAI_API_KEY} 引用的供應商 API 金鑰不在備份中,因此請另外儲存。
為什麼要複製 v0.3.2 tag,而不是 main?
tag 代表固定的樹狀版本,因此您讀到的設定金鑰與 CLI 命令,就是實際取得的內容。main 會持續變動,而設定金鑰可能在撰寫指南與實際執行之間重新命名。專案也警告,npm 上未加 scope 的 managed-agents 套件不是本專案,因此 npx managed-agents 會安裝無關的內容。Release v0.3.1 的主要用途,是以固定 tag 的原始碼路徑取代 npm 快速入門流程。