Перейти к основному содержимому

ADR 0019 — Разблокировать реальный DevAgent: запись, терминал, ADR по модулю и бюджет по проекту

  • Статус: принят
  • Дата: 2026-07-25
  • Фаза: 4a (закрытие отклонений реального DevAgent)

Контекст​

Реальный DevAgent (ADR 0012) был завершён на бумаге — ToolLoop в worktree, ReportDone, требующий зелёной suite, блокировка с диагнозом, бюджет на task, — но ни разу не прогонял критерий приёмки с настоящим LLM. При попытке аудит нашёл причину: он не мог ничего реализовать.

Решения​

1. DevAgent был заблокирован в ДВУХ местах (центральная находка)​

  • write_file от dev-* не был в whitelist WriteFilePolicy (по умолчанию там был только 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_builder dev (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. Это решение пользователя — платформа готова к обоим.