158 KiB
Task Queue / Agenda Rollout Plan
Date: 2026-04-21
Status: Proposed
Scope: Team Management, MCP task surfaces, agent operational queue
TL;DR
✅ Базовое решение:
- перед полноценным agenda rollout сначала делаем Phase 0 hardening для слабых сигналов
- source of truth остаётся в сырых task/review/kanban файлах
- в Phase 1 не заводим второй persisted projection-файл
- вместо этого строим derived queue projection on read через controller-owned board snapshot под team-level lock
- вводим явные derived-поля:
actionOwner,nextAction,queueCategory,watchers - разделяем 4 разных surface:
member_briefing= только bootstrap + правила + рольtask_briefing= каноническая очередь конкретного участникаlead_briefing= каноническая очередь лидаtask_list= inventory/search surface, который постепенно уходит в filtered role
- Phase 2 добавляет
revisionи только потом, если реально нужно по профайлингу,delta
Главная мысль: проблема не в том, что task_list "слишком длинный". Проблема в том, что он сейчас смешивает inventory, workflow state, операционный приоритет и шум, заставляя LLM самому угадывать "что мне делать сейчас".
⚠️ После дополнительного code review важны 4 уточнения:
- сейчас в коде нет общего board-level lock поверх task + kanban + review mutations, но Phase 0 надо ограничить controller snapshot/mutation scope, а не пытаться сразу перевести под него все main-side readers
- reviewer для review-задачи в Phase 0/1 надо выводить только из текущего review cycle в
historyEvents, потому что оба write-path сегодня заводят kanban review entry сreviewer: null needsClarification: "lead"в коде сейчас auto-clear'ится шире, чем обещают prompt-инструкции, поэтому safest Phase 0 - explicit clear onlytask_listнельзя резко ломать сменой default semantics, потому что lead prompts уже используют его как full inventory entrypoint
1. Почему вообще понадобился этот redesign
Сейчас у системы есть 2 разных read-path, но ни один из них не задаёт правильную operational semantics:
task_list
- технически возвращает почти весь список задач команды
- это не "очередь работы", а полу-сырой inventory dump
- он слишком тяжёлый по payload
- главное, он не говорит явно, кто сейчас должен действовать по задаче
- агенту приходится самому вычислять:
- моя ли это задача
- жду ли я ревью
- я ли ревьюер
- надо ли пинговать лида
- задача реально actionable или просто informational
task_briefing
- даёт более компактное представление
- но сейчас завязан в основном на
owner === memberName - поэтому не покрывает важные сценарии:
- задача формально owned одним участником, но action сейчас у ревьюера
- задача зависла из-за отсутствующего reviewer
- задача ждёт решения лида
- участнику нужна awareness по своим задачам, даже если actionOwner временно не он
Итог:
- лид не получает нормальную operational queue
- тиммейт может не видеть "что от него реально требуется"
- LLM тратит токены и качество на вывод политики из сырых полей
- при росте команды и количества задач путаница становится системной
2. Что именно не так в текущей модели
2.1 task_list сегодня слишком близок к raw dump
По факту текущий task_list делает почти "отдать все задачи, слегка урезав крупные поля". Это blocklist-подход:
- убираются тяжёлые поля вроде комментариев и истории
- почти всё остальное сохраняется
Такой подход плох не только по размеру, но и по смыслу:
- агенту всё ещё видны почти все задачи команды
- инструмента нет opinionated workflow surface
- список не говорит, какие карточки именно должны попасть в текущую очередь участника
2.2 task_briefing сегодня не отделяет action от awareness
Сейчас логика ближе к "покажи мои assigned tasks". Но operational queue и assigned list не одно и то же.
Примеры:
- owner =
alice, reviewer =bob, задача в review
aliceдолжна понимать, что задача не пропала, но actionOwner ужеbob - задача completed, reviewer ещё не назначен
action нужен не owner, а лиду - задача ждёт ответа от user
actionOwner =user, но лид должен видеть это как oversight item
2.3 Raw task list не должен быть основным surface для LLM
LLM лучше работает, когда система даёт:
- кто сейчас должен действовать
- почему именно он
- какое следующее ожидаемое действие
- что informational, а что actionable
LLM хуже работает, когда ему дают 50 карточек и ожидают, что он сам выведет workflow policy.
2.4 Важные факты из текущего кода
Ниже то, на что уже можно опираться в rollout, без изобретения новой подсистемы с нуля:
task_listсегодня реально отдаёт почти весь team inventory в урезанном виде, а не opinionated queuetask_briefingсегодня в основном строится черезowner === memberNamereview_requestтехнически может отправить задачу в review даже без явно резолвленного reviewer- active reviewer для agenda-safe routing в Phase 0/1 надо выводить только из текущего review cycle в
historyEvents; per-task kanban reviewer сейчас не является надёжным signal - runtime identity для участника уже умеет частично резолвиться из существующего runtime context
- UI и MCP уже в основном идут через
agent-teams-controller, то есть rollout можно делать эволюционно вокруг текущего controller layer - task и kanban сегодня пишутся atomically по файлам, но не transactionally как единый board update
- controller-side task/kanban write paths сейчас вообще не используют существующий
withFileLockSync(...); текущий lock primitive живёт отдельно, синхронный, busy-wait, с acquire timeout5sи stale timeout30s review_requestсейчас делает multi-step sequencekanban -> task history -> message, значит queue read действительно может увидеть промежуточное состояниеreview_requestпри ошибке отправки уведомления сейчас делает толькоkanban.clear, но уже записанныйreview_requestedhistory event не откатываетneedsClarification: "lead"в task store сейчас auto-clear'ится при комментарии любого не-owner автора, а не только лидаtask_listиспользует blocklist-подход, поэтому payload может незаметно разрастаться при появлении новых task-полей- текущий
task_listв MCP напрямую вызываетcontroller.tasks.listTasks()без фильтров и затем прогоняет результат черезslimTaskForList(...), то есть filter-capable inventory contract пока даже не выбран task_listвходит в teammate operational tool catalog, значит prompt-only migration недостаточна - нужен ещё catalog/access rollout- в репо есть как минимум две локальные
.d.tsдекларации дляagent-teams-controller, и они уже расходятся по полноте surface mcp-serverтесты сейчас явно проверяют текущую blocklist-semanticstask_list, значит migration должна обновлять и test contract, а не только runtimeownerиneedsClarificationхранятся как текущие поля task payload, но не имеют такого же history-backed контракта, как review transitions- task logs умеют видеть
task_set_owner/task_set_clarification, но это отдельный observability слой, а не базовый queue source of truth - в schema у
kanban-state.tasks[taskId]естьreviewer, но официальный mutation patch сейчас не несёт reviewer как поддерживаемый per-task signal - prompt знает правило вида "если есть reviewer/qa/tech-lead, отправляй на review", но это не равняется канонической machine-readable review policy в runtime state
- roster resolution уже существует минимум в двух вариантах: controller-side
resolveTeamMembers(...)и main/UI-sideTeamMemberResolver, и они нормализуют состав команды не полностью одинаково - main-side
TeamDataServicecompatibility layer сейчас резолвитreviewerкакkanbanTaskState.reviewer ?? history(review_approved|review_started|review_requested), аreviewStateберёт из persisted field, что уже шире и слабее, чем queue-safe current-cycle contract setTaskOwnerиtask_set_clarificationменяют task payload иupdatedAt, но не добавляют workflow events вhistoryEvents- stall-monitor уже использует более строгую review-window semantics, где открытое review окно начинается с
review_started, а не просто сreview_requested
Это важно, потому что план не требует:
- переписывать storage
- менять формат task-файлов
- вводить новый отдельный board runtime
3. Цели redesign
Нужно добиться одновременно 5 свойств:
-
Однозначность По каждой активной задаче должен существовать один primary
actionOwnerили явноnone/user. -
Компактность Обычный teammate не должен получать весь board dump только ради того, чтобы понять свои 2-4 задачи.
-
Надёжность Read model не должен становиться вторым нестабильным source of truth.
-
Понятная иерархия surfaces Bootstrap, operational queue, full context и inventory/search должны быть разными инструментами.
-
Эволюционность Phase 1 должен сильно улучшить поведение без большого риска stale projection bugs.
3.1 Confidence Map After Code Review
Ниже зоны, где после просмотра кода у нас разная степень уверенности.
A. Derived agenda как базовый read model
🎯 10 🛡️ 10 🧠 6
Почему уверенность высокая:
- это не требует второго durable state
- current storage уже содержит достаточно сигналов для derived routing
- основной риск здесь не идея, а discipline around rollout
B. Controller-owned board snapshot + lock
🎯 9 🛡️ 9 🧠 5
Почему уверенность выросла:
- после дополнительного просмотра видно, что main-side raw readers (
TeamTaskReader,TeamKanbanManager) живут отдельно и не требуют немедленного включения в agenda path - для agent-facing queue достаточно сначала сделать один канонический controller snapshot contract
- это сильно уже и безопаснее, чем пытаться в Phase 0 синхронно унифицировать весь repo вокруг одного lock API
C. Reviewer resolution
🎯 8 🛡️ 9 🧠 4
Почему уверенность стала выше:
- и controller-side
kanbanStore.setKanbanColumn(...), и main-sideTeamKanbanManager.updateTask(...)при переводе вreviewзаписываютreviewer: null - значит ambiguity здесь на самом деле меньше, а не больше: Phase 0/1 просто не должны опираться на per-task kanban reviewer
- остаётся чётко определить только current-cycle precedence внутри
historyEvents
D. Clarification semantics
🎯 8 🛡️ 10 🧠 3
Это всё ещё слабая зона текущего runtime, но у неё теперь есть понятный safe fallback.
Причина:
- prompts говорят одно
- код auto-clear'ит флаг шире
- но safest fix очень прямой: в Phase 0 убрать implicit auto-clear из routing contract и жить через explicit
task_set_clarification clear
E. task_list migration без silent breakage
🎯 7 🛡️ 7 🧠 5
Почему:
- целевая роль
task_listпонятна - но менять его default резко опасно, потому что старые лид-промпты уже ожидают текущую семантику
- значит migration должна идти через новый canonical surface, а не через скрытую подмену смысла
F. Queue-side roster normalization
🎯 7 🛡️ 8 🧠 5
Почему это остаётся важной зоной:
- controller-side
resolveTeamMembers(...)и UI-sideTeamMemberResolverдо сих пор фильтруют roster не полностью одинаково - для queue это критично, потому что invalid owner/reviewer routing зависит от того, кого мы вообще считаем валидным member
- но здесь достаточно зафиксировать минимальный queue contract и покрыть его тестами, без большого package refactor
G. Lead identity normalization for lead_briefing
🎯 6 🛡️ 8 🧠 4
Почему это остаётся слабее других зон:
- controller-side
inferLeadName(...)использует ad hoc heuristics:agentType === "team-lead",roleсодержитlead, имяteam-lead, иначе первый config member - shared
leadDetectionutilities уже живут по чуть более другому контракту - для
lead_briefingи future ergonomic clarification clear нельзя опираться на "может быть это и есть lead" без явного phase rule
H. Tool-catalog scoping for lead_briefing
🎯 8 🛡️ 9 🧠 4
Почему это важно:
- текущий
mcpToolCatalogрежет доступность по group-levelteammateOperational, а не по per-tool policy - если просто положить
lead_briefingв existing task-group, он автоматически окажется в teammate operational tool set - это не runtime bug, но это прямой путь к лишнему шуму и путанице у teammate agents
3.2 Top 3 Options In The Least Certain Areas
Ниже не "все возможные идеи", а именно те развилки, где реально можно ошибиться архитектурно.
A. Где должен жить shared board lock
1. Controller-owned board snapshot API + controller-local team lock
🎯 10 🛡️ 9 🧠 5
Примерный объём: 80-160 строк изменений
Суть:
- внутри
agent-teams-controllerввести один каноническийwithTeamBoardLock(...)илиgetBoardSnapshot(...) - под него завести multi-file mutations review/task/kanban и agenda reads
- main-process не заставлять в той же фазе массово переходить на этот primitive, если ему не нужен именно agenda-grade snapshot
Почему это лучший вариант:
- один semantic owner для agent-facing queue
- не раздувает Phase 0 до cross-layer refactor всей файловой модели
- оставляет возможность позже экспортировать тот же snapshot наружу, если он реально понадобится UI
2. Exported generic shared primitive across controller + main
🎯 7 🛡️ 8 🧠 6
Примерный объём: 100-180 строк изменений
Суть:
- сделать универсальный lock/snapshot contract, который одинаково используют controller и main
Риск:
- полезно в долгую, но для ближайшего rollout это уже больше работа, чем нужно
- есть риск начать чинить "архитектурную красоту" вместо реально агентского queue path
3. Пер-file lock orchestration без отдельного team lock
🎯 3 🛡️ 3 🧠 6
Примерный объём: 60-120 строк изменений
Суть:
- пытаться добиться консистентности через набор локов на task/kanban файлы
Риск:
- сложнее reasoning
- выше риск partial interleaving
- хуже читается и хуже тестируется
Выбор:
- брать вариант 1
- вариант 2 рассматривать только если после rollout тот же snapshot contract реально понадобится non-controller consumers
B. Как резолвить reviewer для active review cycle
1. Pure history-first current-cycle resolver
🎯 10 🛡️ 10 🧠 4
Примерный объём: 70-140 строк изменений
Суть:
- сканировать history backwards только в пределах текущего review cycle
- использовать
review_started.actorиreview_requested.reviewerкак основные сигналы - в Phase 0/1 вообще не использовать
kanban-state.tasks[taskId].reviewerкак routing signal
Почему это лучший вариант:
- максимально опирается на уже существующие durable events
- согласуется с уже существующей derivation логикой review state через history
- это полностью соответствует реальному коду: и controller, и main сейчас создают review-entry с
reviewer: null
2. History-first now, kanban fallback only after writer hardening
🎯 6 🛡️ 8 🧠 6
Примерный объём: 110-210 строк изменений
Суть:
- сначала расширить write-path так, чтобы per-task
reviewerреально persist'ился - только после этого разрешить kanban fallback в resolver
Риск:
- это уже не Phase 0 hardening, а изменение durable semantics
- если делать одновременно с agenda rollout, возрастает blast radius
3. Новый persisted reviewer slot в task schema
🎯 7 🛡️ 8 🧠 7
Примерный объём: 120-240 строк изменений
Суть:
- добавить explicit reviewer прямо в task payload и синхронизировать его в review flow
Плюсы:
- проще future reads
Минусы:
- новая durable schema responsibility
- выше цена ошибок и миграции
Выбор:
- для Phase 0/1 брать вариант 1
- вариант 3 оставлять как later optimization, только если history-based resolver окажется недостаточным
C. Как harden'ить clarification clearing
1. Phase 0 explicit-clear-only semantics
🎯 10 🛡️ 10 🧠 3
Примерный объём: 25-60 строк изменений
Суть:
- не делаем никакой implicit auto-clear частью routing contract
- clarification снимается только через явный
task_set_clarification clear - prompts и briefing обновляются синхронно
Почему это лучший вариант:
- максимально надёжно
- убирает silent false negatives в очереди
- делает поведение легко тестируемым и объяснимым
2. Controller-layer lead-aware auto-clear after alias hardening
🎯 8 🛡️ 9 🧠 6
Примерный объём: 70-140 строк изменений
Суть:
- после стабилизации lead identity вернуть удобство:
leadclarification clear'ится на комментарий лидаuserclarification clear'ится на комментарий пользователя
Риск:
- нужно сначала жёстко определить:
- canonical lead identity
- alias policy (
team-lead, фактическое имя лида, возможныйlead) - valid actor normalization
3. Протянуть explicit canClearClarification / leadName в taskStore API
🎯 5 🛡️ 6 🧠 6
Примерный объём: 50-110 строк изменений
Суть:
- taskStore всё ещё делает auto-clear, но получает workflow policy сверху
Риск:
- API store начинает знать слишком много про workflow policy
Выбор:
- для Phase 0 брать вариант 1
- вариант 2 оставлять как optional ergonomics pass только после стабилизации lead alias semantics
Отдельное уточнение:
- это не двухстрочная правка в storage layer
taskStore.addTaskComment()сам по себе не знает, кто является lead для команды
Поэтому "clear only when lead answers" нельзя надёжно реализовать внутри store без:
- переноса policy выше, в controller/context layer
- или явной передачи lead-aware policy signal в storage API
D. Как делать queue revision в Phase 2
1. Stable hash of compact agenda DTO
🎯 9 🛡️ 9 🧠 4
Примерный объём: 40-90 строк изменений
Суть:
- как в
feedRevision, считать hash от уже нормализованного compact agenda payload
Почему это лучший вариант:
- не требует extra durable file
- легко проверять
- удобно для
unchangedshort-circuit
2. Team directory mtime / file timestamps
🎯 4 🛡️ 4 🧠 3
Примерный объём: 20-40 строк изменений
Риск:
- платформенная хрупкость
- плохая детерминированность
3. Dedicated revision counter file
🎯 7 🛡️ 8 🧠 6
Примерный объём: 70-140 строк изменений
Плюсы:
- дешёвые reads
Минусы:
- ещё одна durable write responsibility
Выбор:
- сначала вариант 1
- вариант 3 рассматривать только если hash on read реально станет bottleneck
E. Где должна жить canonical agenda derivation logic
1. Controller-owned single implementation
🎯 9 🛡️ 9 🧠 5
Примерный объём: 90-170 строк изменений
Суть:
- agenda derivation живёт в
agent-teams-controller - MCP tools только оборачивают её
- main-process, если ему нужен тот же semantic surface, либо вызывает controller API, либо использует тот же exported helper
Почему это лучший вариант:
- один semantic owner
- меньше риска drift между MCP/runtime/UI
- уже сейчас controller является главным write-path для board operations
2. Отдельные реализации в controller и main-process
🎯 4 🛡️ 4 🧠 4
Примерный объём: 120-220 строк изменений
Риск:
- review/clarification semantics почти гарантированно разъедутся со временем
- сложнее тестировать и объяснять различия
3. Новый общий workspace package / shared helper
🎯 7 🛡️ 8 🧠 7
Примерный объём: 140-260 строк изменений
Плюсы:
- архитектурно чище в долгую
Минусы:
- это уже mini-refactor package boundaries
- для Phase 0/1 цена выше, чем польза
Выбор:
- для ближайшего rollout брать вариант 1
- вариант 3 оставлять как later cleanup, если agenda surface реально понадобится нескольким runtime слоям в одном виде
F. Как уводить task_list от teammate default workflow
1. Prompt + tool-catalog phased migration
🎯 9 🛡️ 9 🧠 5
Примерный объём: 60-120 строк изменений
Суть:
- сначала вводим
lead_briefing - затем меняем prompts
- затем убираем
task_listиз teammate operational catalog или переводим его в less-prominent surface
Почему это лучший вариант:
- migration управляемая
- меньше surprising behavior для уже работающих flows
2. Prompt-only migration
🎯 5 🛡️ 5 🧠 2
Примерный объём: 15-35 строк изменений
Риск:
task_listвсё равно остаётся у тиммейта "под рукой"- модель будет продолжать иногда использовать его как shortcut
3. Instant hard removal from teammate catalog
🎯 6 🛡️ 7 🧠 4
Примерный объём: 20-60 строк изменений
Плюсы:
- быстро убирает temptation
Минусы:
- выше шанс сломать существующие prompt assumptions и recovery workflows
Выбор:
- брать вариант 1
G. Как трактовать "review required" без явной policy-модели
1. Explicit-review-only semantics in Phase 0/1
🎯 10 🛡️ 10 🧠 3
Примерный объём: 20-50 строк изменений
Суть:
- queue считает review обязательным только когда есть явный review state / review cycle signal
- completed task без active review не получает auto-routing в
assign_reviewerтолько потому, что "в команде есть reviewer"
Почему это лучший вариант:
- не выдумывает policy, которой в runtime state сейчас нет
- минимизирует ложные lead action items
2. Inference from free-form member roles
🎯 3 🛡️ 3 🧠 3
Примерный объём: 20-40 строк изменений
Суть:
- пытаться читать
roleвродеreviewer,qa,tech-leadи решать, что completed task должна уйти в review
Риск:
- free-form role text не является надёжным policy contract
- легко получить false positives и странные queue jumps
3. Introduce explicit review policy field later
🎯 8 🛡️ 9 🧠 7
Примерный объём: 90-180 строк изменений
Суть:
- в будущем добавить machine-readable team/task review policy
- только тогда можно safely строить routing вроде
completed -> assign_reviewerбез explicit review event
Выбор:
- для Phase 0/1 брать вариант 1
- вариант 3 держать как possible future enhancement
H. Какая roster resolution считается канонической для queue
1. Controller-owned roster normalization for queue
🎯 8 🛡️ 9 🧠 5
Примерный объём: 50-110 строк изменений
Суть:
- queue в controller layer использует controller-side roster resolver
- но этот resolver сначала доводится до минимально достаточного predictable contract:
- removed members
- lead alias normalization
- suppression of phantom inbox-derived aliases where necessary
- ignore qualified external recipients
- ignore generated/internal pseudo-agent names, если они не пришли из explicit config/meta
Плюсы:
- queue остаётся рядом со своим runtime context
- не появляется cross-layer dependency на UI resolver
2. Reuse UI TeamMemberResolver semantics indirectly
🎯 5 🛡️ 6 🧠 7
Примерный объём: 90-170 строк изменений
Суть:
- пытаться тащить roster rules из main/UI слоя обратно в controller path
Риск:
- package boundary усложняется
- для ближайшего rollout это слишком тяжело
3. Shared future roster package/helper
🎯 7 🛡️ 9 🧠 8
Примерный объём: 120-220 строк изменений
Суть:
- вынести roster normalization в реально общий helper/package
Плюсы:
- меньше semantic drift в долгую
Минусы:
- для Phase 0/1 это уже дополнительный refactor
Выбор:
- в ближайшем rollout брать вариант 1
- но явно зафиксировать expected queue roster semantics тестами, чтобы drift от UI не был скрытым
I. Где должен жить lead_briefing в MCP tool catalog
1. Отдельная lead group с teammateOperational: false
🎯 10 🛡️ 10 🧠 4
Примерный объём: 30-70 строк изменений
Суть:
- добавить отдельную tool group, например
lead - положить туда
lead_briefing - не смешивать этот tool с existing
taskgroup - синхронно расширить
AgentTeamsMcpToolGroupIdв обеих локальных.d.ts
Почему это лучший вариант:
- текущий catalog already group-scoped, а не per-tool scoped
- это самый дешёвый способ гарантировать, что lead surface не попадёт в
AGENT_TEAMS_TEAMMATE_OPERATIONAL_TOOL_NAMES - semantics остаётся прозрачной: teammate task tools отдельно, lead-only tool отдельно
2. Оставить lead_briefing в task group, но добавить per-tool denylist override
🎯 5 🛡️ 7 🧠 6
Примерный объём: 40-90 строк изменений
Суть:
- сохраняем существующую
taskgroup - поверх неё добавляем дополнительную per-tool политику, чтобы скрыть только
lead_briefing
Риск:
- чинит symptom, а не модель
- делает catalog rules менее очевидными
- повышает шанс future drift между group semantics и effective availability
3. Оставить lead_briefing в task group и полагаться только на prompts
🎯 2 🛡️ 2 🧠 1
Примерный объём: 5-20 строк изменений
Суть:
- tool физически доступен тиммейтам
- просто стараемся не упоминать его в teammate prompts
Риск:
- это не access policy, а надежда на prompt discipline
- модель всё равно сможет брать лишний tool как shortcut
- именно так и рождается путаница "почему у меня есть lead queue"
Выбор:
- для rollout брать вариант 1
- отдельную per-tool policy layer обсуждать только если позже реально появятся mixed-access tools внутри одной semantic group
J. Где должна жить semantics для filtered task_list
1. Dedicated controller-owned inventory method с явным allowlisted output contract
🎯 9 🛡️ 10 🧠 5
Примерный объём: 50-110 строк изменений
Суть:
- raw
tasks.listTasks()остаётся raw/read helper - для
task_listвводится отдельный semantic owner:- либо public
tasks.listTaskInventory(filters?) - либо internal controller helper с тем же смыслом, если public width хотят держать узкой
- либо public
- output contract фиксируется как explicit inventory row allowlist, а не
slimTaskForList(...)blocklist passthrough
Почему это лучший вариант:
- фильтры и payload-shaping живут рядом с controller-owned queue/inventory semantics
- не приходится тихо переопределять смысл старого raw
listTasks() - migration к компактному output становится явной и тестируемой
2. Перегрузить существующий tasks.listTasks(filters?) и постепенно менять его смысл
🎯 5 🛡️ 6 🧠 4
Примерный объём: 35-90 строк изменений
Суть:
- тот же метод начинает иногда означать raw list, а иногда filtered inventory
Риск:
- имя метода начинает врать о своей семантике
- выше шанс type/runtime drift и неожиданных побочных эффектов в existing callers
3. Оставить controller raw, а фильтрацию и shape собирать прямо в MCP tool
🎯 4 🛡️ 5 🧠 3
Примерный объём: 25-70 строк изменений
Суть:
task_listMCP tool сам вызывает rawtasks.listTasks()+ kanban/review helpers и строит filtered inventory локально
Риск:
- semantics inventory начинает жить вне controller
- MCP и другие consumers почти гарантированно начнут drift'ить
Выбор:
- брать вариант 1
- raw
tasks.listTasks()не надо тихо превращать в semantic inventory API
K. Как безопасно seed'ить lead-first tool access после появления lead_briefing
1. Отдельный explicit lead bootstrap seed list
🎯 9 🛡️ 10 🧠 4
Примерный объём: 25-70 строк изменений
Суть:
- не переиспользовать
AGENT_TEAMS_NAMESPACED_TEAMMATE_OPERATIONAL_TOOL_NAMESкак proxy для lead-first surfaces - завести отдельный explicit список initial lead bootstrap tools
- включить туда
lead_briefingи другие реально нужные first-turn lead surfaces
Почему это лучший вариант:
- lead bootstrap и teammate operational workflow - разные вещи
- появление lead-only tool больше не ломает initial permission seed path
- semantics разрешений становится явной, а не побочной
2. Seed'ить лидеру все registered agent-teams tools
🎯 4 🛡️ 6 🧠 2
Примерный объём: 10-30 строк изменений
Суть:
- чтобы не думать о разделении, лид получает сразу весь namespaced surface
Риск:
- слишком широкий blast radius по permissions
- теряется смысл role-scoped tool surfaces
3. Ничего не seed'ить отдельно, положиться на runtime permission prompt
🎯 3 🛡️ 5 🧠 1
Примерный объём: 5-15 строк изменений
Суть:
lead_briefingможет быть доступен, но первый вызов пройдёт через ad hoc permission flow
Риск:
- первый ход лида становится менее детерминированным
- prompt говорит "сначала вызови lead_briefing", а runtime может отвечать permission friction
Выбор:
- брать вариант 1
- bootstrap-critical lead surfaces не должны зависеть от teammate permission list случайно
L. Как удержать agenda/inventory helpers internal при текущем bindModule(...)
1. Вынести их в отдельный internal module, который controller не bind'ит
🎯 9 🛡️ 10 🧠 5
Примерный объём: 40-100 строк изменений
Суть:
- agenda/inventory derivation живёт в отдельном internal helper module
tasks.jsимпортирует его, но не ре-экспортирует целикомcreateController()не видит этот helper module как public API
Почему это лучший вариант:
- accidental public export risk исчезает по самой структуре кода
- проще держать public controller contract узким
2. Оставить helpers в tasks.js, но не экспортировать их
🎯 8 🛡️ 8 🧠 3
Примерный объём: 20-50 строк изменений
Суть:
- helpers остаются локальными функциями файла
Риск:
- работает, но
tasks.jsи так уже перегружен разными обязанностями - выше шанс, что позже кто-то всё же экспортнет helper "для удобства"
3. Экспортнуть helper из tasks.js, но считать его internal по договорённости
🎯 1 🛡️ 2 🧠 1
Примерный объём: 5-15 строк изменений
Суть:
- helper появляется в
module.exports, но мы просто стараемся не использовать его как public API
Риск:
- при текущем
bindModule(...)это уже не internal helper, а реальный public controller method - это прямой путь к silent surface creep и type drift
Выбор:
- брать вариант 1
- вариант 2 допустим только если хотят минимальный diff и готовы жёстко держать export discipline
M. Как harden'ить lead bootstrap readiness для lead_briefing
1. Role-aware required-tool preflight после wiring stabilization
🎯 9 🛡️ 10 🧠 5
Примерный объём: 40-100 строк изменений
Суть:
- после того как
leadgroup, registration path и permission seed уже стабилизированы - добавляем role-aware preflight invariant:
- teammate path требует
member_briefing - lead path требует
lead_briefing
- teammate path требует
- validation делается через тот же
tools/list/tools/callstyle readiness check
Почему это лучший вариант:
- если prompt делает
lead_briefingcanonical first action, runtime тоже должен это уметь проверять - исчезает класс багов "prompt уже требует tool, а launch path его ещё не гарантирует"
2. Prompt-level canonical first call + explicit fallback until readiness proven
🎯 7 🛡️ 7 🧠 2
Примерный объём: 15-40 строк изменений
Суть:
- lead prompt рекомендует
lead_briefing - но пока нет role-aware preflight, допускается fallback на inventory path при tool/permission failure
Риск:
- migration мягче, но менее детерминированна
- остаётся окно, где canonical surface ещё не является hard runtime invariant
3. Ничего не проверять дополнительно и надеяться на registration/permissions
🎯 2 🛡️ 4 🧠 1
Примерный объём: 5-15 строк изменений
Суть:
- считаем, что если tool зарегистрирован где-то в системе, этого достаточно
Риск:
- код уже показывает, что explicit MCP readiness gate сейчас существует только для
member_briefing - значит для lead path это просто wishful thinking, а не контракт
Выбор:
- брать вариант 1
- до его внедрения prompt не должен делать
lead_briefinghard bootstrap blocker без fallback
N. Где должен жить source of truth для lead bootstrap tool list
1. Exported controller/package constant рядом с existing teammate constants
🎯 10 🛡️ 10 🧠 3
Примерный объём: 20-50 строк изменений
Суть:
- если нужен lead bootstrap tool list, он живёт не локально в
TeamProvisioningService - а экспортируется из
mcpToolCatalogрядом с:AGENT_TEAMS_TEAMMATE_OPERATIONAL_TOOL_NAMESAGENT_TEAMS_NAMESPACED_TEAMMATE_OPERATIONAL_TOOL_NAMES
- например как:
AGENT_TEAMS_LEAD_BOOTSTRAP_TOOL_NAMESAGENT_TEAMS_NAMESPACED_LEAD_BOOTSTRAP_TOOL_NAMES
Почему это лучший вариант:
- bootstrap runtime не получает второй ручной список tool names
- catalog/permissions/prompts/tests смотрят в один и тот же source of truth
- migration для
lead_briefingне размазывает naming policy по нескольким слоям
2. Локальный список прямо в TeamProvisioningService
🎯 4 🛡️ 5 🧠 2
Примерный объём: 10-30 строк изменений
Суть:
- просто прописать lead bootstrap tools строками в runtime service
Риск:
- это создаёт второй source of truth рядом с catalog
- при следующем tool rename или regrouping drift почти неизбежен
3. Вычислять список динамически из AGENT_TEAMS_MCP_TOOL_GROUPS по эвристике
🎯 5 🛡️ 6 🧠 5
Примерный объём: 20-60 строк изменений
Суть:
- runtime сам выводит lead bootstrap tools из group metadata или naming conventions
Риск:
- слишком неявно для bootstrap-critical path
- эвристика потом будет спорить с реальным prompt/runtime contract
Выбор:
- брать вариант 1
- bootstrap-critical tool lists должны экспортироваться явно, а не вычисляться по косвенным признакам
3.3 Decision Freeze For Phase 0/1
Чтобы rollout не расползся и агенты не получили смесь полу-готовых правил, для Phase 0/1 фиксируем жёсткие ограничения:
- per-task
kanban reviewerне участвует в routing, пока write-path не начнёт реально его поддерживать kanban.reviewers[]трактуется только как reviewer pool / availability list, а не assignment на конкретную задачу- clarification в routing contract живёт через explicit clear, а не через implicit "кто-то ответил"
- board lock в Phase 0 обязан покрыть controller agenda snapshot и multi-file board mutations, но не обязан сразу заменять все main-side raw readers
- queue roster использует controller-owned normalization c явно зафиксированными тестами, а не ad hoc смесь controller/UI эвристик
lead_briefingв Phase 1 остаётся role-scoped surface без обязательногоleadNameпараметраlead_briefingв catalog живёт в отдельной non-teammate-operational group, а не прячется внутриtaskgroup- delivery уведомлений не входит в rollback boundary board-state mutation; board commit и message send надо разводить
- новая tool group не считается введённой, пока у неё нет явного registration path без duplicate registrations
- lead-first bootstrap не зависит только от teammate operational permission seed list
- source of truth для lead bootstrap tools экспортируется явно, а не живёт локальным списком в runtime service
- availability
lead_briefingи permission seed для него трактуются как разные rollout concerns - canonical first-turn
lead_briefingне становится hard bootstrap invariant раньше, чем у lead path появляется явная readiness validation или безопасный fallback - agenda/inventory helpers не утекают в public controller surface через случайный
module.exports - filtered
task_listне должен вечно оставаться MCP-local blocklist wrapper над rawtasks.listTasks() - generic board snapshot helper не становится public API, пока у него нет второго доказанного consumer
3.4 Concrete Migration Blockers Found In Code
Это не теоретические риски, а уже существующие места, которые диктуют rollout order.
A. Lead prompt still teaches task_list
🎯 10 🛡️ 10 🧠 2
Факт из кода:
TeamProvisioningServiceпрямо содержит строкуList all tasks: task_list { teamName: "..." }
Следствие:
- нельзя считать migration завершённым, пока lead prompt не будет переписан на
lead_briefingкак canonical first call - одного нового MCP tool недостаточно
- member path сегодня жёстко валидирует именно
member_briefing; lead path такого bootstrap gate пока не имеет - поэтому у плана только 2 честных варианта:
- либо
lead_briefingсначала canonical recommendation with fallback - либо после stabilization wiring добавить явный lead-side preflight invariant
- либо
- prompt-only canonicalization без одного из этих двух путей здесь недостаточна
B. Member path already anchors on member_briefing -> task_briefing
🎯 10 🛡️ 10 🧠 2
Факт из кода:
- member bootstrap сейчас жёстко валидирует наличие
member_briefing - member prompts уже учат использовать
task_briefingкак compact queue
Следствие:
- teammate migration почти не требует semantic renaming
- основной prompt migration blocker сейчас именно на lead path, а не на teammate path
C. Current task_briefing renderer lives in taskStore
🎯 9 🛡️ 9 🧠 4
Факт из кода:
tasks.taskBriefing(...)просто проксирует вtaskStore.formatTaskBriefing(...)
Следствие:
- нельзя делать
lead_briefingкак второй независимый renderer рядом в store - сначала нужен общий structured agenda DTO в controller-owned layer, и только потом два text renderer поверх него
D. Local .d.ts shims already drift in concrete ways
🎯 10 🛡️ 9 🧠 3
Факт из кода:
src/types/agent-teams-controller.d.tsиmcp-server/src/agent-teams-controller.d.tsуже расходятся не только "вообще", а по конкретным методам и формам- примеры:
- main shim не знает
tasks.memberBriefing(...) - main shim не знает
tasks.getTaskComment(...) - main shim не знает
review.startReview(...) - main shim не знает
runtime lookupMessage(...)типизирован по-разному между shim файлами
- main shim не знает
Следствие:
- любой rollout нового agenda/helper surface обязан идти вместе с type-sync gate
- иначе документ будет обещать безопасную миграцию, а монорепа получит тихий type/runtime drift
E. lead_briefing лучше, чем lead_queue, именно в Phase 1
🎯 9 🛡️ 9 🧠 3
Причина выбора:
- текущие briefing surfaces уже текстовые и хорошо ложатся в prompt workflow
lead_briefingсемантически ближе к уже существующимmember_briefingиtask_briefing- JSON/queue-style surface можно добавить позже отдельным mode или отдельным tool, не мешая Phase 1 rollout
Итог:
- в Phase 1 canonical lead surface называем
lead_briefing - название
lead_queueне используем как primary tool name в первой фазе
F. Lead name inference still drifts from shared lead detection
🎯 9 🛡️ 8 🧠 3
Факт из кода:
- controller-side
inferLeadName(...)ищет lead по ad hoc правилам и даже может упасть вconfig.members[0] - shared
leadDetectionutilities живут по другому контракту и не должны тихо расходиться с queue semantics
Следствие:
- нельзя делать
lead_briefingtool, который зависит от обязательногоleadNameinput или от жёсткого "угадай одного lead actor" до того, как это выровнено - role-scoped
lead_briefingбезопаснее, чем name-scoped surface
G. Как lead_briefing должен резолвить lead identity
1. Role-based lead surface without required leadName input
🎯 10 🛡️ 10 🧠 3
Примерный объём: 20-40 строк изменений
Суть:
lead_briefing- это role-scoped surface- tool не требует
leadName - внутри response можно вернуть display header с каноническим lead name, если он надёжно резолвится, но routing не зависит от этого имени
Почему это лучший вариант:
- lead agenda одна на команду, а не на произвольного actor name
- не заставляет новый tool зависеть от слабого
inferLeadName(...)как от hard requirement - снижает шанс ложных ошибок из-за alias drift
Edge cases, которые этот выбор должен переживать без падения:
- в конфиге нет ни одного явно резолвимого lead candidate
- в конфиге несколько lead-like actors и single canonical name не выводится надёжно
- исторические task payload всё ещё содержат owner/reviewer alias
team-lead
Во всех этих случаях:
lead_briefingвсё равно должен возвращать valid oversight queue- допустим generic header уровня
Lead queue - ошибка из-за ambiguous lead identity здесь считается неправильным поведением Phase 1
2. Require explicit leadName parameter
🎯 4 🛡️ 6 🧠 4
Примерный объём: 20-50 строк изменений
Суть:
- сделать
lead_briefing(memberNameLikeLead)по аналогии сtask_briefing(memberName)
Риск:
- лишняя зависимость от prompt/runtime name correctness
- появится ещё один alias-sensitive contract там, где role-based surface и так достаточен
3. Infer lead name hard and fail if ambiguous
🎯 5 🛡️ 7 🧠 5
Примерный объём: 40-80 строк изменений
Суть:
- заставить tool сначала выбрать один canonical lead name и падать при малейшей ambiguity
Риск:
- создаёт лишний bootstrap blocker для surface, которому имя лида не нужно для core routing
Выбор:
- для Phase 1 брать вариант 1
- отдельную canonical lead-name cleanup делать независимо от самого
lead_briefing
H. Public API width in Phase 0/1
1. Keep new snapshot/agenda helpers internal, expose only user-facing surfaces
🎯 9 🛡️ 10 🧠 3
Примерный объём: 20-50 строк изменений
Суть:
- structured agenda DTO и board snapshot helper живут как internal controller implementation detail
- наружу в public controller/tool surface добавляются только:
tasks.taskBriefing(...)tasks.leadBriefing(...)- расширенный
task_list(...)
Почему это лучший вариант:
- минимальный public blast radius
- меньше type-shim drift
- проще Phase 0 acceptance и rollback
2. Export generic getBoardSnapshot(...) immediately
🎯 5 🛡️ 7 🧠 5
Примерный объём: 40-90 строк изменений
Риск:
- второй consumer для этого API ещё не доказан
- придётся сразу синхронно расширять runtime export surface и оба локальных
.d.tsshims
3. Export both generic snapshot helper and structured agenda DTO
🎯 3 🛡️ 5 🧠 6
Примерный объём: 70-140 строк изменений
Риск:
- слишком раннее раскрытие внутреннего abstraction surface
- потом намного сложнее будет менять внутренний shape без downstream breakage
Выбор:
- для Phase 0/1 брать вариант 1
I. mcpToolCatalog сейчас group-scoped и не умеет безопасно скрывать lead_briefing внутри task
🎯 10 🛡️ 10 🧠 3
Факт из кода:
AGENT_TEAMS_MCP_TOOL_GROUPSопределяет teammate visibility на уровне whole group черезteammateOperationaltaskgroup сейчас teammate-operationallead_briefing, если просто добавить его вAGENT_TEAMS_TASK_TOOL_NAMES, автоматически попадёт вAGENT_TEAMS_TEAMMATE_OPERATIONAL_TOOL_NAMES- обе локальные
.d.tsдекларации сейчас знают только group ids:taskkanbanreviewmessageprocessruntimecrossTeam
Следствие:
- rollout
lead_briefingдолжен включать не только новый tool, но и отдельное catalog решение - safest Phase 1 choice:
- новая
leadgroup teammateOperational: false- синхронное обновление обеих
AgentTeamsMcpToolGroupIdдеклараций
- новая
- prompt migration без catalog migration здесь недостаточна
J. Board lock в controller path ещё не существует как реальный board primitive
🎯 10 🛡️ 9 🧠 4
Факт из кода:
- task/kanban write paths сегодня не обёрнуты в общий board lock
- существующий
withFileLockSync(...)живёт отдельно и используется не board path'ом, а cross-team inbox/outbox flow - сам primitive синхронный, busy-wait и имеет фиксированные таймауты
5sacquire /30sstale
Следствие:
- нельзя писать в плане просто "reuse existing lock" и считать, что board consistency уже почти есть
- нужен новый явный board contract:
- один lock на team board scope
- без silent unlocked fallback
- без ложного обещания, что low-level writes уже сериализованы сами по себе
K. review_request сейчас может оставить history/kanban drift при ошибке уведомления
🎯 10 🛡️ 10 🧠 4
Факт из кода:
review_request(...)делаетkanban.set -> task.history/reviewState update -> message send- если
message sendпадает, catch делает толькоkanban.clear - уже записанный
review_requestedhistory event не откатывается
Следствие:
- Phase 0 не должен строиться на предположении, что текущий rollback у review flow transactionally чистый
- safest fix:
- board mutation commit считать отдельной фазой
- уведомления отправлять post-commit как best-effort side effect
- queue semantics не должна зависеть от успеха inbox delivery
L. Main-side compatibility helpers уже расходятся с queue-safe reviewer contract
🎯 9 🛡️ 9 🧠 4
Факт из кода:
TeamDataService.attachKanbanCompatibility(...)берётreviewStateиз persisted fieldreviewerтам резолвится какkanbanTaskState.reviewer ?? history(review_approved|review_started|review_requested)
Следствие:
- agenda rollout нельзя строить через reuse этих compatibility semantics
- current-cycle queue resolver должен жить отдельно и явно
- если потом main/UI захочет тот же semantic surface, он должен звать официальный queue/inventory contract, а не копировать старую compatibility derivation
M. У task_list ещё нет настоящего backing contract для filters
🎯 10 🛡️ 9 🧠 3
Факт из кода:
- MCP
task_listсейчас делает только:controller.tasks.listTasks().map(slimTaskForList)
ControllerTaskApi.listTasks()типизирован без filters
Следствие:
- filters/limit для
task_listтребуют не только новую zod-схему в MCP tool - нужно отдельно выбрать semantic owner:
- dedicated controller inventory method
- или internal controller inventory helper
- иначе filtered
task_listполучится полуручным MCP-side overlay без нормального contract owner
N. Новая MCP group требует отдельного registration wiring, а не только catalog entry
🎯 10 🛡️ 10 🧠 3
Факт из кода:
mcp-server/src/tools/index.tsстроит registration черезREGISTRATION_BY_GROUP[group.id]- если добавить новый group id без этого map entry,
registerTools()получитundefined register - если наивно направить и
task, иleadgroup на один и тот жеregisterTaskTools, легко получить duplicate registration того же tool set
Следствие:
- rollout новой
leadgroup обязан синхронно решить registration strategy - safest path:
- либо отдельный
registerLeadTools - либо другой явный one-time registration design без дублирования task tools
- либо отдельный
- одного изменения
mcpToolCatalog.jsздесь недостаточно
O. Lead permission seed path сейчас завязан на teammate operational tool set
🎯 10 🛡️ 9 🧠 3
Факт из кода:
TeamProvisioningServiceв lead bootstrap spec кладётpermissionSeedToolsизAGENT_TEAMS_NAMESPACED_TEAMMATE_OPERATIONAL_TOOL_NAMES- runtime suggestion expansion и teammate settings seeding тоже завязаны на этот же список
- этот permission seed path вообще активируется только когда
skipPermissions === false - при
skipPermissions !== falselaunch идёт черезbypassPermissions, и availability tool'а определяется registration/runtime wiring, а не seed list
Следствие:
- если
lead_briefingуходит в новую non-teammate group, он не попадёт в lead bootstrap seed автоматически - prompt может требовать
lead_briefingкак first call раньше, чем runtime permission path к нему стабилизирован - rollout должен добавить отдельный lead-first seed contract, а не надеяться, что teammate list "и так почти подходит"
- при этом в плане надо различать 2 разных вопроса:
- tool available at runtime
- tool pre-allowed/seeded in permission-enabled mode
- смешивать availability и permission seeding в один пункт нельзя, иначе rollout обещает слишком много
Q. MCP preflight readiness сейчас зафиксирован только для member_briefing
🎯 10 🛡️ 9 🧠 3
Факт из кода:
TeamProvisioningServiceделает explicittools/list+tools/callvalidation дляmember_briefing- при отсутствии этого tool launch path падает как real bootstrap error
- аналогичного explicit readiness gate для
lead_briefingсейчас нет
Следствие:
- если
lead_briefingстановится canonical first call, это пока ещё не равно hard-validated launch invariant - Phase 1/1.5 должен выбрать один из честных путей:
- добавить role-aware required-tool preflight
- или сохранить explicit fallback до его появления
- нельзя писать в плане так, будто у lead path уже есть та же степень bootstrap гарантии, что и у teammate path
R. Для lead bootstrap list сейчас нет exported constant, аналогичного teammate constant
🎯 10 🛡️ 9 🧠 2
Факт из кода:
mcpToolCatalog.jsэкспортируетAGENT_TEAMS_NAMESPACED_TEAMMATE_OPERATIONAL_TOOL_NAMESTeamProvisioningServiceимпортирует именно этот exported constant- аналогичного exported constant для lead bootstrap surfaces сейчас нет
Следствие:
- если lead bootstrap tools выбрать правильно, их всё равно легко положить "временным локальным массивом" в runtime service
- это создаст новый source of truth рядом с catalog и tests
- safest path:
- экспортировать dedicated lead bootstrap constants из controller package root
- и уже их использовать в runtime/bootstrap wiring
S. Unreadable task rows сейчас silently выпадают из raw reader
🎯 9 🛡️ 9 🧠 4
Факт из кода:
taskStore.listRawTasks(...)обходит все task JSON files- при
normalizeTask(...)error такая строка просто пропускается - это значит, что malformed task row может исчезнуть из list-based views без явного сигнала
Следствие:
- derived queue не должна inherit'ить semantics "если задача не читается, притворимся, что её нет"
- особенно опасно это для lead surface, потому что board corruption превращается в silent omission
- safest Phase 0 path:
- queue-grade snapshot builder собирает
anomalies[] lead_briefingподнимает такие случаи как repair bucket / warning summarytask_briefingне делает вид, что board чист, если snapshot уже увидел unreadable rows
- queue-grade snapshot builder собирает
P. createController() автоматически публикует весь module.exports bound module'а
🎯 10 🛡️ 10 🧠 3
Факт из кода:
controller.jsделаетbindModule(context, tasks)поверх всегоmodule.exportsизinternal/tasks.js- значит любой новый export из
tasks.jsавтоматически становится method наcontroller.tasks
Следствие:
- internal agenda/inventory helpers нельзя просто "временно экспортнуть" из
tasks.js - иначе public controller surface начнёт расползаться почти случайно
- safest path:
- отдельный internal helper module
- или local non-exported functions в
tasks.js
4. Что не является целью
- не строим в Phase 1 полноценный event-sourced board engine
- не переносим source of truth в отдельный projection-файл
- не делаем сложный real-time delta protocol с первой итерации
- не пытаемся заменить
task_getподробной очередью - не делаем
task_listосновным рабочим API для тиммейта
5. Рассмотренные варианты
Вариант 1. Derived agenda on read + team lock + role queues
🎯 10 🛡️ 10 🧠 6
Примерный объём: 250-420 строк изменений
Суть:
- не создаём persisted projection как новый durable слой
- строим derived queue прямо во время чтения
- читаем raw task/review/kanban состояние под общим team-level lock
- делаем канонические queue surfaces для лида и участника
task_listпревращаем в filtered inventory
Плюсы:
- минимальный риск двойного source of truth
- проще доказать корректность
- быстрее rollout
- меньше шансов получить stale queue
Минусы:
- нет built-in delta sync с первого дня
- каждое queue чтение требует пересчёта
Вариант 2. Persisted projection sidecar с первого дня
🎯 8 🛡️ 7 🧠 6
Примерный объём: 320-520 строк изменений
Суть:
- при каждом mutation пересчитывать и сохранять projection-файл рядом с board state
Плюсы:
- быстрые чтения
- удобная база для future delta
Минусы:
- второй durable слой
- выше риск расхождения raw state и projection
- нужно аккуратно закрывать partial write / multi-file atomicity
Вариант 3. Сразу agenda + delta + persisted projection
🎯 6 🛡️ 5 🧠 8
Примерный объём: 450-750 строк изменений
Суть:
- сразу строить полноценный snapshot/revision/delta протокол
Плюсы:
- самая "умная" архитектура на бумаге
Минусы:
- это лучший способ слишком рано усложнить систему
- высокий риск словить subtle bugs в очередях
- большая цена ошибок, потому что именно queue tool влияет на поведение агентов
Выбор
Выбираем Вариант 1 как Phase 1, а delta/revision переносим в Phase 2.
Это даёт правильную последовательность:
- сначала стабилизируем семантику и ownership
- потом оптимизируем транспорт и payload
6. Архитектурный принцип
Правильный порядок слоёв такой:
-
Raw source of truth
- task files
- kanban-state
- review history / reviewState
-
Derived decision layer
actionOwnernextActionqueueCategorywatchersreasonCode
-
Role-based queue surfaces
- teammate agenda
- lead agenda via
lead_briefing
-
Inventory/search surface
- filtered
task_list
- filtered
-
Full-detail surface
task_get
⚠️ Важно: политика работы должна жить в layer 2, а не в голове LLM.
7. Канонические tool surfaces
7.1 member_briefing
Назначение:
- identity
- роль
- рабочие правила
- как пользоваться board tooling
Не должно включать:
- полный живой список задач
- большие live queue payload
Причина:
- bootstrap-инструмент не должен разрастаться вместе с board state
- иначе он станет одновременно и prompt bootstrap, и live queue, и будет быстро протухать
7.2 task_briefing
Назначение:
- каноническая compact operational queue для конкретного участника
Новый смысл:
- не просто "мои assigned tasks"
- а "что мне сейчас делать и что мне важно знать"
Структура ответа:
- identity / actor
actionableawareness- компактные counters
revisionв future phase
⚠️ Важное уточнение после code review:
- в текущем runtime
task_briefingвозвращает текст, а не JSON - поэтому safest Phase 0/1 path такой:
- внутри строим structured agenda DTO
- наружу по-прежнему рендерим компактный текст для совместимости
- JSON-variant можно добавлять позже как отдельный surface или новый output mode
Так мы не блокируем future delta/revision, но и не делаем лишний breaking change в самом начале.
Exact Phase 1 contract:
- input:
teamNamememberName- обязателен
- output:
- text briefing
- semantics:
- actor-specific
- primary teammate operational surface
- built from internal structured agenda DTO, а не напрямую из legacy formatter logic
Дополнительное архитектурное правило:
taskStore.formatTaskBriefing(...)в нынешнем виде не должен становиться permanent semantic owner новой agenda logic- правильный direction такой:
- controller-owned structured agenda DTO
- текстовый renderer для
task_briefing - текстовый renderer для
lead_briefing
7.3 lead_briefing
Нужен отдельный lead surface.
Выбор имени для Phase 1:
- canonical tool name:
lead_briefing
Почему не lead_queue:
- текущие briefings уже текстовые
- prompt ecosystem уже заточен на human-readable briefing surfaces
- это уменьшает объём breaking changes в MCP descriptions и lead prompts
- future structured/JSON variant можно добавить позже без конфликта имён
Почему не надо использовать raw task_list как lead queue:
- лид тоже не должен разгребать весь inventory каждый ход
- lead queue должна показывать именно:
- unassigned
- reviewer missing
- clarification needed
- dependency repair
- review routing anomalies
- waiting on user
- aggregate board pressure
Рекомендация по rollout:
- вводим новый lead-specific surface отдельно
- в первой фазе это именно
lead_briefing, а не новый JSON queue contract - только после этого перестаём учить lead использовать
task_listкак first stop - не делаем silent redefinition
task_listв тот же самый момент
Exact Phase 1 contract:
- input:
teamName- без обязательного
leadName
- controller contract:
tasks.leadBriefing(): Promise<string>
- MCP contract:
lead_briefing { teamName, claudeDir? }
- output:
- text briefing
- semantics:
- role-scoped lead operational surface
- не зависит от точного caller alias
- может отображать resolved lead name в header, но routing не должен зависеть от него
- ambiguity handling:
- unique lead name найден -> можно показать его в header
- unique lead name не найден -> вернуть generic
Lead queueheader без ошибки
- catalog placement:
- отдельная
leadgroup teammateOperational: falselead_briefingне должен попадать вAGENT_TEAMS_TEAMMATE_OPERATIONAL_TOOL_NAMES
- отдельная
7.3.1 Bootstrap sequencing for lead_briefing
Здесь нельзя делать rollout "в любом порядке".
Безопасная последовательность такая:
- сначала зарегистрировать
lead_briefingкак отдельный tool surface:- новая
leadgroup - явный registration path в
mcp-server/src/tools/index.ts - синхронные
.d.tsupdates
- новая
- потом экспортировать отдельный lead bootstrap tool list рядом с teammate constants
- потом wired'ить lead permission seed path для режима, где
skipPermissions === false - потом выбрать один честный runtime contract для first-turn availability:
- либо role-aware preflight hard-check
- либо явный documented fallback
- только после этого переписывать lead prompt так, чтобы
lead_briefingбыл canonical first action
No-go rule:
- нельзя сначала написать в lead prompt "сначала вызови
lead_briefing", а потом уже разбираться, видит ли его runtime вообще - нельзя считать
bypassPermissionsmode доказательством того, что отдельный lead bootstrap path не нужен - availability, registration и permission seeding - это три разных слоя rollout
7.4 task_list
Новая роль:
- filtered inventory/search
- fallback browse tool
- не основной operational queue
Желаемое поведение:
- additive filters только по стабильным dimension'ам:
ownerstatusreviewStatekanbanColumnrelatedToblockedBylimit
- для routine workflow агентам в prompt рекомендуем не
task_list, аtask_briefing
Что сознательно не добавляем в первой фазе:
memberfilterreviewerfilter
Причина:
memberслишком двусмысленный: owner, actionOwner, watcher или просто "как-то связан"reviewerв текущем runtime не является стабильным raw inventory field- actor-centric queries должны идти через
task_briefingиlead_briefing, а не через перегруженныйtask_list
Уточнение по фильтрам:
statusфильтрует rawtask.statusreviewStateфильтрует effective review statekanbanColumnв первой фазе допустим только как alias для review overlay columns:reviewapproved
- не надо вводить generic
column=todo|in_progress|done, будто inventory layer уже знает full kanban model для всех задач relatedToиblockedByдолжны принимать обычный task ref в том же формате, что иtask_get/resolveTaskRef
Правила фильтрации:
- фильтры conjunctive, а не "любой из"
limitприменяется после фильтрации- отсутствие фильтров сохраняет совместимую unfiltered semantics
- filtered
task_listне сортирует по agenda-priority и не подменяетactionOwner - output остаётся slim inventory JSON, а не agenda DTO
- если filter требует kanban knowledge (
reviewState,kanbanColumn), он реализуется в controller-owned inventory layer, а не через rawtaskStore.listTasks()напрямую
Exact Phase 1 MCP contract:
task_list { teamName, claudeDir?, owner?, status?, reviewState?, kanbanColumn?, relatedTo?, blockedBy?, limit? }
Exact backing rule:
- raw
tasks.listTasks()не надо тихо переопределять как filtered inventory API - safest path:
- dedicated controller inventory contract
- или internal controller-owned inventory helper, который является semantic owner для MCP tool
- MCP layer не должен сам становиться permanent owner review/kanban-aware filtering logic
Output-shape migration rule:
- текущий
slimTaskForList(...)blocklist-подход приемлем только как legacy compatibility starting point - целевой compact contract для
task_listдолжен быть explicit allowlist inventory row - migration на allowlist делается явно и с contract tests, а не тихо "когда-нибудь потом"
Public-surface rule for Phase 0/1:
task_briefinglead_briefingtask_list
это public MCP/controller surfaces.
А вот:
- structured agenda DTO
- generic board snapshot helper
остаются internal implementation details, пока у них не появится второй доказанный consumer.
7.4.1 Target inventory row for task_list
Чтобы migration away from blocklist была однозначной, у task_list нужен целевой public row contract уже в плане.
Target V1 row:
type TaskInventoryRow = {
id: string;
displayId: string;
subject: string;
status: 'pending' | 'in_progress' | 'completed' | 'deleted';
owner?: string;
reviewState: 'none' | 'review' | 'needsFix' | 'approved';
needsClarification?: 'lead' | 'user';
blockedBy?: string[];
blocks?: string[];
related?: string[];
commentCount: number;
createdAt?: string;
updatedAt?: string;
};
Важно:
- здесь
reviewState- это effective inventory review state, а не слепой passthrough сырогоtask.reviewState - inventory row deliberately не включает:
commentshistoryEventsworkIntervalsattachmentspromptsourceMessage- произвольные future fields по blocklist-инерции
- allowlist должен быть закрытым и тестируемым
- если позже реально понадобится ещё одно поле, его добавляют осознанно через contract change, а не "оно само просочилось"
Уточнение по migration safety:
- в раннем rollout лучше сначала добавить фильтры и новый
lead_briefing - а default unfiltered semantics
task_listоставить совместимой, пока prompts и callers не переедут - teammate catalog можно урезать раньше, чем глобально менять meaning самого инструмента
7.5 task_get
Роль остаётся прежней:
- полный контекст одной конкретной задачи
- history/comments/dependencies/files/clarifications
Новая практика:
- queue surface выдаёт компактные карточки и ссылки на task ids
- детали читаются через
task_get
8. Derived read model
8.1 Raw входы
Derived layer должен смотреть на:
task.statustask.ownertask.dependenciestask.reviewState- history events по review / status / clarification
- kanban column
- reviewer pool availability
- runtime identity actor
- queue roster snapshot
8.1.1 History-derived vs field-derived signals
Это важное уточнение после code review.
Не все workflow-сигналы в текущей системе одинаково "историчны":
- history-derived
- review state
- review cycle transitions
- work status transitions
- field-derived
- current owner
- current
needsClarification - current dependency lists
- current related links
Практическое следствие:
- review resolver можно и нужно строить на
historyEvents - owner/clarification resolver нельзя делать так, будто у него такой же append-only history contract внутри task JSON
- task logs и transcript activity могут быть полезны для диагностики, но не должны становиться required input для queue derivation в Phase 0/1
- actor identity normalization обязательна:
- реальное имя лида
team-leadдолжны считаться одним logical actor там, где речь идёт о valid ownership/reply semantics
- roster validity должна проверяться по одной канонической queue-side нормализации, а не по смешению controller и UI resolver heuristics
Ещё одно важное следствие:
- queue semantics и stall-monitor semantics не обязаны совпадать 1-в-1
- для queue review может быть actionable уже с
review_requested - для stall-monitor "started review" может начинаться только с
review_started
Это не конфликт, а разные вопросы:
- queue отвечает "кто должен сделать следующий шаг"
- stall-monitor отвечает "есть ли доказательство, что review реально начали"
8.1.2 Hard exclusions for Phase 0/1
Чтобы derived queue не строилась на сигналах, которые код сегодня не поддерживает как надёжные:
- не используем
kanban-state.tasks[taskId].reviewerдля routing - не используем
kanban.reviewers[]как assignment конкретного reviewer - не используем task logs / transcript activity как required input
- не выводим mandatory review из free-form role текста вроде
reviewer,qa,tech-lead
8.1.3 Signal trust matrix
Важное правило: derived queue не "усредняет" все сигналы. У каждого сигнала должен быть свой trust level.
| Signal | Role in queue | Trust level in Phase 0/1 |
|---|---|---|
historyEvents.review_* текущего цикла |
active review routing | authoritative |
task.reviewState |
fallback review signal | advisory fallback |
kanban-state.tasks[taskId].column |
overlay hint for review/approved | advisory fallback |
task.owner |
current accountable implementer | authoritative after roster normalization |
task.needsClarification |
current wait reason | authoritative after explicit-clear hardening |
task.blockedBy / blocks / related |
current dependency graph | authoritative but target refs must be revalidated |
kanban.reviewers[] |
reviewer pool / candidate set | advisory only, never per-task routing |
kanban-state.tasks[taskId].reviewer |
per-task reviewer | forbidden for routing in Phase 0/1 |
| task logs / transcript activity | diagnostics | debug-only |
Практическое правило:
- если два сигнала противоречат друг другу, выигрывает более trusted source
- weaker signal можно использовать только как fallback, но не как "доказательство против" stronger signal
8.1.4 Conflict resolution when sources disagree
Ниже не просто примеры, а конкретные implementation guardrails:
| Conflict | Winner | Why |
|---|---|---|
historyEvents говорит, что review активен, а task.reviewState === none |
history | review cycle уже durable в истории |
review_approved или review_changes_requested уже записан, но kanban всё ещё выглядит как review |
history | stale kanban overlay не должен держать review открытым |
task.reviewState === review, но current-cycle reviewer не резолвится |
lead assign_reviewer |
open review без валидного reviewer хуже, чем false confident routing |
needsClarification выставлен и одновременно есть owner/reviewer routing |
clarification | вопрос/блокер сильнее обычного execution flow |
| owner невалиден, но review reviewer валиден | lead assign_owner |
потерян accountable owner, Phase 0/1 чинит ownership раньше обычного routing |
requestReview durable commit прошёл, а notification send упал |
committed board state | queue truth не должна зависеть от доставки сообщения |
Дополнительный guardrail:
- derived queue не пытается "склеивать" conflicting truth из
task.reviewStateиkanbanв новый synthetic state - если resolver не может безопасно доказать ownership/reviewer path, он обязан деградировать в lead repair bucket
8.2 Derived поля
Минимальный набор:
type DerivedActionOwner =
| { kind: 'member'; memberName: string }
| { kind: 'lead' }
| { kind: 'user' }
| { kind: 'none' };
type DerivedNextAction =
| 'execute'
| 'review'
| 'apply_changes'
| 'assign_owner'
| 'assign_reviewer'
| 'clarify_with_user'
| 'clarify_with_lead'
| 'repair_dependencies'
| 'wait_dependency'
| 'wait_review'
| 'none';
type DerivedQueueCategory = 'actionable' | 'waiting' | 'oversight' | 'done';
8.2.1 Minimal internal agenda DTO for Phase 0/1
Чтобы text renderers не собирали семантику каждый по-своему, у внутреннего DTO должен быть один минимальный контракт:
type AgendaItem = {
taskId: string;
displayId: string;
subject: string;
status: 'pending' | 'in_progress' | 'completed' | 'deleted';
reviewState: 'none' | 'review' | 'needsFix' | 'approved';
actionOwner: DerivedActionOwner;
nextAction: DerivedNextAction;
queueCategory: DerivedQueueCategory;
reasonCode: string;
owner?: string;
reviewer?: string | null;
blockedBy?: string[];
watchers?: string[];
needsClarification?: 'lead' | 'user';
lastMeaningfulEventAt?: string;
derivedFrom?: string[];
};
type AgendaAnomaly = {
code:
| 'unreadable_task'
| 'invalid_dependency_ref'
| 'invalid_owner_ref'
| 'invalid_reviewer_ref';
detail: string;
taskId?: string;
};
type AgendaSnapshot = {
actor:
| { kind: 'member'; memberName: string }
| { kind: 'lead' };
actionable: AgendaItem[];
awareness: AgendaItem[];
anomalies: AgendaAnomaly[];
counters: {
actionable: number;
awareness: number;
blocked: number;
waitingOnUser: number;
waitingOnLead: number;
reviewNeeded: number;
anomalies: number;
};
};
Важно:
- это internal contract для derivation + rendering
task_briefingиlead_briefingрендерятся из негоtask_listиз него не рендерится и не обязан совпадать с ним по shape
Дополнительно полезно:
reasonCodewatchersrelatedMemberNamesblockedByreviewerlastMeaningfulEventAtderivedFrom
Где derivedFrom - это внутренний debug/verification след:
history_review_requestedhistory_review_startedkanban_stateclarification_flagdependency_graphowner_status
Он не обязателен в финальном публичном payload, но сильно помогает тестировать resolver и объяснять спорные queue decisions.
Ограничение по lastMeaningfulEventAt:
- это поле нельзя делать опорой для routing
- его можно использовать для sorting / summaries / tie-breaks
Причина:
- часть важных изменений живёт только в current task fields и
updatedAt - а не в полноценном append-only workflow event stream
8.2.2 reasonCode should use a closed v1 taxonomy
Если reasonCode оставить "любой строкой", resolver и renderer очень быстро начнут drift'ить.
Минимальный закрытый v1 набор лучше зафиксировать сразу:
waiting_user_clarificationwaiting_lead_clarificationowner_missingowner_invalidreview_reviewer_missingreview_requested_waiting_pickupreview_in_progressdependency_brokendependency_waitingneeds_fixowner_executingowner_readycompleted_no_followupterminal_approvedterminal_deletedanomaly_unreadable_task
Это не значит, что список никогда не расширится.
Это значит:
- расширение должно быть явным
- snapshot tests и text renderers должны работать от одной allowlist reason codes
- новая строка reasonCode считается contract change, а не случайной внутренней деталью
8.3 Что такое actionOwner
actionOwner - это один primary actor, который должен сделать следующий meaningful workflow шаг.
Это не:
- просто owner
- просто reviewer
- просто последний писавший комментарий
Это именно ответ на вопрос:
кто сейчас должен сдвинуть задачу вперёд
8.4 Что такое watchers
watchers - это не primary routing, а вспомогательная visibility-модель.
Их роль:
- awareness
- summaries
- future notifications
- optional context in lead queue
Их нельзя использовать как главный способ строить operational queue, иначе лид или участник снова начнут видеть почти всё подряд.
9. Базовый precedence для actionOwner
Ниже канонический порядок принятия решения. Более раннее правило сильнее позднего.
9.1 Terminal
Если задача:
- deleted
- approved
- окончательно завершена без ожидаемого follow-up
Тогда:
actionOwner = nonenextAction = none- в operational queue не попадает
- остаётся доступной через inventory/search
9.2 Clarification from user
Если задача явно ждёт ответа от пользователя:
actionOwner = usernextAction = clarify_with_user- лид получает oversight item
- owner получает awareness item, если задача его
9.3 Clarification from lead
Если задача ждёт решения/уточнения от лида:
actionOwner = leadnextAction = clarify_with_lead
Phase 0 guardrail:
- пока clarification semantics стабилизированы через explicit clear, комментарий лида сам по себе не считается достаточным для снятия wait-state
- это сознательная консервативность: лучше лишний lead oversight item, чем скрытый неочищенный blocker
9.4 Invalid owner
Если owner отсутствует, пустой, удалён или не резолвится в roster:
actionOwner = leadnextAction = assign_owner
Уточнение:
owner === "team-lead"нельзя автоматически считать invalid- alias
team-leadи canonical lead name должны проходить через одну lead-normalization логику
9.5 Review requested, reviewer unresolved
Если задача ушла в review, но reviewer не удалось надёжно определить:
actionOwner = leadnextAction = assign_reviewer
9.6 Review requested, reviewer resolved
Если review активен и reviewer валиден:
actionOwner = member(reviewer)nextAction = review- owner получает awareness item
wait_review
Дополнительная проверка валидности reviewer обязательна:
- если reviewer помечен как removed member
- или reviewer больше не резолвится в текущий roster
то задача должна деградировать в:
actionOwner = leadnextAction = assign_reviewer
Уточнение:
- reviewer нельзя выводить только из
kanban-state.reviewers[0]как будто это стабильное назначение на конкретную задачу - для queue нужен отдельный resolver активного reviewer именно для текущего review cycle
- safest precedence:
- latest
review_started.actorвнутри текущего review cycle - latest
review_request.reviewerвнутри текущего review cycle - иначе unresolved ->
assign_reviewer
- latest
Здесь есть важное ужесточение после повторного code review:
- в Phase 0/1 не надо делать fallback в
kanban-state.tasks[taskId].reviewer - текущие write-path на входе в review всё равно пишут туда
null - значит такой fallback только создаст ложное ощущение надёжности, но не даст реальной пользы
Практическая реализация должна быть совместима с уже существующей history-based derivation review state:
function resolveCurrentCycleReviewer(task, validMembers) {
const events = [...(task.historyEvents ?? [])];
for (let i = events.length - 1; i >= 0; i -= 1) {
const event = events[i];
if (event.type === 'review_started' && isValidQueueMember(event.actor, validMembers)) {
return { reviewer: event.actor, source: 'history_review_started_actor' };
}
if (
event.type === 'review_requested' &&
isValidQueueMember(event.reviewer, validMembers)
) {
return { reviewer: event.reviewer, source: 'history_review_requested_reviewer' };
}
if (event.type === 'review_approved' || event.type === 'review_changes_requested') {
break;
}
if (event.type === 'status_changed' && event.to === 'in_progress') {
break;
}
if (event.type === 'task_created') {
break;
}
}
return { reviewer: null, source: 'none' };
}
Консервативное правило:
- если resolver сомневается, задача идёт в lead queue как
assign_reviewer - лучше false positive в lead queue, чем false confident routing на не того reviewer
И ещё один важный guardrail:
- даже если в
kanban-stateschema есть полеreviewer, Phase 0/1 не должны считать его canonical signal - пока официальный mutation surface не поддерживает reviewer как стабильно обновляемое per-task поле, history-first resolver остаётся единственно правильной базой
Дополнительные edge rules:
- если
review_started.actorиreview_requested.reviewerрасходятся, выигрываетreview_started.actorкак более сильное доказательство того, кто реально взял review - если reviewer совпадает с owner, queue не пытается "исправить" это сама - self-review сегодня не запрещён runtime-контрактом, значит это отдельная policy-задача, а не routing-эвристика
- если последний
review_requested.reviewerилиreview_started.actorбольше не проходит queue roster normalization, routing деградирует вassign_reviewer, а не в попытку взять "первого доступного reviewer из пула"
9.7 Broken dependencies
Если есть dependency на несуществующую, deleted или испорченную задачу:
- не делаем auto-unblock
actionOwner = leadnextAction = repair_dependencies
9.8 Healthy blocking dependency
Если задача блокируется живой dependency:
actionOwner = nonenextAction = wait_dependency- owner получает awareness
- лид может видеть это только в aggregate summary или при stall/escalation
9.9 Needs fixes after review
Если был review_request_changes или equivalent state:
actionOwner = member(owner)nextAction = apply_changes
9.10 In progress
Если задача реально исполняется и ничего выше не сработало:
actionOwner = member(owner)nextAction = execute
9.11 Pending with owner
Если задача pending и готова к работе:
actionOwner = member(owner)nextAction = execute
9.12 Completed without active review
Если задача completed, reviewer не нужен или ещё не инициирован:
- в Phase 0/1 по умолчанию
actionOwner = none - completed task не должна автоматически превращаться в
assign_reviewerтолько из-за reviewer pool или free-form reviewer role в команде
Важно:
- пока в системе нет explicit machine-readable review policy, queue не должна придумывать обязательность review из эвристик
- review routing начинается только с явного review signal:
review_requestedreview_startedreviewState === review- future explicit review policy field, если он когда-то появится
10. Критические edge cases
10.1 Self-review
Если reviewer === owner, это плохой routing.
Правильное поведение:
- не давать такой задаче стать нормальной review-card для owner
- отправлять её в lead queue как
assign_reviewer - reasonCode:
self_review_invalid
10.2 Несколько reviewers
Если когда-то появится multi-review:
- Phase 1 не должен пытаться распределить actionOwner между несколькими людьми
- если активных reviewers > 1, это lead attention item, пока не появится формальная multi-review модель
Иначе будет неочевидный primary owner.
10.3 Missing reviewer после review_request
Даже если текущий runtime допускает review_request без reviewer, queue layer не должен делать вид, что всё нормально.
Правильное поведение:
- lead action item
- не "висит у owner"
- не "висит ни у кого без объяснения"
10.3.1 Previous review cycle bleed-through
Отдельный риск:
- если reviewer выводится из history слишком грубо, можно случайно подтянуть reviewer из предыдущего review cycle
Поэтому queue resolver должен быть cycle-aware:
- смотреть на последние review events
- учитывать reset после возврата задачи в
in_progress/ нового рабочего цикла - не использовать старого approver как текущего reviewer только потому, что это последнее известное review-событие
10.3.2 Review requested but not started yet
Это отдельный важный sub-state.
Если:
- review уже request'нут
- reviewer валиден
- но
review_startedещё не было
то для queue это всё равно reviewer-owned actionable item:
actionOwner = member(reviewer)nextAction = reviewreasonCodeстоит различать, напримерreview_requested_waiting_pickup
Но при этом нельзя механически переносить сюда stall-monitor semantics:
- для stall policy review окно может считаться ещё не "started"
- для queue reviewer уже является тем, кто должен сделать следующий шаг
10.4 Missing/deleted dependency
Это не wait-state. Это broken workflow.
Правильное поведение:
- лид получает repair item
- owner не должен автоматически считать задачу разблокированной
10.5 Комментарий на completed/review/approved задаче
Новый комментарий сам по себе не меняет actionOwner.
Иначе будут ложные reopening-сигналы.
Комментарий может:
- обновить
lastMeaningfulEventAt - появиться в awareness
Но не должен автоматически переводить задачу в actionable queue другого актёра.
10.6 Owner changed mid-review
Если owner поменяли, пока review активен:
- actionOwner остаётся reviewer, пока review открыт
- новый owner видит awareness
- после
review_request_changesилиreview_approveуже действует обычная пост-review логика
10.6.1 Removed member as owner/reviewer
Если owner или reviewer указывает на участника, который уже в removedNames:
- queue не должна считать такого actor valid action owner
- это не waiting state, а routing repair case
Правильное поведение:
- removed owner -> lead queue
assign_owner - removed reviewer -> lead queue
assign_reviewer
Причина:
resolveTeamMembers(...)уже умеет исключать removed members из активного roster- значит derived agenda должна использовать ту же реальность, а не слепо верить старому имени в task field/history
10.7 Runtime identity не определился
После code review это не такой большой риск для task_briefing, как казалось сначала, потому что текущий MCP tool уже требует явный memberName.
Поэтому правильная формулировка такая:
- для
task_briefingambiguity runtime identity не должна быть primary problem - для
member_briefingfallback по runtime identity остаётся полезным bootstrap-path - если появится future tool без явного
memberName, он уже не должен молча опираться на неуверенную runtime identity
Если system не смогла надёжно вывести memberName там, где identity всё же нужна:
- queue tool не должен молча вернуть
No tasks - нужно:
- либо требовать
memberName - либо возвращать явную ошибку identity resolution
- либо требовать
Тихой пустой очереди быть не должно.
10.8 Внешние записи мимо controller
Если кто-то пишет в raw файлы вне контроллера:
- derived read path всё равно должен уметь читать tolerant snapshot
- но team-level lock гарантирует consistency только для controller-driven mutations
Это важно явно зафиксировать, чтобы не переоценить силу lock.
10.9 Race при multi-file update
Самый опасный баг в этой зоне:
- reader увидел уже обновлённый task
- но ещё старый kanban/review state
или наоборот.
Именно поэтому Phase 1 лучше строить вокруг:
- общего mutation coordination
- общего snapshot чтения под одним team-level lock
10.10 Unreadable / corrupt task row
Это отдельный failure mode, который нельзя трактовать как "задачи просто нет".
Если queue-grade reader не может нормализовать task row:
- snapshot builder должен записать anomaly
lead_briefingдолжен показать repair/warning signaltask_briefingне должен молча превращать board problem в "No tasks"
Важное уточнение:
- tolerant snapshot допустим
- silent omission - нет
Самая опасная версия бага здесь такая:
- damaged task исчезает из inventory
- никто не видит, что board уже частично испорчен
- derived queue выглядит "чище", чем реальное состояние команды
11. Как должны выглядеть очереди
11.1 Очередь участника
Участник должен получать не "все свои задачи", а такой shape:
{
"actor": { "kind": "member", "memberName": "alice" },
"actionable": [
{
"taskId": "task-12",
"displayId": "12",
"subject": "Implement sync retries",
"actionOwner": { "kind": "member", "memberName": "alice" },
"nextAction": "execute",
"queueCategory": "actionable",
"reasonCode": "owner_ready",
"reviewer": null,
"blockedBy": []
}
],
"awareness": [
{
"taskId": "task-9",
"displayId": "9",
"subject": "API auth refactor",
"actionOwner": { "kind": "member", "memberName": "bob" },
"nextAction": "review",
"queueCategory": "waiting",
"reasonCode": "waiting_review"
}
],
"anomalies": [],
"counters": {
"actionable": 1,
"awareness": 1,
"blocked": 0,
"waitingOnUser": 0,
"waitingOnLead": 0,
"reviewNeeded": 0,
"anomalies": 0
}
}
Ключевая идея:
- actionable должно быть коротким и операционным
- awareness должно быть ещё компактнее и не засорять reasoning
11.2 Очередь лида
Лид должен получать не full board, а priority bucket:
- needs owner assignment
- needs reviewer assignment
- needs clarification from lead
- broken dependency graph
- waiting on user
Важно:
stalled review / stalled workне должны становиться primary lead bucket самого agenda resolver в Phase 0/1- stall-сигналы можно потом добавить как отдельное summary/enrichment, если они уже приходят из существующего stall-monitor surface
Плюс summary:
- сколько задач у кого actionable
- сколько pending review
- сколько blocked
- сколько orphaned / anomalous
Лид потом уже по id прицельно открывает task_get или filtered task_list.
11.3 Inventory
task_list остаётся полезным, но только как:
- browse/search
- audit/debug
- filtered discovery
Не как "starting point for every turn".
12. Почему watchers нужны, но не должны править миром
Полезные watcher-кейсы:
- owner должен знать, что его задача ушла на review
- лид должен знать, что задача ждёт user input
- reviewer может быть watcher до момента formal review assignment
Но если строить queue по watchers, получится:
- лидер снова видит почти весь board
- участник получает слишком много secondary state
- LLM начинает путать "надо делать" и "полезно знать"
Поэтому правило такое:
- routing делается по
actionOwner - watchers идут только во вторичный слой visibility
13. Locking и consistency model
13.1 Что вводим
В Phase 0 вводим controller-owned team-level board lock для операций, которые меняют:
- task files
- review routing
- kanban state
- derived queue snapshot reads
Но lock должен жить на правильном уровне:
- не внутри каждой мелкой low-level функции
- а вокруг композитных board operations
- и вокруг queue snapshot read, который хочет увидеть консистентный cross-file state
Рекомендуемая реализация:
- новый controller-owned primitive вида
withTeamBoardLock(paths, fn) - lock file рядом с team state, например в
teamDir - reuse можно брать только как low-level идею file-based exclusivity, но не как слепое обещание, что текущий primitive уже подходит без contract hardening
После дополнительного code review это важно уточнить жёстче:
- существующий
withFileLockSync(...)sync-only и busy-wait - его текущие таймауты
5sacquire /30sstale сами по себе ещё не являются доказанным board contract - Phase 0 не должен молча падать обратно на unlocked read/write, если board lock не взят
Минимальный board-lock contract для плана:
- один lock на board scope команды, а не набор несвязанных task-file lock'ов
- lock timeout должен отдавать явную ошибку mutation/read caller'у, а не скрытый unlocked fallback
- lock держим только вокруг canonical board files и derived snapshot build
- уведомления и прочие non-board side effects не должны бесконечно удерживать board lock
13.1.1 Mutation classes and exact commit boundary
Чтобы rollout не был расплывчатым, полезно разделить board operations на 3 класса:
Class A - routing-affecting task mutations
task_set_ownertask_set_statustask_starttask_completetask_set_clarificationtask_add_comment, если комментарий меняет queue-visible state илиlastMeaningfulEventAt- dependency link/unlink, если оно меняет blocking graph
Правило:
- даже если durable write происходит в один task file, mutation всё равно должна проходить под board lock
- иначе queue snapshot под тем же contract не имеет единого serialization point
Class B - multi-file board mutations
review_requestreview_startreview_approvereview_request_changes- любые операции, которые меняют и task state, и kanban overlay, и возможно несколько task rows
Правило:
- весь board commit происходит под одним board lock
- lock release только после того, как durable board files уже записаны
Class C - post-commit side effects
- inbox/system notifications
- observability warnings
- expensive attachment copy/link work, если оно не влияет на queue routing
Правило:
- эти шаги идут после board commit
- они не имеют права отменять уже committed board mutation
13.2 Зачем
Чтобы queue read видел:
- либо старый консистентный state
- либо новый консистентный state
Но не невозможную смесь.
13.3 Почему этого достаточно для Phase 1
Потому что Phase 1 не добавляет второй durable projection.
Следовательно:
- нечему отдельно "протухать"
- нет projection write, который надо атомарно коммитить рядом с raw state
13.4 Ограничение
Если кто-то пишет в raw файлы вообще в обход controller, lock этого не предотвратит.
Но это допустимое ограничение, если:
- UI
- MCP tools
- planned task workflow
маршрутизируются через controller.
После дополнительного просмотра кода это допущение выглядит разумным:
- основные UI mutation paths уже идут через
getController(...).tasks/review/kanban - но это не значит, что в Phase 0 надо сразу переписать все main-side read paths под тот же lock
Отдельно важно:
- board читают не только MCP tools
- main-process сервисы вроде
TeamTaskReader,TeamKanbanManagerи stall-monitor snapshots тоже строят picture of truth из тех же файлов
Более правильный scope для первой фазы:
- queue projector читает state под controller-owned lock
- review/task multi-file mutations используют тот же controller-owned lock
- если позже какой-то non-controller consumer реально потребует agenda-grade snapshot, он должен идти через официальный snapshot API, а не копировать сырой read path
Иначе можно случайно раздуть rollout до общего I/O refactor и потерять фокус на агентском operational surface.
13.4.1 Notification boundary must be post-commit
Это отдельное ужесточение после просмотра review_request(...).
Для queue correctness опасно, когда board mutation и inbox notification живут в одном pseudo-transaction, но rollback умеет откатить только часть шагов.
Phase 0 rule:
- canonical board mutation commit завершается до отправки inbox/system notifications
- notification send считается best-effort side effect
- неуспешная доставка уведомления не должна оставлять board в "откаченном наполовину" состоянии
Практический смысл:
- queue semantics не зависит от того, дошло ли служебное сообщение
- если уведомление упало, нужен warning/observability signal, а не partial rollback board state
13.4.2 Failure contract must be explicit
У board path должен быть не только lock, но и понятная failure semantics.
Минимальный contract:
- lock acquire timeout -> explicit retryable error, никакого unlocked fallback
- board commit failed до durable write -> mutation error, side effects не запускаются
- board commit succeeded, side effect failed -> mutation считается committed, side effect failure попадает в warning/diagnostics
- snapshot reader встретил unreadable row -> snapshot помечает anomaly, а не делает вид, что board полностью здоров
Это особенно важно для review_request(...)-подобных flows:
- если task/history уже committed
- а notification не ушла
то queue обязана видеть committed review state, а не fictional rollback state
13.5 Почему clarification надо стабилизировать до agenda rollout
Это отдельный guardrail, потому что здесь уже есть расхождение между "как система себя описывает" и "как она реально работает".
Сейчас:
- prompts говорят, что
needsClarification: "lead"auto-clear'ится, когда отвечает lead - код в task store снимает этот флаг, когда комментирует любой не-owner автор
Риск:
- derived queue может решить, что лидер ответил и задача больше не ждёт clarification
- хотя фактически мог прокомментировать другой teammate или reviewer
Рекомендуемая нормализация перед полноценным agenda rollout:
- Phase 0 safe mode
- любой clarification clear'ится только через явный
task_set_clarification clear - queue не верит implicit auto-clear вообще
- любой clarification clear'ится только через явный
- Phase 1 optional ergonomics
- после стабилизации lead identity можно вернуть:
leadclarification clear на комментарий лидаuserclarification clear на комментарийuser
- после стабилизации lead identity можно вернуть:
Это важное изменение по сравнению с предыдущей версией плана:
- раньше мы пытались сразу сохранить удобство auto-clear
- теперь приоритет сдвинут в пользу надёжности и объяснимости
Практический вывод для rollout:
- в Phase 0
needsClarificationстановится надёжным routing-сигналом именно потому, что clear происходит только явно - ambiguous clarification больше не должен зависеть от того, кто случайно оставил комментарий
Минимальный implementation note:
- Phase 0: убрать implicit clear из routing expectation и синхронно обновить prompts/briefings
- если потом возвращать ergonomic auto-clear, делать это только на controller layer, а не внутри storage
No-go rule:
- нельзя оставлять situation, где prompt обещает "lead comment closes clarification", а queue layer всё ещё зависит от текущего store behavior "любой non-owner comment clears it"
13.6 Почему task logs не должны становиться queue dependency в Phase 0/1
После дополнительного code review видно, что task log / transcript слой уже умеет видеть:
task_set_ownertask_set_clarification- reviewer details в board activity
Но это не означает, что queue надо строить поверх логов.
Почему это плохая идея для первой фазы:
- логи тяжелее и дороже для read path
- это observability layer, а не canonical board state
- появится второй semantic source рядом с task/kanban state
- при расхождении будет очень сложно объяснить, почему board показывает одно, а queue решила другое
Правило для Phase 0/1:
- queue derivation строится только из canonical board state:
- task files
- kanban state
- roster resolution
- task logs допускаются только как:
- debug aid
- diagnostics
- future validation tooling
14. Phase rollout
Phase 0 - Hardening the weak signals first
Это новая обязательная фаза после дополнительного code review.
Её цель:
- не начинать agenda rollout поверх уже известных semantic cracks
Что делаем:
- вводим controller-owned
withTeamBoardLock(...)/getBoardSnapshot(...)для agenda reads и multi-file board mutations - выделяем отдельный queue-grade reviewer resolver, который работает только по текущему review cycle history
- выносим inbox/system notifications за board-state commit boundary, чтобы message delivery не ломала queue semantics частичным rollback'ом
- в Phase 0 переводим clarification semantics в explicit-clear-only mode и синхронно обновляем prompts/briefings
- фиксируем canonical queue roster normalization в controller layer, а не в нескольких runtime местах сразу
- создаём внутренний structured agenda DTO + text renderers для backward-compatible
task_briefingи новогоlead_briefing - выбираем настоящий semantic owner для filtered
task_listи не оставляем это MCP-local ad hoc логикой - фиксируем registration strategy для новой
leadgroup, чтобыregisterTools()не получил missing или duplicate registration path - экспортируем явный source of truth для lead bootstrap tool list рядом с existing teammate constants
- добавляем отдельный lead bootstrap permission seed path для
lead_briefingи других first-turn lead surfaces - выбираем lead bootstrap strategy честно:
- role-aware preflight
- или explicit fallback, пока preflight ещё не внедрён
- держим agenda/inventory helpers вне accidental public
controller.taskssurface - добавляем filters/limit в
task_list, не ломая пока его default meaning - синхронизируем local
.d.tscontracts дляagent-teams-controller, чтобы новые exports/tools не жили только в runtime без type surface - явно фиксируем phase rule: no inferred mandatory review without explicit policy signal
- переписываем lead prompt snippets в
TeamProvisioningService, чтобы canonical first call сталlead_briefing, а не rawtask_list - вводим queue-grade anomaly reporting, чтобы unreadable task rows не исчезали silently из board views
Критерий завершения Phase 0:
- у нас есть набор слабых сигналов, которые либо стабилизированы, либо явно помечены как unreliable
- после этого derived agenda уже можно строить на нормальной базе, а не на wishful thinking
Phase 0 exit gates:
- agenda resolver не использует per-task kanban reviewer
- review/task multi-file operations и agenda snapshot используют один controller lock contract
- board-state mutations не откатываются частично из-за ошибки inbox/system notification
- clarification больше не может тихо исчезнуть из queue из-за комментария произвольного teammate
- queue roster normalization зафиксирован тестами на
team-lead/lead, removed members, external recipients и generated ids - новая
leadgroup имеет реальный MCP registration path без duplicate tool registration - exported lead bootstrap constant существует и используется как source of truth
- lead bootstrap permission seed включает
lead_briefing, если prompt делает его first action - lead path либо валидирует
lead_briefingкак required tool, либо сохраняет явный fallback до такой валидации - agenda/inventory helper не утёк в public
controller.taskssurface случайным export'ом - filtered
task_listбольше не висит на неопределённой MCP-only semantics task_listdefault unfiltered semantics остаётся совместимой, несмотря на добавление filters/limit- lead prompt больше не рекламирует
task_listкак primary board entrypoint lead_briefingcontract зафиксирован как role-scoped tool без обязательногоleadName- generic board snapshot helper остаётся internal implementation detail
- queue snapshot больше не может silently терять unreadable task rows без warning/anomaly signal
Phase 1 - Semantic cleanup without persisted projection
Цель:
- правильно определить action routing
- сократить payload
- убрать путаницу между inventory и queue
Что делаем:
- добавляем derived resolver для
actionOwner/nextAction/reasonCode - строим новый queue projector поверх raw state
- используем controller-owned team-level lock для queue snapshot и board mutations
- перерабатываем
task_briefingпод operational agenda - добавляем отдельный
lead_briefing - ужимаем и фильтруем
task_list, но без слишком раннего silent semantic break - обновляем prompts, чтобы:
- teammates стартовали с
task_briefing - lead стартовал с
lead_briefing task_listиспользовался только при необходимости browse/search
- teammates стартовали с
- поэтапно меняем teammate operational tool catalog, чтобы
task_listперестал быть default teammate shortcut - переводим
task_listoutput с legacy blocklist semantics на explicit allowlisted inventory contract, когда callers уже готовы к явному переходу - только если когда-то появится explicit review policy, рассматриваем более сильный post-complete review routing
Ожидаемый результат:
- агент сразу видит свой реальный action set
- лид видит routing issues и pressure points, а не весь board dump
- исчезает большая часть лишней token-нагрузки
Phase 1.5 - Compatibility and prompt hardening
Что делаем:
- сохраняем backward compatibility имени
task_briefing - если нужно, старый формат прячем за флагом или мягким переходом
- обновляем tool descriptions
- обновляем team provisioning instructions
- меняем lead prompts так, чтобы
lead_briefingстал canonical first call, аtask_listостался inventory/audit tool - teammate prompts дополнительно поджимаем, чтобы они не тянули
task_listбез явной причины - если role-aware lead preflight выбрали не сразу, сохраняем temporary explicit fallback path до завершения readiness hardening
- обновляем tests, которые сегодня закрепляют blocklist-semantics
task_list, чтобы transition был явным и осознанным
Phase 2 - Revision first, delta second
Важно: Phase 2 не должен начинаться, пока Phase 1 не покажет стабильную queue semantics.
Phase 2A - Revision / no-change short-circuit
Добавляем:
- stable queue
revision - возможность ответа
unchanged
Это уже даст экономию токенов и ускорение без сложного diff protocol.
Лучший practical path:
- повторить pattern, уже используемый в
feedRevision - строить
revisionкак stable hash от уже нормализованного compact agenda DTO - обязательно сортировать items детерминированно перед hashing
Что именно должно входить в revision payload:
- actor
- actionable task refs + minimal derived fields
- awareness task refs + minimal derived fields
- counters
- critical summary buckets for lead queue
Что не должно влиять на revision в первой версии:
- декоративный текст renderer
- локальные wording changes
- поля, которые не меняют queue semantics
Phase 2B - Optional delta sync
Делать только если реально нужно по профайлингу.
Возможный контракт:
- клиент передаёт
sinceRevision - если сервер может корректно отдать delta, отдаёт delta
- если нет, отдает full compact queue
Правило:
- delta должен быть оптимизацией транспорта
- а не единственным способом понять board state
15. Миграция и backward compatibility
Что не ломаем
task_getостаётся source of full detailsmember_briefingостаётся bootstrap tool- старые raw task files остаются валидными
Что меняется в поведении
task_briefingперестаёт быть простым owner list- lead перестаёт использовать full
task_listкак первую точку входа - teammate больше не зависит от общего списка задач команды
- reviewer routing становится более явным и меньше зависит от "угадай reviewer из косвенных полей"
- clarification queue semantics в первой фазе становятся deliberately explicit, а не "магически auto-cleared"
Совместимость
Если где-то старый runtime ещё вызывает task_briefing с ожиданием owner-only semantics, это обычно безопасно:
- новая agenda всё ещё покажет owned actionable items
- просто дополнительно добавит правильный awareness
task_list compatibility надо трактовать отдельно и жёстче:
- Phase 0/1:
- можно сохранить совместимую unfiltered semantics
- но filters/limit уже должны идти через выбранный semantic owner, а не через случайный MCP overlay
- Phase 1/1.5:
- переход к explicit allowlisted inventory row делается как осознанный contract change
- tests и prompt/docs migration должны идти в той же фазе, а не постфактум
Отдельный compatibility note:
- новые controller exports / tool names / helper contracts нельзя добавлять только в runtime код
- нужно синхронно обновлять:
src/types/agent-teams-controller.d.tsmcp-server/src/agent-teams-controller.d.ts- runtime export surface
После повторного code review здесь уже есть конкретные известные расхождения, а не абстрактный риск:
- main shim не знает
tasks.memberBriefing(...) - main shim не знает
tasks.getTaskComment(...) - main shim не знает
review.startReview(...) - main shim не знает
runtime lookupMessage(...)типизирован по-разному в двух shim файлах
Именно поэтому новый public controller contract в этой задаче должен быть узким:
- добавить
tasks.leadBriefing(): Promise<string> - сохранить
tasks.taskBriefing(memberName): Promise<string> - не добавлять generic public
getBoardSnapshot(...)в ту же фазу
Иначе получится неприятный класс ошибок:
- код работает локально в одном слое
- но типы и другой слой monorepo продолжают жить со старым контрактом
16. Что конкретно важно протестировать
16.1 Unit tests на resolver
Нужны table-driven тесты для сценариев:
- pending + owner
- in_progress + owner
- completed + reviewer missing
- review + reviewer resolved
- review + reviewer missing
- review + stale reviewer from previous cycle
- review requested but not started yet
- review column entry exists but per-task kanban reviewer is null
- self-review
- removed owner
- removed reviewer
- canonical lead name vs
team-leadalias - zero explicit lead candidates still yields valid lead queue
- multiple lead-like members do not make
lead_briefingfail - external inbox recipient does not become valid queue member
- generated/internal pseudo-agent id does not become valid queue member
- completed task + reviewer pool configured + no explicit review event
- waiting on user clarification
- waiting on lead clarification
- clarification cleared by wrong actor
- healthy dependency block
- broken dependency
- needsFix after review
- approved/deleted terminal
- unreadable task row produces anomaly instead of silent omission
16.2 Snapshot tests на queue surfaces
Проверить отдельно:
- teammate actionable/awareness
- lead action buckets
- filtered inventory outputs
- anomaly summary surfaces in lead-facing outputs when board rows are unreadable
lead_briefingtext output не дублирует весь board и не превращается в disguisedtask_listlead_briefingoutput не зависит от обязательногоleadNameinput
16.3 Concurrency tests
Проверить:
- multi-file mutation не даёт невозможного mixed snapshot
- read под lock не ломает корректность
- stale lock cleanup не создаёт ложных успешных чтений
- board lock timeout не приводит к silent unlocked fallback
review_requestиreview_request_changesне оставляют queue в промежуточном ambiguity state- ошибка inbox/system notification после board commit не оставляет history/kanban drift
- unreadable task row не теряется silently и поднимается как anomaly
- новая
leadgroup не ломаетregisterTools()и не вызывает duplicate registration существующих task tools - exported lead bootstrap constant совпадает с реальным runtime usage path, а не дублируется локальным списком
- если lead path делает
lead_briefinghard first action, readiness preflight действительно валидирует его наличие - текстовый renderer
task_briefingне расходится со structured agenda DTO
16.4 Prompt-path tests
Проверить, что team prompts реально подталкивают:
- member ->
task_briefing - lead ->
lead_briefing - details ->
task_get
И отдельно зафиксировать:
TeamProvisioningServiceбольше не содержит lead hintList all tasks: task_list ...как primary recommendation- новый lead hint рекомендует
lead_briefingраньше, чемtask_list - если lead prompt делает
lead_briefinghard-first, launch/preflight path тоже проверяет этот tool, а не только prompt text
16.5 Contract tests and type sync
Проверить:
- local
.d.tsshim declarations синхронны с реальным runtime surface AGENT_TEAMS_TEAMMATE_OPERATIONAL_TOOL_NAMESотражает новый teammate access policylead_briefingявно отсутствует вAGENT_TEAMS_TEAMMATE_OPERATIONAL_TOOL_NAMES- если вводится новая
leadgroup, обе локальныеAgentTeamsMcpToolGroupIdдекларации знаютlead - если выбран public controller inventory method, обе локальные
.d.tsдекларации знают и его точный contract - у каждого
AgentTeamsMcpToolGroupIdесть валидный registration path в MCP layer - exported lead bootstrap tool constants синхронны между runtime package surface,
.d.tsи фактическим bootstrap usage - lead bootstrap permission seed contract отдельно зафиксирован:
lead_briefingдоступен для first-turn lead flow- seed path не зависит только от teammate operational list
- seed path трактуется отдельно от general runtime availability при
bypassPermissions
- lead bootstrap readiness contract отдельно зафиксирован:
- teammate path hard-checks
member_briefing - lead path не объявляется equally strict, пока не добавлен explicit preflight или documented fallback
- teammate path hard-checks
- accidental helper exports не появляются в
createController(...).tasks, если они не объявлены частью public contract task_listtests меняются осознанно и явно фиксируют transition semantics, а не ломаются побочным эффектом- queue-side roster normalization behaviour зафиксирован явно и не drift'ит незаметно относительно UI expectations
task_listfilter contract зафиксирован отдельно:- only stable filters
- conjunctive semantics
limitafter filtering- no hidden agenda ordering
task_listoutput contract зафиксирован отдельно:- explicit allowlisted inventory row as target contract
- migration away from legacy blocklist is explicit and tested
- queue anomaly contract зафиксирован отдельно:
- unreadable task rows surface as anomaly
- lead-facing outputs do not silently hide board corruption
TeamProvisioningServicelead prompt contract зафиксирован отдельно:lead_briefingрекомендуют раньшеtask_list- строка
List all tasks: task_list ...больше не используется как primary recommendation
lead_briefingpublic contract зафиксирован отдельно:- no required
leadName - text surface in Phase 1
- role-scoped semantics, not name-scoped semantics
- no required
16.6 Non-goals enforcement tests
Проверить:
- queue derivation не зависит от task logs/transcript activity в Phase 0/1
- owner/clarification semantics не подменяются history heuristics там, где canonical signal - это текущее task field
- queue не копирует бездумно stall-monitor правило "open review window only after review_started"
17. Признаки того, что rollout удался
Хорошие сигналы
- lead перестал регулярно вызывать полный
task_listдля рутинной навигации - teammate получает короткий actionable набор
- меньше "забытых" задач в review без reviewer
- меньше orphaned tasks
- меньше случаев, где LLM делает неверный workflow шаг из-за неоднозначного контекста
Плохие сигналы
- queue стала слишком "умной" и начала скрывать важные задачи
- разные инструменты дают разное понимание одного и того же task state
- lead queue снова разрастается до pseudo-full-board
- delta/revision добавлены слишком рано и ломают простые сценарии
- clarification queue decisions всё ещё расходятся с реальным поведением board
- reviewer остаётся "магическим" и не объясняется понятным resolver precedence
17.1 Conservative Bias Rules
Если сигнал неоднозначен, queue system должна ошибаться в сторону безопасности:
- ambiguous reviewer -> lead queue, а не speculative reviewer routing
- ambiguous clarification clear -> lead oversight, а не скрытие wait-state
- ambiguous dependency integrity -> repair bucket у лида, а не auto-unblock
- ambiguous ownership ->
assign_owner, а не молчаливая подстановка watcher/last actor - unreadable task row -> anomaly / lead repair signal, а не тихое исчезновение из queue
Это очень важное правило для LLM-facing surface:
- false positive в lead queue обычно терпим
- false negative в actionable queue часто приводит к реально потерянной работе
18. Чего делать не надо
Не надо 1
Не надо сразу вводить persisted board-projection.json как обязательную истину.
Это красивее на схеме, но опаснее в реальности.
Не надо 2
Не надо строить primary queue по watchers.
Это почти гарантированный путь обратно к информационному шуму.
Не надо 3
Не надо считать, что фильтрованный task_list уже равен agenda.
Даже хороший фильтрованный inventory не заменяет явный actionOwner.
Не надо 4
Не надо молча возвращать пустую очередь, если runtime identity не определился.
Такие silent failures очень дорогие.
Не надо 5
Не надо строить Phase 1 на предположении, что текущие clarification и reviewer signals уже идеально надёжны.
Сначала их нужно harden или честно ограничить область применения.
Не надо 6
Не надо добавлять новый queue surface только в runtime код, забыв про tool catalog, local .d.ts и тестовый контракт.
Для этой зоны "почти работает" особенно опасно, потому что баг всплывает не сразу и не в одном месте.
Не надо 7
Не надо соблазняться task logs как "богаче сигналами" и тихо тащить их в основной queue resolver первой фазы.
Это почти гарантированный способ получить вторую, более дорогую и менее объяснимую source-of-truth плоскость.
Не надо 8
Не надо делать lead_briefing как второй независимый renderer рядом с taskStore.formatTaskBriefing(...).
Если teammate и lead surfaces будут собираться разными ad hoc formatter'ами, drift почти неизбежен.
Не надо 9
Не надо преждевременно экспортировать generic getBoardSnapshot(...) как public controller API.
Пока у него нет второго доказанного consumer, это только увеличит surface area и type-sync burden.
Не надо 10
Не надо вводить в task_list misleading filter вроде column=todo|in_progress|done, будто inventory layer уже стал полным kanban projection.
Для actor-centric и agenda-centric views есть task_briefing и lead_briefing.
Не надо 11
Не надо класть lead_briefing в existing task group и надеяться, что prompts сами спрячут его от тиммейтов.
Если доступность инструмента определяется catalog'ом, то и ограничение должно быть в catalog, а не только в тексте подсказок.
Не надо 12
Не надо оставлять task_list навсегда на blocklist-подходе только потому, что так проще пережить первую миграцию.
Иначе payload снова будет незаметно пухнуть при каждом новом task field, а inventory surface опять начнёт вести себя как полу-сырой dump.
Не надо 13
Не надо завязывать rollback board-state mutation на успех inbox/system notification.
Если notification упала, надо сигнализировать о delivery problem, а не оставлять history и kanban в полурасходящемся состоянии.
Не надо 14
Не надо добавлять новую lead group только в catalog и .d.ts, забыв про mcp-server/src/tools/index.ts.
Иначе rollout сломается не на semantics, а на missing registration path.
Не надо 15
Не надо считать teammate operational permission seed list автоматически подходящей базой для lead-first surfaces.
Как только появляется lead-only tool вроде lead_briefing, это предположение перестаёт быть надёжным.
Не надо 16
Не надо экспортить agenda/inventory helper из internal/tasks.js "временно", если он не должен быть public API.
При текущем bindModule(...) это уже не временный helper, а новый controller.tasks.* method.
Не надо 17
Не надо писать в prompt, что lead_briefing является обязательным first step для лида, если runtime path ещё не умеет это валидировать или честно fallback'ить.
Иначе получится фальшивая bootstrap-гарантия: текст обещает одно, а launch contract её ещё не держит.
Не надо 18
Не надо заводить lead bootstrap tool list как локальный массив в TeamProvisioningService, если catalog уже является source of truth для MCP surface.
Иначе первый же rename/regrouping даст тихий drift между catalog, permissions и bootstrap runtime.
Не надо 19
Не надо inherit'ить из текущего raw reader правило "unreadable task row можно просто пропустить".
Для queue это не tolerant behavior, а скрытая потеря board truth.
19. Рекомендуемый implementation order
- ввести controller-owned board lock primitive / snapshot API
- вынести board notifications в post-commit best-effort path
- перевести clarification routing в explicit-clear-only semantics и обновить prompts
- выделить queue-grade reviewer resolver для текущего review cycle
- формализовать queue roster normalization
- выделить общий derived resolver
resolveTaskActionState(...) - покрыть resolver table-driven тестами
- собрать structured agenda DTO + text renderers
- обновить
task_briefing - добавить
lead_briefing - завести отдельную
leadtool group и синхронно обновить local.d.tsgroup unions - завести для
leadgroup отдельный MCP registration path без duplicate task registrations - экспортировать dedicated lead bootstrap tool constants из controller package surface
- добавить explicit lead bootstrap permission seed contract
- выбрать lead bootstrap readiness path:
- explicit preflight
- или temporary documented fallback
- удержать agenda/inventory helpers вне accidental public controller surface
- выбрать backing contract для filtered
task_list - добавить filters/limit в
task_list, не ломая его default слишком рано - после migration readiness перевести
task_listна allowlisted inventory row contract - обновить tool descriptions и provisioning prompts
- после стабилизации добавить
revision - только потом решать, нужен ли
delta
19.1 Likely Change Surface
Наиболее вероятные точки изменения, если реализовывать этот план без лишнего расползания:
agent-teams-controller/src/internal/tasks.js- новый
lead_briefingsurface - controller-level agenda snapshot orchestration
- agenda renderer wiring
- новый
agent-teams-controller/src/internal/agenda.jsили аналогичный internal helper module- safest home для derived agenda/inventory helpers, которые не должны стать public controller methods автоматически
agent-teams-controller/src/internal/taskStore.js- storage-only cleanup
- legacy formatting only
- убрать из store implicit clarification policy как routing assumption
agent-teams-controller/src/internal/review.js- queue-grade reviewer resolution hooks
- board commit vs notification side-effect boundary
- при необходимости явнее фиксировать signals текущего review cycle
agent-teams-controller/src/internal/fileLock.js- база для controller-owned board lock primitive
agent-teams-controller/src/internal/runtimeHelpers.js- queue roster normalization
- lead alias normalization helpers
inferLeadName(...)scope reduction so role-scopedlead_briefingdoes not depend on weak name inference
src/shared/utils/leadDetection.ts- only if controller-side lead normalization is aligned with shared rules instead of ad hoc heuristics
src/shared/types/team.ts- only if future explicit review policy is introduced
- until then, no invented review-required field in Phase 0/1
mcp-server/src/tools/taskTools.ts- новый lead tool
- optional filters/limit для
task_list - совместимая эволюция
task_briefing - явный transition away from
slimTaskForList(...)blocklist semantics
mcp-server/src/tools/index.ts- registration wiring for new
leadgroup - защита от missing/duplicate group registration
- registration wiring for new
agent-teams-controller/src/mcpToolCatalog.js- phased teammate access policy for
task_list - отдельная
leadgroup - registration of
lead_briefing - exported lead bootstrap tool constants
- phased teammate access policy for
src/main/services/team/TeamProvisioningService.ts- prompt migration для lead/member flows
- lead bootstrap permission seed split from teammate operational seed
- role-aware MCP readiness validation or explicit fallback for
lead_briefing
agent-teams-controller/src/controller.js- only if public bind surface needs extra guardrails around what becomes
controller.tasks.*
- only if public bind surface needs extra guardrails around what becomes
src/types/agent-teams-controller.d.ts- local type shim sync for main app
mcp-server/src/agent-teams-controller.d.ts- local type shim sync for MCP package
mcp-server/test/tools.test.ts- explicit transition of
task_listexpectations
- explicit transition of
agent-teams-controller/test/controller.test.js- reviewer resolver and clarification semantics hardening
src/shared/utils/taskHistory.ts- при необходимости helper для current review cycle traversal
Принцип:
- сначала добавлять новые explicit surfaces
- только потом снижать роль старых ambiguous surfaces
20. Финальная формулировка решения
Итоговое решение, к которому пришли:
- Phase 0: hardening слабых сигналов (
lock,reviewer resolver,clarification semantics, compatible renderer) - Phase 1: derived projection on read как база
- Primary routing: через
actionOwner - Secondary visibility: через
watchers - Main teammate surface:
task_briefing - Main lead surface: отдельный
lead_briefing - Search/inventory:
task_listс filters/limit и постепенным уходом из роли default queue - Inventory contract:
task_listидёт к explicit allowlistedTaskInventoryRow, а не остаётся вечным blocklist dump - Consistency: controller-owned team-level lock around board mutations and queue snapshot reads
- Reviewer source: only current-cycle history in Phase 0/1
- Clarification source: explicit clear semantics first, convenience auto-clear only later if really needed
- Signal trust: history/task/kanban signals имеют явный priority order, queue не усредняет conflicting inputs
- Lead contract:
lead_briefingrole-scoped, without requiredleadName - Lead plumbing: отдельная
leadgroup, отдельный MCP registration path и отдельный lead bootstrap permission seed - Bootstrap sequencing: lead prompt становится hard-first only after registration + seed + readiness path are real
- Public API discipline: snapshot/DTO helpers stay internal until a second real consumer exists
- Export discipline: agenda/inventory helpers не должны случайно становиться
controller.tasks.*methods черезbindModule(...) - Board anomalies: unreadable task rows surface as anomalies, not as silent omissions
- Phase 2: сначала
revision, потом только при реальной необходимостиdelta
Это самый оптимальный баланс между:
- надёжностью
- предсказуемостью для агентов
- понятностью инструментария
- умеренной сложностью rollout
Если сформулировать совсем коротко:
Не надо учить агента самому вычислять board policy из сырых задач. Надо один раз правильно вывести
actionOwnerи отдать каждому актёру его компактную очередь.