fix(ai-chat): защита от петель агента — lockdown под тоггл, детектор деградации, бюджет шагов (#444)
Третий класс петли (инцидент 2026-07-10): ран упёрся в 20-шаговый кап, спалив все шаги на чтение; на 20-м шаге final-step lockdown отнял инструменты (toolChoice:'none') посреди незаконченной работы → модель выродилась в текст-повтор («loadTools.» ×20416, 255КБ). Пакет защит по дизайну владельца: - MAX_AGENT_STEPS 20→50; спеки выводятся из константы (нет захардкоженных 19/20). - Final-step lockdown под env-тогглом AI_CHAT_FINAL_STEP_LOCKDOWN (дефолт OFF, по образцу AI_CHAT_DEFERRED_TOOLS): OFF — инструменты доступны на всех шагах + мягкий финальный нудж; ON — легаси toolChoice:'none'+FINAL_STEP_INSTRUCTION. Спеки параметризованы по тогглу. .env.example задокументирован. - Пустой ход (все шаги без текста, шаги исчерпаны) получает синтетический маркер-текст — виден в UI и реплее. - Детектор токен-деградации в onChunk (единственная защита от болтовни, БЕЗ maxOutputTokens — tool-аргументы это выходные токены): чистые правила (≥25 одинаковых строк ИЛИ периодический хвост), при срабатывании abort через внутренний AbortController ∪ effectiveSignal (AbortSignal.any), финализация в onAbort: усечение хвоста, ai_chat_runs.error=Output degeneration detected, лизы MCP/снапшоты освобождаются (существующий lifecycle). - Предупреждение о бюджете шагов на MAX-6…MAX-2 с убывающим N. - loadTools-описание и преамбула каталога явно говорят, что CORE-тулы всегда активны (список из CORE_TOOL_KEYS динамически). Внутренний цикл: 2 прохода. Ревью нашло data-loss-риск: правило периодичности детектора ложно срабатывало на markdown-разделителях/setext-подчёркиваниях/ хвостовых пробелах (монохар-хвост p-периодичен при ЛЮБОМ p → ложный abort с пометкой error и усечением). Починка: отдельная монохар-проверка (порог 60, выше любого реального разделителя) + требование ≥2 различных символов в периодическом блоке при p≥2. Реальный loadTools-цикл (период ~10) ловится. Мутационно: 59 одинаковых — не флаг, 60 — флаг; loadTools×20416 — флаг. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -292,6 +292,24 @@ export class EnvironmentService {
|
||||
return enabled === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Final-step lockdown for the in-app agent loop (#444). When ON (legacy), the
|
||||
* LAST allowed step forces a text-only answer: tools are stripped
|
||||
* (toolChoice:'none') and a synthesis instruction is appended. Defaults to OFF:
|
||||
* stripping the tools mid-work triggered a token-loop degeneration incident
|
||||
* (the model, robbed of its tools on the final step, emitted a 255KB block
|
||||
* repeating a single token). With the toggle OFF the last step keeps its tools
|
||||
* and gets only a SOFT nudge to finish with a text summary; the universal
|
||||
* anti-babble guard is the token-degeneration detector instead. Enable this
|
||||
* only for a model that does NOT reliably end its turns with a text answer.
|
||||
*/
|
||||
isAiChatFinalStepLockdownEnabled(): boolean {
|
||||
const enabled = this.configService
|
||||
.get<string>('AI_CHAT_FINAL_STEP_LOCKDOWN', 'false')
|
||||
.toLowerCase();
|
||||
return enabled === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumable SSE transport for durable agent runs (#184 phase 1.5). When
|
||||
* enabled, a run tees its SSE frames into the in-memory run-stream registry so
|
||||
|
||||
Reference in New Issue
Block a user