Skip to content

feat(tools): add Sprites stateful sandbox backend 👾 - #30112

Open
kylemclaren wants to merge 28 commits into
NousResearch:mainfrom
kylemclaren:add-sprites-terminal-backend
Open

feat(tools): add Sprites stateful sandbox backend 👾#30112
kylemclaren wants to merge 28 commits into
NousResearch:mainfrom
kylemclaren:add-sprites-terminal-backend

Conversation

@kylemclaren

@kylemclaren kylemclaren commented May 21, 2026

Copy link
Copy Markdown

What does this PR do?

Adds a new terminal backend, TERMINAL_ENV=sprites, that runs the agent's shell commands inside a Sprite — a stateful cloud sandbox on Fly.io with native checkpoint & restore.

Sprites fills a gap between the existing cloud-sandbox backends. e.g:

  • vs Modal / Vercel Sandbox — Sprites are persistent by default: a Sprite outlives the Hermes session and is reused (not just its filesystem snapshot) on the next run with the same identity. Live processes, sockets, and PID space survive across cleanup.

Wired through the existing _create_environment factory; no changes to the agent or tool surfaces. A pip install 'hermes-agent[sprites]' plus SPRITES_TOKEN is the only user-facing setup, and hermes setup terminal walks through it.

Sprite identity is profile-scoped: sandboxes are named hermes-{profile}-{task_id} (hermes-{task_id} on the default profile), so independent Hermes profiles never resume into one another's live Sprite, while the same (profile, task_id) always resumes the same one. Within-profile reuse across sessions is the backend's designed behavior — the same naming scheme and task-id resolution the Daytona backend uses on main.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • Backend: tools/environments/sprites.pySpritesEnvironment on sprites-py. Spawn-per-call, stdin via heredoc, persistent by default. _resolve_sprite_name derives the deterministic, profile-scoped, Fly/DNS-safe Sprite name.
  • Wiring: terminal_tool.py (dispatch + _CONTAINER_BACKENDS cwd sanitization + container_config builder, so container_persistent: false actually reaches the backend), environments/__init__.py, file_operations.py.
  • Backend classifications: registered sprites in every shared remote-backend set on current main — agent/prompt_builder.py (_REMOTE_TERMINAL_BACKENDS + fallback description, so host info is suppressed and the live in-backend probe is used), tools/approval.py (_should_skip_container_guards), file_tools.py, code_execution_tool.py, skills_tool.py.
  • CLI: setup.py (menu + token prompt), status.py (SDK/token state), doctor.py (diagnostics), config.py (token status), AGENTS.md (structure tree).
  • Dependency: sprites-py>=0.5.0,<0.6 in lazy_deps.py + pyproject.toml (upper-bounded per the pre-1.0 rule; full live integration suite verified against 0.5.0).
  • Tests: test_sprites_environment.py (27 unit, mocked SDK — construction, naming/profile isolation, dispatch wiring, persistence, file sync), test_sprites_terminal.py (8 integration vs live API, gated by SPRITES_TOKEN).
  • Docs: README.md (backend count → eight), configuration.md (per-backend section + restricted-token notes), environment-variables.md (SPRITES_TOKEN row), tools.md, security.md, cli-config.yaml.example (Sprites stanza).

How to Test

  1. pip install -e . && pip install 'hermes-agent[sprites]'
  2. Get a token: sprite login (or sprite auth setup --token …). Drop it into ~/.hermes/.env as SPRITES_TOKEN=….
  3. Configure: ~/.hermes/config.yamlterminal.backend: sprites, terminal.cwd: /home/sprite, terminal.container_persistent: true.
  4. Smoke test the backend directly:
    from tools.environments.sprites import SpritesEnvironment
    env = SpritesEnvironment(cwd="/home/sprite", timeout=60, task_id="repro", persistent_filesystem=True)
    print(env.execute("sprite-env info"))   # → sprite_name=hermes-repro, distinct boot_id
    env.cleanup()
  5. End-to-end via the agent: hermes --yolo -z "Run sprite-env info and quote the output". Confirm the sprite_name / sprite_id / boot_id returned differ from the host's.
  6. Resume: re-run step 5, confirm client.list_sprites() shows the same Sprite (status warm) and the filesystem state from the prior run is intact.
  7. Ephemeral: rerun with container_persistent: false, confirm client.list_sprites() no longer contains the Sprite after cleanup().
  8. Profile isolation: run step 5 under a named profile (HERMES_HOME=~/.hermes/profiles/work), confirm a separate hermes-work-default Sprite is created and the default profile's Sprite is untouched.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (feat(terminal):, docs(terminal):)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run the touched test suites (test_sprites_environment.py, test_container_cwd_sanitize.py, test_prompt_builder.py, test_approval.py, test_file_tools.py — 500+ tests) and all pass. The full pytest tests/ -q currently has 11 collection errors on unmodified upstream modules (acp/mcp) that reproduce on main.
  • I've added tests for my changes
  • I've tested on my platform: Ubuntu 25.10 (Sprite VM)

Documentation & Housekeeping

  • I've updated relevant documentation (user-guide/configuration.md, reference/environment-variables.md, module docstrings)
  • I've updated cli-config.yaml.example (OPTION 7: Sprites cloud execution)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A (no architecture changes; followed the existing BaseEnvironment contract)
  • I've considered cross-platform impact — backend uses httpx + websockets (both cross-platform) and bash -c on the remote Sprite (Linux); same host requirements as the existing Modal/Daytona backends. No new Windows/macOS constraints.
  • I've updated tool descriptions/schemas — N/A (no tool schema changes; the change is purely the execution-environment layer)

Screenshots / Logs

Full agent loop:

$ hermes --yolo -z "Run 'sprite-env info' and quote its raw output verbatim. Then also run 'hostname' and 'cat /proc/sys/kernel/random/boot_id'."

Here's the raw output from sprite-env info:

{"org":"","sprite_id":"sprite-dd79f1ee-...","sprite_name":"hermes-default","sprite_url":"https://hermes-default-xyz.sprites.app","version":"0.0.1-rc43"}

hostname: hermes-default
boot_id: 64d0ad04-...

Host VM (for comparison) reports sprite_name=hermes, sprite_id=sprite-8366d184-…, boot_id=16a68ded-… — all distinct, confirming the agent's command ran inside the Sprite the backend created (not on the host).

Notes

  • No sync-back on cleanup. SSH/Modal/Daytona copy agent-modified files back to ~/.hermes/cache/remote-syncs/… because their sandboxes are torn down or reset between sessions. Sprites is persistent by default — the ext4 filesystem is the authoritative store and the same Sprite is resumed by (profile, task_id), so a sync-back would be redundant work.
  • No region / CPU / memory / disk / base-URL config. Sprites allocates compute dynamically (up to 8 CPU / 16 GB RAM per Sprite) and pins the API endpoint to api.sprites.dev; the dashboard does not expose user-selectable knobs for those today. The setup wizard surfaces this as a note instead of prompting for ignored values.
  • Cmd.kill() is a no-op. The sprites-py SDK has no public cancel hook on a running WebSocket exec; timeouts use the SDK's WebSocket deadline.

Changelog since the automated review

  • July: merged onto then-current main; profile-scoped Sprite identity (_resolve_sprite_name) + TestSpriteNaming covering resume, cross-profile isolation, slug sanitization, and resolver-failure fallback; registered sprites in the then-current backend classification paths (prompt host-suppression/probe, cwd sanitization, container_config builder, approval guard helper) with wiring tests pinning each.
  • August: re-merged onto current main (~8.4k commits, spanning the Vercel Sandbox revert/re-add — resolutions keep upstream's Vercel surface and add Sprites alongside); swept the classification sites added since July (tools/env_probe.py _REMOTE_BACKENDS — the file cited in the review — plus file_tools fallback set/backend sniff, terminal_tool env-parse gate, prompt_builder probe config, credential_files cache-path translation); modernized the sprites-py pin to the stable 0.5.x series; re-ran the full live integration suite (8/8 green against api.sprites.dev on sprites-py 0.5.0).

@kylemclaren kylemclaren changed the title Add sprites terminal backend feat(terminal): add Sprites sandbox backend May 21, 2026
@alt-glitch alt-glitch added type/feature New feature or request tool/terminal Terminal execution and process management comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles P3 Low — cosmetic, nice to have labels May 21, 2026
@kylemclaren
kylemclaren force-pushed the add-sprites-terminal-backend branch from 015e4fe to f8d2b98 Compare May 22, 2026 08:06
@kylemclaren kylemclaren changed the title feat(terminal): add Sprites sandbox backend feat(tools): add Sprites sandbox backend May 22, 2026
@kylemclaren kylemclaren changed the title feat(tools): add Sprites sandbox backend feat(tools): add Sprites terminal backend May 22, 2026
@kylemclaren kylemclaren changed the title feat(tools): add Sprites terminal backend feat(tools): add Sprites terminal backend 👾 May 22, 2026
@kylemclaren
kylemclaren marked this pull request as ready for review May 22, 2026 09:15
@kylemclaren
kylemclaren requested a review from a team May 22, 2026 09:15
@kylemclaren kylemclaren changed the title feat(tools): add Sprites terminal backend 👾 feat(tools): add Sprites stateful sandbox backend 👾 May 22, 2026
kylemclaren and others added 16 commits May 22, 2026 12:17
Adds a new TERMINAL_ENV=sprites option backed by the sprites-py SDK
(Fly.io). Persistent by default; sprites are keyed by hermes-{task_id}
so sessions resume cleanly across restarts. Verified end-to-end against
api.sprites.dev (exec, cwd tracking, env persistence, stdin heredoc,
exit codes, file sync, ephemeral vs persistent cleanup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprites does not yet expose region or per-sandbox compute sizing
(CPU/memory/disk) to API consumers, so the SpriteConfig and the setup
flow are simplified to match: sprite creation no longer passes a
SpriteConfig at all, the setup wizard no longer prompts for region or
container resources, and the docs YAML example drops the
container_cpu/memory/disk knobs with a note that they are ignored on
this backend.

Adds the per-backend section (mirrors Daytona/Vercel pattern), the
SPRITES_TOKEN and SPRITES_BASE_URL env-var rows, and the troubleshooting
bullet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per sprites.dev, a Sprite is a hardware-isolated, stateful Firecracker VM
on Fly.io with checkpoint & restore — not just a generic "cloud sandbox."
This normalizes the wording across the user guide, env-var reference,
setup wizard, and module docstrings:

- "Sprite" (singular, capitalized) for an instance; "Sprites" for the
  service/product
- Backend description leans on Firecracker / Fly.io / stateful framing
  instead of the generic "cloud sandbox / cloud VM" labels
- Compute-sizing note is reworded to match the platform's dynamic
  allocation model (up to 8 CPU / 16 GB RAM) rather than implying static
  defaults

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ud sandbox"

Per maintainer preference, the public-facing description shouldn't lean on
the underlying hypervisor name. Keeps the "stateful sandbox / checkpoint &
restore" framing aligned with sprites.dev but reverts the implementation
detail to the generic "cloud sandbox" wording used by Modal/Daytona/Vercel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the Daytona section structure: requirements, what it's good for,
the dynamic-compute-allocation caveat, and a minimal YAML stanza.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Sprites API endpoint is static (api.sprites.dev) — there is no
self-hosted deployment story to support, so exposing a base-URL override
in setup, .env, and docs was just noise. SpritesClient is now constructed
with no base_url kwarg (lets the SDK use its own default). Setup keeps a
one-line cleanup that removes any previously-saved SPRITES_BASE_URL from
existing users' .env on next `hermes setup terminal` run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Sprites backend deliberately doesn't copy agent-modified files back
to ~/.hermes/cache/remote-syncs/... on cleanup the way SSH/Modal/Daytona
do. Those backends need it because their sandboxes are torn down or
reset between sessions; Sprites' ext4 filesystem is persistent and the
same Sprite (by task_id) is resumed on the next session with all state
intact, so a sync_back would just duplicate the canonical store.

- sprites.py cleanup() drops the no-op sync_manager.sync_back() call and
  replaces it with a comment explaining the design choice.
- configuration.md splits "Credential files" into push (still applies)
  and a new "No sync-back, by design" note; the Remote-to-Host File Sync
  section calls out the Sprites carve-out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprites tokens default to full-account access, but the dashboard
(Account → Tokens → ⚙ → Restricted Token Options) can mint tokens
scoped to a name prefix and a max-sprites cap. Pair this with our
deterministic hermes-{task_id} naming by creating a hermes-prefixed
token — the token can manage everything Hermes spawns and nothing else.

- configuration.md: new "Restricted tokens" subsection under Sprites
  authentication, explaining the two restriction knobs and why the
  hermes prefix is the right default for CI / shared envs.
- setup.py: surface the same tip inline when the wizard prompts for
  the token so first-time users see it before pasting an unrestricted
  one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CONTRIBUTING.md (post Mar/May 2026 supply-chain rules) requires every
new PyPI dependency to declare a `<next_major` ceiling rather than an
exact pin. `0.0.1rc37` falls under the pre-1.0 rule: floor must include
the rc tag so pip opts in to the pre-release; ceiling is
`<0.(current_minor + 2) = <0.2`. Future 0.0.x / 0.1.x patches resolve;
a hostile 0.2.0 doesn't.

pip dry-run confirms the new spec still resolves to 0.0.1rc37 today.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unit tests (tests/tools/test_sprites_environment.py): 18 cases against
a mocked sprites-py SDK — no token, no network. Cover construction
(missing-token error, persistent get-first, create-when-not-found,
no compute kwargs, no base_url kwarg), cwd resolution (default /root
→ detected home, ~ rewrite, explicit cwd preserved), cleanup
(persistent leaves the Sprite alive, ephemeral deletes it, idempotency,
client.close), _run_bash exit-code surfacing (zero, ExitError → 7,
TimeoutError → 124), filesystem push (write_bytes + parent.mkdir,
unlink per path), and the _stdin_mode = heredoc declaration.

Integration tests (tests/integration/test_sprites_terminal.py): 8
cases against the live api.sprites.dev — gated by SPRITES_TOKEN and
@pytest.mark.integration. Module-level skip when the token is absent.
Token is captured at import time and re-injected via an autouse
fixture because the project conftest's hermetic env wipes everything
ending in _TOKEN. Covers basic exec / non-zero exit / OS info / Python
availability, write+read, env var persistence across calls, the
sprite-env info identity check (asserts hermes-default substring and
that the in-Sprite boot_id differs from the host's), and filesystem
persistence across a session recycle.

Verified locally via scripts/run_tests.sh — 24,007/24,033 pass (26
pre-existing failures in unrelated test files: acp, gateway systemd,
browser binary lookup, etc.). 18 unit tests pass under per-file
isolation in ~5 min; integration tests pass against the live API in
~80s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps "Seven" → "Eight" and adds a one-sentence framing for Sprites:
stateful Fly.io sandboxes with native checkpoint & restore that resume
session-to-session (vs. Modal/Daytona, which hibernate-and-wake).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the styling of every other backend name in the same sentence —
none of the others link out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…stence

Sprites' hibernate-when-idle / wake-on-demand cost model is the same
as Daytona's and Modal's, so the single grouped sentence carries it
without needing a dedicated callout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Sprites backend was already wired through the agent runtime
(_create_environment, requirements check) but missing from the
diagnostic CLI surfaces, so users with sprites configured got a bare
"Backend: sprites" with no token/SDK detail and `hermes doctor` had no
proactive check.

- hermes_cli/status.py: new branch reporting sprites-py install status
  and whether SPRITES_TOKEN is set.
- hermes_cli/doctor.py: dedicated block mirroring the Daytona/Vercel
  pattern — checks SPRITES_TOKEN presence, SDK install, and prints the
  persistence semantics ("Sprite stays alive" vs "Sprite is deleted on
  cleanup").
- hermes_cli/config.py: new branch in `hermes config show` reporting
  whether the token is configured.
- AGENTS.md: add sprites (and the previously-missed vercel_sandbox)
  to the project-structure backends listing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Several cross-cutting registrations only listed the prior sandboxed
backends (docker / singularity / modal / daytona / vercel_sandbox);
Sprites is also a remote, hardware-isolated sandbox and needs the same
treatment. Without this, the agent path on a Sprites backend hits false
dangerous-command approval prompts, leaks SPRITES_TOKEN to local-
backend subprocesses, and silently drops container_persistent overrides
from the code_execution_tool / file_tools dispatch paths.

- tools/approval.py: add "sprites" to both sandboxed-backend skip sets
  (the agent's command is running inside the Sprite, not on the host —
  same isolation guarantee as the other cloud backends).
- tools/environments/local.py: add SPRITES_TOKEN / SPRITE_TOKEN to the
  provider env blocklist so they are stripped from local-backend child
  process environments (matches the VERCEL_*, DAYTONA_API_KEY, and
  MODAL_TOKEN_* treatment).
- tools/skills_tool.py: add "sprites" to _REMOTE_ENV_BACKENDS so the
  skills tool routes its remote/local distinction correctly.
- tools/file_tools.py: add "sprites" to the container_config dispatch
  set so container_persistent: false can take effect through the
  file-tool code path.
- tools/code_execution_tool.py: same dispatch fix (I had removed it
  in 015e4fe5b on the grounds that sprites ignores CPU/memory/disk —
  but container_persistent IS honored).
- hermes_cli/web_server.py: add "sprites" to the dashboard's
  terminal.backend select-control options.

Surfaced by comparing this branch against NousResearch#17445
(the Vercel Sandbox backend PR), which had to make every one of these
registrations explicitly. Same audit applies here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the placement vercel_sandbox got in PR NousResearch#17445:

- features/tools.md: row in the backend comparison table, "sprites"
  added to the backend-enum comment, and a dedicated "Sprites (Fly.io)"
  subsection covering install + auth, the hermes-{task_id} resume
  model, the restricted-token recommendation for CI / shared envs,
  the persistence semantics, and the "no sync-back, by design"
  rationale.
- security.md: container-bypass info note and production-tip
  paragraph both mention sprites; comparison table gains a row
  showing dangerous-command checks are skipped (because the Sprite
  is the security boundary).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kylemclaren
kylemclaren force-pushed the add-sprites-terminal-backend branch from c918b78 to fb81807 Compare May 22, 2026 12:17
@kylemclaren

Copy link
Copy Markdown
Author

@teknium1 what's a good way to get this over the line?

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the substantial backend implementation and the mocked/live coverage.

Problems

  • tools/environments/sprites.py:83 names persistent resources from task_id, but the branch resolver maps ordinary top-level calls and delegated children to default (tools/terminal_tool.py:973-989). The new integration test explicitly accepts hermes-default for every supplied task id (tests/integration/test_sprites_terminal.py:103-106). That makes durable state shared across normal sessions rather than session-scoped.
  • This branch predates current backend classification paths. Current main requires remote backends to be represented in tools/terminal_tool.py:1216, tools/file_tools.py:206-212, agent/prompt_builder.py:891-908, tools/env_probe.py:48-53, and tools/approval.py:2242-2253. The PR does not cover that current surface, so a direct salvage would retain host-path/host-prompt behavior in some paths.

Suggested changes

  • Define a stable, profile/session-safe Sprite identity and test both intended resume and isolation between independent sessions.
  • Salvage the backend onto current main's shared backend classifications, including file-tool-first and prompt/probe paths, then add tests for those routes.

Automated hermes-sweeper review.

Comment thread tools/environments/sprites.py Outdated
Comment thread tests/integration/test_sprites_terminal.py
Comment thread tools/terminal_tool.py
@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 13, 2026
Sprite and others added 3 commits July 13, 2026 11:49
# Conflicts:
#	README.md
#	hermes_cli/config.py
#	hermes_cli/doctor.py
#	hermes_cli/setup.py
#	hermes_cli/status.py
#	hermes_cli/web_server.py
#	pyproject.toml
#	tools/approval.py
#	tools/code_execution_tool.py
#	tools/environments/__init__.py
#	tools/environments/local.py
#	tools/file_operations.py
#	tools/file_tools.py
#	tools/lazy_deps.py
#	tools/skills_tool.py
#	tools/terminal_tool.py
#	website/docs/reference/environment-variables.md
#	website/docs/user-guide/configuration.md
#	website/docs/user-guide/features/tools.md
#	website/docs/user-guide/security.md
…rrent backend classifications

Addresses the hermes-sweeper salvage review on NousResearch#30112.

Problem 1 — durable state shared across sessions: the Sprite name was
`hermes-{task_id}`, and the task-id resolver collapses ordinary sessions to
`default`, so every session shared one live `hermes-default` Sprite (its
processes, sockets, and PID space — not just a filesystem snapshot). Scope
the name by the active Hermes profile via `_resolve_sprite_name`
(`hermes-{profile}-{task_id}`; unchanged `hermes-{task_id}` on the default
profile for backward compatibility) so independent profiles never resume into
one another's live Sprite, while the same (profile, task_id) still resumes.
Names are slugified to a Fly/DNS-safe form.

Problem 2 — branch predated current classification paths: register `sprites`
in the shared backend classifications main grew after this branch forked —
`_REMOTE_TERMINAL_BACKENDS` + `_BACKEND_FALLBACK_DESCRIPTIONS` (host-info
suppression / live probe in the system prompt), `_CONTAINER_BACKENDS` (cwd
sanitization), and the container_config builder (so `container_persistent`
reaches the backend and ephemeral mode works).

Tests: add TestSpriteNaming (resume + cross-profile isolation + slugification
+ resolver-failure fallback); the integration identity test now derives the
expected name instead of hard-coding `hermes-default`; extend the container /
prompt set-pinning guards to include sprites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ed contract

- TestDispatchWiring drives the real terminal_tool() body and asserts the
  container_config builder includes sprites (container_config=None would
  silently discard container_persistent: false, making ephemeral mode
  unreachable), plus pins the _create_environment → SpritesEnvironment
  kwarg handoff (persistent_filesystem, task_id, cwd).
- _sprite_name is now read at runtime (cleanup-failure log) and asserted
  in the construction tests instead of being a write-only attribute.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sprite and others added 3 commits August 18, 2026 22:43
…l-backend

# Conflicts:
#	README.md
#	agent/prompt_builder.py
#	hermes_cli/config.py
#	hermes_cli/doctor.py
#	hermes_cli/setup.py
#	hermes_cli/status.py
#	hermes_cli/web_server.py
#	pyproject.toml
#	tests/agent/test_prompt_builder.py
#	tests/tools/test_container_cwd_sanitize.py
#	tools/approval.py
#	tools/code_execution_tool.py
#	tools/environments/__init__.py
#	tools/environments/local.py
#	tools/file_operations.py
#	tools/file_tools.py
#	tools/lazy_deps.py
#	tools/skills_tool.py
#	tools/terminal_tool.py
#	website/docs/reference/environment-variables.md
#	website/docs/user-guide/configuration.md
#	website/docs/user-guide/features/tools.md
#	website/docs/user-guide/security.md
…ed since July

Upstream grew new shared classification sites while this branch aged; sweep
them so sprites keeps remote/container semantics everywhere:

- tools/env_probe.py _REMOTE_BACKENDS (host Python-state probe line must not
  leak into a sprites session's prompt; explicitly kept in sync with
  prompt_builder._REMOTE_TERMINAL_BACKENDS)
- tools/file_tools.py _CONTAINER_PATH_BACKENDS_FALLBACK + class-name sniff in
  _terminal_env_type_for_task
- tools/terminal_tool.py container_backend env-var parse gate
- agent/prompt_builder.py _probe_remote_backend container_config set
- tools/credential_files.py cache-path translation (sprites homes are
  ~/.hermes like ssh/daytona/vercel, not host paths)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rc37-era pin predates the SDK's stable series. Full live integration
suite (8 e2e tests vs api.sprites.dev) verified against 0.5.0; client,
sprite, filesystem, and exception surfaces are all compatible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kylemclaren

Copy link
Copy Markdown
Author

This comment is an answer to the automated hermes-sweeper review. It maps each problem to the commits that correct the problem.

Problem 1: All usual sessions shared one durable Sprite

The old code made the Sprite name from task_id only. The resolver maps usual sessions to default. Thus all usual sessions used one Sprite.

Commit 4c4b387 corrects this. The function _resolve_sprite_name now makes the Sprite name. The name is hermes-{profile}-{task_id}. The default profile keeps the old name hermes-{task_id}. Thus old Sprites keep their names. The function changes each name part into a safe DNS format.

The result is this. Two different profiles cannot attach to one live Sprite. The same profile and the same task always attach to the same Sprite.

Note: Sessions in one profile share one Sprite. This is the design, not an error. The Daytona backend on main has the same name procedure. A Sprite keeps live state between sessions. This is the primary feature of this backend.

Problem 2: The branch was older than the current backend classification paths

Two commits correct this. Commit 4c4b387 added sprites to the classification sets that were on main in July. Commit dd39668 added sprites to the sets that came to main after July. The merge commit dfdae48 put the branch on the current main.

The five cited files now all include sprites:

  • tools/terminal_tool.py_CONTAINER_BACKENDS, the dispatch, the container-config builder, and the env-parse gate
  • tools/file_tools.py — the primary set, the fallback set, and the backend-name check
  • agent/prompt_builder.py_REMOTE_TERMINAL_BACKENDS, the fallback description, and the probe config
  • tools/env_probe.py_REMOTE_BACKENDS (this file was not on main in July)
  • tools/approval.py_should_skip_container_guards

Thus the prompt does not show host data for a Sprite session. The file tools use the container path rules. The approval layer uses the container rules.

Suggested change 1: A stable identity, with tests for resume and for isolation

The tests are in the unit suite. TestSpriteNaming has tests for these conditions: a stable name for resume, isolation between profiles, safe name format, and a fallback when the profile resolver fails.

The live test test_filesystem_survives_session_recycle does a resume against the real API. The teardown of each live test does an ephemeral deletion. We did the full live suite on 2026-08-18 against api.sprites.dev with sprites-py 0.5.0. All 8 tests passed.

Suggested change 2: Salvage on the current classifications, with tests for those routes

This is complete. See Problem 2 above. The test TestDispatchWiring fails if sprites is not in the container-config builder. The set guards in test_container_cwd_sanitize.py and test_prompt_builder.py now include sprites. Thus a regression will cause a test failure.

Other changes

  • The sprites-py pin is now >=0.5.0,<0.6. The old pin was for a pre-release version.
  • The inline review comments have answers with more detail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/terminal Terminal execution and process management type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants