Files
homelabstack/docs/superpowers/plans/2026-06-27-hermes-ecosystem-integration.md
ginnoirandClaude Opus 4.8 dc2225d384 docs: fold ecosystem expansion + delegation fabric into Hermes plan
Add hermes-motif (skill discovery; complementary to curator, not a rival),
hermes-web-search-plus (mature multi-provider search, pairs with camofox),
optional llmtrim/rtk context efficiency, and the claude/codex/cursor/antigravity
delegation fabric. Record that Claude Code + Codex are now installed on valhalla.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 05:45:34 -05:00

781 lines
42 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Hermes Ecosystem Integration 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.
**Goal:** Integrate selected Hermes-ecosystem tools into ginnoir's live valhalla deployment — a host-side delegation skill and self-improvement plugin (Phase 1), a stealth-browser homelab stack and a skill pre-filter trial (Phase 2), and an optional UI upgrade (Phase 3) — each reversible and sized for the single-P100 / weak-local-model constraints.
**Architecture:** Two integration classes. **Class A** (host-side Hermes plugins/skills) install into `~/.hermes/` on the valhalla host and are applied by SSH + `hermes` CLI + `sudo systemctl restart hermes-gateway.service`; they are **host-managed, NOT committed to this repo** (tracked in project memory + the Obsidian vault, like the rustdesk/obsidian/xvfb units). **Class B** (Docker services) become `stacks/<name>/` entries deployed via the normal Gitea-poll path, fronted by Caddy `internal_only` + Authentik. The companion spec is `docs/superpowers/specs/2026-06-27-hermes-ecosystem-integration-design.md`.
**Tech Stack:** Hermes Agent v0.17.0 (host systemd), llama-swap/Tesla P100 backend (`gpt-oss-20b`, `--parallel 1`), Python 3.11 (`~/.hermes/hermes-agent/venv`), `uv`, SQLite, Docker Compose + Portainer (Gitea-polled), Caddy, Authentik, Codex + Claude Code CLIs.
---
## How to read this plan (operational, not codebase-TDD)
These are operational integrations against a live host and third-party services, so the TDD rhythm
is adapted: each task is **back up → change → verify with a smoke test → document/commit**. The
"test" is a real verification command with expected output. **Class A (host) changes are not git
commits** — their checkpoint is a backup + smoke test + a memory/vault note. **Class B (repo)
changes do commit** (and push triggers Portainer). Run every step from the Windows workstation;
host steps use `ssh -o BatchMode=yes ginnoir@valhalla "..."`.
**Global guardrails (apply to every task):**
- `hermes` is only on the **login-shell** PATH → over SSH call it by full path: `~/.local/bin/hermes`.
- Gateway restart needs root: `sudo systemctl restart hermes-gateway.service`.
- Always back up `~/.hermes/config.yaml` before editing (`cp ...bak.$(date +%s)`).
- **Never** load a second model onto the P100. Keep curator/eagle-eye semantic layers on CPU or off.
- Read third-party code before running it (curator writes skills; acp-skill spawns external agents; camofox automates a browser).
**Decisions (RESOLVED 2026-06-27 — all phases actionable):** trial **both** UIs and keep the winner
(Phase 3); curator stays **report-only** (Task 3); **trial eagle-eye** — it's the only direct
skill-router in the ecosystem (Task 6); camofox wired as a **minimal 2-tool skill** (Task 5 Step 8).
---
# PHASE 1 — Host-side, reversible, high-leverage (actionable now)
## Task 1: Pre-flight — capture current Hermes state
**Files:**
- Host only (no repo files).
- [ ] **Step 1: Verify host reachability and Hermes services are up**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "systemctl is-active hermes-gateway.service hermes-dashboard.service hermes-webui.service"
```
Expected: three lines, each `active`.
- [ ] **Step 2: Snapshot config + inventory skills/plugins/sessions dirs**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "cp ~/.hermes/config.yaml ~/.hermes/config.yaml.bak.preflight.$(date +%s) && ls -la ~/.hermes/skills ~/.hermes/plugins ~/.hermes/sessions 2>&1 | head -60 && ~/.local/bin/hermes --version"
```
Expected: a backup is created; directory listings print (note whether `~/.hermes/plugins` exists yet); `hermes` prints a version (≈ v0.17.0). Record the skills-dir path — confirms `~/.hermes/skills` is correct for later tasks.
- [x] **Step 3: Confirm Codex and Claude Code are invocable — DONE 2026-06-27 (installed this session)**
Both delegation CLIs were installed on valhalla this session:
- `claude``~/.local/bin/claude` **v2.1.195** (login pending)
- `codex``/usr/bin/codex` **v0.142.3** (login pending; harmless PATH-alias warning at install)
**Gotcha recorded:** `/home/ginnoir/.claude` existed as an empty **root-owned** dir (created Jun 17,
likely a prior sudo op) and blocked the installer until `sudo chown ginnoir:ginnoir ~/.claude`. Codex
global install needs `sudo` (npm global prefix is `/usr`). Re-verify any time with:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "bash -lc 'claude --version; codex --version'"
```
**ginnoir must log in** to each (`claude`, then `codex login`) before Task 2's external delegation
smoke-tests will succeed. Cursor + Antigravity targets are added later in **Task 10**.
- [ ] **Step 4: Checkpoint**
No commit (host inventory only). Record findings (skills-dir path, whether `plugins/` exists, Codex/Claude availability) in the session notes for use in Tasks 23.
---
## Task 2: Install `hermes-agent-acp-skill` (multi-agent delegation)
**Files:**
- Host: `~/.hermes/skills/hermes-acp-orchestrator/` (skill files), `~/.hermes/config.yaml` (delegation block).
- Scratch: clone under `/storage1/hermes/workspace/clones/` (never root; see the disk gotcha).
- [ ] **Step 1: Clone and read the skill before installing**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "mkdir -p /storage1/hermes/workspace/clones && git -C /storage1/hermes/workspace/clones clone https://github.com/Rainhoole/hermes-agent-acp-skill && sed -n '1,200p' /storage1/hermes/workspace/clones/hermes-agent-acp-skill/SKILL.md"
```
Expected: repo clones; `SKILL.md` prints. **Read it** to confirm: the skill folder/name, how `delegate_task()` is wired, and whether it expects a specific install path or a config key. The README omits install steps, so the SKILL.md is authoritative — follow whatever placement it documents. If SKILL.md specifies a different mechanism than the manual copy below, use SKILL.md's.
- [ ] **Step 2: Place the skill into the Hermes skills directory**
Run (adjust the destination name to match SKILL.md's declared skill name):
```bash
ssh -o BatchMode=yes ginnoir@valhalla "mkdir -p ~/.hermes/skills/hermes-acp-orchestrator && cp -r /storage1/hermes/workspace/clones/hermes-agent-acp-skill/SKILL.md /storage1/hermes/workspace/clones/hermes-agent-acp-skill/README.md ~/.hermes/skills/hermes-acp-orchestrator/ && ls -la ~/.hermes/skills/hermes-acp-orchestrator/"
```
Expected: `SKILL.md` and `README.md` present in the new skill dir.
- [ ] **Step 3: Add the delegation config block (with a safe backup)**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "cp ~/.hermes/config.yaml ~/.hermes/config.yaml.bak.acp.$(date +%s) && printf '\ndelegation:\n external_timeout_seconds: 900\n external_max_output_chars: 24000\n' >> ~/.hermes/config.yaml && tail -8 ~/.hermes/config.yaml"
```
Expected: a `.bak.acp.*` backup exists; the `delegation:` block is appended and printed. (If SKILL.md says the block belongs under a different key or nesting, edit accordingly instead of this append.)
- [ ] **Step 4: Restart the gateway and confirm the skill registers**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "sudo systemctl restart hermes-gateway.service && sleep 5 && systemctl is-active hermes-gateway.service && ~/.local/bin/hermes skills list 2>&1 | grep -i acp"
```
Expected: gateway `active`; the ACP/orchestrator skill appears in `hermes skills list`. (If the subcommand differs, use `~/.local/bin/hermes skills --help` to find the list command — verify on host.)
- [ ] **Step 5: Smoke-test a trivial delegation to the local hermes subagent first**
Run (a no-external-dependency delegation — routes to `hermes`, not Codex/Claude, to isolate the skill from CLI availability):
```bash
ssh -o BatchMode=yes ginnoir@valhalla "~/.local/bin/hermes run 'Use delegate_task to ask a hermes subagent to reply with exactly the word PONG, then report its output.' 2>&1 | tail -30"
```
Expected: the delegated subagent returns `PONG` and the parent reports it. **This proves the skill mechanics.** (Exact `hermes` one-shot invocation may differ — confirm the non-interactive run command via `~/.local/bin/hermes --help` in Step 1's read-through.)
- [ ] **Step 6: Smoke-test an external delegation (only if Codex/Claude were found in Task 1)**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "~/.local/bin/hermes run 'Use delegate_task with agent=claude-code to print the current working directory and nothing else, then report it.' 2>&1 | tail -40"
```
Expected: Claude Code is spawned within the 900 s timeout, returns the cwd, output is captured under the 24,000-char cap. **If it hangs or auths interactively**, the external CLI needs non-interactive credentials on the service env — note for ginnoir; the `hermes`-target path (Step 5) still works.
- [ ] **Step 7: Checkpoint (host note + reversibility recorded)**
No git commit. Record in session notes: skill installed at `~/.hermes/skills/hermes-acp-orchestrator/`, config backup at `~/.hermes/config.yaml.bak.acp.*`. **Rollback** = `rm -rf ~/.hermes/skills/hermes-acp-orchestrator`, restore the `.bak.acp.*`, restart gateway.
---
## Task 3: Install `hermes-curator-evolver` (self-improvement, report-only)
**Files:**
- Host: `~/.hermes/plugins/curator-evolver/` (plugin + `data/evidence.sqlite`), systemd **user** timer.
- [ ] **Step 1: Read the plugin source before installing (it can write to skills)**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "git -C /storage1/hermes/workspace/clones clone https://github.com/pingchesu/hermes-curator-evolver && sed -n '1,160p' /storage1/hermes/workspace/clones/hermes-curator-evolver/README.md"
```
Expected: repo clones; README prints. Confirm the apply path requires `--approve` (it does per the README) and that `auto-run` without `--apply-low-risk --approve-auto-apply` is **dry-run only**.
- [ ] **Step 2: Install the plugin (no semantic/embedding extras — keep it off the P100)**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "bash -lc '~/.local/bin/hermes plugins install pingchesu/hermes-curator-evolver --enable && uv pip install --python ~/.hermes/hermes-agent/venv/bin/python -e ~/.hermes/plugins/curator-evolver && ~/.hermes/hermes-agent/venv/bin/hermes-curator-evolver bootstrap'"
```
Expected: plugin installs to `~/.hermes/plugins/curator-evolver`; editable pip install succeeds; `bootstrap` configures and installs a **systemd user timer**. **Do NOT pass `--semantic`** (that pulls Qwen/BGE models — CPU/VRAM cost we're avoiding for now; BM25/FTS ranking is the v1 default).
- [ ] **Step 3: Backfill recent sessions and generate the first dry-run report**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "~/.hermes/hermes-agent/venv/bin/hermes-curator-evolver backfill-sessions --sessions-dir ~/.hermes/sessions --days 30 --format json 2>&1 | tail -20 && ~/.hermes/hermes-agent/venv/bin/hermes-curator-evolver report --days 7 --format json 2>&1 | tail -40"
```
Expected: evidence is mined into `~/.hermes/plugins/curator-evolver/data/evidence.sqlite`; `report` prints a JSON summary of candidate skill improvements. **No skill files are modified** (report is read-only).
- [ ] **Step 4: Generate a dry-run proposal for one skill and inspect it**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "~/.hermes/hermes-agent/venv/bin/hermes-curator-evolver auto-run --skills-dir ~/.hermes/skills --format json 2>&1 | tail -60"
```
Expected: a JSON set of **proposed** (not applied) edits. Confirm no files under `~/.hermes/skills` changed:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "find ~/.hermes/skills -newermt '-10 minutes' -type f 2>/dev/null"
```
Expected: empty output (nothing modified) — proves dry-run safety.
- [ ] **Step 5: Confirm the scheduled timer is report-only**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "bash -lc 'systemctl --user list-timers \"hermes-curator-evolver*\" --all --no-pager' && ssh -o BatchMode=yes ginnoir@valhalla \"systemctl --user cat 'hermes-curator-evolver*' 2>&1 | grep -iE 'ExecStart|approve|apply'\""
```
Expected: a timer is listed; its `ExecStart` runs `auto-run` **without** `--apply-low-risk`/`--approve-auto-apply`. **If the bootstrap-installed unit includes those flags, override it** to remove them (the morning decision in spec §6.2 defaults to report-only). If user-lingering isn't enabled the timer won't fire across logout — enable with `sudo loginctl enable-linger ginnoir` (note for ginnoir).
- [ ] **Step 6: Checkpoint (host note + reversibility recorded)**
No git commit. Record: plugin at `~/.hermes/plugins/curator-evolver`, DB at `.../data/evidence.sqlite`, timer name, report-only confirmed. **Rollback** = `systemctl --user disable --now <timer>`, `~/.local/bin/hermes plugins uninstall curator-evolver` (verify exact uninstall verb), `rm -rf ~/.hermes/plugins/curator-evolver`.
---
## Task 3b: Install `hermes-motif` (skill DISCOVERY, proposal-only)
> Complements curator-evolver — does **not** compete with it (spec §7.1). motif *discovers new*
> skills by mining repeated tool sequences; curator *refines existing* ones. **Zero P100 cost**
> (makes no LLM calls). Together with eagle-eye (routing) they form a skill factory:
> motif creates → curator refines → eagle-eye routes.
**Files:**
- Host: `~/.hermes/plugins/` (motif plugin), `~/.hermes/plugins/<motif>/plugin/plugin.yaml`.
- [ ] **Step 1: Clone and read; confirm proposal-only config**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "git -C /storage1/hermes/workspace/clones clone https://github.com/Saurav0989/hermes-motif && sed -n '1,160p' /storage1/hermes/workspace/clones/hermes-motif/README.md && cat /storage1/hermes/workspace/clones/hermes-motif/plugin/plugin.yaml 2>&1"
```
Expected: README + `plugin.yaml` print. **Confirm `auto_install: false`** (proposal-only) and note
`min_occurrences` / sequence-length thresholds. Note the referenced Hermes trace bug (#12922) that
can affect trace completeness — acceptable for a proposal-only trial.
- [ ] **Step 2: Install the plugin**
Run (per its README — clone + pip + `scripts/install_plugin.sh`):
```bash
ssh -o BatchMode=yes ginnoir@valhalla "cd /storage1/hermes/workspace/clones/hermes-motif && bash scripts/install_plugin.sh 2>&1 | tail -20"
```
Expected: the plugin lands under `~/.hermes/plugins/` and registers. (If the script expects a
different layout, follow the README's exact steps.)
- [ ] **Step 3: Verify it mines and PROPOSES without modifying skills**
Restart the gateway, run the agent through a couple of repeated multi-tool workflows, then check for
proposals (drafts), confirming nothing under `~/.hermes/skills` was auto-written:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "sudo systemctl restart hermes-gateway.service && sleep 5 && find ~/.hermes/plugins -iname '*propos*' -o -iname '*draft*' 2>/dev/null | head && find ~/.hermes/skills -newermt '-10 minutes' -type f 2>/dev/null"
```
Expected: proposal/draft artifacts may appear under the plugin dir; the second `find` is **empty**
(no skill files auto-modified) — proves `auto_install: false` safety.
- [ ] **Step 4: Checkpoint**
No repo commit (host-side). **Rollback** = remove the motif plugin dir + restart gateway. Record in
`memory/hermes-extensions.md` alongside curator (skill factory: motif=create, curator=refine).
---
## Task 4: Document Phase 1 in memory + vault (durable knowledge)
**Files:**
- Memory: `C:\Users\MattC\.claude\projects\C--Users-MattC-Documents-homelabstack\memory\hermes-extensions.md` + `MEMORY.md` pointer.
- Vault: append to the Hermes project note via Obsidian MCP (`mcp__obsidian__*`).
- [ ] **Step 1: Write the memory file**
Create `memory/hermes-extensions.md` (frontmatter `type: project`) recording: acp-skill installed (delegation to hermes/codex/claude-code/cursor/antigravity, 900s/24k caps); curator-evolver installed **report-only** (CPU ranking, no `--semantic`, no auto-apply flags); **motif** installed proposal-only (skill factory: motif creates → curator refines → eagle-eye routes); **claude v2.1.195 + codex v0.142.3 installed on valhalla 2026-06-27** (login pending; `~/.claude` was root-owned → chowned); exact paths and rollback commands; the host-vs-repo boundary. Link `[[llm-stack-hermes]]`, `[[multi-agent-tool-configs]]`, `[[obsidian-app-on-valhalla]]`.
- [ ] **Step 2: Add the MEMORY.md index pointer**
Append one line to `MEMORY.md`:
`- [Hermes host extensions](hermes-extensions.md) — acp delegation skill + curator-evolver (report-only) on valhalla; host-managed in ~/.hermes, not in the repo`
- [ ] **Step 3: Write back to the Obsidian vault**
Per the global rule, use the Obsidian MCP (never write CouchDB directly) to append a session note to the Hermes project folder summarizing Phase 1 (what, why report-only, rollback). If the MCP is unreachable, tell ginnoir and skip — do not hand-edit.
- [ ] **Step 4: Checkpoint**
No code commit required (memory files live outside the repo). Phase 1 complete and documented.
---
# PHASE 2 — New capability + experiment
> Decisions resolved (spec §6.3 eagle-eye trial; §6.4 camofox minimal). Actionable.
## Task 5: `camofox-browser` as a homelab stack (Class B)
**Files:**
- Create: `stacks/camofox/docker-compose.yml`, `stacks/camofox/stack.env`.
- Modify: `Caddyfile` (new site block), `bookmarks-domains.html` + `bookmarks-ports.html` (regenerated).
- Host (image): build under `/storage1/hermes/workspace/clones/camofox-browser`.
- [ ] **Step 1: Decide image provenance and build it**
camofox publishes **no registry image** (`make up` builds locally). Recommended default: build on
valhalla and tag `camofox-browser:local`, reference that tag from compose (Watchtower already
disabled for pinned infra). Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "git -C /storage1/hermes/workspace/clones clone https://github.com/jo-inc/camofox-browser && cd /storage1/hermes/workspace/clones/camofox-browser && docker build -t camofox-browser:local . 2>&1 | tail -20 && docker image ls camofox-browser:local"
```
Expected: image builds; `camofox-browser:local` is listed. **Alternative (if a reproducible/Gitea-Actions build is preferred, like famapp):** build + push to `registry.ginnoir.com/ginnoir/camofox-browser` and reference that instead — flag this choice for ginnoir.
- [ ] **Step 2: Write the stack compose**
Create `stacks/camofox/docker-compose.yml`:
```yaml
# camofox stack — stealth headless browser REST API for the Hermes agent.
# No published image: built on-host as camofox-browser:local (see plan Task 5).
# Internal-only; reachable by Caddy over edge and by host-side Hermes.
services:
camofox:
image: camofox-browser:local
container_name: camofox
restart: unless-stopped
labels:
- "com.centurylabs.watchtower.enable=false"
env_file:
- stack.env
networks: [edge, camofox]
volumes:
- /config/camofox/cookies:/home/node/.camofox/cookies
- /config/camofox/profiles:/home/node/.camofox/profiles
ports:
- "172.20.0.1:9377:9377"
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:9377/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 40s
networks:
edge:
external: true
camofox:
name: camofox
driver: bridge
```
(The `172.20.0.1:9377` host-port mirrors the llm stack's pattern so host-side Hermes can reach it directly; Caddy reaches it over `edge` by container name.)
- [ ] **Step 3: Write `stack.env` (secrets; LF endings)**
Create `stacks/camofox/stack.env` with a generated bearer key (replace the value with a real secret before push):
```
CAMOFOX_ACCESS_KEY=GENERATE_A_LONG_RANDOM_KEY
CAMOFOX_ADMIN_KEY=GENERATE_A_SECOND_RANDOM_KEY
CAMOFOX_PORT=9377
```
Generate the keys: `ssh ... "openssl rand -hex 32"` (run twice). **Ensure LF line endings** (`.gitattributes` enforces this — verify the file isn't CRLF before committing). Leave `CAMOFOX_API_KEY` unset (cookie-import endpoint stays disabled).
- [ ] **Step 4: Add the Caddy site block (internal-only)**
Add to `Caddyfile` (place near other internal admin services). Since camofox enforces its own bearer auth and Hermes calls it machine-to-machine, gate by LAN only (no Authentik forward-auth, which would block the agent's API calls):
```caddy
camofox.ginnoir.com {
import internal_only
reverse_proxy camofox:9377
}
```
- [ ] **Step 5: Create host config dirs, regenerate bookmarks, then deploy**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "sudo mkdir -p /config/camofox/cookies /config/camofox/profiles && sudo chown -R ginnoir:ginnoir /config/camofox"
```
Then regenerate bookmarks and push (Portainer must have the `stacks/camofox` git stack registered — see Step 6):
```powershell
./scripts/gen-bookmarks.ps1
git add stacks/camofox/ Caddyfile bookmarks-domains.html bookmarks-ports.html
git commit -m "feat(camofox): stealth browser stack for the Hermes agent"
git push
```
Expected: commit + push; Gitea Actions reloads Caddy (Caddyfile changed); Portainer redeploys the camofox stack within 5 min.
- [ ] **Step 6: Register the stack in Portainer if new, and verify it runs**
New stacks need one-time Portainer registration (see `portainer-new-stack-registration` memory). After deploy, verify:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "docker ps --filter name=camofox --format '{{.Names}} {{.Status}}' && curl -fsS http://172.20.0.1:9377/health"
```
Expected: container `Up (healthy)`; `/health` returns OK.
- [ ] **Step 7: Smoke-test the browser API end-to-end**
Run (creates a tab, snapshots a page):
```bash
ssh -o BatchMode=yes ginnoir@valhalla "K=\$(grep CAMOFOX_ACCESS_KEY /config/portainer/compose/*/stacks/camofox/stack.env | cut -d= -f2); ID=\$(curl -fsS -H \"Authorization: Bearer \$K\" -H 'Content-Type: application/json' -d '{\"userId\":\"smoke\",\"sessionKey\":\"t1\",\"url\":\"https://example.com\"}' http://172.20.0.1:9377/tabs | python3 -c 'import sys,json;print(json.load(sys.stdin)[\"id\"])'); curl -fsS -H \"Authorization: Bearer \$K\" \"http://172.20.0.1:9377/tabs/\$ID/snapshot?userId=smoke\" | head -20"
```
Expected: a tab id comes back; the snapshot returns accessibility text containing "Example Domain". (Adjust the JSON id field name to match the real response from Step 1's README read.)
- [ ] **Step 8: Wire camofox into Hermes as a minimal tool surface (per §6.4 decision)**
Default recommendation: a **small Hermes skill** (2 high-level tools — `browse(url)` and `search(query)`) that curls camofox, rather than exposing the full REST surface (respects the tool-budget that keeps gpt-oss-20b functional). Create `~/.hermes/skills/camofox-browse/SKILL.md` documenting the two operations against `http://172.20.0.1:9377` with the bearer key, restart the gateway, and smoke-test:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "sudo systemctl restart hermes-gateway.service && sleep 5 && ~/.local/bin/hermes run 'browse https://example.com and tell me the page heading' 2>&1 | tail -20"
```
Expected: Hermes uses the camofox tool and reports "Example Domain". **If §6.4 chose an MCP shim instead**, build/register the MCP server and add it to `mcp_servers:` with a 2-tool `tools.include` allowlist (per the MCP-curation pattern in `llm-stack-hermes`).
- [ ] **Step 9: Checkpoint**
Repo changes are committed (Step 5). Update `memory/hermes-extensions.md` + the vault note with the camofox stack + tool wiring and the bearer-key location.
---
## Task 5b: Install `hermes-web-search-plus` (multi-provider search; pairs with camofox)
> Mature (v2.6.1, MIT, stdlib-only). Complements camofox (spec §7.2): search-plus *finds* via cheap
> provider APIs, camofox *browses/renders*. Lighter and higher-frequency — good default reach-for.
**Files:**
- Host: `~/.hermes/plugins/` (plugin), provider key(s) in `~/.hermes/config.yaml` (or the plugin's config).
- [ ] **Step 1: Install the plugin**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "bash -lc '~/.local/bin/hermes plugins install robbyczgw-cla/hermes-web-search-plus --enable'"
```
Expected: plugin installs and enables.
- [ ] **Step 2: Configure at least one provider key (free tier)**
All provider keys are optional but ≥1 is needed to function. Pick a free-tier provider (e.g. Tavily,
Exa, or self-hosted SearXNG; Keenable has a keyless public tier). Add the key per the plugin's README
(read `/storage1/hermes/workspace/clones/...` or the plugin docs for the exact env/config key), then:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "sudo systemctl restart hermes-gateway.service && sleep 5 && systemctl is-active hermes-gateway.service"
```
Expected: gateway `active`. **Mind the tool-budget rule** — if it exposes both `web_search_plus` +
extract, that's fine (2 tools); don't also enable redundant search MCPs.
- [ ] **Step 3: Smoke-test a search**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "~/.local/bin/hermes run 'search the web for the latest Hermes Agent release version and cite the source' 2>&1 | tail -20"
```
Expected: the agent calls the search tool, returns a current result with a source URL.
- [ ] **Step 4: Checkpoint**
No repo commit (host-side). Document the chosen provider + key location in `memory/hermes-extensions.md`.
---
## Task 6: Trial `eagle-eye` skill pre-filter (Class A, behind a switch)
> Confirmed the chosen tool: eagle-eye is the **only** direct skill-router in the Hermes ecosystem
> (per `awesome-hermes-agent` / Hermes Atlas). It directly serves the goal of "many skills installed,
> few injected per turn." Complementary (not a substitute) and worth a later look on the *tool* side:
> `llmtrim` (compresses tool schemas + MCP output before each request). `hermes-motif` overlaps
> curator-evolver (trace→micro-skill), not this router.
**Files:**
- Host: `~/.hermes/plugins/eagle-eye/` (or skills dir per its README), config toggle in `~/.hermes/config.yaml`.
- [ ] **Step 1: Clone and read; confirm graceful-degradation and the jieba dependency**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "git -C /storage1/hermes/workspace/clones clone https://github.com/willingning-coder/eagle-eye && sed -n '1,200p' /storage1/hermes/workspace/clones/eagle-eye/README.md"
```
Expected: README prints. Confirm the install hook, the on/off switch, and that L2L5 (incl. dense embeddings) are optional. **Plan to run with the dense layer disabled** (CPU/keep off the P100) — lean on L1 (hard triggers) + L2 (BM25) only for the trial.
- [ ] **Step 2: Install with an easy off-switch and minimal deps**
Install per the README (likely `~/.local/bin/hermes plugins install willingning-coder/eagle-eye --enable`), then restart the gateway:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "bash -lc '~/.local/bin/hermes plugins install willingning-coder/eagle-eye --enable' && ssh -o BatchMode=yes ginnoir@valhalla 'sudo systemctl restart hermes-gateway.service && systemctl is-active hermes-gateway.service'"
```
Expected: plugin enabled; gateway `active`. (If install fails on `jieba`, `uv pip install --python ~/.hermes/hermes-agent/venv/bin/python jieba` then retry — note the foreign-language dep for maintenance.)
- [ ] **Step 3: A/B test skill selection on representative prompts**
Pick 5 prompts that should each map to a known skill and 2 that should map to none. Run each with eagle-eye enabled, then disable it (`~/.local/bin/hermes plugins disable eagle-eye` + gateway restart) and run the same 7. Record which skills each surfaced and whether the local model then picked the right one.
```bash
ssh -o BatchMode=yes ginnoir@valhalla "~/.local/bin/hermes run '<representative prompt>' 2>&1 | tail -25"
```
Expected: with eagle-eye on, the prompt's prompt-injected skill candidates are ≤5 and include the right one; the "no skill needed" prompts proceed without forced skill loading.
- [ ] **Step 4: Keep-or-cut decision**
**Keep only if** skill selection measurably improved (right skill surfaced more often AND/OR fewer wrong skills loaded) without regressions. Otherwise disable and uninstall:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "~/.local/bin/hermes plugins uninstall eagle-eye && sudo systemctl restart hermes-gateway.service"
```
Record the verdict + evidence in `memory/hermes-extensions.md`.
- [ ] **Step 5: Checkpoint**
No repo commit (host-side). Document the A/B result and final state (kept/cut) in memory + vault.
---
# PHASE 2b — Delegation fabric & context efficiency
> Extends acp-skill (Task 2) from 3 targets to 4 external agents, and adds optional token-trimming.
## Task 10: Wire Cursor + Antigravity into the delegation fabric
**Files:**
- Host: Cursor + `agy` binaries; acp-skill config or a generic shell-agent skill in `~/.hermes/skills/`.
- [ ] **Step 1: Install the Cursor CLI (official cursor.com)**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "bash -lc 'curl https://cursor.com/install -fsS | bash' && ssh -o BatchMode=yes ginnoir@valhalla 'bash -lc \"command -v cursor-agent && cursor-agent --version\"'"
```
Expected: `cursor-agent` installs and prints a version. ginnoir logs in later.
- [ ] **Step 2: Install the Antigravity CLI (`agy`) from the OFFICIAL Google source**
Do **not** use blog-derived URLs. Get the exact installer from the official pages first:
`https://antigravity.google/download` and `https://antigravity.google/docs/gcli-migration`. Then run
the official one-line installer they document, e.g.:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "bash -lc '<official agy installer from antigravity.google/docs>' && ssh -o BatchMode=yes ginnoir@valhalla 'bash -lc \"command -v agy && agy --version\"'"
```
Expected: `agy` (Go binary, ideal for headless SSH) installs and prints a version. Auth later via
keyring/Google sign-in or `ANTIGRAVITY_TOKEN`.
- [ ] **Step 3: Confirm each agent answers in headless mode (after ginnoir logs in)**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "bash -lc 'claude -p \"say PONG\"; codex exec \"say PONG\"; cursor-agent -p --output-format json --trust \"say PONG\"; agy -p \"say PONG\"'"
```
Expected: each prints PONG-ish output. **Cursor caveat:** `-p` has a known hang bug — always pass
`--output-format json` and wrap with a timeout (`timeout 120 cursor-agent ...`).
- [ ] **Step 4: Extend acp-skill routing (or add a generic shell-agent skill)**
Read `~/.hermes/skills/hermes-acp-orchestrator/SKILL.md` to see if `agent=` routing is extensible.
- **If extensible:** add `cursor` and `antigravity` targets mapping to the Step 3 invocations
(with the cursor timeout + json flags), honoring the `delegation:` `external_timeout_seconds: 900`
/ `external_max_output_chars: 24000` caps.
- **If not:** add `~/.hermes/skills/shell-agent/SKILL.md` exposing **one** tool
`delegate(agent, goal)` that shells out to claude/codex/cursor/agy with the caps + cursor guard.
One tool keeps the surface within the tool-budget.
Then restart the gateway:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "sudo systemctl restart hermes-gateway.service && sleep 5 && systemctl is-active hermes-gateway.service"
```
- [ ] **Step 5: End-to-end smoke-test each delegation target**
Run one delegated task per agent (e.g. `agent=cursor`, `agent=antigravity`) and confirm output is
captured under the cap. Record any that hang/auth-fail for follow-up.
- [ ] **Step 6: Checkpoint**
No repo commit (host-side). Document the four-target fabric + cursor caveat in `memory/hermes-extensions.md`.
---
## Task 11: (OPTIONAL) Context efficiency — llmtrim on the cloud-delegation path
> Opt-in. Start where the win is unambiguous and local-risk-free: trimming the **cloud** delegation
> agents' traffic (Claude Code/Codex/Cursor → Anthropic/OpenAI). Defer the llama-swap-fronting idea
> until validated. `rtk-hermes` (shell-output trimming) is a separate lighter opt-in.
**Files:**
- Host: llmtrim service + `HTTPS_PROXY` env for the delegation agents.
- [ ] **Step 1: Install llmtrim and run setup**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "bash -lc 'npm install -g @llmtrim/cli@latest && llmtrim setup' 2>&1 | tail -20"
```
Expected: installs; `setup` installs the name-constrained CA + background proxy. **Review the MITM-CA
trust implication first** — it's name-constrained to LLM API domains, but it's still a CA on the host.
- [ ] **Step 2: Point the cloud delegation agents through it; measure**
Ensure the delegation agents inherit `HTTPS_PROXY` (llmtrim sets this). Run a representative delegated
task via Claude Code/Codex and compare token counts / cost before vs after (llmtrim reports savings).
Keep only if the reduction is real with no quality loss.
- [ ] **Step 3: (Later, separate) Evaluate llama-swap fronting + rtk-hermes**
Document — do not implement here — the two deferred ideas: (a) llmtrim in front of `172.20.0.1:8090`
via `LLMTRIM_EXTRA_HOSTS` to shrink prompts and speed Pascal prefill (needs validation; unproven for
local OpenAI-compatible backends); (b) `rtk-hermes` (`pre_tool_call` shell rewrite) if the `rtk`
binary is available on Ubuntu.
- [ ] **Step 4: Checkpoint**
No repo commit. Record the decision + measured savings (or rejection) in `memory/hermes-extensions.md`.
`llmtrim uninstall` fully reverses the proxy + CA if rejected.
---
# PHASE 3 — UI trial: stand up BOTH, keep the winner
> Decision resolved (spec §6.1): trial `hermes-ui` (Task 7) **and** `hermes-workspace` (Task 8) in
> parallel, compare head-to-head against the bundled webui (Task 9), keep one and tear down the
> rest. `mission-control` is skipped.
## Task 7: Trial `hermes-ui` (lightweight, no build)
**Files:**
- Host: clone at `/storage1/hermes/workspace/clones/hermes-ui`; optional `hermes-ui.service` (host unit) or a tiny container; Caddy block if exposed.
- [ ] **Step 1: Clone and run the stdlib proxy against the live gateway**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "git -C /storage1/hermes/workspace/clones clone https://github.com/pyrate-llama/hermes-ui && cd /storage1/hermes/workspace/clones/hermes-ui && (~/.hermes/hermes-agent/venv/bin/python3 serve_lite.py >/tmp/hermes-ui.log 2>&1 &) && sleep 3 && curl -fsS http://127.0.0.1:3333/hermes-ui.html | head -5"
```
Expected: the proxy starts on :3333 (defaults to gateway `127.0.0.1:8642`, which matches your deployment), and the HTML serves. If your gateway port differs, edit the `HERMES` variable at the top of `serve_lite.py` (no env var exists).
- [ ] **Step 2: Expose it on the LAN for evaluation (don't finalize yet)**
Bind the proxy to the host IP so Caddy can reach it, add a temporary internal-only Caddy block, and
keep it running for the Task 9 comparison:
```caddy
hermes-ui.ginnoir.com {
import internal_only
reverse_proxy 172.20.0.1:3333
}
```
Run `serve_lite.py` bound appropriately (edit its bind host if it defaults to `127.0.0.1`), regenerate
bookmarks, push the Caddyfile change. **Do not** create the persistent `hermes-ui.service` yet — that
happens in Task 9 only for the winner.
- [ ] **Step 3: Checkpoint**
hermes-ui is reachable at `https://hermes-ui.ginnoir.com` (LAN) for the head-to-head. Leave the
final keep/revert + boot-persistence to Task 9.
---
## Task 8: Deploy `hermes-workspace` as a stack (Class B) — for evaluation
**Files:**
- Create: `stacks/hermes-workspace/docker-compose.yml`, `stacks/hermes-workspace/stack.env`.
- Modify: `Caddyfile` (Authentik-gated site block), `bookmarks-domains.html` + `bookmarks-ports.html`.
- Host (image): build under `/storage1/hermes/workspace/clones/hermes-workspace`.
- [ ] **Step 1: Clone and read its deployment docs (get exact build, ports, env)**
Run:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "git -C /storage1/hermes/workspace/clones clone https://github.com/outsourc-e/hermes-workspace && sed -n '1,200p' /storage1/hermes/workspace/clones/hermes-workspace/README.md && ls /storage1/hermes/workspace/clones/hermes-workspace/{Dockerfile,docker-compose*.yml,.env*} 2>&1"
```
Expected: README + a `Dockerfile`/compose appear. Record the exact image build command, the served
port, and the env var(s) that point the frontend at the gateway (`:8642`) and dashboard (`:9119`).
**Note the swarm caveat for Task 9:** Swarm Mode (tmux worker pools) can't parallelize inference on
one P100 — evaluate the workspace/observability features, not swarm.
- [ ] **Step 2: Resolve container→host-service reachability**
hermes-workspace (a container) must reach the host's gateway `:8642` and dashboard `:9119`. Check
what interface those bind to:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "ss -ltnp | grep -E ':8642|:9119'"
```
Expected: shows the bind address. **If bound to `127.0.0.1`**, the container can't reach them — pick
one: (a) add `extra_hosts: ["host.docker.internal:host-gateway"]` and target `host.docker.internal`,
or (b) rebind the Hermes services to the docker-bridge host IP `172.20.0.1` (config change + gateway
restart, with backup). Default recommendation: **(a)** (no Hermes config change; reversible).
- [ ] **Step 3: Build the image on-host**
Run (use the build command discovered in Step 1; tag locally since there's no published image):
```bash
ssh -o BatchMode=yes ginnoir@valhalla "cd /storage1/hermes/workspace/clones/hermes-workspace && docker build -t hermes-workspace:local . 2>&1 | tail -20 && docker image ls hermes-workspace:local"
```
Expected: `hermes-workspace:local` is built and listed.
- [ ] **Step 4: Write the stack compose**
Create `stacks/hermes-workspace/docker-compose.yml` (adjust the served port and gateway/dashboard env
keys to Step 1's findings; this uses host.docker.internal per Step 2 option (a)):
```yaml
# hermes-workspace stack — full web command center for the Hermes agent (trial).
# No published image: built on-host as hermes-workspace:local (see plan Task 8).
# Human-facing UI → Authentik-gated. Reaches host gateway :8642 + dashboard :9119
# via host.docker.internal.
services:
hermes-workspace:
image: hermes-workspace:local
container_name: hermes-workspace
restart: unless-stopped
labels:
- "com.centurylabs.watchtower.enable=false"
env_file:
- stack.env
networks: [edge]
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "172.20.0.1:8088:8088"
networks:
edge:
external: true
```
- [ ] **Step 5: Write `stack.env` (gateway/dashboard targets; LF endings)**
Create `stacks/hermes-workspace/stack.env` using the real env keys from Step 1, e.g.:
```
HERMES_GATEWAY_URL=http://host.docker.internal:8642
HERMES_DASHBOARD_URL=http://host.docker.internal:9119
PORT=8088
```
Verify LF endings before committing.
- [ ] **Step 6: Add an Authentik-gated Caddy block**
Unlike camofox (machine-to-machine), this is a human UI → gate with Authentik forward_auth (Pattern B):
```caddy
workspace.ginnoir.com {
import internal_only
route {
import authentik_outpost
import authentik_forward_auth
reverse_proxy hermes-workspace:8088
}
}
```
- [ ] **Step 7: Deploy and verify**
Run:
```powershell
./scripts/gen-bookmarks.ps1
git add stacks/hermes-workspace/ Caddyfile bookmarks-domains.html bookmarks-ports.html
git commit -m "feat(hermes-workspace): trial command-center stack (eval vs hermes-ui)"
git push
```
Register the stack in Portainer if new, then:
```bash
ssh -o BatchMode=yes ginnoir@valhalla "docker ps --filter name=hermes-workspace --format '{{.Names}} {{.Status}}' && curl -fsS http://172.20.0.1:8088/ | head -5"
```
Expected: container `Up`; the workspace HTML serves; logging into `https://workspace.ginnoir.com`
via Authentik shows live chat/memory/skills wired to your gateway.
- [ ] **Step 8: Checkpoint**
Repo changes committed (Step 7). Leave the keep/tear-down decision to Task 9.
---
## Task 9: Head-to-head UI decision — keep one, tear down the rest
**Files:**
- Modify (on tear-down): `Caddyfile`, `stacks/...` (remove the loser), bookmarks; host unit for the winner.
- [ ] **Step 1: Compare bundled webui vs hermes-ui vs hermes-workspace**
Use all three live for representative work (chat/streaming, tasks/kanban, files, terminal, skills,
MCP browser, cron, memory, health). Score against: does it surface your curated tools cleanly, does
it stay responsive against the P100's latency, and does it add real value over the bundled webui.
Record the verdict in the vault.
- [ ] **Step 2: Make the winner permanent**
- If **hermes-ui** wins: create host unit `hermes-ui.service` (host-managed, like `obsidian.service`
— NOT in this repo), `After=hermes-gateway.service`, `Restart=on-failure`; keep its Caddy block.
- If **hermes-workspace** wins: keep its stack + Authentik block as-is.
- If **bundled webui** wins: keep status quo.
- [ ] **Step 3: Tear down the losers (reversible, clean)**
- Remove the hermes-workspace stack if it lost: delete `stacks/hermes-workspace/`, its Caddy block,
regenerate bookmarks, commit + push, then delete the stack in Portainer and
`docker rm -f hermes-workspace`, `docker image rm hermes-workspace:local`.
- Stop/remove hermes-ui if it lost: `pkill -f 'serve_lite[.]py'` (bracket trick), remove its Caddy
block + clone, commit the Caddyfile change.
- [ ] **Step 4: Checkpoint**
One UI kept and documented in memory + vault; losers fully removed; repo reflects the final state.
---
## Self-Review (completed)
- **Spec coverage:** Original 7 repos — acp-skill (T2), curator-evolver (T3), camofox (T5),
eagle-eye (T6), hermes-ui (T7), hermes-workspace (T8 deploy) + keep-one decision (T9);
mission-control (skipped per spec §2.5/§5, intentional). **Ecosystem expansion (spec §7)**
hermes-motif (T3b), hermes-web-search-plus (T5b), delegation fabric for cursor+antigravity (T10),
optional llmtrim/rtk context efficiency (T11). Claude Code + Codex install is **done** (T1 Step 3).
Phase ordering, single-P100 discipline, host-vs-repo boundary, provenance (official installers
only — Antigravity URL verified to `antigravity.google`), reversibility, and the §6 decisions are
all reflected.
- **Placeholders:** None of the prohibited kinds. Where a third-party command form can't be verified
remotely (e.g. exact `hermes` subcommand spelling, acp-skill install mechanism, response field
names), the plan's **first step is a concrete "clone + read the README/SKILL.md" command** that
resolves it before use — a real action with expected output, not a TBD.
- **Consistency:** Paths and names are consistent throughout (`~/.hermes/skills`, `~/.hermes/plugins/curator-evolver`, `camofox-browser:local`, port 9377, gateway 8642, `172.20.0.1` host-IP publish pattern, `sudo systemctl restart hermes-gateway.service`).
- **Decision gates:** Phases 23 are clearly gated on spec §6 and must not start before ginnoir answers.