Objective
- Move the use of fall back LLM proxies
modelrelay and change to use 9router transparently.
- update dependencies version
- Update PI to use pi-failover@main branch instead of a non-existing hermes-impl
- Maybe update .devcontainer to use .minions for agentic coding framework.
- Add .agent/skills folder for dedicate skills for agent to code Hermes-Webtop (following firstmate repo: https://github.com/gitricko/firstmate-codespace/tree/main/.agents/skills)
- eg: Do not do docker build locally becos not enough disk-space etc
Implementation Proposal
Proposed by: Hermes Agent (via #52 audit)
Reference: PR #57 (merged, gitricko/hermes-codespace) — canonical renames and config changes.
Design principle: Karpathy — minimal, surgical, verified-by-real-output.
Phase 1 Proposal: ModelRelay → 9Router (port 7352 stays)
Issue: #52 — Migrate the use of ModelRelay to 9Router, keeping port 7352 unchanged.
Reference: PR #57 (merged, gitricko/hermes-codespace) — provides the canonical renames, config changes, and verification expectations.
Scope: Docker container layer only (docker/, docker-compose.yml, docs/, README.md). The .devcontainer/ layer (PR #57's primary target) is handled separately; Phase 1 mirrors its intent onto the current docker/ layout.
Design principle: Karpathy — surgical, minimal, verified. Touch only what must change; port 7352 is preserved end-to-end.
1. File-by-file change table
All paths relative to repo root /workspaces/hermes-webtop.
| # |
Path |
Lines |
Old → New |
Rationale |
| 1 |
docker/Dockerfile |
3 |
ARG MODELRELAY_VERSION=1.22.1 → ARG NINEROUTER_VERSION=0.5.81; npm install -g modelrelay@${MODELRELAY_VERSION} → npm install -g 9router@${NINEROUTER_VERSION} |
Package identity swap; prefix /usr/local/lib retained (see §3) |
| 2 |
docker/start-hermes.sh |
~5 |
for bin in modelrelay omniroute ollama hermes mnemon → for bin in 9router omniroute ollama hermes mnemon; provider config providers.modelrelay.* → providers.9router.*; fallback_providers.provider modelrelay → 9router; launch setsid /usr/local/bin/modelrelay >> /tmp/modelrelay.log → nohup /usr/local/bin/9router --host 0.0.0.0 --host 127.0.0.1 --port 7352 --no-browser --skip-update >> /tmp/9router.log |
Boot-time service rename + new 9router CLI flags (see §4) |
| 3 |
docker/start-modelrelay.sh |
26 |
Rename → docker/start-ninerouter.sh; all internal refs (ModelRelay, modelrelay, /tmp/modelrelay.log, /usr/local/bin/modelrelay, /usr/local/lib/node_modules/modelrelay) → 9router, 9router, /tmp/9router.log, /usr/local/bin/9router, /usr/local/lib/node_modules/9router |
Desktop-launcher script rename; preserves auto-restart loop semantics |
| 4 |
docker/ModelRelay.desktop |
8 |
Rename → docker/9Router.desktop; Name=ModelRelay, Exec=mate-terminal --title="ModelRelay" -e "bash -c 'modelrelay --disable; modelrelay; exec bash'" → Name=9Router, Exec=mate-terminal --title="9Router" -e "bash -c '9router --host 0.0.0.0 --host 127.0.0.1 --port 7352 --no-browser --skip-update; 9router; exec bash'" |
GUI launcher identity; preserves desktop-icon usability |
| 5 |
docker/self-check.sh |
~5 + ~15 |
7352:ModelRelay → 7352:9Router in port-poll loops and display labels; add new # 9Router block that polls http://localhost:7352/v1/models and reports model count (mirrors the existing OmniRoute block) |
Verification: port 7352 still serves + model availability check |
| 6 |
docker-compose.yml |
1 |
Comment # modelrelay specific ports → # 9Router specific ports; port mapping 7352:7352 unchanged |
Exposed port label only; no mapping change |
| 7 |
docker/pi-models.json |
~36 |
"modelrelay" provider key → "9router"; "provider": "modelrelay" → "provider": "9router"; "chain": ["modelrelay/auto-fastest"] → ["9router/auto-fastest"]; fallback baseUrl http://localhost:7352/v1 unchanged |
Pi agent failover config; keeps same REST endpoint |
| 8 |
docs/architecture.md |
~20 |
All ModelRelay / ModelRelayDash / ModelRelayProxy labels → 9Router / 9RouterDash / 9RouterProxy; class annotations; prose ModelRelay → 9Router; Fallback :7352 arrow label unchanged |
Documentation traceability |
| 9 |
README.md |
~10 |
ModelRelay shield/badge → 9Router; prose references; npm package link modelrelay → 9router; github.com/gitricko/modelrelay → github.com/decolua/9router; log path /tmp/modelrelay.log → /tmp/9router.log; providers.modelrelay → providers.9router; fallback_providers.provider modelrelay → 9router |
User-facing docs consistency |
| 10 |
docs/architecture-diagram.excalidraw |
~30 |
ModelRelay Dashboard, ModelRelay Proxy :7352 text nodes → 9Router Dashboard, 9Router Proxy :7352; element IDs (modelrelay-dash, modelrelay-api) → 9router-dash, 9router-api |
Diagram sync (IDs + labels) |
Total touched files: 10 (matches prior audit count of 10 ModelRelay-referencing files).
Files with structural renames: start-modelrelay.sh → start-ninerouter.sh, ModelRelay.desktop → 9Router.desktop.
2. Renames
| Old path |
New path |
Notes |
docker/start-modelrelay.sh |
docker/start-ninerouter.sh |
Shell script; content updated per §3 below |
docker/ModelRelay.desktop |
docker/9Router.desktop |
Desktop-entry file; content updated per §3 below |
Both renames are git mv-equivalent operations. The .desktop file's Name= field changes to 9Router so the desktop icon reads correctly. The start-ninerouter.sh filename is consistent with the existing start-omniroute.sh / start-ollama.sh naming convention.
3. npm package change
| Property |
Old |
New |
| Package name |
modelrelay |
9router |
| Version pin |
1.22.1 |
0.5.81 |
| Install prefix |
/usr/local/lib (implicit via npm install -g) |
/usr/local/lib (explicit via --prefix /usr/local/lib in post-create-cmd.sh; Dockerfile uses global npm install -g which installs under /usr/local/lib by default) |
| Binary name |
modelrelay |
9router |
| Binary path |
/usr/local/bin/modelrelay |
/usr/local/bin/9router |
| Module dir |
/usr/local/lib/node_modules/modelrelay |
/usr/local/lib/node_modules/9router |
| Source repo |
github:gitricko/modelrelay |
github:decolua/9router |
| npm registry |
npmjs.com/package/modelrelay |
npmjs.com/package/9router |
Dockerfile change (line 65–67):
# OLD
ARG MODELRELAY_VERSION=1.22.1
...
RUN npm install -g modelrelay@${MODELRELAY_VERSION} && \
npm cache clean --force
# NEW
ARG NINEROUTER_VERSION=0.5.81
...
RUN npm install -g 9router@${NINEROUTER_VERSION} && \
npm cache clean --force
Prefix note: The --prefix /usr/local/lib flag (used in post-create-cmd.sh per PR #57) ensures the package lands under /usr/local/lib/9router with the binary symlinked to /usr/local/bin/9router. In the Dockerfile, npm install -g without --prefix also resolves to /usr/local/lib on the node:slim-derived base, so the layout is identical — only the package name changes.
4. Binary launch change
Old launch pattern (setsid modelrelay --disable):
modelrelay --disable
setsid /usr/local/bin/modelrelay >> /tmp/modelrelay.log 2>&1 &
The --disable flag was ModelRelay-specific (disables its built-in dashboard/server component in one mode). The setsid wrapper detached the process from the controlling terminal.
New launch pattern (nohup 9router --host ... --port 7352 ...):
nohup /usr/local/bin/9router --host 0.0.0.0 --host 127.0.0.1 --port 7352 --no-browser --skip-update >> /tmp/9router.log 2>&1 &
Flag mapping:
| Old flag |
New flag |
Purpose |
| (none — implicit default port 7352) |
--port 7352 |
Explicit port binding (port preserved) |
| (none) |
--host 0.0.0.0 |
Bind all interfaces (container-accessible) |
| (none) |
--host 127.0.0.1 |
Also bind localhost |
--disable |
--no-browser |
Don't auto-launch browser |
| (n/a) |
--skip-update |
Skip version-check on boot |
setsid wrapper |
nohup wrapper |
Detach from controlling terminal (equivalent) |
Why nohup instead of setsid: Both detach the process, but nohup is the pattern already used by start-hermes.sh (nohup hermes gateway run ...) and start-omniroute.sh (nohup omniroute serve ...). Consistency with sibling scripts reduces cognitive load.
5. New 9router-config.sh (71 lines)
A new script docker/9router-config.sh automates the initial 9Router REST API setup. It runs after 9Router is ready (post-install, in post-create-cmd.sh and optionally at first boot). Full content (71 lines, shebang + set -euo pipefail):
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="http://localhost:7352"
COOKIE_FILE="$(mktemp)"
trap 'rm -f "$COOKIE_FILE"' EXIT
# Log in and save the session cookie
curl -fsS -c "$COOKIE_FILE" \
-X POST "$BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"password":"123456"}' | jq
# Disable dashboard login and API-key enforcement
curl -fsS -b "$COOKIE_FILE" \
-X PATCH "$BASE_URL/api/settings" \
-H "Content-Type: application/json" \
-d '{"requireLogin":false,"requireApiKey":false}' | jq '{requireLogin,requireApiKey}'
# Delete the combo if it already exists
COMBO_ID="$(curl -fsS -b "$COOKIE_FILE" "$BASE_URL/api/combos" |
jq -r '.combos[] | select(.name=="auto-fastest") | .id' | head -n 1)"
if [[ -n "$COMBO_ID" ]]; then
curl -fsS -b "$COOKIE_FILE" \
-X DELETE "$BASE_URL/api/combos/$COMBO_ID" | jq
fi
# Create auto-fastest with all free oc/ models
curl -fsS -b "$COOKIE_FILE" \
-X POST "$BASE_URL/api/combos" \
-H "Content-Type: application/json" \
-d '{
"name": "auto-fastest",
"models": [
"oc/muse-spark-1.2-contributor-free",
"oc/muse-spark-1.3-contributor-free",
"oc/union-alpha",
"oc/big-pickle",
"oc/mimo-v2.5-free",
"oc/ling-3.0-flash-fin-free",
"oc/nemotron-3-ultra-free",
"oc/nemotron-3.5-lightning-free"
]
}' | jq '{name,models}'
# Preserve other combo strategies and set auto-fastest to round-robin
STRATEGIES="$(
curl -fsS -b "$COOKIE_FILE" "$BASE_URL/api/settings" |
jq -c '
(.comboStrategies // {})
| .["auto-fastest"] = ((.["auto-fastest"] // {}) + {fallbackStrategy: "round-robin"})
'
)"
curl -fsS -b "$COOKIE_FILE" \
-X PATCH "$BASE_URL/api/settings" \
-H "Content-Type: application/json" \
-d "{\"comboStrategies\":$STRATEGIES}" | jq '.comboStrategies["auto-fastest"]'
# Test the combo
curl -fsS "$BASE_URL/v1/chat/completions" \
-H "Content-Type: application/json" \
-d '{
"model": "auto-fastest",
"messages": [
{"role": "user", "content": "Reply with exactly: OK"}
],
"stream": false,
"max_tokens": 16
}' | jq
What it does (7 steps):
- Logs in to 9Router's REST API (default creds
password:123456).
- Disables dashboard login and API-key enforcement.
- Deletes any pre-existing
auto-fastest combo (idempotent re-run).
- Creates the
auto-fastest combo with 8 free oc/ models.
- Sets
auto-fastest strategy to round-robin while preserving other combo strategies.
- Patches the settings endpoint with the updated strategies.
- Smoke-tests the combo via
/v1/chat/completions.
Integration point: Called from post-create-cmd.sh after 9Router is confirmed ready (poll http://localhost:7352/api/health up to 300 attempts).
6. Log file rename
| Old log path |
New log path |
Written by |
/tmp/modelrelay.log |
/tmp/9router.log |
start-ninerouter.sh, start-hermes.sh (launch redirect) |
/tmp/modelrelay.log (CI copy) |
/tmp/9router.log (CI copy) |
.github/workflows/devcontainer-ci.yml (failure-log archival) |
All cp /tmp/modelrelay.log /tmp/failure-logs/ → cp /tmp/9router.log /tmp/failure-logs/ in CI workflow. No other log-path references exist in the current repo.
7. Verification checklist
Phase 1 is verified when all three checks pass:
7.1 Port 7352 still serves
# After container start, confirm 9Router responds on the preserved port
curl -sf http://localhost:7352/v1/models > /dev/null && echo "✓ 9Router (7352)" || echo "✗ 9Router"
curl -sf http://localhost:7352/api/health > /dev/null && echo "✓ 9Router health" || echo "✗ 9Router health"
# self-check.sh should report "9Router" with HTTP status at :7352 (not "ModelRelay")
Expected: self-check.sh line ~105/116 shows 7352:9Router (was 7352:ModelRelay) and the new # 9Router models block populates model count from http://localhost:7352/v1/models.
7.2 Hermes fallback works
# Confirm Hermes config references 9router as fallback provider
hermes config get fallback_providers.provider # expect: "9router"
hermes config get providers.9router.base_url # expect: "http://localhost:7352/v1"
hermes config get providers.9router.api_key # expect: "no-key-needed"
# The fallback chain in pi-models.json should resolve to "9router/auto-fastest"
Expected: providers.modelrelay.* keys are absent; providers.9router.* keys exist and serve the same REST endpoint. pi-models.json "fallback.chain" resolves to ["9router/auto-fastest"].
7.3 Self-check labels
# Run self-check.sh and confirm all labels reference 9Router
bash /usr/local/bin/self-check 2>&1 | grep -E "9Router|ModelRelay"
Expected output:
- Port-poll section:
9Router (7352) — ✅ HTTP 200 (Xs)
- Models section:
9Router — N models available (default: auto-fastest) + json_add "models" "ok" ... with "default":"9router"
- No
ModelRelay strings appear anywhere in the output
- Exit code 0 (all services responding) or 1 (warnings only, no ModelRelay references)
8. Cross-reference: PR #57 mapping
PR #57 targeted .devcontainer/ paths (post-create-cmd.sh, start-hermes.sh, self-check.sh, .pi, pi-config/, wiki). This repo's layout differs:
| PR #57 path |
This repo equivalent |
Phase 1 action |
.devcontainer/post-create-cmd.sh |
docker/start-hermes.sh + docker/start-ninerouter.sh |
Merge into start-hermes.sh boot section + new start-ninerouter.sh |
.devcontainer/start-hermes.sh |
docker/start-hermes.sh |
In-place edit (§1 row 2) |
.devcontainer/self-check.sh |
docker/self-check.sh |
In-place edit (§1 row 5) |
.devcontainer/9router-config.sh (new) |
docker/9router-config.sh (new) |
Create (§5) |
.devcontainer/pi-config/models.json |
docker/pi-models.json |
In-place edit (§1 row 7) |
.devcontainer/pi-config/settings.json |
docker/pi-settings.json |
Not in Phase 1 scope (see §9) |
.devcontainer/wiki/* |
docs/architecture.md, README.md |
In-place edit (§1 rows 8–9) |
.github/workflows/devcontainer-ci.yml |
(not present in docker/ layer) |
Not in Phase 1 scope |
.devcontainer/Dockerfile |
docker/Dockerfile |
In-place edit (§1 row 1) |
.devcontainer/ModelRelay.desktop |
docker/ModelRelay.desktop |
Rename (§2) |
9. Out of scope (Phase 2+)
docker/pi-settings.json — pi-failover@hermes-impl → pi-failover@main and defaultProjectTrust addition (touched in PR #57 but not present in current docker/ layout).
.github/workflows/devcontainer-ci.yml — CI log-path rename (cp /tmp/modelrelay.log → cp /tmp/9router.log). Only relevant if CI config moves into this repo.
.devcontainer/ layer — the .devcontainer/ directory in this repo contains devcontainer.json, Makefile, globalState.json, post-create.sh, secrets.json, free-disk.sh (per audit); these are NOT the same files as PR #57's .devcontainer/ (which had post-create-cmd.sh, start-hermes.sh, etc.). The .devcontainer/ layer follows a different structure and is handled separately.
docs/architecture-diagram.excalidraw — ID remap (modelrelay-* → 9router-*) is included in Phase 1 (row 10) but the excalidraw JSON is large; verify with a targeted jq filter.
10. Success criteria summary
| Criterion |
Verify command |
Pass condition |
| Port 7352 serves |
curl -sf http://localhost:7352/v1/models |
HTTP 200 |
| No ModelRelay refs |
rg -i modelrelay docker/ docker-compose.yml README.md docs/ |
Empty (or only in comments noting the rename) |
| 9Router binary runs |
9router --version / 9router --help |
Binary found, no --disable flag |
| Hermes fallback config |
hermes config get fallback_providers.provider |
Returns 9router |
| self-check passes |
bash /usr/local/bin/self-check |
Exit 0/1, all labels 9Router, no ModelRelay |
| Desktop launcher works |
cat docker/9Router.desktop |
Name=9Router, Exec=...9router... |
| Log path correct |
grep -r "9router.log" docker/ |
All modelrelay.log → 9router.log |
Phase 1 complete → proceed to Phase 2 (pi-failover branch fix, .agents/skills addition) once verified.
Phase 2: PI Branch Fix + Dependency Bumps
Version Pins Found
| File:Line |
Current |
Proposed |
docker/start-pi.sh:32 |
git:github.com/gitricko/pi-failover@hermes-impl |
git:github.com/gitricko/pi-failover@main |
docker/pi-settings.json:5 |
git:github.com/gitricko/pi-failover@hermes-impl |
git:github.com/gitricko/pi-failover@main |
docker/Dockerfile:7 |
PI_VERSION=0.85.1 |
PI_VERSION=0.87.1 |
docker/Dockerfile:5 |
NODE_VERSION=26.7.0 |
NODE_VERSION=26.10.0 |
docker/Dockerfile:3 |
MODELRELAY_VERSION=1.22.1 |
MODELRELAY_VERSION=1.22.2 (includes Ninerouter 0.5.81 bump) |
docker/Dockerfile:9 |
HERDR_VERSION=0.7.4 |
leave 0.7.4 SHA-pinned (unless deliberate) |
Priority Fix: hermes-impl → main (critical)
docker/start-pi.sh:32 and docker/pi-settings.json:5 reference git:github.com/gitricko/pi-failover@hermes-impl, but hermes-impl is a non-existing ref. This breaks pi install entirely — the PI coding agent cannot be installed.
- Fix: bump both lines to
@main so the fallback extension resolves against the default branch.
- This fix must come first in any dependency-update sequence.
Bump Order (recommended)
- Critical branch fix —
hermes-impl → main in start-pi.sh:32 and pi-settings.json:5 (enables PI install)
- 9Router / OmniRoute —
OMNIROUTE_VERSION=3.8.50 stays (already current; no Ninerouter change needed at this step)
- PI / Node —
PI_VERSION=0.87.1, NODE_VERSION=26.10.0
- ModelRelay —
MODELRELAY_VERSION=1.22.2 (drags in Ninerouter 0.5.81 as a transitive dependency bump)
- HERDR — leave
0.7.4 SHA-pinned in scripts/fm-install-herdr.sh (protocol-16 release; only bump with a re-verified real-Herdr matrix)
Verification (remote-only)
docker build on the remote builder only — no local Node/Ollama build required.
- Confirm
pi install succeeds after the hermes-impl → main bump (PI agent installs and registers).
- Confirm no disk-space local build is needed: all bumps are
npm install -g or Dockerfile ARG bumps that resolve from the network.
Proposal generated from repo scan of /workspaces/hermes-webtop. All version pins verified against Dockerfile and docker/ scripts.
Phase 3 Proposal — .agents/skills for hermes-webtop
Issue #52 follow-up. Prior audit confirmed .agents/ did not exist in this repo. Firstmate (firstmate-codespace, https://github.com/gitricko/firstmate-codespace/tree/main/.agents/skills) carries 21 skills but they are tightly coupled to the Firstmate fleet model (multi-node orchestration, fleet.yaml, herdr session backends, firstmate CLI) and are not portable. We created .agents/skills/parallel-delegation/ as the first hermes-webtop skill. This proposal defines the remaining skill set tailored to hermes-webtop's actual stack.
Workspace path: /workspaces/hermes-webtop/.devcontainer
1. Why NOT to copy firstmate skills wholesale
| Firstmate assumption |
hermes-webtop reality |
Consequence of blind copy |
Fleet of Codespaces / fleet.yaml + firstmate spawn |
Single container hermes-webtop (LinuxServer webtop + docker-compose.yml) |
Fleet orchestration, health, and deploy skills have no target |
herdr as primary pane backend, tmux fallback, fleet-aware session naming |
herdr installed via docker/scripts/fm-install-herdr.sh but used only as one startup primitive among many; no fleet |
Session-management skills assume fleet context |
firstmate CLI owns lifecycle |
make start / docker compose owns lifecycle; CI is docker-publish.yml with jlumbroso/free-disk-space + self-check |
Build/deploy skill wiring is wrong |
Skills assume firstmate-codespace repo layout (fleet/, scripts/fm-*) |
Repo layout is docker/ + .devcontainer/ + docs/ |
File paths in skill bodies would all 404 |
Principle adopted for Phase 3: every proposed skill must map 1:1 to a file that already exists in this repo (see §4). No fleet abstractions, no speculative tooling. If the repo doesn't have it, the skill doesn't need it.
2. Directory layout
.agents/
└── skills/
├── parallel-delegation/ # ✅ DONE — general orchestration discipline
│ └── SKILL.md
├── docker-build-remote-only/ # NEW
│ └── SKILL.md
├── hermes-self-check/ # NEW
│ └── SKILL.md
├── hermes-startup/ # NEW
│ └── SKILL.md
├── hermes-memory-automation/ # NEW (promote docker/skill-memory-automation.md)
│ └── SKILL.md
└── hermes-dependency-scan/ # NEW
└── SKILL.md
- One folder per skill, folder name = skill
name (kebab-case). Single file SKILL.md per skill — matches the parallel-delegation precedent and the firstmate pattern.
- No
references/, scripts/, templates/ subdirs until a skill actually needs them (YAGNI). Add when a skill grows a checklist or helper script.
.agents/skills/ is intentionally at repo root so both the host (Codespace devcontainer) and the container (mounted via .:/codespace in docker-compose.yml) see the same skills.
3. Frontmatter convention (enforced for all 6 skills)
All SKILL.md files MUST share the frontmatter below, matching the parallel-delegation precedent:
---
name: <kebab-case, == folder name>
description: "Use when <trigger condition>. <one-line behavior>."
version: 1.0.0 # semver; bump on rule changes
author: hermes-webtop
license: MIT
platforms: [linux, macos, windows]
tags:
- <primary-domain>
- <secondary>
related_skills: # optional; cross-link within .agents/skills/
- <other-skill-name>
metadata:
hermes:
tags: [<same as tags>]
related_skills: [<same as related_skills>]
---
Rules:
name — kebab-case, stable identifier. Used by skill_view(name='...') and by agent instructions (load the hermes-self-check skill).
description — Must start with Use when — this is the agent's retrieval trigger. Keep to one sentence + one clause. Example: Use when running or validating the hermes-webtop container health. Run self-check and interpret its exit codes.
tags — 2–5 terms, lowercase, hyphens only. Mirror them in metadata.hermes.tags for tooling parity.
related_skills — optional but recommended for the hermes-webtop cluster so the agent discovers lateral skills.
4. Proposed skill set (6 total; 1 done + 5 new)
4.1 parallel-delegation — ✅ Already done
- Status: Implemented at
.agents/skills/parallel-delegation/SKILL.md (197 lines).
- Purpose: Main-agent orchestration discipline — fan out
delegate_task for independent work, stay unblocked, consume results out-of-order.
- Maps to: No single
docker/ file — cross-cutting workflow used e.g. to audit docker/start-*.sh scripts in parallel.
- Frontmatter:
description: "Use when the main agent faces independent tasks. Delegate in parallel via delegate_task to stay unblocked." — already conforms to Use when rule.
4.2 docker-build-remote-only — NEW (highest priority)
- Trigger:
Use when building or testing the hermes-webtop Docker image. Never build locally; use CI or remote builders.
- Why hermes-webtop-specific: Local
docker build exhausts Codespace disk (32 GB). Past incidents forced manual recovery.
- Maps to:
Makefile:97-98 — docker-build: docker build -t $(DOCKER_IMAGE_NAME) -f ./docker/Dockerfile ./docker (the forbidden local path)
docker/Dockerfile (126 lines) — multi-stage build (ollama-bin, node-bin, linuxserver/webtop base, Hermes/ModelRelay/OmniRoute/Pi/code-server/mnemon/herdr layers)
.devcontainer/free-disk.sh (483 lines) — the only safe pre-build step; reclaims space (android, dotnet, haskell, conda, llvm, nvm, java, ruby, swap)
.github/workflows/docker-publish.yml:44-59 — canonical remote build (free-disk → build → smoke-test → push to GHCR with buildx + QEMU for linux/amd64,linux/arm64)
.devcontainer/devcontainer.json:8 — postCreateCommand: bash ./.devcontainer/free-disk.sh (auto-free on Codespace create)
- Skill body outline:
docker build locally → forbidden — explain disk exhaustion, show df -h pre-check.
- If you must validate before push:
sudo bash .devcontainer/free-disk.sh --dry-run → --force, then docker build only if df shows >20 GB free — otherwise push a branch and let CI build.
- Canonical remote path:
git push → docker-publish.yml (build-test job) → push-to-ghcr job; or workflow_dispatch with manual_push: yes for :test tag.
- Verification: CI runs
self-check inside the container (exit 0/1 pass, 2 fail) — don't re-implement locally.
4.3 hermes-self-check — NEW
- Trigger:
Use when validating hermes-webtop health after startup or in CI. Poll service ports, check models/mnemon/hermes config, and interpret exit codes.
- Maps to:
docker/self-check.sh (487 lines) — the single source of truth; 8 check sections (Services, Models, Mnemon, Hermes, Disk, Memory, Cron, Ollama), JSON report at /tmp/health-report.json, Telegram delivery auto-discovered from ~/.hermes/.env / config.yaml
docker/start-hermes.sh:118 — self-check || echo WARNING (boot-time invocation)
.github/workflows/docker-publish.yml:82-118,249-285 — CI smoke test (docker exec -u abc self-check; exit 2 = fail, 1 = warn-only pass)
- Skill body outline:
- How to run:
self-check (installed at /usr/local/bin/self-check via Dockerfile:51) or docker exec -u abc hermes-webtop self-check.
- Threshold env vars:
HERMES_WEBTOP_SKIP_CHECKS, HERMES_WEBTOP_DISK_WARN_PCT, HERMES_WEBTOP_CRITICAL_SERVICES.
- Exit-code contract:
0 all ok, 1 warnings, 2 critical — CI only fails on 2.
- Debugging:
cat /tmp/health-report.json, per-section curl checks (:3000, :8888, :7352, :20128, :9119, :11434).
4.4 hermes-startup — NEW
- Trigger:
Use when starting, stopping, or debugging the hermes-webtop container and its boot sequence.
- Maps to:
docker/start-hermes.sh (124 lines) — chown, ~/.hermes init (model.default auto-fastest, provider omniroute, memory/mnemon, kanban), mnemon plugin sync, hermes gateway + hermes dashboard + socat :9119→:9009, self-check at end
docker/start-*.sh family — start-omniroute.sh (OmniRoute auto-fastest combo, requireLogin false), start-codeserver.sh (code-server + 3 extensions), start-ollama.sh, start-modelrelay.sh, start-pi.sh, start-tailscale.sh, start-ohmyzsh.sh
docker/common.sh (85 lines) — sync_desktop_file, ensure_ownership (used by every start script)
docker-compose.yml (64 lines) — ports 3000/9119/8642/7352/20128/8888, volume hermes-webtop-config:/config, .:/codespace
Makefile:26-55 — make start, make start-codespace (-f docker-compose.yml -f docker-compose.local.yml), make stop, make backup/restore
.devcontainer/devcontainer.json — postStartCommand: $HOME/.minions/boot.sh
- Skill body outline:
- Lifecycle:
make start (local), make start-codespace (Codespace overlay), make stop, make backup/restore.
- Boot order inside container (
/custom-cont-init.d/*.sh alphabetical, start-hermes.sh backgrounds itself with sleep 5 guard).
- Debugging:
docker compose logs -f, docker exec hermes-webtop cat /tmp/*.log, ~/.hermes/logs/{gateway,dashboard,socat-9119}.log.
- Ownership pitfall: everything runs as
abc; never chown as root inside /config.
4.5 hermes-memory-automation — NEW (promotion)
- Trigger:
Use when working with Hermes persistent memory via Mnemon. Recall on start, recall before each turn, save after each response.
- Maps to:
docker/skill-memory-automation.md (248 lines) — existing skill file currently baked into the image at /custom-cont-init.d/skill-memory-automation.md; installed by start-hermes.sh:48-49 to ~/.hermes/skills/memory-automation/SKILL.md
docker/.hermes.md (12 lines) — Before Every Turn / After Every Response — Two-Tier Save — the in-container instruction that references this skill
docker/Dockerfile:97-111 — mnemon binary install (MNEMON_VERSION, TARGETARCH handling)
docker/start-hermes.sh:62-79,95-98 — Mnemon plugin sync from gitricko/hermes-plugin-mnemon, USER.md priming
.github/workflows/docker-publish.yml:120-146,287-313 — Mnemon integration test (Hermes remembers → mnemon recall → Claude retrieves)
- Skill body outline: Promote
docker/skill-memory-automation.md verbatim into .agents/skills/hermes-memory-automation/SKILL.md (already has correct frontmatter name: memory-automation, description: "Automated mnemon memory persistence workflow …" — rename name to hermes-memory-automation to match folder or keep alias via related_skills). Add a header note explaining the two locations (repo .agents/ = source of truth for agents coding the repo; container ~/.hermes/skills/ = runtime for agents inside the webtop) and the sync direction (repo → Dockerfile COPY → container).
4.6 hermes-dependency-scan — NEW
- Trigger:
Use when auditing or bumping hermes-webtop dependency versions. Check Dockerfile ARGs, npm globals, and system packages.
- Maps to:
docker/Dockerfile:1-10 — 9 pinned ARG versions (HERMES_VERSION, OMNIROUTE_VERSION, MODELRELAY_VERSION, OLLAMA_VERSION, NODE_VERSION, CODE_SERVER_VERSION, PI_VERSION, MNEMON_VERSION, HERDR_VERSION) — each is a bump guard
docker/scripts/fm-install-herdr.sh — SHA-256 pinned herdr installer; Dockerfile:123 asserts herdr --version == HERDR_VERSION
docker/start-codeserver.sh:35,43 — hardcoded VSIX versions for hermes-code-agent (VERSION=3.0.3), saoudrizwan.claude-dev, anthropic.claude-code with inline claude/install.sh
docker/pi-models.json, docker/pi-settings.json, docker/claude-vscode-settings.json — extension/model manifests with implicit version coupling
.github/dependabot.yml — verified present: monitors docker and github-actions ecosystems weekly. Gap: no npm ecosystem entry, so npm install -g globals in the Dockerfile (modelrelay, omniroute, pi-coding-agent) and VSIX versions in start-codeserver.sh are invisible to Dependabot — the skill must cover them manually.
Makefile / docker-compose.yml — no version pinning but affected by base image lscr.io/linuxserver/webtop:ubuntu-mate
- Skill body outline:
- Inventory: list all 9
ARG versions + herdr installer SHA + code-server extension VSIX versions in start-codeserver.sh:35,43.
- Bump procedure: update
ARG → update HERDR_VERSION guard → update fm-install-herdr.sh pin → smoke-test via docker-publish.yml build-test job (never locally per docker-build-remote-only).
- Scan:
grep -R ARG.*VERSION docker/Dockerfile, grep -R VERSION.*= docker/start-*.sh, check GHCR/base-image advisories. Manually check npm globals since Dependabot does not cover them.
5. What is intentionally excluded (and why)
- Fleet / multi-node skills (
fleet-deploy, fleet-health, herdr-fleet) — no fleet in this repo.
firstmate CLI skills — hermes-webtop uses hermes + docker compose + make; firstmate is not installed in the image.
- Generic
code-review, tdd, systematic-debugging — already available as global Hermes skills; no need to duplicate under .agents/.
- Per-language lint/format skills — no
pyproject.toml / eslint config in repo; out of scope until the codebase grows beyond shell + Docker.
6. Implementation checklist for Phase 3
7. References
- Prior audit:
.agents/ did not exist; firstmate https://github.com/gitricko/firstmate-codespace/tree/main/.agents/skills (21 skills, fleet-coupled).
- Existing skill:
.agents/skills/parallel-delegation/SKILL.md (created Phase 2).
- Source-of-truth files for each new skill are listed in §4.2–4.6 with line counts.
Objective
modelrelayand change to use9routertransparently.Implementation Proposal
Proposed by: Hermes Agent (via #52 audit)
Reference: PR #57 (merged, gitricko/hermes-codespace) — canonical renames and config changes.
Design principle: Karpathy — minimal, surgical, verified-by-real-output.
Phase 1 Proposal: ModelRelay → 9Router (port 7352 stays)
Issue: #52 — Migrate the use of ModelRelay to 9Router, keeping port 7352 unchanged.
Reference: PR #57 (merged,
gitricko/hermes-codespace) — provides the canonical renames, config changes, and verification expectations.Scope: Docker container layer only (
docker/,docker-compose.yml,docs/,README.md). The.devcontainer/layer (PR #57's primary target) is handled separately; Phase 1 mirrors its intent onto the currentdocker/layout.Design principle: Karpathy — surgical, minimal, verified. Touch only what must change; port 7352 is preserved end-to-end.
1. File-by-file change table
All paths relative to repo root
/workspaces/hermes-webtop.docker/DockerfileARG MODELRELAY_VERSION=1.22.1→ARG NINEROUTER_VERSION=0.5.81;npm install -g modelrelay@${MODELRELAY_VERSION}→npm install -g 9router@${NINEROUTER_VERSION}/usr/local/libretained (see §3)docker/start-hermes.shfor bin in modelrelay omniroute ollama hermes mnemon→for bin in 9router omniroute ollama hermes mnemon; provider configproviders.modelrelay.*→providers.9router.*;fallback_providers.provider modelrelay→9router; launchsetsid /usr/local/bin/modelrelay >> /tmp/modelrelay.log→nohup /usr/local/bin/9router --host 0.0.0.0 --host 127.0.0.1 --port 7352 --no-browser --skip-update >> /tmp/9router.logdocker/start-modelrelay.shdocker/start-ninerouter.sh; all internal refs (ModelRelay,modelrelay,/tmp/modelrelay.log,/usr/local/bin/modelrelay,/usr/local/lib/node_modules/modelrelay) →9router,9router,/tmp/9router.log,/usr/local/bin/9router,/usr/local/lib/node_modules/9routerdocker/ModelRelay.desktopdocker/9Router.desktop;Name=ModelRelay,Exec=mate-terminal --title="ModelRelay" -e "bash -c 'modelrelay --disable; modelrelay; exec bash'"→Name=9Router,Exec=mate-terminal --title="9Router" -e "bash -c '9router --host 0.0.0.0 --host 127.0.0.1 --port 7352 --no-browser --skip-update; 9router; exec bash'"docker/self-check.sh7352:ModelRelay→7352:9Routerin port-poll loops and display labels; add new# 9Routerblock that pollshttp://localhost:7352/v1/modelsand reports model count (mirrors the existing OmniRoute block)docker-compose.yml# modelrelay specific ports→# 9Router specific ports; port mapping7352:7352unchangeddocker/pi-models.json"modelrelay"provider key →"9router";"provider": "modelrelay"→"provider": "9router";"chain": ["modelrelay/auto-fastest"]→["9router/auto-fastest"]; fallbackbaseUrlhttp://localhost:7352/v1unchangeddocs/architecture.mdModelRelay/ModelRelayDash/ModelRelayProxylabels →9Router/9RouterDash/9RouterProxy; class annotations; proseModelRelay→9Router;Fallback :7352arrow label unchangedREADME.mdModelRelayshield/badge →9Router; prose references;npmpackage linkmodelrelay→9router;github.com/gitricko/modelrelay→github.com/decolua/9router; log path/tmp/modelrelay.log→/tmp/9router.log;providers.modelrelay→providers.9router;fallback_providers.provider modelrelay→9routerdocs/architecture-diagram.excalidrawModelRelay Dashboard,ModelRelay Proxy :7352text nodes →9Router Dashboard,9Router Proxy :7352; element IDs (modelrelay-dash,modelrelay-api) →9router-dash,9router-apiTotal touched files: 10 (matches prior audit count of 10 ModelRelay-referencing files).
Files with structural renames:
start-modelrelay.sh→start-ninerouter.sh,ModelRelay.desktop→9Router.desktop.2. Renames
docker/start-modelrelay.shdocker/start-ninerouter.shdocker/ModelRelay.desktopdocker/9Router.desktopBoth renames are
git mv-equivalent operations. The.desktopfile'sName=field changes to9Routerso the desktop icon reads correctly. Thestart-ninerouter.shfilename is consistent with the existingstart-omniroute.sh/start-ollama.shnaming convention.3. npm package change
modelrelay9router1.22.10.5.81/usr/local/lib(implicit vianpm install -g)/usr/local/lib(explicit via--prefix /usr/local/libinpost-create-cmd.sh; Dockerfile uses globalnpm install -gwhich installs under/usr/local/libby default)modelrelay9router/usr/local/bin/modelrelay/usr/local/bin/9router/usr/local/lib/node_modules/modelrelay/usr/local/lib/node_modules/9routergithub:gitricko/modelrelaygithub:decolua/9routernpmjs.com/package/modelrelaynpmjs.com/package/9routerDockerfile change (line 65–67):
Prefix note: The
--prefix /usr/local/libflag (used inpost-create-cmd.shper PR #57) ensures the package lands under/usr/local/lib/9routerwith the binary symlinked to/usr/local/bin/9router. In the Dockerfile,npm install -gwithout--prefixalso resolves to/usr/local/libon thenode:slim-derived base, so the layout is identical — only the package name changes.4. Binary launch change
Old launch pattern (
setsid modelrelay --disable):The
--disableflag was ModelRelay-specific (disables its built-in dashboard/server component in one mode). Thesetsidwrapper detached the process from the controlling terminal.New launch pattern (
nohup 9router --host ... --port 7352 ...):Flag mapping:
--port 7352--host 0.0.0.0--host 127.0.0.1--disable--no-browser--skip-updatesetsidwrappernohupwrapperWhy
nohupinstead ofsetsid: Both detach the process, butnohupis the pattern already used bystart-hermes.sh(nohup hermes gateway run ...) andstart-omniroute.sh(nohup omniroute serve ...). Consistency with sibling scripts reduces cognitive load.5. New
9router-config.sh(71 lines)A new script
docker/9router-config.shautomates the initial 9Router REST API setup. It runs after 9Router is ready (post-install, inpost-create-cmd.shand optionally at first boot). Full content (71 lines, shebang +set -euo pipefail):What it does (7 steps):
password:123456).auto-fastestcombo (idempotent re-run).auto-fastestcombo with 8 freeoc/models.auto-fasteststrategy toround-robinwhile preserving other combo strategies./v1/chat/completions.Integration point: Called from
post-create-cmd.shafter 9Router is confirmed ready (pollhttp://localhost:7352/api/healthup to 300 attempts).6. Log file rename
/tmp/modelrelay.log/tmp/9router.logstart-ninerouter.sh,start-hermes.sh(launch redirect)/tmp/modelrelay.log(CI copy)/tmp/9router.log(CI copy).github/workflows/devcontainer-ci.yml(failure-log archival)All
cp /tmp/modelrelay.log /tmp/failure-logs/→cp /tmp/9router.log /tmp/failure-logs/in CI workflow. No other log-path references exist in the current repo.7. Verification checklist
Phase 1 is verified when all three checks pass:
7.1 Port 7352 still serves
Expected:
self-check.shline ~105/116 shows7352:9Router(was7352:ModelRelay) and the new# 9Routermodels block populates model count fromhttp://localhost:7352/v1/models.7.2 Hermes fallback works
Expected:
providers.modelrelay.*keys are absent;providers.9router.*keys exist and serve the same REST endpoint.pi-models.json"fallback.chain"resolves to["9router/auto-fastest"].7.3 Self-check labels
Expected output:
9Router (7352) — ✅ HTTP 200 (Xs)9Router — N models available (default: auto-fastest)+json_add "models" "ok" ...with"default":"9router"ModelRelaystrings appear anywhere in the output8. Cross-reference: PR #57 mapping
PR #57 targeted
.devcontainer/paths (post-create-cmd.sh,start-hermes.sh,self-check.sh,.pi,pi-config/, wiki). This repo's layout differs:.devcontainer/post-create-cmd.shdocker/start-hermes.sh+docker/start-ninerouter.shstart-hermes.shboot section + newstart-ninerouter.sh.devcontainer/start-hermes.shdocker/start-hermes.sh.devcontainer/self-check.shdocker/self-check.sh.devcontainer/9router-config.sh(new)docker/9router-config.sh(new).devcontainer/pi-config/models.jsondocker/pi-models.json.devcontainer/pi-config/settings.jsondocker/pi-settings.json.devcontainer/wiki/*docs/architecture.md,README.md.github/workflows/devcontainer-ci.yml.devcontainer/Dockerfiledocker/Dockerfile.devcontainer/ModelRelay.desktopdocker/ModelRelay.desktop9. Out of scope (Phase 2+)
docker/pi-settings.json—pi-failover@hermes-impl→pi-failover@mainanddefaultProjectTrustaddition (touched in PR #57 but not present in currentdocker/layout)..github/workflows/devcontainer-ci.yml— CI log-path rename (cp /tmp/modelrelay.log→cp /tmp/9router.log). Only relevant if CI config moves into this repo..devcontainer/layer — the.devcontainer/directory in this repo containsdevcontainer.json,Makefile,globalState.json,post-create.sh,secrets.json,free-disk.sh(per audit); these are NOT the same files as PR #57's.devcontainer/(which hadpost-create-cmd.sh,start-hermes.sh, etc.). The.devcontainer/layer follows a different structure and is handled separately.docs/architecture-diagram.excalidraw— ID remap (modelrelay-*→9router-*) is included in Phase 1 (row 10) but the excalidraw JSON is large; verify with a targeted jq filter.10. Success criteria summary
curl -sf http://localhost:7352/v1/modelsrg -i modelrelay docker/ docker-compose.yml README.md docs/9router --version/9router --help--disableflaghermes config get fallback_providers.provider9routerbash /usr/local/bin/self-check9Router, noModelRelaycat docker/9Router.desktopName=9Router,Exec=...9router...grep -r "9router.log" docker/Phase 1 complete → proceed to Phase 2 (pi-failover branch fix,
.agents/skillsaddition) once verified.Phase 2: PI Branch Fix + Dependency Bumps
Version Pins Found
docker/start-pi.sh:32git:github.com/gitricko/pi-failover@hermes-implgit:github.com/gitricko/pi-failover@maindocker/pi-settings.json:5git:github.com/gitricko/pi-failover@hermes-implgit:github.com/gitricko/pi-failover@maindocker/Dockerfile:7PI_VERSION=0.85.1PI_VERSION=0.87.1docker/Dockerfile:5NODE_VERSION=26.7.0NODE_VERSION=26.10.0docker/Dockerfile:3MODELRELAY_VERSION=1.22.1MODELRELAY_VERSION=1.22.2(includes Ninerouter 0.5.81 bump)docker/Dockerfile:9HERDR_VERSION=0.7.4Priority Fix: hermes-impl → main (critical)
docker/start-pi.sh:32anddocker/pi-settings.json:5referencegit:github.com/gitricko/pi-failover@hermes-impl, buthermes-implis a non-existing ref. This breakspi installentirely — the PI coding agent cannot be installed.@mainso the fallback extension resolves against the default branch.Bump Order (recommended)
hermes-impl→maininstart-pi.sh:32andpi-settings.json:5(enables PI install)OMNIROUTE_VERSION=3.8.50stays (already current; no Ninerouter change needed at this step)PI_VERSION=0.87.1,NODE_VERSION=26.10.0MODELRELAY_VERSION=1.22.2(drags in Ninerouter 0.5.81 as a transitive dependency bump)0.7.4SHA-pinned inscripts/fm-install-herdr.sh(protocol-16 release; only bump with a re-verified real-Herdr matrix)Verification (remote-only)
docker buildon the remote builder only — no local Node/Ollama build required.pi installsucceeds after thehermes-impl→mainbump (PI agent installs and registers).npm install -gor Dockerfile ARG bumps that resolve from the network.Proposal generated from repo scan of
/workspaces/hermes-webtop. All version pins verified against Dockerfile and docker/ scripts.Phase 3 Proposal —
.agents/skillsfor hermes-webtopWorkspace path:
/workspaces/hermes-webtop/.devcontainer1. Why NOT to copy firstmate skills wholesale
fleet.yaml+firstmate spawnhermes-webtop(LinuxServer webtop +docker-compose.yml)herdras primary pane backend,tmuxfallback, fleet-aware session namingherdrinstalled viadocker/scripts/fm-install-herdr.shbut used only as one startup primitive among many; no fleetfirstmateCLI owns lifecyclemake start/docker composeowns lifecycle; CI isdocker-publish.ymlwithjlumbroso/free-disk-space+self-checkfirstmate-codespacerepo layout (fleet/,scripts/fm-*)docker/+.devcontainer/+docs/Principle adopted for Phase 3: every proposed skill must map 1:1 to a file that already exists in this repo (see §4). No fleet abstractions, no speculative tooling. If the repo doesn't have it, the skill doesn't need it.
2. Directory layout
name(kebab-case). Single fileSKILL.mdper skill — matches theparallel-delegationprecedent and the firstmate pattern.references/,scripts/,templates/subdirs until a skill actually needs them (YAGNI). Add when a skill grows a checklist or helper script..agents/skills/is intentionally at repo root so both the host (Codespace devcontainer) and the container (mounted via.:/codespaceindocker-compose.yml) see the same skills.3. Frontmatter convention (enforced for all 6 skills)
All
SKILL.mdfiles MUST share the frontmatter below, matching theparallel-delegationprecedent:Rules:
name— kebab-case, stable identifier. Used byskill_view(name='...')and by agent instructions (load the hermes-self-check skill).description— Must start withUse when— this is the agent's retrieval trigger. Keep to one sentence + one clause. Example:Use when running or validating the hermes-webtop container health. Run self-check and interpret its exit codes.tags— 2–5 terms, lowercase, hyphens only. Mirror them inmetadata.hermes.tagsfor tooling parity.related_skills— optional but recommended for the hermes-webtop cluster so the agent discovers lateral skills.4. Proposed skill set (6 total; 1 done + 5 new)
4.1
parallel-delegation— ✅ Already done.agents/skills/parallel-delegation/SKILL.md(197 lines).delegate_taskfor independent work, stay unblocked, consume results out-of-order.docker/file — cross-cutting workflow used e.g. to auditdocker/start-*.shscripts in parallel.description: "Use when the main agent faces independent tasks. Delegate in parallel via delegate_task to stay unblocked."— already conforms toUse whenrule.4.2
docker-build-remote-only— NEW (highest priority)Use when building or testing the hermes-webtop Docker image. Never build locally; use CI or remote builders.docker buildexhausts Codespace disk (32 GB). Past incidents forced manual recovery.Makefile:97-98—docker-build: docker build -t $(DOCKER_IMAGE_NAME) -f ./docker/Dockerfile ./docker(the forbidden local path)docker/Dockerfile(126 lines) — multi-stage build (ollama-bin, node-bin, linuxserver/webtop base, Hermes/ModelRelay/OmniRoute/Pi/code-server/mnemon/herdr layers).devcontainer/free-disk.sh(483 lines) — the only safe pre-build step; reclaims space (android,dotnet,haskell,conda,llvm,nvm,java,ruby,swap).github/workflows/docker-publish.yml:44-59— canonical remote build (free-disk → build → smoke-test → push to GHCR withbuildx+QEMUforlinux/amd64,linux/arm64).devcontainer/devcontainer.json:8—postCreateCommand: bash ./.devcontainer/free-disk.sh(auto-free on Codespace create)docker buildlocally → forbidden — explain disk exhaustion, showdf -hpre-check.sudo bash .devcontainer/free-disk.sh --dry-run→--force, thendocker buildonly ifdfshows >20 GB free — otherwise push a branch and let CI build.git push→docker-publish.yml(build-testjob) →push-to-ghcrjob; orworkflow_dispatchwithmanual_push: yesfor:testtag.self-checkinside the container (exit 0/1 pass, 2 fail) — don't re-implement locally.4.3
hermes-self-check— NEWUse when validating hermes-webtop health after startup or in CI. Poll service ports, check models/mnemon/hermes config, and interpret exit codes.docker/self-check.sh(487 lines) — the single source of truth; 8 check sections (Services, Models, Mnemon, Hermes, Disk, Memory, Cron, Ollama), JSON report at/tmp/health-report.json, Telegram delivery auto-discovered from~/.hermes/.env/config.yamldocker/start-hermes.sh:118—self-check || echo WARNING(boot-time invocation).github/workflows/docker-publish.yml:82-118,249-285— CI smoke test (docker exec -u abc self-check; exit 2 = fail, 1 = warn-only pass)self-check(installed at/usr/local/bin/self-checkviaDockerfile:51) ordocker exec -u abc hermes-webtop self-check.HERMES_WEBTOP_SKIP_CHECKS,HERMES_WEBTOP_DISK_WARN_PCT,HERMES_WEBTOP_CRITICAL_SERVICES.0all ok,1warnings,2critical — CI only fails on2.cat /tmp/health-report.json, per-section curl checks (:3000,:8888,:7352,:20128,:9119,:11434).4.4
hermes-startup— NEWUse when starting, stopping, or debugging the hermes-webtop container and its boot sequence.docker/start-hermes.sh(124 lines) — chown,~/.hermesinit (model.default auto-fastest,provider omniroute, memory/mnemon, kanban),mnemonplugin sync,hermes gateway+hermes dashboard+socat :9119→:9009,self-checkat enddocker/start-*.shfamily —start-omniroute.sh(OmniRouteauto-fastestcombo,requireLogin false),start-codeserver.sh(code-server + 3 extensions),start-ollama.sh,start-modelrelay.sh,start-pi.sh,start-tailscale.sh,start-ohmyzsh.shdocker/common.sh(85 lines) —sync_desktop_file,ensure_ownership(used by every start script)docker-compose.yml(64 lines) — ports3000/9119/8642/7352/20128/8888, volumehermes-webtop-config:/config,.:/codespaceMakefile:26-55—make start,make start-codespace(-f docker-compose.yml -f docker-compose.local.yml),make stop,make backup/restore.devcontainer/devcontainer.json—postStartCommand: $HOME/.minions/boot.shmake start(local),make start-codespace(Codespace overlay),make stop,make backup/restore./custom-cont-init.d/*.shalphabetical,start-hermes.shbackgrounds itself withsleep 5guard).docker compose logs -f,docker exec hermes-webtop cat /tmp/*.log,~/.hermes/logs/{gateway,dashboard,socat-9119}.log.abc; neverchownas root inside/config.4.5
hermes-memory-automation— NEW (promotion)Use when working with Hermes persistent memory via Mnemon. Recall on start, recall before each turn, save after each response.docker/skill-memory-automation.md(248 lines) — existing skill file currently baked into the image at/custom-cont-init.d/skill-memory-automation.md; installed bystart-hermes.sh:48-49to~/.hermes/skills/memory-automation/SKILL.mddocker/.hermes.md(12 lines) —Before Every Turn/After Every Response — Two-Tier Save— the in-container instruction that references this skilldocker/Dockerfile:97-111—mnemonbinary install (MNEMON_VERSION,TARGETARCHhandling)docker/start-hermes.sh:62-79,95-98— Mnemon plugin sync fromgitricko/hermes-plugin-mnemon,USER.mdpriming.github/workflows/docker-publish.yml:120-146,287-313— Mnemon integration test (Hermes remembers →mnemon recall→ Claude retrieves)docker/skill-memory-automation.mdverbatim into.agents/skills/hermes-memory-automation/SKILL.md(already has correct frontmattername: memory-automation,description: "Automated mnemon memory persistence workflow …"— renamenametohermes-memory-automationto match folder or keep alias viarelated_skills). Add a header note explaining the two locations (repo.agents/= source of truth for agents coding the repo; container~/.hermes/skills/= runtime for agents inside the webtop) and the sync direction (repo → DockerfileCOPY→ container).4.6
hermes-dependency-scan— NEWUse when auditing or bumping hermes-webtop dependency versions. Check Dockerfile ARGs, npm globals, and system packages.docker/Dockerfile:1-10— 9 pinnedARGversions (HERMES_VERSION,OMNIROUTE_VERSION,MODELRELAY_VERSION,OLLAMA_VERSION,NODE_VERSION,CODE_SERVER_VERSION,PI_VERSION,MNEMON_VERSION,HERDR_VERSION) — each is a bump guarddocker/scripts/fm-install-herdr.sh— SHA-256 pinnedherdrinstaller;Dockerfile:123assertsherdr --version == HERDR_VERSIONdocker/start-codeserver.sh:35,43— hardcoded VSIX versions forhermes-code-agent(VERSION=3.0.3),saoudrizwan.claude-dev,anthropic.claude-codewith inlineclaude/install.shdocker/pi-models.json,docker/pi-settings.json,docker/claude-vscode-settings.json— extension/model manifests with implicit version coupling.github/dependabot.yml— verified present: monitorsdockerandgithub-actionsecosystems weekly. Gap: nonpmecosystem entry, sonpm install -gglobals in the Dockerfile (modelrelay,omniroute,pi-coding-agent) and VSIX versions instart-codeserver.share invisible to Dependabot — the skill must cover them manually.Makefile/docker-compose.yml— no version pinning but affected by base imagelscr.io/linuxserver/webtop:ubuntu-mateARGversions +herdrinstaller SHA +code-serverextension VSIX versions instart-codeserver.sh:35,43.ARG→ updateHERDR_VERSIONguard → updatefm-install-herdr.shpin → smoke-test viadocker-publish.ymlbuild-testjob (never locally perdocker-build-remote-only).grep -R ARG.*VERSION docker/Dockerfile,grep -R VERSION.*= docker/start-*.sh, check GHCR/base-image advisories. Manually checknpmglobals since Dependabot does not cover them.5. What is intentionally excluded (and why)
fleet-deploy,fleet-health,herdr-fleet) — no fleet in this repo.firstmateCLI skills — hermes-webtop useshermes+docker compose+make;firstmateis not installed in the image.code-review,tdd,systematic-debugging— already available as global Hermes skills; no need to duplicate under.agents/.pyproject.toml/eslintconfig in repo; out of scope until the codebase grows beyond shell + Docker.6. Implementation checklist for Phase 3
.agents/skills/withSKILL.mdeach (keepparallel-delegationas-is).SKILL.mdgets the frontmatter in §3;descriptionstartsUse when.hermes-memory-automation/SKILL.md— copy fromdocker/skill-memory-automation.md, adjustnameand add repo-vs-container note.docker-build-remote-only/SKILL.md— codify the "neverdocker buildlocally" guard with thefree-disk.sh --dry-runpre-check and the CI remote-build path.README.md(ordocs/architecture.md) with a one-paragraph pointer to.agents/skills/so contributors discover the skills.grep -R '^description: "Use when' .agents/skills/*/SKILL.md) to enforce the frontmatter convention.7. References
.agents/did not exist; firstmatehttps://github.com/gitricko/firstmate-codespace/tree/main/.agents/skills(21 skills, fleet-coupled)..agents/skills/parallel-delegation/SKILL.md(created Phase 2).