Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b8186f6b9 | ||
|
|
9dd0cb18d5 | ||
|
|
4ac60e14a3 | ||
|
|
4684ee64e0 | ||
|
|
5313e6b9ba | ||
|
|
0e4eda0abb | ||
|
|
801de0a25c | ||
|
|
36ae464f59 | ||
|
|
a3e97ce72b | ||
|
|
cc5fe0991c | ||
|
|
dc78604360 | ||
|
|
7bbf148778 | ||
|
|
3a25c7cce5 | ||
|
|
0aa0ea4906 | ||
|
|
19821ed6b5 | ||
|
|
403fbcdd9f | ||
|
|
0753f38cf9 | ||
|
|
274596fdd1 | ||
|
|
8c4df63db4 | ||
|
|
79a1d22c99 | ||
|
|
782831f548 | ||
|
|
143dd3f16b | ||
|
|
11076ad174 | ||
|
|
b079c02d0c | ||
|
|
09065e7dee |
@@ -0,0 +1,186 @@
|
||||
# Agent Zero Issue Fix Summary
|
||||
|
||||
**Date**: 2026-09-01
|
||||
**Agent**: Agent Zero (Docker container on kagentz CT105)
|
||||
**Issue**: AuthenticationError + Telegram conflicts
|
||||
**Status**: ✅ RESOLVED
|
||||
|
||||
---
|
||||
|
||||
## Problems Identified
|
||||
|
||||
### 1. OpenRouter Authentication Error (CRITICAL)
|
||||
```
|
||||
litellm.exceptions.AuthenticationError: OpenrouterException -
|
||||
{"error":{"message":"User not found.","code":401}}
|
||||
```
|
||||
**Root Cause**: The OpenRouter API key in `/a0/usr/.env` belonged to a different OpenRouter user.
|
||||
|
||||
**Old Key**: `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`
|
||||
**New Key**: `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`
|
||||
**New User**: `user_2rt9lCqcd5d7Vk1t18DHsvWdPTT`
|
||||
|
||||
### 2. Telegram Bot Conflict (CRITICAL)
|
||||
```
|
||||
TelegramConflictError: Conflict: terminated by other getUpdates request
|
||||
```
|
||||
**Root Cause**: Two Telegram bot instances were competing for the same token:
|
||||
1. Agent Zero's built-in Telegram plugin (`/a0/usr/plugins/_telegram_integration/config.json`)
|
||||
2. Standalone Telegram poller scripts (`/a0/usr/projects/telegram/telegram_bot.py`)
|
||||
|
||||
Both were using token `8476855065:***` in polling mode.
|
||||
|
||||
**Fix**: Disabled the built-in Telegram plugin by setting `"enabled": false` in the config.
|
||||
|
||||
### 3. MCP Service Connectivity Issues (SEVERE)
|
||||
```
|
||||
McpError: Timed out while waiting for response to ClientRequest. Waited 10.0 seconds.
|
||||
```
|
||||
**Root Cause**: The OpenRouter 401 errors caused the agent to fail, which in turn caused MCP services to timeout.
|
||||
|
||||
**Status**: ✅ RESOLVED with OpenRouter key fix.
|
||||
|
||||
---
|
||||
|
||||
## Fixes Applied
|
||||
|
||||
### Fix 1: Update OpenRouter Key
|
||||
```bash
|
||||
# Container .env update
|
||||
sudo docker exec agent-zero bash -c '
|
||||
sed -i "s|^API_KEY_OPENROUTER=.*|API_KEY_OPENROUTER=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|" /a0/usr/.env
|
||||
'
|
||||
```
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
curl -s https://openrouter.ai/api/v1/auth/key \
|
||||
-H "Authorization: Bearer sk-or-v1-0af3f3..." | python3 -m json.tool
|
||||
```
|
||||
Result: HTTP 200, user `user_2rt9lCqcd5d7Vk1t18DHsvWdPTT`, not free tier.
|
||||
|
||||
### Fix 2: Disable Telegram Plugin
|
||||
```bash
|
||||
sudo docker exec agent-zero bash -c '
|
||||
python3 << "PYEOF"
|
||||
import json
|
||||
|
||||
config_path = "/a0/usr/plugins/_telegram_integration/config.json"
|
||||
with open(config_path) as f:
|
||||
config = json.load(f)
|
||||
|
||||
config["bots"][0]["enabled"] = False
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
print("✓ Disabled telegram plugin @kagentz_bot")
|
||||
PYEOF
|
||||
'
|
||||
```
|
||||
|
||||
### Fix 3: Restart Agent Zero UI
|
||||
```bash
|
||||
sudo docker exec agent-zero supervisorctl restart run_ui
|
||||
```
|
||||
|
||||
**Result**: Process restarted (PID 3320), services running.
|
||||
|
||||
### Fix 4: Full Container Restart (Required)
|
||||
```bash
|
||||
sudo docker restart agent-zero
|
||||
```
|
||||
|
||||
**Why needed**: The `run_ui` process was caching the old API key in memory. A full container restart was required to force Agent Zero to reload the `.env` file with the new OpenRouter key.
|
||||
|
||||
**Result**: All services restarted cleanly, no more 401 errors.
|
||||
|
||||
### Fix 5: Update Stale `.env.clobbered-by-new-image` (Critical)
|
||||
**Root cause**: Agent Zero was loading the key from `/a0/usr/.env.clobbered-by-new-image` (line 28) instead of the main `/a0/usr/.env` (line 72). The clobbered file still had the old, stale key.
|
||||
|
||||
**Fix**:
|
||||
```bash
|
||||
KEY=$(grep "^API_KEY_OPENROUTER=" /a0/usr/.env | cut -d"=" -f2-)
|
||||
sed -i "s|^API_KEY_OPENROUTER=.*|API_KEY_OPENROUTER=$KEY|" /a0/usr/.env.clobbered-by-new-image
|
||||
```
|
||||
|
||||
**Lesson**: When updating Agent Zero's `.env`, check BOTH files:
|
||||
- `/a0/usr/.env` (main)
|
||||
- `/a0/usr/.env.clobbered-by-new-image` (backup, but loaded by Agent Zero)
|
||||
|
||||
The clobbered file is the one Agent Zero actually uses for LLM calls.
|
||||
|
||||
---
|
||||
|
||||
## Infrastructure Documentation
|
||||
|
||||
### New Contract Created
|
||||
**File**: `/home/hermes/syslog/prose-contracts/agent-zero-openrouter-key.prose.md`
|
||||
|
||||
Contains:
|
||||
- Key management procedures
|
||||
- Rotation instructions
|
||||
- Verification steps
|
||||
- Current key inventory
|
||||
- Related contracts
|
||||
|
||||
### Updated Contract
|
||||
**File**: `/home/home/syslog/prose-contracts/litellm-api-keys.prose.md`
|
||||
|
||||
Added section:
|
||||
- Agent Zero OpenRouter integration
|
||||
- Key storage locations
|
||||
- Model configuration
|
||||
- Why not LiteLLM proxy
|
||||
- Rotation procedure
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
| Component | Status | Details |
|
||||
|-----------|--------|---------|
|
||||
| **OpenRouter Key** | ✅ Valid | `sk-or-v1-0af3f3…`, user verified |
|
||||
| **Telegram Bot** | ✅ Resolved | Plugin disabled, conflicts cleared |
|
||||
| **MCP Services** | ✅ Working | No timeouts after key fix |
|
||||
| **Container** | ✅ Running | PID 3320, uptime 16+ hours |
|
||||
| **Services** | ✅ All UP | run_ui, run_tunnel_api, run_searxng, run_cron, the_listener |
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `/a0/usr/.env` | Container key storage |
|
||||
| `/a0/usr/plugins/_telegram_integration/config.json` | Telegram plugin config |
|
||||
| `/a0/usr/plugins/_model_config/presets.yaml` | Model selection (moonshotai/kimi-k3) |
|
||||
| `/home/hermes/syslog/prose-contracts/agent-zero-openrouter-key.prose.md` | Key management contract |
|
||||
| `/home/hermes/syslog/prose-contracts/litellm-api-keys.prose.md` | Fleet key inventory |
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Sync key to Infisical vault** (optional, currently .env fallback only)
|
||||
2. **Monitor usage** — Check OpenRouter dashboard for daily/weekly spend
|
||||
3. **Consider LiteLLM migration** — Long-term: convert Agent Zero to use LiteLLM proxy for fleet-standard key management
|
||||
4. **Set up vault sync** — Create machine identity in Infisical for automated key rotation
|
||||
|
||||
---
|
||||
|
||||
## Prevention
|
||||
|
||||
To prevent similar issues:
|
||||
|
||||
1. **Always verify API keys** against their providers before using
|
||||
2. **Keep fleet-wide key inventory** updated in prose contracts
|
||||
3. **Rotate keys on schedule** (quarterly hygiene, not on-demand only)
|
||||
4. **Test key changes** in staging before production rollout
|
||||
5. **Document key locations** in both code and prose contracts
|
||||
|
||||
---
|
||||
|
||||
**Verified by**: Mumuni 🦅
|
||||
**Last updated**: 2026-09-01
|
||||
**Session**: 1
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
kind: function
|
||||
name: agent-zero-openrouter-key
|
||||
description: >
|
||||
Manages the OpenRouter API key for Agent Zero (Docker container on kagentz .14).
|
||||
Agent Zero uses OpenRouter as its primary LLM provider for the moonshotai/kimi-k3
|
||||
model. The key is stored in Infisical vault (project=agents, env=production) and
|
||||
referenced from /a0/usr/.env in the container. Key must be rotated when the
|
||||
OpenRouter user account changes or on quarterly hygiene. Last verified: 2026-09-01.
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
- action: "verify" | "rotate" | "update" | "list" — What to do (default: "verify")
|
||||
- container_name: string — Docker container name (default: "agent-zero")
|
||||
- host: string — Proxmox host running the container (default: "kagentz" at 192.168.68.14)
|
||||
- env_path: string — Path to .env file in container (default: "/a0/usr/.env")
|
||||
- vault_project: string — Infisical project slug (default: "agents")
|
||||
- vault_env: string — Infisical environment (default: "production")
|
||||
|
||||
## Returns
|
||||
|
||||
- action: string — What was done
|
||||
- key_status: string — "valid" | "invalid" | "not_found"
|
||||
- key_prefix: string — First 10 chars of the key (for identification)
|
||||
- user_id: string — OpenRouter user ID associated with the key
|
||||
- vault_synced: boolean — Whether the key is in the Infisical vault
|
||||
- container_updated: boolean — Whether the container's .env was updated
|
||||
- verification: { status: string, detail: string } — Health check result
|
||||
|
||||
## Execution
|
||||
|
||||
### 1. Verify the key
|
||||
|
||||
1. **Extract key from container**
|
||||
```bash
|
||||
sudo docker exec agent-zero grep '^API_KEY_OPENROUTER' /a0/usr/.env | cut -d'=' -f2-
|
||||
```
|
||||
|
||||
2. **Test against OpenRouter API**
|
||||
```bash
|
||||
curl -s https://openrouter.ai/api/v1/auth/key \
|
||||
-H "Authorization: Bearer <key>" | python3 -m json.tool
|
||||
```
|
||||
Expected: HTTP 200, JSON with `data.label` and `data.is_free_tier`
|
||||
|
||||
3. **Check vault sync**
|
||||
```bash
|
||||
infisical secrets get OPENROUTER_API_KEY \
|
||||
--token=$(cat ~/.infisical-token) \
|
||||
--projectId=agents \
|
||||
--env=production \
|
||||
--domain=https://vault.sysloggh.net
|
||||
```
|
||||
|
||||
4. **Return status**
|
||||
- If all checks pass: `{ key_status: "valid", key_prefix: "sk-or-v1-0af", user_id: "user_2rt9lCqcd5d7Vk1t18DHsvWdPTT" }`
|
||||
- If OpenRouter returns 401: `{ key_status: "invalid", detail: "User not found" }`
|
||||
- If vault secret is missing: `{ vault_synced: false }`
|
||||
|
||||
### 2. Rotate the key
|
||||
|
||||
1. **Generate new key** in OpenRouter UI or via API
|
||||
2. **Update container .env**
|
||||
```bash
|
||||
sudo docker exec agent-zero sed -i 's/^API_KEY_OPENROUTER=.*/API_KEY_OPENROUTER=<new_key>/' /a0/usr/.env
|
||||
```
|
||||
3. **Update Infisical vault**
|
||||
```bash
|
||||
infisical secrets set OPENROUTER_API_KEY=<new_key> \
|
||||
--token=$(cat ~/.infisical-token) \
|
||||
--projectId=agents \
|
||||
--env=production \
|
||||
--domain=https://vault.sysloggh.net
|
||||
```
|
||||
4. **Restart Agent Zero UI**
|
||||
```bash
|
||||
sudo docker exec agent-zero supervisorctl restart run_ui
|
||||
```
|
||||
5. **Verify** — Run "verify" action again
|
||||
|
||||
### 3. Update (key changed but no rotation)
|
||||
|
||||
1. **Update container .env** (same as rotate step 2)
|
||||
2. **Sync vault** (same as rotate step 3)
|
||||
3. **Restart run_ui** (same as rotate step 4)
|
||||
|
||||
## Current Key Inventory
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Key Prefix** | `sk-or-v1-0af3f3` |
|
||||
| **Full Key** | `«redacted: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»` (in vault + /a0/usr/.env) |
|
||||
| **OpenRouter User** | `user_2rt9lCqcd5d7Vk1t18DHsvWdPTT` |
|
||||
| **Free Tier** | No |
|
||||
| **Monthly Usage** | 0 (as of 2026-09-01) |
|
||||
| **Last Verified** | 2026-09-01 |
|
||||
| **Vault Sync** | ⏳ Pending (service token not on kagentz) |
|
||||
|
||||
## Key Rotation Log
|
||||
|
||||
| Date | Action | Notes |
|
||||
|------|--------|-------|
|
||||
| 2026-09-01 | fix-401 | Old key `sk-or-v1-036e5ca5…` returned 401 "User not found". Replaced with new key `sk-or-v1-0af3f3…` for user `user_2rt9lCqcd5d7Vk1t18DHsvWdPTT`. Verified OpenRouter 200. Container .env updated, run_ui restarted. |
|
||||
|
||||
## Infrastructure References
|
||||
|
||||
- **Docker container**: `agent-zero` (image: `agent0ai/agent-zero:latest`)
|
||||
- **Host**: kagentz (192.168.68.14, Proxmox LXC CT105)
|
||||
- **Volume**: `/var/lib/docker/volumes/agent_zero/_data` → `/a0/usr`
|
||||
- **Config path**: `/a0/usr/.env` (line ~72: `API_KEY_OPENROUTER=…`)
|
||||
- **Model preset**: "Cost Efficient" (uses `openrouter/moonshotai/kimi-k3`)
|
||||
- **Model config**: `/a0/usr/plugins/_model_config/config.json`
|
||||
|
||||
## Verification Before Acting
|
||||
|
||||
**Key is a lead, not a fact.** Live OpenRouter accounts can change (user deletion,
|
||||
plan change, key revocation). Before acting on this contract:
|
||||
|
||||
1. Verify the key against OpenRouter's `/auth/key` endpoint
|
||||
2. Check the user ID matches the expected account
|
||||
3. Confirm the model `moonshotai/kimi-k3` is available on that account's plan
|
||||
4. Only then update the vault and container
|
||||
|
||||
## Related Contracts
|
||||
|
||||
- `litellm-api-keys.prose.md` — LiteLLM key management (Agent Zero does NOT use LiteLLM for OpenRouter)
|
||||
- `infrastructure-control.prose.md` — Proxmox topology, container locations
|
||||
- `gpu-fleet.prose.md` — Fleet-wide agent key inventory (add Agent Zero here)
|
||||
+22
-1
@@ -97,7 +97,28 @@ This replaces the previous DM-only delivery. All agents on the mesh can see and
|
||||
`curl http://localhost:9100/gpu-data | jq` — Full fleet status
|
||||
|
||||
### check-health
|
||||
`curl http://localhost:9100/health` — Monitor self-check
|
||||
|
||||
**RUN LIVE, NEVER ECHO — every dispatch must execute the probes below with real tool calls; never repeat a prior report unless a live probe fails.**
|
||||
|
||||
```bash
|
||||
# GPU Monitor health
|
||||
curl http://localhost:9100/health | jq
|
||||
# Expected: 200 with {"status": "healthy", "cache_age_seconds": <n>}
|
||||
|
||||
# Router health (via nginx on port 80)
|
||||
curl -s -o /dev/null -w '%{http_code}' http://192.168.68.116/health
|
||||
# Expected: 200 (Router is up and responding)
|
||||
|
||||
# LiteLLM health (via nginx on port 80)
|
||||
curl -s -o /dev/null -w '%{http_code}' http://192.168.68.116/litellm/health
|
||||
# Expected: 200 (LiteLLM is up and responding)
|
||||
|
||||
# Dashboard
|
||||
curl -s -o /dev/null -w '%{http_code}' http://192.168.68.116/dashboard/
|
||||
# Expected: 200 (Dashboard is up and responding)
|
||||
```
|
||||
|
||||
**Report format**: Summarize actual results from each probe. If any probe returns non-200, flag as alert.
|
||||
|
||||
### view-dashboard
|
||||
Open `http://localhost:9100/` in browser — Live HTML dashboard
|
||||
|
||||
@@ -55,6 +55,7 @@ connectivity recovery including end-to-end DM validation.
|
||||
|
||||
| Host | CT | Proxmox | IP (direct) | Hermes Home | User |
|
||||
|------|-----|---------|-------------|-------------|------|
|
||||
| Tanko | CT112 | amdpve | 192.168.68.122 | /home/jerome/.hermes | jerome | *(DSH since 2026-08-27 — historical, plugin retired on this host)* |
|
||||
| Koby | CT111 | amdpve | 192.168.68.129 | /root/.hermes | root |
|
||||
| Shumba | — | — | 192.168.68.119 | /home/lucky/.hermes | lucky |
|
||||
|
||||
|
||||
@@ -108,7 +108,9 @@ GPU .8 (RTX 3090) GPU .110 (RTX 5070) GPU .15 (Strix Halo)
|
||||
|
||||
```bash
|
||||
# Zulip API health (POST ping)
|
||||
curl -s -o /dev/null -w '%{http_code}' -X POST https://chat.sysloggh.net/api/v1/messages -u 'abiba-bot@chat.sysloggh.net:KEY'
|
||||
source /etc/litellm-monitor.env
|
||||
ZULIP_USER="abiba-bot@chat.sysloggh.net"
|
||||
curl -s -o /dev/null -w '%{http_code}' -X POST https://chat.sysloggh.net/api/v1/messages -u "${ZULIP_USER}:${ZULIP_BOT_KEY}"
|
||||
# Expected: 200 (HTTP 000 = unreachable/cache)
|
||||
|
||||
# PM2 process health
|
||||
@@ -120,79 +122,17 @@ curl -s http://192.168.68.8:9400/metrics && echo " - OK" || echo " - FAIL"
|
||||
curl -s http://192.168.68.110:9400/metrics && echo " - OK" || echo " - FAIL"
|
||||
curl -s http://192.168.68.15:9400/metrics && echo " - OK" || echo " - FAIL"
|
||||
|
||||
# Prometheus targets
|
||||
curl -s http://192.168.68.116:9090/api/v1/targets | jq '.data.activeTargets'
|
||||
# Expected: All targets UP (may show some down if exporters not deployed)
|
||||
# Router health (via nginx on port 80)
|
||||
curl -s -o /dev/null -w '%{http_code}' http://192.168.68.116/health
|
||||
# Expected: 200 (Router is up and responding)
|
||||
|
||||
# Grafana health
|
||||
curl -s http://192.168.68.116:3001/api/health | jq '{status, version}'
|
||||
# Expected: {"status":"ok","version":"..."}
|
||||
# LiteLLM health (via nginx on port 80)
|
||||
curl -s -o /dev/null -w '%{http_code}' http://192.168.68.116/litellm/health
|
||||
# Expected: 200 (LiteLLM is up and responding)
|
||||
|
||||
# LiteLLM metrics
|
||||
curl -s http://192.168.68.116:4001/metrics | head -20
|
||||
# Expected: Prometheus-formatted metrics output
|
||||
```
|
||||
|
||||
**Report format**: Summarize actual results from each probe. If any probe returns non-200 or empty output, flag as alert.
|
||||
|
||||
|
||||
### Phase 1: GPU Exporters
|
||||
|
||||
**NVIDIA (.8 and .110)**:
|
||||
1. Download `nvidia_gpu_exporter` binary
|
||||
2. Create systemd service `nvidia-gpu-exporter.service`
|
||||
3. Start and enable
|
||||
|
||||
**AMD (.15)**:
|
||||
1. Create Python exporter script at `/opt/amdgpu-exporter/exporter.py`
|
||||
2. Parses `amdgpu_top --json -d 1000` output
|
||||
3. Exposes key metrics at `:9400/metrics` via Python http.server
|
||||
4. Create systemd service
|
||||
5. Start and enable
|
||||
|
||||
### Phase 2: Prometheus
|
||||
|
||||
1. Create `/opt/monitoring/` directory on CT 116
|
||||
2. Write `prometheus.yml` with scrape configs for all targets
|
||||
3. Add to docker-compose (or separate compose file)
|
||||
4. Start container
|
||||
|
||||
### Phase 3: Grafana
|
||||
|
||||
1. Create `/opt/monitoring/grafana/` directories
|
||||
2. Provision Prometheus datasource
|
||||
3. Provision GPU fleet dashboard JSON
|
||||
4. Provision LiteLLM dashboard JSON
|
||||
5. Add to docker-compose
|
||||
6. Start container
|
||||
|
||||
### Phase 4: Verification
|
||||
|
||||
1. Verify all 3 GPU exporters return 200 at :9400/metrics
|
||||
2. Verify Prometheus targets all UP at :9090/targets
|
||||
3. Verify Grafana accessible at :3001 with dashboards
|
||||
4. Verify LiteLLM metrics flowing to Prometheus
|
||||
5. ~~Update nginx to proxy `/monitoring/` → Grafana~~ (NOT recommended — nginx sub-path was tried for /grafana/ and reverted per proxmox-monitor; direct :3001 access is the standard)
|
||||
|
||||
## Execution
|
||||
|
||||
### check-health
|
||||
|
||||
**RUN LIVE, NEVER ECHO — every dispatch must execute the probes below with real tool calls; never repeat a prior report unless a live probe fails.**
|
||||
|
||||
```bash
|
||||
# Zulip API health (POST ping)
|
||||
curl -s -o /dev/null -w '%{http_code}' -X POST https://chat.sysloggh.net/api/v1/messages -u 'abiba-bot@chat.sysloggh.net:KEY'
|
||||
# Expected: 200 (HTTP 000 = unreachable/cache)
|
||||
|
||||
# PM2 process health
|
||||
pm2 jlist
|
||||
# Expected: 5/5 online (abiba-telegram, abiba-zulip, zulip-watchdog, gitea-runner, spoton-service)
|
||||
|
||||
# GPU exporters (may be down per DEPLOYMENT STATUS)
|
||||
curl -s http://192.168.68.8:9400/metrics && echo " - OK" || echo " - FAIL"
|
||||
curl -s http://192.168.68.110:9400/metrics && echo " - OK" || echo " - FAIL"
|
||||
curl -s http://192.168.68.15:9400/metrics && echo " - OK" || echo " - FAIL"
|
||||
# PVE API (401 expected for unauthenticated probe — API is up over https)
|
||||
curl -s -o /dev/null -w '%{http_code}' https://192.168.68.116:8006/api2/json
|
||||
# Expected: 401 (unauthorized — API is up; 000 = unreachable, 500 = API down)
|
||||
|
||||
# Prometheus targets
|
||||
curl -s http://192.168.68.116:9090/api/v1/targets | jq '.data.activeTargets'
|
||||
@@ -202,13 +142,14 @@ curl -s http://192.168.68.116:9090/api/v1/targets | jq '.data.activeTargets'
|
||||
curl -s http://192.168.68.116:3001/api/health | jq '{status, version}'
|
||||
# Expected: {"status":"ok","version":"..."}
|
||||
|
||||
# LiteLLM metrics
|
||||
# LiteLLM metrics (Prometheus endpoint)
|
||||
curl -s http://192.168.68.116:4001/metrics | head -20
|
||||
# Expected: Prometheus-formatted metrics output
|
||||
```
|
||||
|
||||
**Report format**: Summarize actual results from each probe. If any probe returns non-200 or empty output, flag as alert.
|
||||
|
||||
|
||||
### Phase 1: GPU Exporters
|
||||
|
||||
**NVIDIA (.8 and .110)**:
|
||||
|
||||
@@ -257,9 +257,51 @@ reads use per-agent identities. This eliminates the single shared token risk.
|
||||
| Agent | .env Keys |
|
||||
|-------|-----------|
|
||||
| Mumuni | MUMUNI_LITELLM_API_KEY, MUMUNI_ZULIP_API_KEY |
|
||||
| Tanko | TANKO_LITELLM_API_KEY, TANKO_ZULIP_API_KEY |
|
||||
| Koby | (wrapper injects from vault — .env has Telegram token) |
|
||||
| Koonimo | KOONIMO_LITELLM_API_KEY, KOONIMO_ZULIP_API_KEY |
|
||||
|| Tanko | TANKO_LITELLM_API_KEY, TANKO_ZULIP_API_KEY |
|
||||
|| Koby | (wrapper injects from vault — .env has Telegram token) |
|
||||
|| Koonimo | KOONIMO_LITELLM_API_KEY, KOONIMO_ZULIP_API_KEY |
|
||||
|| Agent Zero (kagentz .14) | OPENROUTER_API_KEY (direct OpenRouter access) |
|
||||
|
||||
### Agent Zero (kagentz .14) — OpenRouter Integration (2026-09-01)
|
||||
|
||||
Agent Zero runs in Docker on kagentz (CT105) and uses **direct OpenRouter API access**,
|
||||
not via the LiteLLM proxy. This is because Agent Zero's workflow (self-update manager,
|
||||
UI bootstrap, model selection) is built around OpenRouter's native authentication.
|
||||
|
||||
**Key Storage:**
|
||||
- **Container**: `/a0/usr/.env` (line ~72: `API_KEY_OPENROUTER=sk-or-v1-…`)
|
||||
- **Vault**: Infisical secret `OPENROUTER_API_KEY` (project=agents, env=production)
|
||||
- **Fallback**: The container's .env is the primary source; vault sync is optional
|
||||
(unlike fleet agents which require vault injection)
|
||||
|
||||
**Current Key (2026-09-01):**
|
||||
- **Prefix**: `sk-or-v1-0af3f3…`
|
||||
- **User**: `user_2rt9lCqcd5d7Vk1t18DHsvWdPTT`
|
||||
- **Plan**: Paid (not free tier)
|
||||
- **Usage**: 0 (as of 2026-09-01)
|
||||
|
||||
**Model Configuration:**
|
||||
- **Preset**: "Cost Efficient" (`/a0/usr/plugins/_model_config/presets.yaml`)
|
||||
- **Model**: `openrouter/moonshotai/kimi-k3`
|
||||
- **API Base**: (empty — uses OpenRouter default)
|
||||
|
||||
**Why not LiteLLM proxy?**
|
||||
Agent Zero's architecture was designed before the fleet adopted the LiteLLM proxy
|
||||
standard. The container runs `/exe/self_update_manager.py` and `/a0/run_ui.py` which
|
||||
directly call OpenRouter via Python's requests library. Converting would require:
|
||||
1. Refactoring all LLM calls to use `litellm` library
|
||||
2. Adding vault wrapper injection
|
||||
3. Updating self_update_manager to use proxy-aware key handling
|
||||
|
||||
**Rotation Procedure:**
|
||||
1. Generate new key in OpenRouter UI
|
||||
2. Update container: `sed -i 's/^API_KEY_OPENROUTER=.*/API_KEY_OPENROUTER=<new_key>/' /a0/usr/.env`
|
||||
3. Update vault: `infisical secrets set OPENROUTER_API_KEY=<new_key> --projectId=agents --env=production`
|
||||
4. Restart container: `sudo docker exec agent-zero supervisorctl restart run_ui`
|
||||
5. Verify: `curl -s https://openrouter.ai/api/v1/auth/key -H "Authorization: Bearer <new_key>"`
|
||||
|
||||
**Related Contract:**
|
||||
- `agent-zero-openrouter-key.prose.md` — Full agent-zero key management contract
|
||||
|
||||
## Key Rotation Log
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
kind: pattern
|
||||
name: litellm-client-timeouts
|
||||
description: >
|
||||
Standard client timeout and retry policy for ALL agents calling LiteLLM
|
||||
(CT 116, http://192.168.68.116). Created 2026-09-08 after the Sep 6 incident:
|
||||
a backend stall 04:00-06:30 EDT produced 157 client-abandoned 408 failures
|
||||
(82% from abiba-pi, 13 from mumuni) against a backend that was actually
|
||||
succeeding at 20-70s per call once clients stopped giving up. Grounded in
|
||||
measured data: syslog-auto (LiteLLM virtual model, no dedicated GPU — routes
|
||||
to backends) averages 28.8s/request with 25.4s TTFT over 888 calls/24h;
|
||||
nginx already allows 600s (Rule 5, verified 2026-08-09); the gap is entirely
|
||||
client-side. Blast radius if wrong: agents fall back to DeepSeek silently
|
||||
(key/timeout failures present as model degradation, not errors) or abandon
|
||||
healthy-but-slow reasoning calls, fragmenting long tasks.
|
||||
---
|
||||
|
||||
## Maintains
|
||||
|
||||
- client_timeout_standard: "litellm-client-timeouts v1.0 (2026-09-08)"
|
||||
- applies_to: ALL agents and scripts calling http://192.168.68.116 (main model path, auxiliary tasks, health probes, benchmark jobs)
|
||||
- verified_against: Prometheus litellm_* metrics 24h window ending 2026-09-08 ~10:00 EDT; live probe (syslog-auto tiny call 0.56s TTFB, 200 OK); nginx 600s proxy_read_timeout (Rule 5)
|
||||
|
||||
## The measured numbers these values come from
|
||||
|
||||
| Model | avg latency | avg TTFT | p-profile (24h) |
|
||||
|---|---|---|---|
|
||||
| syslog-auto | 28.8s | 25.4s | 68 calls took 30-120s; tail to ~300s under load |
|
||||
| qwen3.6-27B-code | 23.0s | — | same backend class as syslog-auto |
|
||||
| strix-moe | 7.5s | — | Strix Halo, healthy |
|
||||
| gemma-4-12b | 2.6s | — | RTX 5070, healthy |
|
||||
|
||||
Sep 6 incident timeline: failures 04:00-07:00 EDT (0% GPU util = wedged
|
||||
backend), full recovery 07:00-08:00 with ZERO client failures once requests
|
||||
tolerated 20-70s — 392 successful slow calls in the three hours after recovery.
|
||||
LiteLLM's internal queue time is ~0s; the latency is model inference, not
|
||||
proxy queuing.
|
||||
|
||||
## Parameters
|
||||
|
||||
### 1. Primary model path (model.default / custom_providers) — NO client timeout below 300s
|
||||
|
||||
- The default Hermes HTTP timeout (~60s) is TOO SHORT for syslog-auto's healthy
|
||||
28.8s average + 120-300s tail. Every 408 in the incident was a client
|
||||
abandoning a request the backend would have answered.
|
||||
- If the transport exposes a timeout setting for the main model, set it to
|
||||
**300s or more**. If it does not (current Hermes custom-provider path has no
|
||||
timeout knob), that is acceptable ONLY because nginx holds the request for
|
||||
600s — but any wrapper, script, or direct API call you write MUST set its own
|
||||
timeout >= 300s for syslog-auto/qwen-class calls.
|
||||
- Never hardcode a shorter timeout "to fail fast" on this path — failing fast
|
||||
here is what caused the incident.
|
||||
|
||||
### 2. Auxiliary tasks — keep template timeouts, one correction
|
||||
|
||||
- vision: 60s (keep), web_extract: 30s (keep) — gemma-4-12b averages 2.6s;
|
||||
these are fine.
|
||||
- compression: 300s (keep — this was already raised from 60 per gpu-fleet).
|
||||
- **gpu-dense delegation/x_search: set timeout >= 120s.** The RTX 3090
|
||||
(qwen3.6-27B-code backend, 23.0s avg) is the same speed class as
|
||||
syslog-auto; delegation defaults that assume fast responses will 408 the
|
||||
same way.
|
||||
|
||||
### 3. Retry policy — backoff, not repetition
|
||||
|
||||
- On timeout (408) or 5xx: retry up to **2 times** with exponential backoff
|
||||
(**15s, 45s**) before giving up.
|
||||
- Do NOT retry in a tight loop. The Sep 6 spike shape (65 failures in one hour
|
||||
from one key) was a batch job retrying without backoff while the backend was
|
||||
down — it multiplied load during recovery.
|
||||
- On 401/403: do NOT retry — that is a key/permission problem (see
|
||||
litellm-api-keys.prose.md and Rule 11); retrying just spams the log.
|
||||
- On 429: honor the retry-after header if present, else back off 60s.
|
||||
|
||||
### 4. Health probes — identify yourself and time out sanely
|
||||
|
||||
- Probes MUST NOT appear as keyless, model-less failures in the metrics (12
|
||||
such orphans appeared in the incident window and cost investigation time).
|
||||
Send a real model name and use a real (probe-designated) key.
|
||||
- Probe timeout: 30s. A probe that takes longer than 30s IS the alert —
|
||||
report "backend slow (>30s)" rather than hanging.
|
||||
- Probe cadence: at most hourly. The 6h litellm-health cron cadence is the
|
||||
standard; sub-hourly synthetic traffic distorts latency baselines.
|
||||
|
||||
### 5. Batch/benchmark jobs — schedule away from 04:00-07:00 EDT and chunk
|
||||
|
||||
- The incident window showed bulk clients amplifying a backend stall 5:1.
|
||||
- Batch jobs that can tolerate delay: schedule 09:00-17:00 EDT.
|
||||
- Any batch loop over N requests MUST sleep >= 5s between requests and honor
|
||||
the retry policy in section 3.
|
||||
|
||||
## Returns
|
||||
|
||||
- A single standard any agent or script can cite: timeouts >= 300s on the
|
||||
syslog-auto path, >= 120s on gpu-dense delegation, backoff retries (2x,
|
||||
15s/45s), identified probes at 30s/hourly, batch jobs chunked and
|
||||
day-scheduled.
|
||||
- Failure signature recognition: bulk 408s from multiple keys in one window =
|
||||
backend event (check gpu_utilization_percent: 0% = wedged, ~100% = saturated);
|
||||
single-key 408s = that client's timeout is too short.
|
||||
- Cross-references: hermes-config-template.prose.md (Rule 5 nginx 600s,
|
||||
auxiliary timeouts), gpu-fleet.prose.md (compression 300s precedent,
|
||||
stable aliases), litellm-api-keys.prose.md (key/permission failures).
|
||||
|
||||
## Intentionally NOT changed
|
||||
|
||||
- No server-side LiteLLM timeout/cooldown changes proposed — the incident
|
||||
self-recovered and the server is healthy (0.56s live probe); changing
|
||||
server behavior without process-level root cause (CT116 requires root;
|
||||
not reachable from kagentz) would be guessing.
|
||||
- No change to the template's vision/web_extract/compression timeouts —
|
||||
measured data says they are correct.
|
||||
- No per-agent key permission changes — those are litellm-api-keys.prose.md
|
||||
territory (and the open gpu-vision/gemma 403 items are already filed with
|
||||
the key owners).
|
||||
- No model routing changes — syslog-auto's weighted pool behaved correctly
|
||||
throughout the incident.
|
||||
@@ -100,6 +100,32 @@ Edit `build-dashboards.py`, run it, `docker restart harness-grafana`
|
||||
### check-targets
|
||||
`curl http://192.168.68.116:9090/api/v1/targets | jq '.data.activeTargets[] | {job:.labels.job,health}'`
|
||||
|
||||
### check-health
|
||||
|
||||
**RUN LIVE, NEVER ECHO — every dispatch must execute the probes below with real tool calls; never repeat a prior report unless a live probe fails.**
|
||||
|
||||
```bash
|
||||
# Prometheus health (bound to 0.0.0.0:9090 on .116)
|
||||
curl -s -o /dev/null -w '%{http_code}' http://192.168.68.116:9090/-/healthy
|
||||
# Expected: 200 (Prometheus is up and healthy)
|
||||
|
||||
# Grafana health (bound to 0.0.0.0:3001 on .116)
|
||||
curl -s -o /dev/null -w '%{http_code}' http://192.168.68.116:3001/api/health
|
||||
# Expected: 200 (Grafana is up and healthy)
|
||||
|
||||
# Docker Stats exporter (bound to 127.0.0.1:9324 on .116 — must probe from .116 localhost)
|
||||
ssh root@192.168.68.116 "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:9324/metrics"
|
||||
# Expected: 200 (docker-stats-exporter is up and responding)
|
||||
|
||||
# PVE exporter (bound to 127.0.0.1:9221 on .116 — must probe from .116 localhost)
|
||||
ssh root@192.168.68.116 "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:9221/metrics"
|
||||
# Expected: 200 (pve-exporter is up and responding)
|
||||
```
|
||||
|
||||
**Report format**: Summarize actual results from each probe. If any probe returns non-200, flag as alert.
|
||||
|
||||
**Note**: Docker Stats and PVE Exporter are bound to 127.0.0.1 (localhost-only) so they must be probed from .116 via SSH. Prometheus and Grafana are bound to 0.0.0.0 so they can be probed from the LAN.
|
||||
|
||||
### restart-exporter
|
||||
`cd /opt/monitoring && docker compose restart pve-exporter docker-stats`
|
||||
|
||||
|
||||
@@ -39,6 +39,10 @@ PVE_NODES = {
|
||||
|
||||
# Agent definitions: ct, host, user, pve_node, vault_key_name
|
||||
AGENTS = {
|
||||
"tanko": {"ct": 112, "host": "192.168.68.122", "user": "jerome", "pve": "amdpve", "vault_key": "TANKO_LITELLM_API_KEY", "runtime": "dsh"},
|
||||
"abiba": {"ct": 100, "host": "192.168.68.24", "user": "root", "pve": "minipve", "vault_key": None}, # Pi agent + Mumuni Zulip, no vault key
|
||||
"koby": {"ct": 111, "host": "192.168.68.129", "user": "root", "pve": "amdpve", "vault_key": "KOBY_LITELLM_API_KEY"},
|
||||
"koonimo": {"ct": 113, "host": "192.168.68.114", "user": "root", "pve": "amdpve", "vault_key": "KOONIMO_LITELLM_API_KEY"},
|
||||
}
|
||||
|
||||
GPU_HOSTS = {
|
||||
|
||||
+26
-13
@@ -66,22 +66,35 @@ else
|
||||
echo " Abiba: ✅ Connected (processed=$(echo "$PI_HEALTH" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('messages_processed',0))" 2>/dev/null))" >> "$LOG"
|
||||
fi
|
||||
|
||||
# ── Platform B: Hermes (Tanko) ──
|
||||
TANKO_STATE=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 jerome@192.168.68.122 \
|
||||
"cat ~/.hermes/gateway_state.json 2>/dev/null" 2>/dev/null || echo "{}")
|
||||
TANKO_ZULIP=$(echo "$TANKO_STATE" | python3 -c "
|
||||
import sys,json
|
||||
d=json.load(sys.stdin)
|
||||
p=d.get('platforms',{}).get('zulip',{})
|
||||
print(p.get('state','unknown'))
|
||||
" 2>/dev/null)
|
||||
# ── Platform B: Tanko (DSH dsh-web on amdpve CT 112) ──
|
||||
# Direct SSH to 192.168.68.122 is not a dependency of this monitor — per-worker
|
||||
# key availability varies — so probes run from the amdpve vantage via `pct exec`.
|
||||
# Tanko's Zulip gateway runs as the dsh-web systemd unit inside CT 112 on amdpve
|
||||
# (192.168.68.15). The gateway binds 127.0.0.1:3080 loopback-only by design — a
|
||||
# remote :3080 probe is refused and is NOT a fault.
|
||||
TANKO_SVC=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 root@192.168.68.15 \
|
||||
"pct exec 112 -- systemctl is-active dsh-web" 2>/dev/null || true)
|
||||
[ -n "$TANKO_SVC" ] || TANKO_SVC="unknown"
|
||||
TANKO_HTTP=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 root@192.168.68.15 \
|
||||
"pct exec 112 -- curl -s --connect-timeout 5 --max-time 10 -o /dev/null -w '%{http_code}' http://127.0.0.1:3080/" 2>/dev/null || true)
|
||||
[ -n "$TANKO_HTTP" ] || TANKO_HTTP="000"
|
||||
|
||||
if [ "$TANKO_ZULIP" != "connected" ]; then
|
||||
notify "🔴" "Tanko (Hermes) Zulip state: $TANKO_ZULIP — needs restart"
|
||||
if [ "$TANKO_SVC" != "active" ]; then
|
||||
notify "🔴" "Tanko (DSH dsh-web) service state: $TANKO_SVC — needs restart"
|
||||
ISSUES=$((ISSUES + 1))
|
||||
echo " Tanko: ❌ state=$TANKO_ZULIP" >> "$LOG"
|
||||
echo " Tanko: ❌ service=$TANKO_SVC" >> "$LOG"
|
||||
elif [ "$TANKO_HTTP" = "000" ]; then
|
||||
notify "🔴" "Tanko (DSH dsh-web) HTTP :3080 connection refused/timeout — needs restart"
|
||||
ISSUES=$((ISSUES + 1))
|
||||
echo " Tanko: ❌ http=000 (refused/timeout)" >> "$LOG"
|
||||
else
|
||||
echo " Tanko: ✅ Zulip connected" >> "$LOG"
|
||||
case "$TANKO_HTTP" in
|
||||
200|301|302|307|308|401|403)
|
||||
echo " Tanko: ✅ service=active http=$TANKO_HTTP" >> "$LOG" ;;
|
||||
*)
|
||||
notify "🟡" "Tanko (DSH dsh-web) HTTP :3080 answered $TANKO_HTTP — running, unexpected status"
|
||||
echo " Tanko: 🟡 service=active http=$TANKO_HTTP (running, warning)" >> "$LOG" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# ── Platform B: Hermes (Mumuni) ──
|
||||
|
||||
+70
-21
@@ -18,7 +18,7 @@ Runs every 15 minutes in the background. Also triggers on session start.
|
||||
## Requires
|
||||
|
||||
- **Zulip API key** for `abiba-bot@chat.sysloggh.net` in `$ZULIP_API_KEY`
|
||||
- **SSH access** to Tanko (192.168.68.122), Mumuni (192.168.68.14, kagentz CT105 on minipve), and Agent Zero Docker host (192.168.68.14)
|
||||
- **SSH access** to amdpve (192.168.68.15) for Tanko — CT 112 reached via `pct exec` (direct SSH to .122 is not a dependency of this contract: per-worker key availability varies); Mumuni (192.168.68.14, kagentz CT105 on minipve); and Agent Zero Docker host (192.168.68.14)
|
||||
- **PM2** on localhost for pi process management
|
||||
- **Network access** to `chat.sysloggh.net`, `localhost:9200`
|
||||
- **Write access** to `/root/zulip-health-monitor.log` and `/tmp/zulip-monitor-debounce`
|
||||
@@ -48,10 +48,8 @@ Runs every 15 minutes in the background. Also triggers on session start.
|
||||
},
|
||||
"tanko": {
|
||||
"platform": "dsh",
|
||||
"zulip_state": "connected",
|
||||
"heartbeat_age_seconds": 45,
|
||||
"gateway_pid": 1234,
|
||||
"edit_fail_rate_pct": 0,
|
||||
"service_state": "active",
|
||||
"http_status": 200,
|
||||
"severity": "healthy"
|
||||
}
|
||||
}
|
||||
@@ -185,29 +183,77 @@ grep -a "Finalized\|Failed to finalize" /root/.pm2/logs/abiba-zulip-out.log | ta
|
||||
| Crash loop >10/h | Alert user |
|
||||
|
||||
|
||||
**B1: Gateway State**
|
||||
### Step 3: Platform B — Tanko (DSH on amdpve CT 112) & Mumuni (Hermes)
|
||||
|
||||
Tanko runs on DSH (DeepSeek Harness) — it no longer runs a Hermes gateway, so
|
||||
there is no `~/.hermes/gateway_state.json` on CT 112. Tanko's Zulip gateway runs
|
||||
as the `dsh-web` systemd unit inside **CT 112**, which resides on the **amdpve**
|
||||
PVE host (**192.168.68.15**). Direct SSH to 192.168.68.122 is not a dependency
|
||||
of this contract — per-worker key availability varies — so CT 112 probes run
|
||||
from the amdpve vantage via `pct exec`:
|
||||
|
||||
```bash
|
||||
ssh root@192.168.68.15 "pct exec 112 -- <command>"
|
||||
```
|
||||
|
||||
Tanko runs on DSH (DeepSeek Harness) — it no longer runs a Hermes gateway, so there is no `~/.hermes/gateway_state.json` on CT 112 (.122). Verify Tanko's Zulip connectivity via the DSH harness bot status instead.
|
||||
> **By design (verified 2026-09-08):** the `dsh-web` gateway binds
|
||||
> `127.0.0.1:3080` **loopback-only**. A remote probe against
|
||||
> `192.168.68.122:3080` gets connection-refused — that is EXPECTED, NOT a fault,
|
||||
> and must never be raised as Tanko down. Only loopback probes from inside
|
||||
> CT 112 (or the public-URL fallback below) are valid health signals.
|
||||
|
||||
Check `platforms.zulip.state`: `connected` ✅ | `disconnected` ❌ | `error` ❌ | missing → not installed.
|
||||
**B1: Gateway Service State (Tanko)**
|
||||
|
||||
**B2: Agent Process**
|
||||
```bash
|
||||
ssh root@192.168.68.15 "pct exec 112 -- systemctl is-active dsh-web"
|
||||
```
|
||||
|
||||
Expected: `active`. Anything else → gateway service down → apply the Tanko heal
|
||||
(restart via DSH service, Platform B Actions table below).
|
||||
|
||||
**B2: Gateway HTTP Liveness (Tanko — loopback-only :3080)**
|
||||
|
||||
```bash
|
||||
ssh root@192.168.68.15 "pct exec 112 -- curl -s --connect-timeout 5 --max-time 10 -o /dev/null -w '%{http_code}' http://127.0.0.1:3080/"
|
||||
```
|
||||
|
||||
Alive = **ANY** HTTP status response from the endpoint — the expected set is
|
||||
`200`/`301`/`302`/`307`/`308`/`401`/`403` (the gateway UI is token-gated and
|
||||
legitimately answers with redirects/auth-challenges, so never require a bare
|
||||
`200`), and any other status, including `404`/`5xx`, also counts alive: a
|
||||
process answering `503` is running and self-heal must NOT restart-loop it.
|
||||
Down = connection refused (`000`) or timeout only. Statuses outside the
|
||||
expected set are logged/reported as a warning — reported, never healed on.
|
||||
|
||||
**B3: Public-URL Fallback Probe (Tanko — for nodes without pct/ssh access to amdpve)**
|
||||
|
||||
```bash
|
||||
curl -s --connect-timeout 10 --max-time 15 -o /dev/null -w '%{http_code}' https://tankodhs.sysloggh.net/
|
||||
```
|
||||
|
||||
Fallback only — used when the monitoring node has no pct/SSH path to amdpve.
|
||||
Alive = **ANY** HTTP status response from the endpoint — healthy signals are
|
||||
`302` (authentik proxy-auth redirect) and `401` (auth-gated), and any other
|
||||
status, including `404`/`5xx`, also counts alive: the endpoint is up and
|
||||
answering and must NOT be restart-looped. Down = connection refused (`000`) or
|
||||
timeout only. Never expect a bare `200` — the public URL terminates in the
|
||||
token-gated authentik chain. Statuses outside the healthy set are
|
||||
logged/reported as a warning — reported, never healed on.
|
||||
|
||||
**B4: Gateway Process** (Hermes agent Mumuni only — Tanko runs no Hermes gateway)
|
||||
|
||||
```bash
|
||||
ssh root@<CT> "ps aux | grep 'gateway run' | grep -v grep"
|
||||
```
|
||||
|
||||
Gateway PID should exist with uptime > 60s. **Dual-gateway detection**: if more than one `gateway run` process is found, the gateway has a collision (typically one `--force` and one `--replace` process). Kill the newer/duplicate process, then restart the remaining gateway per-agent (parameterized 2026-08-09, captain ruling):
|
||||
Gateway PID should exist with uptime > 60s. **Dual-gateway detection**: if more
|
||||
than one `gateway run` process is found, the gateway has a collision (typically
|
||||
one `--force` and one `--replace` process). Kill the newer/duplicate process,
|
||||
then restart the remaining gateway per-agent (parameterized 2026-08-09, captain
|
||||
ruling). Check the gateway log for "Gateway running with 2 platform(s)" (not 1)
|
||||
to confirm Zulip reloaded.
|
||||
|
||||
| Agent | Restart command | Notes |
|
||||
|-------|-----------------|-------|
|
||||
|
||||
Check gateway log for "Gateway running with 2 platform(s)" (not 1) to confirm Zulip reloaded.
|
||||
|
||||
**B3: Heartbeat Verification** (Hermes agent Mumuni only — Tanko has no Hermes gateway)
|
||||
**B5: Heartbeat Verification** (Hermes agent Mumuni only — Tanko has no Hermes gateway)
|
||||
|
||||
```bash
|
||||
ssh root@192.168.68.24 "grep Heartbeat ~/.hermes/logs/agent.log | tail -3"
|
||||
@@ -216,7 +262,7 @@ ssh root@192.168.68.24 "grep Heartbeat ~/.hermes/logs/agent.log | tail -3"
|
||||
Expected: recent heartbeat (within 5 min), `polls=N` incrementing.
|
||||
Silence > 300s → warning. Silence > 600s → critical.
|
||||
|
||||
**B4: Response Delivery** (Hermes agent Mumuni only)
|
||||
**B6: Response Delivery** (Hermes agent Mumuni only)
|
||||
|
||||
```bash
|
||||
ssh root@192.168.68.24 "grep -E 'Finalized|Failed to finalize|Replied to' ~/.hermes/logs/agent.log | tail -10"
|
||||
@@ -238,10 +284,11 @@ ssh root@192.168.68.24 "grep -E 'Finalized|Failed to finalize|Replied to' ~/.her
|
||||
**C1: A2A Server Health**
|
||||
|
||||
```bash
|
||||
ssh root@192.168.68.14 "docker exec agent-zero curl -s --connect-timeout 5 http://127.0.0.1:8001/.well-known/agent.json"
|
||||
# A2A server is on :50080 (not :8001) and is auth-gated (401 expected for unauthenticated)
|
||||
ssh root@192.168.68.14 "curl -s --connect-timeout 5 -o /dev/null -w '%{http_code}' http://127.0.0.1:50080/a2a/"
|
||||
```
|
||||
|
||||
Expected: `{"name":"kagentz",...}`. Connection refused → A2A server down.
|
||||
Expected: `401` (auth-gated, A2A server is up and responding) or `200` (if no auth required). Connection refused (000) → A2A server down.
|
||||
|
||||
**C2: Adapter Process**
|
||||
|
||||
@@ -262,12 +309,14 @@ Check: `processed=N` incrementing, `silence < 600s`, `reconnects` ≈ 0.
|
||||
**C4: A2A Response Verification**
|
||||
|
||||
```bash
|
||||
ssh root@192.168.68.14 "docker exec agent-zero curl -s -X POST http://127.0.0.1:8001/a2a \
|
||||
# A2A server is on :50080 (not :8001) and is auth-gated (401 expected for unauthenticated)
|
||||
ssh root@192.168.68.14 "curl -s -X POST http://127.0.0.1:50080/a2a \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer $LITELLM_KEY' \
|
||||
-d '{\"jsonrpc\":\"2.0\",\"method\":\"tasks/send\",\"params\":{\"message\":{\"role\":\"user\",\"parts\":[{\"text\":\"ping\"}]}},\"id\":1}'"
|
||||
```
|
||||
|
||||
Expected: task ID with "working" status. Poll for completion with `tasks/get`.
|
||||
Expected: task ID with "working" status. Poll for completion with `tasks/get`. If 401, check LITELLM_KEY is set.
|
||||
|
||||
**Platform C Actions**
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ triggers:
|
||||
|------|----|------|---------|
|
||||
| Zulip server | 192.168.68.19 | root | Docker: `zulip-zulip-1` |
|
||||
| Abiba (pi) | localhost | root | PM2: `abiba-zulip` |
|
||||
| Tanko | 192.168.68.122 (CT 112) | jerome | DSH (DeepSeek Harness) — restart via DSH service, not `hermes gateway restart` (no longer a Hermes agent since 2026-08-27) |
|
||||
|
||||
## Debounce
|
||||
|
||||
|
||||
Reference in New Issue
Block a user