想让 Hermes 用上某个云沙箱当终端?现在写个插件就能接入,不用等官方改代码

你一直想让 Hermes 用某个云沙箱服务跑终端命令——代码更安全、环境更干净,还能顺带解决本地依赖冲突。结果打开文档一看:官方只支持内置的那几个后端,你想要的那个得等官方把代码合并进核心仓库,或者自己 fork 一份长期维护。8 月 25 日合并的 PR #94400 终结了这种等待:终端后端正式变成可插拔子系统,第三方云沙箱可以以独立插件的形式注册成 terminal.backend 的一个取值——不需要改核心代码,不需要 fork。
为什么这是“最后一个大头”
Hermes 的工具生态里,图片/视频生成、网页抓取、浏览器、记忆、TTS/STT、模型供应商这些子系统早就支持插件式接入了;唯独终端后端一直要求厂商把代码合进主仓库——因为终端涉及审批、容器路径、缓存、密钥剥离这些敏感环节,改动面最大。PR #94400 补上了这最后一块:以后第三方沙箱厂商按文档写一个插件,就能让用户 terminal.backend: <你的后端名> 直接使用。
顺带一提,这背后有个真实案例:Sprites 云端沙箱后端(原 #93523)就是第一个按这套接口迁移的消费者——它搬到了独立的私有插件仓库里,不再住在核心代码中。
插件长什么样:一个抽象类 + 注册函数
这套机制的核心是两个新文件:agent/terminal_env_provider.py 定义了 TerminalEnvironmentProvider 抽象基类,agent/terminal_env_registry.py 是线程安全的注册表。插件作者要做的事(官方文档 developer-guide/terminal-environment-plugin.md 里的最小示例):
# ~/.hermes/plugins/acmebox/__init__.py
from agent.terminal_env_provider import TerminalEnvironmentProvider
class AcmeBoxEnvironment:
"""满足 BaseEnvironment 的鸭子类型接口(execute/cleanup 等)"""
def __init__(self, cwd, timeout, task_id):
self.cwd, self.timeout, self.task_id = cwd, timeout, task_id
def execute(self, command, timeout=None, **kwargs):
... # 在沙箱里执行命令
return {"output": "...", "exit_code": 0}
def cleanup(self):
... # 拆除/分离
class AcmeBoxProvider(TerminalEnvironmentProvider):
name = "acmebox"
display_name = "AcmeBox"
is_remote = True # 命令不跑在宿主机上
is_container = True # 容器式路径/cwd 语义
@property
def cache_path_base(self):
return "~/.hermes" # 同步缓存文件落在哪,不需要翻译就返回 None
@property
def strip_env_keys(self):
return frozenset({"ACMEBOX_TOKEN"}) # 子进程剥离的密钥环境变量
def create_environment(self, *, cwd, timeout, task_id="default",
image=None, container_config=None, **kwargs):
return AcmeBoxEnvironment(cwd, timeout, task_id)
def register(ctx):
ctx.register_terminal_environment_provider(AcmeBoxProvider())
插件目录里再放一个 plugin.yaml(声明名字、版本、kind: backend),然后启用并选择它:
hermes plugins enable acmebox
hermes config set terminal.backend acmebox
内置后端名(local、docker、singularity、modal、daytona、vercel_sandbox、ssh)是保留字,插件只能扩展、不能覆盖。
六个分类标志,把“新后端漏改某处”的坑堵死
以前加一个后端,要在代码里七八个地方同步修改判断逻辑——哪个后端是远程的、哪个是容器、哪个要跳过审批、缓存路径怎么翻译——漏改一处就是难查的 bug(历史上 #30112 就为此扫过七个地方)。新设计把这些决策全部声明化,插件作者只需声明六个标志:
is_remote:命令跑在宿主机之外。会抑制系统提示里的宿主环境提示、宿主 Python 探测等;is_container:行为类似容器/沙箱,有自己的文件系统。容器资源配置、cwd 清洗、文件工具路径解析都会按容器处理;skip_container_guards:沙箱隔离足够强,危险命令的审批提示可以跳过(默认跟随is_container;能挂载宿主路径的后端要覆写为 False);cache_path_base:自动同步的~/.hermes/cache文件在后端内部落在哪里(如~/.hermes或/root/.hermes),不需要翻译就填 None;strip_env_keys:该后端拥有的凭据环境变量名(厂商 API token),agent 派生的每个子进程都会剥离这些变量,防止模型写的命令读到它们;session_isolated_when_nonpersistent:非持久化模式下每个会话用独立的沙箱身份(避免两个临时运行互相销毁对方的沙箱)。
接入后能在哪里看到
插件注册后不是只在配置文件里生效,各个入口都会自动识别:
hermes setup的后端选择器会出现新选项,带厂商的配置引导;hermes status/hermes doctor会列出插件后端及其状态;- Web 控制台的终端后端选择器支持插件后端,且每次请求都会重新计算——会话中途装的新插件立即可见。
官方还专门写了插件开发文档(developer-guide/terminal-environment-plugin.md),从零到一教你写一个后端插件。
这对普通用户意味着什么
如果你只是用内置后端(本地终端、Docker、Modal、SSH),这次改动对你没有行为变化——只是架构上多了条路。但生态层面的意义不小:以后某个沙箱厂商说“支持 Hermes”,不再是“等官方合代码”,而是“去装个插件”;插件质量由厂商自己负责,你可以用 hermes doctor 检查它是否健康。想了解插件体系的基础玩法,可以看 hermes plugins 命令参考;想知道插件入口点机制(pip 包注册 Provider)的来龙去脉,我们之前写过一篇 pip 模型供应商插件指南。终端后端相关的日常操作,安装指南 里有后端选择的完整说明。
小结
终端后端可插拔化,意味着“想让 Hermes 用某个云沙箱”从“求官方合并代码”变成“写一个插件、声明六个标志、注册即用”。这是 Hermes 工具生态最后一块拼图的落位——第三方后端从此与核心解耦,安全策略靠声明式标志统一约束。