UI: trial hermes-ui + hermes-workspace in parallel, keep winner (new Task 9). Curator stays report-only. eagle-eye trial confirmed (only direct skill-router in the ecosystem; llmtrim noted as complementary). camofox minimal 2-tool skill. All phases now ungated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
584 lines
32 KiB
Markdown
584 lines
32 KiB
Markdown
# 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.
|
||
|
||
- [ ] **Step 3: Confirm Codex and Claude Code are invocable from the host as ginnoir**
|
||
|
||
Run:
|
||
```bash
|
||
ssh -o BatchMode=yes ginnoir@valhalla "bash -lc 'which codex; which claude; codex --version 2>&1 | head -1; claude --version 2>&1 | head -1'"
|
||
```
|
||
Expected: paths for both `codex` and `claude`, and a version line each. (Uses `-lc` to get the login PATH.) **If either is missing**, the ACP delegation skill (Task 2) can still install but its `codex`/`claude-code` targets will be non-functional until they're on the service PATH — note this for ginnoir.
|
||
|
||
- [ ] **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 2–3.
|
||
|
||
---
|
||
|
||
## 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 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, 900s/24k caps), curator-evolver installed **report-only** (CPU ranking, no `--semantic`, no auto-apply flags), exact paths and rollback commands, and 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 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 L2–L5 (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 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:** All 7 repos map to tasks — acp-skill (T2), curator-evolver (T3), camofox (T5),
|
||
eagle-eye (T6), hermes-ui (T7), hermes-workspace (T8 deploy), with the head-to-head keep-one
|
||
decision in T9; mission-control (explicitly skipped per spec §2.5/§5, no task — intentional).
|
||
Phase ordering, single-P100 discipline, host-vs-repo boundary, provenance/reversibility, and the
|
||
§6 decisions (all resolved 2026-06-27) are 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 2–3 are clearly gated on spec §6 and must not start before ginnoir answers.
|