ADR 0018 — NoopDevAgent как постоянный режим выполнения
- Статус: принят
- Дата: 2026-07-25
- Фаза: 4a (валидация инфраструктуры выполнения)
Контекст
NoopDevAgent существовал в Фазе 4a (ADR 0011) как тело :work
DevAgentServer и был заменён реальным агентом в следующей сессии
(ADR 0012). Из-за этого единственным способом задействовать инфраструктуру выполнения
от начала до конца — изолированный worktree, идентичность dev-<modulo>[bot], конвейер
proposed_actions, PR — стал запуск РЕАЛЬНОГО агента, зависящего от
LLM: дорогого, медленного и недетерминированного. Smoke-тест инфраструктуры не
может зависеть от модели.
Эта сессия возвращает Noop, теперь как постоянный режим выполнения, сосуществующий с реальным агентом.
Решения
1. Engine.Dev.AgentIo — общие побочные эффекты
Новый модуль с via/2, emit/3, claim_task/1, propose/3,
propose_commit/2 (идентичность), propose_push/1, propose_pr/3,
persist/1, block_task/3 и worktree_manager/0, извлечёнными из
DevAgentServer без изменения поведения. Оба сервера используют один и тот же
код: Noop, переписывающий worktree/идентичность/конвейер, проверял бы
копию, а не инфраструктуру. dev_agent_server_test.exs (12 случаев) — страховочная
сеть извлечения — прошёл без правки.
2. Engine.Dev.NoopDevAgentServer — режим, а не идентичность
Собственный GenServer, тот же Registry, тот же agent_id (dev-<modulo>) и
то же долговечное состояние, что у реального агента. Поскольку agent_id тот же, автономия и
инструкции, засеянные api, действуют без каких-либо изменений. Цикл: claim →
worktree → тривиальный файл → commit → push → PR → task in_review.
Не открывает gate (QA — LLM-агент): Noop останавливается на PR. {:correct, _}
имеет защитную клаузулу, которая возвращает task с диагнозом вместо обрушения
процесса, — DevAgentServer.correct/3 — это cast в via/2, и он пришёл бы сюда.
3. dev_agent_states.impl — режим долговечен
Новая колонка ("real" по умолчанию). Регидратация выбирает, какой модуль
поднять: без сохранения режима Noop вернулся бы как реальный агент после
перезапуска узла — начав тратить токены, хотя никто не просил.
DevAgentSupervisor.start_agent/7 получает impl; server_for/1 отображает
режим → модуль (всё, что не "noop", попадает в реальный — безопасное значение по умолчанию —
продакшен). Принятие распараллеливания наследует impl
базового агента по той же причине, по которой уже наследовало пределы.
4. Принятие распараллеливания начинает засевать дополнительного субагента
Исправление реального бага: AcceptParallelizationUseCase не создавал строку
agent_autonomy и инструкцию для dev-<modulo>-2. Без автономии decide()
падает в значение по умолчанию require_approval — "принятие одним кликом" превращалось в три
ручных одобрения на task, и критерий приёмки ("видеть третьего
работающим") не закрывался. Теперь засевает инструкцию + auto_approve в тех же
DEV_AUTO_GIT_ACTIONS, что у базового агента, до вызова engine.
git_merge остаётся вне.
5. Bare repos смонтированы RW для engine в Compose
Было git_local_repos:/data/git-repos:ro. С Фазы 4a исполнитель
git_push работает В ENGINE и отправляет ветку worktree в bare repo — при
монтировании только для чтения push умирает с remote unpack failed: unable to create temporary object directory, и PR dev agent никогда не открывается. :ro был
предпосылкой Фазы 1 (engine лишь делал checkout), которую Фаза 4a сделала недействительной
так, что никто не заметил: демо критерия приёмки не проходило с
прежней конфигурацией.
Последствия
POST /projects/:id/execution/activateпринимаетdevAgentImpl: 'real' | 'noop'(опущено =real). UI не меняется — режим Noop — инструмент валидации, а не возможность продукта; демо включает его скриптом.pnpm --filter api demo:noop-executionпрогоняет весь критерий приёмки против стека Compose.- Новые тесты:
noop_dev_agent_server_test.exs(цикл, идентичность, отсутствие LLM, падающий worktree, 2 параллельных агента),dev_rehydrator_test.exs(режим переживает перезапуск,:workне перезапускается, идемпотентность) иgit_executor_test.exs(идентичность в настоящем git: автор bot +Co-authored-by, изолированные worktrees) — последний закрыл пробел: до сих пор утверждался лишь payload предложения, никогда — получившийся коммит.
Область и допущения
Noop ни в чём не заменяет реального агента: не читает контекст task, не запускает
suite, не проходит gates. Он служит, чтобы ответить "поднята ли инфраструктура
выполнения?", не включая LLM. Предложение распараллеливания по-прежнему
countClaimableByModule >= 2, а не граф зависимостей tasks (в схеме нет
зависимостей между tasks) — остаётся открытым, как уже
фиксировал ADR 0017.