--- title: "End-to-End Flow — 端到端任务流程设计" created: 2026-06-22 version: v1.5 status: draft changelog: v1.5 采纳司马懿 S1-S4+P1-P3:判断逻辑补 infrastructure 分支、flow/discuss deprecated、定向讨论 fallback、task_state.status 完整生命周期、parent_status 查表不硬编码 v1.4 §22.6/§22.8 设计补足:基础设施排除、定向讨论简化为单spawn自主模式、迁移策略、TC数据源/round_count分流通透 v1.3 §22.6 重构为「所有 TC 流程第一步走 Discuss」+ §22.8 TC Round Review Gitea 适配设计 v1.2 §22.3/§22.4 更新 Phase 1 为 ✅ 已实现(PR #124) v1.1 补充轻量路径设计(§22.6)、数据流澄清(§22.7)、修正设计原则3、统一Phase编号、Phase 1标注 v1.0 初版 --- # End-to-End Flow — 端到端任务流程设计 > 本文档描述一个任务从发起到结束的**完整系统行为链路**。 > §21 各章节按功能点分章,本文档把它们串成一条端到端流程。 > 每个 Phase 标注:触发源、daemon 函数、prompt 来源、agent 行为、留痕位置。 --- ## §22.1 流程总览 ``` Phase 0: 庞统创建 parent Issue(无 assignee, 有 type/* label) ↓ webhook: issues/opened Phase 1: Discussion 广播(所有空闲 agent) ↓ agent 在 Gitea Issue comment 讨论 → 创建 sub Issue(assign 自己) Phase 2: sub Issue assigned → executor 分派 ↓ webhook: issues/assigned Phase 3: 编码 + PR + CI ↓ webhook: pull_request/opened → CI 自动触发 ↓ CI 通过 → 等 Review / CI 失败 → agent 修复 → 重跑 Phase 4: Review(司马懿) ↓ APPROVED → 通知 agent 合并 / REQUEST_CHANGES → agent 修改 → 回 Phase 3 Phase 5: Merge + sub Issue 自动关闭 ↓ webhook: pull_request/closed(merged) Phase 6: Round Review(庞统三问) ↓ GOAL_ACHIEVED → 关闭 parent / 需要新轮 → 创建新 sub → 回 Phase 2 Phase 7: parent Issue 关闭 ↓ webhook: issues/closed ``` **核心设计原则**: 1. **协作面是 Gitea**(Issue/PR comment),不是黑板 DB 2. **每个 Phase 有明确的前置条件和产出**,前一步未完成则后续不触发 3. **webhook 和 ticker 协同**——webhook 负责事件感知和 task 创建,ticker 负责广播调度(Phase 1)和聚合检测(Phase 6) 4. **agent 在 Gitea 留痕**(Issue comment、PR、Review),黑板 DB 只存 daemon 内部状态 --- ## §22.2 阶段详解 ### Phase 0: parent Issue 创建 | 维度 | 内容 | |------|------| | **触发** | 庞统(或用户)在 Gitea 创建 parent Issue | | **条件** | 无 assignee + 有 `type/*` label | | **daemon 函数** | `_handle_issues`(`toolchain_routes.py`),opened 分支 | | **daemon 行为** | 检测无 assignee + 有 type/* label → 创建 toolchain task(`assignee=None`, `action_type=issue_discussion`)写入 `_toolchain` DB | | **agent 行为** | 无(此阶段不 spawn agent) | | **产出** | `_toolchain` DB 中一条 pending task | **webhook 流转**: ``` Gitea: Issue created (no assignee, label type/feat) → webhook: issues/opened → daemon: _handle_issues → action="opened" → 非部署失败 + 无 assignee + 有 type/* label → _send_toolchain_task(to_agent=None, action_type="issue_discussion") ``` --- ### Phase 1: Discussion 广播 | 维度 | 内容 | |------|------| | **触发** | ticker 30s 扫到 Phase 0 创建的 pending task | | **daemon 函数** | `ticker._dispatch_pending` → `dispatcher.decide` → `_broadcast_claim`。action_type=issue_discussion 时调用 `_build_discussion_prompt`(Gitea API),否则用 `_build_claim_prompt`(黑板 API) | | **daemon 行为** | assignee=None → router 返回 mode=delegate → ticker 归入 broadcast_tasks → 广播给所有空闲 agent | | **prompt 来源(设计期望)** | `discussion prompt`(§13.2):你是谁 + 你必须做什么(4 维度)+ Gitea API + Boids 行为准则 | | **agent 行为(设计期望)** | 每个 agent 在 **Gitea Issue** comment(角色名开头,4 维度回应);需要参与的 agent 创建 sub Issue(Gitea API,assign 自己);在 parent Issue comment 注册 sub | | **产出** | parent Issue 上有所有 agent 的讨论 comment;Gitea 上有若干 sub Issue | **discussion prompt 核心(§13.2)**: ``` 你被 spawn 来参与 Gitea Issue 讨论。 ## 讨论主题 {parent Issue body 全文} ## 你是谁 你是 {agent_id}({display_name}),角色是 {role},能力是 {capabilities} ## 你必须做什么(每个 agent 必须 comment) 1.【定位】这个需求和你有什么关系? 2.【建议】你对实现方案有什么建议? 3.【认领】如果你需要参与,创建 sub Issue 并在 parent comment 注册: POST /repos/{repo}/issues title: "[moz][sub][parent #{N}] 任务名" body: "Parent: #{N}\nDepends: #M\n## 任务\n..." assignees: ["{你的 agent_id}"] 4.【风险】如果发现风险或不合理假设,直接提出 ## Comment 格式 [角色名] 你的观点 ## API(Gitea) - 读 Issue: GET /repos/{repo}/issues/{N} - 写 comment: POST /repos/{repo}/issues/{N}/comments - 创建 sub Issue: POST /repos/{repo}/issues ``` **Phase 1 的结束条件**: - 所有被广播的 agent 都已 comment(或 NO_REPLY) - 至少有一个 agent 创建了 sub Issue - 如果没有任何 agent 创建 sub Issue → ticker 升级庞统(3 轮无 taker 机制) --- ### Phase 2: sub Issue assigned → executor 分派 | 维度 | 内容 | |------|------| | **触发** | agent 在 Phase 1 创建 sub Issue 时 assign 自己 → Gitea 发 `issues/assigned` webhook | | **daemon 函数** | `_handle_issues`(`toolchain_routes.py`),assigned 分支 | | **daemon 行为** | 解析 type/* label → 确定 business_type → 从 `toolchain-templates.yaml` 获取 steps → 创建 toolchain task(`action_type=issue_assigned`)→ ticker dispatch | | **prompt 来源** | `ToolchainHandler.build_prompt`(通过 PromptComposer 拼装 sections) | | **agent 行为** | 建分支 `{type}/{sub_issue_number}-{brief}` → 编码 → 写测试 → 创建 PR(body 含 `Closes #N` + `Parent #M`) | | **产出** | Gitea 上有 PR + 分支有代码 | **steps 来源(`config/toolchain-templates.yaml`)**: ```yaml issue_assigned: feat: steps: - "理解需求(Issue body)" - "git checkout -b feat/{issue_number}-{brief}" - "编码 + 写 UT" - "文档同步检查" - "git push + 创建 PR(body 含 Closes #{issue_number})" - "等 Review" ``` --- ### Phase 3: 编码 + PR + CI | 维度 | 内容 | |------|------| | **触发** | agent 创建 PR → webhook `pull_request/opened` → CI 自动触发 | | **daemon 函数(PR opened)** | `_handle_pull_request` → 创建 `review_request` task → 通知司马懿 | | **daemon 函数(CI)** | CI 结果通过 Gitea comment 反馈 → `_handle_issue_comment` CI 路径 | | **CI 通过** | 等待 Review(Phase 5) | | **CI 失败** | 创建 `ci_failure` task → 通知 agent 修复 | | **agent prompt(CI 失败时)** | `ToolchainHandler.build_prompt`,action_type=ci_failure,steps 含 CI 日志查看 + 根因判断 + 修复 | | **产出** | CI 通过的 PR | --- ### Phase 4: Review(司马懿) | 维度 | 内容 | |------|------| | **触发** | `review_request` task 被 ticker dispatch 给司马懿 | | **daemon 函数** | ticker spawn 司马懿 → 司马懿通过 Review API 提交 verdict → webhook `pull_request_review` → `_handle_pull_request_review` | | **prompt 来源** | `ToolchainHandler.build_prompt`,action_type=review_request | | **Review APPROVED** | 创建 `review_result_approved` task → 通知 agent 合并 | | **Review REQUEST_CHANGES** | 创建 `review_result_request_changes` task → 通知 agent 修改 → agent push 同分支 → CI 重跑 → 回 Phase 3 | | **产出** | Gitea Review 记录 | --- ### Phase 5: Merge + sub Issue 关闭 | 维度 | 内容 | |------|------| | **触发** | agent merge PR | | **daemon 函数** | Gitea 自动关闭 sub Issue(PR body 含 `Closes #N`)→ webhook `pull_request/closed(merged)` → `_handle_pull_request` closed 分支 | | **daemon 行为** | 创建 `review_merged` task → 通知 agent(纯通知);ToolchainHandler verify: auto-pass | | **产出** | sub Issue closed + 分支自动删除 | --- ### Phase 6: Round Review(庞统三问) | 维度 | 内容 | |------|------| | **触发** | daemon 检测 parent Issue 下所有 sub Issue 终态(done/closed) | | **daemon 函数** | `ticker._check_round_complete` 扫描 parent/sub 映射 → 所有 sub 终态 → spawn 庞统 | | **prompt 来源** | `ticker._build_review_prompt`(三问框架) | | **agent 行为** | 庞统通过 Gitea API 读 parent Issue body + 所有 sub Issue comments/outputs → 三问评估 | | **GOAL_ACHIEVED** | 庞统关闭 parent Issue → Phase 7 | | **需要新轮** | 庞统创建新 sub Issues → 回 Phase 2 | | **产出** | parent Issue closed 或新一轮 sub Issues | **三问框架**: ``` 1. Goal 还清晰吗?(是否有 goal drift) 2. 成果物覆盖 goal 了吗?(逐条检查验收标准 + docs/design 同步确认) 3. 下一轮需要做什么?(创建新 sub / 标记完成 / 调整方向) ``` --- ### Phase 7: parent Issue 关闭 | 维度 | 内容 | |------|------| | **触发** | 庞统关闭 parent Issue | | **daemon 函数** | webhook `issues/closed` → `_handle_issues` closed 分支 | | **daemon 行为** | 创建 `issue_closed` task → 通知(纯通知,auto-pass) | | **产出** | parent Issue closed,全流程结束 | --- ## §22.3 Prompt 模板对照表 | Phase | prompt 用途 | 设计指定的模板来源 | 当前实际来源 | 一致? | |-------|-----------|----------------|------------|-------| | 1 Discussion | 广播讨论 | discussion prompt(§13.2,Gitea API) | discussion prompt(`_build_discussion_prompt`,Gitea API) | ✅ | | 2 Executor | 编码执行 | `ToolchainHandler.build_prompt` + YAML steps | 同设计 | ✅ | | 3 CI 失败 | 修复 CI | `ToolchainHandler.build_prompt` ci_failure | 同设计 | ✅ | | 4 Review | 审查 PR | `ToolchainHandler.build_prompt` review_request | 同设计 | ✅ | | 5 Merge | 合并通知 | `ToolchainHandler.build_prompt` review_merged | 同设计 | ✅ | | 6 Round Review | 庞统三问 | `ticker._build_review_prompt` | 同设计 | ✅ | | 7 Issue closed | 关闭通知 | `ToolchainHandler.build_prompt` issue_closed | 同设计 | ✅ | **唯一偏差已修复**:PR #124 将 ticker broadcast 改为根据 action_type 选择 discussion prompt(Gitea API)或 claim prompt(黑板 API)。 --- ## §22.4 当前实现差距 | Phase | 实现状态 | 差距描述 | |-------|---------|---------| | 0 parent Issue 创建 | ✅ 已实现 | PR #113 `_handle_issues` opened 分支,无 assignee + type/* label → toolchain task | | 1 Discussion 广播 | ✅ **已实现** | PR #124 修复:ticker `_broadcast_claim` 判断 `action_type=issue_discussion` → 调用 `_build_discussion_prompt`(Gitea API)。`_build_discussion_prompt` 从 `must_haves.context` 解析 `repo` / `issue_number` 注入模板 | | 2 sub Issue → executor | ✅ 已实现 | assigned 路径 + YAML steps 已实现(PR #107),Phase 1 修复后 agent 会创建 sub Issue → 走到此阶段 | | 3 PR + CI | ✅ 已实现 | toolchain handler 正常处理 PR opened + CI 失败 | | 4 Review | ✅ 已实现 | Review 请求 + Review 结果通知正常 | | 5 Merge + sub 关闭 | ✅ 已实现 | merge 通知正常。executor prompt(YAML steps)中已包含 `Closes #{issue_number}` | | 6 Round Review | ✅ **已实现** | `_check_round_complete` 支持双源扫描:黑板 `tasks.parent_task` + toolchain `task_state.parent_issue` | | 7 parent Issue 关闭 | ✅ 已实现 | PR #113 issue_closed auto-pass | --- ## §22.5 差距优先级排序 | 优先级 | 差距 | 影响范围 | 修复建议 | |--------|------|---------|---------| | **P0** | Phase 1 discussion broadcast | ✅ **已完成**(PR #124) | ~~核心断裂~~ 已修复:ticker 判断 action_type=issue_discussion → discussion prompt | | **P1** | Phase 5 PR body Closes #N | ✅ **已完成** | YAML steps 已包含 Closes #N,P0 修复后自然走通 | | **P2** | Phase 6 Round Review Gitea 适配 | ✅ **已完成** | `_check_round_complete` 双源扫描 task_state.parent_issue + tasks.parent_task | **关键结论**:P0/P1/P2 全部完成。§22 端到端流程设计已全部实现。 --- ## §22.6 轻量路径设计(Direct Assignment) > **核心原则:所有 TC 流程第一步都走 Discuss(基础设施和 flow/direct 除外)。** > Discussion 不是可选项,而是 TC 流程的必经阶段——确保方案对齐、风险暴露后再进入 exec。 > 区别只在于「谁参与讨论」。 ### 路径决策矩阵 | 条件 | 讨论范围 | 流程 | |------|---------|------| | Issue 无 assignee + type/* label | **广播所有空闲 agent** | Phase 0 → 1(广播讨论)→ 2 → … | | Issue 有 assignee(非 infrastructure) | **assignee 自主定向讨论** | assignee 写方案 → @司马懿 review → 创建 sub Issue 进 exec | | Issue 有 `type/infrastructure` label | **跳过讨论** | → executor(运维排障,无需方案审查) | | Issue 有 `flow/direct` label | **跳过讨论** | → Phase 2(极小改动逃生舱) | **设计原则**: 1. 基础设施 Issue(`type/infrastructure`)→ 直接到 executor(运维排障任务,不需要写方案+审查) 2. `flow/direct` label → 直接到 executor(明确不需要讨论的小改动) 3. 有 assignee(非 infrastructure)→ assignee 自主走「方案→review→exec」流程,daemon 不编排 4. 没有 assignee → 广播所有 agent 讨论,有 agent 认领后创建 sub Issue > **v1.4 补充**:v1.3 原设计将 infrastructure 也纳入 Discuss,但基础设施排障任务(Gitea 挂了、磁盘满等)不需要写实现方案和司马懿审查,应排除。 ### 判断逻辑 ``` parent Issue 创建 ├─ 有 flow/direct label?→ Direct 路径(→ Phase 2,跳过讨论) ├─ 有 type/infrastructure label?→ Direct 路径(→ executor,运维排障) ├─ 有 assignee(非 infrastructure)?→ 定向 Discussion(assignee 自主模式) └─ 无 assignee(默认)→ 广播 Discussion(所有空闲 agent) ``` ### 定向讨论流程(有 assignee,非 infrastructure) > **v1.4 修正**:v1.3 原设计「ticker 同时 spawn assignee + 司马懿」过于复杂,需要 daemon 协调两个 agent 生命周期。 > v1.4 简化为**单 spawn 自主模式**——daemon 只创建 discussion task 给 assignee,assignee 在一个 session 内自主完成全部流程。 ``` Issue assigned webhook → daemon 创建 issue_discussion task(assignee=该 agent,context_data 带 issue/repo) → ticker dispatch:有 assignee → 确定性路由 spawn assignee 单人 → discussion prompt 引导 assignee: 1. 读 Issue 全文,在 Gitea Issue comment 写实现方案(技术选型、实现路径、影响范围) 2. @simayi-challenger 请求方案审查(利用现有 @mention 机制自动创建 review task) 3. 等司马懿 review 结果(通过 @mention 回传) 4. review 通过 → 创建 sub Issue(assign 自己)→ 进入 exec 5. review 驳回 → 修改方案重新提交(重新 comment + @司马懿) ``` **为什么不双 spawn**: 1. assignee 写方案时间不确定,双 spawn 后司马懿可能干等 2. 两阶段串行更简单,assignee 完成后 @mention 自动触发司马懿 3. 和广播讨论一致——daemon 只创建初始 task,后续靠 agent 自主 + @mention **与广播讨论的区别**: - 广播讨论:assignee=None → ticker 归入 broadcast_tasks → 广播所有空闲 agent - 定向讨论:assignee=该 agent → ticker 归入 deterministic_tasks → 确定性路由 spawn 单人 ### 定向讨论 fallback(assignee 无响应) > **v1.4 补充(司马懿 S4)**:广播讨论有「3 轮无 taker → 升级庞统」机制,定向讨论也需要 fallback。 单 spawn assignee 后,如果 assignee NO_REPLY 或 session 超时: ``` ticker 定期 check(复用现有 broadcast _broadcast_tracker 机制) → discussion task 仍 pending/working 且超过 3 轮 check 未终态 → 升级庞统(escalated 状态) → 庞统判断:重新分配 / 转为广播讨论 / 直接关闭 ``` 复用现有 `_broadcast_tracker` 的 `round_number >= 3 → escalated` 逻辑,不引入新机制。 ### 广播讨论流程(无 assignee) ``` Issue opened webhook → daemon 创建 issue_discussion task(assignee=None) → ticker broadcast:广播所有空闲 agent → discussion prompt 引导每个 agent: 1. 在 Gitea Issue comment 回应(定位/建议/认领/风险) 2. 需要参与的 agent 创建 sub Issue assign 自己 ``` ### 适用场景 | 路径 | 适用场景 | 示例 | |------|---------|------| | 广播讨论 | 需求不明确、跨模块协作、涉及 3+ agent、无人认领 | 新功能设计、架构变更 | | 定向讨论 | 需求明确但需要方案确认 | 有明确 assignee 的 Issue | | Direct | 改 typo、改配置值等极小改动 | `flow/direct` label | ### Discussion 路径中的降级 Discussion 进行中,如果所有 agent 都认为任务足够简单只涉及一个角色,任何 agent 可以在 comment 中建议: ``` @pangtong-fujunshi 建议直接指派 @agent-id,理由:... ``` 庞统判断后创建 sub Issue 直接 assign → 该 agent 跳过讨论直接进入 Phase 2。 ### 迁移策略 > **v1.4 补充**:v1.3 未说明现有 `issue_assigned` 路径如何处理。 **保留 `issue_assigned` 给 infrastructure / flow-direct,新增定向讨论路径**: | 条件 | 旧路径(v1.2) | 新路径(v1.4) | 变化 | |------|----------------|----------------|------| | 有 assignee + 非 infrastructure | issue_assigned → executor | issue_discussion → discussion(定向) | **改** | | 有 assignee + `type/infrastructure` | issue_assigned → executor | issue_assigned → executor(不变) | 无 | | 有 `flow/direct` label | issue_assigned → executor | issue_assigned → executor(不变) | 无 | | 无 assignee + type/* label | issue_discussion → broadcast | issue_discussion → broadcast(不变) | 无 | `_ACTION_HINTS` / YAML steps / `ToolchainContextSection` 中的 `issue_assigned` 相关逻辑**保留**(infrastructure/flow-direct 还用),新增 `issue_review_discussion` action_type。 > **`flow/discuss` label deprecated**:v1.2 曾设计 `flow/discuss` label(强制 Discussion 即使有 assignee),v1.4 废弃。所有有 assignee 的 Issue(非 infrastructure)默认走定向讨论,无需显式 label。Gitea 上 `flow/discuss` label(id=101)保留但不影响路由。 ### 实现方案 **toolchain_routes.py**: - `opened` 分支(无 assignee)→ discussion task(assignee=None)— 现有逻辑 ✅ - `assigned` 分支改造: - `is_infrastructure=True` → executor task(现有逻辑保留 ✅) - `has_flow_direct=True` → executor task(现有逻辑保留 ✅) - **其他** → discussion task(assignee=该 agent,context_data 带 issue/repo)— **需要改动** **ticker.py**: - 定向讨论 task 有 assignee → `_dispatch_pending` 确定性路由直接 spawn assignee 单人 - 广播讨论 task 无 assignee → `_broadcast_claim` 广播所有空闲 agent(现有逻辑 ✅) - `_broadcast_claim` 中的 action_type 判断逻辑保留(discussion task 走 `_build_discussion_prompt`) **讨论 prompt 差异化**: - 定向讨论 prompt 额外指引:你是 assignee,需要写方案并主动 @司马懿 review - 广播讨论 prompt 保持现有模板(4 维度回应 + 自主认领) **不影响**:mail / task 流程(它们不走 toolchain_routes.py) --- ## §22.7 数据流设计澄清 > 本文档涉及两个数据源(黑板 DB 和 Gitea Issue),parent/sub 映射的数据流必须明确。 ### 当前状态 vs 设计目标 | 数据 | 当前存储 | §20/§21 设计目标 | 状态 | |------|---------|-----------------|------| | 协作面(title/body/comment) | Gitea Issue/PR(Phase 1 已修复) | Gitea Issue/PR | ✅ | | parent/sub 映射 | `task_state.parent_issue`(已实现) + `tasks.parent_task`(兼容) | `task_state.parent_issue`(新表) | ✅ | | 执行状态 | toolchain DB tasks.status + task_state.status | task_state.status | ✅ | | 成果物 | git commit + PR | git commit + PR | ✅ | ### parent/sub Issue 映射数据流(设计) ``` Agent 在 Phase 1 创建 sub Issue: POST /repos/{repo}/issues title: "[moz][sub][parent #{N}] 任务名" assignees: ["{agent_id}"] ↓ Gitea webhook: issues/opened (with assignee) ↓ daemon: _handle_issues → assigned 分支 → 解析标题 [parent #{N}] → 提取 parent_issue_number → 写入 task_state (issue_number, parent_issue=N, status='pending') ↓ ticker: _check_round_complete → SELECT DISTINCT parent_issue FROM task_state WHERE parent_issue IS NOT NULL → 对每个 parent_issue: SELECT status FROM task_state WHERE parent_issue=? → 全部终态 → spawn 庞统 review ``` ### 前置条件(已全部实现 ✅) 1. **task_state 表创建**:已在 PR #125 中实现(`_init_task_state_table`,CREATE IF NOT EXISTS)。 2. **parent_issue 解析**:已实现(`_ensure_task_state`,`_handle_issues` assigned 分支解析 `[parent #N]`)。 3. **_check_round_complete 双源扫描**:已实现(`task_state.parent_issue` + `tasks.parent_task`)。 ### 混合期处理 task_state 表创建前,`_check_round_complete` 仍扫 `tasks.parent_task`。两种数据可以共存: - **黑板路径**(Phase 1 断裂时):agent 通过黑板 API 认领 → `tasks.parent_task` 有值 - **Gitea 路径**(Phase 1 修复后):agent 创建 sub Issue → `task_state.parent_issue` 有值 `_check_round_complete` 需要同时扫两个源,直到黑板路径完全废弃。 ### 各 Phase 数据读写汇总 | Phase | daemon 写 | daemon 读 | agent 读 | agent 写 | |-------|----------|----------|----------|----------| | 0 | toolchain DB: task(pending, action_type=issue_discussion) | Gitea webhook payload | — | — | | 1 | toolchain DB: broadcast log | toolchain DB: pending tasks | Gitea Issue body | Gitea Issue comment + sub Issue | | 2 | toolchain DB: task(pending, action_type=issue_assigned) | Gitea webhook payload, toolchain-templates.yaml | Gitea Issue body | git branch + PR | | 3 | toolchain DB: task(review_request/ci_failure) | Gitea webhook (PR opened, CI status) | Gitea PR + CI logs | git push (修复) | | 4 | toolchain DB: task(review_result_*) | Gitea webhook (pull_request_review) | Gitea PR diff + files | Gitea Review API | | 5 | toolchain DB: task(review_merged) | Gitea webhook (PR closed/merged) | — | — | | 6 | task_state: round_count++ | task_state: parent_issue + sub status | Gitea parent Issue body + sub Issue comments | Gitea Issue comment(三问结论)+ 关闭/创建 Issue | | 7 | toolchain DB: task(issue_closed) | Gitea webhook (issues/closed) | — | — | --- ## §22.8 TC Round Review Gitea 适配 > **设计原则:三个流程已通过 Handler 架构分离(§14)。TC Round Review 的改造通过 ToolchainHandler 分流,不影响黑板流程。** ### 问题(v1.4 修正:不只是 prompt 不同,数据查询链路也不同) `_check_round_complete` 中调用 `bb.get_subtasks_summary(parent_id)` / `bb.get_aggregate_outputs()` / `bb.get_round_comments()` 全部查黑板 DB。但 TC 的子任务信息在 `task_state` 表 + Gitea Issue/PR,**根本不在黑板 tasks/outputs/comments 表**。 因此 handler 分流不只是 prompt 层面,需要贯穿**数据查询 → prompt 构建 → round_count 递增**三个环节。 ### TC 数据源 vs 黑板数据源对照 | 环节 | 黑板路径 | TC 路径 | |------|---------|---------| | 子任务状态 | `bb.get_subtasks_summary()` → tasks 表 | 新增 `get_tc_subtask_summary()` → task_state 表 | | 成果物 | `bb.get_aggregate_outputs()` → outputs 表 | 庞统自己读 Gitea API(PR/commit) | | 讨论历史 | `bb.get_round_comments()` → comments 表 | 庞统自己读 Gitea Issue comments | | round_count | `bb.increment_round_count()` → tasks.round_count | `UPDATE task_state SET round_count+1` | | parent status | tasks.status | task_state.status(需补充更新逻辑) | ### task_state.status 更新机制 > **v1.4 补充(司马懿 S3)**:当前 `_ensure_task_state` 只做 `INSERT OR IGNORE`,status 初始为 `pending`,后续没有任何代码更新。需补充。 **状态生命周期**: | 状态 | 触发来源 | 更新位置 | |------|---------|----------| | `pending` | `_ensure_task_state` 初始插入 | `toolchain_routes.py` assigned 分支 | | `done` | Gitea Issue closed webhook | `toolchain_routes.py` closed 分支 | | `failed` | executor task 验证失败且未重试 | `toolchain_handler.py` `post_complete` → `_update_task_state_status()` | **关键路径说明**: 1. **sub Issue 创建** → `_ensure_task_state` 写入 `status='pending'` 2. **sub Issue 对应的 executor task 完成** → ToolchainHandler 通过 @mention 指引 agent 创建 PR(含 `Closes #N`)→ PR 合并时 Gitea 自动关闭 Issue → webhook `issues/closed` → 更新 `status='done'` 3. **executor task 失败** → `ToolchainHandler.post_complete()` 中检测 verify 失败且 retry_count 达上限 → 调用 `_update_task_state_status(issue_number, 'failed')` **`post_complete` 何时触发**:spawner 完成 session 后(agent exit),ticker 在下个 tick 检测到 session 终态 → 调用 `handler.post_complete(task_id, agent_id, outcome, db_path)`。此时 handler 可以: - 检查 verify 结果 - 如果 failed 且不可 retry → 查 task_state 找到对应 issue_number → 更新 status='failed' **新增方法**: ```python # toolchain_handler.py def _update_task_state_status(self, issue_number: int, status: str): """更新 task_state.status(executor 失败时调用)""" tc_db = _toolchain_db_path() conn = get_connection(tc_db) try: conn.execute( "UPDATE task_state SET status=?, updated_at=datetime('now') " "WHERE issue_number=?", (status, issue_number) ) conn.commit() finally: conn.close() ``` **toolchain_routes.py closed 分支补充**: ```python elif action == "closed": # 更新 task_state status='done' tc_db = _toolchain_db_path() conn = get_connection(tc_db) try: conn.execute( "UPDATE task_state SET status='done', updated_at=datetime('now') " "WHERE issue_number=?", (issue_number,) ) conn.commit() finally: conn.close() # ... 现有通知逻辑 ``` > **注意**:`get_tc_subtask_summary` 中的 `all_terminal` 只依赖 `done` 和 `failed` 两种终态。`pending`/`working` 计入 `other`,确保未完成的 sub 不会误触发 round review。 ### 分流方案(全链路) ``` _check_round_complete: handler = TaskTypeRegistry.get_by_project(project_id) is_tc = handler and handler.virtual_project == "_toolchain" # 1. 数据查询分流 if is_tc and parent_id in task_state_parents: summary = get_tc_subtask_summary(tc_db, int(parent_id)) # 不查 outputs/comments — 庞统 spawn 后自己读 Gitea API else: summary = bb.get_subtasks_summary(parent_id) outputs = bb.get_aggregate_outputs(parent_id) comments = bb.get_round_comments(parent_id) # 2. prompt 构建分流 if is_tc: review_prompt = handler.build_round_review_prompt(parent_id, summary, ...) else: review_prompt = self._build_review_prompt(parent_task, summary, outputs, comments, ...) # 3. round_count 递增分流 if spawned: if is_tc: UPDATE task_state SET round_count = round_count + 1 WHERE issue_number = ? else: bb.increment_round_count(parent_id) ``` ### BaseTaskHandler 接口扩展 在 `BaseTaskHandler`(`task_type_registry.py`)中新增默认方法: ```python def build_round_review_prompt(self, parent_id: str, summary: dict, outputs: list, comments: list, round_num: int, **kwargs) -> str: """构建 Round Review prompt。默认 raise NotImplementedError。""" raise NotImplementedError( f"{self.task_type} handler does not support round review") ``` 普通 task 和 mail 不调用此方法(走 ticker 原有 `_build_review_prompt`),只有 ToolchainHandler override。 ### ToolchainHandler.build_round_review_prompt TC 版本的 Round Review prompt。核心区别: | 维度 | 黑板版(原) | Gitea 版(新) | |------|-----------|-------------| | Goal 来源 | `parent_task.description`(黑板 DB) | parent Issue body(庞统自己读 Gitea API) | | 成果物 | `bb.get_aggregate_outputs()`(黑板 DB) | PR / commit(庞统自己读 Gitea API) | | 讨论历史 | `bb.get_round_comments()`(黑板 DB) | Issue comments(庞统自己读 Gitea API) | | 创建新 sub | `POST localhost:8083/api/.../tasks`(黑板 API) | `POST Gitea /repos/.../issues`(Gitea API) | | 关闭 parent | 更新黑板 status | `PATCH Gitea /repos/.../issues/{N}` | **prompt 设计**:只给 Issue 编号和 repo,让庞统 spawn 后自己调 Gitea API 读详情。daemon 不做 Gitea API 调用。 ``` ## 庞统 TC Round Review(第 {round_num} 轮) ### Parent Issue - Repo: {repo} - Issue: #{parent_issue_number} ### 本轮 Sub Issue 状态 - 完成: {done} - 失败: {failed} - 总计: {total} ### 你必须做什么 1. 读 parent Issue: GET /repos/{repo}/issues/{parent_issue_number} 2. 读所有 sub Issue 的 comments 和 PR 3. 三问评估: - Goal 还清晰吗? - 成果物覆盖 goal 了吗?(逐条检查 + docs/design 同步) - 下一轮需要做什么? 4. 决策: - GOAL_ACHIEVED → 关闭 parent Issue - 需要新轮 → 创建新 sub Issues ### Gitea API - 读 Issue: GET /repos/{repo}/issues/{N} - 读 comments: GET /repos/{repo}/issues/{N}/comments - 创建 sub Issue: POST /repos/{repo}/issues - 关闭 Issue: PATCH /repos/{repo}/issues/{N} (state=closed) ``` ### 不影响黑板流程的保证 1. **Handler 分流**:`_check_round_complete` 通过 `TaskTypeRegistry.get_by_project(project_id)` 判断。普通项目(无 handler)走 `_build_review_prompt`(黑板版本不变) 2. **数据隔离**:TC review 读 `task_state` 表 + Gitea API;黑板 review 读 `tasks` 表 + `outputs` / `comments` 表 3. **prompt 隔离**:TC prompt 只含 Gitea API 指引;黑板 prompt 只含黑板 API 指引 4. **Handler 分流模式已在 `_dispatch_reviews` 中验证可行**:L1379 `if handler: return []` 用 Handler 分流跳过 handler 项目的 PR Review 流程,TC Round Review 采用相同模式 ### 实现方案 | 文件 | 改动 | |------|------| | `task_type_registry.py` | `BaseTaskHandler` 加 `build_round_review_prompt` 默认方法(raise NotImplementedError) | | `toolchain_handler.py` | `ToolchainHandler` override `build_round_review_prompt` → 返回 Gitea API 版 prompt;新增 `get_tc_subtask_summary()` 静态方法 | | `ticker.py` | `_check_round_complete` 全链路分流:数据查询 + prompt 构建 + round_count 递增 | | `toolchain_routes.py` | `_handle_issues` closed 分支:更新 `task_state.status = 'done'` | ### get_tc_subtask_summary 方法设计 ```python def get_tc_subtask_summary(tc_db: Path, parent_issue: int) -> Optional[dict]: """TC 路径:从 task_state 表查子任务状态摘要""" conn = get_connection(tc_db) try: parent_row = conn.execute( "SELECT round_count, status FROM task_state WHERE issue_number = ?", (parent_issue,) ).fetchone() if not parent_row: return None rows = conn.execute( "SELECT status, COUNT(*) as cnt FROM task_state " "WHERE parent_issue = ? GROUP BY status", (parent_issue,) ).fetchall() if not rows: return None summary = { "parent_id": str(parent_issue), "parent_status": parent_row["status"], # 从 task_state 读取实际状态 "round_count": parent_row["round_count"], "total": 0, "done": 0, "failed": 0, "cancelled": 0, "other": 0, } for row in rows: summary["total"] += row["cnt"] if row["status"] in ("done", "failed", "cancelled"): summary[row["status"]] += row["cnt"] else: summary["other"] += row["cnt"] summary["all_terminal"] = summary["other"] == 0 and summary["total"] > 0 return summary finally: conn.close() ``` ### TaskState round_count 同步 TC 路径的 `round_count` 存在 `task_state` 表中。`_check_round_complete` 需要在 spawn review 成功后递增: ```python if handler and handler.virtual_project == "_toolchain": # TC 路径:递增 task_state.round_count conn.execute("UPDATE task_state SET round_count = round_count + 1 WHERE issue_number = ?", ...) else: # 黑板路径:原有 bb.increment_round_count(parent_id) ```