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
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# 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](https://github.com/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:
|
||||
|
||||
```nix
|
||||
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:
|
||||
|
||||
```nix
|
||||
{ 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`):
|
||||
|
||||
```nix
|
||||
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.
|
||||
Reference in New Issue
Block a user