fix(alignment): resolve /root/ hardcoding and stale tanko-DSH references #147
@@ -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`
|
||||
|
||||
@@ -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/
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 | ❌ |
|
||||
|
||||
@@ -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:
|
||||
@@ -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)")
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user