Hermes — Totem Oversight Agent · Setup Guide
Building the read-only spec-original stack on the ThinkPad — deterministic gate, daily heartbeat, thin Telegram bridge.
Updated 2026-06-15
Spec of record: projects/totem-orchestration/system-refactor-2026-06/design/hermes-agent-spec.md
(vault-relative; /home/jonny/Documents/totem-terminal/... on the ThinkPad). Every constraint below traces to that spec or to the verified research dossier (2026-06-09).
1. Decision summary
Build the spec-original stack (Path A). Do not run Nous Research Hermes as the runtime. All three judges converged on this (scores 9.5 / 8 / 9 for Path A vs 3–4.5 for adopting Nous Hermes wholesale):
- What you build: (1)
hermes-review.py— a deterministic Python gate (no LLM) for the Fireflies AUTO-POST/HOLD/DROP decision; (2) a daily heartbeat digest as a read-onlyclaude -pscheduler task; (3) a thinhermes-bridge.py— Telegram long-poll that shells every message out toclaude -pwith a hard-coded read-onlyallowedToolslist, and enqueues any command into the existing scheduler instead of executing it. - Why: the spec's safety model is structural — "the allowlist is the gate" (§Enforcement).
claude -penforcesallowedToolsin the harness, so the NEVER list (mem0 writes, InfraNodus mutation, vault edits) is unreachable by construction. Nous Hermes inverts this posture: its default Telegram toolset is identical to its CLI toolset (full shell, file write, browser — verified against its toolsets reference), its safety gate is a conversational approval prompt on the same channel an injection would ride, its own SECURITY.md states "nothing inside the agent process constitutes containment — not any tool allowlist," and it autonomously writes memory to~/.hermes/memories/, which violates the spec's promote-don't-commit rule. Whether it supports a strict per-tool deny-list remains an open unknown. - What happens to the downloaded Nous Hermes: it stays on the ThinkPad as a sandbox toy. Do not point it at the vault, do not give it the Telegram bot token. If its docs later confirm a deny-by-default per-tool config, revisit it as a transport layer only (judge consensus: Path C is the legitimate future upgrade, gated on that verification).
- Model split (judge consensus):
- Gate: deterministic Python, no model. $0.
- Heartbeat digest: one
claude -prun/day on the Max subscription (Haiku/Sonnet class). $0 marginal. - Telegram Q&A:
claude -pon Max, default CLI model (Sonnet 4.6 class for tool discipline). $0 marginal. - Heavy jobs (SBPI, site builds, dkr work): unchanged — existing scheduler tasks on Max. Hermes only reads their results.
- BYOK hybrid: one spend-capped Anthropic API key (
claude-haiku-4-5, $1/$5 per MTok) kept in the bridge's.envas a fallback if Max quota contention or auth decay ever bites. Estimated total incremental cost: $0–5/month.
- Build order: gate + digest first (Phase 1, spec §7), bridge second (Phase 2). Ship the thing worth querying before building the remote.
- donePhase 0 (gate):
hermes-review.pyis built and live in the Fireflies pipeline (system/agents/claude/scheduler/fireflies/, 226 lines). - builtPhase 1 (Mac runtime):
system/agents/claude/hermes/hermes-claude.sh(read-only wrapper),append-alert.sh(the only permitted vault write, tested against a scratch daily note), andhermes-heartbeat.sh(digest orchestrator) exist; thehermes-heartbeatscheduler task is registered (daily 07:30, script type). Remaining before relying on it: run the two wrapper smoke tests (§2.3) in a plain terminal — they cannot run nested inside a Claude Code session. - pendingPhase 2 (Telegram bridge, ThinkPad): BotFather bot,
hermes-bridge.py, systemd unit. - pendingPhase 3 (workflow wiring).
2. Part 1: ThinkPad setup (Omarchy / Arch)
2.1 Preconditions
- Confirm the vault syncs at
/home/jonny/Documents/totem-terminaland is onmaster(thecom.totem.terminal-syncjob fixed 2026-06-04 handles the Mac side; verify the ThinkPad pulls cleanly):cd /home/jonny/Documents/totem-terminal && git pull && git status - Confirm Claude Code CLI is installed and authenticated with the Max subscription OAuth login (
claude→/loginif needed). This is what makesclaude -pbill $0 marginal. - Auth footgun: if
ANTHROPIC_API_KEYis exported anywhere in the shell profile,claude -pmay silently bill the API account instead of the subscription (reported in the models research; unverified — see checklist §5). Defensive rule regardless: the wrapper script below runsenv -u ANTHROPIC_API_KEY. - Check MCP availability on the ThinkPad: mem0 (localhost:8765 Docker) and InfraNodus MCP are configured on the Mac; whether they exist on the ThinkPad is unconfirmed. The allowlist grants their search tools, and if the servers are absent the tools simply don't resolve — Hermes degrades to vault-files-only, which is acceptable. Do not install Docker/mem0 on the ThinkPad just for this.
2.2 Quarantine the downloaded Nous Hermes
You downloaded Nous Hermes; which artifact (curl-installer CLI vs desktop app) is unconfirmed. Either way:
hermes gateway status # if the CLI is installed
hermes gateway stop # ensure no gateway daemon is running
systemctl --user list-units | grep -i hermes-gateway # confirm no serviceDo not run hermes gateway setup, do not put TELEGRAM_BOT_TOKEN in ~/.hermes/.env, never set GATEWAY_ALLOW_ALL_USERS=true. If you want to experiment with it, do so in a directory that is not the vault, with a BYOK key (note: its Anthropic OAuth path requires "Max + extra usage credits" — verified in its quickstart docs — so plain Max does not cover its native loop; an API key would be pay-per-token).
2.3 The allowlist wrapper (the load-bearing piece)
Create system/agents/claude/hermes/hermes-claude.sh in the vault. Every Hermes LLM invocation — bridge and heartbeat alike — goes through this one script so the allowlist cannot drift (judge risk #2):
#!/usr/bin/env bash
# Hermes read-only claude -p wrapper. The allowlist IS the gate (spec §Enforcement).
set -euo pipefail
VAULT="$HOME/Documents/totem-terminal"
cd "$VAULT"
exec env -u ANTHROPIC_API_KEY claude -p "$1" \
--allowedTools "Read,Grep,Glob,Bash(git log *),mcp__openmemory__search_memory,mcp__openmemory__list_memories,mcp__infranodus__search"chmod +x system/agents/claude/hermes/hermes-claude.sh
git add system/agents/claude/hermes/ && git commit -m "hermes: read-only claude -p wrapper"The grant set is verbatim from spec §Enforcement. Nothing else — no Write, no Edit, no general Bash, no mcp__openmemory__add_memories, no mcp__infranodus__memory_add_relations, no broad permissionMode.
Smoke test (run both):
system/agents/claude/hermes/hermes-claude.sh "Read system/agents/claude/scheduler/registry.json and report each task's last_run status in one line each."
system/agents/claude/hermes/hermes-claude.sh "Create a file /tmp/hermes-test.txt containing 'x'."
# Expected: first returns a status summary; second must FAIL to write — the harness
# rejects un-granted tools. If /tmp/hermes-test.txt exists afterward, STOP: the
# allowlist is not being applied. Do not proceed to the bridge.2.4 The deterministic gate — hermes-review.py (Phase 1, no LLM)
Build at system/agents/claude/scheduler/fireflies/hermes-review.py per spec §2/§7.1. Have a normal (interactive) Claude Code session write it — this is doer work, outside Hermes. Contract to implement, no inventions beyond the spec:
- Input: an artifact descriptor (destination, body, named entities).
- Output:
AUTO-POST | HOLD | DROP+ reason; verdict appended tosystem/agents/claude/scheduler/fireflies/manifest.json(idempotent — DROP on duplicateprocessed_ids). - Hard HOLD triggers, all deterministic string/route checks: any external destination (Slack channel, client, Google Doc share); Fiserv-named content (route note: → Nuri); Slack mrkdwn violations (single-asterisk bold, angle-bracketed URLs — reuse the
slack-format-checkrules); pronoun/brand law violations (Limore = he/him; ShurAI + Shur Creative Partners only). - No LLM in v1. An optional anti-slop scoring pass via the wrapper (Haiku-class) can be added later; it advises, the rules decide.
2.5 The Alerts append helper
The daily-note ## Alerts block is Hermes's only permitted vault write. Per judge risk #3, the LLM never edits the note — a small deterministic script does:
Create system/agents/claude/hermes/append-alert.sh: takes stdin text, locates today's daily/YYYY/YYYY-MM-DD.md, appends under ## Alerts (creating neither duplicate daily notes nor new sections), commits nothing else. Have Claude Code write it; test it against a scratch copy of a daily note first.
2.6 systemd user service for the bridge (built in Part 2)
loginctl enable-linger jonny # keep user services alive without a login session
mkdir -p ~/.config/systemd/user~/.config/systemd/user/hermes-bridge.service:
[Unit]
Description=Hermes Telegram bridge (read-only oversight)
After=network-online.target
[Service]
ExecStart=/usr/bin/python3 %h/Documents/totem-terminal/system/agents/claude/hermes/hermes-bridge.py
Restart=always
RestartSec=10
EnvironmentFile=%h/.config/hermes-bridge/.env
[Install]
WantedBy=default.targetmkdir -p ~/.config/hermes-bridge && chmod 700 ~/.config/hermes-bridge
# .env created in Part 2; chmod 600 it.
systemctl --user daemon-reload
# enable + start after Part 2:
systemctl --user enable --now hermes-bridge
journalctl --user -u hermes-bridge -fKeep the bridge token/env outside the vault (~/.config/hermes-bridge/.env) so secrets never sync or commit.
3. Part 2: Telegram access
3.1 Create the bot (verified flow)
- In Telegram, open
t.me/BotFather→/newbot→ display name (e.g. "Totem Hermes") → username ending inbot(e.g.totem_hermes_bot). - BotFather returns a token like
123456789:ABC.... If it ever leaks:/revokein BotFather. - Get your numeric user ID: DM @userinfobot.
- Write both to
~/.config/hermes-bridge/.env,chmod 600:TELEGRAM_BOT_TOKEN=... TELEGRAM_ALLOWED_USER=<your numeric ID> # optional fallback, spend-capped key created in the Anthropic console: # ANTHROPIC_FALLBACK_API_KEY=...
3.1a Third-Party AI keys (fallback only)
You do not need a paid API key to run Hermes. The gate uses no model ($0). The heartbeat digest and Telegram Q&A run through claude -p on your Max subscription at $0 marginal cost. A third-party key is a fallback — used only if Max quota contention or headless OAuth decay on the ThinkPad ever interrupts the subscription path. Set one up if you want the bridge to degrade gracefully instead of going silent.
Two options. Either, neither, or both can live in ~/.config/hermes-bridge/.env.
Option 1 — Anthropic API key (closest match to the Max model family)
- Go to console.anthropic.com → sign in → Settings → API Keys → Create Key. Name it
hermes-bridge-fallbackso it is revocable in isolation. - Cap the spend before you use it (this is the point of a fallback key — it must never run away): Settings → Billing → Usage limits, set a low monthly cap (e.g. $5). If your org uses Workspaces, create a dedicated
hermesWorkspace and set its budget so a leaked key can't touch your main balance. - Default model
claude-haiku-4-5($1 / $5 per MTok in/out) — more than enough for a job-health digest or a one-paragraph Telegram answer. - Store it as
ANTHROPIC_FALLBACK_API_KEY=sk-ant-...in~/.config/hermes-bridge/.env(chmod 600). The bridge reads it only when the Max path fails.
never export ANTHROPIC_API_KEY=... in ~/.bashrc / ~/.zshrc / ~/.profile. A globally exported key makes claude -p silently bill the API account instead of your Max subscription — turning "$0 marginal" into per-token charges. The wrapper's env -u ANTHROPIC_API_KEY (§2.3) defends the heartbeat, but a global export still poisons every other claude -p you run. Keep keys only in the bridge's .env, named ANTHROPIC_FALLBACK_API_KEY (not the magic ANTHROPIC_API_KEY name), and have the bridge pass it explicitly to the fallback call.
Option 2 — OpenRouter key (cheapest fallback; native Nous Hermes models)
- Go to openrouter.ai → sign in → Keys → Create Key. Name it
hermes-bridge. - Set a credit limit on the key (OpenRouter lets you cap per-key spend at creation — do it).
- Pre-load a few dollars of credit (OpenRouter is prepaid). Models:
nousresearch/hermes-4-70b(~$0.13 / $0.40 per MTok) ornousresearch/hermes-4-405b(~$1.00 / $3.00), 131K context — cheap enough that a capped key is effectively unspendable on digest-sized traffic. - Store as
OPENROUTER_API_KEY=sk-or-...in the same.env. The bridge calls OpenRouter's OpenAI-compatible endpoint (https://openrouter.ai/api/v1/chat/completions) as a last-resort path. Note: an OpenRouter answer comes from a Hermes-4 model, not Claude — fine for a status readout, weaker on strict tool discipline, so keep it as the last fallback rung, after Anthropic-Haiku.
Key hygiene (applies to every key):
- Live outside the vault (
~/.config/hermes-bridge/.env,chmod 600) so secrets never sync or get committed. Confirm.envis not under any git-tracked path. - Spend cap is mandatory, not optional — a fallback key with no cap defeats the "$0–5/month" guarantee the whole design rests on.
- Rotate on any suspicion: Anthropic console → delete key; OpenRouter → disable key; both take effect immediately.
- The bridge logs which path served each reply (Max / Anthropic-fallback / OpenRouter) so a silent failover to a paid path is visible in the ops-log, not a surprise on a bill.
3.2 Build hermes-bridge.py
Location: system/agents/claude/hermes/hermes-bridge.py. Have Claude Code write it against this contract (patterns harvested from the dormant sc_telegram_bots per spec §4 Option A — long-poll loop, direct reply_text):
- Long-poll via
python-telegram-bot(v20.x is already proven insc_telegram_bots). - Auth: default-deny. Drop any update whose
from.id != TELEGRAM_ALLOWED_USER. No pairing flow, no group handling in v1. - Queries: send an immediate "working…" ack (a
claude -prun takes 30–120 s and otherwise reads as a dead bot), thensubprocess.run([".../hermes-claude.sh", msg], timeout=300), reply with stdout. On error, reply with the literal error text — never swallow it. - Commands ("release the MicroCo draft", "run dkr pulse"): the bridge does not execute. It writes a one-off task into
system/agents/claude/scheduler/registry.json(same shape as the existingrun_oncemicroco job) and replies "queued — runs under the scheduler's permission preset; result in the next heartbeat." This is the spec §4 composite: B for conversation, C for actions. A Telegram message never gets inline write/deploy power. - Output hygiene: chunk replies at 4,000 chars (Telegram caps at 4,096); send plain text, no
parse_mode, in v1. - Frozen feature set: query, enqueue, release. Any new verb becomes a scheduler job — enforced by the allowlist, never by discipline.
Then systemctl --user enable --now hermes-bridge and message the bot from your phone: "what's in today's heartbeat?"
3.3 Other devices
Telegram itself is the multi-device layer: phone, iPad, Telegram Web, and any other laptop all converse with the same bot DM. Zero per-device setup. There is no second channel in v1 — Telegram only, per the spec.
3.4 The read-only constraint from chat
Restated because it is the whole design: every message — including a prompt-injected forwarded document or URL — lands in a claude -p process that structurally has no write tools. Injection can at worst produce a wrong answer or a queued task that still runs under the scheduler's existing per-task permission preset. This is the property the judges scored Path A on; do not "temporarily" widen the wrapper's allowlist for convenience.
4. Part 3: Workflow integration
4.1 Daily heartbeat digest (Capability B)
Register a new scheduler task hermes-heartbeat (daily, e.g. 07:30) on the Mac scheduler — the spec (§7.2) reuses the existing launchd executor, and the Mac is where registry.json, results dirs, and the dkr log already live. The task:
- Runs the read-only wrapper (the Mac needs its own copy of
hermes-claude.sh— same file, vault-synced, identical allowlist) with a self-contained prompt: readsystem/agents/claude/scheduler/registry.json(each task'slast_run.timestamp,exit_code,status— surface thestatus: error+exit_code: 0drift the spec flags), the latestsystem/agents/claude/scheduler/results/<task>/<date>/*.md,~/Library/Logs/totem-recipes/dkr-nightly.log, the newestprojects/dkr-agency/briefs/*-connection-brief.md, andscheduler/fireflies/manifest.json. - Emits the digest block in the spec's format (
### Hermes heartbeat — <date>: jobs run N/M, held-for-release count, DKR brief synthesized or not, the single "Needs you" action — plus anycandidate insight: <X> — promote?lines per the promote-don't-commit rule). - Pipes that text through
append-alert.shinto today's daily note## Alerts. The script writes; the model only produces text. - Includes a liveness line for the ThinkPad bridge (e.g. checks a
last-polltimestamp file the bridge touches, synced via the vault) so a dead bridge surfaces within one day.
If you later move it to the ThinkPad (systemd timer), remove the Mac task — duplicate appends to ## Alerts are worse than either home.
4.2 Fireflies HOLD-queue review
hermes-review.pyruns as part of the Fireflies pipeline; every verdict lands inmanifest.json.- HOLDs appear as one-line review items in the heartbeat digest ("Held for release: 2 client Slack drafts, 1 Fiserv note → route to Nuri").
- Release flow from anywhere: Telegram → "release <item>" → bridge enqueues a scheduler task → the scheduler's existing permission preset does the send → result reported in the next heartbeat or as a Telegram follow-up. External sends thus always pass a human moment, satisfying the
dont_share_means_no_permissionsandno_slack_draftsrules.
4.3 dkr-pulse triggering
dkr-nightly keeps producing connection briefs mechanically; the digest flags any brief sitting un-synthesized ("DKR brief: produced, NOT yet synthesized — run /dkr-pulse"). From Telegram, "run dkr pulse" enqueues a one-off scheduler task that executes the dkr-pulse skill as a doer job. Hermes never runs the synthesis itself — it nags and enqueues.
4.4 Coexistence with the Mac-side scheduler
- The Mac launchd scheduler remains the only executor of doer jobs (SBPI, site builds, Slack posts, dkr-pulse). Nothing moves.
- The ThinkPad runs exactly one new daemon:
hermes-bridge.service. - The vault + git sync is the data plane between them. Consequence: a Telegram answer reflects the last sync, so state can lag by one sync interval; the bridge should run
git pull(read-only, safe) before each query if staleness becomes noticeable. - Hermes reads scheduler state; it never edits
registry.jsonexcept to append one-off enqueue entries (the bridge's single, narrow, git-versioned write — log each enqueue to the optional ops-log atsystem/agents/claude/hermes/ops-log/YYYY-MM-DD.md).
4.5 First-week adoption plan
- Day 1–2 (Phase 1): write + commit
hermes-claude.sh,append-alert.sh,hermes-review.py; run the two smoke tests; registerhermes-heartbeat; verify the first digest lands in## Alertsand the gate writes verdicts tomanifest.json. - Day 3–4 (Phase 2): BotFather bot; build
hermes-bridge.py; systemd unit; first phone query against the live digest. - Day 5: test the enqueue path end-to-end with a harmless one-off task (e.g. re-run dkr-pulse); confirm the result shows in the next heartbeat.
- Day 6–7: live use — read the heartbeat each morning instead of opening the scheduler dirs; release one real HOLD from the phone; re-run the write-rejection smoke test once more after any wrapper edit. Log lessons to
system/lessons-learned/. - Habit anchor: the digest's "Needs you:" line is the daily entry point; if it stays accurate for a week, Hermes has replaced the manual morning scheduler check.
5. Open questions & verification checklist
Items the research could not verify. Confirm during setup; none block Phase 1.
Billing / auth
- Confirm whether the reported 2026-06-15 change (Agent SDK /
claude -pusage moving to a separate monthly credit on Max plans instead of plan limits) is real — check Anthropic's help center. The Path A design works either way; this only affects quota accounting. - Confirm the
ANTHROPIC_API_KEYsilent-API-billing footgun against currentclaude -pdocs (the wrapper'senv -umakes this moot, but verify before ever exporting a key globally). - Verify Max OAuth works headless on the ThinkPad and note the token-expiry behavior; the heartbeat's bridge-liveness line is the detection net.
ThinkPad environment
- Are the mem0 (localhost:8765) and InfraNodus MCP servers configured/reachable on the ThinkPad? If absent, accept vault-files-only answers from the bridge (no action needed).
- Confirm
python-telegram-botinstalls cleanly on Omarchy (pacman/pip) and the version matches thesc_telegram_botspatterns being harvested. - Confirm which Nous Hermes artifact was actually downloaded (curl-installer CLI vs desktop app) and that no
hermes-gatewaysystemd unit is enabled:systemctl --user list-unit-files | grep hermes.
Nous Hermes (only relevant if Path C is ever revisited — all are hard preconditions)
- Read
website/docs/reference/toolsets-reference.mdanduser-guide/security.mdin the repo: does a strict per-tool deny-by-default allowlist exist (equivalent toallowedTools)? Specifically, canread_filebe enabled withwrite_file/patch disabled inside thefiletoolset? (Thehermes toolscurses UI claims tool-level granularity per platform — verify by running it.) - Verify the exact
platform_toolsetsYAML thathermes toolswrites to config.yaml. - Can the approval-from-chat flow be disabled per-platform so Telegram can never approve a dangerous command?
- Does
hermes gateway installwrite the user unit to~/.config/systemd/user/hermes-gateway.service? (Path inferred from convention, unconfirmed.) - Nous Portal pricing tiers / whether Portal proxies Claude-class models — only relevant to billing comparisons, currently moot.
- AUR package details (
yay -Si hermes-agent) and gateway idle resource footprint /--skip-browsereffects — moot unless adopted. - Pin the version before any adoption;
hermes updatecan change default toolsets silently.
6. Sources
- Totem Hermes spec (authoritative):
/Users/jonnydubowsky/Documents/totem-terminal/projects/totem-orchestration/system-refactor-2026-06/design/hermes-agent-spec.md - Nous Hermes repo: https://github.com/NousResearch/hermes-agent (MIT, v0.16.0 / tag v2026.6.5, 2026-06-06)
- Nous Hermes docs: https://hermes-agent.nousresearch.com/docs/ — installation, configuration,
user-guide/messaging/telegram,user-guide/security,reference/toolsets-reference - Nous Hermes trust model: https://github.com/NousResearch/hermes-agent/blob/main/SECURITY.md ("the only security boundary against an adversarial LLM is the operating system")
- Nous Hermes providers/quickstart (Anthropic OAuth = "Max + extra usage credits"): https://raw.githubusercontent.com/NousResearch/hermes-agent/main/website/docs/getting-started/quickstart.md and
.../integrations/providers.md - Telegram Bot API changelog (Private Chat Topics, Bot API 9.3/9.4): https://core.telegram.org/bots/api-changelog
- Anthropic pricing (Opus 4.8 $5/$25, Sonnet 4.6 $3/$15, Haiku 4.5 $1/$5 per MTok): https://claude.com/pricing and the claude-api skill (cached 2026-05-26)
- Hermes 4 70B/405B pricing ($0.13/$0.40 and $1.00/$3.00, 131K ctx): https://openrouter.ai/nousresearch/hermes-4-70b , https://openrouter.ai/nousresearch/hermes-4-405b
- Nous Portal tiers: https://openclawlaunch.com/guides/nous-portal , https://portal.nousresearch.com
- Existing vault infrastructure referenced throughout:
system/agents/claude/scheduler/registry.json,system/agents/claude/scheduler/fireflies/manifest.json,system/scripts/dkr-nightly.sh,projects/dkr-agency/briefs/