Files
sanguo_moziplus_v2/docs/design/21-unified-toolchain-design.md
T
cfdaily 38c676c627
CI / lint (pull_request) Successful in 31s
CI / test (pull_request) Successful in 53s
CI / frontend (pull_request) Successful in 27s
CI / notify-on-failure (pull_request) Successful in 0s
[moz] docs(§21): v1.2 §15b Comment @assignee 行为约束
问题:agent 在 Issue/PR 上写 comment 不 @ 人时,assignee 收不到通知。
方案:prompt 约束(不走代码自动通知),让 agent 自主判断何时 @assignee。
不做:代码路径 3(自动通知 assignee,噪音问题)、subscribe 机制。

Closes #115
2026-06-21 23:27:10 +08:00

1207 lines
47 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 | 黑板 APIaction report/outputs+ Gitea APIIssue/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 ReviewAPPROVE/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 <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"
- "创建 PRbody 引用 IssueCloses #{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"
- "创建 PRbody 引用 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"
- "创建 PRbody 说明根因和修复方式)"
- "等 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}"
- "重构 + 确保现有测试不 breakpython -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_assignedaction_type=issue_assigned + business_type 从 label 确定
- ci_failureaction_type=ci_failure(无 business_typeCI 失败就是 CI 失败)
- review_resultaction_type=review_result(无 business_typeReview 就是 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-dataPR #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 ReviewGitea 原生,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 对应一个活跃的 taskdaemon 不知道 Issue 已关闭
### 11.2 设计
`_handle_issues` 增加 `action == "closed"` 分支:
**谁被通知**Issue 创建者(`issue.user.login`)。
**通知内容**(通过 toolchain task 发给创建者):
- Issue 标题 + 编号
- 关闭者(`payload.sender.login`
- 关闭时间
- Issue 上最后一个 comment 的摘要(修复说明)
**通知类型**:纯通知(event_type=issue_closedverify 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 taskassignee=None, action_type=issue_discussion
→ ticker 扫到 pending task
→ router.route: assignee=None → 不走快速路径 4 → delegate 庞统
→ 庞统收到 discussion promptspawner._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 promptIssue 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 taskassignee=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 IssuePOST /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 缺失已补齐。
例:[姜维] 我与此需求无直接关系,但建议关注回测滑点设置。
## APIGitea
- 读 Issue 详情+commentsGET /repos/{repo}/issues/{N}
- 写 commentPOST /repos/{repo}/issues/{N}/comments
- 创建 sub IssuePOST /repos/{repo}/issues
## 行为准则
1. 你是自主的。读 Issue、思考、行动,不要等指令。
2. 不重复别人的工作。动手前先读 Issue comments 看谁在做什么(Separation)。
3. 保持方向对齐。你的产出方向和 parent goal 对齐,不确定时 @pangtong-fujunshiAlignment)。
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 bodygoal| 每个 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 分支创建时机
| 阶段 | 谁创建分支? | 分支名 |
|------|-----------|-------|
| DiscussionPhase 3 | ❌ 不创建分支 | — |
| ExecutorPhase 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 bodygoal| 每个 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 verdictAPPROVE/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 Issueassign 自己)
→ 分支:还不存在
→ 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 创建 PRhead: {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 → 提交 ReviewReview 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 驳回不新建 PRpush 到同分支更新 |
| 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 创建者(只通知 assigneeassignee 是责任人)
### 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 bodygoal+ 所有 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
GiteaIssue body 中 `Depends: #N`。daemon 解析 → 读前序 Issue 的 comments(含 Action Report)→ 注入 prompt。
### 17.3 retry 上下文
currentretry_count 字段 + _build_retry_context
Giteatask_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 |