Mumuni CT114 destroyed. Mumuni now runs inside Abiba CT100 at 192.168.68.24. Updated all contract files and agent-health-check.py.
12 KiB
kind, name, version, description, author
| kind | name | version | description | author |
|---|---|---|---|---|
| enforcement | hermes-key-enforcement | 1.0.0 | Enforces standardized API key configuration across all Hermes agents. Harness/LiteLLM providers MUST use api_key_env indirection. External providers (DeepSeek, OpenAI, Anthropic) may use hardcoded keys. Single source of truth: Infisical vault (project=agents, env=production) — injected at runtime via `infisical run --` wrapper. /etc/environment is DEPRECATED for agent keys post-migration. Designed to make key rotation a one-step vault operation. | Abiba (pi agent) |
Hermes Key Enforcement Contract
Rule (One Sentence)
All harness/litellm providers MUST use api_key_env: LITELLM_API_KEY with authenticated path http://192.168.68.116/litellm/v1/responses — hardcoded keys AND unauthenticated /v1 direct access are both forbidden.
Scope
Applies to all Hermes agent configs across all hosts. Covers these config sections:
model.api_keycustom_providers[].api_key(whennamecontainsharnessorlitellm)auxiliary.*.api_key(whenproviderisharnessor containslitellm)delegation.api_key(whenproviderisharnessor containslitellm)compression.api_key(whenproviderisharnessor containslitellm)fallback_providers[].api_key(when provider is harness)
Architecture (2026-07-10)
Syslog is migrating away from unauthenticated direct access to the shared inference harness.
| Path | Auth | Status |
|---|---|---|
http://192.168.68.116/v1 |
None (direct) | ❌ DEPRECATED — being phased out |
http://192.168.68.116/litellm/v1/responses |
Bearer sk-* key |
✅ CURRENT — authenticated LiteLLM proxy |
All harness/litellm providers MUST use the authenticated /litellm/v1/responses path.
Any base_url pointing to bare /v1 on 192.168.68.116 is a migration violation.
🔥 CRITICAL: Double-Path Bug (2026-07-10)
When api_mode: responses is set, Hermes appends /v1/responses to base_url.
If base_url already includes /litellm/v1/responses, the result is:
http://192.168.68.116/litellm/v1/responses/v1/responses → 404
The base_url must end at /v1 — never include /responses:
# ✅ CORRECT — Hermes appends /v1/responses for api_mode: responses
base_url: http://192.168.68.116/litellm/v1
# ❌ WRONG — produces double path
base_url: http://192.168.68.116/litellm/v1/responses
This applies to ALL sections using the harness provider: custom_providers, delegation, auxiliary.*.
Exemptions
External providers are explicitly exempt and may use hardcoded keys:
- DeepSeek (
api.deepseek.com) - OpenAI (
api.openai.com) - Anthropic (
api.anthropic.com) - OpenRouter
- Any provider whose base_url does NOT match
192.168.68.116orlitellm.sysloggh.net
Standard Pattern
Canonical vault process (2026-07-16): see
litellm-api-keys§ Production Vault Access Process. All agents MUST use theinfisical-gateway.shwrapper (live vault injection). Hardcoded systemd drop-ins / config.yaml keys are DEPRECATED — they rot on rotation (root cause of the 2026-07-16 401 storm). 4/5 agents migrated; tanko (user jerome) pending.
# ✅ CORRECT — all harness/litellm providers (authenticated path, NO /responses suffix)
model:
provider: harness
base_url: http://192.168.68.116/litellm/v1 # ← Hermes appends /v1/responses
api_key_env: LITELLM_API_KEY
custom_providers:
- name: harness
api_mode: responses
base_url: http://192.168.68.116/litellm/v1 # ← NO /responses suffix!
api_key_env: LITELLM_API_KEY
auxiliary:
compression:
provider: harness
base_url: http://192.168.68.116/litellm/v1 # ← NO /responses suffix!
api_key_env: LITELLM_API_KEY
# ✅ ALSO CORRECT — external providers
fallback_providers:
- provider: deepseek
base_url: https://api.deepseek.com
api_key: sk-b7d9... # ← hardcoded OK (external)
api_key_env: DEEPSEEK_API_KEY # ← also OK if set in environment (vault or /etc/environment)
# ❌ FORBIDDEN — hardcoded key (top) OR unauthenticated path (bottom)
model:
provider: harness
api_key: sk-Flc62smlegyMEaSo1ka8JA # ← RULE VIOLATION: hardcoded key
model:
provider: harness
base_url: http://192.168.68.116/v1 # ← RULE VIOLATION: unauthenticated path
api_key_env: LITELLM_API_KEY
Detection Query
Run on any Hermes host to detect violations:
# 1. Check config.yaml for hardcoded harness keys
grep -rn 'api_key: sk-' /root/.hermes/ \
--include='config.yaml' \
| grep -v 'deepseek\|openai\|anthropic\|DEEPSEEK'
# 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
# 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-litellm-7f96080d' /root/.config/systemd/ 2>/dev/null
# 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
If any output from step 2 — critical violation (master key leaked). Fix immediately.
Rotation Procedure
With this standard enforced, key rotation is one vault update:
# 1. Generate new key in LiteLLM: POST /key/generate with agent alias
# 2. Update Infisical vault secret
infisical secrets set LITELLM_API_KEY=sk-NEW_KEY \
--project=agents --env=production
# 3. Restart agent gateway (key auto-injected via infisical run -- wrapper)
ssh root@<host> "systemctl restart hermes-gateway"
# 4. Verify
curl -s -H "Authorization: Bearer sk-NEW_KEY" http://192.168.68.116/litellm/v1/models
Done. No config file changes needed. No /etc/environment edits needed.
The agent picks up the new key via infisical run -- at gateway startup.
Post-migration note: /etc/environment is NO LONGER the key source. Strip all
LITELLM_API_KEYlines from /etc/environment (comment out with# [INFISICAL]) and let theinfisical run --wrapper inject the key at runtime.
Key Longevity Policy (2026-07-04)
Keys are permanent and use bare agent name aliases.
- Duration:
null— keys never expire. This is enforced bydefault_key_generate_paramsinlitellm_config.yaml. - Alias convention: bare agent name only (e.g.,
tanko,mumuni,koby,koonimo). No dates, no versions. The alias IS the identity. - Rotation triggers: compromise, personnel departure, or quarterly security hygiene. NOT calendar-driven.
- Max budget: $100 per key (config default).
# In litellm_config.yaml — ensures all future keys inherit these defaults:
litellm_settings:
default_key_generate_params:
models: ["syslog-auto", "qwen3.6-27B-code", "gemma-4-12b"]
duration: null # ← permanent
max_budget: 100
metadata:
purpose: "agent-inference"
Verified Agents (2026-07-05 update)
| Agent | CT | IP | LiteLLM Alias | Key Source | Status | Gateway Wrapper | Last Verified |
|---|---|---|---|---|---|---|---|
| Tanko | 112 | .122 | tanko |
Infisical vault | ✅ Fixed | infisical run |
20:17 UTC Jul 5 |
| Mumuni | 100 (abiba) | .24 | mumuni |
Infisical vault | ✅ Fixed | Pi Hermes gateway | 2026-07-27 |
| Koby | 111 | ? | koby |
Infisical vault | ✅ Fixed | infisical run |
23:30 UTC Jul 5 |
| Koonimo | 113 | ? | koonimo |
Infisical vault | ✅ Fixed | infisical run (migrated 2026-07-11) |
2026-07-11 |
| Abiba | 100 | .65 | abiba-pi |
Infisical vault | ✅ N/A (pi native) | — | 19:44 UTC Jul 5 |
| Kagenz0 | 105 | ? | — | — | ❌ DOWN | — | 19:14 EDT Jul 4 |
Note
: CT hostnames (tdunna, baggy) differ from agent identities (koby, koonimo). LiteLLM key aliases use agent identity, not CT hostname.
Migration Status: Authenticated Path
| Agent | /litellm/v1/responses |
Deprecated /v1 |
Status |
|---|---|---|---|
| Mumuni | ✅ 5 sections | 0 | ✅ Authenticated |
| Tanko | ⚠️ No SSH access | — | Needs check |
| Koby | ⚠️ No route to host | — | Needs check |
| Koonimo | ⚠️ Connection timed out | — | Needs check |
Systemd Service Pattern (2026-07-11 — vault migration)
All Hermes agents use systemd to manage their gateway. The gateway service is wrapped
with infisical run -- to inject secrets at runtime.
Correct pattern (post-migration):
# Service file wraps gateway with Infisical:
[Service]
ExecStart=/usr/bin/infisical run --project=agents --env=production -- \
/usr/bin/hermes gateway run
# /etc/environment is CLEAN — no LITELLM_API_KEY present
# (strip it and tag with # [INFISICAL] if present)
Legacy pattern (deprecated — pre-migration only):
# DO NOT USE post-migration:
EnvironmentFile=/etc/environment
# This pattern was replaced by infisical run -- wrapper
Rotation procedure (one vault operation with this standard):
- Generate new key in LiteLLM:
curl /key/generatewith agent alias - Update Infisical vault:
infisical secrets set LITELLM_API_KEY=sk-NEW --project=agents --env=production - Restart:
systemctl restart hermes-gateway(key auto-injected via wrapper)
Violation Response
- Detect — run detection query above
- Fix — replace
api_key: sk-...withapi_key_env: LITELLM_API_KEYin all harness/litellm sections - Verify —
grep -c "api_key_env" config.yamlshould increase, hardcoded harness keys should be 0 - Restart — gateway must restart to pick up env var
- Confirm — test key against LiteLLM:
curl -H "Authorization: Bearer $KEY" .../v1/models→ 200 - Update — bump the verified table above
- Use safe-mutate — if the fix requires updating vault secrets or restarting the gateway on a remote host, use
safe-mutateto verify current state before mutating.
Related Contracts
hermes-config-template.prose.md— full configuration templatelitellm-health.prose.md— LiteLLM stack health verificationzulip-platform-verification.prose.md— cross-platform agent verificationlitellm-api-keys.prose.md— API key creation, rotation, and verification
CI Pipeline (2026-07-04)
All contract changes must pass the PR Pipeline before merge:
auth → validate → lint → ai-review → gate
- Trigger: push to master (abiba-bot only) or pull request
- Branch protection: Only
abiba-botcan push directly to master. All other users must use PRs. - Status check:
PR Pipeline — Authorize → Validate → Review → Mergerequired before merge - Runner:
runner-ct110(Gitea Actions v0.6.1) on CT 110 - Config:
.gitea/workflows/pr-pipeline.yaml
Known Bug: auxiliary_client ignores api_key_env (2026-07-05)
Bug: _resolve_task_provider_model() in agent/auxiliary_client.py reads
api_key from auxiliary task configs (vision, compression, etc.) but does NOT
resolve api_key_env. The custom provider resolution path handles api_key_env,
but auxiliary tasks take a different code path that ignores it.
Impact: Vision analysis and compression calls fall through to the "no-key-required"
placeholder, causing 401 errors on LiteLLM/harness (which require sk-* keys).
Workaround: Set api_key directly alongside api_key_env in each auxiliary
task config:
auxiliary:
vision:
api_key: sk-<agent-key-from-vault> # ← workaround (get via: infisical secrets get LITELLM_API_KEY --project=agents --env=production --plain)
api_key_env: LITELLM_API_KEY
base_url: http://192.168.68.116/v1
model: gemma-4-12b
provider: harness
compression:
api_key: sk-<agent-key-from-vault> # ← workaround (same as above)
api_key_env: LITELLM_API_KEY
base_url: http://192.168.68.116/v1
model: gemma-4-12b
provider: harness
Affected agents: All Hermes agents with harness/LiteLLM provider and
api_key_env in auxiliary configs (all 4 Hermes agents patched 2026-07-05).
Source location: agent/auxiliary_client.py line 5478