Files
microdao-daarion/docs/fabric_contract.md
Apple e9dedffa48 feat(production): sync all modified production files to git
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
2026-03-03 07:13:29 -08:00

12 KiB
Raw Blame History

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 обирає ноду, не "модель":

  1. find_nodes_with_capability(cap) — тільки ноди де cap=true
  2. require_fresh_caps(ttl=30) — preflight guard
  3. Circuit breaker — виключити ноди з відкритим breaker
  4. Load scoring — inflight * 10 + (100 if mem_pressure=high)
  5. 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 План змін (обов'язково)

Перед зміною відповісти на:

  1. Що міняємо
  2. Чому
  3. Що може зламатися
  4. Як перевіряємо (postflight)
  5. 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) — PR1PR3

Архітектура

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_ms hint для 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_ENABLED feature flag
  • Voice scoring logic (local prefer, mem penalty, remote wins when saturated)
  • No silent fallback invariants