Автономность мелких моделей: OUTPUT DISCIPLINE, milestone-todo, перехват финала хода
Два последовательных захода над одной проблемой — мелкие модели (12–30B) в navi_code
теряют автономность на трудных шагах: «остановился поболтать» вместо действия и
слишком большое расстояние между пунктами плана.

Заход 1 (З1–З3):
- З1: OUTPUT DISCIPLINE в системном промпте navi_code — «act, don't announce»,
  без few-shot антипримеров. Контракт хода: ответ без tool_calls = конец хода, поэтому
  объявление намерения текстом убивает автономность; правила заставляют вызывать
  инструмент в том же ходе.
- З2: плоский todo + метка группы milestone + декомпозиция. _parse_plan_steps
  возвращает list[tuple[milestone, text]]; milestone — метка группировки (не сущность,
  без статуса), «done» вычисляется при рендеринге; подшаги = больше плоских шагов
  (без вложенности). TUI side-panel группирует по milestone (плоский фолбэк при пустом
  milestone). Plan depth: max 15→20 + правило декомпозиции.
- З3: adaptive re-plan «длинный шаг» — nudge «разбей шаг» при in_progress ≥ порога
  итераций без смены todo (порог 4, раньше общего anti-stall warning на 8).

Заход 2 (шаги 1–3, после cloud-теста 31b vs 12b):
Корневая структурная причина: весь спасательный механизм (anti-stall warning с явным
предложением reflect, adaptive re-plan) живёт только внутри tool-цикла — nudge
инжектируется в pre_turn *следующей* итерации, которой при «остановился поболтать»
нет (ход закрылся по return до post_turn). 31b застревала через «продолжаю
tool-итерации» → дожала до warning → спаслась; 12b — через «замолчала текстом» → мимо
всех nudge.

- Шаг 1: перехват финала хода. Если модель выдала bare-text, но в todo есть открытые
  шаги (pending/in_progress) и лимит не исчерпан — НЕ эмитить StreamEnd, а сохранить
  ассистентский текст в session.context, поставить системный nudge и continue
  (без StreamEnd, без workers — консистентно с multi-iteration tool-турами). Счётчик
  final_interceptions на AgentTurnContext, лимит final_intercept_limit (default 2),
  эскалация жёсткости (мягкий → «second stop»). has_open_steps в todo.py: пустой
  todo → False (защита casual-сообщений), failed/skipped терминальны. Профильные
  флаги final_intercept_enabled/limit в base.py + loader + admin.
- Шаг 2: жёсткий reflect-триггер в промпте — «~3 tool attempts on the same step
  without progress → call reflect IN THIS TURN (tool call, not reasoning aloud)».
- Шаг 3: открыть replan для застревания — «call replan when reflect showed the whole
  approach is dead (not one failed step, but the approach itself)».

Тесты: 874 passed, 1 skipped. Новые — has_open_steps (5), final intercept (5),
milestone-группировка, adaptive long-step nudge, парсер шагов с milestone-маркером.

cloud: navi_code model → gemma4:31b-cloud для тестирования догадки (31b признала
застревание, 12b — нет); .env cloud-host уже gitignored.

Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 05c420d commit 2305129e120297d35f275087d0bd37b3efd70a02
@Eugene Sukhodolskiy Eugene Sukhodolskiy authored 15 hours ago
Showing 19 changed files
View
clients/terminal/tui/widgets/todo_list.py
View
navi/api/routes/admin.py
View
navi/core/agent.py
View
navi/core/agent_run_context.py
View
navi/core/anti_stall.py
View
navi/core/events.py
View
navi/core/planning.py
View
navi/profiles/base.py
View
navi/profiles/loader.py
View
navi/profiles/navi_code/config.json
View
navi/profiles/navi_code/system_prompt.txt
View
navi/tools/todo.py
View
tests/clients/test_todo_list.py
View
tests/integration/test_api_routes.py
View
tests/unit/core/test_agent.py
View
tests/unit/core/test_anti_stall.py
View
tests/unit/core/test_planning.py
View
tests/unit/profiles/test_base.py
View
tests/unit/tools/test_todo.py