ADR 0019 — Разблокировать реальный DevAgent: запись, терминал, ADR по модулю и бюджет по проекту
- Статус: принят
- Дата: 2026-07-25
- Фаза: 4a (закрытие отклонений реального DevAgent)
Контекст
Реальный DevAgent (ADR 0012) был завершён на бумаге — ToolLoop в worktree,
ReportDone, требующий зелёной suite, блокировка с диагнозом, бюджет на task,
— но ни разу не прогонял критерий приёмки с настоящим LLM. При попытке
аудит нашёл причину: он не мог ничего реализовать.
Решения
1. DevAgent был заблокирован в ДВУХ местах (центральная находка)
write_fileотdev-*не был в whitelistWriteFilePolicy(по умолчанию там был толькоecho), поэтому становилсяproposed_action— а уwrite_fileнет исполнителя в api, то есть даже одобренный он не писал.- У
terminalне было ни одного правила:decide()падал вrequire_approval, и действие рождалось ожидающим.
Поскольку ReportDone разрешает PR лишь после terminal с exit 0 в
истории, зелёная suite была недостижима: каждая task заканчивалась блокировкой по
пределу итераций. Принуждение дисциплины завершения было верным; не хватало
того, чтобы агент мог действовать.
Исправления:
WriteFilePolicyполучает префиксы агентов (:write_file_agent_prefixes, по умолчанию["dev-"]): dev пишет по любому пути внутри своего корня. Это безопасно, потому что его корень ЕСТЬ worktree, аWorkspaceFiles.write_file/3уже блокирует обход — песочница — это worktree, а не список префиксов путей.DEV_TERMINAL_ALLOW_PATTERNS(домен api) засевается вpermissions.jsonпри активации. УЗКИЕ шаблоны тестов/сборки (Terminal(pnpm test),Terminal(npm test),Terminal(mix test), …), с переопределением в DTO. Намеренно неagent_autonomy auto_approveдляterminal: это разрешило бы любую команду. Будучи правилом файла,denyпродолжает побеждать,BUILTIN_DENY_PATTERNSостаются активными, а составная команда требует совпадения каждого сегмента.
2. ADR, отфильтрованные по модулю
open_adr_pr получает modules?: string[] (инструмент Arquiteto его заполняет).
GetDevTaskContextUseCase фильтрует по модулю dev; ADR без modules
сквозной и входит всегда — что включает весь массив до появления поля, без
миграции данных. Без указанного модуля фильтра нет, что сохраняет gates
QA/SecOps, переиспользующие тот же контекст.
3. Приоритет отбрасывания в prompt был инвертирован
estado_tarefa был [story, task | adrs], а PromptAssembler.fit_units/3
отбрасывает с головы: при давлении контекста story (RF/RNF/DoD)
приносилась в жертву, а ADR выживали — противоположность того, что нужно тому, кто будет
реализовывать. Теперь это adrs ++ [story, task].
4. Бюджет на task, сохраняемый в проекте
Был лишь параметром активации, живущим в engine.dev_agent_states: повторная активация без
его передачи молча возвращала значение по умолчанию. Новая колонка
projects.task_budget_micros; разрешение параметр → проект → значение по умолчанию, с
сохранением, когда он приходит в параметре.
5. Артефакт task_blocked
Блокировка выдавала лишь событие + флаг в базе. Теперь выдаёт также
artifact.task_blocked, валидируемый ArtifactSchemas, — выдаётся сервером, никогда
через tool call (модель не выбирает объявить, что сдалась).
6. Диагностируемость: ошибка LLM не может стать пустым диагнозом
ToolLoop возвращал {:ok, ctx} как когда модель останавливалась без сигнала,
так и когда вызов provider ПАДАЛ, — и DevAgentServer блокировал
task с диагнозом "". Ctx начинает нести :last_error, и диагноз
различает два случая. Это не теоретически: первое реальное демо умерло на
невидимом Req.TransportError{reason: :timeout}.
7. Таймаут хода LLM
llm_turn использовал значение по умолчанию Req. С локальной моделью ПЕРВЫЙ ход ещё
загружает несколько ГБ весов до первого токена и превышает время. Новый
llm_turn_timeout_ms (по умолчанию 300 с, env LLM_TURN_TIMEOUT_MS).
8. Node в образе engine
Suite управляемого проекта выполняется внутри контейнера engine (именно там
выполняется terminal, в worktree). Без toolchain npm test никогда не даёт exit 0.
Известное ограничение: это не масштабируется на произвольные стеки — настоящий выход —
песочница на проект, вне области Фазы 4a.
Последствия
- Новые тесты:
write_file_policy(dev пишет в корень; обход заблокирован),context_builderdev (RF/RNF/DoD + ADR модуля + AGENTS.md действительно в prompt; ADR отбрасываются раньше story), красная suite до предела с выводом теста в диагнозе, артефактtask_blocked, фильтр ADR по модулю,activate-execution(порядок бюджета, шаблоны терминала, никогдаgit_merge) иlib/execution.tsв web. - UI: заголовок task в панели (раньше только ветка
task-<8hex>), инвалидация backlog через канал (blocked вживую) и кнопка разблокировки.
Результат критерия приёмки (честно)
Вся цепочка была задействована от начала до конца с локальным LLM: атомарный claim,
изолированный worktree, собранный контекст, ход LLM и исход блокировки с
диагнозом и артефактом. Что НЕ закрылось — это 2 зелёные PR, и причина —
модель, а не платформа: qwen2.5-coder:7b не реализует tool calling в
шаблоне Ollama. Проверено напрямую через /api/chat, с stream: false и
корректным payload tools, ответ приходит с tool_calls: null и вызовом,
написанным как JSON в content. Никакая настройка в OllamaProvider этого не исправит.
Закрытие критерия требует модели с настоящим шаблоном tools (llama3.1:8b
или больше) или платного provider. Это решение пользователя — платформа готова
к обоим.