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>
32 KiB
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):
hermesis 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.yamlbefore 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:
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:
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:
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:
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):
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:
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:
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):
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:
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:
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:
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:
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:
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:
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:
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.mdpointer. -
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:
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:
# 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):
camofox.ginnoir.com {
import internal_only
reverse_proxy camofox:9377
}
- Step 5: Create host config dirs, regenerate bookmarks, then deploy
Run:
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):
./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:
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):
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:
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-motifoverlaps 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:
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:
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.
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:
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) andhermes-workspace(Task 8) in parallel, compare head-to-head against the bundled webui (Task 9), keep one and tear down the rest.mission-controlis skipped.
Task 7: Trial hermes-ui (lightweight, no build)
Files:
-
Host: clone at
/storage1/hermes/workspace/clones/hermes-ui; optionalhermes-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:
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:
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:
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:
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):
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)):
# 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):
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:
./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:
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, likeobsidian.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 anddocker 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
hermessubcommand 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.1host-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.