Want Hermes to use a specific cloud sandbox as its terminal? Now you write a plugin — no waiting for core changes


You’ve always wanted Hermes to run terminal commands in a particular cloud sandbox — safer code, cleaner environments, and no more local dependency conflicts. Then you open the docs and see only the built-in backends listed. The one you want means waiting for the maintainers to merge vendor code into the core repo, or maintaining your own fork forever. PR #94400, merged on August 25, ends that wait: terminal backends are now a pluggable subsystem. A third-party cloud sandbox can register itself as a terminal.backend value through a standalone plugin — no core changes, no fork.

Why this was the “last big one”

Across Hermes’ tool ecosystem, image/video generation, web scraping, browser, memory, TTS/STT, and model providers all supported plugin-style integration long ago — except terminal backends. Terminals touch too many sensitive spots: approvals, container paths, caches, secret stripping — the largest blast radius of any subsystem. PR #94400 closes that gap: sandbox vendors can now write a plugin and let users set terminal.backend: <your-backend-name> directly.

There’s a real first consumer behind this: the Sprites cloud-sandbox backend (originally #93523) was the first to migrate onto this interface — it moved to a standalone private plugin repo instead of living in core.

What a plugin looks like: an ABC + a registration function

The mechanism centers on two new files: agent/terminal_env_provider.py defines the TerminalEnvironmentProvider abstract base class, and agent/terminal_env_registry.py is a thread-safe registry. What a plugin author does (the minimal example from the official developer guide, developer-guide/terminal-environment-plugin.md):

# ~/.hermes/plugins/acmebox/__init__.py
from agent.terminal_env_provider import TerminalEnvironmentProvider

class AcmeBoxEnvironment:
    """Must satisfy the BaseEnvironment duck-typed contract."""
    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):
        ...  # run the command in the sandbox
        return {"output": "...", "exit_code": 0}

    def cleanup(self):
        ...  # tear down / detach

class AcmeBoxProvider(TerminalEnvironmentProvider):
    name = "acmebox"
    display_name = "AcmeBox"
    is_remote = True       # commands don't run on the host
    is_container = True    # container-style path/cwd semantics

    @property
    def cache_path_base(self):
        return "~/.hermes"  # where synced cache files land, or None

    @property
    def strip_env_keys(self):
        return frozenset({"ACMEBOX_TOKEN"})  # secrets stripped from subprocesses

    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())

The plugin directory also carries a plugin.yaml (name, version, kind: backend). Then enable and select it:

hermes plugins enable acmebox
hermes config set terminal.backend acmebox

Built-in backend names (local, docker, singularity, modal, daytona, vercel_sandbox, ssh) are reserved — plugins extend the set, never shadow a built-in.

Six classification flags that kill the “new backend missed a site” bug class

In the past, adding a backend meant syncing judgment logic across seven or eight places in the code — which backends are remote, which are containers, which skip approvals, how cache paths translate — and missing one was a hard-to-find bug (issue #30112 needed a seven-site sweep). The new design declares all of it:

  • is_remote: commands run somewhere other than the host. Suppresses host OS/home/cwd hints, the host Python env probe, and remote-aware skill handling;
  • is_container: behaves like a container/sandbox with its own filesystem — container resource config passes through, host-looking cwds are sanitized, file tools use container path resolution;
  • skip_container_guards: the sandbox is isolated enough that dangerous-command approval prompts are skipped (defaults to is_container; backends that can mount host paths should override to False);
  • cache_path_base: where auto-synced ~/.hermes/cache files land inside the backend (e.g. ~/.hermes or /root/.hermes), or None when nothing needs translating;
  • strip_env_keys: credential env vars owned by this backend (vendor API tokens), stripped from every subprocess the agent spawns so model-written commands can never read them;
  • session_isolated_when_nonpersistent: non-persistent mode gives each session its own sandbox identity instead of sharing one.

Where the plugin shows up once registered

A registered backend isn’t just a config-file value — every surface picks it up automatically:

  • The hermes setup backend picker shows the new option with vendor-guided configuration;
  • hermes status / hermes doctor list the plugin backend and its health;
  • The dashboard’s terminal-backend picker supports plugin backends and recomputes per request — a plugin installed mid-session appears immediately.

The official developer guide walks through writing a backend plugin from scratch.

What this means for ordinary users

If you only use the built-in backends (local terminal, Docker, Modal, SSH), this change is behaviorally invisible — it’s an architectural door, not a behavior switch. The ecosystem impact is what matters: when a sandbox vendor says “supports Hermes”, it now means “install the plugin”, not “wait for a core merge”; plugin quality is the vendor’s responsibility, and hermes doctor tells you whether it’s healthy. For plugin basics, see the hermes plugins command reference; for the entry-point mechanism behind pip-installed providers, our pip model-provider plugin guide covers the lineage. Backend selection day-to-day is documented in the install guide.

Summary

Pluggable terminal backends turn “get Hermes to use my cloud sandbox” from “beg for a core merge” into “write a plugin, declare six flags, register, done”. It’s the last piece of Hermes’ tool ecosystem to go plugin-native — third-party backends are now decoupled from core, with security policy enforced uniformly through declarative flags.