Article

The Complete Guide to Hermes Deployment and Updates

What the Hermes agent service and the WebUI service each run, what an update restarts and in what order, why an incomplete clone changes the plan, and how to prove your session survived.

An update is the moment a self-hosted agent stops looking like one program and starts looking like three. The command you type reads as a single action. Underneath it, a gateway restarts, a browser front end keeps serving old code, and a code checkout that was cloned in an unusual way decides whether the update can fast-forward at all.

Hermes Agent is Nous Research’s open-source agent framework. It runs in a terminal, a desktop app, messaging platforms and IDEs, and it is the same agent core behind every one of those surfaces. When you self-host it, you usually end up running more than one long-lived process: the agent’s gateway, the built-in dashboard, and often a separate browser app called Hermes WebUI.

This guide covers the architecture those pieces form, what hermes update restarts and in what order, why an incomplete clone changes your plan, and how to prove a session survived. For the step-by-step procedure, read the update guide. For a side-by-side of the agent and the browser app, read Hermes Agent vs Hermes WebUI.

The agent service is the process that stays up

The part of Hermes that keeps running between your questions is the gateway. hermes gateway manages it, with separate subcommands for run, start, stop, restart, status, install and list. In its installed form it is a systemd or launchd background service, so it survives a terminal closing and starts again after a reboot.

The gateway is not just messaging. It receives turns from messaging platforms, runs scheduled and webhook-triggered work, and delivers the results. If your agent does something while you are asleep, the gateway is the process that did it. That is why an update restarting the gateway is the event you feel: your bots go quiet for the length of the code swap and the dependency sync.

A machine can run several profiles. hermes gateway list shows each profile’s status, and hermes gateway migrate converges per-profile gateways onto one host gateway. So “the agent service” is really “whichever gateway processes your profiles are running right now”, and hermes update --plan is how you see that set before you change anything.

The web UI service is a different process, and there are two of them

“Hermes Web UI” points at two different things, and the update behaviour differs between them.

The first is the built-in dashboard, started with hermes dashboard. The docs describe it as a browser-based management panel for one machine: configuration, API keys, MCP servers, pairing, webhooks, memory, sessions, logs, cron jobs and skills, plus an embedded chat. It is a machine-level surface, so one server manages every profile, with a profile switcher in the sidebar. It ships with the agent.

The second is Hermes WebUI, a separate community project under its own MIT license. It is a browser front end with full CLI parity, and it is deliberately small: no build step, no framework, no bundler, just Python and vanilla JavaScript. You start it with python3 bootstrap.py, or with its ctl.sh wrapper, and it keeps state under the Hermes home.

The important part for deployment is how it runs the agent. The WebUI loads the Hermes agent in-process, reading your existing agent install and models rather than starting a second copy. That single design choice explains almost every update surprise that follows.

Why the service topology decides what a restart touches

Two systemd scopes are in play. The gateway is commonly installed as a per-user service, while the dashboard and the WebUI are commonly system services. systemctl --user and systemctl therefore answer differently about “Hermes”, and a systemctl is-active hermes-gateway run in the system scope can report inactive while the gateway is running fine in the user scope.

Each unit also has its own cgroup, and systemd’s default KillMode=control-group stops every process inside it. That includes a chat turn the unit is currently serving. This is the trap behind “the update killed my session”. The restart did not fail. It did exactly what a service restart does to the process tree that hosts the turn.

What hermes update actually restarts, and in what order

For a managed source checkout, hermes update runs a fixed pipeline. The updating docs list the stages:

  1. A pre-update snapshot of state files.
  2. The code update itself, applying your configured source channel.
  3. Post-pull syntax validation, with an automatic rollback to the pre-pull commit if a critical file fails to parse.
  4. Dependency preparation through the package manager.
  5. Config migration for options added since your version.
  6. A desktop-app rebuild, when the app was built from this checkout.
  7. The gateway restart, followed by a multi-profile migration step.

The restart is the part that matters here. On Linux and macOS, the updater pauses every gateway running from that install right before it first changes the checkout, and starts exactly that set again on the new code once dependencies are prepared. The restart is drain-first: the gateway refuses new turns, waits for in-flight chat turns and cron jobs to finish, and reports what it is still waiting on. The wait is capped by agent.restart_after_turn_timeout, which defaults to 30 minutes.

Two things are not in that restart set. Manually launched hermes serve and hermes dashboard backends are left running, and the updater reminds their owner to restart them. And a separate Hermes WebUI checkout is not part of the install at all, so no part of hermes update touches it.

So the order is fixed, and it is the order the dependency demands: the agent first, the WebUI second. Update the agent, let the gateways come back on the new code, then pull the WebUI checkout and restart its server. Do it the other way round and the WebUI restarts onto old agent code, and you repeat the restart once the agent update lands.

Why the agent has to move first

Read the WebUI’s interpreter rule again: it uses the agent’s Python environment when it can find it. That environment is exactly what an agent update can rebuild. A running WebUI keeps serving the modules it imported at startup, so an agent update never refreshes it. Restarting the WebUI is what loads the new agent code into that process.

That also means an agent update can move a runtime the WebUI’s unit referenced by path. After any agent update, check that the WebUI unit’s ExecStart, or the interpreter its launcher discovered, still points at a Python that exists. A unit that suddenly crash-loops after an agent update is usually pointing at an interpreter the update replaced, not suffering a bad commit.

The update receipt helps here. Every update writes a machine-readable receipt under the Hermes home, keeping the recent ones, with the pre-update fleet plan and each step taken. Comparing that plan with what the running services actually report is how you catch a service that was not restarted and is still on old code.

Why a shallow clone changes your update strategy

Git history is what git pull --ff-only and behind/ahead counts depend on. The current installer understands that. It clones the agent with --filter=blob:none, a blobless partial clone that keeps every commit and release tag while fetching file contents on demand. Full history, lazy contents.

A shallow clone is the opposite trade. git clone --depth 1 cuts history at the boundary it fetched, and git records that boundary in a .git/shallow file. You can see it directly: a depth-1 clone reports git rev-parse --is-shallow-repository as true and git rev-list --count HEAD as 1. Every history-based question now answers from a truncated past.

That is what changes the strategy. On a shallow or otherwise incomplete agent checkout, git rev-list --left-right --count origin/main...HEAD gives numbers you cannot trust, and a hand git pull is not the right tool. Do not pull the agent checkout by hand. Let hermes update own it: it detects the divergence, saves the old HEAD under a backup ref in the checkout, and resets onto the channel. If you genuinely need the history back for your own tooling, git fetch --unshallow restores a complete clone before you touch it.

The practical rule is the same whatever kind of incomplete clone you have: treat the agent checkout as the updater’s, not yours.

How to verify the session survived

“Did my session survive?” has a real answer, and it is not systemctl is-active. A unit can be active and still be running the code it loaded before your update.

Verify with evidence:

For the gateway, the same idea applies with different tools. Watch the updater’s paused and restarted lines, and use hermes gateway status to confirm the fleet is on the new commit. A restart from inside a chat that the gateway itself hosts is handled specially: the update moves itself into a scope of its own first, so the gateway pause does not kill the turn issuing the update.

A safe update sequence

  1. Recon, read-only. Check hermes --version, hermes update --check and hermes update --plan. Note which unit hosts the chat you are in.
  2. Update the agent with hermes update. Run it in the background and watch for the paused and restarted lines. A drain can legitimately take minutes.
  3. Confirm the agent. hermes --version should show the new commit, and hermes gateway status should list every profile running it.
  4. Update the WebUI. Fetch its checkout, check whether its dependency files changed, fast-forward, then restart its server from outside its own cgroup.
  5. Verify with timestamps. Compare MainPID and ActiveEnterTimestamp against the moment you pulled, and confirm the browser service still answers at its auth gate.

When this model does not apply

This split-service picture is for a managed source checkout. Other installs update by their own rules. A Docker image is pulled and the container recreated, a Nix install updates the flake and rebuilds, a Store or sideloaded desktop package updates through its own channel, and a Termux package updates through pkg. If you run only the CLI on your laptop, with no gateway and no browser service, there is no topology to reason about and nothing to restart.

The other exception is a service that is not service-managed. A gateway started by hand with hermes gateway run, or a WebUI started from a terminal, has no unit to restart and no MainPID to compare. There, “verify the session survived” becomes “check the process is the new one”, and the updater’s reminders are the closest thing to a receipt.

Takeaways

Hermes Agent docs Update without breaking a session