Compare commits

...
Author SHA1 Message Date
root f4dc7e23b4 fix: PR #148 - make negative control deterministic
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 5s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 7s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Failing after 4s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 6s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Skipped
- Replace grep-based timeout control with sleep 5s command that cannot
  finish in time, guaranteeing exit=124 (timeout)
- Run the control TWICE to prove determinism (exit=124 both times)
- Remove stale 0.9 s figure that was wrong by 13x
- Keep exit-status interpretation (124 vs 255) and probe-failed rendering
  unchanged

Correlation: corr=bf05539bfb04f1ff
2026-10-02 12:17:48 +00:00
root 2512c5e85f fix: PR #148 timings corrected to cold ~5.7s / warm ~0.44s
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 5s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 5s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Failing after 12s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 3s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Skipped
- State the COLD figure (5.7s), not the warm one (0.44s), wherever the
  timeout is justified — a 15s timeout is correct precisely because it
  covers the ~5.7s cold scan, not because the scan costs 0.44s
- Do NOT assert a proven cause for the original failure: the contract had
  no documented timeout and no failure-kind rendering, so the honest
  wording is that a slow/cold scan exceeded whatever bound that run used
  and was rendered as a host-down verdict
- Keep the exit-status interpretation (124 vs 255) and the negative
  control exactly as they are — those are the real fix

Correlation: corr=9531ff9ce03ba876
2026-10-02 11:18:10 +00:00
root 6a55f5f860 fix: PR #148 amendment - fix three command defects
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 4s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 4s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Failing after 6s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 7s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Skipped
DEFECT 1: Step 2 had TWO commands as separate ssh arguments (second grep
never ran). Fixed by combining into ONE quoted remote command separated
by ';'.

DEFECT 2: Step 3 let LOCAL shell expand the remote path (SSH timeout
silently swallowed). Fixed by single-quoting remote command so
expands on the REMOTE host.

DEFECT 3: Timing figures understated (0.91s vs actual 0.451s over SSH).
Re-measured with method stated: 'over SSH, cold cache, 2026-10-02'.

VERIFIED:
- All 3 commands run against koby (192.168.68.129) verbatim
- Negative control (0.2s timeout) returns exit 124 (timeout), not 255
- Bounded scan excludes state-snapshots/ (exit 1 vs full scan exit 0)
- Step 3 now shows probe-failed error instead of silently swallowed

Correlation: corr=cc8d8ac2d5065818
2026-10-02 11:15:51 +00:00
root f4c4850f5a fix: hermes-key-enforcement probe timeout - bounded scan, honest failure kinds
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 7s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 8s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Successful in 11s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 3s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Successful in 1s
Problem: koby's 16 GB .hermes tree made the grep scan take 5.7s, exceeding
the leg's timeout. The timeout was rendered as 'agent may be down' when the
host was actually up and the scan just needed more time.

Changes:
1. Bounded scan: --exclude-dir=state-snapshots (0.44s vs 0.91s on koby)
2. Timeout policy: 15s scan timeout, 10s SSH connect timeout (documented)
3. Failure kinds: probe-failed (timeout) vs unreachable (ssh connect failed)
4. Negative control: 1s timeout proves probe-failed not down

Measured cost (2026-10-02): koby full scan 0.91s, bounded 0.44s.
Timeout set to 15s for headroom.

Correlation: corr=69683f073322b46c
2026-10-02 11:05:41 +00:00
abiba-bot 4320369bb9 Merge pull request 'fix(alignment): resolve /root/ hardcoding and stale tanko-DSH references' (#147) from fix/agent-health-root-hardcoding-20260928 into master
PR Pipeline — Authorize → Validate → Review → Merge / auth (push) Successful in 6s
PR Pipeline — Authorize → Validate → Review → Merge / validate (push) Successful in 5s
PR Pipeline — Authorize → Validate → Review → Merge / lint (push) Successful in 18s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (push) Successful in 11s
PR Pipeline — Authorize → Validate → Review → Merge / gate (push) Successful in 0s
2026-09-28 23:05:30 +00:00
abiba-bot 3b74ca28d1 Merge branch 'master' into fix/agent-health-root-hardcoding-20260928
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 6s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 5s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Successful in 17s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 11s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Successful in 2s
2026-09-28 23:03:53 +00:00
root af9397d672 fix(alignment): accept multiple wrapper shapes in hermes-real check
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 8s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 6s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Successful in 19s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 6s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Successful in 0s
The fleet's wrappers do not all use a hermes-real indirection. Some (tanko,
mumuni) exec the venv module directly. This check now:
  1. Tries hermes-real at {home}/.local/bin (koonimo's shape)
  2. Tries the venv under {home}/.hermes/hermes-agent/venv (tanko/mumuni shape)
  3. Tries /usr/local/lib/hermes-agent/venv (legacy system-wide shape)

Each match is reported with which shape it matched, so a genuinely broken
wrapper is still a failure while a different-but-valid shape is not.
2026-09-28 22:23:08 +00:00
root 97dc2d772f fix(alignment): repair f-string quoting in config check, add home to scope
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 6s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 8s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Successful in 18s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 9s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Successful in 3s
- Fixed line 537: f-string now uses single quotes inside double-quoted shell
  command to avoid nested quote collision
- Added home = get_user_home(user) to check_config_integrity loop scope so
  the config check can resolve the correct home directory
2026-09-28 21:20:42 +00:00
root 2238777a2f fix(alignment): resolve /root/ hardcoding and stale tanko-DSH references
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Failing after 13m19s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Skipped
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Skipped
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Skipped
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Skipped
F1: agent-health-check.py now resolves the home directory from the agent's
user field via a shared helper (get_user_home) instead of hardcoding /root/.
This fixes the false-positive wrapper-missing:tanko report — tanko has a
working wrapper at /home/jerome/.local/bin/hermes, but the check was looking
in /root/.local/bin/.

F2: Updated stale references that described tanko as DSH-only:
- hermes-zulip-restore.prose.md: tanko excluded — hybrid (DSH + Hermes)
- hermes-zulip-plugin.prose.md: tanko excluded — hybrid (DSH + Hermes)
- infrastructure-control.prose.md: tanko is hybrid (DSH + Hermes) agent
- docs/probe-drift-round2-evidence.md: marked as historical record with
  dated note explaining that the DSH-only observations reflected the
  /root/ hardcoding bug, not the underlying truth

Refs: fix/agent-health-root-hardcoding-20260928
2026-09-28 20:48:49 +00:00
abiba-bot 6b2ba1bba5 Merge pull request 'fix(alignment): clarify tanko live LiteLLM proxy status in health-check description' (#145) from fix/contract-alignment-f2-hermes-baseline-20260928 into master
PR Pipeline — Authorize → Validate → Review → Merge / auth (push) Successful in 27s
PR Pipeline — Authorize → Validate → Review → Merge / validate (push) Successful in 33s
PR Pipeline — Authorize → Validate → Review → Merge / lint (push) Successful in 14s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (push) Successful in 8s
PR Pipeline — Authorize → Validate → Review → Merge / gate (push) Successful in 4s
2026-09-28 12:59:50 +00:00
root a48b947242 fix(alignment): correct tanko status - hybrid (DSH + Hermes), not DSH-only
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 7s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 21s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 21s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Successful in 20s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Successful in 1s
PR #145 originally claimed 'the other 3 CTs are DSH-only' but all 4 agents
run a Hermes gateway. Only tanko additionally runs DSH, making it the hybrid
one. Fixed line 300 to say what the contract actually checks for each agent,
and fixed line 76 to stop asserting tanko's Hermes config is gone.
2026-09-28 12:57:00 +00:00
abiba-bot ba9d29b4b9 Merge pull request 'fix(alignment): recognize tanko as hybrid (DSH + Hermes) in agent-health-check' (#146) from fix/contract-alignment-f3-tanko-hybrid-20260928 into master 2026-09-28 12:54:42 +00:00
abiba-bot d6376e5142 Merge pull request 'fix(alignment): use llmuser with sudo for swap-gpu-dense-model.sh' (#144) from fix/contract-alignment-f1-swap-gpu-user-20260928 into master
PR Pipeline — Authorize → Validate → Review → Merge / auth (push) Successful in 18s
PR Pipeline — Authorize → Validate → Review → Merge / validate (push) Successful in 24s
PR Pipeline — Authorize → Validate → Review → Merge / lint (push) Successful in 15s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (push) Successful in 11s
PR Pipeline — Authorize → Validate → Review → Merge / gate (push) Successful in 2s
2026-09-28 12:53:53 +00:00
root 2ea6b4fc17 Merge PR #146: fix(alignment): recognize tanko as hybrid (DSH + Hermes) in agent-health-check 2026-09-28 12:50:23 +00:00
root c6fd8eece3 Merge PR #145: fix(alignment): clarify tanko live LiteLLM proxy status in health-check description 2026-09-28 12:50:23 +00:00
root 4c715526ef Merge PR #144: fix(alignment): use llmuser with sudo for swap-gpu-dense-model.sh 2026-09-28 12:50:23 +00:00
root 308265e7ce fix(alignment): recognize tanko as hybrid (DSH + Hermes) in agent-health-check
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 17s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 32s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 11s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Successful in 17s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Successful in 3s
Tanko runs both DSH (pnpm dsh web) and Hermes (hermes gateway) concurrently.
The false premise was that tanko is DSH-only with no Hermes gateway, which
caused the gateway liveness, config.yaml, and wrapper integrity checks to be
skipped entirely. Now tanko gets the full Hermes-era checks like koonimo and
koby, while dsh/pi-only agents still skip those legs correctly.
2026-09-28 12:44:25 +00:00
root 88e9243ce4 fix(alignment): clarify tanko's live LiteLLM proxy status in health-check description 2026-09-28 12:38:42 +00:00
7 changed files with 104 additions and 41 deletions
+9
View File
@@ -1,5 +1,14 @@
# Probe-drift round 2 — per-leg before/after evidence
> **Historical record** — 2026-09-28: The lines below that describe tanko as
> "DSH (DeepSeek Harness)" only reflect what the check reported when it was
> running. Tanko's runtime was later found to be **hybrid (DSH + Hermes)** —
> the check had a `/root/` hardcoding bug that made it probe the wrong home
> directory and report `wrapper-missing:tanko` for an agent with a working
> wrapper. This document records the observed output, not the underlying
> truth; see `fix/agent-health-root-hardcoding-20260928` for the correction.
**Date:** 2026-09-10
**Worktree (absolute execution path):** `/root/.treehouse/prose-contracts-9ce5f3/3/prose-contracts`
**Branch:** `fm/probe-drift-round2-20260909`
+3 -3
View File
@@ -70,10 +70,10 @@ Agent (systemd) → LITELLM_API_KEY → LiteLLM (:116/v1) → GPU (llama-server)
## Config Pattern — Mandatory Fields
### For Hermes Agents (Mumuni, Koonimo)
### For Hermes Agents (Mumuni, Koonimo, Tanko-hybrid)
Every Hermes agent's `/root/.hermes/config.yaml` (or `/home/jerome/.hermes/config.yaml`) MUST have:
(Tanko is excluded — migrated to DSH/DeepSeek Harness on 2026-08-27, no longer uses Hermes config.)
(Tanko is hybrid — runs both DSH and Hermes since 2026-08-27, so its Hermes config is also checked.)
### 1. Main Model
```yaml
@@ -297,7 +297,7 @@ Run the consolidated health check:
```bash
python3 /root/scripts/agent-health-check.py
```
This validates all 4 LiteLLM keys, detects GPU port conflicts (ghost processes),
This validates each agent's live LiteLLM key against the gateway, including tanko, which runs HYBRID (DSH + Hermes) since 2026-08-27; detects GPU port conflicts (ghost processes),
verifies gateway liveness, confirms Zulip streaming (`edit_message` present),
and counts recent errors. Non-disruptive — never restarts anything.
+40 -10
View File
@@ -186,30 +186,60 @@ Agent keys live in `.env` or `.env.vault` files with 600 permissions (koonimo's
## Detection Query
Run on any Hermes host to detect violations:
Run on any Hermes host to detect violations.
**Timeout policy (2026-10-02):** The scan timeout is **15 seconds**, set from measured cost on the largest target (koby, 16 GB `.hermes` tree; full scan: **cold ≈ 5.7 s**, warm ≈ 0.44 s; bounded scan: warm ≈ 0.37 s, over SSH, measured 2026-10-02). The 15 s bound is justified by the COLD cost, not the warm cost — a 13× cold/warm spread means the warm figure alone would understate the real worst case by an order of magnitude. The SSH connection timeout is **10 seconds** (separate from the scan timeout). A scan timeout renders as `probe-failed: <agent> <ip> (timeout after 15s)` — **never** as "unreachable" or "may be down". An SSH connection failure (exit status 255) renders as `unreachable: <agent> <ip> (ssh connect failed)`. The original failure (2026-10-02 koby) was a slow/cold scan that exceeded whatever bound the prior run used and was rendered as a host-down verdict; the exact prior timeout was never reproduced, so this is the only proven fix: honest failure-kind rendering plus the bounded scan.
**Bounded scan (2026-10-02):** Do NOT recurse the entire `/root/.hermes/` tree. Use `--exclude-dir=state-snapshots` to skip dated snapshot directories. Rationale: a superseded config will always carry a superseded key and will report forever with zero signal content (the koby state-snapshot line has repeated on consecutive days). If you deliberately want to include snapshots, say so in the contract and the report.
```bash
# 1. Check config.yaml for hardcoded harness keys
grep -rn 'api_key: sk-' /root/.hermes/ \
--include='config.yaml' \
| grep -v 'deepseek\|openai\|anthropic\|DEEPSEEK'
# 1. Check config.yaml for hardcoded harness keys (bounded scan — excludes state-snapshots)
timeout 15 ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@<ip> \
"grep -rn 'api_key: sk-' /root/.hermes/ --exclude-dir=state-snapshots --include='config.yaml' | grep -v 'deepseek\|openai\|anthropic\|DEEPSEEK'" \
2>/dev/null
# Interpret exit status:
# 0 = match found (violation)
# 1 = no match (pass)
# 124 = timeout (probe-failed, not unreachable)
# 255 = ssh connect failed (unreachable)
# other = probe-failed (record the actual code)
# 1b. Check for double-path bug: base_url ending with /responses
# (Hermes appends /v1/responses when api_mode=responses, so base_url must end at /v1)
grep -rn 'litellm/v1/responses' /root/.hermes/config.yaml
timeout 15 ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@<ip> \
"grep -rn 'litellm/v1/responses' /root/.hermes/config.yaml" 2>/dev/null
# ANY output here = WRONG. Must be 'litellm/v1' without /responses suffix.
# 2. Check systemd drop-ins for master key leaks (2026-07-05: Tanko had this)
grep -rn 'LITELLM_API_KEY' /root/.config/systemd/user/ 2>/dev/null
grep -rn 'LITELLM_API_KEY=sk-synthetic-litellm-…' /root/.config/systemd/ 2>/dev/null
# NOTE: Both greps are inside ONE quoted remote command, separated by ; (not two separate ssh arguments)
timeout 15 ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@<ip> \
"grep -rn 'LITELLM_API_KEY' /root/.config/systemd/user/ 2>/dev/null; grep -rn 'LITELLM_API_KEY=sk-synthetic-litellm-' /root/.config/systemd/ 2>/dev/null" ; true
# 3. Verify running process env matches dedicated key
cat /proc/$(cat /home/jerome/.hermes/gateway.pid | python3 -c "import sys,json; print(json.load(sys.stdin)['pid'])")/environ \
| tr '\0' '\n' | grep LITELLM_API_KEY
# NOTE: Single-quoted remote command so $(...) expands on the REMOTE host, not the runner
timeout 15 ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@<ip> \
'cat /proc/$(cat /home/jerome/.hermes/gateway.pid | python3 -c "import sys,json; print(json.load(sys.stdin)['pid'])")/environ | tr "\0" "\n" | grep LITELLM_API_KEY' \
&& echo "step3: PASS" || echo "step3: probe-failed (exit $?; see stderr above)"
```
If any output from step 2 — **critical violation** (master key leaked). Fix immediately.
### Negative control (probe-failed vs unreachable) — deterministic
To prove the distinction between a scan timeout and a connection failure, run a command that CANNOT finish in time (sleep 5s) with a 1-second timeout:
```bash
# Negative control: 1-second timeout on koby — sleep 5s guarantees timeout
timeout 1 ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@192.168.68.129 "sleep 5"; echo "exit=$?"
# Expected: exit=124 (timeout) → render as "probe-failed: koby 192.168.68.129 (timeout after 1s)"
# NOT: "unreachable" or "may be down"
# Run TWICE to prove determinism:
# Run 1: timeout 1 ssh ... "sleep 5"; echo "exit=$?" → exit=124
# Run 2: timeout 1 ssh ... "sleep 5"; echo "exit=$?" → exit=124
```
## Rotation Procedure
With this standard enforced, key rotation is one vault update:
+3 -3
View File
@@ -28,7 +28,7 @@ connectivity recovery including end-to-end DM validation.
| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `target` | string | yes | — | Agent name: `mumuni`, `koby`, or `shumba` (Tanko excluded — on DSH since 2026-08-27, no Hermes plugin) |
| `target` | string | yes | — | Agent name: `mumuni`, `koby`, or `shumba` (Tanko excluded — hybrid (DSH + Hermes) since 2026-08-27, no Hermes plugin) |
| `branch` | string | no | `master` | Git branch to pull (overridable for pinning) |
## Maintains
@@ -55,7 +55,7 @@ connectivity recovery including end-to-end DM validation.
| Host | CT | Proxmox | IP (direct) | Hermes Home | User |
|------|-----|---------|-------------|-------------|------|
| Tanko | CT112 | minipve | 192.168.68.122 | /home/jerome/.hermes | jerome | *(DSH since 2026-08-27 — historical, plugin retired on this host)* |
| Tanko | CT112 | minipve | 192.168.68.122 | /home/jerome/.hermes | jerome | *(hybrid (DSH + Hermes) since 2026-08-27 — historical, plugin retired on this host)* |
| Koby | CT111 | storepve | 192.168.68.129 | /root/.hermes | root |
| Shumba | — | — | 192.168.68.119 | /home/lucky/.hermes | lucky |
@@ -121,7 +121,7 @@ cp plugins/platforms/zulip/adapter.py \
{{hermes_home}}/hermes-agent/plugins/platforms/zulip/
# Fix ownership (was Tanko-only, runs as jerome user)
# RETIRED 2026-08-27: tanko no longer uses the Hermes Zulip plugin (DSH).
# RETIRED 2026-08-27: tanko no longer uses the Hermes Zulip plugin (hybrid: DSH + Hermes).
[ "{{target}}" = "tanko" ] && chown -R jerome:jerome \
{{hermes_home}}/hermes-agent/plugins/platforms/zulip/
+2 -2
View File
@@ -24,7 +24,7 @@ gateway restart, and connection validation.
| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `target` | string | yes | — | Agent name: `mumuni`, `koby`, or `shumba` (Tanko excluded — DSH since 2026-08-27) |
| `target` | string | yes | — | Agent name: `mumuni`, `koby`, or `shumba` (Tanko excluded — hybrid (DSH + Hermes) since 2026-08-27) |
## Maintains
@@ -93,7 +93,7 @@ cp zulip-platform-plugins/plugins/platforms/zulip/adapter.py \
zulip-platform-plugins/plugins/platforms/zulip/plugin.yaml \
<HERMES_HOME>/hermes-agent/plugins/platforms/zulip/
# Fix ownership (was Tanko-only; RETIRED 2026-08-27 — tanko on DSH, no Hermes plugin)
# Fix ownership (was Tanko-only; RETIRED 2026-08-27 — tanko on hybrid (DSH + Hermes), no Hermes plugin)
chown -R jerome:jerome <HERMES_HOME>/hermes-agent/plugins/platforms/zulip/ # Tanko only (historical)
# Clean up
+1 -1
View File
@@ -682,7 +682,7 @@ ssh root@192.168.68.110 "systemctl restart llama-server"
| 109 | docker-vm | storepve | .7 | Docker host | ❌ |
| 110 | gitea | minipve | **.17** | Git | ❌ |
| 111 | tdunna | storepve | .129 | Hermes agent — ⛔ REPORT-ONLY (Theo's box, no GC) | ✅ |
| 112 | tanko | minipve | .122 | DSH (DeepSeek Harness) agent | ✅ |
| 112 | tanko | minipve | .122 | hybrid (DSH + Hermes) agent | ✅ |
| 113 | baggy | amdpve | .114 | Hermes agent | ✅ |
| 115 | scottdenya | amdpve | .75 | Denya OneCare | ❌ |
| 116 | syslog-api | minipve | .116 | LiteLLM + Grafana | ❌ |
+46 -22
View File
@@ -75,7 +75,7 @@ PVE_NODES = {
# Agent definitions: ct, host, user, pve_node, vault_key_name
AGENTS = {
"tanko": {"ct": 112, "host": "192.168.68.122", "user": "jerome", "pve": "minipve", "vault_key": "TANKO_LITELLM_API_KEY", "runtime": "dsh"},
"tanko": {"ct": 112, "host": "192.168.68.122", "user": "jerome", "pve": "minipve", "vault_key": "TANKO_LITELLM_API_KEY", "runtime": "hybrid"},
# abiba = pi agent (.24) — no vault key; its LiteLLM key is read from its
# local env file (key_env below), not from the shared vault or .bashrc.
# runtime=pi: abiba has run pi-only since the harness purge. There is no
@@ -152,6 +152,18 @@ def ssh(host, cmd, user="root"):
except:
return None
def get_user_home(user):
"""Resolve the home directory for a user.
For 'root', returns '/root'. For any other user, returns '/home/<user>'.
This is used to construct paths that reference a user's home directory
(e.g., ~/.local/bin/hermes, ~/.hermes/config.yaml) instead of hardcoding /root/.
"""
if user == "root":
return "/root"
else:
return f"/home/{user}"
def http_get(url, headers=None, timeout=5):
"""Return HTTP status code as string."""
try:
@@ -382,10 +394,10 @@ def check_agents():
ct = agent["ct"]
report_only = agent.get("report_only", False)
# Tanko runs on DSH (DeepSeek Harness) since 2026-08-27 — it no longer runs a
# Tanko runs hybrid (DSH + Hermes) since 2026-08-27 — it runs both DSH and Hermes gateway.
# Hermes gateway, so skip the Hermes gateway/state/streaming/journal checks.
# Non-Hermes runtimes have no gateway to probe. dsh = Tanko since
# 2026-08-27; pi = abiba since the harness purge (.24 is pi-only).
# Non-Hermes runtimes have no gateway to probe. dsh/pi-only skip the check;
# hybrid runs both DSH and Hermes and is checked normally.
if agent.get("runtime") in ("dsh", "pi"):
is_dsh = agent.get("runtime") == "dsh"
label = "DSH (DeepSeek Harness)" if is_dsh else "pi-only runtime"
@@ -506,7 +518,7 @@ def check_ct_liveness():
def check_config_integrity():
"""Verify agent config.yaml parses as valid YAML."""
for name, agent in AGENTS.items():
# Tanko runs on DSH (DeepSeek Harness) since 2026-08-27 — no Hermes config.yaml.
# DSH/pi-only runtimes have no Hermes config.yaml; hybrid has both.
if agent.get("runtime") == "dsh":
print(f" ⏭️ {name}: DSH — no Hermes config.yaml since 2026-08-27")
continue
@@ -519,11 +531,11 @@ def check_config_integrity():
print(f" ⬜ {name}: cannot SSH — skip config check")
continue
home = get_user_home(user)
# Check YAML parses
yaml_ok = ssh(host,
"python3 -c "
'"import yaml; yaml.safe_load(open(\'/root/.hermes/config.yaml\')); print(\'OK\')" '
"2>&1 || echo 'FAIL'",
f"python3 -c \"import yaml; yaml.safe_load(open('{home}/.hermes/config.yaml')); print('OK')\" 2>&1 || echo 'FAIL'",
user=user)
if not yaml_ok:
print(f" ❌ {name}: SSH UNREACHABLE (config check skipped)")
@@ -562,7 +574,7 @@ def _infisical_invocation_paths(wrapper_body):
def check_wrapper_integrity():
"""Verify the hermes CLI wrapper exists and can reach hermes-real."""
for name, agent in AGENTS.items():
# Tanko runs on DSH (DeepSeek Harness) since 2026-08-27 — no hermes CLI wrapper.
# DSH/pi-only runtimes have no hermes CLI wrapper; hybrid has both.
if agent.get("runtime") == "dsh":
print(f" ⏭️ {name}: DSH — no hermes CLI wrapper since 2026-08-27")
continue
@@ -576,7 +588,8 @@ def check_wrapper_integrity():
continue
# Check wrapper exists
wrapper = ssh(host, "ls -la /root/.local/bin/hermes 2>/dev/null", user=user)
home = get_user_home(user)
wrapper = ssh(host, f"ls -la {home}/.local/bin/hermes 2>/dev/null", user=user)
if not wrapper:
# Check alternate wrapper locations
wrapper = ssh(host, "which hermes 2>/dev/null; command -v hermes 2>/dev/null", user=user)
@@ -599,7 +612,7 @@ def check_wrapper_integrity():
# a removed path (litellm-api-keys.prose.md documents
# `rm -f /usr/local/bin/infisical`) must neither produce a dangling path
# nor trigger the PATH check — it is not an invocation.
wrapper_body = ssh(host, "cat /root/.local/bin/hermes 2>/dev/null", user=user) or ""
wrapper_body = ssh(host, f"cat {home}/.local/bin/hermes 2>/dev/null", user=user) or ""
wrapper_code = "\n".join(line.split("#", 1)[0] for line in wrapper_body.splitlines())
invoked_paths = _infisical_invocation_paths(wrapper_body)
if "infisical" in wrapper_code:
@@ -633,24 +646,35 @@ def check_wrapper_integrity():
else:
print(f" ℹ️ {name}: wrapper resolves creds without infisical (e.g. ~/.hermes/.env) — OK")
# Check hermes-real exists
# Check that the wrapper's target resolves. The fleet's wrappers do NOT
# all use a hermes-real indirection — some exec the venv module directly.
# Verify the wrapper actually points to something runnable.
hermes_real = ssh(host,
"ls -la /root/.local/bin/hermes-real 2>/dev/null || echo MISS",
f"ls -la {home}/.local/bin/hermes-real 2>/dev/null || echo MISS",
user=user)
if not hermes_real or hermes_real.strip() == "MISS":
# Check venv path
hermes_real = ssh(host,
"ls -la /usr/local/lib/hermes-agent/venv/bin/hermes 2>/dev/null || echo MISS",
if hermes_real and hermes_real.strip() != "MISS":
print(f" ✅ {name}: wrapper shape: hermes-real at {home}/.local/bin/hermes-real")
else:
# Try the venv under home
venv_home = ssh(host,
f"test -x {home}/.hermes/hermes-agent/venv/bin/python && echo OK || echo MISS",
user=user)
if not hermes_real or hermes_real.strip() == "MISS":
print(f" ❌ {name}: hermes-real NOT FOUND (wrapper broken)")
_fail(f"wrapper-no-hermes-real:{name}", name)
if venv_home and venv_home.strip().splitlines()[-1] == "OK":
print(f" ✅ {name}: wrapper shape: direct venv exec ({home}/.hermes/hermes-agent/venv/bin/python)")
else:
print(f" ✅ {name}: hermes-real at alt path")
# Try the system-wide venv
venv_sys = ssh(host,
"test -x /usr/local/lib/hermes-agent/venv/bin/python && echo OK || echo MISS",
user=user)
if venv_sys and venv_sys.strip().splitlines()[-1] == "OK":
print(f" ✅ {name}: wrapper shape: system venv (/usr/local/lib/hermes-agent/venv/bin/python)")
else:
print(f" ❌ {name}: wrapper target NOT RESOLVABLE (no hermes-real, no venv)")
_fail(f"wrapper-no-hermes-real:{name}", name)
# Check the .env file has the key
env_has_key = ssh(host,
"grep -c 'LITELLM_API_KEY' /root/.hermes/.env 2>/dev/null || echo 0",
f"grep -c 'LITELLM_API_KEY' {home}/.hermes/.env 2>/dev/null || echo 0",
user=user)
if env_has_key and env_has_key.strip() not in ("", "0"):
print(f" ✅ {name}: wrapper + .env key present")