Skip to content

Sandbox Backends ​

源码版本v2026.7.20

Responsibility ​

The agent needs to execute commands and run code, but arbitrary commands can't be allowed to hit the host directly. Hermes's terminal tool supports 6 execution backends, chosen by safety/isolation/cost tradeoffs: Local (direct on host), Docker (container isolation), SSH (remote machine), Singularity (HPC container), Modal (cloud sandbox, direct or managed), Daytona (cloud dev environment). Container hardening + namespace isolation; dangerous commands are also intercepted via approval callbacks.

Design Motivation ​

Why 6 backends instead of 1? The agent's execution scenarios span too wide a range — during dev you just want it fast on the local machine; production batch jobs need container isolation; HPC users must run inside Singularity (Docker isn't an option); unattended cloud runs want a per-second cloud sandbox like Modal. No single backend covers them all. Hermes abstracts "execution" into a uniform interface, plugs backends in by scenario, and shares the dangerous-command approval callback across backends. The Docker backend has to specifically detect whether bind mounts expose host paths — because the safety premise of container isolation is "the container can't see outside," and docker run -v /:/host breaks that. Modal has to distinguish direct (the user's own credentials) from managed (gateway-hosted) — the former bills the user's account, the latter bills the Nous portal quota, and the permission and billing paths are entirely different. These detection logics don't live inside the backends — they're extracted into tool_backend_helpers.py, decoupling backend choice from safety-level judgment.

Key Files ​

Data Flow ​

  1. The agent decides to execute a command → tool_call terminal → tools/terminal_tool.py.
  2. The backend is chosen by job config (local/docker/modal/ssh/singularity/daytona):
  3. Dangerous commands are intercepted by approval callbacks — the main loop uses interactive approval; subagents default to auto-deny (tools/delegate_tool.py:75), and cron/batch can opt into auto-approve.
  4. Command output is backfilled to the main loop; long tasks can be daemonized (tools/daemon_pool.py).

The backend list declaration is simple — a single comment at the top of terminal_tool.py: "A terminal tool that executes commands in local, Docker, Modal, SSH, Singularity, and Daytona environments."

The Docker backend's bind-mount detection checks the spec prefix: anything starting with /, ~, ./, ../, or anything with a colon as the second character (like a Windows drive letter C:\) counts as a host path, triggers has_host_access=True, and routes to a stricter permission branch:

python
def _docker_volume_uses_host_path(volume_spec: str) -> bool:
    """Return True when a docker volume spec bind-mounts a host path."""
    if not isinstance(volume_spec, str):
        return False
    vol = volume_spec.strip()
    return bool(vol) and (
        vol.startswith(("/", "~", "./", "../")) or
        (len(vol) >= 3 and vol[1] == ":" and vol[2] in ("/", "\\"))
    )

Modal resolution has three tiers: direct (the user's own Modal credentials), managed (Nous portal hosted), auto (default; falls back to direct when managed is unavailable). resolve_modal_backend_state is centralized in one place — if it can't pick a backend, it returns None. has_direct_modal_credentials checks both the MODAL_TOKEN_ID / MODAL_TOKEN_SECRET env vars and the ~/.modal.toml file — both missing means direct is unavailable. managed_nous_tools_enabled queries the Nous Portal account and fails closed on error: a query failure doesn't accidentally open up the managed path.

Singularity is a special case for HPC — users can't install Docker, only Apptainer/Singularity. _find_singularity_executable looks for apptainer first, then singularity; if neither is found, it errors out with install instructions. The scratch directory prefers /scratch (the standard mount on HPC clusters) and falls back to get_sandbox_dir() / "singularity". The container runs with --containall --no-home plus capability dropping — mandatory on a shared HPC cluster, otherwise you'd see into other users' home directories.

Boundaries and Failure ​

  • Docker bind-mount false negative: The detection function treats /, ~, ./, ../ prefixes as host paths, but a named volume (like mydata:/data) passes through. When a named volume backs onto a host path (custom driver), _docker_has_host_access can't see it and mis-judges the risk as low.
  • Modal managed failure fallback: In auto mode, managed falls back to direct when unavailable. But if the user only configured managed and has no direct credentials, selected_backend becomes None — backend selection fails and only blows up at runtime; terminal creation may not surface a clear error.
  • Singularity SIF cache bloat: SIF images are often hundreds of MB to several GB; without a rotation policy the scratch directory can fill the disk. HPC nodes usually have a cluster-level cleanup policy on /scratch; when the two don't align, you get "the cluster wiped the cache but the agent still thinks the SIF is there."
  • Remote backend network jitter: SSH / Daytona commands run over the network. When the connection drops, the running subprocess's output is lost. Output backfill assumes a single synchronous return — there's no reconnect-and-resume, so long tasks are riskiest on remote backends.
  • Timeout and OOM: local / docker have script_timeout, but remote-backend timeouts rely on the remote process to kill itself. The local scheduler waits for a response that never comes and stalls, or gets falsely declared dead by the lease TTL in _run_job_script_with_claim_heartbeat.

Summary ​

The sandbox layer is "execution isolation + dangerous-command control": 6 backends cover host through HPC to cloud; Docker detects bind mounts to prevent privilege escalation; Modal distinguishes direct/managed credentials; dangerous commands uniformly go through approval callbacks. Subagents and cron take the non-interactive auto-deny/auto-approve path.

Unofficial community learning site. Content based on the MIT-licensed NousResearch/hermes-agent source. · Privacy · Terms · About