# Matrix Bridge DAGI — Ops Runbook (H4) **Сервіс:** `matrix-bridge-dagi` | **Нода:** NODA1 | **Порт:** 7030 (localhost) **Stack:** Matrix (Synapse) → bridge → Router `/v1/agents/{id}/infer` → Matrix reply **Фаза:** M2.2 (N rooms + mixed room routing), H1/H2/H3 hardening активний --- ## 0. Purpose Операційні перевірки та troubleshooting для `matrix-bridge-dagi` на NODA1: - Matrix ↔ Bridge ↔ Router ↔ Agent ↔ Matrix - Audit events в sofiia-console (`POST /api/audit/internal`) - Rate limit (room/sender RPM) - Backpressure queue (drops/queue wait) --- ## 1. Quick Status (30 секунд) ### 1.1 Health ```bash curl -sS http://127.0.0.1:7030/health | python3 -m json.tool ``` Очікування: ```json { "ok": true, "matrix_reachable": true, "gateway_reachable": true, "mappings_count": 1, "queue": {"size": 0, "max": 100, "workers": 2}, "rate_limiter": {"room_rpm_limit": 20, "sender_rpm_limit": 10, "active_rooms": 0, "active_senders": 0} } ``` | Поле | Норма | Тривога | |------|-------|---------| | `ok` | `true` | `false` → дивитись `error` | | `matrix_reachable` | `true` | `false` → I4 | | `gateway_reachable` | `true` | `false` → I1 | | `queue.size` | ≈0 idle | >50 → I3 | | `mappings_count` | ≥1 | 0 → перевірити `BRIDGE_ROOM_MAP` | ### 1.2 Mappings ```bash curl -sS http://127.0.0.1:7030/bridge/mappings | python3 -m json.tool ``` ### 1.3 Logs (останні 30 рядків) ```bash docker logs matrix-bridge-dagi-node1 --tail 30 ``` Шукати: `ERROR`, `Rate limited`, `Queue full`, `Invoke ok`, `Reply sent` --- ## 2. Smoke Test (2–3 хв) ### 2.1 E2E через Element (рекомендовано) 1. Element → "Change homeserver" → `matrix.daarion.space` 2. Логін: `test_user` / `TestUser_2026!` 3. Room: **DAGI — Sofiia** 4. Відправити: `ping` 5. Очікування: reply ≤ 5s (Mistral/DeepSeek залежний) ### 2.2 Smoke через curl (без Element) ```bash # Логін як test_user TOKEN=$(curl -sS -X POST http://localhost:8008/_matrix/client/v3/login \ -H 'Content-Type: application/json' \ -d '{"type":"m.login.password","identifier":{"type":"m.id.user","user":"test_user"},"password":"TestUser_2026!"}' \ | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])') # Відправити повідомлення ROOM_ID=$(grep 'SOFIIA_ROOM_ID' /opt/microdao-daarion/.env | cut -d= -f2) TXN_ID="smoke-$(date +%s)" curl -sS -X PUT "http://localhost:8008/_matrix/client/v3/rooms/$ROOM_ID/send/m.room.message/$TXN_ID" \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"msgtype":"m.text","body":"smoke test ping"}' | python3 -c 'import sys,json; print("sent:", json.load(sys.stdin).get("event_id","ERROR"))' # Чекати reply (~15-30s залежить від sync cycle) sleep 30 # Перевірити health metrics curl -sS http://127.0.0.1:7030/metrics | grep -E 'matrix_bridge_(messages_received|messages_replied)_total' ``` ### 2.3 Перевірити .well-known (для Element auto-discovery) ```bash curl -sS https://matrix.daarion.space/.well-known/matrix/client # Очікування: {"m.homeserver":{"base_url":"https://matrix.daarion.space"}} ``` --- ## 3. Операційні Метрики ### 3.1 Traffic counters ```bash curl -sS http://127.0.0.1:7030/metrics | grep -E \ "matrix_bridge_messages_(received|replied)_total|matrix_bridge_rate_limited_total|matrix_bridge_queue_dropped_total" ``` | Метрика | Значення | Що означає | |---------|----------|------------| | `messages_received_total` | росте | повідомлення надходять | | `messages_replied_total{status="ok"}` | росте | агент відповідає | | `messages_replied_total{status="error"}` | >0 | проблема send_text → I1 | | `rate_limited_total` | ≈0 | норма; >0 → I2 | | `queue_dropped_total` | 0 | норма; >0 → I3 | ### 3.2 Latency histograms ```bash curl -sS http://127.0.0.1:7030/metrics | grep -E \ "(invoke_duration|send_duration|queue_wait)_seconds_(bucket|count|sum)" ``` Орієнтири: | Histogram | Норма (p95) | Тривога | |-----------|-------------|---------| | `invoke_duration_seconds` | 1–5s | >20s → LLM деградація | | `send_duration_seconds` | <200ms | >2s → Synapse проблема | | `queue_wait_seconds` | <50ms idle | >5s → I3 (workers перевантажені) | ### 3.3 Queue state ```bash curl -sS http://127.0.0.1:7030/health | python3 -c \ 'import sys,json; d=json.load(sys.stdin); print("queue:", d.get("queue"))' ``` --- ## 4. Audit Events (sofiia-console) ### 4.1 Перевірити audit ingest (internal endpoint) ```bash INT_TOKEN=$(grep 'SOFIIA_INTERNAL_TOKEN' /opt/microdao-daarion/.env | cut -d= -f2) curl -sS -X POST http://127.0.0.1:8002/api/audit/internal \ -H 'Content-Type: application/json' \ -H "X-Internal-Service-Token: $INT_TOKEN" \ -d '{"event":"matrix.smoke_check","agent_id":"sofiia","node_id":"NODA1","status":"ok"}' | python3 -m json.tool # Очікування: {"ok": true, "event": "matrix.smoke_check"} ``` ### 4.2 Події для пошуку в audit log | Event | Коли | Норма | |-------|------|-------| | `matrix.message.received` | при кожному вхідному | завжди | | `matrix.agent.replied` | при успішній відповіді | завжди | | `matrix.rate_limited` | при перевищенні RPM | рідко/ніколи | | `matrix.queue_full` | при переповненні черги | ніколи | | `matrix.error` | при помилці invoke/send | ніколи | --- ## 5. Common Incidents ### I1: "Element підключений, але Sofiia не відповідає" ```bash # Step 1: health check curl -sS http://127.0.0.1:7030/health | python3 -c 'import sys,json; d=json.load(sys.stdin); print("matrix:", d.get("matrix_reachable"), "gw:", d.get("gateway_reachable"), "maps:", d.get("mappings_count"))' # Step 2: metrics — received vs replied curl -sS http://127.0.0.1:7030/metrics | grep -E 'matrix_bridge_messages_(received|replied)_total' # Step 3: logs docker logs matrix-bridge-dagi-node1 --tail 50 | grep -E 'ERROR|invoke|reply|error' # Step 4: router direct test curl -sS -X POST http://127.0.0.1:9102/v1/agents/sofiia/infer \ -H 'Content-Type: application/json' \ -d '{"prompt":"ping","session_id":"smoke-check","user_id":"ops"}' | python3 -c 'import sys,json; d=json.load(sys.stdin); print("router:", d.get("response","ERROR")[:80])' ``` **Фікс:** - `gateway_reachable: false` → перевірити `DAGI_GATEWAY_URL=http://dagi-router-node1:8000` - `mappings_count: 0` → перевірити `BRIDGE_ROOM_MAP` в env - received росте, replied ні → router або send_text проблема (Step 4) --- ### I2: "Bagato rate_limited в metrics" ```bash curl -sS http://127.0.0.1:7030/metrics | grep rate_limited_total curl -sS http://127.0.0.1:7030/health | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("rate_limiter"))' ``` **Причина:** RPM занадто низький або room flood (bot/скрипт). **Фікс** (тимчасово, через env): ```bash # На NODA1: cd /opt/microdao-daarion # Відредагувати .env: # RATE_LIMIT_ROOM_RPM=40 # RATE_LIMIT_SENDER_RPM=20 docker compose -f docker-compose.matrix-bridge-node1.yml up -d ``` --- ### I3: "Queue drops ростуть / queue.size велике" ```bash curl -sS http://127.0.0.1:7030/metrics | grep queue_dropped_total curl -sS http://127.0.0.1:7030/metrics | grep 'invoke_duration_seconds_bucket' ``` **Причина:** модель/Router повільні або `WORKER_CONCURRENCY` малий. **Фікс:** ```bash # Збільшити workers і queue (в .env): # WORKER_CONCURRENCY=4 # QUEUE_MAX_EVENTS=300 docker compose -f docker-compose.matrix-bridge-node1.yml up -d ``` Паралельно перевірити latency histogram — якщо `invoke_duration` >20s, проблема на стороні LLM. --- ### I4: "matrix_reachable: false у /health" ```bash # Synapse публічний ендпоінт curl -sS https://matrix.daarion.space/_matrix/client/versions | python3 -c 'import sys,json; d=json.load(sys.stdin); print("ok, versions:", list(d.get("versions",[]))[:3])' # Synapse внутрішній (з NODA1) curl -sS http://localhost:8008/_matrix/client/versions # Synapse контейнер docker ps | grep synapse docker logs dagi-synapse-node1 --tail 20 # Nginx curl -sI https://matrix.daarion.space/_matrix/client/versions | head -3 ``` **Фікс:** - Synapse контейнер впав → `docker compose -f docker-compose.synapse-node1.yml up -d` - TLS / .well-known → перевірити nginx (`nginx -t && nginx -s reload`) - Токен невалідний → I5 (rotation) --- ### I5: "Замінити MATRIX_ACCESS_TOKEN" ```bash # 1. Отримати новий токен (з NODA1) NEW_TOKEN=$(curl -sS -X POST http://localhost:8008/_matrix/client/v3/login \ -H 'Content-Type: application/json' \ -d '{"type":"m.login.password","identifier":{"type":"m.id.user","user":"dagi_bridge"},"password":"DAGIbr1dge_M4tr1x_2026!"}' \ | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])') echo "New token: ${#NEW_TOKEN} chars" # 2. Оновити в .env sed -i "s/^MATRIX_ACCESS_TOKEN=.*/MATRIX_ACCESS_TOKEN=$NEW_TOKEN/" /opt/microdao-daarion/.env # 3. Restart bridge cd /opt/microdao-daarion docker compose -f docker-compose.matrix-bridge-node1.yml up -d # 4. Verify sleep 10 curl -sS http://127.0.0.1:7030/health | python3 -c 'import sys,json; d=json.load(sys.stdin); print("matrix:", d.get("matrix_reachable"))' ``` --- ### I6: "Замінити SOFIIA_INTERNAL_TOKEN" ```bash # 1. Генерувати новий токен NEW_INT_TOKEN=$(openssl rand -base64 32 | tr -d '/+=') # 2. Оновити в .env sed -i "s/^SOFIIA_INTERNAL_TOKEN=.*/SOFIIA_INTERNAL_TOKEN=$NEW_INT_TOKEN/" /opt/microdao-daarion/.env # 3. Restart обох сервісів cd /opt/microdao-daarion docker compose -f docker-compose.node1.yml up -d dagi-sofiia-console-node1 docker compose -f docker-compose.matrix-bridge-node1.yml up -d sleep 15 # 4. Verify audit ingest INT_TOKEN=$(grep 'SOFIIA_INTERNAL_TOKEN' .env | cut -d= -f2) curl -sS -X POST http://127.0.0.1:8002/api/audit/internal \ -H 'Content-Type: application/json' \ -H "X-Internal-Service-Token: $INT_TOKEN" \ -d '{"event":"matrix.token_rotated","agent_id":"sofiia","node_id":"NODA1","status":"ok"}' | python3 -c 'import sys,json; print("audit ok:", json.load(sys.stdin).get("ok"))' ``` --- ## 6. Restart / Rollback ### Restart bridge ```bash cd /opt/microdao-daarion docker compose -f docker-compose.matrix-bridge-node1.yml up -d --force-recreate sleep 15 curl -sS http://127.0.0.1:7030/health | python3 -c 'import sys,json; d=json.load(sys.stdin); print("ok:", d["ok"])' ``` ### Restart Synapse ```bash docker compose -f docker-compose.synapse-node1.yml up -d sleep 20 curl -sS http://localhost:8008/_matrix/client/versions | python3 -c 'import sys,json; print("synapse ok:", "versions" in json.load(sys.stdin))' ``` ### Мінімальний smoke після restart ```bash curl -sS http://127.0.0.1:7030/health | python3 -c \ 'import sys,json; d=json.load(sys.stdin); print("bridge ok:", d["ok"], "| matrix:", d.get("matrix_reachable"), "| gw:", d.get("gateway_reachable"))' # Потім: 1 повідомлення в Element → reply ``` --- ## 7. Release Checklist (перед деплоєм bridge) ``` [ ] health ok (ok: true, matrix: true, gw: true) [ ] queue drops == 0 (curl /metrics | grep queue_dropped_total → 0) [ ] rate_limited == 0 (curl /metrics | grep rate_limited_total → 0) [ ] smoke test: Element → ping → reply ≤ 5s [ ] audit events з'являються (matrix.message.received, matrix.agent.replied) [ ] .well-known → JSON з base_url [ ] TLS cert valid (не <7 days до expiry) ``` ### Перевірка TLS cert (термін дії) ```bash echo | openssl s_client -connect matrix.daarion.space:443 -servername matrix.daarion.space 2>/dev/null \ | openssl x509 -noout -dates # notAfter — запас не менше 7 днів (auto-renew certbot) ``` --- --- ## 9. Mixed Room Debug Guide (M2.2) ### 9.1 Перевірка конфігурації mixed rooms ```bash # Поточний mapping (regular + mixed) curl -sS http://127.0.0.1:7030/bridge/mappings | python3 -m json.tool # Guard rail параметри (з /health) curl -sS http://127.0.0.1:7030/health | python3 -m json.tool | python3 -c " import sys, json; h=json.load(sys.stdin) print('mixed_rooms:', h.get('mixed_rooms_count',0)) print('total_agents:', h.get('total_agents_in_mixed_rooms',0)) print('guard_rails:', json.dumps(h.get('mixed_guard_rails',{}), indent=2)) " ``` ### 9.2 Smoke test для mixed room (6 acceptance test cases) Відправляємо з `test_user` у mixed room `!roomX:daarion.space`: ```bash # Змінна для зручності ROOM_ID="!roomX:daarion.space" TOKEN="@test_user_token" # або через Element UI # 1. Slash → Sofiia curl -sX POST "https://matrix.daarion.space/_matrix/client/v3/rooms/${ROOM_ID}/send/m.room.message/txn1" \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"msgtype":"m.text","body":"/sofiia ping"}' | jq '.event_id' # 2. Slash → Helion curl -sX POST "...txn2" -d '{"msgtype":"m.text","body":"/helion ping"}' | jq '.event_id' # 3. @mention → Sofiia curl -sX POST "...txn3" -d '{"msgtype":"m.text","body":"@sofiia status"}' | jq '.event_id' # 4. colon-mention → Sofiia curl -sX POST "...txn4" -d '{"msgtype":"m.text","body":"sofiia: status"}' | jq '.event_id' # 5. Plain text → default agent curl -sX POST "...txn5" -d '{"msgtype":"m.text","body":"ping"}' | jq '.event_id' # 6. Unknown slash → audit matrix.route.rejected curl -sX POST "...txn6" -d '{"msgtype":"m.text","body":"/unknown test"}' | jq '.event_id' ``` **Очікувана поведінка у кімнаті:** | Команда | Reply prefix | Router agent | |---------|-------------|--------------| | `/sofiia ping` | `Sofiia: ...` | sofiia | | `/helion ping` | `Helion: ...` | helion | | `@sofiia status` | `Sofiia: ...` | sofiia | | `sofiia: status` | `Sofiia: ...` | sofiia | | `ping` (plain) | `: ...` | перший у списку | | `/unknown test` | `⚠️ Unknown agent...` або тиша (залежно від `UNKNOWN_AGENT_BEHAVIOR`) | — | ### 9.3 Перевірка routing метрик ```bash # Successful routing breakdown by reason curl -sS http://127.0.0.1:7030/metrics | grep 'matrix_bridge_routed_total' # Очікування: # matrix_bridge_routed_total{agent_id="sofiia",reason="slash_command"} N # matrix_bridge_routed_total{agent_id="helion",reason="slash_command"} N # matrix_bridge_routed_total{agent_id="sofiia",reason="at_mention"} N # matrix_bridge_routed_total{agent_id="sofiia",reason="default"} N # Rejections curl -sS http://127.0.0.1:7030/metrics | grep 'matrix_bridge_route_rejected_total' # Очікується > 0 лише якщо були /unknown або занадто довгі токени # Active concurrency locks curl -sS http://127.0.0.1:7030/metrics | grep 'active_room_agent_locks' # Зазвичай 0 (між повідомленнями) ``` ### 9.4 Debug: "Wrong agent responds" **Симптом:** У mixed room `/helion ...` → відповідає sofiia, або відповідає не той агент. **Діагностика:** ```bash # 1. Перевірити audit events в sofiia-console # (через psql або API) curl -sS http://127.0.0.1:8002/api/audit \ | python3 -m json.tool | grep -A5 '"event":"matrix.message.received"' \ | grep '"routing_reason"' # routing_reason має бути "slash_command", "at_mention", "colon_mention" або "default" # 2. Перевірити логи bridge docker logs matrix-bridge-dagi-node1 --tail 100 2>&1 | grep -E 'route|Route|routing' # Очікування: "Slash route: /helion → helion" або "Default route: → sofiia" # 3. Перевірити BRIDGE_MIXED_ROOM_MAP в .env grep BRIDGE_MIXED_ROOM_MAP /opt/microdao-daarion/.env # Формат: "!roomX:server=sofiia,helion" # Перший у списку = default agent ``` **Виправлення:** - Якщо порядок агентів неправильний — змінити `BRIDGE_MIXED_ROOM_MAP` або встановити `BRIDGE_MIXED_DEFAULTS` - Перезапустити bridge: `docker restart matrix-bridge-dagi-node1` ### 9.5 Debug: "Session context змішується між агентами" **Симптом:** Helion "пам'ятає" контекст розмови sofiia. **Перевірка:** Session key у логах (`session_id` у invoke payload) ```bash docker logs matrix-bridge-dagi-node1 --tail 50 2>&1 | grep session_id # Очікування для mixed room: # session_id = "matrix:roomX_daarion_space:sofiia" ← ізольований per-agent # session_id = "matrix:roomX_daarion_space:helion" ← окремий контекст ``` Якщо обидва агенти мають однаковий `session_id` — це баг рефакторингу, відкати на M2.0. ### 9.6 Debug: Concurrency lock "застрявання" **Симптом:** Запит зависає, не відповідає, active_lock_count > 0 протягом >60s. ```bash # Перевірити active locks curl -sS http://127.0.0.1:7030/health | python3 -m json.tool | python3 -c \ "import sys,json; h=json.load(sys.stdin); print(h.get('mixed_guard_rails',{}).get('active_room_agent_locks',0))" # Якщо > 0 протягом довгого часу — Router застряг curl -sS http://127.0.0.1:9102/health | jq '.status' # Якщо Router недоступний — перезапуск bridge звільнить locks (graceful shutdown + cancel) docker restart matrix-bridge-dagi-node1 ``` ### 9.7 Guard rail: перевірка MAX_AGENTS_PER_MIXED_ROOM Якщо у `.env` є рядок з 6+ агентами і `MAX_AGENTS_PER_MIXED_ROOM=5`: ```bash docker logs matrix-bridge-dagi-node1 --tail 20 2>&1 | grep 'Config error\|MAX_AGENTS' # Очікується: "❌ Config error: BRIDGE_MIXED_ROOM_MAP parse errors: Room ... has 6 agents > MAX..." # Bridge не стартує → /health поверне {"ok":false,"error":"..."} ``` **Виправлення:** Зменшити кількість агентів або збільшити `MAX_AGENTS_PER_MIXED_ROOM`. --- ## 8. Що прикріпити до інциденту ```bash echo "=== /health ===" && curl -sS http://127.0.0.1:7030/health | python3 -m json.tool echo "=== /bridge/mappings ===" && curl -sS http://127.0.0.1:7030/bridge/mappings | python3 -m json.tool echo "=== /metrics traffic ===" && curl -sS http://127.0.0.1:7030/metrics \ | grep -E 'matrix_bridge_(messages|rate_limited|queue_dropped|gateway_errors|routed|route_rejected)' echo "=== /metrics latency ===" && curl -sS http://127.0.0.1:7030/metrics \ | grep -E '(invoke|send|queue_wait)_duration_seconds_(count|sum)' echo "=== logs ===" && docker logs matrix-bridge-dagi-node1 --tail 50 2>&1 \ | grep -E 'ERROR|WARN|rate_limited|queue_full|Reply sent|invoke ok|route|rejected' ```