docs(platform): add policy configs, runbooks, ops scripts and platform documentation

Config policies (16 files): alert_routing, architecture_pressure, backlog,
cost_weights, data_governance, incident_escalation, incident_intelligence,
network_allowlist, nodes_registry, observability_sources, rbac_tools_matrix,
release_gate, risk_attribution, risk_policy, slo_policy, tool_limits, tools_rollout

Ops (22 files): Caddyfile, calendar compose, grafana voice dashboard,
deployments/incidents logs, runbooks for alerts/audit/backlog/incidents/sofiia/voice,
cron jobs, scripts (alert_triage, audit_cleanup, migrate_*, governance, schedule),
task_registry, voice alerts/ha/latency/policy

Docs (30+ files): HUMANIZED_STEPAN v2.7-v3 changelogs and runbooks,
NODA1/NODA2 status and setup, audit index and traces, backlog, incident,
supervisor, tools, voice, opencode, release, risk, aistalk, spacebot

Made-with: Cursor
This commit is contained in:
Apple
2026-03-03 07:14:53 -08:00
parent 129e4ea1fc
commit 67225a39fa
102 changed files with 20060 additions and 0 deletions

View File

@@ -0,0 +1,518 @@
# NODA1 Full Audit — DAARION.city
**Дата:** 2026-02-27
**Сервер:** node1-daarion | 144.76.224.179 | NVIDIA RTX 4000 SFF Ada (20GB VRAM)
**Аудитор:** Sofiia — Chief AI Architect
---
## EXECUTIVE SUMMARY
| Напрям | Стан | Критичність |
|--------|------|-------------|
| Фото E2E (Telegram→Vision) | ✅ Працює, але є shortcut (не через NATS) | MEDIUM |
| PDF/Документи | ⚠️ render-pdf-worker idle, index-doc DNS fail | HIGH |
| Router/Profiles | ✅ OK — DeepSeek top-level, 27B crew, smollm2 CPU | LOW |
| STT/TTS | ✅ CPU-only (Whisper), TTS unloaded | LOW |
| Swapper | ⚠️ Потрібен — єдина точка Vision/STT/OCR/Document | KEEP |
| GPU policy | ✅ 27B GPU, smollm2 CPU, policy_ok=1 | OK |
| NODA1↔NODA2 | ⚠️ K3s cluster (flannel), NATS не з'єднані між нодами | HIGH |
| CTO Sofiia control plane | ⚠️ control-plane сервіс є, але тільки prompts+policy JWT | MEDIUM |
---
## 1. INVENTORY — Що реально запущено
### Контейнери (48 total, ключові):
```
swapper-service-node1 healthy 8890-8891
dagi-router-node1 healthy 9102→8000
dagi-nats-node1 up 4222
dagi-memory-service-node1 healthy 8000
dagi-qdrant-node1 healthy 6333
dagi-gateway-node1 healthy 9300
parser-pipeline up 8101
ingest-service up 8100
render-pdf-worker-node1 up (no port)
render-pptx-worker-node1 up (no port)
index-doc-worker-node1 up (no port)
presentation-renderer-node1 healthy 9212
rag-service-node1 healthy 9500
dagi-vision-encoder-node1 healthy 8001
control-plane up 9200
dagi-crawl4ai-node1 healthy 11235
oneok-gotenberg-node1 up 3010
plant-vision-node1 healthy 8085
crewai-nats-worker up 9011
dagi-staging-crewai-service up 9010
artifact-registry-node1 healthy 9220
dagi-minio-node1 up 9000-9001
```
### Systemd:
- `ollama.service`**active** (GPU, port 11434, qwen3.5:27b-q4_K_M, KEEP_ALIVE=10m)
- `ollama-cpu.service`**active** (CPU, port 11435, smollm2:135m)
- `gpu-ollama-exporter.service`**active** (port 9400)
- `ollama-warmup-27b.timer`**active** (кожні 15хв)
---
## 2. ROUTER — Профілі, моделі, routing
### CURRENT STATE
**Env у контейнері dagi-router-node1:**
```
ENABLE_CREW_MODEL_ROUTING=1
CREW_SMALL_MODEL=smollm2:135m
CREWAI_WORKER_LLM_PROFILE=crew_local_27b
DEEPSEEK_API_KEY=sk-0db94... (production key)
NATS_URL=nats://nats:4222
VISION_ENCODER_URL=http://vision-encoder:8001
```
**Профілі (router-config.yml):**
| Profile | Provider | Model | URL |
|---------|----------|-------|-----|
| `cloud_deepseek` | deepseek | deepseek-chat | api.deepseek.com |
| `cloud_mistral` | mistral | mistral-large-latest | api.mistral.ai |
| `crew_local_27b` | ollama | qwen3.5:27b-q4_K_M | 172.17.0.1:11434 (GPU) |
| `crew_vision_27b` | ollama | qwen3.5:27b-q4_K_M | 172.17.0.1:11434 (GPU) |
| `crew_local_small` | ollama | smollm2:135m | host.docker.internal:11435 (CPU) |
| `service_local_cpu` | ollama | smollm2:135m | host.docker.internal:11435 (CPU) |
| `vision_encoder` | — | — | vision-encoder:8001 (ViT-L-14) |
| `crewai` | — | — | localhost:9010 |
**Агенти з vision моделлю:** greenfood, druid, eonarch, helion → `qwen3-vl:8b` (через swapper)
**Метрики:** `llm_heavy_share_ratio=0.0` — важкі запити ще не логовані (лічильники нульові, нові після restart).
### GAPS
- `local_qwen3_8b`, `qwen3_strategist_8b`, ... — **всі вказують на 27B замість 8B** (рядки в config не оновлені після зміни). Назви оманливі.
- `crew_local_27b` використовує `172.17.0.1:11434` — не `host.docker.internal`. Inconsistency: CPU профілі через host.docker.internal, GPU — через IP.
### RECOMMENDED PATCHES
**Patch 1:** Уніфікувати GPU профілі на `host.docker.internal:11434`:
```yaml
# services/router/router-config.yml
crew_local_27b:
base_url: http://host.docker.internal:11434 # було 172.17.0.1
crew_vision_27b:
base_url: http://host.docker.internal:11434
```
**Patch 2:** Перейменувати оманливі профілі (або залишити as-is якщо вони deprecated):
```yaml
# local_qwen3_8b → local_qwen3_27b (або видалити невикористані)
```
---
## 3. ФОТО E2E — Telegram → Vision → Агент
### CURRENT STATE (Два шляхи!)
#### Шлях A: Прямий (основний для більшості агентів)
```
Telegram photo → Gateway (http_api.py:~2085)
↓ download photo via Telegram Bot API → file_url (https://api.telegram.org/file/...)
↓ send_to_router({file_url, images: [file_url], prompt})
↓ Router (main.py:~2445) → SWAPPER_URL/vision
payload: {model: "qwen3-vl-8b", prompt, images: [file_url]}
↓ Swapper /vision → завантажує qwen3-vl:8b (ollama pull) → відповідь
↓ Router повертає text → Gateway → Telegram
```
#### Шлях B: Через NATS ATTACHMENTS (для parser-pipeline)
```
Telegram photo → Gateway
↓ (окремий worker?) → NATS ATTACHMENTS stream
↓ parser-pipeline consumer
process_image() → SWAPPER_URL/vision (base64 encode)
↓ result → ??? (не ясно куди іде результат)
```
**КРИТИЧНО:** `parser-pipeline` логи показують **тисячі** `ServiceUnavailableError` між рестартами — NATS stream `ATTACHMENTS` зникає після рестарту `dagi-nats-node1` (нема persistence). Після рестарту parser підключається знову (`Consumer created: parser-pipeline`).
### Vision model flow (Swapper):
- Gateway надсилає `file_url` (не base64 завантаження)
- Router передає `images: [file_url]` у Swapper
- Swapper `/vision``qwen3-vl:8b` через Ollama (6.1GB, lazy load)
- **qwen3-vl:8b зараз `unloaded`** — cold-start ~30-60s при першому виклику
### GAPS
1. **NATS stream ATTACHMENTS не персистентний** — після `docker restart dagi-nats-node1` stream зникає. Parser спамить `ServiceUnavailableError` поки не перезапустити.
2. **parser-pipeline `SWAPPER_URL=http://swapper-service:8890`** — але контейнер називається `swapper-service-node1`. DNS може не резолвитись.
3. **ingest-service** також має `SWAPPER_URL=http://swapper-service-node1:8890``socket.gaierror: Temporary failure in name resolution` — сервіс намагається резолвити щось не те.
4. **Шлях B результат незрозумілий** — куди parser-pipeline відправляє результат обробки зображення після Vision?
5. **qwen3-vl:8b cold-start** — перший запит до vision займе 30-60s (lazy load).
### RECOMMENDED PATCHES
**Patch 3:** Виправити `SWAPPER_URL` в parser-pipeline compose:
```yaml
# docker-compose.node1.yml, parser-pipeline service
environment:
- SWAPPER_URL=http://swapper-service-node1:8890 # було: http://swapper-service:8890
```
**Patch 4:** NATS stream ATTACHMENTS — зробити файловий storage з retention:
```yaml
# nats-js-init service (вже є в compose) — перевірити що він запускається після рестарту NATS
```
---
## 4. PDF/ДОКУМЕНТИ — Обробка
### CURRENT STATE
**Сервіси обробки документів:**
| Сервіс | Статус | Роль |
|--------|--------|------|
| `render-pdf-worker-node1` | ✅ up, **idle** | PDF → PNG/зображення (NATS: artifact.job.render_pdf.requested) |
| `render-pptx-worker-node1` | ⚠️ DNS fail (`nats`) | PPTX → PNG (NATS: нема з'єднання) |
| `index-doc-worker-node1` | ⚠️ DNS fail (RAG service?) | RAG indexing (NATS: artifact.job.*) |
| `presentation-renderer-node1` | ✅ healthy (9212) | API сервіс рендерингу |
| `oneok-gotenberg-node1` | ✅ up (3010) | HTML/PDF generation (Gotenberg) |
| `rag-service-node1` | ✅ healthy (9500) | RAG retrieval |
| `artifact-registry-node1` | ✅ healthy (9220) | Артефакт реєстр |
| `dagi-minio-node1` | ✅ up (9000-9001) | S3 storage |
| `parser-pipeline` | ✅ up (8101) | NATS consumer → Swapper doc+image |
**Docling:** НЕ ВСТАНОВЛЕНИЙ як окремий контейнер. Є як модель у Swapper (`granite-docling`, тип `document`, 2.5GB, `unloaded`).
**Шлях обробки документа (PDF):**
```
Telegram doc → Gateway → ?
або send_to_router з doc_url
або через NATS → parser-pipeline → Swapper /document
Swapper /document → granite-docling (lazy load, 2.5GB) → текст
Паралельно:
→ artifact.job.render_pdf.requested → render-pdf-worker → PNG → artifact-registry → MinIO
→ artifact.job.index_doc.requested → index-doc-worker → rag-service (RAG indexing)
```
### GAPS
1. **render-pptx-worker** не може резолвити `nats` DNS — на іншій docker network або compose group.
2. **index-doc-worker** DNS fail (щось не резолвить) — перевірити network config.
3. **granite-docling** у swapper `unloaded` — завантажується lazily, займе час при першому запиті документа. GPU увімкнений для docling? (GPU_ENABLED=false зараз!)
4. **Немає Docling окремим сервісом** — вся обробка документів через Swapper, який зараз CPU-only через наші зміни.
### GAPS — КРИТИЧНО
> **Swapper GPU_ENABLED=false** — означає, що granite-docling, got-ocr2, qwen3-vl-8b і whisper будуть завантажуватись в CPU/RAM. При 20GB VRAM це субоптимально для Vision і OCR моделей.
### RECOMMENDED PATCHES
**Patch 5:** Виправити network для render-pptx-worker та index-doc-worker:
```yaml
# docker-compose.node1.yml — додати network dagi-network до цих сервісів
render-pptx-worker:
networks:
- dagi-network # щоб резолвити 'nats'
index-doc-worker:
networks:
- dagi-network
```
---
## 5. STT/TTS/SWAPPER — Детальний аналіз
### CURRENT STATE
**Swapper /health:** `{"status":"healthy","active_model":"qwen3-8b","mode":"single-active"}`
**Swapper конфіг (фактичний):**
- `mode: multi-active` в yaml, але ENV `MAX_CONCURRENT_MODELS=1` → single-active режим
- `GPU_ENABLED=false` (наша зміна) — але config.yaml каже `gpu_enabled: true`
- `WHISPER_DEVICE=cpu, WHISPER_COMPUTE_TYPE=int8`
**Моделі в Swapper:**
| Модель | Тип | Розмір | Статус |
|--------|-----|--------|--------|
| qwen3-8b | llm | 5.2GB | **loaded** (Ollama) |
| qwen3-vl-8b | vision | 6.1GB | unloaded |
| got-ocr2 | ocr | 7.0GB | unloaded |
| donut-base | ocr | 3.0GB | unloaded |
| donut-cord | ocr | 3.0GB | unloaded |
| granite-docling | document | 2.5GB | unloaded |
| faster-whisper-large | stt | 3.0GB | unloaded |
| whisper-small | stt | 0.5GB | unloaded |
| xtts-v2 | tts | 2.0GB | unloaded |
| flux-klein-4b | image_gen | 15.4GB | unloaded |
**STT:**
- STT startup: `[STT-POLICY] WHISPER_DEVICE env='cpu' | actual_device='cpu'`
- Swapper `/stt` ← parser-pipeline (audio processing)
- Swapper `/stt` ← router (STT_URL)
- Swapper `/stt` ← gateway (STT_SERVICE_URL)
- **Whisper завантажується lazily при першому аудіо-запиті** на CPU (int8)
**TTS:** xtts-v2 (2GB) — `unloaded`. Не використовується активно.
**Висновок по Swapper: ЗАЛИШИТИ (він критичний)**
Swapper є єдиним агрегатором для:
1. **Vision** (`/vision`) — qwen3-vl:8b для всіх агентів що аналізують фото
2. **STT** (`/stt`) — Whisper для голосових повідомлень
3. **OCR** (`/ocr`) — got-ocr2 для документів
4. **Document** (`/document`) — granite-docling для PDF/DOCX
5. **TTS** (`/tts`) — xtts-v2 (поки не активований)
**Проблема:** `active_model=qwen3-8b` через Ollama — це **дублювання** з основним Ollama GPU. Swapper завантажує qwen3:8b через свій ollama, поки є окремий Ollama на 11434 з 27B. При виклику vision, swapper **swap'ає** qwen3:8b і завантажує qwen3-vl:8b — займає VRAM GPU.
> **Але GPU_ENABLED=false!** — Значить qwen3-vl:8b завантажиться в RAM/CPU, що дуже повільно (>30s).
### RECOMMENDED PATCHES
**Patch 6 (ВАЖЛИВИЙ):** Вирішити GPU конфлікт Swapper vs Ollama:
Варіанти:
- **A (рекомендований):** Swapper Vision через Ollama GPU (11434), STT на CPU:
```yaml
# docker-compose.node1.yml, swapper-service
environment:
- GPU_ENABLED=true # дозволити GPU для vision/OCR
- WHISPER_DEVICE=cpu # але STT лишається CPU
- WHISPER_COMPUTE_TYPE=int8
# Прибрати CUDA_VISIBLE_DEVICES= (empty block GPU)
```
Потрібно додати GPU device back:
```yaml
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
```
Тоді Swapper поверне GPU для vision і OCR.
- **B (поточний стан):** GPU_ENABLED=false → all CPU → Vision дуже повільно
---
## 6. GPU POLICY
### CURRENT STATE ✅
```
VRAM: 18783 MiB / 20475 MiB (qwen3.5:27b-q4_K_M завантажений — warmup timer)
GPU Ollama (11434): 1 model — qwen3.5:27b-q4_K_M (17434 MiB)
CPU Ollama (11435): 0 models (smollm2:135m unloaded, lazy)
gpu_single_model_policy_ok = 1 ✅
ollama_cpu_instance_up = 1 ✅
```
**Проблема:** Swapper показує `active_model=qwen3-8b` — це qwen3:8b через ollama **всередині swapper**, але Swapper зараз CPU-only. Значить qwen3:8b у свопері не займає GPU VRAM поки GPU_ENABLED=false. Але якщо повернути GPU Swapper — треба перевірити що 27B + qwen3-vl-8b не одночасно в VRAM (20GB максимум).
**Потенційний конфлікт:** 27B (17.4GB) + qwen3-vl-8b (6.1GB) = **23.5GB > 20GB VRAM** → OOM!
Необхідна координація: коли Swapper завантажує vision модель, Ollama GPU має вивантажити 27B або навпаки.
---
## 7. NODA1 ↔ NODA2 — З'єднання
### CURRENT STATE
**Інфраструктура:**
- NODA1 і NODA2 (`llm80-che-1-1`, IP 192.168.1.240) — це **K3s cluster** (flannel CNI)!
- NODA1: `node1-daarion` — **control-plane, master** (Ready)
- NODA2 (`llm80-che-1-1`): `worker node` — **NotReady** (проблема!)
- **Flannel:** `10.42.0.0/24` (NODA1), `10.42.1.0/24` (NODA2) — pod overlay network
- **WireGuard:** НЕ встановлений
- **NATS:** cluster config є (`my_cluster`, port 6222), але `routes = []` — **NATS не з'єднаний між нодами**
**K3s pods на NODA2 (llm80-che-1-1):** більшість `Terminating` або `Pending` — NODA2 NotReady!
**Що це означає:**
- Фізично NODA1 і NODA2 з'єднані через K3s/flannel (LAN, 192.168.x.x)
- Але Docker Compose сервіси на NODA2 (memory service, qdrant, neo4j) — **окремі**, не в K3s
- NATS між нодами не federatederated — жоден cross-node message bus не налаштований
### GAPS
1. **K3s worker NODA2 NotReady** — pods Terminating/Pending. Не ясно чи це критично для поточного продакшну.
2. **NATS не кластеризований** — немає leafnode/route між NODA1 і NODA2 NATS.
3. **Немає cross-node subjects** для агентів.
4. **NODA2 підключення до NODA1:** NODA2 має свій Docker Compose (окремі memory/qdrant), немає спільного bus.
### RECOMMENDED PATCHES
**Patch 7 (NATS federation між нодами):**
```conf
# /opt/microdao-daarion/nats/nats-server.conf (NODA1)
leafnodes {
port: 7422
}
# NATS на NODA2 підключається як leafnode:
leafnodes {
remotes = [{ url: "nats://144.76.224.179:7422" }]
}
```
Це дозволить NODA2 публікувати/підписуватись на `node.control.noda2.*` через NODA1.
---
## 8. CTO SOFIIA — Control Plane
### CURRENT STATE
**`control-plane` контейнер (порт 9200):**
- FastAPI сервіс з JWT auth (`SERVICE_ROLE=controlplane`)
- Endpoints:
- `GET /prompts/{agent_id}` — версіоновані system prompts з файлів `*_prompt.txt`
- `GET /policy/{agent_id}` — RBAC/entitlements (DefaultPolicies)
- `GET /prompts/{agent_id}/hash` — hash промпту для drift detection
- **401 Unauthorized** при зверненні без JWT — це правильно
**Що є:**
- ✅ Промпти централізовані та версіоновані
- ✅ JWT auth для сервіс-до-сервіс
- ✅ Policy/RBAC per agent
- ✅ `dagi-vision-encoder-node1` — ViT-L-14 на CPU (embeddings)
**Що НЕ реалізовано:**
- ❌ Node operations (restart/deploy/health через control-plane)
- ❌ Sofiia не має NATS-control topic для публікації команд
- ❌ Немає `node-ops-worker` на кожній ноді
- ❌ Sofiia добавляє нову ноду тільки через SSH root (bRhfV7uNY9m6er — hardcoded!)
- ❌ Немає механізму "додати нову ноду без root"
**Поточний механізм керування нодами:** SSH з паролем root. Небезпечно.
### RECOMMENDED PATCHES
**Patch 8 (мінімальний control plane extension):**
Додати в control-plane endpoints для node ops:
```python
# services/control-plane/app/main.py (або новий node_ops.py)
# Sofiia публікує на NATS:
# node.control.noda1.restart_service → {service_name, reason}
# node.control.noda1.health_check → {}
# node.control.noda1.get_logs → {service_name, lines}
# node-ops-worker (новий мікросервіс) підписується на ці subjects
# виконує whitelist commands (docker restart, docker logs tail, health curl)
# відповідає на node.control.noda1.reply.*
```
**Мінімальна реалізація (50 рядків Python):**
```python
# services/node-ops-worker/main.py
ALLOWED_COMMANDS = {
"restart_service": lambda s: f"docker restart {s}",
"health_check": lambda s: f"curl -sf http://localhost:{PORT_MAP[s]}/health",
"logs_tail": lambda s, n: f"docker logs --tail {n} {s}",
}
# Subscribe to node.control.noda1.> via NATS
# Execute only ALLOWED_COMMANDS
# Reply to reply subject
```
---
## VALIDATION CHECKLIST
```bash
# 1. Router CPU profiles (host.docker.internal)
docker exec dagi-router-node1 curl -s http://host.docker.internal:11435/api/tags | python3 -c 'import sys,json; print("CPU Ollama OK:", len(json.load(sys.stdin).get("models",[])))'
# 2. GPU policy
curl -s http://localhost:9400/metrics | grep gpu_single_model_policy_ok
# 3. Swapper Vision (cold start test — без кешу)
# УВАГА: займе 30-60s якщо GPU_ENABLED=false!
# curl -s -X POST http://localhost:8890/vision -H 'Content-Type: application/json' \
# -d '{"model":"qwen3-vl-8b","prompt":"що на фото?","images":["<url>"]}' | jq .
# 4. Parser pipeline connected
docker logs --tail 5 parser-pipeline 2>&1 | grep -E 'Connected|Consumer created'
# 5. NATS stream ATTACHMENTS exists
curl -s 'http://localhost:8222/jsz?streams=true' | python3 -m json.tool | grep -A3 'ATTACHMENTS'
# 6. render-pptx-worker DNS fix check
docker logs --tail 5 render-pptx-worker-node1 2>&1 | grep -v 'getaddrinfo'
# 7. index-doc-worker DNS fix check
docker logs --tail 5 index-doc-worker-node1 2>&1 | grep -v 'getaddrinfo'
# 8. Control plane health
curl -s http://localhost:9200/health
# 9. Swapper STT device
docker logs swapper-service-node1 2>&1 | grep STT-POLICY
# 10. K3s NODA2 status
kubectl get nodes
```
---
## PRIORITIZED ACTION PLAN
### P0 — Негайно (production impact):
| # | Патч | Файл | Вплив |
|---|------|------|-------|
| 3 | SWAPPER_URL fix в parser-pipeline | docker-compose.node1.yml | Vision через parser |
| 5 | Network fix render-pptx + index-doc | docker-compose.node1.yml | Документи |
| 6 | GPU повернути Swapper (Vision повільний!) | docker-compose.node1.yml | Vision latency |
### P1 — Цього тижня:
| # | Патч | Файл | Вплив |
|---|------|------|-------|
| 1 | host.docker.internal для GPU профілів | router-config.yml | Stability |
| 4 | NATS ATTACHMENTS persistence | nats config | Parser stability |
| 7 | NATS leafnode NODA1↔NODA2 | nats-server.conf | Cross-node |
### P2 — Наступний спринт:
| # | Патч | Файл | Вплив |
|---|------|------|-------|
| 8 | node-ops-worker для Sofiia control | нові файли | Security |
| 2 | Profile rename в router-config | router-config.yml | Clarity |
---
## ВІДПОВІДІ НА 7 КЛЮЧОВИХ ПИТАНЬ
### 1. Фото E2E
**Telegram photo → Gateway** (скачує файл → file_url) → **`send_to_router({images:[file_url]})`** → **Router** перевіряє агента → якщо vision-агент → **`SWAPPER_URL/vision`** → Swapper → Ollama `qwen3-vl:8b` → text опис → Router → Gateway → Telegram. Parser-pipeline — паралельний worker для асинхронної обробки (не основний шлях). Payload: `{model, prompt, images:[url], max_tokens}`.
### 2. Документи/PDF
**Немає Docling як сервісу.** Docling вбудований в Swapper як `granite-docling` (lazy, unloaded). Шлях: Gateway → Router → `SWAPPER_URL/document` → Swapper → granite-docling. Паралельно через NATS: `artifact.job.render_pdf.requested` → render-pdf-worker → PNG → MinIO/artifact-registry. `index-doc-worker` індексує в RAG але має DNS fail.
### 3. Router
Top-level агенти → **DeepSeek API** (cloud_deepseek). Crew tasks → **qwen3.5:27b-q4_K_M** (crew_local_27b, GPU). Monitoring/small → **smollm2:135m** (crew_local_small, CPU Ollama 11435). `ENABLE_CREW_MODEL_ROUTING=1` активний. Vision агенти отримують `qwen3-vl-8b` через Swapper.
### 4. TTS/STT
STT: **Whisper (CPU, int8)** через Swapper `/stt`. `WHISPER_DEVICE=cpu` підтверджено логами. Lazy load при першому аудіо. Підтримується: faster-whisper-large (3GB), whisper-small (0.5GB). TTS: xtts-v2 (2GB) — **not deployed активно** (unloaded). Немає VRAM конкуренції для STT.
### 5. Swapper
**Залишити.** Є єдиним агрегатором для Vision (qwen3-vl:8b), STT (Whisper), OCR (got-ocr2), Document (granite-docling), TTS (xtts-v2). Без Swapper треба окремі сервіси для кожного. Але: `active_model=qwen3-8b` — потенційно невикористана ролі (є окремий Ollama). **Слід розглянути видалення qwen3-8b зі Swapper** — він дублює GPU Ollama, залишити тільки Vision/OCR/STT/Document функції.
### 6. NODA1↔NODA2
З'єднані через **K3s cluster** (flannel, 10.42.0.0/24). NODA2 (`llm80-che-1-1`, 192.168.1.240) — K3s worker, зараз **NotReady**. NATS між нодами **не з'єднаний** (routes=[]), немає leafnode. Docker Compose сервіси незалежні. Для cross-node messaging потрібен NATS leafnode або Flannel pod networking.
### 7. CTO Sofiia Control Plane
Поточний стан: `control-plane` (9200) — JWT-захищений сервіс з prompts + policy. **Немає node-ops механізму**. Sofiia керує нодами через SSH root (небезпечно). Правильний шлях: NATS-control plane + `node-ops-worker` на кожній ноді з whitelist команд. control-plane вже є основою — треба додати NATS subscription для node operations.
---
*Звіт згенеровано автоматично аудитом NODA1 | Sofiia v2.7 | 2026-02-27*