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

25 KiB
Raw Permalink Blame History

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.serviceactive (GPU, port 11434, qwen3.5:27b-q4_K_M, KEEP_ALIVE=10m)
  • ollama-cpu.serviceactive (CPU, port 11435, smollm2:135m)
  • gpu-ollama-exporter.serviceactive (port 9400)
  • ollama-warmup-27b.timeractive (кожні 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.

Patch 1: Уніфікувати GPU профілі на host.docker.internal:11434:

# 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):

# 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 /visionqwen3-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:8890socket.gaierror: Temporary failure in name resolution — сервіс намагається резолвити щось не те.
  4. Шлях B результат незрозумілий — куди parser-pipeline відправляє результат обробки зображення після Vision?
  5. qwen3-vl:8b cold-start — перший запит до vision займе 30-60s (lazy load).

Patch 3: Виправити SWAPPER_URL в parser-pipeline compose:

# 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:

# 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 моделей.

Patch 5: Виправити network для render-pptx-worker та index-doc-worker:

# 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).

Patch 6 (ВАЖЛИВИЙ): Вирішити GPU конфлікт Swapper vs Ollama:

Варіанти:

  • A (рекомендований): Swapper Vision через Ollama GPU (11434), STT на CPU:

    # 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:

    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-daarioncontrol-plane, master (Ready)
    • NODA2 (llm80-che-1-1): worker nodeNotReady (проблема!)
  • 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.

Patch 7 (NATS federation між нодами):

# /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. Небезпечно.

Patch 8 (мінімальний control plane extension):

Додати в control-plane endpoints для node ops:

# 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):

# 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

# 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