ADR 0009 — Агент PO, backlog (epics/stories/tasks) и прослеживаемость
- Статус: принят
- Дата: 2026-07-24
- Фаза: 3b (сессия 2)
Контекст
Сессия 1 3b поставила разговорный Criativo (выдаёт business_rule,
консолидирует product_brief и предлагает handoff к PO — handoffs со статусом
offered, правило активации, требующее accepted). Эта сессия замыкает цикл:
пользователь принимает handoff, PO активируется, потребляет brief + правила
и производит backlog (эпики → истории → задачи) с DoD/DoR и обязательной
привязкой к бизнес-правилам, сохраняя прослеживаемость правило→stories.
Решения
1. PO как разговорный GenServer с циклом tool use на ход
Engine.Agents.PoServer повторяет CriativoServer (GenServer на сессию,
состояние + регидратация + streaming через канал Phoenix), но каждый ход выполняет
ограниченный цикл tool use: диспетчеризирует create_epic/create_story/create_task и
внедряет результат (id/ошибку) обратно как сообщение tool, повторно вызывая
модель — так она выстраивает цепочку (id эпика → create_story → create_task).
Criativo так не делает (один ход). Активированный принятым handoff (а не
командой пользователя), PO делает :kickoff при СВЕЖЕМ старте (читает brief + правила
из event log и генерирует backlog); перезапуск/повторная активация не генерируют заново.
2. Backlog в собственных таблицах; массивы в JSONB; привязка к правилу по ULID
epics/stories/tasks (миграция 0011). Поскольку проект не использует массивы
Postgres, RF/RNF/DoD/DoR/business_rule_ids — это jsonb ($type<string[]>).
business_rule_ids ссылается на session_events.id (артефакт
artifact.business_rule) текстовым ULID без FK — тот же выбор, что
handoffs.artifact_id (логическая, а не реляционная связь). Задачи наследуют
привязку истории (выводится, не хранится).
3. Готовность draft→ready проверяется в ДОМЕНЕ
domain/backlog/story-readiness.ts (assertReady → StoryNotReadyError) требует
непустых DoD, DoR, ≥1 RF и ≥1 привязанного правила — "валидация в домене, а не только
в prompt". story-state-machine.ts покрывает draft→ready→in_progress→done.
create_story создаёт в draft и продвигает в ready той же операцией, когда
правило выполнено (PO использует лишь 3 названных инструмента — инструмента
перехода нет); TransitionStoryUseCase открывает явный переход (протестирован) и
обслуживает будущие состояния.
4. Инструменты PO проходят через api (никогда прямой SQL/insert)
create_epic/create_story/create_task — это инструменты :direct, вызывающие
EngineApiClient → внутренние endpoints → use cases с валидацией домена.
CreateStoryUseCase проверяет, что каждый business_rule_id действительно ссылается на
существующее событие artifact.business_rule (отвергает выдуманные id; ничего
не создаётся). Каждое создание выдаёт backlog.* в event log (повествование PO).
5. Прослеживаемость правило→stories (открытый пункт = находка)
domain/backlog/coverage.ts (computeCoverage, чистая) + GET /projects/:id/ coverage: для каждого artifact.business_rule проекта — какие истории его
покрывают (через business_rule_ids). Правило без покрытия — это находка —
UI показывает его красным во вкладке Backlog. Уровень проекта (агрегирует правила
сессий через join с sessions).
6. Принятие handoff активирует PO по правилу сессии 1
AcceptHandoffUseCase (действие пользователя): offered→accepted + handoff.accepted
→ вызывает ActivateAgentUseCase для toAgent. Правило активации (требующее
handoff accepted) теперь проходит ровно благодаря этому принятию. UI: кнопка
"Aceitar handoff e iniciar PO" в сессии.
Последствия
- Вкладка Backlog — на уровне проекта (локальное состояние, без нового маршрута), читаемая опросом
GET backlog+GET coverage, — отражает генерацию PO в реальном времени. - Composer сессии направляет активному агенту (PO имеет приоритет над
Criativo) через
sendAgentMessage. - Тесты: домен (
story-readiness,story-state-machine,coverage), use case (create-storyотвергает несуществующее правило;transition-storyотклоняет draft→ready без DoD) иpo_server_test(kickoff выстраивает цепочку create_epic→ create_story с внедрением результата; невалидное правило становится tool-result с ошибкой, не обрушивая цикл; broadcast; регидратация).
Область
Только PO + backlog + прослеживаемость. Arquiteto (ADR в docs/adr/ репозитория
проекта, module_map, проверка, что story ссылается на существующий модуль) —
следующая сессия. in_progress/done и ручное редактирование backlog в UI остаются
на потом (машина состояний уже их покрывает).