Verified on ground 2026-07-16 against CT 116 litellm_config.yaml + GPU hosts: - AMD host serves qwen3.6-35B-udq4 (LiteLLM alias strix-moe); ornith-1.0-35b does NOT exist - All 3 GPUs at 256K ctx, parallel 2 (RTX 3090 was listed 128K/parallel 1) - LiteLLM timeouts: qwen 300s, gemma 120s, strix 300s (were stale 90s/120s) - Added LiteLLM model surface + key scoping to litellm-self-heal - Patched health-check script path ref Files: litellm-self-heal, litellm-health, gpu-fleet, gpu-self-heal, zulip-adapter-lessons, abiba-zulip-restore, hermes-agent-baseline, delegation-prose-contract, mumuni-delegation-prose-contract
10 KiB
kind, name, description, agent, version, status, runtime_contract
| kind | name | description | agent | version | status | runtime_contract |
|---|---|---|---|---|---|---|
| function | abiba-zulip-restore | Restores Zulip connectivity for Abiba (pi agent). Verifies the v2 router-worker extension code, starts a PM2 process as the Zulip gateway with correct env vars, validates health endpoint, and confirms DM delivery. Run this whenever Abiba stops responding on Zulip or after system restart. | abiba | 1.0.0 | active | 2 |
Abiba Zulip Restore — Resume pi Zulip Communication
Single-shot function that restores full Zulip connectivity for the Abiba pi agent. Covers extension code validation, PM2 process management, health endpoint verification, and DM loopback testing.
Live-State Fields
| Field | Value | Trust |
|---|---|---|
| Agent name | abiba | ✅ |
| Bot email | abiba-bot@chat.sysloggh.net | ✅ |
| Zulip server | https://chat.sysloggh.net | ✅ |
| Extension path | /root/.pi/agent/extensions/zulip/index.js | ✅ |
| Config path | /root/.pi/agent/extensions/zulip/config.yaml | ✅ |
| Health port | 9200 | ✅ |
| @all-bots user ID | 20 | ✅ (config, verified by API at runtime) |
| PM2 process name | abiba-zulip | ✅ |
| Provider | syslog-harness (http://192.168.68.116/v1) | ✅ |
| Default model | deepseek-v4-pro | ✅ (settings.json) |
Architecture
The pi Zulip extension uses a router-worker architecture (v2):
- Router (PM2
abiba-zulip,ZULIP_ROLE=router): Polls Zulip for events, maintains a per-sender pool of pi RPC worker processes. Each sender gets their ownpi --mode rpc --session-dirprocess with persistent sessions. Handles streaming edits back to Zulip. - Worker (child
pi --mode rpc): Runs the agent with per-sender persistent sessions. No Zulip logic in the worker — the router handles all Zulip I/O.
The extension loads in ALL pi sessions (via settings.json extensions array)
but is a NO-OP unless ZULIP_ROLE=router is set. Only the PM2 router process
carries the env var.
Maintains
- extension_valid: bool — Extension code imports without errors
- pm2_running: bool — PM2 process
abiba-zulipis online - health_responding: bool — GET :9200/health returns "ok"
- zulip_connected: bool — Queue registered, bot identity resolved
- loopback_delivered: bool — Test DM sent and received
- model_valid: bool — Configured models match LiteLLM authorized models
Postconditions
- PM2 process
abiba-zuliponline and stable (uptime > 30s) - Health endpoint returns
{ status: "ok", connected: true } - Worker pool creates sessions on demand
- Echo prevention active (BOT_EMAILS includes all known bots)
- PM2 saved for auto-restart on boot
Requires
- Node.js with
yamlmodule available - PM2 installed globally
- Zulip API key in
config.yaml piCLI available on PATH- Zulip server accessible at https://chat.sysloggh.net
- LiteLLM provider accessible at http://192.168.68.116/v1
Execution
Step 1: Validate Extension Code
node -e "import('file:///root/.pi/agent/extensions/zulip/index.js').then(() => console.log('OK')).catch(e => {console.error('FAIL:', e.message); process.exit(1)})"
Expected: OK. If FAIL → check for missing dependencies, syntax errors.
Step 2: Validate Model IDs
Compare configured models against LiteLLM authorized models:
API_KEY=$(grep -oP 'apiKey:\s*\K.*' /root/.pi/agent/models.json | head -1)
curl -s -H "Authorization: Bearer $API_KEY" http://192.168.68.116/v1/models | \
python3 -c "import json,sys; d=json.load(sys.stdin); [print(m['id']) for m in d.get('data',[])]" 2>/dev/null
Check: Every model ID in models.json must appear in the authorized list.
If not → fix models.json to only include authorized models (prefer syslog-auto).
Step 3: Verify Config Integrity
python3 -c "
import yaml, sys
with open('/root/.pi/agent/extensions/zulip/config.yaml') as f:
cfg = yaml.safe_load(f)
required = ['zulip.site', 'zulip.email', 'zulip.api_key', 'agent.name']
for k in required:
keys = k.split('.')
v = cfg
for kk in keys:
v = v.get(kk)
if v is None:
print(f'MISSING: {k}')
sys.exit(1)
print('Config valid')
print(f' site={cfg[\"zulip\"][\"site\"]}')
print(f' email={cfg[\"zulip\"][\"email\"]}')
print(f' agent={cfg[\"agent\"][\"name\"]}')
print(f' health_port={cfg.get(\"health_port\", 9200)}')
"
Expected: Config valid with all fields non-empty.
Step 4: Remove Stale Systemd Service
The old abiba-zulip.service points to /opt/abiba-zulip/dist/index.js (compiled
TypeScript, not the v2 extension). It's disabled and stale. Remove it:
systemctl stop abiba-zulip 2>/dev/null || true
systemctl disable abiba-zulip 2>/dev/null || true
rm -f /etc/systemd/system/abiba-zulip.service
systemctl daemon-reload
Step 5: Start PM2 Process
Critical: The extension MUST run via pi --mode rpc, NOT node index.js directly.
The extension exports a function that requires pi's session lifecycle. Running
node index.js loads the module but never calls the export, so nothing happens.
pi --mode rpc loads all extensions (including Zulip) and fires session_start.
# Stop existing if any
pm2 delete abiba-zulip 2>/dev/null || true
# Start pi in RPC mode with ZULIP_ROLE=router env
ZULIP_ROLE=router pm2 start "$(which pi)" \
--name abiba-zulip \
--interpreter none \
-- --mode rpc --no-session
Wait 10 seconds for pi to load all extensions, fire session_start, and the Zulip router to register its event queue.
Step 6: Validate PM2 Process
pm2 show abiba-zulip --no-color
Check: status=online, restarts=0, uptime > 5s.
Step 7: Check Logs for Connection
sleep 3
tail -20 /root/.pm2/logs/abiba-zulip-out.log
Look for:
[zulip-ext] Connecting to https://chat.sysloggh.net as abiba-bot@chat.sysloggh.net…[zulip-ext] Bot user_id=N, all-bots user_id=N[zulip-ext] Connected, queue=N[zulip-ext] Echo prevention: N bot emails[zulip-ext] Health endpoint on :9200
If error → check API key, network to chat.sysloggh.net.
Step 8: Health Endpoint
curl -s http://localhost:9200/health | python3 -m json.tool
Check:
status: "ok"(not "down")zulip.connected: truezulip.queue_idis non-null stringzulip.bot_user_idis positive integer
Step 9: DM Loopback Test
curl -s http://localhost:9200/health | python3 -c "
import json,sys
d = json.load(sys.stdin)
if d.get('zulip',{}).get('connected'):
print(f'✅ Zulip connected. Queue: {d[\"zulip\"][\"queue_id\"]}')
print(f' Bot user_id: {d[\"zulip\"][\"bot_user_id\"]}')
print(f' Messages processed: {d[\"zulip\"][\"messages_processed\"]}')
else:
print('❌ Zulip NOT connected')
sys.exit(1)
"
Step 10: Save PM2 for Auto-Start
pm2 save
pm2 startup systemd -u root --hp /root 2>/dev/null || true
Step 11: Report
Compile results:
| Check | Pass? |
|---|---|
| Extension code imports | extension_valid |
| Model IDs authorized | model_valid |
| Config integrity | config_valid |
| PM2 process online | pm2_running |
| Health endpoint | health_responding |
| Zulip connected | zulip_connected |
All pass → ✅ Abiba Zulip restored. Relay success to user.
Partial failure → see recovery matrix below.
Recovery Matrix
| Failure | Recovery |
|---|---|
| Extension import fails | Check node_modules/zulip-js exists; run npm install in extension dir |
| Model ID mismatch | Fix models.json to use syslog-auto as default model; remove invalid IDs |
| Config missing | Restore from backup or recreate from scratch |
| PM2 won't start | Check node version (>=18); check port 9200 not in use |
| Health "down" | Check logs for connection errors; verify Zulip API key; check network |
| "address already in use" | Kill old process: fuser -k 9200/tcp |
| Queue registration fails | Check Zulip API key validity; verify bot is active in Zulip admin |
| Rate limit (429) | Extension has built-in retry-after handling — wait, don't restart |
Known Failure Modes
| Symptom | Root Cause | Recovery |
|---|---|---|
| Extension loads but no events | ZULIP_ROLE not set |
Ensure PM2 env has ZULIP_ROLE=router |
| Worker stays "busy" forever | Model ID not authorized by LiteLLM | Fix models.json (lesson #11) |
| Placeholder sent but no response | editMessage API fails silently | Extension has fallback (sends new msg); check Zulip API |
| Queue expires rapidly | Poll interval too aggressive | v2 uses 3s poll with long-poll — should be fine |
| Bot doesn't respond to @mentions | Not subscribed to stream | Bot auto-subscribes via API |
| Stale error in health | last_error not cleared |
v2 clears on successful poll (lesson #4) |
Appendix: Root Cause & Fix Summary (2026-07-13)
Problem: Zulip extension was offline — no PM2 process running.
Root cause: The PM2 command ran node index.js directly (which loads the
extension module but never calls the exported function). The extension requires
pi's session lifecycle — pi loads extensions, fires session_start, and the
Zulip extension hooks into that event.
Fix: Run pi --mode rpc (not node index.js). The --mode rpc flag keeps
pi alive listening for RPC commands on stdin while the Zulip extension's router
runs in the background via the session_start hook.
ZULIP_ROLE=router pm2 start "$(which pi)" --name abiba-zulip \
--interpreter none -- --mode rpc --no-session
Additional fixes applied:
- Fixed
models.json: replacedqwen3.6-35B-A3B(not authorized by LiteLLM) withsyslog-auto+strix-moe(prevents silent worker failure per Lesson #11) - Removed stale systemd unit
abiba-zulip.service(pointed to old TS code) - PM2 saved for auto-restart on boot
Appendix: PM2 Ecosystem Config (Optional)
If preferred over manual pm2 start, create /root/ecosystem.config.js entry:
module.exports = {
apps: [{
name: 'abiba-zulip',
script: '/bin/pi',
interpreter: 'none',
args: '--mode rpc --no-session',
cwd: '/root',
env: {
ZULIP_ROLE: 'router',
},
log_file: '/root/.pm2/logs/abiba-zulip-out.log',
error_file: '/root/.pm2/logs/abiba-zulip-error.log',
max_restarts: 20,
restart_delay: 5000,
}]
};
Last verified good state: 2026-07-13 — Extension v2 running via pi --mode rpc, health endpoint :9200 returning {status:"ok",connected:true}, queue a669f21e.