SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-28

如何自托管 SandBase Harness v0.3.2

在自己的 VPS 上运行 SandBase Harness v0.3.2,涵盖固定标签安装、Agent YAML、MCP 服务器、沙箱模式,以及将 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 的端点,也支持自托管网关以及 DeepSeek V4 等提供商。您仍需提供 API key,或提供一个支持 OpenAI API 的本地服务器。

开始前的准备工作

  • 一台运行 Ubuntu 24.04 的 VPS,至少有 2 GB RAM。安装过程中,TypeScript 构建占用的资源最多。
  • Node.js 22 或更高版本,以及 npm 10 或更高版本。这两个版本号是项目明确要求的最低版本。
  • git,以及您计划使用的模型提供商的 API key。
  • Docker;只有需要为每个会话提供容器沙箱时才需要。

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 -v

node -v 应输出 v22 或更高版本,npm -v 应输出 10 或更高版本。如果 node -v 仍输出 v18.19.1,说明发行版软件包仍已安装,并且在 PATH 中优先被找到。继续之前请先将其删除,因为构建使用的是 shell 找到的 node

从 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 installci 会安装已提交的 lockfile 中记录的确切版本,因此您的代码树与维护者测试过的代码树一致。npm install 可能解析到更新的版本,这会导致固定标签悄然失去固定效果。

现在创建工作区。工作区是一个独立目录,用于保存代理文件和所有运行时状态。将其放在源代码检出目录之外后,您可以拉取更新的标签,而不会影响数据。

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 会在工作区中写入一个 .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 上可见的未限定作用域的 managed-agents 软件包并不属于该项目。因此,npx managed-agentsnpm install -g managed-agents 获取的不是您需要的运行时。维护者正式发布带作用域的软件包前,请从 GitHub 上带标签的源代码安装。这个问题并非项目历史中的小注释:v0.3.1 的主要目的就是用固定标签的源代码安装路径替代旧的 npm 快速入门。

将工作区指向模型提供商

init 写入 .managed-agents/config.yaml。整个工作区只配置一个提供商,各个代理随后选择具体的模型 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-...。提供商密钥应放在这里。代理在会话期间使用的密钥应放在运行时的凭据保管库中。这是另一个问题,影响范围也不同。在将生产令牌粘贴到任一位置之前,建议阅读避免将密钥暴露给 AI 代理

Agent YAML:mcp_servers、tools 和权限策略

Agent 以 YAML 文件的形式定义在工作区的 agents/ 目录中。这是您实际会花费时间配置的运行时部分。手动编写一个简单的 agent 循环后,这些键的含义会更清晰,因为每个键都对应一项原本需要您自行编写的配置:系统提示词、工具列表,以及工具调用前执行的检查。

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 的 agent。如果 list 没有显示该 agent,说明文件未被解析,原因会写入 .managed-agents/logs/runtime.log

mcp_servers 声明 MCP(模型上下文协议)端点。type: url 表示运行时通过 HTTP 与其他位置运行的服务器通信,因此您已经运行的任何服务都可以在这里使用,包括与运行时部署在同一 VPS 上的 MCP 服务器。网页搜索通常是人们首先使用的工具。在接入搜索工具前,建议先阅读 将您自己的 SearXNG 实例交给代理,因为返回陌生人编写页面的工具会将不受信任的文本直接放入模型上下文中。更稳妥的首次接入方式是采用相反的结构:针对您已经拥有的数据提供只读端点。openGym 在训练记录器旁提供了这样的端点,因此代理可以回答有关您训练历史的问题,但无法改写其中任何内容。

声明服务器不会自动将其工具交给 agent。tools 列表负责完成此操作,其中的 mcp_toolset 条目必须将 mcp_server_name 与上方的 name 匹配。如果 agent 的行为表明 MCP 工具不存在,请先逐字符比较这两个字符串,再检查其他位置。

agent_toolset_20260401 是内置工具集。末尾的日期后缀表示架构版本,因此固定使用该版本的 agent 会继续使用其编写时所依赖的工具定义。default_config 为工具集中的所有工具设置策略,configs 下的每个条目则按工具名称覆盖单个工具的策略,例如示例中的 bash

permission_policy 体现了运行时相较于直接调用模型的价值。always_ask 会暂停会话,等待人工批准后才执行调用。always_allow 则允许调用继续执行。将 bash 设置为 always_ask 后,agent 无法在您看到确切命令之前运行 shell 命令。这与在 VPS 上安全运行 Claude Code时采用的控制方式相同。如果您还运行 DeepSeek Harness,同样的控制会以附加组件的形式提供,而不是作为 YAML 键;限制预算并控制工具调用的插件是与此区块最接近的等效方案。

三种 sandbox 模式及其适用场景

执行代码的工具调用会在 sandbox 中运行。后端按环境选择,可通过环境的 sandbox_provider 配置环境的 config 对象,也可在控制台中依次选择 Settings 和 Sandbox 进行配置。环境通过 API 在 POST /v1/environments 创建。

local 会在主机上将代码作为运行时的子进程运行,并使用运行时自身的用户身份。它是默认模式。当只有您一位用户,且 agent 只读取您拥有的文件时,可以使用此模式。它不提供隔离。删除文件的工具调用会删除您的文件,读取 /etc/sandbase/runtime.env 的工具调用会读取您的 provider key。

docker 会为每个会话启动一个容器。

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

会话拥有独立的文件系统、内存上限和 CPU 配额,容器会随会话一并删除。只要 agent 运行的代码不是您编写的,就应立即切换到此模式。代价是运行时用户需要访问 Docker socket,而加入 docker 组等同于获得主机上的 root 权限。每个会话使用一个容器的模式与 每次运行使用一个容器的自托管 agent sandbox 相同,因此关于逃逸进程能够访问哪些资源的判断在这里同样适用。

kubernetes 会将会话工作负载作为 pod 运行,并通过 kubectl execkubectl cp 驱动该 pod。运行时镜像中必须存在 kubectl,其 ServiceAccount 还需要在目标 namespace 中具备创建、删除、获取、列出和监视 pod 的 RBAC(基于角色的访问控制)权限,以及对 exec 子资源的权限。只有在您已经运行 Kubernetes 集群时,此模式才值得进行配置。

为什么运行时绑定到 127.0.0.1?

因为它启动时未启用身份验证。当至少存在一个 API key 时,运行时才会启用 bearer-token 身份验证,而全新的 init 不会创建任何 key。若在这种默认配置下绑定到 0.0.0.0,就会把一个未经过身份验证的代理运行时暴露在公网中,而该运行时还持有 shell 工具和您的 provider key。

因此,如果要让它可访问,请保留绑定地址不变,并完成以下两项配置。

第一,启用身份验证。在服务环境文件中设置 MANAGED_AGENTS_API_KEY,或使用 POST /v1/api-keys 创建 key。该命令只返回一次 secret_key 字段,之后不会再次显示。客户端随后必须在每个请求中发送 Authorization: Bearer <key>。一个 key 对应一个共享身份。如果您实际需要为每位队友提供独立的隔离代理,并将 provider key 集中保存在一个网关中,请参阅OneCLI 适用于这种架构

第二,在前面配置反向代理,并在反向代理处终止 TLS(传输层安全)。运行时按设计提供明文 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 很重要,因为会话通过服务器发送事件(SSE)进行流式传输。启用缓冲后,nginx 会一直保留响应,直到缓冲区填满。因此,代理运行期间控制台不会显示任何内容,结束时才一次性输出全部内容。proxy_read_timeout 3600s 也很重要,因为默认值为 60 秒。流式传输如果超过 1 分钟没有数据,代理就会在当前回合中途关闭连接,表现为运行时崩溃。

在防火墙上开放 22 和 443。保持 3000 关闭,因为代理通过 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-01anthropic-beta: agent-memory-2026-07-22。对于本地运行时,这些标头不是必需的。保留这些标头后,针对托管部署编写的代码无需修改即可在此运行。

兼容性接近但并不完整。确认某个接口存在前,请先阅读代码库中的 docs/api-matrix.md,因为项目在其中记录了自身的缺失项,包括客户端自定义工具;这些工具仍需在当前事件结果协议之上进行命名注册。

使用普通 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 元数据,包含 agents、sessions、凭据库条目、记忆存储条目和 API keys。
  • files/ 保存上传文件的字节内容,skills/ 保存上传的 skill packages。
  • snapshots/ 保存 session 工作区快照,sandbox/ 保存 local-mode session 的工作目录。
  • logs/runtime.log 是排查某项操作静默无响应时首先应查看的位置。

凭据库由多组机密组成。每组机密都通过一个 auth_type 添加,例如 environment_variable,并在创建 session 时通过 vault_ids 关联到该 session。记忆存储包含命名条目。您可以将这些条目作为 memory_store 挂载到 session,并为其设置独立的访问权限和指令。两者都存储在 data.db 中。这正是它与原始模型调用的区别:运行时会跨 session 保留记忆,并记录发生的情况。

由于所有内容都位于同一个目录中,因此应将整个目录作为一个整体备份。

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

先停止服务。运行时写入 SQLite 数据库时复制数据库,可能会得到一个恢复时无法打开的文件;等到真正需要恢复时才会发现这一点。如果您希望将 agent YAML 保存在 git 中,并将状态数据存放在其他位置,部署文档支持通过 --data-dirstart 上固定状态位置。

恢复时执行相反的操作:在新服务器上检出相同的 tag,将归档解压到工作区,然后启动服务。如果您使用了 ${OPENAI_API_KEY} 形式,归档中不会包含 provider 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

项目自带的部署示例会在 PATH 上调用 managed-agents 二进制文件。通过带标签的源代码安装不会创建该文件,因此 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

正常结果应为 status 返回 active (running),以及 curl 返回 200。如果结果不同,先阅读 journalctl -u sandbase -n 50,再阅读 .managed-agents/logs/runtime.logenable --now 才是关键部分,因为手动启动的进程会在下一次重启后消失。

会发生什么问题,以及你将看到的消息

npm run build 被终止,npm 未报告错误。 在 1 GB VPS 上,TypeScript 编译会被内核的内存不足终止器停止。该终止器会将信息写入内核日志,而不是报告给 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 会显示占用端口的进程。停止该进程,或使用 --port 3001 启动运行时,然后更新代理配置。

从笔记本电脑无法加载控制面板。 这是预期行为,因为运行时绑定到 loopback。使用上文的 SSH 隧道,或完成反向代理配置。不要使用 --host 0.0.0.0 修复此问题,因为在创建密钥前不会启用身份验证。

Docker 沙箱因 permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock 失败。 sandbase 用户不属于 docker 组。使用 sudo usermod -aG docker sandbase 修复,然后重启服务。同时应明确你授予了什么权限:该组在主机上拥有 root 权限,因此运行时使用独立用户的部分隔离作用会被抵消。

Kubernetes 沙箱因 Error from server (Forbidden) 失败。 ServiceAccount 缺少 pod 权限或 exec 子资源权限。直接使用 kubectl auth can-i create pods/exec -n <namespace> 检查。该命令会返回 yesno

添加 API key 后,每个请求都返回 401。 创建第一个 key 后会启用身份验证,并且身份验证同时适用于控制台和 API。发送 Authorization: Bearer <key>。如果丢失了该 key,请重新创建一个,因为 secret_key 只返回一次,并且不会以可读形式存储。

MCP 服务器的工具始终不会出现在会话中。tools 块中的 mcp_server_namemcp_servers 中的 name 对比,然后使用 curl -i <url> 检查运行时是否可以从服务器本身访问该 URL。URL 类型的 MCP 服务器依赖网络。VPS 的名称解析和流量路由方式可能与笔记本电脑不同。

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 端口,使流量只能通过代理进入。

本地、Docker 和 Kubernetes 沙箱有什么区别?

local 在主机上将工具代码作为运行时的子进程执行,使用运行时用户的权限,且没有隔离。docker 为每个会话创建独立容器,并为其提供独立文件系统、内存限制和 CPU 份额;会话结束后,容器会被删除。kubernetes 将会话作为 pod 运行,并通过 kubectl exec 驱动;运行时镜像中必须包含 kubectl,目标命名空间中的 pod 以及 exec 子资源也必须配置 RBAC 权限。

我具体需要备份哪些内容?

备份工作区中的 .managed-agents/ 目录。该目录包含 config.yaml,即保存代理、会话、凭据库条目和记忆条目的 data.db SQLite 数据库;此外还包含上传的文件、技能包和会话快照。复制该目录前请停止服务,避免 SQLite 在归档过程中继续写入。以 ${OPENAI_API_KEY} 引用的提供商 API 密钥不在备份中,因此请单独保存。

为什么要克隆 v0.3.2 标签,而不是 main?

标签对应固定的代码树,因此您看到的配置键和 CLI 命令与实际获取的版本一致。main 会发生变化,指南编写时使用的配置键可能在您执行时已经重命名。项目还警告,npm 上未限定作用域的 managed-agents 软件包不是本项目,因此 npx managed-agents 会安装无关的软件包。Release v0.3.1 的主要作用,是将 npm 快速入门替换为固定标签源码路径。