Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0191c9edc | ||
|
|
2d2259035a | ||
|
|
7ce6cad01f | ||
|
|
0147ac2abb | ||
|
|
c4ab31e56b | ||
|
|
9cf6304e23 | ||
|
|
91ec7ea3d2 | ||
|
|
e59f59d776 | ||
|
|
889f4e4dd1 |
@@ -74,8 +74,9 @@ model:
|
|||||||
Run on any Hermes host to detect violations:
|
Run on any Hermes host to detect violations:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
grep -n 'api_key: sk-' /root/.hermes/config.yaml \
|
grep -rn 'api_key: sk-' /root/.hermes/ \
|
||||||
| grep -v 'deepseek\|b7d9\|openai\|anthropic\|DEEPSEEK'
|
--include='config.yaml' \
|
||||||
|
| grep -v 'deepseek\|openai\|anthropic\|DEEPSEEK'
|
||||||
```
|
```
|
||||||
|
|
||||||
If any output — violation. Fix immediately.
|
If any output — violation. Fix immediately.
|
||||||
@@ -118,14 +119,36 @@ litellm_settings:
|
|||||||
|
|
||||||
## Verified Agents (2026-07-04 final)
|
## Verified Agents (2026-07-04 final)
|
||||||
|
|
||||||
| Agent | CT | Status | api_key_env entries | Hardcoded harness | Last Verified |
|
| Agent | CT | Status | LiteLLM Alias | Key Type | Systemd Source | Last Verified |
|
||||||
|-------|-----|--------|---------------------|-------------------|---------------|
|
|-------|-----|--------|---------------|----------|----------------|---------------|
|
||||||
| Tanko | 112 | ✅ Compliant (key: tanko) | 4 | 0 | 22:45 EDT |
|
| Tanko | 112 | ✅ Compliant | `tanko` | Dedicated | `/etc/environment` only | 23:00 EDT |
|
||||||
| Mumuni | 114 | ✅ Compliant | 8 | 0 | 19:14 EDT |
|
| Mumuni | 114 | ✅ Compliant | `mumuni` | Dedicated | `/etc/environment` only | 23:00 EDT |
|
||||||
| Koby | 111 | ✅ Compliant | 14 | 0 | 19:14 EDT |
|
| Koby | 111 | ✅ Compliant | `koby` | Dedicated | User service + drop-in | 23:00 EDT |
|
||||||
| Koonimo | 113 | ✅ Compliant | 7 | 0 | 19:14 EDT |
|
| Koonimo | 113 | ✅ Compliant | `koonimo` | Dedicated | System service + drop-in | 23:00 EDT |
|
||||||
| Abiba | 100 | ✅ N/A (pi native) | — | — | 19:14 EDT |
|
| Abiba | 100 | ✅ N/A (pi native) | — | — | — | 23:00 EDT |
|
||||||
| Kagenz0 | 105 | ❌ DOWN | — | — | 19:14 EDT |
|
| Kagenz0 | 105 | ❌ DOWN | — | — | — | 19:14 EDT |
|
||||||
|
|
||||||
|
### Systemd Service Pattern (2026-07-04 fix)
|
||||||
|
|
||||||
|
All Hermes agents use systemd to manage their gateway. Two issues were fixed:
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
**Correct pattern:**
|
||||||
|
```ini
|
||||||
|
# In service file:
|
||||||
|
EnvironmentFile=/etc/environment
|
||||||
|
|
||||||
|
# Drop-in only for overrides, NOT primary key storage.
|
||||||
|
# If a drop-in exists, it must match /etc/environment.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rotation procedure** (one step 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`
|
||||||
|
|
||||||
## Violation Response
|
## Violation Response
|
||||||
|
|
||||||
@@ -135,9 +158,25 @@ litellm_settings:
|
|||||||
4. **Restart** — gateway must restart to pick up env var
|
4. **Restart** — gateway must restart to pick up env var
|
||||||
5. **Confirm** — test key against LiteLLM: `curl -H "Authorization: Bearer $KEY" .../v1/models` → 200
|
5. **Confirm** — test key against LiteLLM: `curl -H "Authorization: Bearer $KEY" .../v1/models` → 200
|
||||||
6. **Update** — bump the verified table above
|
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.
|
||||||
|
|
||||||
## Related Contracts
|
## Related Contracts
|
||||||
|
|
||||||
- `hermes-config-template.prose.md` — full configuration template
|
- `hermes-config-template.prose.md` — full configuration template
|
||||||
- `litellm-health.prose.md` — LiteLLM stack health verification
|
- `litellm-health.prose.md` — LiteLLM stack health verification
|
||||||
- `zulip-platform-verification.prose.md` — cross-platform agent verification
|
- `zulip-platform-verification.prose.md` — cross-platform agent verification
|
||||||
|
- `litellm-api-keys.prose.md` — API key creation, rotation, and verification
|
||||||
|
|
||||||
|
## CI Pipeline (2026-07-04)
|
||||||
|
|
||||||
|
All contract changes must pass the PR Pipeline before merge:
|
||||||
|
|
||||||
|
```
|
||||||
|
auth → validate → lint → ai-review → gate
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Trigger**: push to master (abiba-bot only) or pull request
|
||||||
|
- **Branch protection**: Only `abiba-bot` can push directly to master. All other users must use PRs.
|
||||||
|
- **Status check**: `PR Pipeline — Authorize → Validate → Review → Merge` required before merge
|
||||||
|
- **Runner**: `runner-ct110` (Gitea Actions v0.6.1) on CT 110
|
||||||
|
- **Config**: `.gitea/workflows/pr-pipeline.yaml`
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
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.
|
||||||
|
agent: abiba
|
||||||
|
version: 1.0.0
|
||||||
|
status: active
|
||||||
|
runtime_contract: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hermes Zulip Restore — Bring Any Agent Back to Good State
|
||||||
|
|
||||||
|
Single-shot function that restores full Zulip connectivity for a Hermes agent.
|
||||||
|
Covers adapter deployment, HTML stripping (slash command fix), env verification,
|
||||||
|
gateway restart, and connection validation.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
| Param | Type | Required | Default | Description |
|
||||||
|
|-------|------|----------|---------|-------------|
|
||||||
|
| `target` | string | yes | — | Agent name: `mumuni`, `tanko`, or `koby` |
|
||||||
|
|
||||||
|
## Maintains
|
||||||
|
|
||||||
|
- adapter_deployed: bool — Whether `_strip_html` adapter is at correct bundled path
|
||||||
|
- zulip_connected: bool — Whether gateway_state shows zulip.state = "connected"
|
||||||
|
- env_valid: bool — Whether .env has ZULIP_SITE, ZULIP_EMAIL, ZULIP_API_KEY
|
||||||
|
- gateway_running: bool — Whether gateway process is running
|
||||||
|
|
||||||
|
### Postconditions
|
||||||
|
|
||||||
|
- `_strip_html` function present in `<hermes-agent>/plugins/platforms/zulip/adapter.py`
|
||||||
|
- All three adapter files (__init__.py, adapter.py, plugin.yaml) present at bundled path
|
||||||
|
- Zulip env vars set in `~/.hermes/.env` (or `/home/jerome/.hermes/.env` for Tanko)
|
||||||
|
- Gateway restarted and zulip platform reports state `connected`
|
||||||
|
- HTML stripping enabled for `/approve` and `/deny` slash command support
|
||||||
|
|
||||||
|
## Requires
|
||||||
|
|
||||||
|
- SSH access to target host (direct or via amdpve for CTs)
|
||||||
|
- Git repo at `https://git.sysloggh.net/SyslogSolution/zulip-platform-plugins.git`
|
||||||
|
- Python 3 with `httpx` installed on target
|
||||||
|
- Zulip server accessible at `https://chat.sysloggh.net`
|
||||||
|
|
||||||
|
## Live-State Fields
|
||||||
|
|
||||||
|
| Host | CT | Proxmox | IP (direct) | Hermes Home | User |
|
||||||
|
|------|-----|---------|-------------|-------------|------|
|
||||||
|
| 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 |
|
||||||
|
|
||||||
|
| Field | Value | Trust |
|
||||||
|
|-------|-------|-------|
|
||||||
|
| Zulip server | https://chat.sysloggh.net | ✅ Verified |
|
||||||
|
| Git repo (zulip-platform) | `https://git.sysloggh.net/SyslogSolution/zulip-platform-plugins.git` | ✅ Verified |
|
||||||
|
| Bundled adapter path | `<HERMES_HOME>/hermes-agent/plugins/platforms/zulip/` | ✅ Verified |
|
||||||
|
| Git branch | `feat/zulip-streaming` | ✅ Verified (contains _strip_html fix) |
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
### Step 1: Locate Target
|
||||||
|
|
||||||
|
Map `target` to connectivity parameters from the live-state table above.
|
||||||
|
For CT112 and CT111, route through `ssh root@amdpve` then `pct exec <id>`.
|
||||||
|
|
||||||
|
### Step 2: Deploy Zulip Adapter
|
||||||
|
|
||||||
|
On the target host:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone or update the plugin repo
|
||||||
|
mkdir -p /tmp/zulip-deploy
|
||||||
|
cd /tmp/zulip-deploy
|
||||||
|
if [ -d zulip-platform-plugins ]; then
|
||||||
|
cd zulip-platform-plugins && git pull origin feat/zulip-streaming
|
||||||
|
else
|
||||||
|
git clone --branch feat/zulip-streaming \
|
||||||
|
https://git.sysloggh.net/SyslogSolution/zulip-platform-plugins.git
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure bundled plugin directory exists
|
||||||
|
mkdir -p <HERMES_HOME>/hermes-agent/plugins/platforms/zulip
|
||||||
|
|
||||||
|
# Copy adapter files
|
||||||
|
cp zulip-platform-plugins/plugins/platforms/zulip/adapter.py \
|
||||||
|
zulip-platform-plugins/plugins/platforms/zulip/__init__.py \
|
||||||
|
zulip-platform-plugins/plugins/platforms/zulip/plugin.yaml \
|
||||||
|
<HERMES_HOME>/hermes-agent/plugins/platforms/zulip/
|
||||||
|
|
||||||
|
# Fix ownership (Tanko only)
|
||||||
|
chown -R jerome:jerome <HERMES_HOME>/hermes-agent/plugins/platforms/zulip/ # Tanko only
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
rm -rf /tmp/zulip-deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Verify _strip_html is Present
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -q "_strip_html" <HERMES_HOME>/hermes-agent/plugins/platforms/zulip/adapter.py
|
||||||
|
```
|
||||||
|
Expected: exit code 0. If not found → adapter is stale, re-run Step 2 with fresh clone.
|
||||||
|
|
||||||
|
### Step 4: Verify Env Credentials
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -E "ZULIP_SITE|ZULIP_EMAIL|ZULIP_API_KEY" <HERMES_HOME>/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all three variables set with non-empty values. If any missing:
|
||||||
|
- ZULIP_SITE: `https://chat.sysloggh.net`
|
||||||
|
- ZULIP_EMAIL: `<agent>-bot@chat.sysloggh.net`
|
||||||
|
- ZULIP_API_KEY: obtain from Zulip admin panel (Bots → show API key)
|
||||||
|
|
||||||
|
### Step 5: Restart Gateway
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <HERMES_HOME>/hermes-agent
|
||||||
|
# Use venv if available
|
||||||
|
python3 -m hermes_cli.main gateway restart # or: venv/bin/python -m hermes_cli.main gateway restart
|
||||||
|
```
|
||||||
|
|
||||||
|
Wait for the restart to complete (up to 45s). Check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep "Gateway running" <HERMES_HOME>/logs/gateway.log | tail -1
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: "Gateway running with N platform(s)" where N > 1 (includes zulip).
|
||||||
|
|
||||||
|
### Step 6: Validate Zulip Connection
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
d = json.load(open('$HERMES_HOME/gateway_state.json'))
|
||||||
|
print('zulip:', d.get('platforms', {}).get('zulip', {}).get('state', 'NOT FOUND'))
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `zulip: connected`. If not connected, check gateway log:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -E "zulip|Zulip|ZULIP" <HERMES_HOME>/logs/gateway.log | tail -10
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: Report
|
||||||
|
|
||||||
|
Compile results: `{ adapter_deployed, zulip_connected, env_valid, gateway_running }`.
|
||||||
|
|
||||||
|
| State | Action |
|
||||||
|
|-------|--------|
|
||||||
|
| All true | ✅ Agent restored — relay success to user |
|
||||||
|
| `adapter_deployed: false` | Re-run Step 2 |
|
||||||
|
| `env_valid: false` | Prompt for missing credentials |
|
||||||
|
| `zulip_connected: false` | Check Zulip server reachability, verify API key |
|
||||||
|
| `gateway_running: false` | Check process logs for crash cause |
|
||||||
|
|
||||||
|
## Known Failure Modes
|
||||||
|
|
||||||
|
| Symptom | Root Cause | Recovery |
|
||||||
|
|---------|-----------|----------|
|
||||||
|
| Gateway running with 1 platform(s) | Adapter at wrong path (user plugins vs bundled) | Deploy to `<hermes-agent>/plugins/platforms/zulip/` not `~/.hermes/plugins/` |
|
||||||
|
| Queue expired / BAD_EVENT_QUEUE_ID | Idle for 10+ minutes → normal | Auto-reconnects — no action needed |
|
||||||
|
| No events received for N seconds | No DMs or @mentions sent to this bot | Normal if nobody messaged the agent |
|
||||||
|
| `httpx` not found | Missing dependency | `pip install httpx` in the Hermes venv or system Python |
|
||||||
|
| Slash commands not matching | Missing `_strip_html` — Zulip sends `<p>/approve</p>` | Verify `_strip_html` in adapter (Step 3) |
|
||||||
|
| Permission denied on gateway restart | Running as wrong user | Use `su - jerome` for Tanko; root for others |
|
||||||
|
|
||||||
|
## Git Branch Reference
|
||||||
|
|
||||||
|
The `_strip_html` fix lives on `feat/zulip-streaming` branch:
|
||||||
|
```
|
||||||
|
https://git.sysloggh.net/SyslogSolution/zulip-platform-plugins/src/branch/feat/zulip-streaming
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit `55ca15d` — `fix(zulip): add _strip_html for slash command matching`
|
||||||
|
Pull request #33 is the primary integration branch.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last verified good state**: 2026-07-08 — Mumuni, Tanko, Koby all connected with `_strip_html` applied.
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
# Posts review comments via Gitea API.
|
# Posts review comments via Gitea API.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
LITELLM_URL="${LITELLM_URL:-https://litellm.sysloggh.net}"
|
LITELLM_URL="${LITELLM_URL:-}"
|
||||||
LITELLM_KEY="${LITELLM_KEY:-sk-litellm-7f96080dd99b15c36bd4b333b58a6796}"
|
LITELLM_KEY="${LITELLM_KEY:-}"
|
||||||
GITEA_TOKEN="${GITEA_TOKEN:-$(grep GITEA_TOKEN /root/.pi/agent/extensions/telegram/.env 2>/dev/null | cut -d= -f2 || echo '')}"
|
GITEA_TOKEN="${GITEA_TOKEN:-$(grep GITEA_TOKEN /root/.pi/agent/extensions/telegram/.env 2>/dev/null | cut -d= -f2 || echo '')}"
|
||||||
GITEA_API="https://git.sysloggh.net/api/v1"
|
GITEA_API="https://git.sysloggh.net/api/v1"
|
||||||
|
|
||||||
@@ -97,6 +97,13 @@ PROMPT
|
|||||||
)
|
)
|
||||||
|
|
||||||
echo "=== Sending to LiteLLM for review ==="
|
echo "=== Sending to LiteLLM for review ==="
|
||||||
|
|
||||||
|
# Skip if LiteLLM is not configured (secrets not available)
|
||||||
|
if [ -z "${LITELLM_URL:-}" ] || [ -z "${LITELLM_KEY:-}" ]; then
|
||||||
|
echo "⚠️ LiteLLM secrets not configured — skipping AI review"
|
||||||
|
echo " Set LITELLM_URL and LITELLM_KEY as Gitea repository secrets"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
RESPONSE=$(curl -sf -X POST "$LITELLM_URL/v1/chat/completions" \
|
RESPONSE=$(curl -sf -X POST "$LITELLM_URL/v1/chat/completions" \
|
||||||
-H "Authorization: Bearer $LITELLM_KEY" \
|
-H "Authorization: Bearer $LITELLM_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
|||||||
Reference in New Issue
Block a user