0.167.12

Fixed

  • Живые role=tool сообщения в БД: инструментальный контекст теперь переживает границу хода ( — P0/blocker; реализация ADR 2026-08-20-live-tool-messages). Корневой дефект контекстного слоя: инструментальная работа агента не переживала границу хода. Внутри хода ReAct-цепочка в spec.Messages была корректной, но в БД писалась под служебными ролями tool_call_history (превью, обрезанные до 2000 байт) и tool_iteration; обе роли исключались из dialogRoleFilter, обратно не парсились нигде. Следствия: агент перечитывал файлы, перезапускал команды, повторял отклонённые подходы, Todo-лист был невидим (обещание системного промпта «You remember it through your previous todo_write calls» не выполнялось). Фикс: миграция 106 tool_messages_is_error (оба каталога, SQLite+PG) — колонка messages.is_error BOOLEAN NOT NULL DEFAULT 0/FALSE + индекс idx_messages_agent_role_id(agent_id, role, id) + ANALYZE; ChatMessage.IsError в store-слое (все SELECT/INSERT/scan в sqlite.go и postgres.go; дублированные списки колонок консолидированы в константы msgCols/pgMsgCols); runner.go персистит живую цепочку синхронно — assistant с заполненной tool_calls (JSON) ДО выполнения инструментов и role=tool на каждый результат с tool_call_id/tool_name/is_error целиком (до MaxToolResultChars; 2000-байтовые превью устранены); защита от персиста отменённого хода (ADR 2026-07-04/#254) сохранена в обеих точках, orphan-assistant при отмене вычищается repairToolPairs на чтении; записи tool_call_history из runner.go и streamHook.AfterIteration удалены (tool_iteration — артефакт UI — не тронут); чтение — новый buildHistoryFromDB (actor.go) переносит is_error и парсит tool_calls, мёртвый skip-list удалён (роли уже фильтруются SQL); uiRoleFilter (оба стора) дополнительно скрывает промежуточные assistant с пустым content и непустыми tool_calls — рендер чата в WebUI не меняется; дубль-персист ask_user assistant удалён (runner пишет сам; findPendingAskUserCore устойчив к множественным tool_calls); tool_log переписан на чтение живой цепочки (привязка результатов по ToolCallID к родительской записи) с сохранением legacy-режима tool_call_history; провал инструмента (включая timeout, panic в parallel-батче, rate-limit, streaming-ошибку) помечается IsError=true и включается в результаты — assistant tool_call больше не остаётся без ответа (невалидная пара для LLM API) и LLM видит причину провала в текущем и следующем ходу.
  • is_error хранится в БД ( — P1, БД-часть; round-trip конвертеров — v0.167.10). Колонка messages.is_error (миграция 106) проходит полный цикл: runner.go пишет флаг с каждым role=tool результатом, buildHistoryFromDB читает в Message.IsError — доезжает до Anthropic tool_result.is_error / <tool_error> / extra-поля openai_compat (конвертеры зафиксированы в v0.167.10). Тесты: round-trip через реальную SQLite-БД (TestAddMessage_IsErrorRoundTrip), персист с is_error из runner (TestRun_PersistsToolIsError), диалоговое окно возвращает флаг (TestGetRecentDialogMessages_ToolRoleAndIsError).
  • Токен-бюджет истории включён: pruneHistoryByTokens + adjustCutForToolGroups перестали быть мёртвым кодом ( — P1). Единственным ограничителем истории был счётчик сообщений: одно tool-сообщение на 40K токенов проходило без сопротивления, а рассчитанный ComputeBudget().History попадал только в отладочный лог. После включения живых role=tool (см. выше) отсутствие жёсткого токен-гейта стало критичным. Фикс: новый applyHistoryBudget (context.go) вызывается в реальном пути handleInbound — порядок уровней: verbatimCount-окно → FastTrimToolResultsapplyHistoryBudget (жёсткий гейт по ComputeBudget(contextMaxTokens).History с атомарностью tool-групп и защитой последнего user-сообщения) → repairToolPairs (вычищает разорванные пары на границе среза); на вырожденном входе (гигантский tool-хвост сам не влезает в бюджет) — обрезка хвоста до бюджета с повторным гейтом (copy-on-write, вход не мутируется), последнее user-сообщение не обрезается никогда; расхождение источников контекста устранено — ContextBuilder.ContextMaxTokens заполняется из resolveContextMaxTokens (единая точка actor-слоя), agentBudget использует его. Тесты: TestApplyHistoryBudget_TrimsToBudget (обрезка до бюджета + защита user), _SmallHistoryUntouched, _NoPanicOnDegenerateInput (бюджет ≤0, гигантское tool-сообщение), _ToolGroupsAtomic (нет orphan tool_call_id).