Includes updates across gateway, router, node-worker, memory-service, aurora-service, swapper, sofiia-console UI and node2 infrastructure: - gateway-bot: Dockerfile, http_api.py, druid/aistalk prompts, doc_service - services/router: main.py, router-config.yml, fabric_metrics, memory_retrieval, offload_client, prompt_builder - services/node-worker: worker.py, main.py, config.py, fabric_metrics - services/memory-service: Dockerfile, database.py, main.py, requirements - services/aurora-service: main.py (+399), kling.py, quality_report.py - services/swapper-service: main.py, swapper_config_node2.yaml - services/sofiia-console: static/index.html (console UI update) - config: agent_registry, crewai_agents/teams, router_agents - ops/fabric_preflight.sh: updated preflight checks - router-config.yml, docker-compose.node2.yml: infra updates - docs: NODA1-AGENT-ARCHITECTURE, fabric_contract updated Made-with: Cursor
12 KiB
Dev Contract: Preflight-first, Node-specific, Zero Assumptions (v0.1)
0. Заборона припущень
Будь-яка дія/пропозиція щодо моделей, провайдерів, портів, compose або routing заборонена без preflight snapshot з цільової ноди.
Без snapshot — ні коміт, ні деплой, ні рекомендація.
1. Обов'язковий Preflight Snapshot
1.1 Збір
Перед кожною зміною запустити ops/fabric_snapshot.py на цільовій ноді:
# NODA2 (локально)
python3 ops/fabric_snapshot.py --node-id NODA2
# NODA1 (через SSH tunnel)
ssh -L 18099:127.0.0.1:8099 -L 18109:127.0.0.1:8109 \
-L 19102:127.0.0.1:9102 -fN root@144.76.224.179
python3 ops/fabric_snapshot.py --node-id noda1 \
--ncs http://127.0.0.1:18099 --worker http://127.0.0.1:18109 \
--router http://127.0.0.1:19102 --ollama http://127.0.0.1:21434 \
--ssh root@144.76.224.179
Snapshot зберігається в ops/preflight_snapshots/<node_id>_<timestamp>.json.
1.2 Endpoints (всі обов'язкові)
| Endpoint | Що перевіряє |
|---|---|
NCS /capabilities |
served_models, capabilities, node_load, runtimes |
NCS /capabilities/caps |
capability flags (stt/tts/ocr/image) |
NCS /capabilities/installed |
installed_artifacts (disk scan) |
node-worker /caps |
provider flags (STT_PROVIDER, TTS_PROVIDER...) |
node-worker /healthz |
NATS connection, concurrency |
Router /v1/capabilities |
global view (всі ноди, capabilities_by_node) |
Router /v1/models |
глобальний пул моделей |
Ollama /api/tags |
реальні Ollama моделі на ноді |
docker ps |
список контейнерів |
1.3 Quality gate (must-pass)
Snapshot валідний тільки якщо:
- Router healthy
- NCS healthy
- Node-worker healthy
capabilities_by_nodeмістить цільову нодуserved_modelsне порожній (або є пояснення "compute-less node")
2. Served ≠ Installed
2.1 Два шари правди
| Шар | Джерело | Використання |
|---|---|---|
| served_models | NCS /capabilities → runtimes (Ollama/llama-server/...) | Routing, model selection, offload |
| installed_artifacts | NCS /capabilities/installed → disk scan | Інвентаризація, recommendations, cleanup |
Модель на диску — це candidate, не "доступна".
2.2 Заборона hardcode
- Заборонено комітити
models:списки в swapper/router configs - Дозволено: policy-only
prefer(тип/клас моделі), але не імена, крім критичних cloud SKU
3. Capability-first routing
3.1 Routing rules
Router обирає ноду, не "модель":
find_nodes_with_capability(cap)— тільки ноди де cap=truerequire_fresh_caps(ttl=30)— preflight guard- Circuit breaker — виключити ноди з відкритим breaker
- Load scoring —
inflight * 10 + (100 if mem_pressure=high) - Offload через NATS
node.{id}.{cap}.request
3.2 Fail fast
Якщо capability відсутня на всіх нодах — fail fast з явним повідомленням:
{"error": "No node with capability 'stt' available", "capabilities_by_node": {...}}
Заборонено: тихий fallback на cloud без WARNING log.
3.3 Нодозалежність
STT/TTS/OCR/Image можуть бути різними на різних нодах:
- NODA2:
STT_PROVIDER=mlx_whisper,TTS_PROVIDER=mlx_kokoro - NODA1:
STT_PROVIDER=none,OCR_PROVIDER=vision_prompted
Вмикання capability = тільки через env flags в node-worker → /caps.
4. Безпечний контроль змін
4.1 План змін (обов'язково)
Перед зміною відповісти на:
- Що міняємо
- Чому
- Що може зламатися
- Як перевіряємо (postflight)
- Rollback (точна команда)
4.2 Rollback
Кожна зміна має мати:
- git commit hash / tag
- одну команду rollback (
docker compose up -d --force-recreate <service>,git checkout)
5. Postflight
Після кожної зміни — повторний snapshot і порівняння:
- served_models count (не зменшився без причини)
- capabilities map (нові = очікувані)
- container count
- error rate (prom_metrics)
6. Жодних прихованих fallback
- Невідомий профіль або відсутній API key → WARNING + deterministic fallback на
agent.fallback_llm - Заборонено: "мовчки пішли в DeepSeek" без логу
7. Канонічні артефакти
| Артефакт | Призначення |
|---|---|
ops/fabric_snapshot.py |
Збір повного snapshot |
ops/fabric_preflight.sh |
Quick check + snapshot save |
ops/preflight_snapshots/ |
Зберігання snapshots |
docs/fabric_contract.md |
Цей контракт |
Реальний стан (snapshot 2026-02-27)
NODA1 (production)
- 49 контейнерів (gateway, router, memory, qdrant, neo4j, redis, postgres, minio, rag, swapper, farmos, brand-, oneok-, plant-vision, crawl4ai, grafana, prometheus...)
- 5 Ollama моделей: qwen3-vl:8b (vision), qwen3:8b, qwen3.5:27b-q4_K_M, smollm2:135m, deepseek-v3.1:671b-cloud
- 14 Telegram агентів active
- NCS P3.5 не задеплоєний — capabilities flags відсутні, installed_artifacts = 0
swapper=disabled, worker NATS connected
NODA2 (development)
- 14 контейнерів (router, node-worker, node-capabilities, nats, gateway, memory, qdrant, postgres, neo4j, redis, open-webui, sofiia-console, swapper)
- 13 served моделей (Ollama: 12 + llama_server: 1)
- 29 installed artifacts на диску (150.3GB LLM + 0.3GB TTS kokoro-v1_0)
- capabilities: llm=Y, vision=Y, ocr=Y, stt=Y, tts=Y, image=N ← Phase 1 enabled
STT_PROVIDER=memory_service,TTS_PROVIDER=memory_service,OCR_PROVIDER=vision_prompted
Phase 1: STT/TTS via Memory Service delegation (2026-02-27)
Мотивація
Увімкнення stt=true / tts=true в Fabric без нових мікросервісів і без ризику MLX-залежностей.
Архітектура
Fabric Router → find_nodes_with_capability("stt"/"tts") → NODA2 node-worker
→ STT_PROVIDER=memory_service → stt_memory_service.transcribe()
→ POST http://memory-service:8000/voice/stt (faster-whisper)
→ {text, segments, language, meta}
Fabric Router → NODA2 node-worker
→ TTS_PROVIDER=memory_service → tts_memory_service.synthesize()
→ POST http://memory-service:8000/voice/tts (edge-tts: Polina/Ostap Neural uk-UA)
→ {audio_b64, format="mp3", meta}
Контракти
STT вхід:
{
"audio_b64": "<base64>", // OR
"audio_url": "http://...", // one is required
"language": "uk", // optional
"filename": "audio.wav" // optional
}
STT вихід (fabric contract):
{"text": "...", "segments": [], "language": "uk", "meta": {...}, "provider": "memory_service"}
TTS вхід:
{"text": "...", "voice": "Polina", "speed": 1.0}
TTS вихід (fabric contract):
{"audio_b64": "<base64-mp3>", "format": "mp3", "meta": {...}, "provider": "memory_service"}
Обмеження Phase 1
- ffmpeg=false: лише формати що Memory Service ковтає нативно (WAV рекомендований)
- Текст TTS: max 500 символів (Memory Service limit)
- Голоси TTS: Polina (uk-UA-PolinaNeural), Ostap (uk-UA-OstapNeural), en-US-GuyNeural
- NODA1: залишається
STT_PROVIDER=none/TTS_PROVIDER=none(не заважає роутингу)
Phase 2 (MLX upgrade — опційний)
Встановити STT_PROVIDER=mlx_whisper та/або TTS_PROVIDER=mlx_kokoro в docker-compose коли:
- готовий ffmpeg або чітко обмежені формати
- потрібний якісніший локальний TTS замість edge-tts
- NODA2 Apple Silicon виграш від MLX
Voice HA (Multi-node routing) — PR1–PR3
Архітектура
Browser → sofiia-console /api/voice/tts
↓ VOICE_HA_ENABLED=false (default)
memory-service:8000/voice/tts ← legacy direct
↓ VOICE_HA_ENABLED=true
Router /v1/capability/voice_tts
↓ (caps + scoring)
node.{id}.voice.tts.request (NATS)
↓
node-worker (voice semaphore)
↓
memory-service/voice/tts
NATS Subjects (Voice HA — відокремлені від generic)
| Subject | Призначення |
|---|---|
node.{id}.voice.tts.request |
Voice TTS offload (окремий semaphore) |
node.{id}.voice.llm.request |
Voice LLM inference (голосові guardrails) |
node.{id}.voice.stt.request |
Voice STT transcription |
Сумісність: generic subjects (node.{id}.tts.request etc.) — незмінні.
Capability Flags
Node Worker /caps повертає:
{
"capabilities": {
"tts": true,
"voice_tts": true,
"voice_llm": true,
"voice_stt": true
},
"voice_concurrency": {
"voice_tts": 4,
"voice_llm": 2,
"voice_stt": 2
}
}
voice_tts=true лише коли TTS_PROVIDER != none і NATS subscription активна.
NCS агрегує ці флаги через _derive_capabilities().
Router Endpoints
| Endpoint | Дедлайн | Суб'єкт |
|---|---|---|
POST /v1/capability/voice_tts |
3000ms | node.{id}.voice.tts.request |
POST /v1/capability/voice_llm |
9000ms (fast) / 12000ms (quality) | node.{id}.voice.llm.request |
POST /v1/capability/voice_stt |
6000ms | node.{id}.voice.stt.request |
Response headers: X-Voice-Node, X-Voice-Mode (local|remote), X-Voice-Cap.
Scoring
score = wait_ms + rtt_ms + p95_ms + mem_penalty - local_bonus
mem_penalty = 300 if mem_pressure == "high"
local_bonus = VOICE_PREFER_LOCAL_BONUS (default 200ms)
Якщо score_local <= score_best_remote + LOCAL_THRESHOLD_MS → вибирається локальна нода.
BFF Feature Flag
# docker-compose.node2-sofiia.yml
VOICE_HA_ENABLED: "false" # default — legacy direct path
VOICE_HA_ROUTER_URL: "http://router:8000" # Router для HA offload
Активація: VOICE_HA_ENABLED=true + rebuild sofiia-console.
Деактивація: VOICE_HA_ENABLED=false — повертається до direct memory-service.
Метрики (Prometheus)
node-worker (/prom_metrics):
node_worker_voice_jobs_total{cap,status}node_worker_voice_inflight{cap}node_worker_voice_latency_ms{cap}(histogram)
router (/fabric_metrics):
fabric_voice_capability_requests_total{cap,status}fabric_voice_offload_total{cap,node,status}fabric_voice_breaker_state{cap,node}(1=open)fabric_voice_score_ms{cap}(histogram)
Контракт: No Silent Fallback
- Будь-який fallback (busy, broken, timeout) логує
WARNING+ інкрементує Prometheus counter TOO_BUSYвключаєretry_after_mshint для Router failover- Circuit breaker per
node+voice_cap— не змішується з generic CB
Тести
tests/test_voice_ha.py — 28 тестів:
- Node Worker voice caps + semaphore isolation
- Router fabric_metrics voice helpers
- BFF
VOICE_HA_ENABLEDfeature flag - Voice scoring logic (local prefer, mem penalty, remote wins when saturated)
- No silent fallback invariants