--- title: "Unified Toolchain Design — 统一工具链工作流设计" created: 2026-06-20 version: v1.2 draft status: draft changelog: v1.2 补充 §15b Comment @assignee 行为约束 v1.1 补充 §11b Issue opened 无 assignee 处理 + 修正 §13.1 触发路径 v1.0 初版 --- # Unified Toolchain Design > **范围**: 仅 toolchain 流程。task 和 mail 不变。 > **前置**: §17 ToolchainHandler、§20 Issue-Centric Orchestration > **目标**: 把割裂的单点优化整合为统一的 AI native 工作流 --- ## §1. 背景与问题 ### 1.1 当前割裂点 | # | 割裂 | 现状 | 本文解决 | |---|------|------|---------| | 1 | agent 同时引用两套 API | 黑板 API(action report/outputs)+ Gitea API(Issue/PR comment) | §3 统一到 Gitea | | 2 | steps 硬编码 | 9 处 `steps=[...]` 写死在 toolchain_routes.py | §4 模板化 | | 3 | issue_assigned 只分 2 路 | 只按 infrastructure label 分流,不分 type/feat vs type/docs | §5 按 type/* 6 路分流 | | 4 | action_type 和 business_type 混在一起 | issue_assigned 内部混入业务场景 | §6 分离两个维度 | | 5 | verify 依赖黑板 comment_type | 迁移到 Gitea 后失效 | §7 重新定义完成检测 + 输出约束 | ### 1.2 黑板中的"无缝接续"设计(不能丢弃) 黑板 DB 中有多个机制确保后续 agent 可以无缝接续: | 机制 | 当前实现 | 用途 | 迁移到 Issue 后 | |------|---------|------|----------------| | **任务描述** | tasks.title + tasks.description | agent 知道"做什么" | Issue title + body ✅ 直接对应 | | **验收标准** | tasks.must_haves | agent 知道"怎样算完成" | Issue body 中结构化字段(模板定义)✅ | | **前序产出** | outputs 表 + depends_on | agent 知道"之前做了什么" | Issue body 引用前序 Issue/PR(如 `Depends: #42`)⚠️ 需约定 | | **handoff comment** | comment_type=handoff | agent 之间交接上下文(≥50 字符) | Issue/PR comment ✅ 天然支持 | | **讨论历史** | comments 表 | agent 知道"讨论了什么" | Issue/PR comment 全部可读 ✅ | | **审查结果** | reviews 表(verdict/round/consensus) | agent 知道"审查结论" | PR Review(APPROVE/REQUEST_CHANGES)+ Issue comment 记录 | | **agent 声明式交接** | next_capability 字段 | agent 声明"我搞不定,需要 XX 能力的人" | Issue comment @对方(已有 mention 机制)✅ | | **retry 历史** | task_attempts 表 | daemon 知道"试了几次" | daemon 内部 task_state 表 ✅ 不变 | | **风险观察** | observations 表 | agent 标记"发现风险" | Issue comment(约定标记)⚠️ 需约定 | **结论**:大部分机制可以自然迁移到 Issue。两个需要约定: 1. **前序产出引用**:Issue body 中用 `Depends: #N` 或 `Parent: #N` 引用前序 Issue 2. **风险观察**:Issue comment 中用约定标记(如 `⚠️ [观察]`) --- ## §2. 设计原则 1. **Gitea only**:toolchain agent 只操作 Gitea,不引用黑板 API、不用 Mail 2. **模板驱动**:steps 从硬编码改为模板,不同 Issue type 对应不同流程 3. **action_type 和 business_type 分离**:事件类型决定 action_hint,业务类型决定 steps 4. **完成检测:终态事件 + 输出约束**:用 Gitea 终态事件检测完成 + 结构化输出约束 agent 汇报 5. **无缝接续不丢弃**:handoff/前序产出/审查结果等机制迁移到 Issue 语义 --- ## §3. 统一到 Gitea(割裂 1 解决) ### 3.1 agent API 引用变更 | 操作 | 现在(黑板 API) | 改造后(Gitea API) | |------|----------------|-------------------| | 提交 action report | `POST localhost:8083/.../comments` (comment_type=action_report) | `POST Gitea .../issues/{N}/comments`(结构化 body,§7 定义) | | 提交产出 | `POST localhost:8083/.../outputs` | git push 到分支(代码/文档/测试) | | 讨论 / @mention | `POST localhost:8083/.../comments` | `POST Gitea .../issues/{N}/comments` | | 创建 PR | 已是 Gitea API(不变) | 不变 | | 创建 Issue | 已是 Gitea API(不变) | 不变 | ### 3.2 agent prompt 中的 API 指引 ToolchainApiSection 改造——去掉所有 `localhost:8083` 引用,只保留 Gitea API: ``` ## 操作指令 ### 汇报执行结果 执行完步骤后,在关联的 Issue 上 comment 汇报: ```bash curl -X POST "{GITEA}/repos/{repo}/issues/{N}/comments" \ -H "Authorization: token " \ -d '{"body": "[Action Report]\n\n**操作**:...\n**结果**:...\n**CI**:..."}' ``` ### 需要其他角色支持 在关联的 Issue/PR 上 comment @对方(已有机制) ### 代码产出 git push 到功能分支 → 创建 PR ``` --- ## §4. steps 模板化(割裂 2 解决) ### 4.1 模板存储 steps 模板存放在 daemon 配置文件 `config/toolchain-templates.yaml`: ```yaml # 每种 business_type 对应一套 steps + output_template issue_assigned: feature: steps: - "理解需求(Issue body)→ 如有不明确在 Issue comment 追问" - "git checkout main && git pull origin main" - "git checkout -b fix/{issue_number}-{brief}" - "编码实现 + 写 UT" - "git add -A && git commit -m '[moz] feat: {title}' && git push" - "创建 PR(body 引用 Issue:Closes #{issue_number})" - "等 CI + Review" output_template: | [Action Report] **分支**:fix/{issue_number}-{brief} **PR**:#{pr_number} **改动文件**:{files} **CI**:{ci_status} impl: steps: - "读设计文档(Issue body 中的路径)→ 理解实现范围" - "git checkout main && git pull origin main" - "git checkout -b impl/{issue_number}-{brief}" - "按设计编码实现 + 写 UT" - "git add -A && git commit -m '[moz] impl: {title}' && git push" - "创建 PR(body 引用 Issue + 设计文档路径)" - "等 CI + Review" output_template: | [Action Report] **设计文档**:{design_doc} **分支**:impl/{issue_number}-{brief} **PR**:#{pr_number} **改动文件**:{files} **CI**:{ci_status} bug: steps: - "读 Bug 描述 + 复现步骤(Issue body)" - "定位根因(读代码/日志,不要猜测)" - "git checkout main && git pull origin main" - "git checkout -b fix/{issue_number}-{brief}" - "修复 + 写回归测试" - "git add -A && git commit -m '[moz] fix: {title}' && git push" - "创建 PR(body 说明根因和修复方式)" - "等 CI + Review" output_template: | [Action Report] **根因**:{root_cause} **修复方式**:{fix_approach} **分支**:fix/{issue_number}-{brief} **PR**:#{pr_number} **CI**:{ci_status} docs: steps: - "读文档目标(Issue body)" - "git checkout main && git pull origin main" - "git checkout -b docs/{issue_number}-{brief}" - "编写文档到 docs/ 对应目录" - "git add -A && git commit -m '[moz] docs: {title}' && git push" - "创建 PR" - "等 Review" output_template: | [Action Report] **文档路径**:{doc_path} **分支**:docs/{issue_number}-{brief} **PR**:#{pr_number} refactor: steps: - "读重构目标 + 影响范围(Issue body)" - "git checkout main && git pull origin main" - "git checkout -b refactor/{issue_number}-{brief}" - "重构 + 确保现有测试不 break(python -m pytest tests/unit/ -q)" - "git add -A && git commit -m '[moz] refactor: {title}' && git push" - "创建 PR(body 说明重构内容和影响范围)" - "等 CI + Review" output_template: | [Action Report] **重构范围**:{scope} **测试结果**:{test_result} passed **分支**:refactor/{issue_number}-{brief} **PR**:#{pr_number} **CI**:{ci_status} test: steps: - "读测试目标(Issue body)" - "git checkout main && git pull origin main" - "git checkout -b test/{issue_number}-{brief}" - "编写测试脚本到 tests/ 对应目录" - "运行测试验证(python -m pytest {test_file} -v)" - "git add -A && git commit -m '[moz] test: {title}' && git push" - "创建 PR" - "等 CI + Review" output_template: | [Action Report] **测试文件**:{test_file} **测试结果**:{test_result} **分支**:test/{issue_number}-{brief} **PR**:#{pr_number} infrastructure: steps: - "根据 Issue body 中的错误来源和日志片段排查问题" - "修复基础设施问题(CI runner/网络/Gitea/磁盘等)" - "修复后在 Issue 上 comment 说明修复方式和结果" - "汇报执行结果" output_template: | [Action Report] **问题**:{problem} **根因**:{root_cause} **修复方式**:{fix} **验证**:{verification} # toolchain 事件(非 issue_assigned)的模板 ci_failure: steps: - "查看 CI 日志(PR 页面或 Gitea Actions)" - "判断失败原因:a.代码问题→修复→push b.基础设施→创建 Issue 指派 jiangwei-infra" - "汇报结果" output_template: | [Action Report] **原因类型**:{cause_type} **操作**:{action} **CI 重跑**:{ci_status} # ... 其他 toolchain 事件(review_result/review_request/...)各自定义 ``` ### 4.2 模板加载 daemon 启动时加载 YAML 配置,运行时按 `action_type + business_type` 查找模板: ```python def get_steps(action_type: str, business_type: str = "") -> list[str]: """从模板配置获取 steps""" section = TEMPLATES.get(action_type, {}) if isinstance(section, dict) and business_type: return section.get(business_type, {}).get("steps", section.get("default", {}).get("steps", [])) return section.get("steps", []) ``` --- ## §5. 按 type/* 6 路分流(割裂 3 解决) ### 5.1 issue_assigned handler 改造 当前只按 `infrastructure` label 分 2 路。改为按 `type/*` label 分流: ```python # 伪代码 labels_list = [lbl.get("name", "") for lbl in (issue.get("labels") or [])] # 1. 基础设施(不变) if any("infrastructure" in lbl.lower() for lbl in labels_list): business_type = "infrastructure" # 2. 按 type/* 确定 business_type elif "type/feat" in labels_list: business_type = "feature" elif "type/impl" in labels_list: business_type = "impl" elif "type/bug" in labels_list: business_type = "bug" elif "type/docs" in labels_list: business_type = "docs" elif "type/refactor" in labels_list: business_type = "refactor" elif "type/test" in labels_list: business_type = "test" else: business_type = "feature" # 默认走编码流程 # 从模板获取 steps + output_template template = get_template("issue_assigned", business_type) steps = template["steps"] output_template = template["output_template"] ``` ### 5.2 action_hint 差异化 当前 action_hint 按 action_type 固定("你收到一个 Issue 指派...")。改为同时体现 business_type: ```python _ACTION_HINTS = { "issue_assigned": { "feature": "你收到一个功能需求,理解需求后编码实现。", "impl": "你收到一个实现任务,按设计文档编码实现。", "bug": "你收到一个 Bug 报告,定位根因后修复。", "docs": "你收到一个文档任务,编写文档。", "refactor": "你收到一个重构任务,重构并确保测试通过。", "test": "你收到一个测试任务,编写测试脚本。", "infrastructure": "你收到一个基础设施问题报告,请排查并修复。", }, "ci_failure": "你收到一个 CI 失败通知,这是一个需要你修复失败测试的事件。", "review_result": "你收到一个 Review 结果通知,这是一个需要你执行动作的事件。", # ... 其他 action_type 不分 business_type } ``` --- ## §6. action_type 和 business_type 分离(割裂 4 解决) ### 6.1 两个维度 | 维度 | 来源 | 决定什么 | 示例 | |------|------|---------|------| | **action_type** | webhook 事件类型 | action_hint("你收到一个 XX 通知") | ci_failure / review_result / issue_assigned | | **business_type** | Issue label type/* | steps + output_template | feature / impl / bug / docs / refactor / test | ### 6.2 组合规则 - issue_assigned:action_type=issue_assigned + business_type 从 label 确定 - ci_failure:action_type=ci_failure(无 business_type,CI 失败就是 CI 失败) - review_result:action_type=review_result(无 business_type,Review 就是 Review) - 只有 issue_assigned 需要 business_type 维度(因为同一个 action_type 下不同业务的流程不同) --- ## §7. 完成检测 + 输出约束(割裂 5 解决) ### 7.1 设计原则 | 关注点 | 方案 | |--------|------| | 完成检测 | Gitea 终态事件优先 + Issue comment 兜底 | | 输出约束 | 按 business_type 定义的 output_template(不是空泛的"简要描述") | ### 7.2 完成检测:按 action_type 分类 | action_type | 终态信号 | 检测方式 | 兜底 | |-------------|---------|---------|------| | issue_assigned | PR merged 或 Issue closed | webhook: pull_request/closed(merged=true) 或 issues/closed | — | | ci_failure | agent Issue comment 汇报 | Issue comment 检测([Action Report] 标记) | ⚠️ Gitea 1.26.2 不触发 CI status webhook,只能靠 comment 兜底。ticker 可选轮询 Gitea commit status API 作为补充 | | review_result(APPROVED) | PR merged | webhook: pull_request/closed(merged=true) | — | | review_result(CHANGES) | agent push 到分支 | webhook: pull_request/synchronize | — | | review_request | Review 提交 | webhook: pull_request_review | — | | review_updated | Review 提交 | webhook: pull_request_review | — | | review_comment | agent comment | webhook: issue_comment/created | — | | mention | agent comment | webhook: issue_comment/created | — | | deploy_failure | agent Issue comment 汇报 | Issue comment 检测 | ✅ | | infrastructure_failure | agent Issue comment 汇报 | Issue comment 检测 | ✅ | | review_merged | — | auto-pass | — | ### 7.3 状态流转:单一终态保证 一个 task 只有一个终态触发。daemon 内部状态机保证: ``` pending → working → done(终态事件触发,只触发一次) → failed(超时/异常) ``` 终态事件到来时检查 task 当前状态: - 如果已经 done/failed → 忽略(幂等) - 如果 working → 标 done - 如果 pending → 异常,记日志 **中间事件**(push/comment/Review submitted)**不改变 task 状态**——它们是过程中的信号,不是终态。 ### 7.4 输出约束:output_template 每种 business_type 有自己的 output_template(§4.1 定义)。agent 完成后在 Issue comment 中按模板汇报。 daemon 的 verify 通过 webhook 事件检测终态(不需要检查 comment 内容)。但 output_template 的价值是**约束 agent 的汇报质量**——不是检测完成用的,而是给后续 agent/审查者提供结构化信息。 output_template 作为 steps 的最后一步注入 prompt: ### 7.5 action report 识别规范 daemon 通过 webhook `issue_comment/created` 感知到新 comment 后,需要判断是否为 action report。 **匹配规则**: - 精确匹配:comment body 以 `[Action Report]` 开头(允许前导空白) - 容错策略:如果 body 包含 `[Action Report]`(不要求开头),也接受 - 大小写不敏感 **匹配失败处理**: - 不匹配的 comment 不触发完成检测 - 作为普通讨论 comment 处理(agent 之间的 handoff/讨论) ``` 最后一步:汇报执行结果,在 Issue 上 comment,格式: [Action Report] **根因**:<根因描述> **修复方式**:<做了什么> **分支**:fix/42-xxx **PR**:#43 **CI**:✅ 通过 ``` --- ## §8. 无缝接续机制迁移 ### 8.1 前序产出引用 当前黑板用 `depends_on` 字段 + `PriorOutputsSection` 注入前序产出摘要。 迁移到 Issue 后,在 Issue body 中用约定引用: ```markdown ## 依赖 Depends: #42(前序任务) Parent: #40(父 Issue) ## 前序产出摘要 - #42 完成了数据获取模块(分支 fix/42-data,PR #43) - 数据路径:/Volumes/stock/xxx ``` agent 读 Issue body 自然获得前序上下文。daemon 的 spawner 在构建 prompt 时,可以解析 Issue body 中的 `Depends: #N`,调 Gitea API 读取前序 Issue 的 comment(包含 action report)作为上下文注入。 ### 8.2 handoff comment 当前 handoff 通过黑板 `comment_type=handoff` + ≥50 字符约束。 迁移到 Issue 后,agent 的 handoff 就是**Issue/PR comment**。不需要 comment_type 字段——所有有实质内容的 comment 都是为后续 agent 提供上下文的"handoff"。 ### 8.3 审查结果 当前黑板 reviews 表存 verdict/round/consensus。 迁移后: - **代码审查**:PR Review(Gitea 原生,APPROVE/REQUEST_CHANGES) - **方案审查**(设计 PR):同上 - **庞统 round review**:保留在 daemon 内部(不迁移,这是编排逻辑) --- ## §9. prompt 层级(L0-L4 不变,L2 重组) | 层 | 内容 | 不变? | |---|------|-------| | L0 铁律 | 安全底线 | ✅ 不变 | | L1 角色 | SOUL.md / IDENTITY.md | ✅ 不变 | | L2 引擎注入 | **本文重组** | 改造 | | L3 被参考 | Skill 列表 | ✅ 不变 | L2 重组后的 section 列表: | priority | Section | 内容 | 来源 | |----------|---------|------|------| | 10 | ToolchainContextSection | action_hint + Issue body(需求)+ steps | 改造:从模板加载 steps | | 20 | PriorContextSection | 前序产出(解析 Issue body 中的 Depends) | 改造现有 PriorOutputsSection | | 30 | RoleSkillSection | 角色 Skill | 不变 | | 35 | GitOperationSection | Git 操作说明(PR #95 已有) | 不变 | | 40 | GiteaApiSection | Gitea API 指引(Issue comment + PR 创建) | 改造:去掉黑板 API | | 50 | ToolchainConstraintsSection | 约束 + Red Flags | 不变 | | 55 | GiteaConventionSection | Gitea 标题规范 | 不变 | | 60 | WikiGuideSection | 知识查询引导 | 不变 | | 65 | DeliveryChecklistSection | 交付检查 | 改造:output_template 替代空泛的"简要描述" | --- ## §10. 涉及改动 | 文件 | 改动 | 工作量 | |------|------|-------| | `config/toolchain-templates.yaml` | 新建:6 种 business_type steps + output_template | 新文件 | | `src/daemon/toolchain_handler.py` | ToolchainApiSection 改为 GiteaApiSection(去黑板 API);action_hint 支持 business_type | 改造 | | `src/daemon/toolchain_handler.py` | verify_completion 改为终态事件检测 + Issue comment 兜底 | 改造 | | `src/api/toolchain_routes.py` | issue_assigned handler 按 type/* 6 路分流 | 改造 | | `src/api/toolchain_routes.py` | steps 从模板加载(替代硬编码) | 改造 | | `src/daemon/toolchain_handler.py` | webhook handler 增加终态事件检测 | 新增 | | `.gitea/ISSUE_TEMPLATE/` | 新增 impl.yml / docs.yml / refactor.yml | 新文件 | | `tests/` | 更新测试 | 改造 | --- ## §11. Issue closed 事件处理 ### 11.1 问题 当前 `_handle_issues` 只处理 `action == "assigned"`,不处理 `action == "closed"`。Issue 被关闭时: - daemon 不感知(webhook `issues/closed` 被忽略) - 创建者 / 关注者收不到通知 - 如果该 Issue 对应一个活跃的 task,daemon 不知道 Issue 已关闭 ### 11.2 设计 `_handle_issues` 增加 `action == "closed"` 分支: **谁被通知**:Issue 创建者(`issue.user.login`)。 **通知内容**(通过 toolchain task 发给创建者): - Issue 标题 + 编号 - 关闭者(`payload.sender.login`) - 关闭时间 - Issue 上最后一个 comment 的摘要(修复说明) **通知类型**:纯通知(event_type=issue_closed,verify auto-pass,和 review_merged 一样)。 **特殊情况**: - 如果关闭者是创建者自己(自己关自己创建的),不通知(避免自环) - 如果 Issue 没有创建者信息或创建者不是已知 agent,跳过 ### 11.3 实现伪代码 ```python # _handle_issues 中新增 if action == "closed": issue_creator = issue.get("user", {}).get("login", "") closed_by = payload.get("sender", {}).get("login", "") # 自己关自己创建的,不通知 if issue_creator == closed_by: return # 只通知已注册的 agent if issue_creator not in AGENT_IDS: return # 读取最后一个 comment 作为修复摘要 comments = issue.get("comments", 0) last_comment_summary = "(无 comment)" # 可选:调 Gitea API 读最后一个 comment title = f"Issue 已关闭: {issue_title} ({repo}#{issue_number})" description = f"Issue {repo}#{issue_number} 已被 {closed_by} 关闭。\n\n{last_comment_summary}" _send_toolchain_task( to_agent=issue_creator, title=title, description=description, event_type="issue_closed", action_type="issue_closed", steps=[], # 纯通知,无步骤 context_data={ "issue_number": issue_number, "repo": repo, "issue_title": issue_title, "closed_by": closed_by, }, ) ``` ### 11.4 _ACTION_HINTS 新增 ```python "issue_closed": "你创建的 Issue 已被关闭。这是一条纯通知,阅读即可。", ``` ### 11.5 EVENT_LABELS_ZH 新增 ```python "issue_closed": "Issue 已关闭", ``` ### 11.6 verify_completion issue_closed 走 auto-pass(和 review_merged 一样),纯通知不需要 agent 动作。 ### 11.7 涉及改动 | 文件 | 改动 | |------|------| | `src/api/toolchain_routes.py` `_handle_issues` | 新增 `action == "closed"` 分支 | | `src/daemon/toolchain_handler.py` `_ACTION_HINTS` | 新增 issue_closed | | `src/daemon/toolchain_handler.py` `EVENT_LABELS_ZH` | 新增 issue_closed | | `src/daemon/toolchain_handler.py` `verify_completion` | issue_closed auto-pass | | `templates/toolchain/issue_closed.md` | 新建通知模板 | | `tests/` | 新增 closed 事件测试 | --- ## §11b. Issue opened 无 assignee 处理 ### 11b.1 问题 `_handle_issues` 的 `action == "opened"` 分支只处理"部署失败"关键词和 @mention。无 assignee 的普通 Issue(如庞统创建的 parent Issue)被静默忽略,无法触发 discussion 流程。 ### 11b.2 设计 `_handle_issues` 的 `opened` 分支新增无 assignee 处理路径: ``` Gitea Issue 创建(无 assignee, 有 type/* label) → webhook: issues/opened → _handle_issues: action == "opened" → 非"部署失败" → 无 assignee + 有 type/* label → 创建 toolchain task(assignee=None, action_type=issue_discussion) → ticker 扫到 pending task → router.route: assignee=None → 不走快速路径 4 → delegate 庞统 → 庞统收到 discussion prompt(spawner._build_discussion_prompt) → 庞统在 Issue 上 comment 发起讨论,引导其他 agent 参与 ``` ### 11b.3 `_send_toolchain_task` 改动 `to_agent` 参数允许 `None`: ```python def _send_toolchain_task( to_agent: str | None, # None = 无指派,待路由 ... ) -> str: # None 不校验 AGENT_IDS(非 None 时仍然校验) if to_agent is not None and to_agent not in AGENT_IDS: logger.warning("Unknown agent: %s, skipping toolchain task", to_agent) return "" task = Task( ... assignee=to_agent, # None → 待路由 ... ) ``` ### 11b.4 `_handle_issues` opened 分支改动 ```python elif action == "opened": if "部署失败" in issue_title: # 部署失败处理(不变) ... elif not (issue.get("assignees") or issue.get("assignee")): # §11b: 无 assignee 的普通 Issue labels_list = [lbl.get("name", "") for lbl in (issue.get("labels") or [])] has_type_label = any(lbl.lower().startswith("type/") for lbl in labels_list) if not has_type_label: return # 无 type label 不处理(避免噪音) title = f"Issue 讨论: {issue_title} ({repo}#{issue_number})" _send_toolchain_task( to_agent=None, # 无指派 → router delegate 庞统 title=title, description=f"## Issue 需要讨论\n\n{issue.get('body', '(无描述)')}", event_type="issue_discussion", action_type="issue_discussion", steps=[], # discussion 不需要结构化步骤 context_data={ "issue_number": issue_number, "repo": repo, "issue_title": issue_title, "issue_body": issue.get("body", ""), }, ) return # @mention 检查(不变) ... ``` ### 11b.5 ToolchainHandler verify 改动 `issue_discussion` 走 auto-pass(纯触发,不需要 agent 执行步骤): ```python if meta.get("action_type") == "issue_discussion": return VerifyResult(True, "discussion_passthrough", "issue_discussion auto-pass") ``` ### 11b.6 为什么用 delegate 而非 broadcast? 现有 ticker 有两种调度路径: - **deterministic**:有 assignee → 直接 spawn 给该 agent - **broadcast**:无 assignee → 所有空闲 agent 收到 claim prompt 无 assignee 的 Issue 如果走 broadcast,每个 agent 收到的是 claim prompt(认领 task),不是 discussion prompt(讨论 Issue)。而 §13.2 设计的 discussion prompt 才是正确行为——agent 应该讨论 Issue 内容,自主决定是否参与,而不是竞争认领一个 task。 **delegate 庞统**是更合理的路径: - 庞统收到 task → 读取 Issue body → 按副军师职责发起讨论 - 庞统在 Issue 上 comment 引导其他 agent 参与 - 其他 agent 看到 @mention 后自主创建 sub Issue 如果未来需要多 agent 并行讨论,可以在 ticker 中新增 discussion broadcast 路径(spawn_type=discussion)。但 MVP 阶段 delegate 庞统已足够。 ### 11b.7 涉及改动 | 文件 | 改动 | |------|------| | `src/api/toolchain_routes.py` `_send_toolchain_task` | `to_agent` 允许 None | | `src/api/toolchain_routes.py` `_handle_issues` | opened 分支加无 assignee 路径 | | `src/daemon/toolchain_handler.py` `verify_completion` | issue_discussion auto-pass | | `src/daemon/toolchain_handler.py` `_ACTION_HINTS` | 新增 issue_discussion | | `src/daemon/toolchain_handler.py` `EVENT_LABELS_ZH` | 新增 issue_discussion | | `tests/` | 新增 issue_discussion 测试 | --- ## §12. AI Native 能力完整性(v2 补充) > 本节确保 Gitea 替代黑板后,PRD-v3.0 的 AI native 能力不降级。 > 对照 §01 四相循环实现 + spawner.py / ticker.py / operations.py 的实际代码逐项检查。 ### 12.1 完整能力对照表 | # | AI native 能力 | 黑板实现 | Gitea Issue 对应 | 保留? | §21 补充 | |---|-------------|---------|-----------------|-------|---------| | 1 | **Discussion 讨论** | spawner DISCUSSION_PROMPT_TEMPLATE + spawn_type=discussion + ticker 广播 | parent Issue 创建后 ticker 广播 spawn discussion prompt;Issue comment 是讨论空间 | ✅ | §13 | | 2 | **Agent 自建 sub** | agent POST /tasks {parent_task, must_haves} | agent POST Gitea /issues {title: "[repo][sub][parent #N] ..."} + assign 自己 | ✅ | §14 | | 3 | **@mention 通知** | comment mentions 字段 → mention_queue → ticker 扫描 → spawn 被@者 | Gitea Issue/PR comment @ → webhook issue_comment → daemon 解析 @ → spawn | ✅ | §15 | | 4 | **Round review** | _check_round_complete → parent sub 全终态 → spawn 庞统三问 | daemon 扫 task_state parent 下所有 sub 终态 → spawn 庞统 review | ✅ | §16 | | 5 | **Retry 上下文** | retry_count 字段 + _build_retry_context | task_state.retry_count + retry prompt(不变) | ✅ | 不需额外 | | 6 | **Handoff comment** | comment_type=handoff + ≥50 字符 | Issue/PR comment(无 type 区分,但 Boids 行为准则约束 agent 写实质内容) | ✅ | §17 | | 7 | **Outputs 产出物** | outputs 表 {agent, type, content_path, summary} | 分支 commit(代码/文档)+ Issue comment(摘要) | ✅ | 不需额外 | | 8 | **Depends_on 前序** | tasks.depends_on 字段 + PriorOutputsSection | Issue body `Depends: #N` + daemon 解析注入 | ✅ | §17 | | 9 | **Boids 行为准则** | DISCUSSION_PROMPT 中的 4 条准则 | discussion prompt 不变(Boids 准则在 prompt 中,与存储介质无关) | ✅ | §13 | | 10 | **Agent 自主涌现** | 无 assignee 的 parent task → ticker 广播 → agent 自主讨论/创建 sub | 无 assignee 的 parent Issue → ticker 广播 discussion → agent 自建 sub Issue | ✅ | §13/§14 | | 11 | **Guardrail 安全红线** | dispatcher check_task → violations → block | 不变(guardrail 查 task_state,不依赖黑板) | ✅ | 不需额外 | | 12 | **Classify outcome** | spawner _classify_outcome → done/failed/pending | 不变(classify 逻辑在 daemon 内部) | ✅ | 不需额外 | | 13 | **Rebuttal** | review.py submit_rebuttal → @mention assignee + 回到 working | PR Review REQUEST_CHANGES → webhook → daemon 通知 agent | ✅ | 不需额外 | | 14 | **Checkpoints** | checkpoint_routes.py → approve/reject | PR Review(代码/方案审查统一走 PR Review) | ✅ | 不需额外 | ### 12.2 结论 **14 项 AI native 能力全部可保留。** 其中需要补充设计的是 5 项(§13-§17),其余沿用现有 daemon 逻辑不变。 --- ## §13. Discussion 能力保留 ### 13.1 设计 庞统创建 parent Issue(无 assignee)后,触发 discussion: ``` 庞统创建 parent Issue(无 assignee) → Gitea webhook: issues/opened(注意:不是 assigned,无 assignee 的 Issue 只触发 opened) → _handle_issues opened 分支检测:无 assignee + 有 type/* label → 创建 toolchain task(assignee=None, action_type=issue_discussion) → ticker 扫到 pending task → router delegate 庞统 → 庞统收到 discussion prompt → 在 Issue 上 comment 发起讨论 → 其他 agent 看到讨论 → 自主创建 sub Issue 认领 ``` **关键修正(v1.1)**:原设计写 "webhook: issues/assigned",实际 Gitea 对无 assignee 的 Issue 只发 `issues/opened` 事件。`_handle_issues` 的 `opened` 分支需要新增无 assignee 处理路径(见 §11b)。 ### 13.2 Discussion Prompt 设计(v3 重构) **设计参考**:Edict(角色驱动主动发言)+ APM(自包含 Task Prompt)+ PAV 循环(输入/输出/验证)。 当前问题:DISCUSSION_PROMPT 只列了"你可以做什么",没引导 agent 思考"我和这个需求什么关系"。agent 读完和自己没关系就忽略了。 重构后 discussion prompt: ``` 你被 spawn 来参与 Gitea Issue 讨论。这是一个四相循环的讨论环节。 ## 讨论主题 {parent Issue body 全文} ## 你是谁 你是 {agent_id}({display_name}),你的角色是 {role},你的专业能力是 {capabilities}。 (如:你是 zhangfei-dev(张飞 翼德),角色是编码先锋,能力是编码/脚本/实现) ## 你必须做什么 读完需求后,在 Issue 上 comment 回应(必须,不是可选): 1.【定位】这个需求和你有什么关系?你的专业能力能贡献什么? 2.【建议】你对实现方案有什么建议?(技术选型、数据来源、实现路径) 3.【认领】如果你需要参与,创建 sub Issue 并在 parent Issue comment 注册: - 创建 sub Issue:POST /repos/{repo}/issues title: "[repo][sub][parent #{N}] 任务名" body: "Parent: #{N}\nDepends: #M (如果有前序依赖)\n## 任务\n..." assignees: ["{你的 agent_id}"] - 创建后在 parent Issue comment:"[{你的角色名}] 我创建了 sub #{M}: {任务名},我负责 {简述}" 4.【风险】如果你发现风险、不合理的假设、或遗漏的环节,直接提出 ⚠️ 每个 agent 必须 comment。即使你认为和自己无关,也要说明原因——这证明你读过并思考过了。不 comment 的 agent 会被视为未参与讨论。 ## Comment 格式 你的 comment 必须以角色名开头,让其他人知道你是谁: [{角色名}] {你的观点} 例:[张飞] 我来负责策略编码,用 vnpy CtaTemplate 实现。 例:[关羽] 这个策略需要风控,连亏 3 天应暂停。 例:[赵云] 数据已就绪,2024-08 缺失已补齐。 例:[姜维] 我与此需求无直接关系,但建议关注回测滑点设置。 ## API(Gitea) - 读 Issue 详情+comments:GET /repos/{repo}/issues/{N} - 写 comment:POST /repos/{repo}/issues/{N}/comments - 创建 sub Issue:POST /repos/{repo}/issues ## 行为准则 1. 你是自主的。读 Issue、思考、行动,不要等指令。 2. 不重复别人的工作。动手前先读 Issue comments 看谁在做什么(Separation)。 3. 保持方向对齐。你的产出方向和 parent goal 对齐,不确定时 @pangtong-fujunshi(Alignment)。 4. 产出可共享。产出写入 Issue comment,让其他人能看到你的成果(Cohesion)。 5. 不越界。安全红线不要碰,超出能力的 @ 庞统升级(Boundary)。 6. 随时讨论。执行过程中需要协作时 @ 对应 Agent,讨论是灵活的不是固定阶段的。 ``` **与现有实现差异**: 1. 新增"你是谁"段——agent 知道自己的角色和能力,有定位感 2. 新增"你必须做什么"——4 条必须回应的维度,不是可选 3. 新增 comment 格式——以角色名开头,其他人知道是谁在说 4. 新增"创建 sub 后在 parent comment 注册"——parent Issue comment 流自然包含所有 agent 的表态和分工 5. 底线约束:不 comment = 未参与——强制每个 agent 思考和表态 **输入/输出/验证标准**(PAV 循环): | 阶段 | 输入 | 输出 | 验证 | |------|------|------|------| | Discussion | parent Issue body(goal)| 每个 agent 的 comment(定位+建议+认领+风险)| 所有被 spawn 的 agent 都 comment 了 | ### 13.3 庞统初始引导 庞统创建 parent Issue 时,可以在 Issue body 中 @ 特定 agent 引导认领: ``` Issue body: ## 任务 做一个双均线量化策略... ## 建议分工 @zhangfei-dev 你来认领策略编码 @zhaoyun-data 你来认领数据准备 ``` 但庞统不需要知道全部——关羽可能发现需要风控,自己创建 sub 去做。司马懿可能发现需要测试,自己创建 sub。**这是涌现,不是分配。** ### 13.4 Parent Issue 中的 sub 注册 agent 创建 sub Issue 后,**自己在 parent Issue 上 comment 注册**(不需要 daemon 做): ``` [张飞] 我创建了 sub #101: 策略编码,我来负责实现。 技术方案:用 vnpy CtaTemplate,金叉买入死叉卖出。 ``` Gitea 的 Issue reference 功能会自动在 parent Issue timeline 显示 "Referenced by #N"。 agent 的 comment + Gitea 自动引用 = parent Issue 中有完整的 sub 注册信息。 ### 13.5 分支创建时机 | 阶段 | 谁创建分支? | 分支名 | |------|-----------|-------| | Discussion(Phase 3) | ❌ 不创建分支 | — | | Executor(Phase 4) | agent 收到 executor prompt 后自己创建 | `{type}/{sub_issue_number}-{brief}`(type 按 Issue 类型:fix/feat/impl/docs/refactor/test) | **executor prompt 的 steps 中明确指定分支名**: ``` git checkout -b fix/{issue_number}-{brief} ``` 多分支并行场景(每个 sub Issue number 不同,分支名不同,不冲突): ``` 张飞 sub #101 → 分支 fix/101-dual-ma-strategy 关羽 sub #102 → 分支 fix/102-risk-control 司马懿 sub #103 → 分支 fix/103-strategy-test ``` ### 13.6 L2 输入输出约束(PAV 循环) 每种 prompt 都有明确的输入/输出/验证标准: | prompt 类型 | 输入 | 输出 | 验证 | |------------|------|------|------| | Discussion | parent Issue body(goal)| 每个 agent comment(定位+建议+认领+风险)| 所有 spawn 的 agent 都 comment 了 | | Executor | sub Issue body + parent Issue body + 所有 comments | [Action Report](分支+PR+改动+CI)| PR 创建 + CI 通过 | | Review(司马懿)| PR diff + sub Issue body + parent goal | Review verdict(APPROVE/REQUEST_CHANGES)| Review API 提交 | | Round Review(庞统)| parent Issue body + 所有 sub outputs + 所有 comments | 三问评估(goal 一致性/成果覆盖/下一步)| GOAL_ACHIEVED 或新轮 sub | --- ## §14. Agent 自建 sub Issue 模式 ### 14.1 设计 替代当前的 claim 竞争模式。每个 agent 自己创建 sub Issue + assign 自己。 **标题格式**: ``` [quant][sub][parent #100] 策略编码 ``` - `[quant]` — 项目代号 - `[sub]` — 标记为 sub Issue - `[parent #100]` — parent Issue 编号(Gitea 自动渲染 #100 为链接) - 后面是人类可读的任务描述 **Sub Issue body**: ```markdown Parent: #100 ## 任务 从 parent Issue 继承的具体任务描述 ## 验收标准 ... ``` Gitea 的 Issue reference 功能会自动在 parent Issue #100 上显示 "Referenced by #101"。 ### 14.2 不需要 claim 竞争 当前黑板的 claim 模式(CAS 原子操作)是为了防止两个 agent 认领同一个 task。 在 Gitea Issue 模式下,每个 agent 创建自己的 sub Issue——**不存在竞争**。各创建各的,各 assign 各的。 **重复不怕**:如果关羽和张飞创建了内容重叠的 sub Issue,庞统在 round review 时引导两人统一看法。这不是错误,是讨论的契机。**不做严谨工作流,做 AI 生态。** ### 14.3 daemon 内部 parent/sub 映射 daemon 维护 parent/sub 层级(用于 round review 检测): ```sql -- task_state 表(§20 设计) CREATE TABLE task_state ( issue_number INTEGER, repo TEXT, parent_issue INTEGER, -- 新增:parent Issue 编号 status TEXT DEFAULT 'pending', ... ); ``` daemon 监听 Issue 创建 webhook → 解析标题中的 `[parent #N]` → 记录 parent_issue。 ⚠️ **需同步更新 §20 task_state DDL** 新增 `parent_issue INTEGER` 列。 --- ## §14b. 分支与 PR 生命周期管理 > 解决分支、PR、Issue 之间的割裂问题,统一定义完整生命周期。 ### 14b.1 Sub Issue 的分支/PR 生命周期 ``` ① 创建 Sub Issue Agent 在 discussion 阶段创建 sub Issue(assign 自己) → 分支:还不存在 → daemon task_state: status=pending, parent_issue=N ② 分支创建(executor 阶段) Agent 收到 executor prompt → git checkout -b {type}/{sub_issue_number}-{brief} → 分支名从 sub Issue number 派生,一一对应 → daemon task_state: status=working(通过 dispatch 触发) ③ 编码 Agent 在分支上编码 → commit → push → 分支远程存在,Gitea 可见 ④ PR 创建 Agent 创建 PR(head: {type}/{sub_issue_number}-{brief} → base: main) PR body 必须包含: - `Closes #{sub_issue_number}`(让 Gitea merge 时自动关 sub Issue) - `Parent: #{parent_issue_number}`(关联到 parent) - 改动说明(改了什么、为什么) → webhook: pull_request/opened → daemon task_state: status=review ⑤ CI PR 创建 → CI 自动触发(lint + test + frontend) → 通过:等 Review → 失败:toolchain handler 创建 ci_failure task → agent 修复 → push 到同分支 → CI 重跑 ⑥ Review 司马懿收到 Review 请求 → 读 PR diff → 提交 Review(Review API) → APPROVED:通知 agent 合并 → REQUEST_CHANGES:通知 agent 修改 ⑦ 修改(如果有) Agent 在同分支上修改 → commit → push → PR 自动更新(不新建 PR) → CI 重跑 → 司马懿重新 Review → 循环回到 ⑥ ⑧ Merge Agent 或 Reviewer merge PR → Gitea 自动关闭 sub Issue(因为 PR body 有 Closes #N) → webhook: pull_request/closed(merged=true) → daemon 终态信号:task_state status=done ⑨ 分支清理 Gitea 配置自动删除已合并分支(推荐) Issue 保持 closed 状态(完整历史保留) ``` ### 14b.2 Parent Issue 的生命周期 ``` ① 创建(庞统) 庞统创建 parent Issue(无 assignee) → 触发 discussion 广播 → parent Issue 不创建分支(parent 是讨论和编排层) ② Discussion Agents 讨论、创建 sub Issues、在 parent comment 注册 ③ 执行 所有 sub Issues 走 §14b.1 生命周期 ④ Round Review 所有 sub Issues 终态 → 庞统三问 → GOAL_ACHIEVED → 庞统关闭 parent Issue → 需要新轮 → 庞统创建新 sub Issues → 回到 ③ ⑤ 关闭 庞统关闭 parent Issue → webhook: issues/closed → daemon 终态 ``` ### 14b.3 核心规则 | 规则 | 定义 | 原因 | |------|------|------| | 分支 : sub Issue = 1:1 | 每个 sub Issue 一个分支 | 分支名从 Issue number 派生 | | 分支 : PR = 1:1 | 每个分支一个 PR | Review 驳回不新建 PR,push 到同分支更新 | | sub Issue : PR = 1:1 | 一个 sub Issue 一个 PR | 简单场景。复杂 sub 按需拆多个 sub Issue | | PR body 必须含 Closes #N | N = sub Issue 编号 | merge 后自动关 Issue | | PR body 必须含 Parent #M | M = parent Issue 编号 | 关联到 parent,方便追溯 | | parent Issue 不创建分支 | parent 是讨论编排层 | 代码产出在 sub Issue 的分支 | | discussion 不碰 git | 只读 Issue + comment + 创建 sub | 避免讨论阶段产生无意义 commit | | 分支名格式 | {type}/{sub_issue_number}-{brief} | 按 Issue 类型:fix/feat/impl/docs/refactor/test | | merge 后自动删分支 | Gitea 配置 | 避免分支堆积 | ### 14b.4 多分支并行场景 一个 parent Issue 下多个 sub Issue 同时执行: ``` parent Issue #100(双均线策略) ├── sub #101 策略编码 → 分支 feat/101-dual-ma → PR #104 │ └── CI 跑 + Review → merge → Closes #101 ├── sub #102 风控规则 → 分支 feat/102-risk-control → PR #105 │ └── CI 跑 + Review → merge → Closes #102 └── sub #103 策略测试 → 分支 test/103-strategy-test → PR #106 └── CI 跑 + Review → merge → Closes #103 所有 sub 终态 → 庞统 round review → parent Issue #100 关闭 ``` 每个 sub Issue 的分支、PR、CI、Review 独立流转,互不干扰。 ### 14b.5 PR body 模板 ```markdown Closes #{sub_issue_number} Parent: #{parent_issue_number} ## 改动说明 <改了什么、为什么> ## 验证 - [ ] CI 通过(lint + test) - [ ] 本地测试通过(如有) ## 改动文件 - `src/xxx.py`: <改了什么> - `tests/test_xxx.py`: <新增什么测试> ``` --- ## §15. @mention 通知迁移 ### 15.1 当前实现 ``` agent 写 comment → mentions 字段 → mention_queue 表 → ticker 扫描 → spawn 被@者 ``` ### 15.2 Gitea 迁移 ``` agent 写 Issue/PR comment → webhook: issue_comment/created → daemon 解析 @ → spawn 被@者 ``` **mention_queue 表保留**,但数据来源改为 webhook payload。daemon 收到 issue_comment webhook 后: 1. 正则提取 comment body 中的 `@( [- ]*)` 2. 写入 mention_queue 3. ticker 消费 mention_queue → spawn 被@者 复用现有 mention_utils.py 的 extract_mentions 逻辑(§25 已实现)。 --- ## §15b. Comment @assignee 行为约束 ### 15b.1 问题 `_handle_issue_comment` 只有两条通知路径(CI 关键词 + @mention)。当 agent 在 Issue/PR 上写了 comment 但没有 @ 任何人时,Issue 的 assignee/创建者收不到通知。 例:姜维在 Issue #114 上写了排查结论,没有 @ 庞统。庞统不知道排查已完成。 ### 15b.2 设计决策:prompt 约束 vs 代码路径 | 方案 | 优点 | 缺点 | |------|------|------| | 代码路径 3(自动通知 assignee) | 不依赖 agent 行为 | 噪音——每条 comment 都通知,review 来回几轮就炸 | | prompt 约束(agent @assignee) | agent 自主判断,语义精确 | 依赖 agent 遵守 | **选择 prompt 约束**。理由: 1. 与 §21 设计哲学一致(agent 自主决策,不过度自动化) 2. 噪音问题是真实的——不是每条 comment 都需要通知 assignee 3. agent 可以根据场景判断:纯确认不 @,有结论/需关注才 @ 4. 兜底机制:round review + 庞统人工检查 ### 15b.3 约束内容 在 ToolchainConstraintsSection §5「所有协作通过 Gitea 完成」中新增一条: ``` ### 5. 所有协作通过 Gitea 完成 ...(现有内容不变) - ⚠️ 在 Issue/PR 上写 comment 时,如果内容需要 Issue 的 assignee 或创建者知晓,必须在 comment 中 @对方。纯确认性回复(如"收到")不需要 @。 ``` 同时在 `_ACTION_HINTS` 中 review_comment 的 hint 补充提示: ``` "review_comment": "你收到一个 Review 评论,这是一个需要你查看并响应的事件。回复时 @评论者。", ``` ### 15b.4 不做的事 - 不在 `_handle_issue_comment` 中加代码路径 3(自动通知 assignee) - 不做 subscribe/unsubscribe 机制 - 不通知 Issue 创建者(只通知 assignee,assignee 是责任人) ### 15b.5 涉及改动 | 文件 | 改动 | |------|------| | `src/daemon/toolchain_handler.py` ToolchainConstraintsSection §5 | 加 @assignee 约束 | | `src/daemon/toolchain_handler.py` `_ACTION_HINTS` | review_comment hint 补充 @提示 | 约 +5 行,1 文件。 --- ## §16. Round Review 迁移 ### 16.1 当前实现 ticker._check_round_complete: 1. 扫描所有 parent task 2. 检查 sub task 是否全部终态 3. spawn 庞统 review(三问框架) ### 16.2 Gitea 迁移 ticker._check_round_complete 改为: 1. 扫描 task_state 中 parent_issue IS NOT NULL(替代原 `SELECT DISTINCT parent_task FROM tasks`,语义不变——都是找有子任务的 parent) 2. 找到所有 parent_issue 相同的 sub Issue 3. 检查 sub Issue 是否全部终态(通过 task_state.status) 4. 全部终态 → spawn 庞统 review **庞统三问 prompt 不变**: ``` 1. Goal 还清晰吗?(是否有 goal drift) 2. 成果物覆盖 goal 了吗?(逐条检查验收标准) 3. 下一轮需要做什么?(创建新 sub / 标记完成 / 调整方向) ``` 庞统通过 Gitea API 读 parent Issue body(goal)+ 所有 sub Issue 的 comments/outputs → 做评估。 --- ## §17. 无缝接续机制完整迁移 ### 17.1 handoff comment 当前:comment_type=handoff + ≥50 字符 Gitea:所有 Issue/PR comment 都是天然的 handoff。Boids 行为准则约束 agent 写实质内容。不强制 ≥50 字符——用 Boids 准则引导比硬阈值更 AI native。 ### 17.2 depends_on 前序引用 当前:tasks.depends_on 字段 + PriorOutputsSection Gitea:Issue body 中 `Depends: #N`。daemon 解析 → 读前序 Issue 的 comments(含 Action Report)→ 注入 prompt。 ### 17.3 retry 上下文 current:retry_count 字段 + _build_retry_context Gitea:task_state.retry_count(不变,daemon 内部)。retry prompt 中加入前序 Issue/PR 链接供 agent 参考。 ### 17.4 状态转换流转 agent 完成后的状态转换(working → review → done)全部由 daemon 内部管理。agent 不需要手动 POST status。daemon 通过以下信号自动感知: | 信号 | 来源 | 触发 | |------|------|------| | agent 创建 sub Issue | webhook: issues/opened | sub status=pending → dispatch | | agent 创建 PR | webhook: pull_request/opened | sub status → review | | PR Review 提交 | webhook: pull_request_review | review 结果 → done 或 back to working | | PR merge | webhook: pull_request/closed(merged) | sub status → done + Issue auto-close | | agent Issue comment | webhook: issue_comment/created | 检查是否为 Action Report → 终态检测 | --- ## §18. 不做的事 | 不做 | 理由 | |------|------| | 不改 task handler | task 流程不变(§20 设计中的 task 逐步迁移到 Issue 是后续工作) | | 不改 mail | mail 职责不变 | | 不改 dispatcher/ticker 核心逻辑 | 调度逻辑不变(§20 Phase 1 的 dispatcher SQL 迁移是前置工作) | | 不做前端改造 | 后续独立设计 | | 不改 experiences/checkpoints/decisions 表 | 执行面表保留在 daemon |