Summary
Hermes WebUI currently gives a polished browser experience, but browser-originated chat runs are executed inside the WebUI server process. That means WebUI is more than an interface layer: it creates in-memory streams, starts background agent threads, instantiates/reuses AIAgent, and owns live cancellation/streaming state.
This RFC proposes shifting WebUI toward the thinnest possible client that still gives full parity features:
Hermes Agent owns execution, sessions, active runs, slash-command semantics, approvals, clarify, tools, models, and durable state. WebUI owns presentation, browser UX, and control/observability surfaces over Hermes.
The goal is not to reduce WebUI capability. The goal is to prevent WebUI from becoming a parallel Hermes runtime.
Current behavior / motivation
Today, the WebUI chat path roughly does this:
- Browser calls
POST /api/chat/start.
- WebUI creates a
stream_id and StreamChannel.
- WebUI stores the stream in process-global
STREAMS.
- WebUI starts a daemon thread running
_run_agent_streaming.
_run_agent_streaming constructs/reuses AIAgent and calls agent.run_conversation(...).
- Agent callbacks push token/tool/reasoning/approval/clarify events back into WebUI-owned in-memory queues.
This has some nice properties: direct low-latency SSE, simple local deployment, and direct access to the live agent object. But it also creates real costs:
- WebUI restarts/updates can kill active browser-originated runs.
- Browser refresh/reconnect semantics depend on WebUI process memory.
- WebUI has to maintain runtime shims for Hermes behavior.
- Features such as slash commands,
/goal, model/provider routing, approvals, clarify, queue/interrupt/steer, etc. risk being reimplemented in WebUI instead of inherited from Hermes.
- Active WebUI streams can block safe WebUI updates.
- WebUI cannot be treated as a disposable observability/control layer.
Design principle
For every feature, ask:
Is this Hermes behavior, or WebUI presentation?
Hermes behavior should be exposed from Hermes and consumed by WebUI. WebUI should own only browser-specific presentation and interaction.
Hermes should own
- agent execution
- sessions and transcript persistence
- active run lifecycle
- model/provider/toolset routing
- memory and skills
- slash command semantics/capabilities
- approvals and clarify lifecycle
- cancel/interrupt/queue/steer semantics
- background and goal continuation
- token/tool/reasoning events
- cron/gateway/session state
WebUI should own
- layout and navigation
- chat rendering
- stream visualization
- tool-call cards
- approval/clarify widgets
- session browser UX
- settings panels
- workspace picker UX
- themes/mobile/PWA/native-wrapper behavior
- adapting Hermes events into a great browser experience
Proposed direction
Move browser-originated execution out of WebUI-owned AIAgent threads and into Hermes Agent’s native runtime/API surface.
Hermes Agent already has an API server with run-like endpoints:
POST /v1/runs
GET /v1/runs/{run_id}
GET /v1/runs/{run_id}/events
POST /v1/runs/{run_id}/stop
WebUI should use Hermes-owned runtime APIs as the execution boundary rather than importing/instantiating AIAgent directly.
Target architecture:
Browser
↓
WebUI: UI, auth/session presentation, SSE/WebSocket adapter, controls
↓
Hermes Agent runtime/API: sessions, active runs, events, commands, approvals, tools
↓
AIAgent / tools / gateway / cron / state.db
Proposed phases
Phase 1: Document the boundary and current coupling
- Document the current WebUI-owned execution path.
- Document which runtime features WebUI currently reimplements or owns directly.
- Define the desired owner for each feature: Hermes vs WebUI.
Acceptance criteria:
- Maintainers agree on the boundary: WebUI should be a thin client over Hermes runtime, not a second runtime.
Phase 2: Adapter spike against Hermes /v1/runs
- Add a feature flag, e.g.
HERMES_WEBUI_USE_HERMES_RUNS=1.
- Keep existing WebUI frontend event format initially.
- Internally route WebUI chat start to Hermes
/v1/runs.
- Subscribe to
/v1/runs/{run_id}/events and adapt Hermes events to current WebUI SSE events.
- Use Hermes
/v1/runs/{run_id}/stop for cancellation.
Acceptance criteria:
- A browser-originated chat can run without WebUI instantiating
AIAgent.
- Existing WebUI rendering continues working through an adapter.
- WebUI restart no longer kills a Hermes-owned run, assuming Hermes remains up.
Phase 3: Identify missing Hermes runtime API capabilities
While building the adapter, record missing pieces that force WebUI to keep private runtime state.
Likely needs:
- list active runs
- map session → active run(s)
- replay events from a cursor /
Last-Event-ID
- approval request/response API
- clarify request/response API
- queue/interrupt/steer API
- command metadata with WebUI support/capability flags
- session/run status fields sufficient for reconnect UI
Acceptance criteria:
- Missing capabilities are tracked upstream in Hermes Agent rather than patched as WebUI-only runtime logic.
Phase 4: Delegate Hermes-native slash/control commands
- Continue using Hermes command metadata.
- Add capability metadata so WebUI can know which commands are supported on the WebUI surface.
- Delegate Hermes-owned commands to Hermes instead of reimplementing them in WebUI.
- Keep WebUI-local commands local, e.g. theme/layout/browser UI commands.
Examples:
| Command / behavior |
Desired owner |
/goal |
Hermes |
/model / provider routing |
Hermes |
/reasoning |
Hermes |
/compress |
Hermes |
/status runtime status |
Hermes |
/theme |
WebUI |
| workspace picker UX |
WebUI presentation over Hermes/workspace config |
| terminal drawer UI |
WebUI presentation |
Acceptance criteria:
- WebUI no longer has to chase parity for Hermes-native behavior one command at a time.
Phase 5: Make WebUI restart/update safe by default
- WebUI restart should only drop the browser connection.
- Browser reconnect should discover the active Hermes run and resume event rendering.
- WebUI updates should not need to block on active Hermes-owned runs.
- Legacy WebUI-owned streams can remain blocked/drained during transition.
Acceptance criteria:
- Start a long-running run from WebUI.
- Restart
hermes-webui.service.
- Reopen browser.
- The run is still active or completed in Hermes.
- WebUI catches up to current run/session state.
Non-goals
- Not a rewrite of the frontend.
- Not a new sidecar service by default.
- Not a requirement for Redis/Kafka/NATS.
- Not horizontal scaling in the first pass.
- Not removing rich WebUI features.
- Not exposing CLI-only or unsafe gateway-only commands blindly.
Open questions
- Should WebUI talk to Hermes Agent API server directly, or through a small local adapter inside WebUI?
- Which current WebUI behaviors must remain WebUI-local because they are presentation-only?
- What is the minimal Hermes API surface needed for feature parity?
- How should browser reconnect discover an active run for the currently viewed session?
- Should Hermes event replay be durable immediately, or can phase 1 start with live Hermes SSE and add replay later?
- How should Hermes Agent updates behave while a Hermes-owned run is active?
- What command capability metadata is needed so WebUI can safely inherit Hermes-native slash commands?
Success criteria
This is successful when:
- WebUI does not import or instantiate
AIAgent for normal chat.
- WebUI can be restarted without killing active Hermes-owned runs.
- Browser refresh/reconnect does not duplicate or lose runs.
- WebUI renders live token/tool/reasoning/approval/clarify state from Hermes events.
- WebUI sends control actions to Hermes rather than mutating live agent objects.
- Hermes-native slash command behavior is inherited where possible.
- WebUI remains rich, but its codebase becomes mostly UI/adapters instead of agent orchestration.
One-line north star
Build WebUI as the first-class Hermes client, not a second Hermes runtime.
Summary
Hermes WebUI currently gives a polished browser experience, but browser-originated chat runs are executed inside the WebUI server process. That means WebUI is more than an interface layer: it creates in-memory streams, starts background agent threads, instantiates/reuses
AIAgent, and owns live cancellation/streaming state.This RFC proposes shifting WebUI toward the thinnest possible client that still gives full parity features:
The goal is not to reduce WebUI capability. The goal is to prevent WebUI from becoming a parallel Hermes runtime.
Current behavior / motivation
Today, the WebUI chat path roughly does this:
POST /api/chat/start.stream_idandStreamChannel.STREAMS._run_agent_streaming._run_agent_streamingconstructs/reusesAIAgentand callsagent.run_conversation(...).This has some nice properties: direct low-latency SSE, simple local deployment, and direct access to the live agent object. But it also creates real costs:
/goal, model/provider routing, approvals, clarify, queue/interrupt/steer, etc. risk being reimplemented in WebUI instead of inherited from Hermes.Design principle
For every feature, ask:
Hermes behavior should be exposed from Hermes and consumed by WebUI. WebUI should own only browser-specific presentation and interaction.
Hermes should own
WebUI should own
Proposed direction
Move browser-originated execution out of WebUI-owned
AIAgentthreads and into Hermes Agent’s native runtime/API surface.Hermes Agent already has an API server with run-like endpoints:
WebUI should use Hermes-owned runtime APIs as the execution boundary rather than importing/instantiating
AIAgentdirectly.Target architecture:
Proposed phases
Phase 1: Document the boundary and current coupling
Acceptance criteria:
Phase 2: Adapter spike against Hermes
/v1/runsHERMES_WEBUI_USE_HERMES_RUNS=1./v1/runs./v1/runs/{run_id}/eventsand adapt Hermes events to current WebUI SSE events./v1/runs/{run_id}/stopfor cancellation.Acceptance criteria:
AIAgent.Phase 3: Identify missing Hermes runtime API capabilities
While building the adapter, record missing pieces that force WebUI to keep private runtime state.
Likely needs:
Last-Event-IDAcceptance criteria:
Phase 4: Delegate Hermes-native slash/control commands
Examples:
/goal/model/ provider routing/reasoning/compress/statusruntime status/themeAcceptance criteria:
Phase 5: Make WebUI restart/update safe by default
Acceptance criteria:
hermes-webui.service.Non-goals
Open questions
Success criteria
This is successful when:
AIAgentfor normal chat.One-line north star