Skip to content

feat: support package-managed installs (Snap/Homebrew) - #37710

Open
beriberikix wants to merge 4 commits into
NousResearch:mainfrom
beriberikix:feat/managed-install-support
Open

feat: support package-managed installs (Snap/Homebrew)#37710
beriberikix wants to merge 4 commits into
NousResearch:mainfrom
beriberikix:feat/managed-install-support

Conversation

@beriberikix

Copy link
Copy Markdown

What does this PR do?

Distinguishes config-managed installs (NixOS — declarative, read-only config) from package-managed installs (Snap/Homebrew — the package owns the install tree, but the user still configures Hermes in a writable HERMES_HOME).

Previously a single is_managed() gate blocked all interactive config/credential writes for any managed install. That is correct for NixOS, but wrong for Snap/Homebrew, where the user must still be able to save settings. This is the prerequisite for snap packaging (#37709), and it independently fixes spurious managed-install guidance on Homebrew/NixOS.

Related Issue

Part of #37709

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)

(Also folds in two small, same-theme fixes: a doctor false warning and a misleading gateway log path under relocated HERMES_HOME.)

Changes Made

  • hermes_cli/config.py: add is_config_managed() (true only for NixOS); recognize snap/snapcraft → "Snap"; Snap update/message helpers; swap is_managed()is_config_managed() in the file-permission and config-write paths so package-managed installs can write to HERMES_HOME.
  • hermes_cli/gateway.py, hermes_cli/setup.py, tui_gateway/server.py: allow config writes on package-managed installs, and route gateway install/uninstall/start/stop/status to snapd-native commands.
  • hermes_cli/main.py: postinstall is a no-op with guidance under Snap; uninstall points at snap remove hermes-agent.
  • hermes_cli/doctor.py: skip the pip/venv "Command Installation" checks on managed installs (the pip install -e advice can't apply to a read-only package).
  • gateway/run.py: the allowlist warning now references the resolved HERMES_HOME/.env path via get_env_path() instead of a hardcoded ~/.hermes/.env.
  • Tests: tests/hermes_cli/test_managed_installs.py, test_gateway_service.py, test_doctor_command_install.py.

How to Test

pytest tests/hermes_cli/test_managed_installs.py \
       tests/hermes_cli/test_gateway_service.py \
       tests/hermes_cli/test_doctor_command_install.py -q

Manual:

HERMES_MANAGED=snap hermes doctor    # "Managed by Snap"; no pip/venv advice
HERMES_MANAGED=snap hermes update    # prints: snap refresh hermes-agent

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (feat(config):, fix(doctor):, …)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this feature (managed-install behavior)
  • All touched-area tests pass. (The full scripts/run_tests.sh has unrelated pre-existing env failures on my machine — optional backends needing API keys/network: web/voice/browser/image/wecom/acp — none in files this PR touches.)
  • I've added tests for my changes
  • Tested on: Ubuntu 24.04 (amd64)

Documentation & Housekeeping

  • Docs — N/A (internal behavior; no user-facing config keys)
  • cli-config.yaml.example — N/A (no config keys added/changed)
  • CONTRIBUTING.md/AGENTS.md — N/A
  • Cross-platform impact considered — platform-neutral Python; the doctor change sits inside the existing sys.platform != "win32" guard
  • Tool descriptions/schemas — N/A

beriberikix and others added 4 commits June 3, 2026 05:43
… installs

Hermes previously had a single `is_managed()` gate that blocked ALL
interactive config/credential writes for every package-manager-managed
install. That is correct for NixOS, which generates the config
declaratively and owns a read-only result, but wrong for package managers
like Homebrew and Snap: those own only the install tree, while the user
still configures Hermes in a writable HERMES_HOME.

This adds `is_config_managed()` (true only for NixOS) and recognizes Snap
as a managed system:

- `_MANAGED_SYSTEM_NAMES` maps `snap`/`snapcraft` -> "Snap" so the
  HERMES_MANAGED env var set by the snap launcher is understood.
- `is_config_managed()` is used by the file-permission and config-write
  paths in this module instead of `is_managed()`, so Snap/Homebrew users
  can save settings and credentials to their writable HERMES_HOME while
  NixOS still refuses mutations.
- Update-command and managed-message helpers learn the Snap variants
  (`snap refresh hermes-agent`).

No behavior change for NixOS or unmanaged installs; this only unblocks the
package-managed-but-config-writable case that Snap needs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…stalls

Builds on the config-managed vs package-managed split. Applies it across
the user-facing commands and routes lifecycle actions to the snapd-native
equivalents so a confined Snap behaves sensibly:

- gateway.py: `gateway setup` now gates on `is_config_managed()` (so Snap
  users can configure the gateway), and `gateway install/uninstall/
  start/stop/restart/status` print snapd guidance (`snap start
  hermes-agent.gateway`, `snap services …`, etc.) instead of trying to
  manage a systemd unit that the snap already declares as a daemon.
- main.py: `postinstall` is a no-op with guidance under Snap (deps are
  baked into the snap, not pip-installed), and `uninstall` points at
  `snap remove hermes-agent` (`--purge` to drop user data).
- setup.py and tui_gateway/server.py: swap `is_managed()` ->
  `is_config_managed()` so the setup wizard and the dashboard's credential
  endpoint allow writes to the writable HERMES_HOME on Snap/Homebrew.

Tests cover get_managed_system()/is_config_managed(), the update/
postinstall/uninstall rewrites, and the gateway service guidance for Snap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Command Installation" section assumes a pip/editable install: it
looks for a venv entry point and a ~/.local/bin/hermes symlink, and on a
miss it advises `pip install -e '.[all]'`. On a package-managed install
(Snap/Homebrew/NixOS) none of that applies — the package manager provides
the command, the tree is read-only, and the pip advice is impossible to
follow. On Snap this surfaced as two spurious warnings plus a bogus
remediation line in the summary.

When `get_managed_system()` is set, doctor now reports
"Managed by <system>" and skips the venv/symlink checks entirely.
Unmanaged installs are unaffected. Also improves Homebrew/NixOS, which
had the same false warning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "no user allowlists configured" warning hardcoded `~/.hermes/.env`,
which is misleading whenever HERMES_HOME is relocated — most visibly under
Snap, where the env file lives at
`$SNAP_USER_COMMON/hermes/.env`. Use `get_env_path()` (the canonical
helper, = `get_hermes_home()/.env`) so the message always points at the
file the user should actually edit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alt-glitch alt-glitch added type/feature New feature or request comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery area/config Config system, migrations, profiles P3 Low — cosmetic, nice to have labels Jun 2, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for identifying the package-managed versus declarative-config distinction. The premise still holds on current main: packaging/homebrew/hermes-agent.rb:32-37 exports HERMES_MANAGED=homebrew, while hermes_cli/config.py:7140-7142, hermes_cli/setup.py:2709-2712, and tui_gateway/server.py:12932-12941 still treat every managed install as configuration-read-only.

Problems

  • The added classification tests in tests/hermes_cli/test_managed_installs.py:14-34 do not prove the central behavioral claim: that config.yaml and .env writes succeed for Homebrew/Snap while NixOS remains blocked.
  • Current main has a direct TUI config writer at tui_gateway/server.py:1951-1957 that bypasses save_config; salvage should preserve the NixOS config-managed boundary there too.

Suggested changes

  • Add temp-HERMES_HOME persistence tests for save_config and save_env_value under Homebrew/Snap, plus a NixOS rejection regression.
  • Reapply the policy split against current writer paths rather than only the PR's June snapshot. GitHub currently reports this branch as conflicting.

This is an automated hermes-sweeper review.

monkeypatch.setenv("HERMES_MANAGED", "homebrew")

assert get_managed_system() == "Homebrew"
assert is_config_managed() is False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checks only the new predicate. Please add a temp-HERMES_HOME behavioral test that exercises save_config and save_env_value: Homebrew/Snap should persist, while NixOS must still reject the writes.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/install-update Installer, updater, packaging, wheels, doctor labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/install-update Installer, updater, packaging, wheels, doctor comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants