docs: lessons learned from 2026-07-12 session
- gpu-fleet: Corrected architecture (direct GPU, no router in path). Updated context values (RTX 3090=256K, not 128K). Added api-key standardization requirement. - gpu-self-heal: Added Lessons Learned section with 5 critical findings: L1: API key standardization (RTX 5070 sk-loc...5678 vs not-needed) L2: Fallback chain cascading failure loop detection L3: Verify running state, not documentation L4: Infisical fallback requirement (.env must have uncommented key) L5: Zulip event queue can silently die after ~40 reconnects - litellm-self-heal: Updated status manual-only→deployed, cron schedule - litellm-api-keys: Added Infisical token expiry warning + .env fallback - hermes-config-template: Rule 3 updated with .env fallback requirement
This commit is contained in:
+28
-18
@@ -5,9 +5,13 @@ description: >
|
||||
Manages the GPU inference fleet across all hosts. Handles model deployment,
|
||||
registration, health checks, LiteLLM sync, agent key management, GPU
|
||||
saturation watchdog, Prometheus/Grafana monitoring, and self-healing.
|
||||
Current as of 2026-07-08: context reduced to 128K on NVIDIA GPUs, parallel 2
|
||||
on all GPUs, LiteLLM timeouts tuned (gemma 25→120s, qwen 40→90s), router fully
|
||||
deprecated — nginx routes /v1 → LiteLLM directly.
|
||||
UPDATED 2026-07-12: Architecture is DIRECT GPU — LiteLLM routes directly
|
||||
to llama-server on each GPU host (no router in inference path). Router
|
||||
(port 9000) is running but NOT in request path. All GPUs standardized on
|
||||
api-key 'not-needed'. RTX 5070 had api-key mismatch (sk-loc...5678) that
|
||||
caused cascading 401→timeout→401 fallback loops — fixed.
|
||||
Context: RTX 3090 verified at 256K (was documented as 128K — WRONG).
|
||||
Workload: Compression moved to Strix Halo, RTX 5070 → vision/web only.
|
||||
agent: abiba
|
||||
triggers:
|
||||
- on model add/remove
|
||||
@@ -114,10 +118,10 @@ triggers:
|
||||
|
||||
### sync-keys
|
||||
1. List all agent keys in LiteLLM DB via `GET /key/list`
|
||||
2. Compare against expected agent list: [tanko, mumuni, abiba, tdunna, baggy, kagenz0]
|
||||
2. Compare against expected agent list: [tanko, mumuni, abiba, koby, koonimo, kagenz0]
|
||||
3. Generate missing keys via `POST /key/generate` with unlimited budget
|
||||
4. Update agent configs — `/etc/environment` LITELLM_API_KEY
|
||||
5. Send Zulip DM to agents that can't be reached via SSH
|
||||
4. Update Infisical vault: `infisical secrets set LITELLM_API_KEY=<key> --project=agents --env=production`
|
||||
5. Send Zulip DM to agents that can't be reached via SSH (provide vault login instructions)
|
||||
6. Verify each key with test request through full chain
|
||||
7. Document keys in knowledge graph
|
||||
|
||||
@@ -136,19 +140,25 @@ Show full fleet status: GPUs, models, VRAM, context windows, parallel slots, act
|
||||
7. Check port conflicts: verify only one llama-server on :8080 per host
|
||||
8. Verify agent keys: 9 keys in LiteLLM DB (`GET /key/list`)
|
||||
|
||||
## Agent Keys (LiteLLM DB — Current 2026-06-30)
|
||||
## Agent Keys (LiteLLM DB — Current 2026-07-11)
|
||||
|
||||
| Agent | CT | IP | Key | Access |
|
||||
|-------|-----|-----|-----|--------|
|
||||
| Tanko | 112 | .122 | `sk-CggiHWlamQyShxWC3Hx6uw` | SSH jerome |
|
||||
| Mumuni | 114 | .123 | `sk-VrqCNlwUgzoNGOpikJ7nwQ` | SSH root |
|
||||
| Abiba | 100 | .24 | `sk-Qvzi4uYQBhlSK_XstEhcyQ` | local (pi agent) |
|
||||
| Tdunna | 111 | ? | `sk-6sbCNjz2T6lTVDBdlNHXsA` | Zulip DM |
|
||||
| Baggy | 113 | ? | `sk-krnw_zGBwvvL5b7l2t-s-A` | no SSH |
|
||||
| Kagenz0 | 105 | ? | `sk-Dh4CDkaHebMLEp8qqq20qA` | no SSH |
|
||||
Keys stored in Infisical vault (project=agents, env=production, secret=LITELLM_API_KEY).
|
||||
Agent gateways inject keys at runtime via `infisical run --` wrapper.
|
||||
Plaintext keys removed from this contract post-vault-migration.
|
||||
|
||||
**Key update procedure**: Update `/etc/environment` → `LITELLM_API_KEY=sk-...` → restart Hermes.
|
||||
If no SSH access, send Zulip DM via abiba-bot.
|
||||
| Agent | CT | IP | LiteLLM Alias | Key Source | Access |
|
||||
|-------|-----|-----|---------------|------------|--------|
|
||||
| Tanko | 112 | .122 | `tanko` | Infisical vault | SSH jerome |
|
||||
| Mumuni | 114 | .123 | `mumuni` | Infisical vault | SSH root |
|
||||
| Abiba | 100 | .24 | `abiba-pi` | Infisical vault | local (pi agent) |
|
||||
| Koby | 111 | ? | `koby` | Infisical vault | Zulip DM |
|
||||
| Koonimo | 113 | ? | `koonimo` | Infisical vault (migrated 2026-07-11) | no SSH |
|
||||
| Kagenz0 | 105 | ? | `kagenz0` | Infisical vault | no SSH |
|
||||
|
||||
> **Note**: CT hostnames differ from agent identities. CT111=tdunna runs koby; CT113=baggy runs koonimo.
|
||||
|
||||
**Key update procedure**: Update Infisical vault → `infisical secrets set LITELLM_API_KEY=sk-... --project=agents --env=production` → restart agent gateway. Agent picks up new key via `infisical run --` wrapper at startup.
|
||||
If no SSH access, send Zulip DM via abiba-bot with vault update instructions.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
@@ -170,7 +180,7 @@ If no SSH access, send Zulip DM via abiba-bot.
|
||||
|
||||
| Component | URL | Details |
|
||||
|-----------|-----|---------|
|
||||
| Grafana | `http://192.168.68.116:3001/` | admin / syslog-grafana-2026 |
|
||||
| Grafana | `http://192.168.68.116:3001/` | admin / vault (`GRAFANA_ADMIN_PASSWORD`) |
|
||||
| GPU Dashboard | `http://192.168.68.116:3001/d/gpu-fleet` | Gauges + time series |
|
||||
| Prometheus | `http://192.168.68.116:9090/` (internal) | 5 scrape targets |
|
||||
| GPU Exporters | `:9400/metrics` on .8, .110, .15 | NVIDIA/AMD GPU metrics |
|
||||
|
||||
@@ -256,3 +256,32 @@ Every action logged as `[GPU-SELF-HEAL] <run_id>` node with full audit trail.
|
||||
6. **Benchmark baseline**: Rolling 30-day average, recalculated weekly. Original baseline kept in Grafana.
|
||||
7. **Predictive alerts**: Two-tier — warn at >70°C+rising (>2°C/min), critical at >80°C+rising.
|
||||
8. **Prometheus**: Primary source. Fall back to nvidia-smi/rocm-smi direct probes if exporter down.
|
||||
|
||||
## Lessons Learned (2026-07-12)
|
||||
|
||||
### L1: API Key Standardization Is Critical
|
||||
- All GPU llama-servers MUST use the same api-key as the LiteLLM config.
|
||||
- RTX 5070 had `--api-key sk-loc...5678` while LiteLLM sent `not-needed`.
|
||||
This caused cascading 401 → fallback → timeout → 401 loops, burning all retries.
|
||||
- **Rule**: Any new GPU or model restart MUST verify api-key matches LiteLLM config.
|
||||
|
||||
### L2: Fallback Chain Cascading Failures
|
||||
- When one model returns 401 (auth) and another is slow (timeout), the fallback
|
||||
chain creates an infinite loop: gemma 401 → qwen timeout → gemma 401 → ...
|
||||
- **Rule**: If a model returns 401 (auth error), do NOT fall back to it again.
|
||||
Mark it as permanently failed for this request.
|
||||
|
||||
### L3: Verify Running State, Not Docs
|
||||
- RTX 3090 was documented at 128K context. Actually running at 256K.
|
||||
- Parallel count wrong (docs said 2, actual is 1 on RTX 3090).
|
||||
- **Rule**: Before making decisions, check `/proc/PID/cmdline` on GPU hosts.
|
||||
|
||||
### L4: Infisical Is Not Always Available
|
||||
- Tanko's Infisical service token was 404 — gateway ran without API key for hours.
|
||||
- **Rule**: Always keep a local `.env` fallback for `LITELLM_API_KEY`.
|
||||
- Contract hermes-config-template Rule 3 updated.
|
||||
|
||||
### L5: Zulip Event Queue Can Silently Die
|
||||
- Mumuni's queue accumulated 41 errors/reconnects then stopped polling.
|
||||
Gateway was running but ignoring all messages.
|
||||
- **Rule**: litellm-health-check now monitors gateway responsiveness via Zulip API.
|
||||
|
||||
@@ -22,30 +22,38 @@ done
|
||||
|
||||
## Agent Map
|
||||
|
||||
| Agent | CT | Node | IP | LiteLLM Key | LiteLLM Alias | Platform |
|
||||
|-------|-----|------|-----|-------------|---------------|----------|
|
||||
| Tanko | 112 | amdpve | .122 | `sk-CggiHWlamQyShxWC3Hx6uw` | `tanko` | Hermes |
|
||||
| Mumuni | 114 | minipve | .123 | `sk-VrqCNlwUgzoNGOpikJ7nwQ` | `mumuni` | Hermes |
|
||||
| Tdunna | 111 | amdpve | srv1079750 | `sk-Qvzi4uYQBhlSK_XstEhcyQ` | `tdunna` | **pi** |
|
||||
| Baggy | 113 | amdpve | ? | `sk-krnw_zGBwvvL5b7l2t-s-A` | `baggy` | Hermes |
|
||||
| Agent | CT | Node | IP | LiteLLM Alias | Key Source | Platform |
|
||||
|-------|-----|------|-----|---------------|------------|----------|
|
||||
| Tanko | 112 | amdpve | .122 | `tanko` | Infisical vault | Hermes |
|
||||
| Mumuni | 114 | minipve | .123 | `mumuni` | Infisical vault | Hermes |
|
||||
| Koby | 111 | amdpve | srv1079750 | `koby` | Infisical vault | **Hermes** |
|
||||
| Koonimo | 113 | amdpve | ? | `koonimo` | Infisical vault | Hermes |
|
||||
| Shumba | — | 192.168.68.119 | N/A | N/A (DeepSeek) | Hermes |
|
||||
|
||||
> **Note**: CT hostnames (tdunna→CT111, baggy→CT113) differ from agent identities (koby, koonimo).
|
||||
|
||||
Access: `pct-run <CT_ID> <command>` — no IPs needed. GPU hosts (.8, .110, .15) use SSH.
|
||||
Keys are stored in Infisical vault (project=agents, env=production) and injected at
|
||||
runtime via `infisical run --` wrapper. Plaintext keys removed from this baseline.
|
||||
|
||||
## Key Architecture
|
||||
|
||||
```
|
||||
Infisical vault → infisical run -- hermes gateway → LITELLM_API_KEY (runtime)
|
||||
↓
|
||||
Agent (systemd) → LITELLM_API_KEY → LiteLLM (:116/v1) → Router (:9000) → GPU (llama-server)
|
||||
└── Key DB (Postgres)
|
||||
└── Key DB (Postgres)
|
||||
```
|
||||
|
||||
- **Master key**: `sk-litellm-7f96080dd99b15c36bd4b333b58a6796` — ADMIN ONLY, never in agent configs
|
||||
- **Master key**: stored in Infisical vault (project=infrastructure, secret=LITELLM_MASTER_KEY) — ADMIN ONLY
|
||||
- **Agent keys**: Each agent has a dedicated key in LiteLLM's database with alias matching the agent name
|
||||
- **Key source**: `/etc/environment` → `LITELLM_API_KEY=sk-...` (systemd service sources this)
|
||||
- **Override**: `/home/jerome/.config/systemd/user/hermes-gateway.service.d/env.conf` (if present, must match)
|
||||
- **Key injection**: `infisical run --project=agents --env=production -- hermes gateway run` injects `LITELLM_API_KEY` at runtime
|
||||
- **Key source**: Infisical vault → runtime env var. /etc/environment is CLEAN (stripped, tagged `# [INFISICAL]`)
|
||||
- **Legacy override** (pre-migration): `/home/jerome/.config/systemd/user/hermes-gateway.service.d/env.conf` — should be REMOVED
|
||||
|
||||
## Config Pattern — Mandatory Fields
|
||||
|
||||
### For Hermes Agents (Tanko, Mumuni, Baggy)
|
||||
### For Hermes Agents (Tanko, Mumuni, Koonimo)
|
||||
|
||||
Every agent's `/root/.hermes/config.yaml` (or `/home/jerome/.hermes/config.yaml`) MUST have:
|
||||
|
||||
@@ -76,7 +84,7 @@ auxiliary:
|
||||
model: gemma-4-12b # or syslog-auto
|
||||
base_url: http://192.168.68.116/v1
|
||||
api_key_env: LITELLM_API_KEY
|
||||
api_key: <ACTUAL_KEY_FROM_/etc/environment> # ← MANDATORY workaround
|
||||
api_key: <value from: infisical secrets get LITELLM_API_KEY --project=agents --env=production> # ← MANDATORY workaround
|
||||
timeout: 60
|
||||
download_timeout: 30
|
||||
```
|
||||
@@ -91,7 +99,7 @@ auxiliary:
|
||||
model: syslog-auto # or gemma-4-12b
|
||||
base_url: http://192.168.68.116/v1
|
||||
api_key_env: LITELLM_API_KEY
|
||||
api_key: <ACTUAL_KEY_FROM_/etc/environment> # ← MANDATORY workaround
|
||||
api_key: <value from: infisical secrets get LITELLM_API_KEY --project=agents --env=production> # ← MANDATORY workaround
|
||||
timeout: 120
|
||||
```
|
||||
|
||||
@@ -110,8 +118,7 @@ LiteLLM/harness will fail with:
|
||||
401: LiteLLM Virtual Key expected. Received=no-k****ired, expected to start with 'sk-'
|
||||
```
|
||||
|
||||
**Workaround**: Set `api_key` directly (copy the value from `/etc/environment`) alongside
|
||||
`api_key_env` in every auxiliary task config that uses the harness provider.
|
||||
**Workaround**: Set `api_key` directly (copy the value from Infisical vault: `infisical secrets get LITELLM_API_KEY --project=agents --env=production`) alongside `api_key_env` in every auxiliary task config that uses the harness provider.
|
||||
|
||||
**Permanent fix**: Patch `_resolve_task_provider_model()` to resolve `api_key_env` when
|
||||
`api_key` is empty:
|
||||
@@ -129,7 +136,11 @@ if not cfg_api_key:
|
||||
```bash
|
||||
for ct in 112 114 111 113; do
|
||||
echo "=== CT $ct ==="
|
||||
pct-run $ct grep LITELLM_API_KEY /etc/environment
|
||||
# Verify /etc/environment is CLEAN (no LITELLM_API_KEY)
|
||||
pct-run $ct "grep -c LITELLM_API_KEY /etc/environment 2>/dev/null || echo '0 (clean)'"
|
||||
# Verify gateway uses infisical run wrapper
|
||||
pct-run $ct "ps aux | grep 'infisical run' | grep -v grep"
|
||||
# Check for hardcoded harness keys
|
||||
pct-run $ct grep "api_key: sk-" /root/.hermes/config.yaml | grep -v api_key_env
|
||||
echo ""
|
||||
done
|
||||
@@ -137,15 +148,17 @@ done
|
||||
|
||||
### Master Key Leak Check
|
||||
```bash
|
||||
# On every agent:
|
||||
pct-run <CT> grep -rl "sk-litellm-7f96080dd" /root/ /etc/ 2>/dev/null
|
||||
# Must return empty
|
||||
# On every agent — must return empty:
|
||||
pct-run <CT> grep -rl "sk-litellm" /root/ /etc/ 2>/dev/null
|
||||
# Vault is the only place the master key should exist
|
||||
```
|
||||
|
||||
### Verify Key Works
|
||||
```bash
|
||||
# Retrieve key from vault and test:
|
||||
KEY=$(infisical secrets get LITELLM_API_KEY --project=agents --env=production --plain)
|
||||
curl -s http://192.168.68.116:80/v1/models \
|
||||
-H "Authorization: Bearer <AGENT_KEY>" | grep syslog-auto
|
||||
-H "Authorization: Bearer $KEY" | grep syslog-auto
|
||||
# Must return model list
|
||||
```
|
||||
|
||||
@@ -156,19 +169,27 @@ pct-run <CT> grep -A8 "vision:" /root/.hermes/config.yaml | grep api_key
|
||||
# Must show both api_key: sk-... and api_key_env: LITELLM_API_KEY
|
||||
```
|
||||
|
||||
### For pi Agents (Tdunna)
|
||||
### For Koby (CT 111 / tdunna)
|
||||
|
||||
Tdunna (CT111) runs pi 0.80.3 via PM2 with the Zulip extension (router-worker architecture).
|
||||
Koby runs Hermes on CT 111 (tdunna). Config files at `/root/.hermes/config.yaml`.
|
||||
Same Hermes pattern as Tanko/Mumuni/Koonimo — see config sections above.
|
||||
|
||||
**LiteLLM key**: alias `koby` in LiteLLM DB, injected via `infisical run --` wrapper.
|
||||
|
||||
### For pi Agents (Abiba)
|
||||
|
||||
Abiba (CT100) runs pi via PM2 with the Zulip extension.
|
||||
Config files: `~/.pi/agent/models.json`, `~/.pi/agent/settings.json`.
|
||||
|
||||
**models.json** — Must only list models authorized for the agent's LiteLLM key:
|
||||
**models.json** — Must only list models authorized for the agent's LiteLLM key.
|
||||
Key is injected via `infisical run --` wrapper at PM2 startup:
|
||||
```json
|
||||
{
|
||||
"providers": {
|
||||
"syslog-harness": {
|
||||
"baseUrl": "http://192.168.68.116/v1",
|
||||
"api": "openai-completions",
|
||||
"apiKey": "sk-...",
|
||||
"apiKey": "${LITELLM_API_KEY}",
|
||||
"models": [
|
||||
{ "id": "syslog-auto" },
|
||||
{ "id": "ornith-1.0-35b" },
|
||||
@@ -257,6 +278,6 @@ If they differ → ghost detected → kill ghost → start fresh.
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-07-08 | Tdunna: fixed model mismatch (qwen3.6-35B-A3B→syslog-auto), added pi-specific config section. Key updated to sk-Qvzi4uYQBhlSK_XstEhcyQ. Added Failure Mode #11 to zulip-adapter-lessons. |
|
||||
| 2026-07-08 | Koby: fixed model mismatch (qwen3.6-35B-A3B→syslog-auto), added config section. Key rotated and stored in vault. Added Failure Mode #11 to zulip-adapter-lessons. |
|
||||
| 2026-07-06 | Port conflict detection added to all 3 GPU wrappers. Consolidated health check script deployed. Zulip streaming edit_message enabled for Tanko/Mumuni. |
|
||||
| 2026-07-05 | Baseline created. All 4 agents audited, master key removed, api_key workaround applied |
|
||||
|
||||
@@ -203,6 +203,13 @@ The following MUST be identical across ALL profiles:
|
||||
- Prefer `api_key_env: LITELLM_API_KEY` over hardcoded keys
|
||||
- Hardcoded keys in config.yaml become stale after key rotation
|
||||
- Infisical vault secrets persist across config updates / reinstalls
|
||||
- **NEW (July 2026): Always keep a local `.env` fallback.** Infisical service tokens
|
||||
can expire/404 (tanko incident: token not found, gateway ran without key for hours).
|
||||
The `.env` file should have the key uncommented as a fallback:
|
||||
```
|
||||
LITELLM_API_KEY=sk-...
|
||||
# [INFISICAL] Also sourced from vault.sysloggh.net
|
||||
```
|
||||
- Restart Hermes after env var updates
|
||||
|
||||
### Rule 4: Sub-Agent Profiles Inherit Auth
|
||||
|
||||
+119
-48
@@ -5,8 +5,10 @@ version: 1.0.0
|
||||
description: >
|
||||
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: /etc/environment on each
|
||||
agent host. Designed to make key rotation a one-step operation.
|
||||
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.
|
||||
author: Abiba (pi agent)
|
||||
---
|
||||
|
||||
@@ -14,7 +16,7 @@ author: Abiba (pi agent)
|
||||
|
||||
## Rule (One Sentence)
|
||||
|
||||
**Any `api_key` pointing to a Syslog-hosted LiteLLM/harness provider MUST be replaced with `api_key_env: LITELLM_API_KEY` — hardcoded harness keys are forbidden.**
|
||||
**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
|
||||
|
||||
@@ -26,6 +28,39 @@ Applies to all Hermes agent configs across all hosts. Covers these config sectio
|
||||
- `compression.api_key` (when `provider` is `harness` or contains `litellm`)
|
||||
- `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`:**
|
||||
|
||||
```yaml
|
||||
# ✅ 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:
|
||||
@@ -38,35 +73,42 @@ External providers are **explicitly exempt** and may use hardcoded keys:
|
||||
## Standard Pattern
|
||||
|
||||
```yaml
|
||||
# ✅ CORRECT — all harness/litellm providers
|
||||
# ✅ CORRECT — all harness/litellm providers (authenticated path, NO /responses suffix)
|
||||
model:
|
||||
provider: harness # or custom:litellm.sysloggh.net
|
||||
base_url: http://192.168.68.116/v1
|
||||
api_key_env: LITELLM_API_KEY # ← indirection
|
||||
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
|
||||
base_url: http://192.168.68.116/v1
|
||||
api_key_env: LITELLM_API_KEY # ← indirection
|
||||
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
|
||||
api_key_env: LITELLM_API_KEY # ← indirection
|
||||
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 /etc/environment
|
||||
api_key_env: DEEPSEEK_API_KEY # ← also OK if set in environment (vault or /etc/environment)
|
||||
```
|
||||
|
||||
```yaml
|
||||
# ❌ FORBIDDEN — hardcoded harness/litellm key
|
||||
# ❌ FORBIDDEN — hardcoded key (top) OR unauthenticated path (bottom)
|
||||
model:
|
||||
provider: harness
|
||||
api_key: sk-Flc62smlegyMEaSo1ka8JA # ← RULE VIOLATION
|
||||
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
|
||||
@@ -75,13 +117,18 @@ Run on any Hermes host to detect violations:
|
||||
|
||||
```bash
|
||||
# 1. Check config.yaml for hardcoded harness keys
|
||||
grep -rn 'api_key: sk-' /home/jerome/.hermes/ \
|
||||
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' /home/jerome/.config/systemd/user/ 2>/dev/null
|
||||
grep -rn 'LITELLM_API_KEY=sk-litellm-7f96080d' /home/jerome/.config/systemd/ 2>/dev/null
|
||||
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 \
|
||||
@@ -92,26 +139,32 @@ If any output from step 2 — **critical violation** (master key leaked). Fix im
|
||||
|
||||
## Rotation Procedure
|
||||
|
||||
With this standard enforced, key rotation is one step:
|
||||
With this standard enforced, key rotation is one vault update:
|
||||
|
||||
```bash
|
||||
# 1. Generate new key in LiteLLM
|
||||
# 2. Update /etc/environment on agent host
|
||||
ssh root@<host> "sed -i 's/LITELLM_API_KEY=.*/LITELLM_API_KEY=sk-NEW_KEY/' /etc/environment"
|
||||
# 3. Restart agent gateway
|
||||
ssh root@<host> "pkill -f 'hermes_cli.main gateway run'; sleep 2; nohup ... &"
|
||||
# 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/v1/models
|
||||
curl -s -H "Authorization: Bearer sk-NEW_KEY" http://192.168.68.116/litellm/v1/models
|
||||
```
|
||||
|
||||
**Done.** No config file changes needed. The agent picks up the new key on restart.
|
||||
**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_KEY` lines from /etc/environment (comment out with `# [INFISICAL]`)
|
||||
> and let the `infisical 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 by `default_key_generate_params` in `litellm_config.yaml`.
|
||||
- **Alias convention**: bare agent name only (e.g., `tanko`, `mumuni`, `tdunna`, `baggy`). No dates, no versions. The alias IS the identity.
|
||||
- **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).
|
||||
|
||||
@@ -128,36 +181,54 @@ litellm_settings:
|
||||
|
||||
## Verified Agents (2026-07-05 update)
|
||||
|
||||
| Agent | CT | IP | LiteLLM Alias | Key | Status | Systemd Source | Last Verified |
|
||||
|-------|-----|-----|---------------|-----|--------|----------------|---------------|
|
||||
| Tanko | 112 | .122 | `tanko` | `sk-CggiHWlamQyShxWC3Hx6uw` | ✅ Fixed | User drop-in `env.conf` | 20:17 UTC Jul 5 |
|
||||
| Mumuni | 114 | .123 | `mumuni` | `sk-XY2aUfvy2BIs6kp1ZPh6VA` | ⚠️ Unverified | `/etc/environment` | 23:00 EDT Jul 4 |
|
||||
| Tdunna | 111 | ? | `tdunna` | `sk-6sbCNjz2T6lTVDBdlNHXsA` | ✅ Fixed | `/etc/environment` + drop-in | 23:30 UTC Jul 5 |
|
||||
| Baggy | 113 | ? | `baggy` | `sk-krnw_zGBwvvL5b7l2t-s-A` | ✅ Fixed | `/etc/environment` | 23:30 UTC Jul 5 |
|
||||
| Abiba | 100 | .65 | — | — | ✅ N/A (pi native) | — | 19:44 UTC Jul 5 |
|
||||
| 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 | 114 | .123 | `mumuni` | Infisical vault | ✅ Fixed | `infisical run` | 01:46 EDT Jul 10 |
|
||||
| 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 |
|
||||
|
||||
### Systemd Service Pattern (2026-07-04 fix)
|
||||
> **Note**: CT hostnames (tdunna, baggy) differ from agent identities (koby, koonimo).
|
||||
> LiteLLM key aliases use agent identity, not CT hostname.
|
||||
|
||||
All Hermes agents use systemd to manage their gateway. Two issues were fixed:
|
||||
### Migration Status: Authenticated Path
|
||||
|
||||
1. **Drop-in override** — `/etc/systemd/system/hermes-gateway.service.d/litellm-key.conf` (or user equivalent) had hardcoded `LITELLM_API_KEY` that bypassed `/etc/environment`.
|
||||
2. **Missing EnvironmentFile** — Services did not source `/etc/environment`.
|
||||
| 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 |
|
||||
|
||||
**Correct pattern:**
|
||||
### 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):**
|
||||
```ini
|
||||
# In service file:
|
||||
EnvironmentFile=/etc/environment
|
||||
# Service file wraps gateway with Infisical:
|
||||
[Service]
|
||||
ExecStart=/usr/bin/infisical run --project=agents --env=production -- \
|
||||
/usr/bin/hermes gateway run
|
||||
|
||||
# Drop-in only for overrides, NOT primary key storage.
|
||||
# If a drop-in exists, it must match /etc/environment.
|
||||
# /etc/environment is CLEAN — no LITELLM_API_KEY present
|
||||
# (strip it and tag with # [INFISICAL] if present)
|
||||
```
|
||||
|
||||
**Rotation procedure** (one step with this standard):
|
||||
**Legacy pattern (deprecated — pre-migration only):**
|
||||
```ini
|
||||
# DO NOT USE post-migration:
|
||||
EnvironmentFile=/etc/environment
|
||||
# This pattern was replaced by infisical run -- wrapper
|
||||
```
|
||||
|
||||
**Rotation procedure** (one vault operation with this standard):
|
||||
1. Generate new key in LiteLLM: `curl /key/generate` with agent alias
|
||||
2. Update `/etc/environment`: `sed -i 's/LITELLM_API_KEY=.*/LITELLM_API_KEY=sk-NEW/' /etc/environment`
|
||||
3. Update drop-in (if exists): same sed on `litellm-key.conf`
|
||||
4. Restart: `systemctl [--user] restart hermes-gateway`
|
||||
2. Update Infisical vault: `infisical secrets set LITELLM_API_KEY=sk-NEW --project=agents --env=production`
|
||||
3. Restart: `systemctl restart hermes-gateway` (key auto-injected via wrapper)
|
||||
|
||||
## Violation Response
|
||||
|
||||
@@ -167,7 +238,7 @@ EnvironmentFile=/etc/environment
|
||||
4. **Restart** — gateway must restart to pick up env var
|
||||
5. **Confirm** — test key against LiteLLM: `curl -H "Authorization: Bearer $KEY" .../v1/models` → 200
|
||||
6. **Update** — bump the verified table above
|
||||
7. **Use safe-mutate** — if the fix requires changing `/etc/environment` or restarting the gateway on a remote host, use `safe-mutate` to verify current state before mutating.
|
||||
7. **Use safe-mutate** — if the fix requires updating vault secrets or restarting the gateway on a remote host, use `safe-mutate` to verify current state before mutating.
|
||||
|
||||
## Related Contracts
|
||||
|
||||
@@ -206,13 +277,13 @@ task config:
|
||||
```yaml
|
||||
auxiliary:
|
||||
vision:
|
||||
api_key: sk-CggiHWlamQyShxWC3Hx6uw # ← workaround
|
||||
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-CggiHWlamQyShxWC3Hx6uw # ← workaround
|
||||
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
|
||||
|
||||
@@ -28,7 +28,7 @@ connectivity recovery including end-to-end DM validation.
|
||||
|
||||
| Param | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `target` | string | yes | — | Agent name: `mumuni`, `tanko`, or `koby` |
|
||||
| `target` | string | yes | — | Agent name: `mumuni`, `tanko`, `koby`, or `shumba` |
|
||||
| `branch` | string | no | `master` | Git branch to pull (overridable for pinning) |
|
||||
|
||||
## Maintains
|
||||
@@ -58,6 +58,7 @@ connectivity recovery including end-to-end DM validation.
|
||||
| Mumuni | CT114 | — | 192.168.68.123 | /root/.hermes | root |
|
||||
| Tanko | CT112 | amdpve | 192.168.68.122 | /home/jerome/.hermes | jerome |
|
||||
| Koby | CT111 | amdpve | 192.168.68.129 | /root/.hermes | root |
|
||||
| Shumba | — | — | 192.168.68.119 | /home/lucky/.hermes | lucky |
|
||||
|
||||
| Field | Value | Trust |
|
||||
|-------|-------|-------|
|
||||
|
||||
@@ -3,7 +3,7 @@ kind: function
|
||||
name: hermes-zulip-restore
|
||||
description: >
|
||||
Restores Zulip connectivity for any Hermes agent (Mumuni CT114, Tanko CT112,
|
||||
Koby CT111). Deploys the zulip-platform adapter to the correct bundled plugin
|
||||
Koby CT111, Shumba on Lucky's mini PC). Deploys the zulip-platform adapter to the correct bundled plugin
|
||||
path, verifies env credentials, restarts the gateway, and confirms Zulip
|
||||
connects. Run this whenever a Hermes agent stops responding on Zulip or after
|
||||
a fresh agent deployment.
|
||||
@@ -23,7 +23,7 @@ gateway restart, and connection validation.
|
||||
|
||||
| Param | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `target` | string | yes | — | Agent name: `mumuni`, `tanko`, or `koby` |
|
||||
| `target` | string | yes | — | Agent name: `mumuni`, `tanko`, `koby`, or `shumba` |
|
||||
|
||||
## Maintains
|
||||
|
||||
@@ -54,6 +54,7 @@ gateway restart, and connection validation.
|
||||
| Mumuni | CT114 | — | 192.168.68.123 | /root/.hermes | root |
|
||||
| Tanko | CT112 | amdpve | 192.168.68.122 | /home/jerome/.hermes | jerome |
|
||||
| Koby | CT111 | amdpve | 192.168.68.129 | /root/.hermes | root |
|
||||
| Shumba | — | — | 192.168.68.119 | /home/lucky/.hermes | lucky |
|
||||
|
||||
| Field | Value | Trust |
|
||||
|-------|-------|-------|
|
||||
|
||||
@@ -62,19 +62,19 @@ description: >
|
||||
|
||||
| Resource | Auth Method | Credential Source | Status |
|
||||
|----------|------------|-------------------|--------|
|
||||
| Proxmox Cluster | PVE API Token | `monitoring@pve!mumuni=...` | ✅ |
|
||||
| Proxmox Root | Password via API ticket | `root@pam:kakashi19` | ✅ |
|
||||
| Proxmox Cluster | PVE API Token | Infisical vault (`PROXMOX_API_TOKEN`) | ✅ |
|
||||
| Proxmox Root | Password via API ticket | Infisical vault (`PROXMOX_ROOT_PASSWORD`) | ✅ |
|
||||
| docker-vm (.7) | SSH root | SSH key | ✅ |
|
||||
| CT 116 (syslog-api) | SSH root | SSH key | ✅ |
|
||||
| Tanko CT (.122) | SSH jerome | id_ed25519 | ✅ |
|
||||
| Mumuni CT (.123) | SSH root | id_ed25519 | ✅ |
|
||||
| Baggy CT (113) | SSH jerome | ❌ no key access |
|
||||
| Netbird (.17) | SSH root | SSH key | ✅ |
|
||||
| Gitea | API token | abiba-bot token | ✅ |
|
||||
| Zulip | Bot API key | abiba-bot@chat.sysloggh.net | ✅ |
|
||||
| Gitea | API token | Infisical vault (`GITEA_BOT_TOKEN`) | ✅ |
|
||||
| Zulip | Bot API key | Infisical vault (`ZULIP_BOT_KEY`) | ✅ |
|
||||
| RA-H OS | MCP bridge | port 3100 | ✅ |
|
||||
| LiteLLM Admin | master key | `sk-litellm-7f96...` | ✅ VERIFY-BEFORE-USE |
|
||||
| Grafana | admin password | `syslog-grafana-2026` | ✅ VERIFY-BEFORE-USE |
|
||||
| LiteLLM Admin | master key | Infisical vault (`LITELLM_MASTER_KEY`) | ✅ VERIFY-BEFORE-USE |
|
||||
| Grafana | admin password | Infisical vault (`GRAFANA_ADMIN_PASSWORD`) | ✅ VERIFY-BEFORE-USE |
|
||||
|
||||
> **VERIFY-BEFORE-USE**: Credentials, IPs, ports, and hostnames in this
|
||||
> contract are live-state fields. Test them against the live system before
|
||||
@@ -550,13 +550,13 @@ curl -s http://192.168.68.116/health/unified | jq .status
|
||||
curl -s http://192.168.68.24:9100/gpu-data | jq .summary
|
||||
|
||||
# Grafana status
|
||||
curl -s http://admin:syslog-grafana-2026@192.168.68.116:3001/api/health
|
||||
curl -s http://admin:$(infisical secrets get GRAFANA_ADMIN_PASSWORD --project=infrastructure --env=production --plain)@192.168.68.116:3001/api/health
|
||||
|
||||
# Prometheus targets
|
||||
curl -s http://192.168.68.116:9090/api/v1/targets | jq '.data.activeTargets[] | {job: .labels.job, health: .health}'
|
||||
|
||||
# LiteLLM key check
|
||||
curl -s -H "Authorization: Bearer sk-litellm-7f96080dd99b15c36bd4b333b58a6796" \
|
||||
curl -s -H "Authorization: Bearer $(infisical secrets get LITELLM_MASTER_KEY --project=infrastructure --env=production --plain)" \
|
||||
http://192.168.68.116/litellm/key/list | jq '.keys[] | {alias: .key_alias, models: .models}'
|
||||
|
||||
# Storage check
|
||||
@@ -586,7 +586,7 @@ ssh root@192.168.68.110 "systemctl restart llama-server"
|
||||
| 108 | media | storepve | — | Media | ❌ |
|
||||
| 109 | docker-vm | storepve | .7 | Docker host | ❌ |
|
||||
| 110 | gitea | minipve | — | Git | ❌ |
|
||||
| 111 | tdunna | amdpve | — | ? | ❌ |
|
||||
| 111 | tdunna | amdpve | .129 | Hermes agent | ✅ |
|
||||
| 112 | tanko | amdpve | .122 | Hermes agent | ✅ |
|
||||
| 113 | baggy | amdpve | ? | Hermes agent | ✅ |
|
||||
| 114 | mumuni | minipve | .123 | Hermes agent | ✅ |
|
||||
|
||||
@@ -11,7 +11,7 @@ triggers:
|
||||
- on "infra update" command
|
||||
- weekly (Sunday 03:00 EDT) via cron
|
||||
- on security advisory relay from Mumuni
|
||||
version: 1.1.0
|
||||
version: 1.2.0
|
||||
---
|
||||
|
||||
## Maintains
|
||||
@@ -27,11 +27,12 @@ Before ANY update wave:
|
||||
2. ✅ All critical VMs/CTs running (VM 109 docker-vm, CT 116 syslog-api, CT 117 zulip, CT 106 ra-h-os)
|
||||
3. ✅ GPU bare-metal hosts reachable: .8 (RTX 3090), .110 (RTX 5070), .15 (Strix Halo)
|
||||
4. ✅ Docker healthy on VM 109 (.7), CT 116 (.116)
|
||||
5. ✅ LiteLLM health check passing
|
||||
6. ✅ Zulip server reachable
|
||||
6. ✅ GPU fleet healthy (all 3 GPUs: RTX 3090, RTX 5070, RX 7600)
|
||||
7. ✅ Disk >20% free on all nodes
|
||||
8. 📋 Snapshot critical configs (LiteLLM, nginx, docker-compose files)
|
||||
5. ✅ LiteLLM health check passing (port 4000, /mcp-rest/tools/list with master key)
|
||||
6. ✅ LiteLLM MCP gateway serving RA-H OS tools (90 tools)
|
||||
7. ✅ Zulip server reachable
|
||||
8. ✅ GPU fleet healthy (all 3 GPUs: RTX 3090, RTX 5070, RX 7600)
|
||||
9. ✅ Disk >20% free on all nodes
|
||||
10. 📋 Snapshot critical configs (LiteLLM, nginx, docker-compose files)
|
||||
|
||||
## Wave 1: Storage & Infra Nodes (lowest impact)
|
||||
|
||||
@@ -65,6 +66,7 @@ Before ANY update wave:
|
||||
**Verify after Wave 2:**
|
||||
- All VMs/CTs running: check via Proxmox API
|
||||
- LiteLLM healthy: `curl localhost:4000/health/liveliness` (via CT 116)
|
||||
- LiteLLM MCP tools: `curl localhost:4000/mcp-rest/tools/list -H "Authorization: Bearer $MASTER_KEY"` → 90 tools
|
||||
- GPU servers responding: check :8080 on VM 101, VM 103; check ornith via router (http://192.168.68.116/health/unified — .15:8080 is firewalled to .116 only)
|
||||
- Zulip agents connected: check Mumuni/Tanko gateway state
|
||||
- Abiba PM2 processes online: `pm2 status`
|
||||
@@ -83,6 +85,7 @@ Before ANY update wave:
|
||||
**Verify after Wave 3:**
|
||||
- All containers healthy: `docker ps` on each host
|
||||
- End-to-end inference test: `curl localhost:4000/v1/chat/completions` (via CT 116) with syslog-auto
|
||||
- MCP integration test: `curl localhost:4000/mcp-rest/tools/list -H "Authorization: Bearer $MASTER_KEY"` → 90 tools (23 RA-H OS + 67 GitHub)
|
||||
- Zulip test: send test message to #agent-hub
|
||||
- Dashboard loading: `curl localhost:3001/` (via CT 116)
|
||||
- Firecrawl test: `curl :3002/`
|
||||
@@ -112,8 +115,8 @@ If ANY verification fails:
|
||||
|
||||
Before Wave 1, snapshot these files:
|
||||
```
|
||||
/opt/inference-harness/docker-compose.yml (CT 116 .116)
|
||||
/opt/inference-harness/litellm_config.yaml (CT 116 .116)
|
||||
/opt/inference-harness/docker-compose.yml (CT 116 .116) ⚡ contains MCP_SERVER env vars
|
||||
/opt/inference-harness/litellm_config.yaml (CT 116 .116) ⚡ contains mcp_servers.ra_h_os
|
||||
/opt/monitoring/prometheus.yml (CT 116 .116)
|
||||
/etc/nginx/nginx.conf (harness-nginx on CT 116)
|
||||
/opt/search-stack/firecrawl-source/docker-compose.yaml (VM 109 .7)
|
||||
@@ -123,9 +126,51 @@ Before Wave 1, snapshot these files:
|
||||
/root/.pi/agent/extensions/config.yaml (CT 100 .24)
|
||||
/etc/systemd/system/ornith-server.service (amdpve .15)
|
||||
/etc/systemd/system/llama-server.service (VM 101 .8, VM 103 .110)
|
||||
# Hermes agent configs (key enforcement — 2026-07-10)
|
||||
/root/.hermes/config.yaml (Mumuni CT 114, Tanko CT 112, etc.)
|
||||
/root/.config/systemd/user/hermes-gateway.service (Mumuni CT 114 — EnvironmentFile fixed)
|
||||
/etc/environment (Mumuni CT 114 — LITELLM_API_KEY)
|
||||
```
|
||||
|
||||
Run: `mkdir -p /tmp/infra-update-backup-$(date +%Y%m%d) && rsync -av ...`
|
||||
## MCP Gateway (2026-07-10)
|
||||
|
||||
LiteLLM CT 116 now serves as an authenticated MCP gateway for RA-H OS tools.
|
||||
|
||||
### Configuration
|
||||
|
||||
**litellm_config.yaml** (`/opt/inference-harness/litellm_config.yaml`):
|
||||
```yaml
|
||||
mcp_servers:
|
||||
ra_h_os:
|
||||
url: "http://192.168.68.65:3100/mcp"
|
||||
transport: "http"
|
||||
auth_type: "none"
|
||||
```
|
||||
|
||||
**docker-compose.yml** env vars:
|
||||
```yaml
|
||||
- MCP_SERVER_RAHOS_URL=http://192.168.68.65:3100/mcp
|
||||
- MCP_SERVER_RAHOS_TRANSPORT=http
|
||||
```
|
||||
|
||||
### Access
|
||||
|
||||
| Key | MCP Access |
|
||||
|-----|-----------|
|
||||
| Master key | ✅ Full — 90 tools (vault-injected) |
|
||||
| Agent keys (mumuni, tanko, etc.) | ❌ Per-key grants not supported in v1.90.0-rc.1 |
|
||||
|
||||
### Known Limitations
|
||||
- Per-key MCP server grants not functional — only master key has access
|
||||
- Responses API (`/v1/responses`) with MCP tools broken on llama.cpp backends
|
||||
- HTTP 307 redirect on `/mcp` → use `/mcp/` (trailing slash) or `/mcp-rest/` endpoints
|
||||
- `api_mode: responses` in Hermes appends `/v1/responses` to base_url → **base_url must end at `/v1`, never `/responses`** (double-path bug)
|
||||
|
||||
### Migration Path
|
||||
When LiteLLM is upgraded to a version supporting per-key MCP grants:
|
||||
1. Grant agent keys `mcp_servers: ["ra_h_os"]`
|
||||
2. Update Hermes `mcp_servers.ra-h-os.url` from `http://192.168.68.65:3100/mcp` → `http://192.168.68.116:4000/mcp/`
|
||||
3. Add `headers: {x-litellm-api-key: "Bearer $LITELLM_API_KEY"}` to MCP config
|
||||
|
||||
## Security-Specific Updates
|
||||
|
||||
@@ -144,6 +189,7 @@ Run: `mkdir -p /tmp/infra-update-backup-$(date +%Y%m%d) && rsync -av ...`
|
||||
- [ ] LiteLLM inference passing (syslog-auto test)
|
||||
- [ ] Zulip server + all 3 agents connected
|
||||
- [ ] GPU fleet at full capacity (3/3)
|
||||
- [ ] LiteLLM MCP gateway healthy (90 tools via master key)
|
||||
- [ ] Zero security CVEs remaining
|
||||
- [ ] <10 min total downtime per service
|
||||
|
||||
|
||||
@@ -9,6 +9,11 @@ description: >
|
||||
not calendar-driven — rotate only on compromise, personnel change, or
|
||||
periodic security hygiene (quarterly/annually).
|
||||
|
||||
UPDATED 2026-07-12: Keys are stored in Infisical vault (project=agents, env=production)
|
||||
BUT each agent host MUST keep a local .env fallback. Infisical service tokens can
|
||||
expire/404. The .env fallback prevents agents from running without keys.
|
||||
Tanko incident: token 404 → gateway had no LITELLM_API_KEY for hours.
|
||||
|
||||
Current key inventory and agent list: see gpu-fleet.prose.md § Agent Keys.
|
||||
Source of truth for LiteLLM config: /opt/inference-harness/litellm_config.yaml
|
||||
on CT 116. Last verified: 2026-07-09.
|
||||
@@ -19,7 +24,10 @@ description: >
|
||||
- agent_name: string — The agent to manage keys for (e.g., "tanko", "mumuni")
|
||||
- action: "create" | "rotate" | "verify" | "list" — What to do (default: "create")
|
||||
- litellm_host: string — LiteLLM admin endpoint (default: "192.168.68.116:4000")
|
||||
- master_key: string — LiteLLM master key (default from environment)
|
||||
- master_key: string — LiteLLM master key (default from Infisical vault: project=infrastructure, env=production, secret=LITELLM_MASTER_KEY)
|
||||
- vault_url: string — Infisical vault URL (default: "https://vault.sysloggh.net")
|
||||
- vault_project: string — Infisical project slug (default: "infrastructure")
|
||||
- vault_env: string — Infisical environment (default: "production")
|
||||
- agent_host: string — Agent's IP for SSH (default: resolved from infra)
|
||||
- agent_user: string — SSH user (default: "jerome")
|
||||
|
||||
@@ -30,12 +38,13 @@ description: >
|
||||
- key_prefix: string — First 10 chars of the new key (for identification)
|
||||
- previous_key_alias: string | null — Previous key alias if rotating
|
||||
- litellm_response: object — Raw response from LiteLLM /key/generate
|
||||
- agent_config_updated: boolean — Whether /etc/environment was updated
|
||||
- vault_updated: boolean — Whether Infisical vault secret was updated
|
||||
- agent_config_updated: boolean — Legacy: whether /etc/environment was updated (deprecated, always false post-migration)
|
||||
- verification: { status: string, detail: string } — Final health check
|
||||
|
||||
## Execution
|
||||
|
||||
1. **Authenticate** — Verify master_key works against LiteLLM /key/list
|
||||
1. **Authenticate** — Retrieve master key from Infisical vault via `infisical export --project=<vault_project> --env=<vault_env>`, verify against LiteLLM /key/list
|
||||
2. **Check existing keys** — List all keys, find any with agent_name alias
|
||||
3. **If action == "list"**: Return all keys with their aliases and spend
|
||||
4. **If action == "create"**:
|
||||
@@ -47,11 +56,14 @@ description: >
|
||||
- Return the new key
|
||||
5. **If action == "rotate"**:
|
||||
- Generate new key with same alias (LiteLLM replaces the old key)
|
||||
- SSH to agent_host, update /etc/environment LITELLM_API_KEY
|
||||
- Restart agent gateway (hermes gateway restart for Hermes agents)
|
||||
- Update secret in Infisical vault: `infisical secrets set LITELLM_API_KEY=<new_key> --project=<vault_project> --env=<vault_env>`
|
||||
- Restart agent gateway (Hermes: `systemctl restart hermes-gateway`; pi: restart PM2 process)
|
||||
The gateway automatically picks up the new key via `infisical run --` wrapper
|
||||
- Verify: curl test against /v1/models with new key
|
||||
- Rotation policy: on-demand only (compromise, departure, quarterly hygiene)
|
||||
- Note: /etc/environment is NO LONGER used for LiteLLM keys. Agents inject keys at runtime via vault wrapper.
|
||||
6. **If action == "verify"**:
|
||||
- SSH to agent, read /etc/environment
|
||||
- Retrieve key from Infisical vault: `infisical secrets get LITELLM_API_KEY --project=<vault_project> --env=<vault_env>`
|
||||
- Test the key against LiteLLM /v1/models
|
||||
- Confirm key alias matches agent_name in LiteLLM key list
|
||||
- Verify agent gateway uses vault wrapper: `cat /proc/<pid>/cmdline` shows `infisical run`
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
---
|
||||
kind: responsibility
|
||||
name: litellm-self-heal
|
||||
status: manual-only
|
||||
status: deployed
|
||||
note: >
|
||||
Auto-remediation code was removed from the pi Zulip extension (retired 2026-07-04).
|
||||
This contract is now manual-only — triggers require explicit user request.
|
||||
Consider reimplementing as a standalone cron job or prose contract.
|
||||
DEPLOYED 2026-07-12 on CT 116 cron: 0 */6 * * *
|
||||
Auto-remediation code was removed from the pi Zulip extension (retired 2026-07-04),
|
||||
now reimplemented as `litellm-health-check.sh` on CT 116.
|
||||
Reports to /var/log/litellm/health-*.json and RA-H OS knowledge graph.
|
||||
GPU monitoring integrated from gpu-monitor on .24:9100.
|
||||
|
||||
Consolidated from litellm-health + litellm-self-heal on 2026-07-09 to eliminate
|
||||
duplication of architecture diagrams, GPU topology, timeout tables, and container
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: memory-audit-maintenance
|
||||
kind: responsibility
|
||||
description: Shared memory audit and maintenance contract for all Hermes agents (Mumuni, Tanko, Tdunna, Baggy). Each agent runs it against its own isolated memory files — no cross-agent access, no shared state. Detects staleness, enforces writer registry, and rotates canary tokens.
|
||||
description: Shared memory audit and maintenance contract for all Hermes agents (Mumuni, Tanko, Koby, Koonimo). Each agent runs it against its own isolated memory files — no cross-agent access, no shared state. Detects staleness, enforces writer registry, and rotates canary tokens.
|
||||
id: 067NC4KG01RG50R40M30E20918
|
||||
---
|
||||
|
||||
@@ -15,13 +15,13 @@ Autonomously audit and reorganize an agent's native memory (MEMORY.md, USER.md,
|
||||
|
||||
### Scope
|
||||
|
||||
This contract is the **Hermes Agent standard** for memory maintenance. It is shared across all Hermes agents (Mumuni, Tanko, Tdunna, Baggy). Each agent runs it against its own memory files only no cross-agent access, no shared state, no shared ledger, no shared canary. The contract is the standard; each agent enforces it independently with fully isolated data.
|
||||
This contract is the **Hermes Agent standard** for memory maintenance. It is shared across all Hermes agents (Mumuni, Tanko, Koby, Koonimo). Each agent runs it against its own memory files only no cross-agent access, no shared state, no shared ledger, no shared canary. The contract is the standard; each agent enforces it independently with fully isolated data.
|
||||
|
||||
**Agent Roster:**
|
||||
- Mumuni
|
||||
- Tanko
|
||||
- Tdunna
|
||||
- Baggy
|
||||
- Koby (CT 111 / tdunna)
|
||||
- Koonimo (CT 113 / baggy)
|
||||
|
||||
**Isolation Principle:** Each agent has its own:
|
||||
- `MEMORY.md` and `USER.md`
|
||||
|
||||
@@ -41,7 +41,7 @@ agent: abiba
|
||||
|
||||
- User: `monitoring@pve` (cluster-replicated)
|
||||
- Role: `PVEAuditor` on `/` (read-only, whole cluster)
|
||||
- Token: `monitoring@pve!prometheus` = `2c74ceb6-f905-444a-94f9-1c4f7889b68c`
|
||||
- Token: `monitoring@pve!prometheus` — stored in Infisical vault (`PROXMOX_MONITOR_TOKEN`)
|
||||
- `verify_ssl: false` (proxmoxer uses `verify_ssl`, NOT `verify_tls`)
|
||||
|
||||
## Grafana Dashboards (file-provisioned, folder "Syslog Fleet")
|
||||
@@ -62,7 +62,7 @@ agent: abiba
|
||||
|
||||
- **URL**: `http://192.168.68.116:3001/` (LAN, direct — Grafana bound to `0.0.0.0:3001`)
|
||||
- **Dashboards**: `http://192.168.68.116:3001/d/gpu-fleet`, `.../d/proxmox-cluster`, `.../d/proxmox-node`, `.../d/docker-containers`
|
||||
- **Credentials**: admin / syslog-grafana-2026
|
||||
- **Credentials**: admin / password stored in Infisical vault (`GRAFANA_ADMIN_PASSWORD`)
|
||||
- Grafana is NOT behind nginx — access port 3001 directly. The `harness-nginx` `/grafana/` sub-path route was tried and reverted (broke the existing `:3001` URL and gpu-fleet path). Do not re-add `GF_SERVER_SERVE_FROM_SUB_PATH` or an nginx `/grafana/` route.
|
||||
- grafana compose port mapping: `"3001:3000"` (0.0.0.0, not 127.0.0.1)
|
||||
|
||||
|
||||
@@ -19,13 +19,53 @@ from datetime import datetime
|
||||
|
||||
LITELLM = "http://192.168.68.116:80"
|
||||
|
||||
def _get_agent_key(agent_name):
|
||||
"""Retrieve agent key from Infisical vault."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["infisical", "secrets", "get", "LITELLM_API_KEY",
|
||||
"--project=agents", "--env=production", "--plain"],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback: try exporting all secrets
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["infisical", "export", "--project=agents", "--env=production",
|
||||
"--format=dotenv"],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
if result.returncode == 0:
|
||||
for line in result.stdout.splitlines():
|
||||
if line.startswith(f"LITELLM_API_KEY_{agent_name.upper()}") or \
|
||||
(line.startswith("LITELLM_API_KEY=") and agent_name == os.uname().nodename):
|
||||
return line.split("=", 1)[1].strip().strip('"').strip("'")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
# Agent keys are pulled from Infisical vault at runtime.
|
||||
# The 'key' field is populated dynamically below.
|
||||
AGENTS = {
|
||||
"tanko": {"ct": 112, "host": "192.168.68.122", "key": "sk-CggiHWlamQyShxWC3Hx6uw", "user": "jerome"},
|
||||
"mumuni": {"ct": 114, "host": "192.168.68.123", "key": "sk-VrqCNlwUgzoNGOpikJ7nwQ", "user": "root"},
|
||||
"tdunna": {"ct": 111, "host": None, "key": "sk-6sbCNjz2T6lTVDBdlNHXsA", "user": None},
|
||||
"baggy": {"ct": 113, "host": None, "key": "sk-krnw_zGBwvvL5b7l2t-s-A", "user": None},
|
||||
"tanko": {"ct": 112, "host": "192.168.68.122", "user": "jerome"},
|
||||
"mumuni": {"ct": 114, "host": "192.168.68.123", "user": "root"},
|
||||
"koby": {"ct": 111, "host": None, "user": None},
|
||||
"koonimo": {"ct": 113, "host": None, "user": None},
|
||||
}
|
||||
|
||||
# Inject keys from vault
|
||||
for agent_name in AGENTS:
|
||||
key = _get_agent_key(agent_name)
|
||||
if key:
|
||||
AGENTS[agent_name]["key"] = key
|
||||
else:
|
||||
AGENTS[agent_name]["key"] = None
|
||||
|
||||
GPU_HOSTS = {
|
||||
"gpu-rtx3090 (.8)": {"host": "192.168.68.8", "port": 8080, "service": "llama-server"},
|
||||
"gpu-rtx5070 (.110)": {"host": "192.168.68.110", "port": 8080, "service": "llama-server"},
|
||||
|
||||
Reference in New Issue
Block a user