# 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`** ```python @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:** ```python 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:** ```python 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:** ```python 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:** ```python # В 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: обмежує глибину делегування **Структура:** ```python _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 ```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 ```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` **Змінити:** ```python # Новий 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 = → оновити expertise_level або preferred_style /farm → показати FarmProfile (коротко: поля, культури, інтеграції, сезон) /farm update = → оновити 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.*