- .agents/OIKOS.md: rewrote entire Build Status section from Python 30-day roadmap to Go Phases 1-6 status. Added Python-era backlog preservation. - knowledge/wiki/containers/105-apps.md: added DEPRECATED notices for homelab-mcp and secrets-issuance services, pointing to Go equivalents and cutover checklist. - knowledge/wiki/infrastructure/auto-deploy.md: marked webhook ids 10+11 as deprecated, replaced Go Docker stack. - knowledge/wiki/infrastructure/index.md: noted topology gen as Python with Go DB-native replacement planned. - .agents/operations/hermes-agent.md: updated MCP references from FastMCP SSE Python to Streamable HTTP Go SDK. - .agents/shared/writing-style.md: updated MCP reference, topology note. - .agents/domains/knowledge/schema.md: updated MCP server reference.
9.6 KiB
Hermes agent — Nous-Hermes-powered Goose sessions on a homelab client
Onboards Nous Research's Hermes (a fine-tuned Llama variant) as a working terminal agent on a homelab client. Builds on top of standard client enrollment (see agent-enrollment.md) — this page covers only the Hermes-specific additions.
The agent runs as a Goose session. Goose provides:
- The chat loop, multi-turn history, and streaming
- The OpenRouter provider that routes to Nous Hermes
- The built-in
developerextension (shell + file editor — same surface Claude Code has) - A remote MCP extension pointed at
mcp.hubris.networkfor read-only homelab context (list_lxcs,tail_log,search_docs, etc.)
The persona is /opt/homelab-context/HERMES.md, symlinked as Goose's global
.goosehints so it's injected into the system prompt on every session.
Prerequisites
| Requirement | How |
|---|---|
Standard enrollment complete (homelab whoami works) |
agent-enrollment.md |
secrets/openrouter-api-key.yaml exists with a real sk-or-... value |
See "Seeding the OpenRouter key" below |
The host's age_pubkey is on the openrouter-api-key.yaml sops rule |
homelab client add <host> --finalize-pubkey <age1...> --with-hermes |
Onboarding flow
# 1. On hubris (or any enrolled client): reserve the inventory entry.
homelab client add new-machine
# 2. Join new-machine to Netbird (setup-key or OIDC).
# 3. On new-machine: bootstrap with --with-hermes.
TOKEN=... # gitea PAT, read:repository
curl -fsSL -u "dtoro:$TOKEN" \
https://git.hubris.network/dtoro/Homelab-Docs/raw/branch/main/bootstrap.sh \
-o /tmp/bootstrap.sh
sudo HOMELAB_GITEA_TOKEN=$TOKEN bash /tmp/bootstrap.sh --with-mcp --with-hermes
# 4. Back on hubris: finalize the age pubkey AND grant the Hermes secret.
homelab client add new-machine \
--finalize-pubkey age1... \
--with-hermes
# 5. Wait ≤5 min for sync, then on new-machine:
hermes "what LXCs are running?"
The bootstrap --with-hermes flag does five things, all idempotent:
- Downloads the latest Goose binary into the operator's
~/.local/bin/goose(upstream installer) and symlinks/usr/local/bin/gooseto it. - Symlinks
/opt/homelab-context/bin/hermes→/usr/local/bin/hermes. - Symlinks
/opt/homelab-context/HERMES.md→/root/HERMES.md(Linux) or/etc/HERMES.md(macOS) forcat-as-operator convenience. - Drops
~/.config/goose/config.yamlpinning the provider, model, and extensions (preserves any keys the operator added by hand). - Symlinks
~/.config/goose/.goosehints→ HERMES.md, so the persona is injected as the system prompt on every session.
Seeding the OpenRouter key
The first time anyone enrolls with --with-hermes, the encrypted file
secrets/openrouter-api-key.yaml contains a placeholder. On hubris (or any
existing recipient):
sops secrets/openrouter-api-key.yaml
# editor opens; replace api_key value with the real sk-or-... key, save, close.
git -C /opt/homelab-context add secrets/openrouter-api-key.yaml
git -C /opt/homelab-context commit -m 'openrouter-api-key: seed real key'
git -C /opt/homelab-context push
Until this step happens, hermes … exits with openrouter-api-key.yaml still contains the placeholder. Subsequent enrollees get the real key automatically
via --with-hermes (which adds them as a sops recipient on
secrets/openrouter-api-key.yaml).
Granting the OpenRouter key to an already-enrolled host
If a host was enrolled without --with-hermes and you want to add it later:
# On hubris:
PUBKEY=$(homelab whoami --hostname <host> | grep age_pubkey | awk '{print $2}')
homelab client add <host> --finalize-pubkey "$PUBKEY" --with-hermes
--finalize-pubkey is required by the existing flow even when the pubkey is
unchanged — it's also the trigger that runs the sops grant.
After ≤5 min sync the host can decrypt the key. Bootstrap doesn't need to re-run; only the secret recipient list changed.
Verifying
homelab whoami # standard enrollment OK
homelab secret openrouter-api-key | head -c 8 # decrypts (prints `api_key:`)
which goose && which hermes # binaries present
goose info -v # provider/model wiring sane
hermes "what LXCs are running?" # interactive Goose session
# Non-interactive smoke test:
echo "List the homelab MCP tools you have available" | hermes
Configuration
The bootstrap-managed keys in ~/.config/goose/config.yaml:
GOOSE_PROVIDER: openrouter
GOOSE_MODEL: deepseek/deepseek-v4-flash
GOOSE_MODE: smart_approve # asks before destructive tool calls
extensions:
developer:
type: builtin
bundled: true
enabled: true
name: developer
timeout: 300
homelab:
type: streamable_http
enabled: true
name: homelab
uri: https://mcp.hubris.network/mcp
timeout: 60
Override via env on a single bootstrap run:
HOMELAB_HERMES_MODEL=nousresearch/hermes-3-llama-3.1-405b \
HOMELAB_HERMES_MCP_URI=https://mcp.hubris.network/mcp \
sudo bash /tmp/bootstrap.sh --with-hermes
Any keys you add by hand (e.g. GOOSE_TEMPERATURE, extra extensions.*) are
preserved across re-bootstraps — the merge only overwrites the keys it manages.
Tool permissions
GOOSE_MODE: smart_approve is the bootstrap default: Goose runs read-only
shell commands without prompting and asks for confirmation before destructive
ones. To make the agent fully unattended (e.g. for scheduled jobs), set
GOOSE_MODE: auto in ~/.config/goose/config.yaml. To require confirmation on
every tool call, use approve. See
goose-permissions.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
hermes: could not decrypt secrets/openrouter-api-key.yaml |
Host isn't a recipient on the sops rule | homelab client add <host> --finalize-pubkey <age1...> --with-hermes from hubris |
hermes: openrouter-api-key.yaml still contains the placeholder |
No real key has been seeded yet | See "Seeding the OpenRouter key" above |
Goose hangs on first hermes invocation with no output |
Goose's interactive configure ran on first launch and is awaiting input |
Re-run; the installer is supposed to skip it (CONFIGURE=false). If it persists, run goose configure once manually in a real terminal to commit the config. |
homelab extension fails to connect / no MCP tools listed |
MCP server upgraded in Go rewrite (internal/mcp/server.go, Streamable HTTP via official MCP SDK). Old FastMCP SSE transport is deprecated. |
Run docker compose --profile full up on mac-mini, or wait for the production cutover from apps/105. |
goose: command not found after bootstrap |
Upstream installer dropped binary in ~/.local/bin/ but /usr/local/bin/goose symlink didn't land |
Re-run bootstrap with --with-hermes; the symlink step is at the end of the install block. If still missing, ln -sfn ~/.local/bin/goose /usr/local/bin/goose manually. |
| Tool calls hit OpenRouter rate limits | One shared key across many hosts | Future: per-host keys; for now, see the rate-limits guide referenced in goose info -v. |
Cross-references
- agent-enrollment.md — base client onboarding the Hermes flow assumes is done.
HERMES.md— the persona the Hermes agent reads on every session start (via~/.config/goose/.goosehints).bin/hermes— the wrapper that decrypts the OpenRouter key and execsgoose session.bootstrap.sh— the--with-hermesflag's install block.
Follow-ups
-
Migrate the MCP server to streamable_http. Goose 1.x deprecated SSE (
"SSE transport is no longer supported - kept only for config file compatibility"incrates/goose/src/agents/extension.rs). Our FastMCP server atinternal/mcp/server.gouses Streamable HTTP (official MCP SDK). Until that's changed, thehomelabMCP extension in Goose will fail to connect. The developer extension (shell + edit) covers most ops without it; this is a polish item, not a blocker. -
Per-host OpenRouter keys for billing attribution. Today all Hermes hosts share one key.
-
Pin the model version rather than tracking
nousresearch/hermes-4-405bdirectly — OpenRouter periodically rotates the underlying weights. -
Local-inference fallback (ollama / vllm) once the homelab has a GPU node. The wrapper, persona, and MCP wiring stay unchanged; only
GOOSE_PROVIDER/GOOSE_MODELchange. -
Caveman auto-setup via post-pull hook. The sync timer now calls
tools/post-pull.sh, which runs anytools/*.setup.shafter git pull. Currently this auto-installs the Caveman npm package, wrapper scripts, and compact output templates on all agent hosts (token efficiency).
Changelog
2026-06-01 — caveman + post-pull auto-setup
Added tools/post-pull.sh sync hook that auto-runs tools/*.setup.sh
after every git pull. First user: tools/setup-caveman.sh installed Caveman
templating + ~/bin/caveman_wrapper.sh + ~/templates/*.txt for token-
efficient CLI output. Replaces raw git pull in launchd/systemd timers.
Also created tools/caveman/ with the wrapper script, JS renderer, and
templates — the canonical source for all agent hosts.
Captures the Hermes-on-Goose onboarding flow added in the same commit as
bootstrap.sh --with-hermes, bin/hermes, the sops rule for
secrets/openrouter-api-key.yaml, and the homelab client add --with-hermes
extension. MCP streamable_http migration is queued as follow-up #1.