New request
#2491: Audit and replace WebUI's direct dependence on the hermes-agent source tree (slice 2 of #2453)
We'll provision a sandbox, run an agent against the issue, and open a draft PR. You can pull the branch and iterate from there.
Issue: Define the WebUI ↔ Agent API contract so the WebUI no longer requires direct access to the agent's source tree
Background
#2453 (RustyLopez) raised that the WebUI's multi-container setup requires mounting the agent's source tree (/opt/hermes) into the WebUI container, which creates implementation coupling and removes any meaningful trust boundary between the two containers.
Two short-term remediations have already shipped in v0.51.84 (PR #2470):
- The
hermes-agent-srcvolume is now mounted read-only on the WebUI service. The WebUI never writes to it; the kernel now enforces that. docs/docker.mdgained an explicit "What the multi-container setup isolates (and what it doesn't)" section calibrating expectations: process / network / resource isolation, not filesystem isolation.
@Michaelyklam and @RustyLopez both pointed out (rightly) that the durable fix is to eliminate the source mount entirely by routing whatever the WebUI currently does at the source-tree level through an explicit, stable agent API.
What this issue tracks
The durable second slice: define the specific API surface the WebUI needs from hermes-agent, then thread WebUI code over those endpoints so the source mount can be removed.
Why the WebUI currently needs source access (audit)
Three concrete consumers in the current WebUI codebase:
-
docker_init.bash:381-403— at container startup, runsuv pip install "$_agent_src[all]"against the agent source tree to get the agent's Python dependencies into the WebUI's virtualenv. This is the biggest reason the source mount exists today. -
api/startup.py:_agent_dir()/_trusted_agent_dir()/auto_install_agent_deps()(lines 58-128) — non-Docker startup path that locates the agent checkout viaHERMES_WEBUI_AGENT_DIRor$HERMES_HOME/hermes-agent, validates ownership/permissions, thenpip installs the agent on demand. Same shape as the docker_init path but for native installs. -
api/streaming.py:429and the broader streaming layer — imports modules from the agent package at runtime (gateway client, session DB, cron scheduler internals). This is the trickier one: it's not a startup-only dependency, it's the runtime API surface.
Proposed slice 2 (this issue)
Replace each of the above with an explicit agent-side API call:
| WebUI consumer | Replace with |
|---|---|
uv pip install from source at container startup | WebUI image bundles its own minimum agent client lib (a pip-installable hermes-agent-client), so it never needs the agent's full source tree. The agent project would publish that client lib to PyPI on every release. |
auto_install_agent_deps() (native installs) | Same client lib. HERMES_WEBUI_AGENT_DIR becomes a contract path (only the API endpoint URL + auth token live there), not a source path. |
Direct module imports in api/streaming.py and friends | Enumerate the actual imports, classify each as (a) trivially replaceable by a documented REST/IPC endpoint, (b) needs a new agent endpoint, or (c) genuinely WebUI-internal and can stay in this repo. |
Acceptance criteria
- A complete enumeration of agent-source imports the WebUI does today (one PR's worth of audit work — pure investigation, no code change).
- A documented
hermes-agentAPI surface that covers every (a) and (b) above. - A new
hermes-agent-clientPyPI package (or equivalent) that wraps the above. - WebUI changes to consume the client library exclusively.
- The
hermes-agent-srcnamed volume can be removed fromdocker-compose.two-container.ymlanddocker-compose.three-container.yml. docker_init.bashno longer runsuv pip installagainst a shared volume at startup.api/startup.py'sauto_install_agent_deps()deletes its source-tree dependency.
What this issue does NOT cover (intentionally)
- Single-container setup: the WebUI runs the agent in-process there. That's by design and doesn't have the coupling problem (one process boundary, one trust boundary). No change needed.
- The runtime-hardening half of #2453: already shipped in PR #2470 (read-only mount + docs framing).
- Defining the actual API endpoints: this issue tracks the work in
nesquena/hermes-webui. The agent-side endpoint design lives in theNousResearch/hermes-agentrepo and will need a companion issue there once we have the audit from criterion 1.
Related
- #2453 (parent — runtime-hardening half done, API-contract half tracked here)
- #1416 (named-volume staleness — also addressed by removing the source mount)
- PR #2470 (the runtime-hardening half)
- PR #2490 (Docker runtime smoke gate — closes the test-coverage gap that let the #2470-class regression nearly ship)
Cross-cutting architectural refactor to replace direct filesystem coupling with a stable, container-spanning API contract.
- api/routes/agent.py
- api/routes/sessions.py
- api/services/agent_proxy.py
- static/js/workspace/browser.js
- docker-compose.yml
- docs/docker.md
- api/bootstrap.py