Skip to content

Example running Hermes Agents in a Sandbox with Sidecars#1587

Open
ehdr wants to merge 2 commits into
mainfrom
ehdr/sandbox-sidecars-hermes
Open

Example running Hermes Agents in a Sandbox with Sidecars#1587
ehdr wants to merge 2 commits into
mainfrom
ehdr/sandbox-sidecars-hermes

Conversation

@ehdr

@ehdr ehdr commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Deploy with

uv pip install --prerelease=allow --upgrade modal

MODAL_ENVIRONMENT=<env> modal secret create anthropic-secret \
      ANTHROPIC_API_KEY=sk-ant-...

MODAL_ENVIRONMENT=<env> modal secret create hermes-dashboard-secret \
      HERMES_DASHBOARD_BASIC_AUTH_USERNAME=<username> \
      HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=<password> \
      HERMES_DASHBOARD_BASIC_AUTH_SECRET=$(openssl rand -hex 32)

MODAL_ENVIRONMENT=<env> modal deploy 13_sandboxes/hermes_agent_sidecars.py

Type of Change

  • New example for the GitHub repo
    • New example for the documentation site (Linked from a discoverable page, e.g. via the sidebar in /docs/examples)
  • Example updates (Bug fixes, new features, etc.)
  • Other (Changes to the codebase, but not to examples)

Monitoring Checklist

  • Example is configured for testing in the synthetic monitoring system, or lambda-test: false is provided in the example frontmatter and I have gotten approval from a maintainer
    • Example is tested by executing with modal run, or an alternative cmd is provided in the example frontmatter (e.g. cmd: ["modal", "serve"])
    • Example is tested by running the cmd with no arguments, or the args are provided in the example frontmatter (e.g. args: ["--prompt", "Formula for room temperature superconductor:"]
    • Example does not require third-party dependencies besides fastapi to be installed locally (e.g. does not import requests or torch in the global scope or other code executed locally)

Documentation Site Checklist

Content

  • Example is documented with comments throughout, in a Literate Programming style
  • All media assets for the example that are rendered in the documentation site page are retrieved from modal-cdn.com

Build Stability

  • Example pins all dependencies in container images
    • Example pins container images to a stable tag like v1, not a dynamic tag like latest
    • Example specifies a python_version for the base image, if it is used
    • Example pins all dependencies to at least SemVer minor version, ~=x.y.z or ==x.y, or we expect this example to work across major versions of the dependency and are committed to maintenance across those versions
      • Example dependencies with version < 1 are pinned to patch version, ==0.y.z

Outside Contributors

You're great! Thanks for your contribution.


Open in Devin Review

@devin-ai-integration devin-ai-integration Bot 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.

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

f"mkdir -p /root/.ssh && ssh-keygen -q -t ed25519 -N '' -f {SSH_KEY}",
)
pubkey = sb.filesystem.read_text(f"{SSH_KEY}.pub")
tools.filesystem.write_text(pubkey, "/root/.ssh/authorized_keys")

@devin-ai-integration devin-ai-integration Bot Jun 10, 2026

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.

🚩 filesystem.write_text argument order could not be verified

All three filesystem.write_text calls at lines 271, 301, and 327 use (data, path) argument ordering — e.g. tools.filesystem.write_text(pubkey, "/root/.ssh/authorized_keys"). Many filesystem APIs conventionally use (path, data) ordering instead. I installed modal==1.5.0 to check but filesystem is a runtime property and the signature wasn't directly inspectable from the class. The author likely knows the correct API, and all three calls are internally consistent, but a reviewer with access to the Modal SDK source should confirm the parameter order is correct. If reversed, the agent would silently write garbage files and fail during bootstrap.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration devin-ai-integration Bot 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.

Devin Review found 2 new potential issues.

Open in Devin Review

.apt_install("git", "curl", "openssh-client", "rsync")
# python-multipart: needed by the dashboard's kanban plugin API routes,
# which fail to load without it
.uv_pip_install(f"hermes-agent[web,pty]=={HERMES_VERSION}", "python-multipart")

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.

🟡 Unpinned python-multipart dependency violates repository pinning rules

python-multipart is installed without a version pin on line 97. The repository's internal/CLAUDE.md:59-66 and .github/pull_request_template.md:45-46 both require all container dependencies to be pinned: packages with version < 1.0 must be pinned to patch version ==0.y.z. Other examples in the repo already pin this package, e.g. 06_gpu_and_ml/world-models/text_to_world.py:497 uses python-multipart==0.0.20.

Suggested change
.uv_pip_install(f"hermes-agent[web,pty]=={HERMES_VERSION}", "python-multipart")
.uv_pip_install(f"hermes-agent[web,pty]=={HERMES_VERSION}", "python-multipart==0.0.20")
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +306 to +311
sb.exec(
"sh",
"-c",
f"while true; do sleep 60; {sync} {HERMES_HOME}/ {STATE_MOUNT_POINT}/hermes/; done"
" >>/var/log/state-sync.log 2>&1",
)

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.

🚩 Fire-and-forget sb.exec() calls for background daemons discard process handles

At lines 306-311 and 317-321, sb.exec() is called for the state-sync loop and the dashboard daemon respectively, but the returned ContainerProcess handles are discarded. This is intentional per the comment at lines 313-315 ("the processes belong to the Sandbox, not to our local handles"), but it means there's no way for the bootstrap code to detect if these daemons crash immediately after starting. The dashboard is indirectly validated by the wait_for curl check at line 324-329, but the state-sync loop at line 306-311 has no such validation — a typo in the rsync command or path would silently fail, and state would never be persisted to the Volume.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants