docs: implementation plan for Hermes ecosystem integrations
Phased, reversible plan. Phase 1 (acp-skill + curator-evolver, report-only) fully actionable; Phases 2-3 (camofox stack, eagle-eye trial, UI) gated on the spec's open decisions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d794c98505
commit
98c4a2a02b
@@ -0,0 +1,442 @@
|
|||||||
|
# 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).
|
||||||
|
|
||||||
|
**Decision gates:** Phase 1 is fully actionable now. **Phases 2 and 3 are gated on the spec §6
|
||||||
|
decisions** (UI choice; curator autonomy; eagle-eye trial; camofox wiring). Do not start a gated
|
||||||
|
task group until ginnoir has answered.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 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 (GATED on spec §6.3 / §6.4)
|
||||||
|
|
||||||
|
> Start only after ginnoir confirms: trial eagle-eye (§6.3) and camofox wiring choice (§6.4).
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
**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 decision (GATED on spec §6.1)
|
||||||
|
|
||||||
|
> Start only after ginnoir picks a UI direction. Default recommended first move: **trial `hermes-ui`**
|
||||||
|
> (Task 7). `hermes-workspace` (Task 8) is the heavier alternative; `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: Evaluate against the bundled webui**
|
||||||
|
|
||||||
|
Browse it from the LAN (port-forward or temporary Caddy block) and compare to the existing `hermes-webui.service`: chat/streaming, tasks/kanban, files, terminal, skills, MCP browser, cron, memory, health. Decide keep-or-revert.
|
||||||
|
|
||||||
|
- [ ] **Step 3: If keeping — make it boot-persistent and gate it**
|
||||||
|
|
||||||
|
Create a host unit `hermes-ui.service` (host-managed, like `obsidian.service` — NOT in this repo) running `serve_lite.py`, `After=hermes-gateway.service`, `Restart=on-failure`. Add a Caddy block `hermes-ui.ginnoir.com { import internal_only; reverse_proxy 172.20.0.1:3333 }` (publish the proxy on the host IP if exposing via Caddy), regenerate bookmarks, push the Caddyfile change. **If reverting**, kill the proxy (`pkill -f 'serve_lite[.]py'` — bracket trick to avoid self-match) and remove the clone.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Checkpoint**
|
||||||
|
|
||||||
|
Repo change is only the Caddy block (if exposed) — commit + push that. The service unit is host-managed; document it in memory + vault alongside the `obsidian.service` note.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 8: (Alternative) Evaluate `hermes-workspace` as a stack — scoping only
|
||||||
|
|
||||||
|
> Only if ginnoir prefers the full command center over `hermes-ui`. This task is a **scoping
|
||||||
|
> checkpoint**, not a build, because it carries a real caveat to resolve first.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Confirm the swarm caveat before investing in a build**
|
||||||
|
|
||||||
|
`hermes-workspace`'s Swarm Mode (parallel tmux worker pools) **cannot parallelize inference** on a
|
||||||
|
single P100 (`--parallel 1`, one model at a time) — swarm workers serialize on the GPU or must
|
||||||
|
target a different backend. Decide with ginnoir whether the non-swarm features (workspace, ops
|
||||||
|
dashboards, Conductor missions) alone justify a React/Vite build + a new `stacks/hermes-workspace`
|
||||||
|
Docker stack behind Caddy + Authentik (gateway :8642 + dashboard :9119). If yes, this becomes its
|
||||||
|
own full plan (separate spec/plan cycle). If no, stop — `hermes-ui` (Task 7) is the chosen UI.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Checkpoint**
|
||||||
|
|
||||||
|
Decision recorded in the vault. No implementation here without a dedicated follow-up plan.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 scoping), 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 decision gates 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 2–3 are clearly gated on spec §6 and must not start before ginnoir answers.
|
||||||
Reference in New Issue
Block a user