Files
nixos/docs/superpowers/specs/2026-09-08-hermes-agent-nixos-design.md
T
finn.markwitzandClaude Sonnet 5 6212b4fcf7 docs: design spec for Hermes Agent on mibook
Locks in host (mibook, not jupiter), deployment mode (NixOS native
module), and auth (claude setup-token / ANTHROPIC_TOKEN, Max extra
credits) after a grilling round. Builds on feat/sops-nix as-is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vbvwg3Qt2u9nbkEA3wL17S
2026-09-08 16:56:26 +02:00

7.8 KiB

Hermes Agent on mibook

Date: 2026-09-08 Machine: mibook (laptop, KDE desktop, current daily driver) Status: Design approved, pending implementation plan Base branch: feat/sops-nix (pushed to origin, not yet merged to main)

Goal

Deploy NousResearch/hermes-agent as a NixOS-native systemd service on mibook, reachable via Telegram and Email, using an Anthropic Claude subscription (claude setup-token) rather than a pay-per-token API key. Design + implementation plan only — no execution in this round; a later session applies the plan.

Decisions

Topic Decision
Host mibook, not jupiter. Availability gap (laptop sleeps/lid-closes) is accepted, not engineered around
Use case Messaging gateway (Telegram + Email), not CLI-only
Deployment mode NixOS native module (services.hermes-agent.* from upstream nixosModules.default), wrapped in my.profiles.hermes-agent. Home Manager was considered and rejected — not wired into this flake anywhere; adding it for one service is a new framework dependency, not proportionate
Module source Official github:NousResearch/hermes-agent, not the community 0xrsydn/nix-hermes-agent wrapper
Nix support tier Docs explicitly call Nix/NixOS "Tier 2, best-effort" — Docker/FHS is upstream's primary supported path. Accepted tradeoff, not a blocker
LLM provider auth Anthropic via claude setup-token (long-lived, headless-friendly token) → ANTHROPIC_TOKEN, not ANTHROPIC_API_KEY
Billing caveat Setup-token/OAuth path only draws from a Claude Max "extra usage credits" pool, never the base Max allowance. Claude Pro cannot use this path at all. User intends to use claude setup-token; not independently re-verified that extra credits are purchased — confirm in Anthropic console before/during implementation
Secrets One sops secret, hermes-env, containing a full env-file blob (matches the module's environmentFiles list-of-paths interface — not a single-value secret)
sops prerequisite None. mibook is already a registered sops recipient (host_mibook in .sops.yaml, secrets/mibook.yaml exists, proven via a live nixos-rebuild switch in the sops-nix work). No "Phase 9"-style bootstrapping needed — that deferred work is about jupiter, irrelevant now that mibook is the target
Network exposure None. Telegram (long-polling) and Email (IMAP/SMTP) are both outbound-initiated from mibook. No Caddy/webserver module changes, no public endpoint
Messaging platforms Telegram + Email only, first pass. Hermes Agent supports 27+ platforms total (Discord, Slack, WhatsApp, Matrix, etc.) — others deliberately out of scope, same pattern applies later if wanted

Deliberately deferred (YAGNI)

  • Any workaround for mibook's availability gap (cloud relay, wake scheduling, HA failover to jupiter)
  • Discord, Slack, or any other messaging platform beyond Telegram + Email
  • Public/internet exposure of any kind
  • Merging or rebasing feat/sops-nix — this design builds on top of it as-is

Part 1 — The module

New flake input in flake.nix, following the exact shape sops-nix already uses:

hermes-agent = {
  url = "github:NousResearch/hermes-agent";
  inputs.nixpkgs.follows = "nixpkgs";
};

Import the upstream module only into mibook's module list, not baseModules — this is a single-host service, unlike sops-nix.nixosModules.sops which every machine needs. In machines/configuration.nix, mibook's modules = defaultModules ++ [ hermes-agent.nixosModules.default ./mibook/configuration.nix ] (exact insertion point to be confirmed against the file at implementation time — defaultModules is currently machine-agnostic, so this is a targeted, minimal deviation from that shared list, not a restructuring of it).

New file modules/environments/hermes-agent/default.nix following the repo's standard profile pattern:

{ config, lib, pkgs, ... }:
let
  cfg = config.my.profiles.hermes-agent;
in
{
  options.my.profiles.hermes-agent.enable = lib.mkEnableOption "Hermes Agent (Telegram + Email gateway)";

  config = lib.mkIf cfg.enable {
    services.hermes-agent = {
      enable = true;
      settings.model.default = "anthropic/claude-sonnet-4-5"; # confirm exact model string at implementation
      environmentFiles = [ config.sops.secrets."hermes-env".path ];
      addToSystemPackages = true;
    };
  };
}

Add ./environments/hermes-agent to modules/environments/default.nix; enable my.profiles.hermes-agent.enable = true in machines/mibook/environments.nix.

Provided for free by the upstream module: hermes system user/group (createUser default true), hardened systemd unit (NoNewPrivileges, ProtectSystem=strict, PrivateTmp — not independently user-configurable per the docs), hermes CLI on $PATH via addToSystemPackages.

Part 2 — Secret

Add to machines/mibook/secrets.nix (existing file, per docs/secrets.md):

sops.secrets.hermes-env = {
  owner = "hermes";
  group = "hermes";
  mode = "0400";
  restartUnits = [ "hermes-agent.service" ]; # confirm exact unit name at implementation
};

Populate via nix develop -c sops secrets/mibook.yaml, adding a multi-line hermes-env block:

ANTHROPIC_TOKEN=<output of `claude setup-token`>
TELEGRAM_BOT_TOKEN=<from @BotFather>
<IMAP/SMTP host, user, password or app-password — exact keys per Hermes' email integration doc>

Verify at implementation, not assumed: the module's own documented example uses ANTHROPIC_API_KEY; the setup-token path is documented separately (and thinly) as ANTHROPIC_TOKEN. Confirm the running module actually picks up ANTHROPIC_TOKEN before treating auth as working — if it doesn't, the fallback is a plain ANTHROPIC_API_KEY (pay-per-token, loses the subscription framing this design was built around).

Must git add secrets/mibook.yaml before nix build — untracked files are invisible to flake evaluation (existing repo rule, docs/secrets.md).

Part 3 — Rollout runbook (on mibook)

  1. Confirm Claude Max extra-usage-credits are actually purchased (Anthropic console) — the whole auth design depends on this pool being funded.
  2. Run claude setup-token on mibook; capture the token.
  3. Create/gather Telegram bot token (@BotFather) and email credentials.
  4. Add the hermes-agent flake input; add the module wrapper file; wire it into modules/environments/default.nix and machines/mibook/environments.nix.
  5. Add sops.secrets.hermes-env to machines/mibook/secrets.nix.
  6. nix develop -c sops secrets/mibook.yaml, populate hermes-env, git add.
  7. nix build '.#nixosConfigurations.mibook.config.system.build.toplevel' — CI-style check before touching the running system.
  8. sudo nixos-rebuild test --flake '.#mibook' — verify before persisting across reboot.
  9. journalctl -u hermes-agent.service -f — confirm it starts, decrypts the secret, connects to Telegram (and doesn't loop-crash on the ANTHROPIC_TOKEN question from Part 2).
  10. Send a test Telegram message; send a test email; confirm both round-trip.
  11. sudo nixos-rebuild switch --flake '.#mibook' once verified.

Rollback

my.profiles.hermes-agent.enable = false, nixos-rebuild switch. No data migration involved (no existing deployment to preserve), so rollback is a one-line revert, not a runbook.

Open risks (carried forward, not resolved by design)

  1. ANTHROPIC_TOKEN vs ANTHROPIC_API_KEY — thin upstream docs, needs a real test (Part 2).
  2. Extra-credits billing pool funding — not independently verified, user-asserted intent (Part 3, step 1).
  3. Tier 2 Nix support — expect some manual troubleshooting; upstream doesn't guarantee the flake stays working across their commits.
  4. Availability gap — accepted by design, not a bug to fix later unless priorities change.