Hermes runtime package
The Hermes package (runtime-packages/hermes) is the only source of the
Hermes Agent runtime: the daemon compiles no Hermes in. It is the package form
of the built-in Hermes runtime that releases up to 0.34.x compiled in, plus the
operator plugin payload that pohunek integration installs into a Hermes
profile. The
directory holds exactly what the archive holds:
runtime-packages/hermes/ runtime.toml runtime descriptor (package pohunek.runtime.hermes 1.0.1) detect.toml detection manifest named by `detect_manifest` operator.toml closed sidecar: every operator payload file and its SHA-256 operator/ the operator plugin payload the sidecar listsIts compatibility lock, the PTY goldens captured from a real Hermes and the
evidence notes stay outside the archive, in compat/hermes/
(compatibility-lock.json, goldens/, README.md). See runtime
packages for the generic commands and the Hermes
operator guide for the plugin, its policy and the
integration lifecycle.
Status: official package on Linux; no built-in
Section titled “Status: official package on Linux; no built-in”hermes is a reserved runtime id. A package serves it only with official
trust: a --catalog install whose signed catalog entry binds the package id,
runtime id and digest, verified against the host’s catalog trust anchor. An
install trusted by --sha256 is refused with package_runtime_not_claimable.
Releases build the package like every other official package
(pohunek-runtime-hermes-<version>.tar.zst from runtime-packages/hermes).
compat/matrix.json attests it for x86_64-unknown-linux-gnu and
x86_64-unknown-linux-musl, so the signed catalog entry lists those platforms
and every Linux daemon archive ships runtime/packages/hermes.tar.zst and
runtime/attestations/hermes-<target>.json beside
runtime/runtime-catalog.json.
Installing official packages
has the procedure:
cd pohunek-daemon-<version>-<target>pohunek plugin install runtime/packages/hermes.tar.zst --catalog runtime/runtime-catalog.jsonpohunek plugin install runtime/packages/hermes.tar.zst --catalog runtime/runtime-catalog.json --yesThe first installed version is enabled and selected and serves the hermes
alias (see official aliases).
pohunek integration install --agent hermes --hermes-profile <name> (target
and access options in the Hermes operator guide) then
installs the operator plugin
from the selected package and reports provenance source=package with the
package digest.
The package is the only source of the operator plugin: the CLI carries no
copy of its payload, and pohunek integration install|update --agent hermes
without an installed package fails with hermes_package_not_installed, whose
recovery hint names the install command above.
On a host without the package nothing serves hermes:
pohunek session new --agent hermesfails withagent_profile_not_found, whose hint namespohunek plugin install runtime/packages/hermes.tar.zst --catalog runtime/runtime-catalog.json(run from the extracted release archive). After the catalog install the same request launches a session pinned to the package.- A host profile with
base = "hermes"and nopackage/digestkeys fails withruntime_not_installed; its hint names the install and thenpohunek plugin profile migrate <name>. While the package serveshermes, such a profile needs that migration before it launches. - The assistant answers
no_capable_agentwhen no package servescodex,claudeorhermesand no capable host profile exists; ahermeson the daemon’sPATHalone does not make it capable. - A
hermesstarted outside pohunek or in the foreground of a shell session is not attributed to Hermes: process recognition reads the package’sdetect.toml. - A session the built-in Hermes of an earlier release launched (pinned, or a
v0.33.0 record without a launch binding) is refused on resume, fork and
every interaction (input, attach, resize, rename, metadata) with
runtime_builtin_removed, whose hint names the install; only stop and remove still work. With the package installed, the package adopts the session, live or stopped, and serves its input again. Resume runs through the package when it honors the session’s recovery contract; fork answersagent_fork_unsupported, because Hermes declares no native fork. A session whose contract the package cannot honor answersruntime_served_by_package, and no command rebinds the session to another runtime source. A v0.33.0 record without a launch binding answers it as well. Without the package,pohunekd upgrade-preflightreports every live built-in Hermes session aswould_lose_recovery(runtime_source_not_served) whether or not it could resume. No earlier release archive carries this package, so such a session cannot be moved onto it before the upgrade: finish or stop it first. - Uninstalling or disabling the package leaves
hermesunserved.
An operator plugin that an older CLI installed from its compiled copy
reports provenance source=embedded; integration update moves it to the
package.
What the package declares
Section titled “What the package declares”The descriptor equals the built-in Hermes descriptor release 0.34.2 shipped
except for four fields: detect_manifest names the package’s detect.toml
instead of that release’s embedded hermes manifest, the version probe is
the data-driven semver-line-v1 parser instead of that release’s compiled
hermes-v1 parser, which this daemon does not compile in, and [input]
declares ready = "screen_idle" and startup_grace_ms = 30000, which that
release’s descriptor could not carry. detect.toml is
byte for byte the built-in detection manifest of release 0.34.2. Golden copies
of both built-in files are in compat/hermes/builtin-v0.34/ (runtime.toml,
detect.toml), each below a two-line header that names its source path and
tag; the package tests compare the package with them.
- Launch:
hermes chat; resume ishermes chat --resume <reference>with anidreference; fork is unsupported. Later input is a bracketed paste followed by Enter 150 ms later, filtered by thehermes_safe_textpolicy. - Readiness:
ready = "screen_idle". Hermes 0.20.0 queries the terminal’s background colour while it starts and restores the terminal mode withTCSAFLUSH, which discards every byte queued so far (measured 0.7 to 1.25 s after spawn), and its prompt reads input only after that. Only the rendered idle prompt, theidle_promptortui_idle_statusrule of the detection manifest, starts the initial input. - Startup grace:
startup_grace_ms = 30000. A first start after an install that wrote no Python bytecode (auvinstall does not) compiles every module before the prompt renders, which took more than 5 s on a CI runner; later starts render well within the 5000 ms host default. For a current client the readiness wait lasts up to the longer of this and the host’sinitial_startup_grace_ms, and ends as soon as the prompt renders; an older client withoutextended_input_ready_waitkeeps the host’s grace. A session whose prompt does not render within its wait failssession.newwithsession_input_not_readyand is rolled back; the initial input is not written. - Version:
version_probe = { parser = "semver-line-v1", args = ["--version"], min = "0.20.0", below = "1.0.0", line = "Hermes Agent v{version} {annotation}" }, which accepts the locked bannerHermes Agent v0.20.0 (2026.8.3)and every later 0.x release. The range equals the lock’ssupportedrange;minis the verified release andbelowthe next major, so later releases are accepted by policy, not verified.Hermes Agent v1.0.0and older releases are unsupported. - Native reference:
strategy = "hook". - Integration: handler
hermes-hook-v1with hook schemaidentity-v1. The package names the compiled handler and schema; its lifecycle runs in the CLI (pohunek integration ... --agent hermes). - Detection (
detect.toml): process matchers are the kernel namehermes, a command line whose program ishermes, or apython/python3[.N]command line running-m hermes_cli,-m hermes_cli.mainorhermes_cli/main.py. Rules, highest priority first:fatal_startup(blocked):Failed to initialize agent:orrequired API key(s) missing:anywhere in the recent screen.dangerous_command_approvalandtui_approval(blocked): the classicDangerous Commandpanel or the alternate-screen TUI’sapproval requiredheading, each withAllow onceandDeny, in the bottom 16 non-empty lines.active_turn_prompt(working): the Hermes staff symbol⚕before the prompt glyph in the bottom 8 non-empty lines;active_turn_progressandtui_active_turn(working): a[thinking]line,◆ aggregatingor the TUI’sCtrl+C to interrupt…there.idle_promptandtui_idle_status(idle): the classic prompt glyph, optionally after a Hermes profile name, or the TUI’s─ readyrule in the bottom 4 non-empty lines.
Operator payload
Section titled “Operator payload”operator.toml (schema 1) is a closed allowlist: it lists the eight payload
files, plugin.yaml, __init__.py, cli.py, hooks.py, policy.py,
redact.py, tools.py and skills/pohunek/SKILL.md, each with its SHA-256.
The CLI verifies every path and digest before it installs the plugin, and an
unlisted, missing or altered file, or a policy-path placeholder in
__init__.py that is absent or repeated, fails with hermes_invalid_asset
(the Hermes operator guide lists the source errors).
The package carries no hook reporter script: the reporter
_pohunek_reporter.py is core-owned, compiled into the CLI, and installed
beside the operator payload.
operator/skills/pohunek/SKILL.md is generated from
docs/knowledge/guides/hermes-operator.md. cargo xtask hermes generate-skill writes it and refreshes its checksum in operator.toml;
cargo xtask hermes check-skill and cargo xtask docs check fail when either
differs. A change of that guide therefore changes the package bytes and its
digest.
How the package is kept honest
Section titled “How the package is kept honest”crates/cli/tests/hermes_package.rsalways runs. It builds the directory into an archive and installs it through the CLI and a real daemon with a throwaway signed catalog (crates/test-support/src/catalog.rs, featurecatalog); the packaged skill gets one extra line, so the installed bytes show which source supplied them. Its scenarios:the_signed_hermes_package_is_selected_and_failed_installs_preserve_it: descriptor and manifest parity with the v0.34.2 goldens above, the supported range equal tocompat/hermes/compatibility-lock.json, the probe accepting the locked banner and a later 0.x release and refusingHermes Agent v1.0.0 (future), and a--sha256install (package_runtime_not_claimable) and a corrupt archive whose digest the catalog does not list (package_untrusted, refused before decoding) that leave the selection unchanged.package_backed_install_writes_the_selected_package_bytes: the installed plugin, its provenance and its policy binding come from the selected package.without_a_hermes_package_install_refuses_and_writes_nothing: with no registry and with a registry that holds no Hermes package, install and update refuse withhermes_package_not_installedand write nothing.an_embedded_install_reports_the_missing_package_and_moves_to_it_on_update: a version 1, 2 or 3 marker with no package source reports the missing package in status and doctor, moves to the package onintegration update, and uninstalls without a package.an_unusable_selection_without_an_install_is_a_typed_source_error,losing_the_recorded_package_refuses_update_without_mutationandupdate_moves_an_install_to_the_newly_selected_package: the other source errors, and the move between package versions onintegration update.the_package_backed_hermes_lifecycle_is_isolated_and_model_free: the complete operator lifecycle through thepohunekbinary, including policy damage, provenance drift, invalid and earlier markers, modified assets and a confirmed uninstall.sidecar_violations_in_a_signed_package_fail_install: a signed package whose sidecar contract is violated never reaches the plugin tree.hermes_launches_only_from_the_installed_official_package: on a host whosePATHfinds a fakehermesof the locked release and no package is installed,session new --agent hermesis refused withagent_profile_not_foundand the release install as the hint, and the assistant answersno_capable_agent; after the catalog install the same request launches a session pinned to the package (which then refusesplugin uninstallwithpackage_referenced), and the assistant pickshermes.
a_real_hermes_turn_uses_the_selected_package_and_public_session_screenis#[ignore]d. It installs the package, installs the operator plugin from it into an isolatedHERMES_HOME, launches a real pinned Hermes withpohunek session newagainst a loopback model provider (crates/cli/tests/support/hermes_provider.rs), submits one prompt withsession input, reads the reply fromsession screen, requires the reported native reference to be the conversation Hermes stored, and resumes it aftersession stop. Run it with the provisioned stage’s executable:POHUNEK_HERMES_E2E=1 POHUNEK_HERMES_BIN=<abs>/venv/bin/hermes POHUNEK_PYTHON_BIN=<abs>/venv/bin/python cargo nextest run --profile package -p pohunek-cli --test hermes_package --run-ignored all --test-threads 1. Without the variables it fails by name instead of skipping.cargo xtask hermes compatibility --bin-dir ABS --package ARCHIVEis the model-free gate of the pinned release: it installs this archive through a copied daemon with a throwaway catalog and checks the CLI, plugin and PTY golden evidence (compat/hermes/README.md).scripts/build-hermes-package <archive>buildsARCHIVEfrom this directory.- The
hermes-packageCI job (“pinned Hermes compatibility (model-free)”) follows the runtime package selection like the other package jobs. It stages the locked release withcargo xtask compat stage-upstream, builds the daemon, session worker and CLI, builds the archive withcargo xtask package build --evidence-dirand uploadspackage-evidence-hermes, requires a verified release baseline digest to match, runs the compatibility gate, restores the untouched stage, runs the real-Hermes scenario under thepackagenextest profile and uploadspackage-junit-hermes. The macOS jobhermes-compatibility-macosruns the gate and the packaged plugin smoke on Apple Silicon. - A release runs the consumer suite on the Linux daemon archives for the
hermesrows ofcompat/matrix.jsonand attests them, and the archive smoke installs the package from each Linux daemon archive’s own catalog in a network namespace (see the release bundle).
Update procedure for a new Hermes release: move the lock
(compat/hermes/compatibility-lock.json) and the provisioner’s pins together,
move the descriptor’s min/below (and line if the banner changes) to the
lock’s supported range, refresh the goldens with cargo xtask hermes refresh-goldens --hermes-bin ABS, rerun the compatibility gate and the
real-Hermes scenario, and bump the package version.