Task 1 is agent-executable repo wiring (flake input, profile module, secret declaration), build-checked with nix build. Tasks 2-5 are an operator-run runbook: OAuth login, Anthropic billing check, Telegram bot setup, and typing real secrets into $EDITOR via sops - none of that is meant to pass through an agent session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbvwg3Qt2u9nbkEA3wL17S
17 KiB
Hermes Agent on mibook Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.Note on nature: Task 1 is repo-only work verifiable with
nix build— no runtime tests exist for declarative config, matching this repo's existing convention (see the Immich plan). Tasks 2–5 are an operator-run runbook, not agent-automatable: Task 2 needs an interactiveclaude setup-tokenOAuth login, an Anthropic billing console check, and a Telegram BotFather chat; Task 3 requires typing real secret material into the$EDITORsops opens; Task 4 runsnixos-rebuildon mibook, the operator's live daily-driver desktop. Never paste secret values (tokens, passwords) into an agent session or chat — Task 3 has the operator type them directly into the editor sops opens. Present Tasks 2–5 for the operator to run and confirm; do not attempt to execute them from an agent session.
Goal: Run Hermes Agent as a hardened NixOS systemd service on mibook, reachable via Telegram and Email, authenticated to Anthropic via a Claude subscription (claude setup-token) rather than a pay-per-token API key.
Architecture: A standard my.profiles.hermes-agent module wraps the upstream services.hermes-agent module (from NousResearch/hermes-agent's nixosModules.default), imported only into mibook's module list. Credentials (Anthropic token, Telegram bot token, email creds) live in one sops secret (hermes-env) consumed via environmentFiles. No network exposure — Telegram long-polling and Email IMAP/SMTP are both outbound-initiated from mibook.
Tech Stack: NixOS (flake-parts), sops-nix, upstream hermes-agent flake (Tier 2/best-effort Nix support), Anthropic Claude (subscription auth).
Spec: docs/superpowers/specs/2026-09-08-hermes-agent-nixos-design.md
Global Constraints
- Machine: mibook only. Do not enable on jupiter.
- Module source: official
github:NousResearch/hermes-agent(nixosModules.default). Not the community0xrsydn/nix-hermes-agentwrapper. - Auth:
claude setup-tokenoutput goes intoANTHROPIC_TOKENin thehermes-envsecret — notANTHROPIC_API_KEY. Verify the running service actually authenticates on this var (Task 4); fall back to a plainANTHROPIC_API_KEYonly if it doesn't. - Billing dependency: the setup-token/OAuth path only draws from Claude Max "extra usage credits" — confirm this pool is funded before Task 2's token generation, or auth will fail even with a valid token.
- Secret: one sops secret named
hermes-env, a full env-file blob (matchesenvironmentFiles— list of paths, not a single-value secret). - No network exposure: do not touch
modules/services/webserveror open any inbound port. Telegram + Email are both outbound-only. - Platforms: Telegram + Email only, first pass. No Discord/Slack/etc.
- Rebuild command:
sudo nixos-rebuild switch --flake '.#mibook'(usenixos-rebuild testfirst, per Task 4). - Build-check command:
nix build '.#nixosConfigurations.mibook.config.system.build.toplevel'. - Format Nix with
nixfmt-rfc-stylebefore committing. - Secret material never enters an agent session, chat, or the git history in plaintext — only via
sops secrets/mibook.yamland$EDITOR. - Base branch:
feat/sops-nix(already pushed; do not merge or rebase it as part of this work).
File Structure
- Modify
flake.nix— add thehermes-agentflake input. - Modify
machines/configuration.nix— destructure the new input; importhermes-agent.nixosModules.defaultinto mibook's module list only. - Create
modules/environments/hermes-agent/default.nix— themy.profiles.hermes-agentmodule (single responsibility: declare Hermes Agent). - Modify
modules/environments/default.nix— add./hermes-agentto the import list. - Modify
machines/mibook/environments.nix— sethermes-agent.enable = true. - Modify
machines/mibook/secrets.nix— replace theplaceholdersecret withhermes-env. - Modify (encrypted, operator-only)
secrets/mibook.yaml— real credential values, never touched by an agent.
No other files change.
Task 1: Wire up the module (flake input, profile module, secret declaration)
Files:
- Modify:
flake.nix - Modify:
machines/configuration.nix - Create:
modules/environments/hermes-agent/default.nix - Modify:
modules/environments/default.nix - Modify:
machines/mibook/environments.nix - Modify:
machines/mibook/secrets.nix
Interfaces:
-
Produces: NixOS option
my.profiles.hermes-agent.enable(bool). When true, configuresservices.hermes-agentwithenvironmentFiles = [ config.sops.secrets."hermes-env".path ]. Declaressops.secrets.hermes-env(owner/grouphermes, path resolves to/run/secrets/hermes-envat runtime). -
Consumes:
hermes-agent.nixosModules.defaultfrom the new flake input; existingsops.secrets.<name>mechanism frommachines/core/sops.nix(already applied to every machine, unaffected by this task). -
Step 1: Add the flake input
In flake.nix, inside inputs = { ... }, add after the sops-nix block:
hermes-agent = {
url = "github:NousResearch/hermes-agent";
inputs.nixpkgs.follows = "nixpkgs";
};
- Step 2: Lock the new input
Run: nix flake lock
Expected: flake.lock gains a hermes-agent entry (and its transitive inputs). No other entries change.
- Step 3: Import the upstream module into mibook only
In machines/configuration.nix, add hermes-agent to the inherit (self.inputs) ... destructure:
inherit (self.inputs)
nixpkgs
nixpkgs-unstable
nixos-hardware
sops-nix
hermes-agent
#hyprland
;
Then change mibook's modules list (leave jupiter's untouched):
mibook = nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit self; };
modules = defaultModules ++ [
# nixos-hardware.nixosModules.mibook
hermes-agent.nixosModules.default
./mibook/configuration.nix
];
};
- Step 4: Read a reference module to match repo style
Read modules/environments/immich/default.nix (same shape: cfg, lib.mkIf, single-purpose options block). Match its formatting.
- Step 5: Create the profile module
Create modules/environments/hermes-agent/default.nix:
# Hermes Agent — Telegram + Email messaging gateway (Claude subscription auth)
{
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";
environmentFiles = [ config.sops.secrets."hermes-env".path ];
addToSystemPackages = true;
};
};
}
- Step 6: Register the module in the environments import list
In modules/environments/default.nix, add ./hermes-agent after ./immich:
./immich
./hermes-agent
];
}
- Step 7: Enable it on mibook
In machines/mibook/environments.nix, inside the my.profiles = { ... } block, add:
hermes-agent.enable = true;
- Step 8: Replace the placeholder secret declaration
In machines/mibook/secrets.nix, replace the whole file body:
# Secrets decrypted on mibook. sopsFile defaults to secrets/mibook.yaml
# via machines/core/sops.nix.
{ ... }:
{
sops.secrets.hermes-env = {
owner = "hermes";
group = "hermes";
mode = "0400";
restartUnits = [ "hermes-agent.service" ]; # confirmed/fixed in Task 4 if the unit name differs
};
}
This removes the placeholder secret declared here — it was an explicit throwaway (comment: "Safe to delete once a real secret is in place"). Its entry in the encrypted secrets/mibook.yaml content itself is cleaned up by the operator in Task 3, not here.
- Step 9: Format
Run: nixfmt-rfc-style modules/environments/hermes-agent/default.nix machines/configuration.nix machines/mibook/secrets.nix machines/mibook/environments.nix flake.nix
- Step 10: Build-check (this is the "test")
Run: nix build '.#nixosConfigurations.mibook.config.system.build.toplevel'
Expected: builds successfully. This succeeds even though secrets/mibook.yaml doesn't yet contain a hermes-env key — sops.validateSopsFiles only checks the file is valid sops-encrypted YAML, not that every declared secret name exists inside it; that check happens at activation (Task 4), not build.
If it fails on an unknown services.hermes-agent.* option, reconcile against the actual module: nix eval --raw '.#nixosConfigurations.mibook.config.system.build.toplevel' errors will name the bad option; cross-check against website/docs/getting-started/nix-setup.md in the NousResearch/hermes-agent repo at the locked revision.
- Step 11: Confirm the option evaluates on
Run: nix eval '.#nixosConfigurations.mibook.config.services.hermes-agent.enable'
Expected: true
- Step 12: Commit
git add flake.nix flake.lock machines/configuration.nix modules/environments/hermes-agent/default.nix modules/environments/default.nix machines/mibook/environments.nix machines/mibook/secrets.nix
git commit -m "feat(mibook): add Hermes Agent profile module"
Task 2: Gather credentials (operator-run, not agent-automatable)
Files: none. All values are held by the operator for Task 3 — do not write them into any file an agent can read, and do not paste them into a chat session.
Interfaces:
-
Produces: an Anthropic setup-token, a Telegram bot token, and email (IMAP/SMTP) credentials — ready to type into
$EDITORin Task 3. -
Step 1: Confirm the billing pool is funded
In the Anthropic console, confirm the Claude Max plan has extra usage credits purchased. The claude setup-token auth path bills against this pool only — it does not draw from the base Max allowance, and Claude Pro cannot use this path at all. If this isn't funded, stop here and decide whether to fund it or fall back to a plain ANTHROPIC_API_KEY (see Global Constraints).
- Step 2: Generate the setup-token
On mibook, run: claude setup-token
This opens an interactive OAuth flow. On completion it prints a long-lived token. Copy it somewhere only you control (e.g. your password manager) — you'll type it into sops in Task 3, not into this session.
- Step 3: Create the Telegram bot
In Telegram, message @BotFather, run /newbot, follow the prompts. Copy the resulting bot token (format 123456:ABC-...).
- Step 4: Gather email credentials
Decide which mailbox Hermes Agent should use, and get its IMAP/SMTP host, username, and password (or app-specific password if the provider requires one, e.g. Gmail). Check website/docs/integrations/ in the NousResearch/hermes-agent repo for the exact env var names the email integration expects (this plan doesn't guess them — confirm the real key names here, not at Task 3, so Task 3 is a straight copy-paste).
Task 3: Populate the secret (operator-run, secret material)
Files:
- Modify (encrypted):
secrets/mibook.yaml
Interfaces:
-
Consumes: the three credentials gathered in Task 2, and the
hermes-envdeclaration from Task 1. -
Produces: a decryptable
hermes-enventry that Task 1'senvironmentFilesreference resolves at activation. -
Step 1: Open the encrypted file
Run: nix develop -c sops secrets/mibook.yaml
This decrypts and opens $EDITOR on the plaintext.
- Step 2: Remove the placeholder key, add
hermes-env
Delete the placeholder: ... entry (Task 1 already removed its Nix declaration; this removes the matching content). Add:
hermes-env: |
ANTHROPIC_TOKEN=<paste the claude setup-token output>
TELEGRAM_BOT_TOKEN=<paste the BotFather token>
<paste the email IMAP/SMTP keys gathered in Task 2, Step 4>
(Exact YAML block-scalar syntax per whatever your $EDITOR shows — sops re-encrypts on save regardless of formatting, as long as it's valid YAML.)
- Step 3: Save and let sops re-encrypt
Save and quit $EDITOR. Run: git diff --stat secrets/mibook.yaml
Expected: the file shows as changed (still fully encrypted — confirm by git diff secrets/mibook.yaml | head and seeing sops's ENC[...] ciphertext, never plaintext).
- Step 4: Stage it
Run: git add secrets/mibook.yaml
Required before the next nix build — untracked sops files are invisible to flake evaluation (repo rule, docs/secrets.md).
- Step 5: Commit
git commit -m "feat(mibook): populate Hermes Agent credentials"
Task 4: Deploy and verify (operator-run, on mibook)
Files: none in repo. Run all commands on mibook.
Interfaces:
-
Consumes: the built config from Task 1, the secret from Task 3.
-
Produces: a running, verified
hermes-agent.servicereachable via Telegram and Email. -
Step 1: Test-activate without persisting
Run: sudo nixos-rebuild test --flake '.#mibook'
Expected: activates without reboot-persisting. If it fails at secret decryption ("hermes-env not found in sops file"), Task 3 wasn't saved/staged correctly — redo Task 3.
- Step 2: Confirm the unit name and watch it start
Run: systemctl list-units | grep -i hermes to get the exact unit name (Task 1's restartUnits = [ "hermes-agent.service" ] was a guess — fix machines/mibook/secrets.nix and re-run nixos-rebuild test if it differs).
Then: journalctl -u hermes-agent.service -f
Expected: starts, reads the env file, and does not crash-loop on auth. If it errors specifically on ANTHROPIC_TOKEN not being recognized, that's the documented-but-unverified case from the spec — try renaming the key to ANTHROPIC_API_KEY in Task 3's secret (note: this switches to pay-per-token billing, defeating the point of the subscription path — treat as a fallback, not silently accept it).
- Step 3: Verify Telegram connects
Send your bot a message from Telegram. Expected: it responds (confirms both the bot token and the LLM auth work end-to-end).
- Step 4: Verify Email connects
Send a test email to the configured mailbox. Expected: Hermes Agent picks it up (check journalctl -u hermes-agent.service for an inbound-message log line) and can send a reply.
- Step 5: Persist across reboot
Run: sudo nixos-rebuild switch --flake '.#mibook'
Expected: same running state as Step 1–4, now persisted.
Task 5: Sign-off (operator-run)
Files: none in repo beyond what Tasks 1 and 3 already committed.
Interfaces:
-
Consumes: a verified running instance (Task 4).
-
Step 1: Open a PR
feat/sops-nix is already pushed to origin but not merged; this work sits on top of it. Push this branch (or continue on feat/sops-nix directly, per your preference) and open a PR per the repo's usual Gitea workflow. Note in the PR description that it depends on feat/sops-nix landing first (or is stacked on it).
- Step 2: Confidence window
Use it for a few days — send yourself Telegram/Email messages periodically, including after mibook has slept and woken up, to see how the accepted availability gap actually behaves in practice.
- Step 3: Rollback (if needed)
my.profiles.hermes-agent.enable = false in machines/mibook/environments.nix, sudo nixos-rebuild switch --flake '.#mibook'. No data migration is involved — this is a clean revert, not a runbook.
Self-Review
Spec coverage:
- Host mibook, not jupiter → Global Constraints + File Structure. ✓
- NixOS native module, official upstream source → Task 1. ✓
- Home Manager rejected → not present anywhere (correctly absent, matches "deliberately deferred" reasoning — no task references it). ✓
claude setup-token→ANTHROPIC_TOKEN, Max extra-credits caveat → Task 2 Steps 1–2, Task 4 Step 2, Global Constraints. ✓hermes-envsingle env-file secret → Task 1 Step 8, Task 3. ✓- No sops prerequisite (mibook already registered) → confirmed by reusing existing
machines/mibook/secrets.nixrather than any.sops.yaml/Phase-9-style bootstrapping. ✓ - No network exposure → Global Constraints; no task touches
modules/services/webserver. ✓ - Telegram + Email only → Task 2 Steps 3–4, Task 4 Steps 3–4. ✓
- Rollback path → Task 5 Step 3. ✓
- Deferred (other platforms, availability-gap workarounds, public exposure) → correctly absent. ✓
Placeholder scan: No bare TBD/TODO. The bracketed values (<paste the claude setup-token output>, etc.) are operator-supplied secret material by design — Task 2 explicitly produces them, Task 3 explicitly consumes them; they're never meant to be concrete in this document since they're credentials, not code.
Type consistency: my.profiles.hermes-agent.enable, services.hermes-agent.*, and the hermes-env secret name are used identically across Tasks 1, 3, and 4. The hermes-agent.service unit name is flagged as unverified in both Task 1 (declaration) and Task 4 (where it's actually checked and corrected if wrong) — consistent, not contradictory.