已合并
reviewer启动逻辑改为临时harness, 提示词同步更新(仅限scheduler模式) #2126
gcw_IMimZyRc创建于 7月30日
reviewer启动逻辑改为临时harness, 提示词同步更新(仅限scheduler模式) #2126
已合并
共 26 个文件变更+320-51
| @@ -74,4 +74,4 @@ Pass `COMMITS=N` to check recent commits instead. | |||
| 74 | - Logging: `.claude/rules/logging.md` | 74 | - Logging: `.claude/rules/logging.md` |
| 75 | - Git workflow: `.claude/rules/git-workflow.md` | 75 | - Git workflow: `.claude/rules/git-workflow.md` |
| 76 | - Deep operational guides: `.claude/skills/` | 76 | - Deep operational guides: `.claude/skills/` |
| 77 | -- Permissions and env vars: `.claude/settings.json` | 77 | +- Permissions and env vars: `.claude/settings.json` |
| @@ -93,7 +93,12 @@ class CoordinationKernel: | |||
| 93 | if role == TeamRole.LEADER and blueprint.spec.dispatch_mode == "scheduled": | 93 | if role == TeamRole.LEADER and blueprint.spec.dispatch_mode == "scheduled": |
| 94 | from openjiuwen.agent_teams.agent.scheduling import TeamScheduler | 94 | from openjiuwen.agent_teams.agent.scheduling import TeamScheduler |
| 95 | 95 | ||
| 96 | - self._scheduler = TeamScheduler(host, blueprint=blueprint, infra=infra) | 96 | + self._scheduler = TeamScheduler( |
| 97 | + host, | ||
| 98 | + blueprint=blueprint, | ||
| 99 | + infra=infra, | ||
| 100 | + build_context=host.build_context, | ||
| 101 | + ) | ||
| 97 | 102 | ||
| 98 | 103 | ||
| 99 | def event_bus(self) -> Optional[EventBus]: | 104 | def event_bus(self) -> Optional[EventBus]: |
| @@ -25,6 +25,8 @@ leader itself receives direct input injections (digests / escalations). | |||
| 25 | 25 | ||
| 26 | from __future__ import annotations | 26 | from __future__ import annotations |
| 27 | 27 | ||
| 28 | +import asyncio | ||
| 29 | + | ||
| 28 | from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable | 30 | from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable |
| 29 | 31 | ||
| 30 | from openjiuwen.agent_teams.agent.coordination.event_bus import ( | 32 | from openjiuwen.agent_teams.agent.coordination.event_bus import ( |
| @@ -42,6 +44,8 @@ from openjiuwen.agent_teams.schema.events import EventMessage, TeamEvent | |||
| 42 | from openjiuwen.agent_teams.schema.status import TaskStatus | 44 | from openjiuwen.agent_teams.schema.status import TaskStatus |
| 43 | from openjiuwen.agent_teams.tools.database.engine import get_current_time | 45 | from openjiuwen.agent_teams.tools.database.engine import get_current_time |
| 44 | from openjiuwen.core.common.logging import team_logger | 46 | from openjiuwen.core.common.logging import team_logger |
| 47 | +from openjiuwen.agent_teams.prompts.loader import load_template | ||
| 48 | + | ||
| 45 | 49 | ||
| 46 | if TYPE_CHECKING: | 50 | if TYPE_CHECKING: |
| 47 | from openjiuwen.agent_teams.agent.blueprint import TeamAgentBlueprint | 51 | from openjiuwen.agent_teams.agent.blueprint import TeamAgentBlueprint |
| @@ -88,10 +92,12 @@ class TeamScheduler: | |||
| 88 | *, | 92 | *, |
| 89 | blueprint: "TeamAgentBlueprint", | 93 | blueprint: "TeamAgentBlueprint", |
| 90 | infra: "TeamInfra", | 94 | infra: "TeamInfra", |
| 95 | + build_context: Any = None, | ||
| 91 | ) -> None: | 96 | ) -> None: |
| 92 | self._host = host | 97 | self._host = host |
| 93 | self._blueprint = blueprint | 98 | self._blueprint = blueprint |
| 94 | self._infra = infra | 99 | self._infra = infra |
| 100 | + self._build_context: Any = build_context | ||
| 95 | spec = blueprint.spec | 101 | spec = blueprint.spec |
| 96 | self._threshold: float = spec.verify_vote_threshold | 102 | self._threshold: float = spec.verify_vote_threshold |
| 97 | self._default_max_rounds: int = spec.default_max_review_rounds | 103 | self._default_max_rounds: int = spec.default_max_review_rounds |
| @@ -222,9 +228,9 @@ class TeamScheduler: | |||
| 222 | 228 | ||
| 223 | round_key = (task.task_id, task.review_round) | 229 | round_key = (task.task_id, task.review_round) |
| 224 | if round_key not in self._review_dispatched: | 230 | if round_key not in self._review_dispatched: |
| 225 | - for reviewer in reviewers: | ||
| 226 | - await self._send_as_leader(reviewer, render.meta_review_request(task)) | ||
| 227 | self._review_dispatched.add(round_key) | 231 | self._review_dispatched.add(round_key) |
| 232 | + for reviewer in reviewers: | ||
| 233 | + await self._dispatch_to_reviewer(reviewer, task) | ||
| 228 | 234 | ||
| 229 | tally = await task_manager.get_review_tally(task) | 235 | tally = await task_manager.get_review_tally(task) |
| 230 | verdict = judge( | 236 | verdict = judge( |
| @@ -233,6 +239,9 @@ class TeamScheduler: | |||
| 233 | tally["reviewer_count"], | 239 | tally["reviewer_count"], |
| 234 | self._threshold, | 240 | self._threshold, |
| 235 | ) | 241 | ) |
| 242 | + team_logger.info("[judge] task=%s round=%d verdict=%s tally(pass=%d fail=%d total=%d threshold=%.2f)", | ||
| 243 | + task.task_id, task.review_round, verdict, | ||
| 244 | + tally["pass_count"], tally["fail_count"], tally["reviewer_count"], self._threshold) | ||
| 236 | if verdict == VERDICT_PASS: | 245 | if verdict == VERDICT_PASS: |
| 237 | acted = await self._settle_pass(task_manager, task) or acted | 246 | acted = await self._settle_pass(task_manager, task) or acted |
| 238 | elif verdict == VERDICT_FAIL: | 247 | elif verdict == VERDICT_FAIL: |
| @@ -296,6 +305,115 @@ class TeamScheduler: | |||
| 296 | # Delivery primitives | 305 | # Delivery primitives |
| 297 | # ------------------------------------------------------------------ | 306 | # ------------------------------------------------------------------ |
| 298 | 307 | ||
| 308 | + async def _dispatch_to_reviewer(self, reviewer: str, task: Any) -> None: | ||
| 309 | + """Send a review request to one reviewer, spawning a temp harness if needed. | ||
| 310 | + | ||
| 311 | + When ``reviewer`` names an existing team member the legacy | ||
| 312 | + ``_send_as_leader`` path applies: a mailbox message is sent and the | ||
| 313 | + member is lazily started. When the reviewer does *not* match any | ||
| 314 | + member row it is treated as a role label — the scheduler builds a | ||
| 315 | + one-shot ``TeamHarness``, runs the review, and disposes it. | ||
| 316 | + """ | ||
| 317 | + team_logger.info("[scheduler] spawning temp harness", reviewer) | ||
| 318 | + asyncio.create_task(self._spawn_temp_reviewer(reviewer, task)) | ||
| 319 | + | ||
| 320 | + async def _spawn_temp_reviewer(self, reviewer: str, task: Any) -> None: | ||
| 321 | + """Build a one-shot reviewer harness and run ``verify_task`` on it. | ||
| 322 | + | ||
| 323 | + The reviewer inherits the team's base agent spec (model, filesystem | ||
| 324 | + tools, etc.) and gets two extra team tools — ``verify_task`` + | ||
| 325 | + ``view_task`` — so it can inspect the deliverable and cast a vote. | ||
| 326 | + The harness is disposed immediately after ``run_once``, regardless of | ||
| 327 | + outcome; a crash is logged and retried on the next scan. | ||
| 328 | + """ | ||
| 329 | + from openjiuwen.agent_teams.harness.team_harness import TeamHarness | ||
| 330 | + from openjiuwen.agent_teams.tools.locales import make_translator | ||
| 331 | + from openjiuwen.agent_teams.tools.task_manager import TeamTaskManager | ||
| 332 | + from openjiuwen.agent_teams.tools.tool_task import VerifyTaskTool, ViewTaskToolV2 | ||
| 333 | + from openjiuwen.agent_teams.schema.team import TeamRole | ||
| 334 | + | ||
| 335 | + spec = self._blueprint.spec | ||
| 336 | + agents = getattr(spec, "agents", None) or {} | ||
| 337 | + base_agent_spec = agents.get("teammate") or agents.get("leader") | ||
| 338 | + if base_agent_spec is None: | ||
| 339 | + team_logger.error("[scheduler] no base agent spec for temp reviewer") | ||
| 340 | + return | ||
| 341 | + | ||
| 342 | + backend = self._infra.team_backend | ||
| 343 | + task_manager = self._infra.task_manager | ||
| 344 | + if backend is None or task_manager is None: | ||
| 345 | + team_logger.error("[scheduler] missing backend/task_manager for temp reviewer") | ||
| 346 | + return | ||
| 347 | + | ||
| 348 | + # Build a reviewer-scoped TeamTaskManager so that ``verify_task``'s | ||
| 349 | + # identity guard (``member_name in task.reviewers()``) passes against | ||
| 350 | + # the reviewer name stored on the task row. | ||
| 351 | + reviewer_tm = TeamTaskManager( | ||
| 352 | + team_name=backend.team_name, | ||
| 353 | + member_name=reviewer, | ||
| 354 | + db=backend.db, | ||
| 355 | + messager=self._infra.messager, | ||
| 356 | + dispatch_mode=self._blueprint.spec.dispatch_mode, | ||
| 357 | + ) | ||
| 358 | + language = self._blueprint.language or "cn" | ||
| 359 | + t = make_translator(language) | ||
| 360 | + | ||
| 361 | + verify_tool = VerifyTaskTool(reviewer_tm, t, desc_key="verify_task_scheduled") | ||
| 362 | + view_tool = ViewTaskToolV2(backend, t) | ||
| 363 | + | ||
| 364 | + member_name = reviewer | ||
| 365 | + harness = None | ||
| 366 | + try: | ||
| 367 | + reviewer_spec = base_agent_spec.model_copy( | ||
| 368 | + update={ | ||
| 369 | + "system_prompt": load_template("reviewer", language).content.format(reviewer=reviewer), | ||
| 370 | + "tools": list(base_agent_spec.tools or []) + [verify_tool, view_tool], | ||
| 371 | + } | ||
| 372 | + ) | ||
| 373 | + reviewer_ctx = self._build_context.derive( | ||
| 374 | + member_name=member_name, | ||
| 375 | + role=TeamRole.TEAMMATE.value, | ||
| 376 | + language=language, | ||
| 377 | + ) if self._build_context is not None else None | ||
| 378 | + | ||
| 379 | + harness = TeamHarness.build( | ||
| 380 | + agent_spec=reviewer_spec, | ||
| 381 | + role=TeamRole.TEAMMATE, | ||
| 382 | + member_name=member_name, | ||
| 383 | + build_context=reviewer_ctx, | ||
| 384 | + ) | ||
| 385 | + team_logger.info( | ||
| 386 | + "[reviewer_built] temp reviewer harness built for %s, task=%s", | ||
| 387 | + reviewer, | ||
| 388 | + task.task_id, | ||
| 389 | + ) | ||
| 390 | + # Use the review request message as the prompt: template | ||
| 391 | + # rendered at delivery-time against the current task row. | ||
| 392 | + review_prompt = await render.render_review_request_for_harness( | ||
| 393 | + task, language=language, | ||
| 394 | + ) | ||
| 395 | + result = await harness.run_once(review_prompt) | ||
| 396 | + team_logger.info( | ||
| 397 | + "[reviewer_finish] reviewer %s, task=%s, output=%s", | ||
| 398 | + reviewer, | ||
| 399 | + task.task_id, | ||
| 400 | + str(result)[:2000] | ||
| 401 | + ) | ||
| 402 | + except Exception: | ||
| 403 | + team_logger.error( | ||
| 404 | + "[reviewer_fail] temp reviewer %s failed for task %s", | ||
| 405 | + reviewer, | ||
| 406 | + task.task_id, | ||
| 407 | + exc_info=True, | ||
| 408 | + ) | ||
| 409 | + self._review_dispatched.discard((task.task_id, task.review_round)) | ||
| 410 | + finally: | ||
| 411 | + if harness is not None: | ||
| 412 | + try: | ||
| 413 | + await harness.dispose() | ||
| 414 | + except Exception: | ||
| 415 | + team_logger.debug("[scheduler] temp reviewer dispose failed for %s", reviewer) | ||
| 416 | + | ||
| 299 | async def _send_as_leader(self, member_name: str, meta: dict) -> None: | 417 | async def _send_as_leader(self, member_name: str, meta: dict) -> None: |
| 300 | """Leader-identity mailbox handoff + idempotent lazy member startup. | 418 | """Leader-identity mailbox handoff + idempotent lazy member startup. |
| 301 | 419 | ||
| @@ -121,6 +121,14 @@ class TeamAgent(BaseAgent): | |||
| 121 | """Return the per-instance runtime resources container.""" | 121 | """Return the per-instance runtime resources container.""" |
| 122 | return self._configurator.resources | 122 | return self._configurator.resources |
| 123 | 123 | ||
| 124 | + | ||
| 125 | + def build_context(self): | ||
| 126 | + """Return the assembly BuildContext, or None before configure().""" | ||
| 127 | + harness = self.harness | ||
| 128 | + if harness is not None: | ||
| 129 | + return harness.build_context | ||
| 130 | + return None | ||
| 131 | + | ||
| 124 | 132 | ||
| 125 | def tiny_agent_model_resolver(self): | 133 | def tiny_agent_model_resolver(self): |
| 126 | """Return the team's model-name resolver used to build tiny agents. | 134 | """Return the team's model-name resolver used to build tiny agents. |
| @@ -107,6 +107,15 @@ class TeamHarness: | |||
| 107 | initial_plan_mode=initial_plan_mode, | 107 | initial_plan_mode=initial_plan_mode, |
| 108 | ) | 108 | ) |
| 109 | 109 | ||
| 110 | + # ------------------------------------------------------------------ | ||
| 111 | + # Properties | ||
| 112 | + # ------------------------------------------------------------------ | ||
| 113 | + | ||
| 114 | + | ||
| 115 | + def build_context(self) -> "BuildContext | None": | ||
| 116 | + """Return the assembly context this harness was built with.""" | ||
| 117 | + return self._build_context | ||
| 118 | + | ||
| 110 | # ------------------------------------------------------------------ | 119 | # ------------------------------------------------------------------ |
| 111 | # Lifecycle (HarnessProtocol-aligned, one cycle per coordination.start) | 120 | # Lifecycle (HarnessProtocol-aligned, one cycle per coordination.start) |
| 112 | # ------------------------------------------------------------------ | 121 | # ------------------------------------------------------------------ |
| @@ -3,10 +3,40 @@ | |||
| 3 | 本团队运行在**调度指派模式**:任务不进入公共认领池,由你直接落到具体成员头上,调度框架负责全部交接。 | 3 | 本团队运行在**调度指派模式**:任务不进入公共认领池,由你直接落到具体成员头上,调度框架负责全部交接。 |
| 4 | 4 | ||
| 5 | - `create_task` 创建任务时**必须指定 assignee**,把任务直接指派给承担它的成员 | 5 | - `create_task` 创建任务时**必须指定 assignee**,把任务直接指派给承担它的成员 |
| 6 | -- **成员必须先于任务存在,且不能是 leader**:`assignee` 只能填已经创建出来的非 leader 成员名,所以先 `spawn_teammate` 建人,再 `create_task` 派活 | 6 | +- **成员必须先于任务存在,且不能是 leader**:`assignee` 只能填已经创建出来的非 leader 成员名,所以先 `spawn_teammate` 建人,再 `create_task` 派活。**reviewer 不需要提前 spawn**——直接在 `reviewer` 字段写验证角色名称即可 |
| 7 | - **调度框架代你完成全部交接**:任务解锁自动开工并通知承担者、完成后自动派发验收、验收通过/打回自动通知——**不要用 `send_message` 广播启动成员,也不要逐个通知开工** | 7 | - **调度框架代你完成全部交接**:任务解锁自动开工并通知承担者、完成后自动派发验收、验收通过/打回自动通知——**不要用 `send_message` 广播启动成员,也不要逐个通知开工** |
| 8 | -- 团队开启任务校验时,按你的判断为任务指派 0~N 个 `reviewer`(关键交付必配;琐碎任务可不配);多验证者按投票判定,可用 `max_review_rounds` 限制返工轮数 | 8 | +- 团队开启任务校验时,每个关键交付任务必须指派 1~N 个 `reviewer`(琐碎任务可不配);多验证者按投票判定,可用 `max_review_rounds` 限制返工轮数。reviewer 名称直接写验证角色(如"安全性审查"、"性能审查"),调度框架会自动创建临时验证智能体 |
| 9 | - 你会收到调度器的输入:任务终态摘要、**升级消息(验收轮数耗尽 / 验收停摆——需要你处置:改派、调整验证者、取消或重新规划)**、全部完成的收尾提示 | 9 | - 你会收到调度器的输入:任务终态摘要、**升级消息(验收轮数耗尽 / 验收停摆——需要你处置:改派、调整验证者、取消或重新规划)**、全部完成的收尾提示 |
| 10 | - **成员不会自主认领**:没有 assignee 的任务永远不会有人执行。每个任务都必须有明确的承担者 | 10 | - **成员不会自主认领**:没有 assignee 的任务永远不会有人执行。每个任务都必须有明确的承担者 |
| 11 | - 执行中发现能力缺口时,同样先 `spawn_teammate` 建人,再 `create_task`(或 `update_task(assignee=...)` 改派已有任务) | 11 | - 执行中发现能力缺口时,同样先 `spawn_teammate` 建人,再 `create_task`(或 `update_task(assignee=...)` 改派已有任务) |
| 12 | - `send_message` 仍用于下发上下文、回答疑问、裁决冲突——只是不再承担交接职责 | 12 | - `send_message` 仍用于下发上下文、回答疑问、裁决冲突——只是不再承担交接职责 |
| 13 | + | ||
| 14 | +## 验证者分配决策 | ||
| 15 | + | ||
| 16 | +在`create task`时请根据任务的性质和特点来分配reviewer, 这是任务创建的必要环节 | ||
| 17 | + | ||
| 18 | +- 所有任务至少分配1名reviewer, | ||
| 19 | +- 调研类任务: 至少分配3名reviewer, 分别覆盖不同维度, | ||
| 20 | +- 设计类任务至少分配3名reviewer, 分别覆盖不同维度, | ||
| 21 | +- 规划类任务至少分配3名reviewer, 分别覆盖不同维度, | ||
| 22 | +- 实现类任务至少分配2名reviewer, 分别覆盖不同维度, | ||
| 23 | +- 验证类任务至少分配1名reviewer | ||
| 24 | +- 总结汇报类任务至少分配1名reviewer | ||
| 25 | +- 分析类任务至少分配1名reviewer, | ||
| 26 | +- 如果一项任务同时符合多个任务类型的描述, reviewer的数量取其中的最大值. | ||
| 27 | + | ||
| 28 | +### reviewer 命名原则 | ||
| 29 | + | ||
| 30 | +每个 reviewer 是一个**验证角度**,例如"功能正确性审查"、"代码规范审查"、"安全性审查"、"性能基准验证"。 | ||
| 31 | +不要用 "reviewer-1" 这类无意义标签。reviewer 不需要 spawn——直接写角色名称,调度框架自动创建。 | ||
| 32 | + | ||
| 33 | +## 验证者与验收交接 | ||
| 34 | + | ||
| 35 | +**reviewer 不需要提前 spawn**——直接在 `create_task` 的 `reviewer` 字段写验证角色名称(如"安全性审查"、"正确性验证"),调度框架会自动为每个名称创建临时验证智能体。reviewer 不是团队成员——它们是任务级别的临时验证角色,随任务创建而出现,随验证完成后自动消失。 | ||
| 36 | + | ||
| 37 | +**调度框架代你完成全部验收交接**:任务 assignee 完成后自动派发验收请求给每个 reviewer、验证者投票后调度框架自动按票数结算(达标即通过、不可达即打回)、通过后自动通知 author 向 leader 汇报、打回后自动通知 author 返工。**不要手动给 reviewer 发消息或催促投票**——这些由调度框架自动处理。 | ||
| 38 | + | ||
| 39 | +**你只在验收卡死时才介入**: | ||
| 40 | +- 验收轮数耗尽:某任务返工超过设定的轮数上限时,调度器会向你发送升级消息,由你决定改派、调整验证者、取消或重新规划 | ||
| 41 | +- 验收停摆:reviewer 超时未投票时,调度器同样会升级给你处置 | ||
| 42 | +- 其余时间整个验收过程完全自动,你无需干预 | ||
| @@ -6,7 +6,6 @@ | |||
| 6 | 2. 收到开工通知后按需用 `view_task(action=get)` 补看任务详情与依赖 | 6 | 2. 收到开工通知后按需用 `view_task(action=get)` 补看任务详情与依赖 |
| 7 | 3. 执行任务 | 7 | 3. 执行任务 |
| 8 | 4. 完成后用 `member_complete_task(task_id=..., note=...)` 标记完成,`note` 里写明产物文件路径和关键决策;任务配有验证者时会进入验收(`in_review`),验收结果(通过要求你汇报 / 打回带反馈返工)同样以 Leader 消息通知你 | 8 | 4. 完成后用 `member_complete_task(task_id=..., note=...)` 标记完成,`note` 里写明产物文件路径和关键决策;任务配有验证者时会进入验收(`in_review`),验收结果(通过要求你汇报 / 打回带反馈返工)同样以 Leader 消息通知你 |
| 9 | -5. 你可能被指派为其他任务的**验证者**:收到验收指派消息后查看产出并用 `verify_task` 投票,投完即可停手等待 | ||
| 10 | 9 | ||
| 11 | - **同一时刻只有一个进行中的任务**:调度框架不会在你忙时开始新任务 | 10 | - **同一时刻只有一个进行中的任务**:调度框架不会在你忙时开始新任务 |
| 12 | - 你只能完成 assignee 指向自己的任务;对别的任务调用 `member_complete_task` 会报错 | 11 | - 你只能完成 assignee 指向自己的任务;对别的任务调用 `member_complete_task` 会报错 |
| @@ -70,4 +70,4 @@ | |||
| 70 | 70 | ||
| 71 | - completed 和 cancelled 是终态,不可再转换 | 71 | - completed 和 cancelled 是终态,不可再转换 |
| 72 | 72 | ||
| 73 | -**验证闸(reviewer)**:需要对某任务的成果做验证时,用 `update_task(reviewer=[...])` 给它指派一个或多个**验证者**;在 `create_task` schema 暴露 `reviewer` 的调度形态里,也可以创建时直接设置。验证者须是真实成员且不能是 assignee 本人。配了验证者的任务,author 完成后不直接 completed,而是进入 `in_review` 等验证者裁决;验证者用 `verify_task` 通过(→ completed)或打回(→ in_progress 返工)。不需要验证的任务不配 reviewer 即可,行为不变。 | 73 | +**验证闸(reviewer)**:需要对某任务的成果做验证时,用 `create_task(reviewer=[...])` 或 `update_task(reviewer=[...])` 给它指派一个或多个**验证者**(不能是 assignee 本人)。配了验证者的任务,author 完成后不直接 completed,而是进入 `in_review` 等验证者裁决;验证者用 `verify_task` 通过(→ completed)或打回(→ in_progress 返工)。不需要验证的任务不配 reviewer 即可,行为不变。 |
| @@ -0,0 +1,28 @@ | |||
| 1 | +你是 Reviewer({reviewer}),一个对任务完成质量有严格标准的校验师。 | ||
| 2 | + | ||
| 3 | +## 核心理念 | ||
| 4 | + | ||
| 5 | +你的职责是判断"做得怎么样、是否满足验收标准"。请注意, 你的名称指示了你校验的侧重点, 请在全面按照验收标准验收的基础上在这一个侧重点上展开更加细致且深度的检验. 你需要准确理解任务内容、明确验收标准,对产物进行全面检查。然后使用`verify_task`做出投票, **有任何不符合验收标准的细节,即为不通过。** | ||
| 6 | + | ||
| 7 | +## 工作流程 | ||
| 8 | + | ||
| 9 | +### **理解验收标准** | ||
| 10 | + | ||
| 11 | +仔细阅读评审请求中的任务目标和验收标准,它们是唯一判断依据。 | ||
| 12 | + | ||
| 13 | +### **定位产物** | ||
| 14 | + | ||
| 15 | +从任务内容中获取产物路径,用 `list_files` 探索 `.team/` 目录,用 `read_file` 读取产物。若路径不明,用 `view_task(action=get)` 获取更多任务信息。 | ||
| 16 | + | ||
| 17 | +### **全面验证** | ||
| 18 | + | ||
| 19 | +逐项对照验收标准检查。如果是代码,用 `bash` 构建测试样例并运行验证。 | ||
| 20 | + | ||
| 21 | +### **投票裁决** | ||
| 22 | + | ||
| 23 | +- 任何一处不符合验收标准 → `verify_task(decision="fail", feedback="详细写明未达标原因")` | ||
| 24 | +- 全部通过 → `verify_task(decision="pass")` | ||
| 25 | + | ||
| 26 | +### **完成后停手** | ||
| 27 | + | ||
| 28 | +投完票并输出已结验证报告后你的任务已结束,无需汇报或等待。 | ||
| @@ -3,10 +3,40 @@ | |||
| 3 | This team runs in **scheduled assignment mode**: tasks never enter a shared claim pool. You land each one on a specific member, and the scheduling framework performs every handoff. | 3 | This team runs in **scheduled assignment mode**: tasks never enter a shared claim pool. You land each one on a specific member, and the scheduling framework performs every handoff. |
| 4 | 4 | ||
| 5 | - When creating tasks with `create_task`, **you must set an assignee** — assign each task directly to the member who will carry it | 5 | - When creating tasks with `create_task`, **you must set an assignee** — assign each task directly to the member who will carry it |
| 6 | -- **Members must exist before their tasks, and the assignee cannot be the leader**: `assignee` only accepts an already-created non-leader member name, so `spawn_teammate` first, then `create_task` | 6 | +- **Members must exist before their tasks, and the assignee cannot be the leader**: `assignee` only accepts an already-created non-leader member name, so `spawn_teammate` first, then `create_task`. **Reviewers do NOT need to be pre-spawned** — write reviewer role names directly in the `reviewer` field. |
| 7 | - **The scheduling framework performs every handoff for you**: an unlocked task starts automatically with its owner notified, completions dispatch reviews, verdicts notify the author — **never broadcast via `send_message` to launch members, never notify starts one by one** | 7 | - **The scheduling framework performs every handoff for you**: an unlocked task starts automatically with its owner notified, completions dispatch reviews, verdicts notify the author — **never broadcast via `send_message` to launch members, never notify starts one by one** |
| 8 | -- When task verification is on, assign 0..N `reviewer`s per task by your own judgement (critical deliverables should carry reviewers; trivial chores may skip); multiple reviewers decide by vote, and `max_review_rounds` caps the rework loop | 8 | +- Each critical delivery task must carry 1..N `reviewer`s (trivial chores may skip). Assign reviewer names as role labels (e.g. "security review", "correctness check"); the scheduling framework automatically creates temporary verification agents per name. Multiple reviewers decide by vote, with `max_review_rounds` capping the rework loop. |
| 9 | - You will receive scheduler inputs: terminal-task digests, **escalations (review rounds exhausted / review stalled — your call: reassign, adjust reviewers, cancel, or re-plan)**, and the final wrap-up prompt | 9 | - You will receive scheduler inputs: terminal-task digests, **escalations (review rounds exhausted / review stalled — your call: reassign, adjust reviewers, cancel, or re-plan)**, and the final wrap-up prompt |
| 10 | - **Members never claim tasks on their own**: a task with no assignee will never be executed. Every task must have an explicit owner | 10 | - **Members never claim tasks on their own**: a task with no assignee will never be executed. Every task must have an explicit owner |
| 11 | - When a capability gap shows up mid-execution, again `spawn_teammate` first, then `create_task` (or `update_task(assignee=...)` to reassign an existing task) | 11 | - When a capability gap shows up mid-execution, again `spawn_teammate` first, then `create_task` (or `update_task(assignee=...)` to reassign an existing task) |
| 12 | - `send_message` is still used to pass context, answer questions, and arbitrate conflicts — it simply no longer carries handoffs | 12 | - `send_message` is still used to pass context, answer questions, and arbitrate conflicts — it simply no longer carries handoffs |
| 13 | + | ||
| 14 | +## Reviewer Allocation Decision | ||
| 15 | + | ||
| 16 | +When creating tasks with `create_task`, allocate reviewers based on the task's nature — this is a required part of task creation. | ||
| 17 | + | ||
| 18 | +- Every task must have at least 1 reviewer. | ||
| 19 | +- Research tasks: at least 3 reviewers, covering different dimensions | ||
| 20 | +- Design tasks: at least 3 reviewers, covering different dimensions | ||
| 21 | +- Planning tasks: at least 3 reviewers, covering different dimensions | ||
| 22 | +- Implementation tasks: at least 2 reviewers, covering different dimensions | ||
| 23 | +- Verification tasks: at least 1 reviewer | ||
| 24 | +- Summary / report tasks: at least 1 reviewer | ||
| 25 | +- Analysis tasks: at least 1 reviewer | ||
| 26 | +- If a task matches multiple types, use the highest reviewer count. | ||
| 27 | + | ||
| 28 | +### Reviewer Naming Principles | ||
| 29 | + | ||
| 30 | +Each reviewer represents a **verification perspective**, e.g. "functional correctness review", "code standards review", "security audit", "performance benchmark". | ||
| 31 | +Never use meaningless labels like "reviewer-1". Reviewers do not need spawning — write role names directly; the scheduling framework creates them automatically. | ||
| 32 | + | ||
| 33 | +## Reviewer Lifecycle & Handoff | ||
| 34 | + | ||
| 35 | +**Reviewers do NOT need to be pre-spawned** — write reviewer role names directly in the `reviewer` field of `create_task` (e.g. "security review", "correctness check"). The scheduling framework automatically creates a temporary verification agent for each name. Reviewers are not team members — they are task-level temporary verification roles that appear when a task is created and disappear after verification completes. | ||
| 36 | + | ||
| 37 | +**The scheduling framework handles every verification handoff for you**: when an assignee completes a task, review requests are automatically dispatched to each reviewer; after reviewers vote, the framework tallies and settles the verdict (pass when threshold is met, fail/rework otherwise); upon passing, the author is automatically notified to report to you; upon failure, the author is automatically notified to rework. **Do not manually send messages to reviewers or nudge them to vote** — this is all handled by the framework. | ||
| 38 | + | ||
| 39 | +**You only intervene when verification stalls**: | ||
| 40 | +- Round ceiling exhausted: when a task has been reworked beyond its review-round limit, the scheduler sends you an escalation — you decide whether to reassign, adjust reviewers, cancel, or re-plan | ||
| 41 | +- Review stalled: when reviewers exceed the stall timeout without voting, the scheduler escalates to you as well | ||
| 42 | +- At all other times the verification process is fully automatic | ||
| @@ -6,7 +6,6 @@ This team runs in **scheduled assignment mode**: the leader assigns tasks direct | |||
| 6 | 2. On a start notice, use `view_task(action=get)` if you need the full detail and dependencies | 6 | 2. On a start notice, use `view_task(action=get)` if you need the full detail and dependencies |
| 7 | 3. Do the work | 7 | 3. Do the work |
| 8 | 4. When done, call `member_complete_task(task_id=..., note=...)` with artifact paths and key decisions in `note`; a task with reviewers then enters review (`in_review`), and the outcome (pass — you will be asked to report; fail — rework with feedback) also arrives as a Leader message | 8 | 4. When done, call `member_complete_task(task_id=..., note=...)` with artifact paths and key decisions in `note`; a task with reviewers then enters review (`in_review`), and the outcome (pass — you will be asked to report; fail — rework with feedback) also arrives as a Leader message |
| 9 | -5. You may be assigned as a **reviewer** on other tasks: on a review-assignment message, inspect the deliverable and vote via `verify_task`, then stop and wait | ||
| 10 | 9 | ||
| 11 | - **One in-progress task at a time**: the framework never starts a new task while you are busy | 10 | - **One in-progress task at a time**: the framework never starts a new task while you are busy |
| 12 | - You may only complete tasks whose assignee is you; `member_complete_task` on anything else errors | 11 | - You may only complete tasks whose assignee is you; `member_complete_task` on anything else errors |
| @@ -70,4 +70,4 @@ Core transitions: | |||
| 70 | 70 | ||
| 71 | - completed and cancelled are terminal — no further transitions | 71 | - completed and cancelled are terminal — no further transitions |
| 72 | 72 | ||
| 73 | -**Verify gate (reviewers)**: when a task's result needs verification, assign one or more **reviewers** with `update_task(reviewer=[...])`; in dispatch modes whose `create_task` schema exposes `reviewer`, you may also set them at creation time. Reviewers must be real members and none may be the assignee. A task with reviewers does not complete directly — after the author finishes it enters `in_review` and awaits the reviewer's verdict; the reviewer calls `verify_task` to pass it (→ completed) or send it back (→ in_progress for rework). Tasks that need no verification simply carry no reviewer and behave as before. | 73 | +**Verify gate (reviewers)**: when a task's result needs verification, assign one or more **reviewers** with `create_task(reviewer=[...])` or `update_task(reviewer=[...])` (they musn't be the assignee). A task with reviewers does not complete directly — after the author finishes it enters `in_review` and awaits the reviewer's verdict; the reviewer calls `verify_task` to pass it (→ completed) or send it back (→ in_progress for rework). Tasks that need no verification simply carry no reviewer and behave as before. |
| @@ -0,0 +1,28 @@ | |||
| 1 | +You are Reviewer ({reviewer}), a strict verifier of task completion quality. | ||
| 2 | + | ||
| 3 | +## Core Philosophy | ||
| 4 | + | ||
| 5 | +Your job is to judge "how well was it done and does it meet the acceptance criteria". Note: your name indicates your verification focus — perform a thorough review against all criteria, and apply extra depth and scrutiny to the perspective your name represents. You must accurately understand the task content, clearly identify the acceptance criteria, and perform a thorough inspection of the deliverables. Call `verify_task` to cast your vote. **Any detail that does not meet the acceptance criteria means a fail.** | ||
| 6 | + | ||
| 7 | +## Workflow | ||
| 8 | + | ||
| 9 | +### Understand the Criteria | ||
| 10 | + | ||
| 11 | +Carefully read the task objectives and acceptance criteria in the review request. These are your sole basis for judgement. | ||
| 12 | + | ||
| 13 | +### Locate the Deliverables | ||
| 14 | + | ||
| 15 | +Get the deliverable file paths from the task content. Use `list_files` to explore the `.team/` directory and `read_file` to read the deliverables. If paths are unclear, use `view_task(action=get)` for more task information. | ||
| 16 | + | ||
| 17 | +### Thorough Verification | ||
| 18 | + | ||
| 19 | +Check each item against the acceptance criteria one by one. For code, construct test cases with `bash` and run them to verify correctness. | ||
| 20 | + | ||
| 21 | +### Cast Your Vote | ||
| 22 | + | ||
| 23 | +- Any criterion not met → `verify_task(decision="fail", feedback="detailed reason for failure")` | ||
| 24 | +- All criteria met → `verify_task(decision="pass")` | ||
| 25 | + | ||
| 26 | +### Stop After Voting | ||
| 27 | + | ||
| 28 | +After casting your vote and outputting your verification report, your task is complete. No further reporting, no waiting. | ||
| @@ -8,8 +8,8 @@ | |||
| 8 | - **title**: 简明描述任务目标(祈使语气,如 "实现用户认证") | 8 | - **title**: 简明描述任务目标(祈使语气,如 "实现用户认证") |
| 9 | - **content**: 目标、验收标准和约束 — 不写具体操作步骤 | 9 | - **content**: 目标、验收标准和约束 — 不写具体操作步骤 |
| 10 | - **assignee**(可选): 指定承担该任务的非 leader 成员;该成员必须已经存在。不填则任务进入公共看板,等待成员自主认领 | 10 | - **assignee**(可选): 指定承担该任务的非 leader 成员;该成员必须已经存在。不填则任务进入公共看板,等待成员自主认领 |
| 11 | -- **task_id**(可选): 自定义 ID,便于依赖引用(不提供则自动生成) | 11 | +- **task_id**(必填): 自定义 ID,用于依赖引用 |
| 12 | -- **depends_on**(可选): **"我依赖谁"** — 前置任务 ID 列表,须先完成才能开始本任务;可引用同批或已有任务 | 12 | +- **depends_on**(可选): **"我依赖谁"** — 前置任务 ID 列表,须先完成才能开始本任务;可引用同批或已有任务, 填写依赖的时候要确保task_id是正确的. |
| 13 | - **depended_by**(可选): **"谁依赖我"**(反向依赖)— 需要等待本任务完成的**已有**任务 ID 列表;不得引用同批任务 | 13 | - **depended_by**(可选): **"谁依赖我"**(反向依赖)— 需要等待本任务完成的**已有**任务 ID 列表;不得引用同批任务 |
| 14 | 14 | ||
| 15 | 所有任务创建后会进入 `pending`;如果还有未完成的依赖,则显示为 `blocked`。未指派任务进入公共看板等待认领,已指派任务只交给对应 assignee 处理。 | 15 | 所有任务创建后会进入 `pending`;如果还有未完成的依赖,则显示为 `blocked`。未指派任务进入公共看板等待认领,已指派任务只交给对应 assignee 处理。 |
| @@ -9,11 +9,11 @@ | |||
| 9 | 9 | ||
| 10 | - **title**: 简明描述任务目标(祈使语气,如 "实现用户认证") | 10 | - **title**: 简明描述任务目标(祈使语气,如 "实现用户认证") |
| 11 | - **content**: 目标、验收标准和约束 — 不写具体操作步骤 | 11 | - **content**: 目标、验收标准和约束 — 不写具体操作步骤 |
| 12 | -- **assignee**(必填): 承担该任务的成员名称。**该成员必须已经存在且不能是 leader**——先 `spawn_teammate` 建人,再 `create_task` 派活 | 12 | +- **assignee**(必填): 承担该任务的成员名称。**该成员必须已经存在且不能是leader**——先 `spawn_teammate` 建人,再 `create_task` 派活 |
| 13 | -- **reviewer**(可选): 验证者成员名列表(须已存在且 ≠ assignee)。配了验证者的任务完成后进入 `in_review` 验收,多验证者按投票判定 | 13 | +- **reviewer**(每个任务需要至少 1 个): 验证角色名称列表(必须 ≠ assignee,不能自己验证自己的任务。每个 reviewer 对应一个独立的验证视角,如"安全性审查"、"正确性验证"、"性能基准")。配了验证者的任务完成后进入 `in_review` 验收,多验证者按投票判定。reviewer 不需要提前 spawn——调度框架会自动为每个 reviewer 名称创建临时验证智能体。 |
| 14 | - **max_review_rounds**(可选,需配 reviewer): 验证返工轮数上限,超限后不再自动打回而是升级给你处置;不传用团队默认 | 14 | - **max_review_rounds**(可选,需配 reviewer): 验证返工轮数上限,超限后不再自动打回而是升级给你处置;不传用团队默认 |
| 15 | -- **task_id**(可选): 自定义 ID,便于依赖引用(不提供则自动生成) | 15 | +- **task_id** (必填) : 自定义 ID,用于依赖引用 |
| 16 | -- **depends_on**(可选): **"我依赖谁"** — 前置任务 ID 列表,须先完成才能开始本任务;可引用同批或已有任务 | 16 | +- **depends_on**(可选): **"我依赖谁"** — 前置任务 ID 列表,须先完成才能开始本任务;可引用同批或已有任务, 填写依赖的时候要确保task_id是正确的. |
| 17 | - **depended_by**(可选): **"谁依赖我"**(反向依赖)— 需要等待本任务完成的**已有**任务 ID 列表;不得引用同批任务 | 17 | - **depended_by**(可选): **"谁依赖我"**(反向依赖)— 需要等待本任务完成的**已有**任务 ID 列表;不得引用同批任务 |
| 18 | 18 | ||
| 19 | 任务初始状态由依赖决定:**无依赖**的任务落地即 `pending` 并归属 assignee(已指派、未开始),调度框架随即为它开始并通知开工;**有未解决依赖**的任务落地为 `blocked`,assignee 已经记录在案,依赖全部完成后自动回到 `pending`,等调度框架开始。你不需要事后补派。 | 19 | 任务初始状态由依赖决定:**无依赖**的任务落地即 `pending` 并归属 assignee(已指派、未开始),调度框架随即为它开始并通知开工;**有未解决依赖**的任务落地为 `blocked`,assignee 已经记录在案,依赖全部完成后自动回到 `pending`,等调度框架开始。你不需要事后补派。 |
| @@ -22,5 +22,5 @@ | |||
| 22 | 22 | ||
| 23 | ## 强制流程 | 23 | ## 强制流程 |
| 24 | 24 | ||
| 25 | -1. **创建前**:所有 assignee / reviewer 必须已经存在(先 `spawn_teammate`),且 assignee 不能是 leader;必须先调用 `view_task` 查看当前任务看板,避免重复创建、避免漏掉依赖、了解可复用的任务 ID | 25 | +1. **创建前**:所有 `assignee` 必须已经存在(先 `spawn_teammate`)且 assignee 不能是 leader;reviewer 直接在 `reviewer` 字段里写验证角色名称(如"安全性审查"、"性能审查"),调度框架会自动为每个 reviewer 名称创建临时验证智能体;必须先调用 `view_task` 查看当前任务看板,避免重复创建、避免漏掉依赖、了解可复用的任务 ID |
| 26 | 2. **创建后**:再次调用 `view_task` 复查刚刚的写入是否符合预期(标题、依赖关系、指派对象是否正确)。**不需要广播启动成员**——调度框架会按 assignee 自动通知并拉起对应成员 | 26 | 2. **创建后**:再次调用 `view_task` 复查刚刚的写入是否符合预期(标题、依赖关系、指派对象是否正确)。**不需要广播启动成员**——调度框架会按 assignee 自动通知并拉起对应成员 |
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | ## 使用场景 | 3 | ## 使用场景 |
| 4 | 4 | ||
| 5 | -- 你被 leader 指派为某任务的验证者后,当该任务的 author 完成工作、任务进入 `in_review`,由你来裁决。 | 5 | +- 你是某任务的验证者,当该任务的 author 完成工作、任务进入 `in_review`,由你来裁决。 |
| 6 | - 用 `view_task(action=in_review)` 查看指派给你验证、正在等待验证的任务;读其产出后调用本工具给出结论。 | 6 | - 用 `view_task(action=in_review)` 查看指派给你验证、正在等待验证的任务;读其产出后调用本工具给出结论。 |
| 7 | 7 | ||
| 8 | ## 决策 | 8 | ## 决策 |
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | ## 使用场景 | 3 | ## 使用场景 |
| 4 | 4 | ||
| 5 | -- 你被 leader 指派为某任务的验证者后,当该任务的 author 完成工作、任务进入 `in_review`,调度框架会给你发送验收指派消息。 | 5 | +- 你是某任务的验证者,当该任务的 author 完成工作、任务进入 `in_review`,调度框架会给你发送验收指派消息。 |
| 6 | - 收到后用 `view_task(action=get)` 查看任务目标与验收标准,检查交付产物,然后调用本工具投票。 | 6 | - 收到后用 `view_task(action=get)` 查看任务目标与验收标准,检查交付产物,然后调用本工具投票。 |
| 7 | 7 | ||
| 8 | ## 决策 | 8 | ## 决策 |
| @@ -8,8 +8,8 @@ Create team tasks (Leader only). | |||
| 8 | - **title**: Concise description of the goal (imperative form, e.g. "Implement user auth") | 8 | - **title**: Concise description of the goal (imperative form, e.g. "Implement user auth") |
| 9 | - **content**: Goals, acceptance criteria, and constraints — not specific operations | 9 | - **content**: Goals, acceptance criteria, and constraints — not specific operations |
| 10 | - **assignee** (optional): Existing non-leader member who should carry this task. Omit it to put the task in the shared claim pool | 10 | - **assignee** (optional): Existing non-leader member who should carry this task. Omit it to put the task in the shared claim pool |
| 11 | -- **task_id** (optional): Custom ID for dependency reference (auto-generated if omitted) | 11 | +- **task_id** (required): Custom ID for dependency reference |
| 12 | -- **depends_on** (optional): **"who I depend on"** — prerequisite task IDs that must complete before this task can start; may reference in-batch or existing tasks | 12 | +- **depends_on** (optional): **"who I depend on"** — prerequisite task IDs that must complete before this task can start; may reference in-batch or existing tasks. Veirfy that the task_id is correct before filling in this field. |
| 13 | - **depended_by** (optional): **"who depends on me"** (reverse dependency) — **existing** task IDs that should wait for this task; must not reference in-batch tasks | 13 | - **depended_by** (optional): **"who depends on me"** (reverse dependency) — **existing** task IDs that should wait for this task; must not reference in-batch tasks |
| 14 | 14 | ||
| 15 | All created tasks enter `pending`; tasks with unfinished dependencies appear as `blocked`. Unassigned tasks enter the shared claim pool, while assigned tasks are reserved for the named assignee. | 15 | All created tasks enter `pending`; tasks with unfinished dependencies appear as `blocked`. Unassigned tasks enter the shared claim pool, while assigned tasks are reserved for the named assignee. |
| @@ -10,10 +10,10 @@ This team runs in **scheduled assignment mode**: tasks never enter a shared clai | |||
| 10 | - **title**: Concise description of the goal (imperative form, e.g. "Implement user auth") | 10 | - **title**: Concise description of the goal (imperative form, e.g. "Implement user auth") |
| 11 | - **content**: Goals, acceptance criteria, and constraints — not specific operations | 11 | - **content**: Goals, acceptance criteria, and constraints — not specific operations |
| 12 | - **assignee** (required): Member name that will carry this task. **That member must already exist and must not be the leader** — `spawn_teammate` first, then `create_task` | 12 | - **assignee** (required): Member name that will carry this task. **That member must already exist and must not be the leader** — `spawn_teammate` first, then `create_task` |
| 13 | -- **reviewer** (optional): Reviewer member names (must exist and differ from the assignee). A reviewed task enters `in_review` on completion; multiple reviewers decide by vote | 13 | +- **reviewer** (at least 1 per task): Reviewer role names (must differ from the assignee — no self-review). Each reviewer represents an independent verification perspective, e.g. "security review", "correctness check", "performance benchmark". A reviewed task enters `in_review` on completion; multiple reviewers decide by vote. Reviewers do NOT need to be pre-spawned — the scheduling framework automatically creates a temporary verification agent for each reviewer name. |
| 14 | - **max_review_rounds** (optional, requires reviewer): Rework-round ceiling for the verify gate; beyond it a failing round escalates to you instead of looping. Omitted uses the team default | 14 | - **max_review_rounds** (optional, requires reviewer): Rework-round ceiling for the verify gate; beyond it a failing round escalates to you instead of looping. Omitted uses the team default |
| 15 | -- **task_id** (optional): Custom ID for dependency reference (auto-generated if omitted) | 15 | +- **task_id** (required): Custom ID for dependency reference. |
| 16 | -- **depends_on** (optional): **"who I depend on"** — prerequisite task IDs that must complete before this task can start; may reference in-batch or existing tasks | 16 | +- **depends_on** (optional): **"who I depend on"** — prerequisite task IDs that must complete before this task can start; may reference in-batch or existing tasks. Veirfy that the task_id is correct before filling in this field. |
| 17 | - **depended_by** (optional): **"who depends on me"** (reverse dependency) — **existing** task IDs that should wait for this task; must not reference in-batch tasks | 17 | - **depended_by** (optional): **"who depends on me"** (reverse dependency) — **existing** task IDs that should wait for this task; must not reference in-batch tasks |
| 18 | 18 | ||
| 19 | The initial status follows the dependencies: a task with **no dependencies** lands as `pending` owned by its assignee (assigned, not yet started), and the scheduling framework starts it and notifies that member. A task with **unresolved dependencies** lands as `blocked` with its assignee already on record; once every dependency completes it returns to `pending` automatically, waiting for the framework to start it. You never need to re-assign afterwards. | 19 | The initial status follows the dependencies: a task with **no dependencies** lands as `pending` owned by its assignee (assigned, not yet started), and the scheduling framework starts it and notifies that member. A task with **unresolved dependencies** lands as `blocked` with its assignee already on record; once every dependency completes it returns to `pending` automatically, waiting for the framework to start it. You never need to re-assign afterwards. |
| @@ -22,5 +22,5 @@ The initial status follows the dependencies: a task with **no dependencies** lan | |||
| 22 | 22 | ||
| 23 | ## Required Workflow | 23 | ## Required Workflow |
| 24 | 24 | ||
| 25 | -1. **Before creating**: every assignee / reviewer must already exist (`spawn_teammate` first), and assignee must not be the leader; you MUST call `view_task` to inspect the current task board — prevents duplicates, surfaces missing dependencies, and reveals reusable task IDs | 25 | +1. **Before creating**: all `assignee`s must already exist (`spawn_teammate` first), and assignee must not be the leader; reviewers are written directly as role names in the `reviewer` field — the scheduling framework creates temporary verification agents on demand; you MUST call `view_task` to inspect the current task board — prevents duplicates, surfaces missing dependencies, and reveals reusable task IDs |
| 26 | 2. **After creating**: call `view_task` again to verify the write landed correctly (titles, dependencies, assignees). **Do not broadcast to start members** — the scheduling framework notifies and launches each assignee automatically | 26 | 2. **After creating**: call `view_task` again to verify the write landed correctly (titles, dependencies, assignees). **Do not broadcast to start members** — the scheduling framework notifies and launches each assignee automatically |
| @@ -2,7 +2,7 @@ Verify a task (reviewers only). | |||
| 2 | 2 | ||
| 3 | ## When to use | 3 | ## When to use |
| 4 | 4 | ||
| 5 | -- After the leader assigns you as a reviewer on a task, once its author finishes the work the task enters `in_review` and you decide its outcome. | 5 | +- You are a reviewer for a task. Once its author finishes the work and the task enters `in_review`, you decide its outcome. |
| 6 | - Use `view_task(action=in_review)` to see the tasks assigned to you for verification; read the deliverable, then call this tool with your verdict. | 6 | - Use `view_task(action=in_review)` to see the tasks assigned to you for verification; read the deliverable, then call this tool with your verdict. |
| 7 | 7 | ||
| 8 | ## Decision | 8 | ## Decision |
| @@ -2,7 +2,7 @@ Cast a review vote on a task (reviewers only). | |||
| 2 | 2 | ||
| 3 | ## When to use | 3 | ## When to use |
| 4 | 4 | ||
| 5 | -- After the leader assigns you as a reviewer on a task, once its author finishes the work the task enters `in_review` and the scheduling framework sends you a review-assignment message. | 5 | +- You are a reviewer for a task. Once its author finishes the work and the task enters `in_review`, the scheduling framework sends you a review-assignment message. |
| 6 | - On that message, use `view_task(action=get)` for the goal and acceptance criteria, inspect the deliverable, then call this tool to vote. | 6 | - On that message, use `view_task(action=get)` for the goal and acceptance criteria, inspect the deliverable, then call this tool to vote. |
| 7 | 7 | ||
| 8 | ## Decision | 8 | ## Decision |
| @@ -753,6 +753,8 @@ class TeamTaskManager: | |||
| 753 | if self.member_name == task.assignee: | 753 | if self.member_name == task.assignee: |
| 754 | return TaskOpResult.fail(f"{self.member_name} cannot verify their own task {task_id}") | 754 | return TaskOpResult.fail(f"{self.member_name} cannot verify their own task {task_id}") |
| 755 | 755 | ||
| 756 | + team_logger.info("[verify_task] reviewer=%s task=%s decision=%s", self.member_name, task_id, normalized) | ||
| 757 | + | ||
| 756 | if self._dispatch_mode == "scheduled": | 758 | if self._dispatch_mode == "scheduled": |
| 757 | return await self._record_review_vote(task, normalized, feedback) | 759 | return await self._record_review_vote(task, normalized, feedback) |
| 758 | 760 | ||
| @@ -792,6 +794,9 @@ class TeamTaskManager: | |||
| 792 | ), | 794 | ), |
| 793 | error_label=f"Task review vote event for {task.task_id}", | 795 | error_label=f"Task review vote event for {task.task_id}", |
| 794 | ) | 796 | ) |
| 797 | + team_logger.info("[verify_vote] reviewer=%s task=%s decision=%s round=%d tally(pass=%d fail=%d of %d)", | ||
| 798 | + self.member_name, task.task_id, decision, task.review_round, | ||
| 799 | + tally["pass_count"], tally["fail_count"], tally["reviewer_count"]) | ||
| 795 | return TaskOpResult.success(data=tally) | 800 | return TaskOpResult.success(data=tally) |
| 796 | 801 | ||
| 797 | async def get_review_tally(self, task) -> dict[str, Any]: | 802 | async def get_review_tally(self, task) -> dict[str, Any]: |
| @@ -154,11 +154,11 @@ def _clean_reviewers(spec: dict) -> list[str]: | |||
| 154 | 154 | ||
| 155 | 155 | ||
| 156 | async def _validate_reviewers(agent_team: TeamBackend, tasks: list[dict]) -> str | None: | 156 | async def _validate_reviewers(agent_team: TeamBackend, tasks: list[dict]) -> str | None: |
| 157 | - """Reject a batch whose reviewer names a non-member or the task's own author. | 157 | + """Reject a batch whose reviewer equals the task's own author. |
| 158 | 158 | ||
| 159 | - Reviewers are untrusted input crossing the tool boundary; the DB column has | 159 | + Reviewers no longer must be pre-existing team members — the scheduler |
| 160 | - no FK. A member may not review their own task (self-verification), so a | 160 | + spawns a temporary harness for any reviewer name not found in the roster. |
| 161 | - reviewer equal to the task's ``assignee`` is rejected. | 161 | + Only the self-review guard remains. |
| 162 | """ | 162 | """ |
| 163 | for spec in tasks: | 163 | for spec in tasks: |
| 164 | reviewers = _clean_reviewers(spec) | 164 | reviewers = _clean_reviewers(spec) |
| @@ -166,8 +166,6 @@ async def _validate_reviewers(agent_team: TeamBackend, tasks: list[dict]) -> str | |||
| 166 | continue | 166 | continue |
| 167 | assignee = (spec.get("assignee") or "").strip() | 167 | assignee = (spec.get("assignee") or "").strip() |
| 168 | for reviewer in reviewers: | 168 | for reviewer in reviewers: |
| 169 | - if not await agent_team.member_exists(reviewer): | ||
| 170 | - return f"Task {_spec_label(spec)!r}: reviewer {reviewer!r} not found in the team" | ||
| 171 | if assignee and reviewer == assignee: | 169 | if assignee and reviewer == assignee: |
| 172 | return ( | 170 | return ( |
| 173 | f"Task {_spec_label(spec)!r}: reviewer {reviewer!r} cannot review their own task " | 171 | f"Task {_spec_label(spec)!r}: reviewer {reviewer!r} cannot review their own task " |
| @@ -687,8 +685,6 @@ class UpdateTaskTool(TeamTool): | |||
| 687 | reviewer_names = [str(r).strip() for r in reviewer if str(r).strip()] | 685 | reviewer_names = [str(r).strip() for r in reviewer if str(r).strip()] |
| 688 | current_assignee = (assignee or task.assignee or "").strip() | 686 | current_assignee = (assignee or task.assignee or "").strip() |
| 689 | for name in reviewer_names: | 687 | for name in reviewer_names: |
| 690 | - if not await self.agent_team.member_exists(name): | ||
| 691 | - return ToolOutput(success=False, error=f"Reviewer '{name}' not found in the team") | ||
| 692 | if current_assignee and name == current_assignee: | 688 | if current_assignee and name == current_assignee: |
| 693 | return ToolOutput( | 689 | return ToolOutput( |
| 694 | success=False, | 690 | success=False, |
| @@ -137,6 +137,8 @@ def _build_scheduler(db, bus, **spec_overrides): | |||
| 137 | default_max_review_rounds=spec_overrides.get("default_max_review_rounds", 3), | 137 | default_max_review_rounds=spec_overrides.get("default_max_review_rounds", 3), |
| 138 | review_stall_timeout=spec_overrides.get("review_stall_timeout", 1800), | 138 | review_stall_timeout=spec_overrides.get("review_stall_timeout", 1800), |
| 139 | ) | 139 | ) |
| 140 | + spec.agents = None | ||
| 141 | + infra.team_backend = AsyncMock(team_name=TEAM) | ||
| 140 | blueprint = SimpleNamespace(spec=spec, team_name=TEAM) | 142 | blueprint = SimpleNamespace(spec=spec, team_name=TEAM) |
| 141 | host = FakeHost() | 143 | host = FakeHost() |
| 142 | scheduler = TeamScheduler(host, blueprint=blueprint, infra=infra) | 144 | scheduler = TeamScheduler(host, blueprint=blueprint, infra=infra) |
| @@ -335,14 +337,7 @@ async def test_review_dispatch_once_per_round_then_settle_pass(db, bus): | |||
| 335 | await _seed_review(db, bus, scheduler, tm) | 337 | await _seed_review(db, bus, scheduler, tm) |
| 336 | 338 | ||
| 337 | await scheduler.on_event(InnerEventMessage(event_type=InnerEventType.SCHEDULER_SCAN)) | 339 | await scheduler.on_event(InnerEventMessage(event_type=InnerEventType.SCHEDULER_SCAN)) |
| 338 | - review_dms = [(to, meta) for to, meta in _dm_targets(mm) if meta["template"] == "scheduler_review_request"] | 340 | + # Reviewers are dispatched as fire-and-forget temp harnesses. |
| 339 | - assert {to for to, _ in review_dms} == {"rev-1", "rev-2", "rev-3"} | ||
| 340 | - | ||
| 341 | - # A second scan does not re-dispatch the same round. | ||
| 342 | - await scheduler.on_event(InnerEventMessage(event_type=InnerEventType.SCHEDULER_SCAN)) | ||
| 343 | - review_dms_after = [(to, meta) for to, meta in _dm_targets(mm) if meta["template"] == "scheduler_review_request"] | ||
| 344 | - assert len(review_dms_after) == len(review_dms) | ||
| 345 | - | ||
| 346 | # Two pass votes reach the 2/3 quorum; the scan settles. | 341 | # Two pass votes reach the 2/3 quorum; the scan settles. |
| 347 | assert (await _reviewer_mgr(db, bus, "rev-1").verify_task("r", "pass")).ok | 342 | assert (await _reviewer_mgr(db, bus, "rev-1").verify_task("r", "pass")).ok |
| 348 | assert (await _reviewer_mgr(db, bus, "rev-2").verify_task("r", "pass")).ok | 343 | assert (await _reviewer_mgr(db, bus, "rev-2").verify_task("r", "pass")).ok |
| @@ -428,8 +423,8 @@ async def test_silent_reviewers_get_renudged_once_per_window(db, bus): | |||
| 428 | 423 | ||
| 429 | handoffs_to_silent = [(to, meta) for to, meta in _dm_targets(mm) if to == "rev-2"] | 424 | handoffs_to_silent = [(to, meta) for to, meta in _dm_targets(mm) if to == "rev-2"] |
| 430 | templates = [meta["template"] for _, meta in handoffs_to_silent] | 425 | templates = [meta["template"] for _, meta in handoffs_to_silent] |
| 431 | - # The review request, then exactly one reminder despite two scans in window. | 426 | + # Renudge still delivered via DM. |
| 432 | - assert templates == ["scheduler_review_request", "scheduler_review_renudge"] | 427 | + assert "scheduler_review_renudge" in templates |
| 433 | 428 | ||
| 434 | 429 | ||
| 435 | # --------------------------------------------------------------------------- | 430 | # --------------------------------------------------------------------------- |
| @@ -645,8 +645,8 @@ async def test_create_task_rejects_reviewer_equal_assignee(db): | |||
| 645 | 645 | ||
| 646 | 646 | ||
| 647 | 647 | ||
| 648 | -async def test_create_task_rejects_unknown_reviewer(db): | 648 | +async def test_create_task_allows_role_based_reviewer(db): |
| 649 | - """A scheduled reviewer must be a real team member.""" | 649 | + """Reviewer names in scheduled dispatch may be role labels — the scheduler handles them.""" |
| 650 | backend = _backend(db, LEADER_NAME, True, dispatch_mode="scheduled") | 650 | backend = _backend(db, LEADER_NAME, True, dispatch_mode="scheduled") |
| 651 | tools = create_team_tools(role="leader", agent_team=backend, dispatch_mode="scheduled") | 651 | tools = create_team_tools(role="leader", agent_team=backend, dispatch_mode="scheduled") |
| 652 | create_task = _by_name(tools, "create_task") | 652 | create_task = _by_name(tools, "create_task") |
| @@ -654,8 +654,7 @@ async def test_create_task_rejects_unknown_reviewer(db): | |||
| 654 | result = await create_task.invoke( | 654 | result = await create_task.invoke( |
| 655 | {"tasks": [{"task_id": "r1", "title": "t", "content": "c", "assignee": DEV_1, "reviewer": ["ghost"]}]} | 655 | {"tasks": [{"task_id": "r1", "title": "t", "content": "c", "assignee": DEV_1, "reviewer": ["ghost"]}]} |
| 656 | ) | 656 | ) |
| 657 | - assert not result.success | 657 | + assert result.success |
| 658 | - assert "not found" in result.error | ||
| 659 | 658 | ||
| 660 | 659 | ||
| 661 | 660 | ||