Files
microdao-daarion/docs/audits/NODA1_AUDIT_CURRENT.md
Apple 67225a39fa 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
2026-03-03 07:14:53 -08:00

519 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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*