ADR 0098 — Согласованные пределы транспорта и эффективное окно сжатия
- Статус: Принят
- Дата: 2026-08-19
- Контекст: исправление
413 request entity too largeв PR (найдено в реальном использовании), RN-412
Контекст
Gate QA/SecOps умирал с 413 request entity too large на легитимных PR.
Комментарии, разбросанные по коду (apps/engine/config/runtime.exs,
apps/engine/lib/engine/actions/terminal_executor.ex, docker/docker-compose.yml),
приписывали ошибку LLM-PROVIDER. Расследование подтвердило, что это
приписывание было ошибочным: Engine.Agents.FalhaDeTurno.origem({413, _})
уже классифицировал сбой как "codigo", а не "modelo", — и
классификатор был прав, просто никто не прошёл по следу до
конца.
У настоящей причины два независимых конца, и оба должны закрыться вместе — исправление лишь одного откладывает переполнение, а не решает его:
- api никогда не настраивала предел тела Express.
NestFactory.createвapps/api/src/main.tsне трогал JSON-парсер, поэтому действовало значение по умолчанию Express: 100 KB. Phoenix (engine) принимает тела до 8 MB. В самом тяжёлом направлении транспорта — engine → api, конкретноPOST /internal/sessions/:sessionId/llm-turn, который получает ВСЮ ИСТОРИЮ разговора на каждой итерацииToolLoop, — api была самым узким местом в 80 раз, и никто этого не решал: это было отсутствие конфигурации, а не выбор. - Сжатие контекста в engine было структурно
недостижимо до переполнения.
Engine.Harness.ContextManager.Defaultрешает сжимать, когда оценка токенов превышаетthreshold * context_window. Два дефекта отодвигали этот триггер далеко за предел транспорта:estimate/1суммировал лишь полеcontentсообщений. СообщенияassistantсtoolCalls(формат вызова инструмента) имеют пустойcontent— аргументы tool call, которые ЯВЛЯЮТСЯ реальными байтами в HTTP-теле, считались как ~ноль токенов.- Окно сжатия использовало лишь
context_window— 128.000 токенов у пяти агентов gate/dev (qa_automacao_agent.ex,qa_performance_seguranca_agent.ex,qa_estrategia_agent.ex,appsec_agent.ex,dev_agent_server.ex). Приthreshold: 0.7это даёт сжатие примерно на ~350 KB оценочного payload — далеко за любым разумным пределом транспорта, и ещё хуже, когда сама оценка уже занижала.
При gate в 60 итераций (предел агентов выполнения/gate) и трёх- четырёх tool results по 32 KiB (ИНДИВИДУАЛЬНЫЙ предел, уже закрытый RN-150) накопленное тело превышало 100 KB задолго до того, как сжатие хотя бы рассматривало возможность действовать.
Решение
Оба конца закрываются одним исправлением, намеренно вместе:
apps/api/src/main.tsначинает явно настраивать предел JSON-парсера (app.useBodyParser('json', { limit })), читаемый изAPI_JSON_BODY_LIMITсо значением по умолчанию10mb— запас поверх 8 MB, которые Phoenix уже принимает, без необходимости нового deploy при изменении предела engine.Engine.Harness.ContextManager.Defaultполучает:estimate/1считаетcontentКАЖДОГО сообщения ПЛЮС JSON- сериализациюtoolCallsсообщенийassistant— эвристика байтов-на-токен ТА ЖЕ, что у приближённого токенизатора (Engine.Harness.Tokenizer.bytes_per_token/0, новая публичная функция, чтобы не дублировать константу).- ЭФФЕКТИВНОЕ окно сжатия становится
min(context_window, teto_de_transporte), где предел транспорта — ОДНА новая конфигурация (transport_max_body_bytes, по умолчанию 8 MiB — предел СОБСТВЕННОГО транспорта engine, не повторяемый в пяти файлах агентов, которые продолжают объявлятьcontext_window: 128_000, потому что это описывает МОДЕЛЬ, а не транспорт). - Разрез между старыми (суммаризируемыми) и недавними
(сохраняемыми) сообщениями начинает соблюдать ГРАНИЦУ ИТЕРАЦИИ
ToolLoop(group_by_iteration/1) — сообщениеassistantсtoolCallsи отвечающие на негоrole: "tool"всегда идут вместе на одну сторону разреза. Разрез посередине сломал бы протокол tool-use provider (результат инструмента без соответствующего вызова в истории).
Общее правило, которое фиксирует этот ADR: сжатие должно срабатывать ДО того, как тело переполнит реальный HTTP-предел, а не до того, как модель "забудет" своё окно. Предел транспорта, существующий лишь неявно (значение по умолчанию библиотеки), — не предел, а сбой, ждущий подходящего payload.
Последствия
- Значение
transport_max_body_bytes(8 MiB) — объявленная конфигурация, не откалиброванная по реальному трафику, — то же мерило честности, которому уже следуют веса гибридного поиска (RAG) и пределыsearch_workspace(RN-150): регулируемая отправная точка, а не окончательное число. - Поднять лишь предел api, не исправив оценку engine (или наоборот), — значит вновь внести дефект в иной форме: либо тело снова растёт без тормоза до большего предела, либо сжатие начинает действовать слишком рано и суммаризировать контекст, который ещё поместился бы в транспорт. Оба конца родились в одном коммите.
dev.awaiting_gateкак состояние dev agent на практике должно стать реже (gate больше не умирает от 413 на легитимном payload), но RN-412 также расширяетDEV_PENDING_TYPES, чтобы удерживать сессию в этом состоянии, — эшелонированная защита, а не расчёт на то, что этот ADR закроет 100% случаев медленного/зависшего gate по иной причине.