Hermes SidePulse
Standalone Hermes Agent integration for the SidePulse local status monitor. It contains a Python lifecycle plugin and a Hermes Desktop companion. The Python half observes Hermes lifecycle callbacks; the Desktop half reports the focused local Desktop session through an authenticated dashboard route. No Hermes core patch is required.
Release candidate:
0.2.0-beta.1(local, not published).
Scope
- Python plugin: registers the stock Hermes lifecycle hooks declared in
python/plugin.yamlin this repository (installed asplugin.yaml) and adds thehermes sidepulsediagnostic command. - Desktop companion: follows the focused session only when both the focused owner and the connected Desktop backend are local. Remote focus is skipped.
- Dashboard backend: exposes authenticated
GET /healthandPOST /activateroutes for the Desktop companion. Activation accepts status metadata only. - Ordinary hooks: keep their non-Desktop behavior. The Desktop path is an additional activation event; it is not a replacement for normal CLI or gateway lifecycle callbacks.
- Transport: best-effort local Unix-socket delivery to SidePulse. A missing monitor or unavailable socket does not stop Hermes.
The Desktop folder id is hermes-sidepulse.
Support boundary
- macOS: automated tests passed locally. The predecessor was live-tested; this exact beta has not replaced the working installation or been hardware-tested.
- Linux: a CI job is included, but hosted Linux has not yet run.
- Windows: unsupported because the transport uses Unix-domain sockets.
- Hermes baseline: Hermes Agent
v0.21.3, upstream commitcf7895c1bd9ebbee3c9fc355c552d4d73338a9b4. The installed checkout reported that newer commits were available; they were intentionally not followed for this documentation pass.
See docs/verification.md for the evidence and the
test scope and platform limitations.
Prerequisite: SidePulse monitor
Install and set up the SidePulse monitor before enabling this plugin. These are the commands documented by the SidePulse project:
uv tool install git+https://github.com/inteliwear/sidepulse
sidepulse setup
The monitor should be running and accepting the local socket at:
${XDG_STATE_HOME:-${HOME}/.local/state}/sidepulse/agent-monitor/events.sock
The Hermes plugin does not install SidePulse, install hardware support, or start the SidePulse status-bar process.
Manual installation
This repository is intentionally documented for a manual copy. Hermes' native Python plugin manager can enable an installed plugin, but this package's Desktop half is a separate app-local runtime folder. Do not assume a Git installer, a profile copy, or an automatic cross-profile deployment.
1. Copy the Python half
HERMES_HOME defaults to ${HOME}/.hermes. Set it explicitly when targeting a
named Hermes profile. SOURCE_DIR is the directory containing this checkout;
it is a placeholder and is not part of the installed package layout.
SOURCE_DIR="/path/to/hermes-sidepulse"
HERMES_HOME="${HERMES_HOME:-${HOME}/.hermes}"
PYTHON_PLUGIN_DIR="$HERMES_HOME/plugins/hermes-sidepulse"
test ! -e "$PYTHON_PLUGIN_DIR" || { printf "Destination exists; use the backup/replace procedure below.\n"; exit 1; }
mkdir -p "$PYTHON_PLUGIN_DIR/dashboard" "$PYTHON_PLUGIN_DIR/scripts"
cp "$SOURCE_DIR/python/__init__.py" "$PYTHON_PLUGIN_DIR/"
cp "$SOURCE_DIR/python/bridge.py" "$PYTHON_PLUGIN_DIR/"
cp "$SOURCE_DIR/python/diagnostics.py" "$PYTHON_PLUGIN_DIR/"
cp "$SOURCE_DIR/python/plugin.yaml" "$PYTHON_PLUGIN_DIR/"
cp "$SOURCE_DIR/python/dashboard/manifest.json" "$PYTHON_PLUGIN_DIR/dashboard/"
cp "$SOURCE_DIR/python/dashboard/noop.js" "$PYTHON_PLUGIN_DIR/dashboard/"
cp "$SOURCE_DIR/python/dashboard/plugin_api.py" "$PYTHON_PLUGIN_DIR/dashboard/"
Install the compatibility helper too; hermes sidepulse compat invokes it:
cp "$SOURCE_DIR/python/scripts/check_hermes_compat.py" "$PYTHON_PLUGIN_DIR/scripts/"
Enable the Python plugin with the supported Hermes CLI command:
HERMES_HOME="$HERMES_HOME" hermes plugins enable hermes-sidepulse
HERMES_HOME="$HERMES_HOME" hermes plugins list --user --no-bundled --json
HERMES_HOME="$HERMES_HOME" hermes plugins validate \
"$PYTHON_PLUGIN_DIR" --json
hermes plugins enable <name>, hermes plugins disable <name>, hermes plugins remove <name>, and hermes plugins validate <path> --json were
verified against the stated Hermes baseline. Validation is read-only; it does
not install the candidate into the live Hermes home.
2. Copy the Desktop half
Install the Desktop companion once in the Hermes home used by the local Hermes Desktop application. It is an app-local folder, not a per-profile backend copy:
HERMES_HOME="${HERMES_HOME:-${HOME}/.hermes}"
test ! -e "$HERMES_HOME/desktop-plugins/hermes-sidepulse" || { printf "Desktop destination exists; back it up first.\n"; exit 1; }
mkdir -p "$HERMES_HOME/desktop-plugins/hermes-sidepulse"
cp "/path/to/hermes-sidepulse/desktop/plugin.js" \
"$HERMES_HOME/desktop-plugins/hermes-sidepulse/plugin.js"
The folder name must remain hermes-sidepulse; the file name must remain
plugin.js.
3. Restart Desktop's backend after first install
After the first Python/Desktop installation, restart the Hermes Desktop
backend before judging the integration. Use Desktop's backend restart control
when available; otherwise quit and reopen Hermes Desktop. Later edits to
desktop/plugin.js may be hot-reloaded by Desktop, but a first install must be
validated after the backend route and Python plugin have been discovered.
For a named profile, repeat the Python copy and enable steps with that
profile's HERMES_HOME (normally ${HOME}/.hermes/profiles/<profile>), or use
Hermes' --profile <profile> selector with the enable/list commands. This guide
makes no automatic cross-profile-install claim, even though the manifest carries
profile_scope: shared; verify every profile that is meant to run the Python
half.
Configuration
New configuration belongs under plugins.entries.hermes-sidepulse.settings:
plugins:
entries:
hermes-sidepulse:
settings:
agent_id: Hermes
# state_dir: /custom/state/sidepulse/agent-monitor
# socket_path: /custom/state/sidepulse/agent-monitor/events.sock
# socket_timeout: 0.2
# log_events: true
The active profile name is used when agent_id is empty. The complete setting
defaults, coercions, event fields, and Desktop activation rules are in
docs/configuration.md.
Privacy model
The plugin emits status metadata only:
- hook-derived event name and normalized SidePulse mode;
- timestamp, provider label, configured display label, and opaque session-scoped status identity;
- Hermes profile name, durable session id, and turn id when supplied;
- client surface/origin label; and
- tool name metadata, never tool arguments or results.
It does not send prompts, transcripts, assistant messages, approval details, tool arguments, tool results, response bodies, or approval routing keys. Final response text is inspected in memory only to choose a final status; it is not placed in the socket event or metadata log. The plugin registers no model-facing tools and adds no tool schema to model prompts.
When log_events is enabled (the default), the plugin appends compact metadata
to hermes.jsonl under the SidePulse state directory and enforces owner-only
file mode 0600. The package contains no operational logs and must not be
published with a user's hermes.jsonl, latest.json, socket, compatibility
baseline, or session data.
Status behavior
The Python lifecycle mapping produces these normalized modes:
| Mode | Meaning |
|---|---|
idle_ready |
Session started/reset or Desktop focus is not running. |
working |
Hermes is processing a prompt or has returned from a successful tool. |
tool_running |
A non-clarification tool is executing. |
waiting_for_input |
Hermes needs approval, clarification, or an explicit final question. |
blocked_error |
An approval was denied, an API request failed, or a tool failure is terminal for the event. |
completed |
A final response or session finalization completed successfully. |
A Desktop focus activation preserves an existing waiting_for_input state; it
does not turn an outstanding question into idle. See the mapping table in
docs/configuration.md.
Diagnose and verify
After the backend restart:
HERMES_HOME="${HERMES_HOME:-${HOME}/.hermes}" hermes sidepulse doctor --json
HERMES_HOME="${HERMES_HOME:-${HOME}/.hermes}" hermes sidepulse test --mode working --json
HERMES_HOME="${HERMES_HOME:-${HOME}/.hermes}" hermes sidepulse test --mode ask --json
HERMES_HOME="${HERMES_HOME:-${HOME}/.hermes}" hermes sidepulse test --mode done --json
Available synthetic modes are idle, working, tool, ask, done, and
error. delivered: false means the local SidePulse socket was unavailable;
that is an honest transport result, not a failed Hermes turn.
After a Hermes update, run:
hermes sidepulse compat --json
The compatibility command checks the current Hermes version and hook set, the enabled plugin, the SidePulse socket, and synthetic event logging. Do not use it as a reason to patch Hermes core; this plugin is designed to fail closed on missing profile provenance and to keep its observer callbacks non-blocking.
Upgrade, rollback, and removal
Disable the Desktop companion in Settings → Plugins and disable the Python plugin first. Move only the existing plugin folders into a new backup directory; do not overlay an upgrade on old files. Repeat the Python step for each profile that received a copy. Run this block for the local Desktop home:
HERMES_HOME="${HERMES_HOME:-${HOME}/.hermes}"
HERMES_HOME="$HERMES_HOME" hermes plugins disable hermes-sidepulse
BACKUP_DIR="$(mktemp -d "$HERMES_HOME/sidepulse-backup.XXXXXX")"
test ! -d "$HERMES_HOME/plugins/hermes-sidepulse" || \
mv "$HERMES_HOME/plugins/hermes-sidepulse" "$BACKUP_DIR/python"
test ! -d "$HERMES_HOME/desktop-plugins/hermes-sidepulse" || \
mv "$HERMES_HOME/desktop-plugins/hermes-sidepulse" "$BACKUP_DIR/desktop"
printf 'Preserved plugin files in %s\n' "$BACKUP_DIR"
For removal, stop here and restart the backend yourself. For an upgrade, follow the fresh installation steps above, enable the Desktop companion again, and restart the backend. Verify the new version before discarding any backups.
For rollback, disable both new components, move them to a different backup,
and move the original python and desktop folders back to their respective
plugins/hermes-sidepulse and desktop-plugins/hermes-sidepulse locations.
Re-enable both components and restart the backend. Keep configuration unchanged
unless the release explicitly requires a configuration migration.
Restart the Desktop backend after removal if its backend route is still reachable. Removal does not delete Hermes sessions, SidePulse state, or other plugins. Do not use a broad profile reset, remove all plugins, or delete the SidePulse state directory as part of this bounded removal.
Migration from an older SidePulse core patch
Use the bounded procedure in docs/migration.md. It backs
up and removes only the known old SidePulse core edits, disables only the old
repair automation, and never performs a broad Hermes reset.
License and provenance
The license and source-history findings are recorded in NOTICE.md.
The root LICENSE preserves the upstream SidePulse MIT notice and
adds attribution for the standalone changes.