Files
microdao-daarion/docs/Humanized_Stepan_Architecture_Plan.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

32 KiB
Raw Permalink Blame History

Humanized Stepan v2 — Architecture Plan

Версія: 0.1-draft
Статус: plan (без коду)
Область змін: crews/agromatrix_crew/ + мінімальний торкання http_api.py
Принцип: fail-closed, backward-compatible, жодної нескінченної рекурсії


1. Проблеми поточної архітектури

Симптом Причина у коді
На "привіт" запускаються всі 5 під-агентів run.py завжди викликає ops, iot, platform, spreadsheet, sustainability
Роботизовані відповіді JSON-схема фінального агента, відсутня адаптація стилю
Степан не знає хто ти Немає UserProfile, жодного звернення до memory-service
Степан не знає твою ферму Немає FarmProfile
Після відповіді немає самоперевірки Reflection відсутній
Оператор і звичайний користувач мають однакову відповідь is_operator є, але стиль не змінюється
Зміна detect_intent() ламає всю логіку Ключові слова захардкожені в одній функції

2. Загальна схема нового потоку

handle_message(text, user_id, chat_id, ops_mode)
    │
    ├─► [activation_gate.pre_check(text)]     ← блокує рекурсію, лічить глибину
    │
    ├─► [memory_manager.load(user_id)]        ← UserProfile + FarmProfile
    │         │ fallback: порожній профіль     ← fail-safe
    │
    ├─► [depth_classifier.classify(text, profile)]
    │         │ → DepthDecision {mode, intent, crew_needed, confidence}
    │         │ fallback: mode="deep"           ← fail-closed: краще зробити більше
    │
    ├─► if mode == "light":
    │       [style_adapter.render(profile)] → system_prompt_prefix
    │       Stepan відповідає сам (без під-агентів)
    │       → response
    │
    ├─► if mode == "deep":
    │       [activation_gate.select_crew(DepthDecision, FarmProfile)]
    │         → {ops?, iot?, platform?, spreadsheet?, sustainability?}
    │       Запускати ТІЛЬКИ потрібних під-агентів
    │       Stepan консолідує
    │       → response
    │
    ├─► [reflection_engine.reflect(response, profile, intent)]  ← один прохід, не рекурсія
    │         │ fallback: оригінальна відповідь
    │
    ├─► [memory_manager.update_async(user_id, text, response)] ← не блокує
    │
    └─► return final_response

3. Нові модулі

3.1 depth_classifier.py

Розташування: crews/agromatrix_crew/depth_classifier.py

Відповідальність: визначити глибину запиту і які под-агенти взагалі потрібні.

Вхід:

  • text: str — текст повідомлення
  • profile: UserProfile | None — профіль користувача
  • farm: FarmProfile | None — профіль ферми

Вихід: DepthDecision

@dataclass
class DepthDecision:
    mode: Literal["light", "deep"]   # ключовий перемикач
    intent: str                       # human-readable intent
    crew_needed: list[str]            # підмножина: ops, iot, platform, spreadsheet, sustainability
    confidence: float                 # 0..1, < 0.4 → force deep
    reason: str                       # для audit логу

Логіка класифікації (rule-based, без LLM):

Light mode — якщо текст відповідає хоча б одному патерну:

LIGHT_PATTERNS = {
    "greeting":      ["привіт", "доброго", "hello", "hi", "добрий ранок", "добрий вечір"],
    "thanks":        ["дякую", "дякуй", "спасибі", "дякую степан"],
    "ack":           ["зрозумів", "ок", "добре", "чудово", "зрозуміла"],
    "whoami_check":  ["хто я", "мої права"],
    "simple_status": ["який статус", "що зараз"],
}

Deep mode — якщо текст відповідає хоча б одному:

DEEP_PATTERNS = {
    "planning":      ["сплануй", "план на", "розробити план", "графік робіт"],
    "multi_ops":     ["по всіх полях", "кілька ділянок", "всі культури"],
    "iot_alert":     ["аномалія", "тривога", "sensors", "вологість впала"],
    "analysis":      ["план/факт", "план факт", "статистика", "зведення", "порівняй"],
    "decision":      ["що робити", "порадь", "проаналізуй", "виріши"],
    "recording":     ["запиши", "зафіксуй", "внеси", "додай операцію"],
}

Crew selection у deep mode:

crew_needed logic:
  "ops" → "запиши" | "зафіксуй" | "внеси" | farmos keywords
  "iot" → "датчик" | "вологість" | "temp" | "sensor" | FarmProfile.has_iot
  "platform" → "статус сервісів" | "інтеграція" | "помилка підключення"
  "spreadsheet" → "таблиц" | "excel" | "звіт" | "xlsx"
  "sustainability" → "зведення" | "агрегація" | "підсумки"

Fail-safe: будь-який виняток → DepthDecision(mode="deep", intent="unknown", crew_needed=["ops","iot","platform","spreadsheet","sustainability"], confidence=0.0, reason="classifier_error").


3.2 memory_manager.py

Розташування: crews/agromatrix_crew/memory_manager.py

Відповідальність: завантажити, зберегти і оновити профілі через memory-service. Повна деградація до in-memory fallback.

API:

def load(user_id: str) -> tuple[UserProfile, FarmProfile]
def update(user_id: str, interaction: InteractionContext) -> None

Реалізація (sync, бо run.py sync):

  • HTTP запити через httpx.Client (sync), timeout 2s
  • При недоступності memory-service → використовує _local_cache: dict (процесна пам'ять)
  • _local_cache зберігає до 200 записів, TTL 30 хвилин
  • Факт-ключі в memory-service:
    • user_profile:agromatrix:{user_id}
    • farm_profile:agromatrix:{user_id}
  • user_id для memory-service: stepan:{user_id} (ізоляція від gateway-агентів)

Fail-safe:

try:
    profile = _fetch_from_memory(user_id)
except Exception:
    profile = UserProfile.default(user_id)  # порожній, але валідний
    logger.warning("memory_manager: fallback to default profile user=%s", user_id)

Не блокуючий update:

def update_async(user_id: str, interaction: InteractionContext):
    """Запускає оновлення в threading.Thread (daemon=True), не чекає результату."""
    t = threading.Thread(target=_do_update, args=(user_id, interaction), daemon=True)
    t.start()

3.3 style_adapter.py

Розташування: crews/agromatrix_crew/style_adapter.py

Відповідальність: сформувати prefix для system prompt Степана залежно від профілю.

Вхід: UserProfile, DepthDecision Вихід: str — prefix для system prompt Степана

Рівні expertise:

novice:       мова проста, уникай термінів, давай короткий приклад, 2-3 речення
intermediate: збалансована відповідь, терміни пояснюй в дужках, до 5 речень
expert:       технічна відповідь, скорочений формат, опускай очевидне

Стилі:

brief:          1-2 речення, тільки суть
detailed:       повний опис з контекстом
conversational: живий тон, питання-відповідь, можна питати уточнення

Формат prefix:

"Відповідай на рівні {expertise_label}.
 Стиль: {style_label}.
 Ти знаєш цього користувача: {name or 'агрономе'}.
 Фермерський контекст: {farm_context_summary}."

Fail-safe: будь-який виняток → повертає порожній рядок, Степан працює зі стандартним backstory.


3.4 reflection_engine.py

Розташування: crews/agromatrix_crew/reflection_engine.py

Відповідальність: одноразова пост-обробка відповіді для відповідності профілю і стилю.

Механізм (без LLM для Light mode, з LLM для Deep mode):

Light mode reflection (rule-based):

  • Відповідь > 500 символів і UserProfile.preferred_style == "brief" → обрізати до 3 речень
  • Відповідь містить JSON-фрагменти → замінити на людський текст
  • Відповідь містить технічні ідентифікатори (uuid, trace_id) → прибрати з відповіді користувачу

Deep mode reflection (LLM, one-shot):

Prompt:
"Оціни цю відповідь для {expertise_level} користувача:
[RESPONSE]
Якщо відповідь занадто технічна — спрости.
Якщо занадто довга для {preferred_style} — скороти.
Відповідай тільки виправленою відповіддю."

Anti-recursion guard:

# В reflection_engine.py — module-level flag
_REFLECTING: bool = False

def reflect(response: str, profile: UserProfile, trace_id: str) -> str:
    global _REFLECTING
    if _REFLECTING:
        logger.warning("reflection: recursion guard active, skipping trace=%s", trace_id)
        return response
    _REFLECTING = True
    try:
        return _do_reflect(response, profile, trace_id)
    except Exception:
        return response
    finally:
        _REFLECTING = False

Fail-safe: будь-який виняток → повертає оригінальну відповідь без змін.


3.5 activation_gate.py

Розташування: crews/agromatrix_crew/activation_gate.py

Відповідальність:

  1. Pre-check: блокує подвійний виклик handle_message з того самого контексту
  2. Select: визначає мінімальний набір під-агентів для запуску
  3. Post-check: обмежує глибину делегування

Структура:

_CALL_DEPTH: threading.local  # per-thread, не глобальне

MAX_DEPTH = 1  # Степан може делегувати, але не можна повторно входити в handle_message

def pre_check(trace_id: str) -> bool:
    """Повертає True якщо дозволено продовжувати, False якщо глибина перевищена."""
    depth = getattr(_CALL_DEPTH, "depth", 0)
    if depth >= MAX_DEPTH:
        logger.error("activation_gate: max depth %d reached trace=%s", MAX_DEPTH, trace_id)
        return False
    _CALL_DEPTH.depth = depth + 1
    return True

def release(trace_id: str):
    """Зменшити лічильник після завершення handle_message."""
    _CALL_DEPTH.depth = max(0, getattr(_CALL_DEPTH, "depth", 0) - 1)

def select_crew(decision: DepthDecision, farm: FarmProfile) -> list[str]:
    """Повернути список під-агентів для запуску."""
    needed = list(decision.crew_needed)
    # Видалити IoT якщо FarmProfile.active_integrations не має iot
    if "iot" in needed and not farm.has_iot_integration:
        needed.remove("iot")
    # Видалити spreadsheet якщо не запит до таблиць
    if "spreadsheet" in needed and "spreadsheet" not in decision.intent:
        needed.remove("spreadsheet")
    return needed if needed else []

4. Структура UserProfile JSON

{
  "_version": 1,
  "_fact_key": "user_profile:agromatrix:{user_id}",
  "user_id": "tg:123456789",
  "agent": "agromatrix",
  "name": "Іван",
  "expertise_level": "intermediate",
  "preferred_language": "uk",
  "preferred_style": "conversational",
  "last_seen": "2026-02-24T10:00:00Z",
  "interaction_count": 42,
  "known_intents": [
    "plan_day",
    "show_critical_tomorrow",
    "iot_status"
  ],
  "context_notes": [
    "has_farmos_access",
    "uses_thingsboard",
    "prefers_short_answers"
  ],
  "farm_profile_ref": "farm_profile:agromatrix:{user_id}",
  "recent_topics": [
    {"intent": "plan_day", "ts": "2026-02-24T09:00:00Z"},
    {"intent": "iot_status", "ts": "2026-02-23T18:00:00Z"}
  ],
  "operator": false,
  "updated_at": "2026-02-24T10:00:00Z"
}

Поля та семантика:

Поле Тип Опис
expertise_level enum novice / intermediate / expert; оновлюється автоматично після 10+ взаємодій
preferred_style enum brief / detailed / conversational
interaction_count int лічильник всіх взаємодій для авто-підвищення рівня
known_intents list[str] унікальні intents, накопичуються; use для FarmProfile автодоповнення
context_notes list[str] вільні мітки, збагачуються під час взаємодій
recent_topics list[{intent, ts}] останні 10 тем (для cold-start relief)
operator bool чи є цей user оператором (AGX_OPERATOR_IDS); read-only у memory

5. Структура FarmProfile JSON

{
  "_version": 1,
  "_fact_key": "farm_profile:agromatrix:{user_id}",
  "user_id": "tg:123456789",
  "farm_name": "Ферма Калинівка",
  "field_ids": ["field:north-01", "field:south-02"],
  "crop_ids": ["crop:wheat-winter", "crop:corn-hybrid"],
  "active_integrations": ["farmos", "thingsboard"],
  "seasonal_context": {
    "current_phase": "growing",
    "active_operations": ["irrigation", "monitoring"],
    "hemisphere": "north",
    "approximate_month": 2
  },
  "iot_sensors": {
    "has_iot_integration": true,
    "sensor_types": ["soil_moisture", "temperature"],
    "last_alert": null
  },
  "typical_intents": ["plan_day", "iot_status", "plan_vs_fact"],
  "alert_thresholds": {
    "soil_moisture_min": 20.0,
    "temperature_min": -5.0,
    "temperature_max": 38.0
  },
  "dict_pending_count": 0,
  "updated_at": "2026-02-24T10:00:00Z"
}

Поля та семантика:

Поле Тип Опис
field_ids list[str] заповнюються під час нормалізації терміну tool_dictionary
crop_ids list[str] аналогічно
active_integrations list[str] визначають які crew_agents потенційно потрібні
seasonal_context object підказки для планування і класифікатора глибини
iot_sensors.has_iot_integration bool ключ для activation_gate: чи включати IoT агента
typical_intents list[str] акумулюються; використовуються для Light/Deep розмежування
dict_pending_count int кеш кількості pending термінів для оператора
alert_thresholds object якщо IoT дані виходять за поріг → auto-trigger Deep mode

6. Коли і як оновлюється профіль

UserProfile

Подія Що оновлюється Коли
Будь-яка взаємодія last_seen, interaction_count, recent_topics Завжди, після відповіді
Новий intent known_intents.append(intent) Якщо intent не порожній
interaction_count >= 10 і всі intents — "planning" expertise_level → intermediate При update
interaction_count >= 30 і є технічні intents expertise_level → expert При update
Оператор надіслав /profile set style brief preferred_style Одразу
FarmProfile змінений farm_profile_ref sync При update

FarmProfile

Подія Що оновлюється Коли
tool_dictionary.normalize успішний field_ids, crop_ids При нормалізації
Новий інтент з IoT active_integrations, iot_sensors.has_iot_integration При Deep mode
Новий інтент з spreadsheet active_integrations.append("spreadsheet") При Deep mode
Оператор /farm update phase=sowing seasonal_context.current_phase Одразу
dict_review.stats() dict_pending_count При ops_mode load

7. Тригери Deep mode

Автоматичні (depth_classifier):

Тригер Умова
Планування текст містить DEEP_PATTERNS["planning"]
Мультипольова операція DEEP_PATTERNS["multi_ops"]
IoT аномалія DEEP_PATTERNS["iot_alert"] АБО IoT дані з alert_thresholds порушені
Аналіз план/факт DEEP_PATTERNS["analysis"]
Запис у farmOS DEEP_PATTERNS["recording"]
Низька впевненість confidence < 0.4 після класифікації
Нові терміни tool_dictionary normalization повернув pending items
Перша взаємодія interaction_count == 0 (невідомий користувач)

Примусові (env/flag):

Тригер Механізм
AGX_FORCE_DEEP=1 env в контейнері (тестування)
Текст починається з --deep парситься в handle_message before classify
Оператор вручну operator_commands + flag в trace

8. Тригери запуску під-команди (активація crew_agent)

Crew Agent Тригер (keyword or FarmProfile) Light може обійтись?
ops "запиши", "внеси", "зафіксуй", "farmOS" Ні
iot "датчик", "вологість", "температура" + has_iot_integration=true Ні
platform "статус", "перевір сервіс", "інтеграція впала" Іноді (кешований статус)
spreadsheet "таблиця", "excel", "звіт", "xlsx" Ні
sustainability "зведення", "агрегація", "підсумки по сезону" Ні
всі одночасно intent == "general" без профілю (fallback) Ні

9. Ситуації, що залишаються Light mode

Ситуація Чому Light Хто відповідає
Привітання будь-якого типу Не потребує даних з farmOS/IoT Степан з style_adapter
"Дякую", "ок", "зрозумів" Підтвердження, не запит Степан (2 слова)
/whoami, /pending, /approve Operator commands operator_commands.py (незмінний)
"Що ти вмієш?" Довідка Степан з профілем
Повторне питання тієї ж теми (< 5 хв) recent_topics cache Степан з кешем контексту
Simple status якщо кеш свіжий FarmProfile.seasonal_context свіжий (< 1 год) Степан без crew
Повідомлення < 4 слів Незрозумілий запит → уточнення Степан питає
Текст не пов'язаний з агрономією Off-topic filter Степан ввічливо redirects

10. Принцип fail-safe

Ієрархія деградації:

Нормальна робота:
    memory-service online → профілі загружені → класифікатор → вибір crew → рефлексія

Деградація 1 (memory недоступна):
    fallback UserProfile.default() → класифікатор без персоналізації → crew → рефлексія skip

Деградація 2 (classifier помилка):
    force Deep mode → всі crew → рефлексія skip

Деградація 3 (частина crew агентів впала):
    інші crew продовжують → Степан синтезує з частковими даними
    run_task_with_retry вже існує (max_retries=2)

Деградація 4 (OpenAI недоступний):
    handle_stepan_message повертає "Помилка обробки. trace_id=..."
    gateway вже обробляє це (stepan_disabled fallback)

Правила:

  • Жодний модуль не може кинути виняток, що зупинить handle_message
  • Кожен новий модуль wrap-ується в try/except з fallback
  • reflection_engine завжди має повертати str, ніколи None або виняток
  • memory_manager.update_async daemon=True — смерть процесу не втрачає відповідь
  • При будь-якій помилці profile: interaction_count=0, expertise_level="intermediate", preferred_style="conversational"

11. Як не створити нескінченну рекурсію

Три незалежні шари захисту:

Шар 1 — activation_gate (threading.local counter)

handle_message:
  pre_check() → depth becomes 1
  ... робота ...
  release()   → depth back to 0

Якщо under_running_task викликає handle_message:
  pre_check() → depth == 1 → MAX_DEPTH reached → return error response

threading.local — ізоляція per-thread, не заважає паралельним викликам з різних чатів.

Шар 2 — reflection_engine._REFLECTING flag

  • Глобальний (module-level) булевий прапорець
  • Встановлюється в True перед LLM-рефлексією, скидається в finally
  • Якщо рефлексія викличе щось що знову зайде в рефлексію → миттєво скидається

Шар 3 — Архітектурна заборона

  • Під-агенти (ops, iot, platform, spreadsheet, sustainability) мають allow_delegation=False
  • Жоден агент не має знань про handle_message або run.py
  • depth_classifier, style_adapter, memory_manager — pure functions, без CrewAI, без LLM
  • Тільки reflection_engine (Deep mode) і фінальна задача Степана — LLM-виклики

12. Де саме інтегрувати

12.1 crews/agromatrix_crew/run.py

Змінити:

# Новий imports (top)
from crews.agromatrix_crew.depth_classifier import classify, DepthDecision
from crews.agromatrix_crew.memory_manager import load_profiles, update_async
from crews.agromatrix_crew.style_adapter import build_prefix
from crews.agromatrix_crew.reflection_engine import reflect
from crews.agromatrix_crew.activation_gate import pre_check, release, select_crew

# handle_message:
# 1. pre_check (перше, до всього)
# 2. load_profiles (до classify)
# 3. classify (до побудови агентів)
# 4. if light → stepan_only_response
# 5. if deep → activation_gate.select_crew → run selected
# 6. reflect (після відповіді)
# 7. update_async (не блокуючий, daemon thread)
# 8. release (в finally)

Зберегти:

  • Весь route_operator_command / route_operator_text (operator_commands не змінюємо)
  • tool_dictionary.normalize_from_text + pending check (залишається до classify)
  • run_task_with_retry (залишається для Deep mode)
  • audit_event (залишається, розширюємо depth/mode в event)
  • farmos_ui_hint (залишається)

НЕ змінювати:

  • Сигнатуру handle_message(text, user_id, chat_id, trace_id, ops_mode, last_pending_list)
  • Формат повернення (str, valid for JSON parse by http_api)

12.2 crews/agromatrix_crew/operator_commands.py

Додати команди:

/profile          → показати UserProfile (user_id, expertise, style, last_seen, interaction_count)
/profile set <k>=<v>  → оновити expertise_level або preferred_style
/farm             → показати FarmProfile (коротко: поля, культури, інтеграції, сезон)
/farm update <k>=<v>  → оновити seasonal_context.current_phase, порогові значення

Зберегти без змін:

  • /whoami, /pending, /approve, /reject, /apply_dict, /pending_stats
  • is_operator() — не змінювати
  • route_operator_command() — розширити case, не переписувати
  • route_operator_text() — залишити

OPERATOR_COMMANDS set — додати "profile", "farm".

12.3 gateway-bot/http_api.py

Мінімальні зміни:

  • Додати env AGX_FORCE_DEEP → якщо "1", передавати в metadata або через handle_message (ops_mode вже є, можна додати depth_override parameter)
  • Нічого більше не змінювати. handle_message вже приймає text, user_id, chat_id, trace_id, ops_mode.

Не змінювати:

  • Маршрутизацію оператор/не-оператор (вже виправлена попереднім патчем)
  • STEPAN_IMPORTS_OK logic
  • doc_context logic

12.4 memory-service

Не змінювати сервіс. Використовуємо існуючий /facts/upsert і /facts/get.

Нові fact-ключі:

user_profile:agromatrix:{user_id}   → UserProfile JSON (fact_value_json)
farm_profile:agromatrix:{user_id}   → FarmProfile JSON (fact_value_json)

memory_manager.py в crews викликає memory-service по HTTP (sync httpx), URL з env:

AGX_MEMORY_SERVICE_URL=http://memory-service:8000

13. Схема файлів після впровадження

crews/agromatrix_crew/
├── __init__.py
├── run.py                        ← ЗМІНЕНО (нові модулі вмонтовані)
├── audit.py                      ← без змін
├── operator_commands.py          ← РОЗШИРЕНО (/profile, /farm)
│
├── depth_classifier.py           ← НОВИЙ
├── memory_manager.py             ← НОВИЙ
├── style_adapter.py              ← НОВИЙ
├── reflection_engine.py          ← НОВИЙ
├── activation_gate.py            ← НОВИЙ
│
├── agents/
│   ├── stepan_orchestrator.py    ← backstory розширюється від style_adapter
│   ├── operations_agent.py       ← без змін
│   ├── iot_agent.py              ← без змін
│   ├── platform_agent.py         ← без змін
│   ├── spreadsheet_agent.py      ← без змін
│   └── sustainability_agent.py   ← без змін
│
├── tasks/
│   ├── intake_and_plan.py        ← без змін (лише для compatibility)
│   ├── execute_ops.py            ← без змін
│   ├── execute_iot.py            ← без змін
│   ├── execute_spreadsheets.py   ← без змін
│   └── reporting.py              ← без змін
│
└── tools/
    └── __init__.py               ← без змін

14. Порядок впровадження (поетапно)

Фаза 1 — Foundation (без змін у run.py)

  1. memory_manager.py — реалізувати, написати unit-тест з mock memory-service
  2. depth_classifier.py — реалізувати rule-based, написати тести по кожному патерну
  3. activation_gate.py — реалізувати pre_check/release/select_crew, тест на рекурсію

Фаза 2 — Light mode 4. style_adapter.py — реалізувати три рівні і три стилі 5. Модифікувати run.py: вставити Light mode path (якщо light → пропустити всі crew) 6. Smoke-test: надіслати "привіт" → відповідь без crew

Фаза 3 — Deep mode + Activation Gate 7. Модифікувати run.py: Deep mode використовує select_crew, не всіх 5 агентів 8. Тест: "сплануй тиждень" → ops + sustainability, але не iot (якщо has_iot=false)

Фаза 4 — Reflection + Profiles 9. reflection_engine.py — rule-based Light reflection (без LLM) 10. Оновити operator_commands.py/profile, /farm 11. E2E тест: 3 взаємодії → перевірка UserProfile накопичення

Фаза 5 — Deep reflection (LLM) 12. Додати LLM-рефлексію тільки для Deep mode 13. Тест на рекурсію: перевірити _REFLECTING flag спрацьовує


15. Метрики успіху

Метрика Ціль
% запитів у Light mode (грітинги + прості) > 30% від загального трафіку
Середній час відповіді Light mode < 2s (без crew launch)
Середній час відповіді Deep mode < 30s (тільки потрібні crew)
% запитів що запускають тільки 1-2 crew > 50% від Deep запитів
Оператор /profile — відображає дані 100% (якщо memory-service online)
Fallback без memory-service Gateway не падає (fail-safe)
Рекурсивний виклик handle_message 0 (activation_gate блокує)

16. Відкриті питання (потрібно вирішити перед реалізацією)

  1. Sync vs async memory_manager: run.py sync, але memory-service async-HTTP. Поточне рішення — sync httpx.Client. Альтернатива: asyncio.run() в окремому thread. Потребує рішення.
  2. UserProfile.expertise_level auto-upgrade: поріг 10/30 взаємодій — достатньо? Або враховувати час між взаємодіями?
  3. reflection LLM model: який LLM для рефлексії — той самий GPT-4, або дешевший GPT-3.5/Mistral? Вплив на latency та cost.
  4. FarmProfile cold-start: перша взаємодія — profile порожній. Deep mode завжди? Або запитати у користувача дані ферми?
  5. Multi-user farm: кілька операторів з однієї ферми — один FarmProfile чи кілька? Зараз user_id-based.
  6. Operator profile isolation: оператор і звичайний користувач можуть мати одне user_id якщо оператор пише без оператор-чату. Чи потрібна окрема UserProfile для ops-mode?

Документ готовий до review. Після погодження — розпочинати Фазу 1.