ADR 0088 — Staff: код готов, спящий для автоматического запуска
- Статус: Принят
- Дата: 2026-08-16
- Контекст:
docs/fluxo.yml, блокid: staff(camada_decisao_tecnica),status: plannedсо времён ADR 0085 - Прямой прецедент: ADR 0053 (Dev Lead — одиночный разговорный агент, без области), ADR 0068 (артефакт без таблицы, версионируемый в event log)
Контекст
docs/fluxo.yml объявляет Staff/Principal Engineer со времён ADR 0085 как
status: planned: «контракт готов, включение решено, ждёт триггера».
Задуманный для него триггер — Anamnese, замечающая ПОВТОРЯЮЩУЮСЯ системную
проблему, — но Anamnese приостановлена (ANAMNESE_ENABLED=false, продуктовое решение
от 2026-08-10) и не предложит никакого handoff, пока это длится.
Владелец продукта решил заранее подготовить КОД, зная, что автоматический триггер не сработает: Staff готов и доступен для ручного вызова (любой агент или пользователь может адресовать ему handoff уже сегодня через общий внутренний маршрут), и в ожидании остаётся только автоматический запуск. Это НЕ полуреализация — это то же различие, которое ADR 0081 уже провёл для жизненного цикла контейнера (таблица готова, оркестратора ещё нет): недостающая часть объявлена, а не спрятана.
Решение
Staff появляется как ПЯТЫЙ одиночный разговорный агент (рядом с Criativo, PO, Arquiteto и Dev Lead), в точности повторяя шаблон Arquiteto — GenServer на сессию, rehydration из event log, собственный ограниченный цикл tool use (потолок 14, тот же калибр, что у Arquiteto/Dev Lead: рассуждение, а не лёгкая беседа) — с двумя намеренными отличиями.
1. Без kickoff/1
Все остальные разговорные lead синтезируют начальную инструкцию
из event log сессии при СВЕЖЕМ старте (kickoff_instruction/1 у
каждого) — всегда есть предыдущий артефакт для пересказа (product_brief, module_map,
backlog). У Staff такого источника нет: системная проблема, которая
выносит его на поверхность, рождается снаружи (Anamnese, когда она появится, или тот, кто создаёт
handoff вручную сегодня), а не из event log самой сессии. Без kickoff
Engine.Agents.StaffSupervisor.start_agent/2 поднимает процесс (регидрирует
историю), и он простаивает до первого user_message — которым
тот, кто адресовал handoff, объясняет проблему.
apps/engine/lib/engine_web/controllers/agent_command_controller.ex отражает
это: клауза start/2 для "staff" НИКОГДА не вызывает StaffServer.kickoff/1
(такой функции даже нет), в отличие от po/arquiteto/dev-lead/infra, которые
вызывают её условно, когда origin == :started.
2. Включение: ОБЩИЙ путь, без USER_STARTED_AGENTS
USER_STARTED_AGENTS (apps/api/src/domain/sessions/agent-activation.ts)
— исключение для Criativo: он стартует БЕЗ handoff, по прямой команде
пользователя. Исследование подтвердило, что Staff НЕ нужно вносить в этот
список: canActivateAgent уже включает любого агента с accepted
handoff, адресованным ему, — тот же путь, что уже действует для
dev-lead/arquiteto/infra сегодня. Для этого в api не понадобилось
никаких изменений домена:
assertHandoffTargetAllowed(apps/api/src/domain/agents/agent-areas.ts) лишь ОТКАЗЫВАЕТ handoff, адресованному СУБАГЕНТУ области (ADR 0038); у Staff нет области — он проходит свободно, как Criativo/PO/Arquiteto/Dev Lead.ActivateAgentUseCase/ApiToEngineClient.startAgentобобщены поagent: string— никакого enum расширять не нужно.
Следствие честное: «доступен вручную» означает, что доменная
МЕХАНИКА допускает handoff и включение, а не что сегодня существует
отдельный экран, на котором человек выбирает «адресовать handoff Staff»
иначе, чем через код другого агента или прямой вызов внутреннего маршрута
(POST .../internal/sessions/:sessionId/handoffs) — общий UI
«ручного handoff агенту на выбор» остаётся в backlog
(docs/explanation/backlog.md), как было и до этого изменения.
3. propose_rfc — единственный инструмент, без proposed_action
Engine.Agents.StaffTools.propose_rfc записывает artifact.rfc_staff (проблема,
варианты с компромиссами, рекомендация, одноразовый PoC) и возвращает handoff
Arquiteto в ТОМ ЖЕ tool call — без подтверждения человеком посередине, тот же
шаблон, что у CriativoServer, испускающего product_brief и предлагающего handoff
PO в том же ответе.
Механизм определили два реальных прецедента, и поэтому НИ ОДНОГО нового сценария использования в api не появилось:
Engine.Harness.Tools.EmitInsight(emit_insightу Arquiteto) — это шаблон артефакта БЕЗ вывода: весь payload приходит из tool call, а событие записывается напрямую черезEngineApiClient.append_event_returning/3— без таблицы, без выделенного сценария использования.artifact.c4_diagram(ADR 0068) — КОНТРАСТ, подтверждающий выбор: у него есть собственный сценарий использования в api, потому что уровень Container ВЫВОДИТСЯ из действующегоmodule_map— эти данные есть только у api. RFC ничего не выводит с той стороны.
propose_rfc также НЕ является proposed_action: запись архитектурного
документа — это не внешний эффект (не git, не терминал и не расход
агента) — реальное решение (принять, адаптировать, отказаться) принимает Arquiteto, в
handoff, который инструмент уже возвращает. То же рассуждение, что и для emit_insight.
Последствия
За
- Staff существует от начала до конца в engine (сервер, инструмент,
supervisor, маршрутизация) и тестируем уже сегодня, не дожидаясь выхода Anamnese из
доработки — когда она вернётся, останется лишь, чтобы КОД Anamnese вызвал
create_handoff(..., "staff", ...); остальное уже готово. - Для включения не понадобилось никаких изменений домена в api —
исследование доказало, что общего пути уже достаточно, и это позволило не открывать
новое исключение (
USER_STARTED_AGENTS), которое потом пришлось бы отменять. docs/fluxo.ymlперестаёт быть единственным источником, знающим о существовании Staff: код совпадает с тем, что реестр уже объявлял, а реальный пробел (автоматический триггер) описан точно, а не обобщённо («ждёт триггера»).
Против, объявлено
- Список участников панели команды (
apps/web/src/lib/agent-status.ts) и карточка dashboard (ProjectCardSummary.roster, черезapps/api/.../projects-summary.repository.ts) показывают Staff, когда он активен —staffActiveдобавлен в оба пути по тому же критерию, что иinfraActive, чтобы не повторить расхождение, о котором уже предупреждал комментарийRosterFacts(«два правила разойдутся на первом новом агенте»). - Экран Сессии (
SessionPage.tsx) НЕ был затронут.staffне входит вAGENTES_DE_CHAT— тот же шаблон, уже принятый дляinfra(агент НАСТОЯЩИЙ, АКТИВНЫЙ и тоже вне этого списка: комментарий самого файла документирует, что handoff к Infra «никогда не принимается ЗДЕСЬ»). Это не НОВЫЙ пробел Staff: это шаблон, который у продукта уже был для lead без диалога, ведомого через composer сессии. Реальное отличие объявлено: у Infra естьkickoff/1(работает сам после принятия); у Staff нет — поэтому без чат-поверхности, которая отправит ему первыйuser_message, сквозной путь использования сегодня — это внутренний маршрут (POST .../agent/message,agent: "staff"), а не экран. Закрыть это значит затронутьSessionPage.tsx, самый оспариваемый файл продукта, и это не входило в это решение — подготовить заранее код, а не UI чата. - Нет записи в
Engine.Harness.Agents.identity/1(apps/engine/lib/engine/harness/agents.ex) — то же состояние, что уdev-leadсегодня, который тоже падает в общий fallback («Você é o agente staff.»). Это не НОВЫЙ пробел: это реальное состояние активного прецедента, не исправленное мимоходом.
Рассмотренные альтернативы
Ждать выхода Anamnese из доработки, чтобы построить Staff. Отвергнуто — явное решение владельца продукта: заблаговременный код устраняет «второй PR» в день, когда Anamnese снова включат, а ценность РУЧНОГО вызова существует независимо от автоматического триггера.
Добавить staff в USER_STARTED_AGENTS. Отвергнуто — исследование
показало, что это не нужно: стандартный путь canActivateAgent уже
включает по принятому handoff, а этот список существует для ПРОТИВОПОЛОЖНОГО исключения (агента,
который стартует БЕЗ handoff). Добавление сюда выдумало бы правило, которого домен
не требует.
Дать Staff kickoff/1 с общей инструкцией («исследуй
системные проблемы»). Отвергнуто — без реального артефакта для пересказа (а его
будет производить только Anamnese) kickoff был бы пустым приглашением;
сессия простаивает до первого сообщения, и честно, что так и есть.
Ссылки
docs/fluxo.yml— блокid: staffdocs/business-rules.md— RN-305/RN-306- ADR 0053 — шаблон одиночного разговорного агента без области
- ADR 0068 — контраст, решающий «без выделенного сценария использования» для RFC
- ADR 0081 — прецедент «деталь готова, реального потребителя ещё нет», объявленный без смягчения
apps/engine/lib/engine/agents/staff_server.ex,staff_tools.ex,staff_supervisor.exapps/engine/lib/engine_web/controllers/agent_command_controller.exapps/api/src/domain/sessions/agent-activation.ts,apps/api/src/domain/agents/agent-areas.tsapps/web/src/lib/agents.ts,apps/web/src/lib/agent-status.ts