Skip to content

Migrate the use of ModelRelay to 9Router by keep port 7352 the same #52

Description

@gitricko

Objective


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):

  1. Logs in to 9Router's REST API (default creds password:123456).
  2. Disables dashboard login and API-key enforcement.
  3. Deletes any pre-existing auto-fastest combo (idempotent re-run).
  4. Creates the auto-fastest combo with 8 free oc/ models.
  5. Sets auto-fastest strategy to round-robin while preserving other combo strategies.
  6. Patches the settings endpoint with the updated strategies.
  7. 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)

  1. Critical branch fix — hermes-impl → main in start-pi.sh:32 and pi-settings.json:5 (enables PI install)
  2. 9Router / OmniRoute — OMNIROUTE_VERSION=3.8.50 stays (already current; no Ninerouter change needed at this step)
  3. PI / Node — PI_VERSION=0.87.1, NODE_VERSION=26.10.0
  4. ModelRelay — MODELRELAY_VERSION=1.22.2 (drags in Ninerouter 0.5.81 as a transitive dependency bump)
  5. 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:
    1. docker build locally → forbidden — explain disk exhaustion, show df -h pre-check.
    2. 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.
    3. 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.
    4. 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:
    1. How to run: self-check (installed at /usr/local/bin/self-check via Dockerfile:51) or docker exec -u abc hermes-webtop self-check.
    2. Threshold env vars: HERMES_WEBTOP_SKIP_CHECKS, HERMES_WEBTOP_DISK_WARN_PCT, HERMES_WEBTOP_CRITICAL_SERVICES.
    3. Exit-code contract: 0 all ok, 1 warnings, 2 critical — CI only fails on 2.
    4. 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:
    1. Lifecycle: make start (local), make start-codespace (Codespace overlay), make stop, make backup/restore.
    2. Boot order inside container (/custom-cont-init.d/*.sh alphabetical, start-hermes.sh backgrounds itself with sleep 5 guard).
    3. Debugging: docker compose logs -f, docker exec hermes-webtop cat /tmp/*.log, ~/.hermes/logs/{gateway,dashboard,socat-9119}.log.
    4. 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:
    1. Inventory: list all 9 ARG versions + herdr installer SHA + code-server extension VSIX versions in start-codeserver.sh:35,43.
    2. 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).
    3. 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

  • Create 5 new folders under .agents/skills/ with SKILL.md each (keep parallel-delegation as-is).
  • Each SKILL.md gets the frontmatter in §3; description starts Use when.
  • hermes-memory-automation/SKILL.md — copy from docker/skill-memory-automation.md, adjust name and add repo-vs-container note.
  • docker-build-remote-only/SKILL.md — codify the "never docker build locally" guard with the free-disk.sh --dry-run pre-check and the CI remote-build path.
  • Update README.md (or docs/architecture.md) with a one-paragraph pointer to .agents/skills/ so contributors discover the skills.
  • Optional: add a CI lint step (grep -R '^description: "Use when' .agents/skills/*/SKILL.md) to enforce the frontmatter convention.

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions