From 5bc53192d634c28b290bbe00b1d33ba6832d2ec8 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Sat, 13 Jun 2026 21:30:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(design):=20=C2=A717=20action=20Mail=20?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=AE=BE=E8=AE=A1=20v2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 action Mail 类型(inform/request/action 三分),解决工具链事件 被 inform 语义导致流程断链的问题。 核心设计: - 五层 Prompt 架构(L0 语义层 → L4 约束层) - 10 个场景完整提示词设计(8 action + 1 inform + 1 补充) - Steps API 调用级别,信息层与指令层严格分离 - 两个维度完整性验证(Webhook event × 流程流转) - 防降级三层设计(语义层 + 结构层 + Red Flag 表) - action_report comment 验证机制 参考优秀实践:Hermes Tool-Use Enforcement、Superpowers Red Flags、 moziplus L1/L2/L3 三层上下文模型 --- docs/design/17-action-mail-type.md | 1695 ++++++++++++++++++++++++++++ 1 file changed, 1695 insertions(+) create mode 100644 docs/design/17-action-mail-type.md diff --git a/docs/design/17-action-mail-type.md b/docs/design/17-action-mail-type.md new file mode 100644 index 0000000..0679a41 --- /dev/null +++ b/docs/design/17-action-mail-type.md @@ -0,0 +1,1695 @@ +--- +title: "action Mail 类型设计" +created: 2026-06-13 +version: v2.0 +status: draft +--- + +# action Mail 类型设计 + +> **状态**: 草案 v2.0 +> **作者**: 庞统(副军师)🐦 +> **日期**: 2026-06-13 +> **定位**: 在现有 inform/request 两种 Mail 类型基础上,新增第三种 `action` 类型,解决工具链事件"需要 Agent 执行动作但不需要回复"的语义缺口 +> **v2.0 更新**: 基于 6 条设计原则和知识库优秀实践,全面重写提示词系统设计(§5.2/§6/§6A/§6B) + +## §1. 问题陈述 + +### 1.1 现状 + +当前 Mail 系统有两种类型: + +| 类型 | 语义 | verify 逻辑 | 完成方式 | +|------|------|------------|---------| +| `inform` | 纯通知,已阅即 done | 始终通过 | Agent crash 或正常退出 → auto-done | +| `request` | 需要回复 | 检查是否回复(tasks 表查 in_reply_to) | 有回复 → done;无回复 → failed | + +### 1.2 问题 + +`toolchain_routes.py` 中 `_send_mail()` 硬编码 `type=inform`(L213),所有工具链事件(包括 Review 驳回、CI 失败、Issue 指派等)全部以 inform 发出。 + +**设计文档 §13 §15.5 早已定义了"流程强约束 Mail 模板"**(含编号步骤和 Gitea API 调用指令),但落地时模板被简化为纯文本一句话,且 type 统一为 inform。 + +**后果**: + +1. Agent 收到 Review 驳回通知,Prompt 说"已阅即可"、"不要执行任何状态转换命令"——Agent 当纯通知处理后就 done 了 +2. Agent 收到 CI 失败通知,同样当纯通知处理——没有修复动作 +3. 流程断链:Review 驳回 → Agent 不修 → 永远卡在驳回状态 + +### 1.3 D1 决策回顾 + +§16.0 D1 决策:"不做第三种 task 类型",理由是"中枢产出就是 Mail(from=system, type=inform),模板填充 description,投递复用 spawner"。 + +**本设计推翻 D1 决策**。理由: + +- D1 假设模板内容足以引导 Agent 行动,但 inform 的语义("已阅即可")和 PromptSection 约束("不要执行任何状态转换命令")直接矛盾于期望行为 +- Agent 的行为受 Prompt 控制,而不是 description 内容。inform 的 PromptSection 主动告诉 Agent 不要做任何事 +- 需要一种类型,其 PromptSection 明确告诉 Agent "执行以下步骤",并且 verify 检查执行信号 + +--- + +## §2. action 类型定义 + +### 2.1 语义 + +> **action**:不需要 Mail 回复,但必须执行结构化 steps,并通过 comment 提交执行报告。 + +三种类型的语义光谱: + +``` +inform ─────── action ─────── request +纯通知 执行动作 需要回复 +已阅即 done 执行+报告 done 回复后 done +``` + +### 2.2 行为差异矩阵 + +| 维度 | inform | action | request | +|------|--------|--------|---------| +| **Prompt 语气** | "已阅即可" | "执行以下步骤" | "处理并回复" | +| **需要回复** | ❌ | ❌ | ✅ | +| **需要执行动作** | ❌ | ✅ | ✅(隐含) | +| **verify 逻辑** | 始终通过 | 检查 action report comment | 检查 mail reply | +| **完成方式** | crash/退出 → auto-done | comment 提交 → done | reply 提交 → done | +| **PromptSection 约束** | 禁止状态转换命令 | 必须执行步骤 + 提交报告 | 必须回复 | +| **失败处理** | 不失败 | 标 failed + 通知发件人 | 标 failed + 通知发件人 | +| **MailApiSection** | 不注入 | 不注入(action 不需要回信) | 注入(回复指引) | + +### 2.3 JSON Schema + +action 类型的 `must_haves` JSON 结构: + +```json +{ + "type": "action", + "performative": "action", + "action_type": "review_request", + "steps": [ + "读取 PR diff(Gitea API: GET /repos/{repo}/pulls/{pr_number}.diff)", + "按审查清单审查(参考 code-review Skill)", + "提交 Review(Gitea API: POST /repos/{repo}/pulls/{pr_number}/reviews)", + "提交 action report(POST /api/projects/_mail/tasks/{task_id}/comments)" + ], + "context": { + "pr_number": 42, + "repo": "sanguo/sanguo_moziplus_v2", + "pr_title": "feat: add login page", + "result": "APPROVED" + }, + "is_read": false, + "conversation_id": "conv-mail-xxx", + "from": "system", + "source": "webhook" +} +``` + +字段说明: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `type` | string | ✅ | 固定 `"action"` | +| `performative` | string | ✅ | 固定 `"action"`(兼容 `_parse_performative` 的字段读取) | +| `action_type` | string | ✅ | 动作分类,用于 Prompt 渲染和日志统计 | +| `steps` | string[] | ✅ | 结构化步骤列表,渲染到 Prompt 中 | +| `context` | object | ❌ | 事件上下文数据(PR 号、仓库名等),供 Agent 执行时参考 | +| `from` | string | ✅ | 发件人 Agent ID | +| `source` | string | ❌ | 来源标识(`webhook` / `system` / agent id) | +| `conversation_id` | string | ❌ | 会话 ID,用于线程追踪 | +| `is_read` | boolean | ❌ | 已读标记 | + +`action_type` 枚举值: + +| action_type | 说明 | +|-------------|------| +| `review_request` | Review 请求 | +| `review_result` | Review 结果通知(含通过/驳回) | +| `review_updated` | PR 更新需重新 Review | +| `review_comment` | Review 评论通知 | +| `ci_failure` | CI 失败 | +| `issue_assigned` | Issue 指派 | +| `deploy_failure` | 部署失败 | +| `mention` | @mention | +| `review_merged` | PR 合并通知(特殊:实际为 inform,但携带可选 action) | + +--- + +## §3. 完成验证机制(关键设计) + +### 3.1 设计目标 + +action 不要求 Mail 回复,但需要验证 Agent 是否执行了动作。验证机制需要: + +1. **可靠**:不能被 Agent 轻易跳过(当前 inform 的问题) +2. **简单**:不引入复杂的外部状态检查 +3. **一致**:与现有架构模式一致(comments 表、tasks 表) +4. **可观察**:验证结果可追溯 + +### 3.2 方案分析 + +| 方案 | 机制 | 优势 | 劣势 | +|------|------|------|------| +| **A:外部状态检查** | 检查 PR 状态变化、CI 重跑等 | 最可靠 | 每个 action_type 需要不同的检查逻辑,实现复杂、耦合 Gitea API、网络依赖 | +| **B:comment 执行报告** | Agent 必须在 comments 表写一条 action report | 简单、统一、可追溯 | Agent 可能"假装执行"(写了 comment 但没真做) | +| **C:信任 + 始终通过** | 和 inform 一样 | 零成本 | 完全失去验证能力,等于 inform | +| **D:outcome 检查** | 检查 Agent 进程退出分类 | 简单 | 只能检测 crash,无法判断是否执行了动作 | + +### 3.3 推荐:方案 B(comment 执行报告) + +**D17-1: action 完成验证采用 comment 执行报告机制** + +Agent 被 Prompt 指示在完成后向 task 提交一条 `comment_type='action_report'` 的评论。`verify_completion` 检查 comments 表中是否存在该类型评论。 + +**选择理由**: + +1. **简洁性**:comments 表已存在(`db.py` L292),不需要新表 +2. **统一性**:与现有 comment 机制一致,API 已有 `POST /projects/{pid}/tasks/{tid}/comments` +3. **可追溯**:action report 记录了 Agent 做了什么,形成审计链 +4. **防"假装执行"**:虽然不能完全防止虚假报告,但 Agent 如果写了报告但没执行,后续事件链(如 CI 不会通过、Reviewer 不会收到 Review)会自然暴露问题。验证机制的目标是防止 Agent"没看到/忽略了",而不是防止恶意行为 + +**设计方案**: + +``` +Agent 收到 action Mail + → Prompt 包含结构化 steps + → Agent 执行 steps(如 clone/fix/push/review) + → Agent 提交 action report(POST comments API) + → Agent 进程退出 + → verify_completion 检查 comments 表 + → 有 action_report comment → pass → done + → 无 action_report comment → fail → 标 failed + 通知发件人 +``` + +**comment 格式**: + +```json +{ + "comment_type": "action_report", + "author": "zhangfei-dev", + "body": "已修复 CI 失败:修正 import 错误,push 到 feat/issue-42 分支。CI 已自动重跑。" +} +``` + +**需要扩展**:`comments` 表的 `comment_type` CHECK 约束需新增 `'action_report'` 值。 + +### 3.4 verify_completion 实现伪代码 + +```python +def verify_completion(self, task_id: str, db_path: Path) -> VerifyResult: + performative = self._parse_performative(task_id, db_path) + + if performative == "inform": + return VerifyResult(True, "inform_auto", f"performative={performative}") + + if performative == "action": + # action: 检查 action_report comment + has_report = self._check_action_report(task_id, db_path) + if has_report: + return VerifyResult(True, "has_action_report", f"performative={performative}") + return VerifyResult(False, "no_action_report", f"performative={performative}") + + # request: 检查是否已回复(现有逻辑) + has_reply = self._check_reply(task_id, db_path) + if has_reply: + return VerifyResult(True, "has_reply", f"performative={performative}") + return VerifyResult(False, "no_reply", f"performative={performative}") + +def _check_action_report(self, task_id: str, db_path: Path) -> bool: + """检查是否已提交 action report(comments 表)""" + try: + conn = get_connection(db_path) + try: + row = conn.execute( + "SELECT id FROM comments WHERE task_id=? AND comment_type='action_report' LIMIT 1", + (task_id,), + ).fetchone() + return row is not None + finally: + conn.close() + except Exception as e: + logger.error("Mail %s: check action report error: %s", task_id, e) + return False # 查询失败保守处理:不通过 +``` + +### 3.5 on_failure 处理 + +action verify 失败时的处理逻辑与 request 一致: + +1. 标 task 为 `failed` +2. 通过 `mail_notify.notify_mail_failed` 通知发件人 +3. reason = `"no_action_report"`(收件人未执行动作) + +--- + +## §4. 场景分类与 type 分配 + +### 4.1 完整场景对照表 + +| 场景 | 当前模板 | 建议 type | 理由 | +|------|---------|-----------|------| +| Review APPROVED → PR 作者 | review_result.md | **action** | 作者需要执行 merge PR 动作 | +| Review REQUEST_CHANGES → PR 作者 | review_result.md | **action** | 作者必须按意见修改代码并 push | +| Review 请求 → reviewer | review_request.md | **action** | reviewer 必须审查并提交 Review | +| Review 有新提交 → reviewer | review_updated.md | **action** | reviewer 必须重新审查 | +| Review 评论 → PR 作者 | review_comment.md | **action** | 作者需要查看评论并响应(修改代码或回复评论) | +| CI 失败 → PR 作者 | ci_failure.md | **action** | 作者必须修复并 push | +| Issue 指派 → 开发者 | issue_assigned.md | **action** | 开发者必须开发 | +| 部署失败 → 运维 | deploy_failure.md | **action** | 运维必须处理 | +| @mention → 被@者 | mention.md | **action** | 被@者通常需要响应(回复/协作/处理) | +| PR 合并 → PR 作者 | review_merged.md | **inform** | 纯通知,合并已完成,无需后续动作 | + +**D17-2: 除 PR 合并通知外,所有工具链事件从 inform 改为 action** + +**设计理由**: + +- Review APPROVED 看似可选(Agent 可以选择不合),但实际在三国团队流程中,Review 通过 = 应该合并。如果作者不合并,流程就卡住了。因此设为 action,确保 Agent 会执行 merge。 +- Review 评论是灰色地带(可能只是 FYI),但现有模板已经写了"请查看评论并回复或修改代码",暗示需要行动。设为 action 确保不被忽略。 +- @mention 意图多样(求助/通知/协作/分配),但当前模板已含响应指引,且 mention 被忽略的代价高于错误触发 action 的代价。 + +### 4.2 PR 合并通知为何保持 inform + +`review_merged.md` 的场景: + +- PR 已经被合并到 main +- 部署已自动触发(deploy workflow) +- 作者无需做任何事 + +这是一个真正的"FYI"通知。设为 inform 正确。 + +--- + +## §5. 影响范围分析 + +改动目录:`~/.openclaw/sanguo_projects/sanguo_moziplus_v2/` + +### 5.1 核心代码改动 + +#### 5.1.1 `src/daemon/mail_handler.py` + +**改动 1:`verify_completion` 方法(L42-56)** + +新增 action 分支,检查 `action_report` comment。 + +```python +def verify_completion(self, task_id: str, db_path: Path) -> VerifyResult: + performative = self._parse_performative(task_id, db_path) + + if performative == "inform": + return VerifyResult(True, "inform_auto", f"performative={performative}") + + if performative == "action": + has_report = self._check_action_report(task_id, db_path) + if has_report: + return VerifyResult(True, "has_action_report", f"performative={performative}") + return VerifyResult(False, "no_action_report", f"performative={performative}") + + # request: 现有逻辑 + has_reply = self._check_reply(task_id, db_path) + if has_reply: + return VerifyResult(True, "has_reply", f"performative={performative}") + return VerifyResult(False, "no_reply", f"performative={performative}") +``` + +**改动 2:新增 `_check_action_report` 方法** + +```python +def _check_action_report(self, task_id: str, db_path: Path) -> bool: + """检查是否已提交 action report""" + conn = get_connection(db_path) + try: + row = conn.execute( + "SELECT id FROM comments WHERE task_id=? AND comment_type='action_report' LIMIT 1", + (task_id,), + ).fetchone() + return row is not None + finally: + conn.close() +``` + +**改动 3:`MailContextSection.render` 新增 action 分支(L131-156)** + +```python +def render(self, context: PromptContext) -> str: + if context.mail_type == "inform": + return self._render_inform(context) + if context.mail_type == "action": + return self._render_action(context) + return self._render_request(context) + +@staticmethod +def _render_action(context: PromptContext) -> str: + steps_text = "\n".join( + f"{i}. {step}" for i, step in enumerate(context.action_steps, 1) + ) + return ( + f"你收到一封行动指令邮件,需要你执行以下操作。\n\n" + f"发件者: {context.from_agent}\n" + f"主题: {context.title}\n" + f"内容: {context.description}\n\n" + f"### 执行步骤\n\n{steps_text}\n\n" + f"### 完成后必须提交执行报告\n\n" + f"执行完上述步骤后,必须提交 action report:\n\n" + f'curl -s -X POST http://localhost:8083/api/projects/_mail/tasks/{context.task_id}/comments \\\n' + f" -H 'Content-Type: application/json' \\\n" + f' -d \'{{"author": "{context.agent_id}", ' + f'"comment_type": "action_report", ' + f'"body": "简要描述你执行了什么操作"}}\'\n\n' + f"⚠️ 不提交 action report 的邮件会被标记为 failed。" + ) +``` + +**改动 4:`MailApiSection.should_include`(L161-163)** + +```python +def should_include(self, context: PromptContext) -> bool: + # request 需要回复指引,action 不需要(action 有自己的 report 指引) + return context.mail_type == "request" +``` +(当前已经是 `mail_type == "request"` 条件,无需改动) + +**改动 5:`MailConstraintsSection.render` 新增 action 约束(L167-176)** + +```python +def render(self, context: PromptContext) -> str: + if context.mail_type == "action": + return ( + "## 硬约束\n\n" + "1. ⚠️ 必须按步骤执行,不能跳过。\n" + "2. ⚠️ 执行完毕后必须提交 action report(comment_type='action_report')。\n" + "3. ⚠️ 不要执行任何状态转换命令(标 working/done/review/failed 等),系统会自动处理。\n" + "4. ⚠️ 不能给自己发邮件。\n" + "5. ⚠️ 不需要回复此邮件(不需要 in_reply_to),提交 action report 即可。" + ) + return ( # inform / request 现有约束 + "## 硬约束\n\n" + "1. ⚠️ 不要执行任何状态转换命令(标 working/done/review/failed 等),系统会自动处理。\n" + "2. ⚠️ 不能给自己发邮件\n" + "3. ⚠️ 发邮件时 to 必须是有效的 agent id\n" + "4. ⚠️ 纯通知用 type=inform,需要对方回复不填 type(默认 request)" + ) +``` + +#### 5.1.2 `src/daemon/prompt_composer.py` + +**改动:`PromptContext` 新增 action 相关字段(L47-69)** + +```python +@dataclass +class PromptContext: + # ... 现有字段 ... + + # mail 专用 + from_agent: str = "" + mail_type: str = "" # inform / request / action + + # action 专用 + action_steps: list = field(default_factory=list) # 结构化步骤列表 + action_context: dict = field(default_factory=dict) # 事件上下文 + + # ... 其余字段不变 ... +``` + +#### 5.1.3 `src/daemon/spawner.py`(L286-292) + +**改动:解析 must_haves 时提取 action 字段** + +```python +meta = json.loads(must_haves) if must_haves else {} +from_agent = meta.get("from", "") +mail_type = meta.get("performative", meta.get("type", "")) +action_steps = meta.get("steps", []) +action_context = meta.get("context", {}) + +ctx = PromptContext( + # ... 现有参数 ... + mail_type=mail_type, + action_steps=action_steps, + action_context=action_context, +) +``` + +#### 5.1.4 `src/api/toolchain_routes.py` + +**改动 1:`_send_mail` 新增 `mail_type` 和 `steps` 参数(L203-242)** + +```python +def _send_mail( + to_agent: str, + title: str, + description: str, + source: str = "webhook", + mail_type: str = "inform", # 新增 + action_type: str = "", # 新增 + steps: list[str] | None = None, # 新增 + context_data: dict | None = None, # 新增 +) -> str: + """创建 Mail Task 并写入数据库。""" + if to_agent not in AGENT_IDS: + logger.warning("Unknown agent: %s, skipping mail", to_agent) + return "" + + mail_id = f"mail-{int(datetime.now().timestamp() * 1000)}" + notify_meta = { + "type": mail_type, + "performative": mail_type, + "is_read": False, + "conversation_id": f"conv-{mail_id}", + "from": "system", + "source": source, + } + # action 类型附加结构化字段 + if mail_type == "action": + notify_meta["action_type"] = action_type + notify_meta["steps"] = steps or [] + notify_meta["context"] = context_data or {} + + task = Task( + id=mail_id, + title=title, + description=description, + assignee=to_agent, + assigned_by="system", + must_haves=json.dumps(notify_meta, ensure_ascii=False), + task_type="mail", + status="pending", + ) + bb = Blackboard(_mail_db_path()) + bb.create_task(task) + logger.info("Mail sent: %s → %s [%s] type=%s", title[:40], to_agent, mail_id, mail_type) + return mail_id +``` + +**改动 2:各 handler 调用时传入 `mail_type="action"` + steps** + +以 `_handle_pr_opened` 为例: + +```python +text = render_template("review_request", {...}) +title = f"Review 请求: {pr_title} ({repo}#{pr_number})" + +_send_mail( + "simayi-challenger", title, text, + mail_type="action", + action_type="review_request", + steps=[ + f"读取 PR diff(Gitea API: GET /repos/{repo}/pulls/{pr_number}.diff)", + "按审查清单审查(参考 code-review Skill)", + f"提交 Review(Gitea API: POST /repos/{repo}/pulls/{pr_number}/reviews)", + f"提交 action report(POST http://localhost:8083/api/projects/_mail/tasks//comments,comment_type=action_report)", + ], + context_data={"pr_number": pr_number, "repo": repo, "pr_title": pr_title}, +) +``` + +**改动 3:所有 handler 的 `_send_mail` 调用更新** + +| handler 函数 | mail_type | action_type | +|-------------|-----------|-------------| +| `_handle_pr_opened` | action | review_request | +| `_handle_pull_request_review` (APPROVED/REQUEST_CHANGES) | action | review_result | +| `_handle_pr_synchronize` | action | review_updated | +| `_handle_pull_request_review` (COMMENTED) | action | review_comment | +| `_handle_issue_comment` (CI failure) | action | ci_failure | +| `_handle_issues` (assigned) | action | issue_assigned | +| `_handle_pr_closed` (merged) | inform | — | +| `_send_deploy_failure_mail` | action | deploy_failure | +| `_send_mention_mails` | action | mention | + +#### 5.1.5 `src/blackboard/db.py` + +**改动:`comments` 表 CHECK 约束新增 `action_report`** + +```sql +comment_type TEXT NOT NULL DEFAULT 'general' CHECK ( + comment_type IN ( + 'general','handoff','observation','review', + 'rebuttal','rebuttal_response','debate_argument', + 'debate_rebuttal','debate_judgment', + 'action_report' -- 新增 + ) +) +``` + +**注意**:SQLite CHECK 约束修改需要重建表。建议通过 migration 处理: +1. 创建新表 `comments_new` 带 `action_report` CHECK +2. `INSERT INTO comments_new SELECT * FROM comments` +3. `DROP TABLE comments` +4. `ALTER TABLE comments_new RENAME TO comments` +5. 重建索引 + +或者更简单的方式:**去掉 CHECK 约束**。现有 CHECK 约束主要是文档作用,去掉不影响功能。 + +### 5.2 模板文件改动(`templates/toolchain/`) + +**全部 8 个 action 场景模板必须重写**。这不是"在现有模板上打补丁"——现有模板是按 inform 语义写的纯通知体,信息量薄。§15.5 原始流程强约束模板(含编号步骤和 API 调用指令)从未真正落地,被简化成了纯文本一句话。 + +#### 设计原则:信息与指令分离 + +基于已确认的 6 条设计原则(见 §6 开头),每个场景的设计分为两层: + +| 层 | 载体 | 内容 | 语气 | +|---|------|------|------| +| **信息层** | 模板(description) | 事件背景、关键链接、影响范围 | 陈述("发生了什么") | +| **指令层** | PromptSection(steps + constraints) | 编号步骤、API endpoint、完成标准 | 祈使("要做什么") | + +模板只负责信息层(渲染为 description)。指令层由 `_send_mail` 传入的 `steps` 参数和 PromptSection 的 action 约束段共同渲染。 + +#### 8 个模板改动清单 + +| # | 模板文件 | 改动类型 | 说明 | +|---|---------|---------|------| +| 1 | `issue_assigned.md` | **重写** | 去掉"建议分支名"提示(移入 steps),补充 Issue 上下文信息 | +| 2 | `review_request.md` | **重写** | 去掉内嵌的 4 步流程(移入 steps),保留 PR 元信息 | +| 3 | `review_result.md` | **重写** | 拆分为纯信息层(result + review_body),去掉内嵌的通过/不通过流程指引 | +| 4 | `review_updated.md` | **重写** | 去掉内嵌的 4 步流程(移入 steps),保留 PR 更新元信息 | +| 5 | `ci_failure.md` | **重写** | 去掉"请检查 CI 日志并修复"(移入 steps),保留错误摘要 | +| 6 | `deploy_failure.md` | **重写** | 去掉"需人工介入排查"(移入 steps),保留部署失败元信息 | +| 7 | `mention.md` | **重写** | 去掉响应指引(移入 steps),保留评论上下文 | +| 8 | `review_comment.md` | **重写** | 去掉"请查看评论并回复或修改代码"(移入 steps),保留评论内容 | +| (9) | `review_merged.md` | **不改** | 保持 inform,纯通知体已正确 | + +详细设计见 §6A(每个场景的完整模板内容 + steps + action_report 格式)。 + +### 5.3 改动量汇总 + +| 文件 | 改动量 | 类型 | +|------|--------|------| +| `src/daemon/mail_handler.py` | ~80 行 | 修改 | +| `src/daemon/prompt_composer.py` | ~5 行 | 修改 | +| `src/daemon/spawner.py` | ~8 行 | 修改 | +| `src/api/toolchain_routes.py` | ~120 行 | 修改 | +| `src/blackboard/db.py` | ~5 行 | 修改 | +| `templates/toolchain/*.md` | ~200 行 | 重写(8 个文件) | +| **总计** | **~420 行** | | + +--- + +## §6. PromptSection 三分支渲染设计 + +### 6.0 设计原则(已确认) + +本节基于以下 6 条已确认的设计原则: + +| # | 原则 | 来源 | +|---|------|------| +| P1 | **信息与指令分离** — 模板=发生了什么;PromptSection=要做什么 | §15.5 原始设计的教训 | +| P2 | **Steps 是可执行、可验证的原子操作** — API 调用级别,不是目标级描述 | Superpowers 实践 3:2-5 分钟任务粒度 | +| P3 | **场景驱动,两个维度交叉验证** — Webhook event × 流程流转 | moziplus 事件驱动设计 | +| P4 | **action_report 结构化建议** — 含"做了什么 + 证据 + 下一步",软性不强制 | Superpowers 实践 8:verification-before-completion | +| P5 | **防降级三层设计** — 语义层 + 结构层 + 反模式层(Red Flag 表) | Superpowers Red Flags + Hermes Tool-Use Enforcement | +| P6 | **Steps 基于角色定制** — 同一事件不同角色收到后做的事不同 | moziplus L1/L2/L3 + Hermes Subagent Isolation | + +### 6.1 PromptSection 分层架构 + +参考 moziplus 的 L1/L2/L3 三层上下文模型和 Hermes 的 Memory Context Fencing,action 类型的 Prompt 采用五层结构: + +``` +┌──────────────────────────────────────────────────┐ +│ [L0] 语义层(Semantic) │ ← MailContextSection 开头 +│ "你收到一封行动指令邮件,必须执行以下操作。" │ +├──────────────────────────────────────────────────┤ +│ [L1] 上下文层(Context)— 必传最小集 ~300 tok │ ← 模板 description 渲染 +│ 发件人、事件类型、PR/Issue URL、影响范围 │ +├──────────────────────────────────────────────────┤ +│ [L2] 执行步骤层(Steps)— 核心结构化指令 │ ← must_haves JSON steps 渲染 +│ 编号步骤列表,每步含:操作描述 + API endpoint │ +├──────────────────────────────────────────────────┤ +│ [L3] 完成报告层(Report)— action_report 提交指引 │ ← MailContextSection 尾部 +│ POST endpoint + body 格式 + ⚠️ failed 警告 │ +├──────────────────────────────────────────────────┤ +│ [L4] 约束层(Constraints)— 防降级硬约束 │ ← MailConstraintsSection +│ 必须执行 + 必须报告 + Red Flag 表 │ +└──────────────────────────────────────────────────┘ +``` + +**设计参考**: +- moziplus L1/L2/L3:L1 必传最小集(~300-500 tokens),L2 按需展开,L3 文件引用 +- Hermes Memory Context Fencing:用明确的语义标记区分数据类型("这不是通知") +- Hermes Tool-Use Enforcement:"Every response should either make progress or deliver a final result" + +### 6.2 MailContextSection 三分支渲染 + +#### inform 分支(保持现有) + +```python +@staticmethod +def _render_inform(context: PromptContext) -> str: + return ( + f"你收到一封飞鸽传书(纯通知)。\n\n" + f"发件者: {context.from_agent}\n" + f"主题: {context.title}\n" + f"内容: {context.description}\n\n" + f"已阅即可。如需回复,用 in_reply_to 回复发件者(不需要填 to)。\n" + f"⚠️ 不要执行任何状态转换命令。" + ) +``` + +#### action 分支(全新设计) + +```python +@staticmethod +def _render_action(context: PromptContext) -> str: + # [L0] 语义层 + header = ( + "⚠️ 这是一封行动指令邮件,你必须执行以下操作。\n" + "这不是纯通知——读完不能直接结束。\n\n" + ) + + # [L1] 上下文层(来自模板 description) + ctx_block = ( + f"--- 事件上下文 ---\n" + f"发件者: {context.from_agent}\n" + f"事件类型: {context.action_type}\n" + f"主题: {context.title}\n" + f"内容:\n{context.description}\n" + f"--- 上下文结束 ---\n\n" + ) + + # [L2] 执行步骤层(来自 must_haves JSON steps) + steps_lines = [] + for i, step in enumerate(context.action_steps, 1): + steps_lines.append(f"{i}. {step}") + steps_block = ( + "--- 执行步骤(必须逐步完成)---\n" + f"{chr(10).join(steps_lines)}\n" + "--- 步骤结束 ---\n\n" + ) + + # [L3] 完成报告层 + report_block = ( + "--- 完成后必须提交执行报告 ---\n" + "执行完上述所有步骤后,必须提交 action report:\n\n" + f"curl -s -X POST http://localhost:8083/api/projects/_mail/tasks/{context.task_id}/comments \\\n" + " -H 'Content-Type: application/json' \\\n" + f" -d '{{\n" + f' "author": "{context.agent_id}",\n' + f' "comment_type": "action_report",\n' + f' "body": "做了什么:{{操作摘要}}\\n证据:{{commit SHA / PR review ID / push 结果}}\\n下一步:{{是否需要其他人介入}}"\n' + f" }}'\n\n" + "⚠️ 不提交 action report 的邮件会被标记为 failed。\n" + "--- 报告指引结束 ---\n" + ) + + return header + ctx_block + steps_block + report_block +``` + +#### request 分支(保持现有) + +```python +@staticmethod +def _render_request(context: PromptContext) -> str: + return ( + f"你收到一封飞鸽传书,需要你处理并回复。\n\n" + f"发件者: {context.from_agent}\n" + f"主题: {context.title}\n" + f"内容: {context.description}\n\n" + f"### 如何回复发件者\n\n" + f'curl -s -X POST http://localhost:8083/api/mail \\\n' + f" -H 'Content-Type: application/json' \\\n" + f' -d \'{{"from": "{context.agent_id}", ' + f'"in_reply_to": "{context.task_id}", ' + f'"title": "回复: {context.title}", ' + f'"text": "你的回复内容"}}\'\n\n" + f"⚠️ 不需要填 \"to\",系统自动回复给发件者。" + ) +``` + +### 6.3 MailConstraintsSection 分支渲染 + +#### inform + request 分支(保持现有约束) + +```python +# inform / request 共用约束 +return ( + "## 硬约束\n\n" + "1. ⚠️ 不要执行任何状态转换命令(标 working/done/review/failed 等),系统会自动处理。\n" + "2. ⚠️ 不能给自己发邮件\n" + "3. ⚠️ 发邮件时 to 必须是有效的 agent id\n" + "4. ⚠️ 纯通知用 type=inform,需要对方回复不填 type(默认 request)" +) +``` + +#### action 分支(全新设计 — 防降级三层) + +```python +# action 专用约束 +return ( + "## 硬约束(行动指令)\n\n" + "1. ⚠️ 必须按步骤执行,不能跳过任何步骤。\n" + " Red Flag: \"这个通知看看就行了\" → 错!这是 action 类型,必须执行。\n" + " Red Flag: \"我不需要做任何事\" → 错!检查上方的执行步骤列表。\n" + " Red Flag: \"随便提交个 report 就行\" → 错!必须真正执行步骤后再提交。\n" + "\n" + "2. ⚠️ 执行完毕后必须提交 action report(comment_type='action_report')。\n" + " report 应包含:做了什么 + 证据(commit SHA / review ID)+ 下一步。\n" + "\n" + "3. ⚠️ 不要执行任何无关的状态转换命令(标 working/done/review/failed 等),系统会自动处理。\n" + "\n" + "4. ⚠️ 不需要回复此邮件(不需要 in_reply_to),提交 action report 即可。\n" + " action ≠ request:request 需要回信,action 只需要执行 + 报告。\n" + "\n" + "5. ⚠️ 遇到步骤无法执行时(如权限不足、文件不存在),在 action report 中说明原因。" +) +``` + +**设计参考**: +- **Superpowers Red Flags 表**:明确列出"自合理化"模式,防止 Agent 跳过约束 +- **Hermes Tool-Use Enforcement**:"You MUST use your tools to take action — do not describe what you would do without actually doing it." +- 三条 Red Flag 覆盖三种降级路径:误判为通知 / 直接跳过 / 虚假报告 + +### 6.4 MailApiSection 条件渲染 + +| mail_type | MailApiSection 是否注入 | 理由 | +|-----------|----------------------|------| +| inform | ❌ | 纯通知,不需要操作 | +| action | ❌ | action 有自己的 report 提交指引(在 MailContextSection L3 层) | +| request | ✅ | 需要回信指引(告诉 Agent 如何给其他人发新邮件) | + +当前 `MailApiSection.should_include` 已是 `context.mail_type == "request"`,无需改动。 + +### 6.5 完整渲染流程伪代码 + +```python +class MailContextSection: + name = "mail_context" + priority = 10 + + def render(self, context: PromptContext) -> str: + if context.mail_type == "inform": + return self._render_inform(context) + if context.mail_type == "action": + return self._render_action(context) + return self._render_request(context) + + def should_include(self, context: PromptContext) -> bool: + return True + + +class MailApiSection: + name = "mail_api" + priority = 40 + + def should_include(self, context: PromptContext) -> bool: + return context.mail_type == "request" + + def render(self, context: PromptContext) -> str: + # 现有实现,不变 + ... + + +class MailConstraintsSection: + name = "mail_constraints" + priority = 50 + + def render(self, context: PromptContext) -> str: + if context.mail_type == "action": + return self._render_action_constraints() + # inform / request 共用约束 + return self._render_default_constraints() + + def should_include(self, context: PromptContext) -> bool: + return True +``` + +最终 Agent 收到的 Prompt 结构(action 类型): + +``` +[mail_context section, priority=10] + ⚠️ 这是一封行动指令邮件... + --- 事件上下文 --- ... --- + --- 执行步骤 --- ... --- + --- 完成后必须提交执行报告 --- ... --- + +[mail_constraints section, priority=50] + ## 硬约束(行动指令) + 1. ⚠️ 必须按步骤执行... + Red Flag: ... + 2. ⚠️ 执行完毕后必须提交 action report... + ... +``` + +**注意**:MailApiSection 被跳过(`should_include` 返回 False),action 不需要回信指引。 + +--- + +## §6A. 8 个 Action 场景完整提示词设计 + +> 每个场景包含 5 个要素:场景描述、角色、模板内容(信息层)、结构化 steps(指令层)、action_report 建议格式。 +> Steps 中的 `{变量}` 由 `_send_mail` 调用时传入的 `context_data` 填充。 + +### 场景 1:Issue 指派(issue_assigned) + +#### 场景描述 +Gitea Issue 被指派给开发者。Webhook 事件:`issues` + `action=assigned`。 + +#### 角色 +收件人:开发者(如 zhangfei-dev)。角色:**执行者** — 从零开始开发功能或修复 Bug。 + +#### 模板内容(信息层 — `issue_assigned.md`) + +```markdown +Issue #{issue_number} 已指派给你。 + +标题: {issue_title} +Issue: http://192.168.2.154:3000/{repo}/issues/{issue_number} +标签: {labels} + +需求描述: +{issue_body} +``` + +#### 结构化 steps(指令层) + +```python +steps = [ + f"开发环境准备:clone 仓库(如未 clone)并拉取最新代码", + f"创建开发分支: git checkout -b {branch_name}", + f"阅读 Issue 需求,理解验收标准", + f"编码实现 + 编写单元测试", + f"本地运行测试确认通过", + f"push 到远程: git push -u origin {branch_name}(push 后 CI 会自动触发)", + f"等待 CI 完成。如果 CI 失败,修复后重新 push", + f"CI 通过后创建 PR: curl -s -X POST http://192.168.2.154:3000/api/v1/repos/{repo}/pulls " + f" -H 'Authorization: token {PAT}' -H 'Content-Type: application/json' " + f" -d '{{\"head\": \"{branch_name}\", \"base\": \"main\", \"title\": \"{issue_title}\"}}'", + f"提交 action report(含分支名 + PR 编号或 push commit SHA)", +] +``` + +#### action_report 建议格式 +``` +做了什么:在分支 {branch_name} 上完成了 Issue #{issue_number} 的开发,创建了 PR #{pr_number} +证据:commit SHA: {sha}, PR: http://192.168.2.154:3000/{repo}/pulls/{pr_number} +下一步:等待司马懿 Review +``` + +--- + +### 场景 2:PR Review 请求(review_request) + +#### 场景描述 +新 PR 被创建。Webhook 事件:`pull_request` + `action=opened`。 + +#### 角色 +收件人:审查者(simayi-challenger)。角色:**审查者** — 审查代码质量并决定通过/驳回。 + +#### 模板内容(信息层 — `review_request.md`) + +```markdown +新 PR 等待 Review。 + +PR: http://192.168.2.154:3000/{repo}/pulls/{pr_number} +标题: {pr_title} +作者: @{pr_author} +分支: {branch} +风险级别: {risk_level} + +改动文件: +{file_list} +``` + +#### 结构化 steps(指令层) + +```python +steps = [ + f"获取 PR diff: curl -s http://192.168.2.154:3000/api/v1/repos/{repo}/pulls/{pr_number}.diff " + f" -H 'Authorization: token {PAT}'", + f"逐文件审查代码质量、逻辑正确性、安全风险", + f"参考 code-review Skill 的审查清单(如有)", + f"提交 Review: curl -s -X POST http://192.168.2.154:3000/api/v1/repos/{repo}/pulls/{pr_number}/reviews " + f" -H 'Authorization: token {PAT}' -H 'Content-Type: application/json' " + f" -d '{{\"event\": \"APPROVED\" 或 \"REQUEST_CHANGES\", \"body\": \"审查意见\"}}'", + f"提交 action report(含 Review 决定 + 理由摘要)", +] +``` + +#### action_report 建议格式 +``` +做了什么:审查了 PR #{pr_number},提交了 {APPROVED/REQUEST_CHANGES} Review +证据:Review ID: {review_id}, PR: http://192.168.2.154:3000/{repo}/pulls/{pr_number} +下一步:{如果通过 → 等待作者合并 / 如果驳回 → 等待作者修改} +``` + +--- + +### 场景 3:Review 驳回(review_result — REQUEST_CHANGES) + +#### 场景描述 +Review 被驳回。Webhook 事件:`pull_request_review` + state=`REQUEST_CHANGES`。 + +#### 角色 +收件人:PR 作者(如 zhangfei-dev)。角色:**修改者** — 按审查意见修改代码。 + +#### 模板内容(信息层 — `review_result.md`) + +```markdown +Review 结果: 驳回 ✗ + +PR: http://192.168.2.154:3000/{repo}/pulls/{pr_number} +标题: {pr_title} +审查者: @{reviewer} + +审查意见: +{review_body} +``` + +#### 结构化 steps(指令层) + +```python +steps = [ + f"仔细阅读审查意见,理解每条修改要求", + f"进入开发分支: git checkout {branch} && git pull origin {branch}", + f"逐条修改代码,确保每个审查意见都被处理", + f"运行本地测试确认修改未引入新问题", + f"push 修改: git add -A && git commit -m 'fix: 按审查意见修改' && git push", + f"push 后 CI 自动触发,等待 CI 结果。如果 CI 失败则继续修复", + f"提交 action report(含修改的 commit SHA + 每条审查意见的处理说明)", +] +``` + +#### action_report 建议格式 +``` +做了什么:按审查意见修改了 PR #{pr_number} 的代码,已 push +证据:commit SHA: {new_sha} +下一步:等待 CI 通过后,司马懿会自动收到重新 Review 通知 +``` + +--- + +### 场景 4:Review 通过(review_result — APPROVED) + +#### 场景描述 +Review 被通过。Webhook 事件:`pull_request_review` + state=`APPROVED`。 + +#### 角色 +收件人:PR 作者(如 zhangfei-dev)。角色:**合并者** — 确认 CI 通过并合并 PR。 + +#### 模板内容(信息层 — `review_result.md`,复用模板但 context 不同) + +```markdown +Review 结果: 通过 ✓ + +PR: http://192.168.2.154:3000/{repo}/pulls/{pr_number} +标题: {pr_title} +审查者: @{reviewer} + +审查意见: +{review_body} +``` + +#### 结构化 steps(指令层) + +```python +steps = [ + f"确认 CI 已通过: curl -s http://192.168.2.154:3000/api/v1/repos/{repo}/commits/{sha}/status " + f" -H 'Authorization: token {PAT}' → 检查 .state == 'success'", + f"如果 CI 未通过,先修复 CI 问题再合并", + f"合并 PR: curl -s -X POST http://192.168.2.154:3000/api/v1/repos/{repo}/pulls/{pr_number}/merge " + f" -H 'Authorization: token {PAT}' -H 'Content-Type: application/json' " + f" -d '{{\"Do\": \"merge\", \"merge_title_field\": \"Merge PR #{pr_number}\"}}'", + f"确认合并成功: 检查返回的 PR 状态 .merged == true", + f"提交 action report(含合并确认)", +] +``` + +#### action_report 建议格式 +``` +做了什么:确认 CI 通过后合并了 PR #{pr_number} +证据:PR merged=true, 合并者: {agent_id} +下一步:部署会自动触发,等待部署结果 +``` + +--- + +### 场景 5:PR 新提交请重新 Review(review_updated) + +#### 场景描述 +PR 有新 push。Webhook 事件:`pull_request` + `action=synchronize`。 + +#### 角色 +收件人:审查者(simayi-challenger 或上一次 Review 的提交者)。角色:**重新审查者** — 重点检查上次意见的修改部分。 + +#### 模板内容(信息层 — `review_updated.md`) + +```markdown +PR 有新提交,请重新 Review。 + +PR: http://192.168.2.154:3000/{repo}/pulls/{pr_number} +标题: {pr_title} +作者: @{pr_author} +新 commit: {new_sha} +``` + +#### 结构化 steps(指令层) + +```python +steps = [ + f"获取更新后的 PR diff: curl -s http://192.168.2.154:3000/api/v1/repos/{repo}/pulls/{pr_number}.diff " + f" -H 'Authorization: token {PAT}'", + f"重点检查上次 Review 意见相关的修改部分", + f"提交 Review: curl -s -X POST http://192.168.2.154:3000/api/v1/repos/{repo}/pulls/{pr_number}/reviews " + f" -H 'Authorization: token {PAT}' -H 'Content-Type: application/json' " + f" -d '{{\"event\": \"APPROVED\" 或 \"REQUEST_CHANGES\", \"body\": \"重新审查意见\"}}'", + f"提交 action report(含 Review 决定)", +] +``` + +#### action_report 建议格式 +``` +做了什么:重新审查了 PR #{pr_number}(新 commit: {new_sha}),提交了 {APPROVED/REQUEST_CHANGES} Review +证据:Review ID: {review_id} +下一步:{如果通过 → 等待作者合并 / 如果驳回 → 等待作者再修改} +``` + +--- + +### 场景 6:CI 失败(ci_failure) + +#### 场景描述 +CI workflow 在 PR 评论中写入 [CI] 失败信息。Webhook 事件:`issue_comment` + body 含 `[CI]` 或 `CI 失败`。 + +#### 角色 +收件人:PR 作者(如 zhangfei-dev)。角色:**修复者** — 根据 CI 错误修复代码。 + +#### 模板内容(信息层 — `ci_failure.md`) + +```markdown +CI 失败 + +PR: http://192.168.2.154:3000/{repo}/pulls/{pr_number} +分支: {branch} + +错误摘要: +{error_summary} +``` + +#### 结构化 steps(指令层) + +```python +steps = [ + f"获取 CI 详细状态: curl -s http://192.168.2.154:3000/api/v1/repos/{repo}/commits/{sha}/status " + f" -H 'Authorization: token {PAT}' → 找到失败的 step", + f"进入开发分支: git checkout {branch} && git pull origin {branch}", + f"根据错误摘要分析失败原因,修复代码", + f"运行本地测试确认修复有效", + f"push 修复: git add -A && git commit -m 'fix: 修复 CI 失败' && git push", + f"push 后 CI 自动重跑,等待 CI 结果", + f"提交 action report(含修复说明 + push 的 commit SHA)", +] +``` + +#### action_report 建议格式 +``` +做了什么:修复了 PR #{pr_number} 的 CI 失败({错误简要描述}),已 push +证据:commit SHA: {new_sha}, 分支: {branch} +下一步:等待 CI 重跑结果 +``` + +--- + +### 场景 7:部署失败(deploy_failure) + +#### 场景描述 +自动部署失败。触发方式:PR merge 后 auto-deploy 失败,或 Issue 创建含"部署失败"。 + +#### 角色 +收件人:庞统(pangtong-fujunshi)+ 姜维(jiangwei-infra)。角色:**运维者** — 排查部署问题。 + +#### 模板内容(信息层 — `deploy_failure.md`) + +```markdown +部署失败 + +仓库: {repo} +触发: PR #{pr_number} merge → auto-deploy + +失败原因: +{reason} +``` + +#### 结构化 steps(指令层) + +```python +steps = [ + f"查看仓库最新部署日志: 检查 PM2 日志或 docker logs", + f"进入安装目录检查服务状态: pm2 status / systemctl status", + f"分析失败原因(常见:依赖缺失、配置错误、端口冲突、权限问题)", + f"修复部署问题(手动部署 / 修配置 / 重装依赖)", + f"确认服务恢复正常: curl -s http://localhost:8083/api/projects → 检查响应", + f"提交 action report(含故障原因 + 修复操作 + 服务状态确认)", +] +``` + +#### action_report 建议格式 +``` +做了什么:排查了 {repo} 部署失败,{故障原因},已{修复操作} +证据:服务状态: {pm2 status / curl 结果} +下一步:{如需要代码修复 → 创建 Issue 指派开发者 / 如已恢复 → 无} +``` + +--- + +### 场景 8:@mention(mention) + +#### 场景描述 +评论/PR/Issue 中有人 @某个 Agent。Webhook 事件:`issue_comment` / `pull_request` / `issues`,body 含 `@agent-id`。 + +#### 角色 +收件人:被 @的 Agent。角色:**响应者** — 根据意图(求助/通知/协作/分配)响应。 + +#### 模板内容(信息层 — `mention.md`) + +```markdown +{mention_type}通知 + +来源: {source_type} {source_url} +评论者: @{commenter} +意图: {intent_hint} + +内容: +{content_snippet} + +📋 获取完整上下文: +- 详情: GET {gitea_api}/repos/{repo}/{source_detail_api_path} +- 评论列表: GET {gitea_api}/repos/{repo}/{source_comments_api_path} +``` + +#### 结构化 steps(指令层) + +```python +steps = [ + f"获取完整上下文: curl -s {gitea_api}/repos/{repo}/{source_detail_api_path} " + f" -H 'Authorization: token {PAT}'", + f"阅读评论内容和上下文,理解@你的意图({intent_hint})", + f"根据意图响应:求助 → 提供帮助/方案;通知 → 确认已了解;协作 → 参与协作;分配 → 执行分配的任务", + f"在 Gitea 回复评论(如需要): curl -s -X POST {gitea_api}/repos/{repo}/issues/{issue_number}/comments " + f" -H 'Authorization: token {PAT}' -H 'Content-Type: application/json' " + f" -d '{{\"body\": \"回复内容\"}}'", + f"提交 action report(含响应说明)", +] +``` + +#### action_report 建议格式 +``` +做了什么:响应了 @{commenter} 的 @mention({intent_hint}),{具体响应操作} +证据:{回复评论 ID / 执行的操作} +下一步:{是否需要后续跟进} +``` + +--- + +### 场景 9(inform):PR 合并通知(review_merged) + +> **此场景保持 inform 类型,不改为 action。** + +#### 场景描述 +PR 已合并到 main 分支。Webhook 事件:`pull_request` + `action=closed` + `merged=true`。 + +#### 角色 +收件人:PR 作者。角色:**被通知者** — 纯 FYI,无需行动。 + +#### 模板内容(保持现有 `review_merged.md`,不改) + +```markdown +## PR 已合并 ✅ + +**仓库**: {repo} +**PR #{pr_number}**: {pr_title} +**作者**: @{pr_author} +**合并者**: @{merged_by} + +PR 已成功合并到主分支。部署会自动触发。 +``` + +#### verify 逻辑 +inform 始终通过。Agent 收到后已阅即 done。 + +--- + +### 场景 10(action):Review 评论(review_comment) + +> 补充场景:Review 评论(COMMENTED 状态)也改为 action。 + +#### 场景描述 +审查者留下了评论(非通过/驳回)。Webhook 事件:`pull_request_review` + state=`COMMENTED`。 + +#### 角色 +收件人:PR 作者。角色:**响应者** — 查看评论并决定是否需要修改。 + +#### 模板内容(信息层 — `review_comment.md`) + +```markdown +Review 评论 + +PR: http://192.168.2.154:3000/{repo}/pulls/{pr_number} +标题: {pr_title} +评论者: @{reviewer} + +{comment_body} +``` + +#### 结构化 steps(指令层) + +```python +steps = [ + f"仔细阅读评论内容,理解审查者的反馈", + f"判断是否需要修改代码(建议修改 / 仅讨论 / 确认问题)", + f"如果需要修改: git checkout {branch} && git pull → 修改代码 → push", + f"如果不需要修改: 在 PR 回复评论说明理由", + f"提交 action report(含评论处理决定)", +] +``` + +#### action_report 建议格式 +``` +做了什么:查看了 PR #{pr_number} 的 Review 评论,{处理决定} +证据:{修改的 commit SHA / 回复的评论 ID} +下一步:{如修改了 → 等 CI 和重新 Review / 如无需修改 → 无} +``` + +--- + +## §6B. 两个维度完整性验证 + +### 维度 1:Webhook Event 覆盖矩阵 + +列出 Gitea 所有相关 webhook event,每种事件对应的 action 场景,验证无遗漏: + +| Gitea Event | action | → Action 场景 | 收件人 | type | §6A 场景 | +|-------------|--------|-------------|--------|------|---------| +| `issues` | `assigned` | Issue 指派 | 被指派人 | action | 场景 1 | +| `issues` | `opened` (含"部署失败") | 部署失败 | 庞统+姜维 | action | 场景 7 | +| `issues` | `opened` (含 @mention) | @mention | 被@者 | action | 场景 8 | +| `pull_request` | `opened` | PR Review 请求 | 审查者 | action | 场景 2 | +| `pull_request` | `opened` (含 @mention) | @mention | 被@者 | action | 场景 8 | +| `pull_request` | `synchronize` | 重新 Review | 审查者 | action | 场景 5 | +| `pull_request` | `closed` (merged) | PR 合并通知 | PR 作者 | **inform** | 场景 9 | +| `pull_request_review` | `APPROVED` | Review 通过 | PR 作者 | action | 场景 4 | +| `pull_request_review` | `REQUEST_CHANGES` | Review 驳回 | PR 作者 | action | 场景 3 | +| `pull_request_review` | `COMMENTED` | Review 评论 | PR 作者 | action | 场景 10 | +| `issue_comment` | `created` (含 [CI]) | CI 失败 | PR 作者 | action | 场景 6 | +| `issue_comment` | `created` (含 @mention) | @mention | 被@者 | action | 场景 8 | +| `deployment_status` | `failure` | 部署失败 | 庞统+姜维 | action | 场景 7 | + +**未覆盖的 Gitea Event**(评估后排除): + +| Event | 说明 | 排除理由 | +|-------|------|---------| +| `push` (to main) | 代码推送到 main | 由 auto-deploy 处理,不需要 Agent 行动 | +| `push` (to branch) | 代码推送到分支 | 由 CI 自动触发,PR push → synchronize 事件覆盖 | +| `pull_request` `closed` (not merged) | PR 被关闭未合并 | 罕见,按需手动处理 | +| `pull_request` `edited` | PR 标题/描述修改 | 无需 Agent 行动 | +| `pull_request` `labeled`/`unlabeled` | 标签变化 | 无需 Agent 行动 | +| `pull_request` `assigned` | PR 被指派 | 与 Review 请求重复,已由 opened 覆盖 | +| `issues` `closed`/`edited`/`labeled` | Issue 状态变化 | 无需 Agent 行动 | +| `issue_comment` `edited`/`deleted` | 评论修改/删除 | 无需 Agent 行动 | + +**结论**:所有需要 Agent 行动的 webhook event 均已覆盖。 + +--- + +### 维度 2:流程流转覆盖矩阵 + +追踪一个需求从 Issue → 分支 → 编码 → CI → PR → Review → Merge → Deploy 全链路: + +``` +[主公提需求] + │ + ▼ +[庞统规划] → 创建 Issue + 指派 + │ + ▼ +┌─────────────────────────────────────────────────┐ +│ 节点 1: Issue 指派 │ +│ 负责人: 开发者(张飞) │ +│ Action: issue_assigned(场景 1) │ +│ Agent 执行: 创建分支 → 编码 → push → 创建 PR │ +│ 完成后触发: PR opened webhook │ +└─────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────┐ +│ 节点 2: CI 检查(首次 push) │ +│ 负责人: CI 系统 │ +│ 如果通过 → 进入 PR Review 节点 │ +│ 如果失败 ↓ │ +│ ┌───────────────────────────────────────┐ │ +│ │ 节点 2a: CI 失败 │ │ +│ │ 负责人: 开发者(张飞) │ │ +│ │ Action: ci_failure(场景 6) │ │ +│ │ Agent 执行: 修复 → push → CI 重跑 │ │ +│ │ 循环直到 CI 通过 → 回到节点 2 │ │ +│ └───────────────────────────────────────┘ │ +└─────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────┐ +│ 节点 3: PR Review 请求 │ +│ 负责人: 审查者(司马懿) │ +│ Action: review_request(场景 2) │ +│ Agent 执行: 读 diff → 审查 → 提交 Review │ +│ 完成后触发: review webhook │ +└─────────────────────────────────────────────────┘ + │ + ├─── Review 通过 ──→ 节点 4a + ├─── Review 驳回 ──→ 节点 4b + └─── Review 评论 ──→ 节点 4c + +┌─────────────────────────────────────────────────┐ +│ 节点 4a: Review 通过 │ +│ 负责人: PR 作者(张飞) │ +│ Action: review_result APPROVED(场景 4) │ +│ Agent 执行: 确认 CI → merge PR │ +│ 完成后触发: PR merged webhook → 节点 5 │ +└─────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────┐ +│ 节点 4b: Review 驳回 │ +│ 负责人: PR 作者(张飞) │ +│ Action: review_result REQUEST_CHANGES(场景 3) │ +│ Agent 执行: 读意见 → 修改代码 → push │ +│ 完成后触发: PR synchronize webhook → 节点 4d │ +└─────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────┐ +│ 节点 4c: Review 评论 │ +│ 负责人: PR 作者(张飞) │ +│ Action: review_comment(场景 10) │ +│ Agent 执行: 看评论 → 修改或回复 │ +│ 如果修改了 → push → 节点 4d │ +└─────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────┐ +│ 节点 4d: PR 更新,重新 Review │ +│ 负责人: 审查者(司马懿) │ +│ Action: review_updated(场景 5) │ +│ Agent 执行: 读 diff → 重点检查修改 → 提交 Review │ +│ 完成后触发: review webhook → 回到节点 4a/4b/4c │ +└─────────────────────────────────────────────────┘ + + │ (从 4a merge) + ▼ +┌─────────────────────────────────────────────────┐ +│ 节点 5: PR 合并通知 │ +│ 负责人: PR 作者(张飞)— 仅通知 │ +│ Type: inform — review_merged(场景 9) │ +│ 自动触发: auto-deploy │ +└─────────────────────────────────────────────────┘ + │ + ├─── 部署成功 → 流程结束 ✓ + └─── 部署失败 ↓ + +┌─────────────────────────────────────────────────┐ +│ 节点 6: 部署失败 │ +│ 负责人: 庞统 + 姜维 │ +│ Action: deploy_failure(场景 7) │ +│ Agent 执行: 查日志 → 排查 → 修复 → 确认恢复 │ +│ 完成后: 手动重新部署或等待修复后自动部署 │ +└─────────────────────────────────────────────────┘ + + 此外,@mention 可在任何节点触发(跨出主流程): + +┌─────────────────────────────────────────────────┐ +│ 旁路: @mention │ +│ 触发: 任何评论/Issue/PR 中包含 @agent-id │ +│ 负责人: 被@的 Agent │ +│ Action: mention(场景 8) │ +│ Agent 执行: 读上下文 → 按意图响应 │ +└─────────────────────────────────────────────────┘ +``` + +**断链检查**: + +| 流程节点 | 入口 Action | 出口触发 | 是否闭环 | +|---------|------------|---------|---------| +| Issue 指派 | issue_assigned | push → CI / PR opened | ✅ | +| CI 失败 | ci_failure | push → CI 重跑 | ✅ 循环到通过 | +| PR Review 请求 | review_request | review webhook | ✅ | +| Review 通过 | review_result APPROVED | PR merged webhook | ✅ | +| Review 驳回 | review_result REQUEST_CHANGES | PR synchronize webhook | ✅ → 重新 Review | +| Review 评论 | review_comment | push / 回复 | ✅ | +| PR 更新 | review_updated | review webhook | ✅ → 回到 Review | +| PR 合并 | inform (review_merged) | auto-deploy | ✅ | +| 部署失败 | deploy_failure | 手动修复 | ✅ | +| @mention | mention | Agent 自主响应 | ✅ | + +**结论**:全链路 10 个节点均有对应 Action 或 inform,无断链。 + +**异常路径覆盖**: + +| 异常场景 | 处理 | +|---------|------| +| Agent 收到 action 但 crash | verify_completion 检查无 action_report → 标 failed → 通知发件人 | +| Agent 收到 action 但降级处理 | Red Flag 表防止降级;即使降级,verify 检查无 report → 标 failed | +| Agent 提交虚假 report | 后续事件链自然暴露(CI 不通过、Reviewer 收不到 Review) | +| Agent 超时未完成 | check_completion 不触发(action 不走 ticker);靠 verify + on_failure | +| 同一事件多次触发 | 幂等检查(delivery UUID + 内容 hash)防止重复 Mail | + +--- + +--- + +## §7. 优秀实践参考 + +### 7.1 §13 §15.5 流程强约束模板 + +设计文档 §15.5 已定义 6 个强约束模板,每个模板包含: +- 结构化步骤列表(编号) +- 具体的 Gitea API 调用指令 +- 明确的完成确认要求("完成后回复此 Mail 确认") +- 时限要求 + +本设计在此基础上: +- 将"回复此 Mail 确认"改为"提交 action report"(更适合 Agent 自动化) +- 将步骤从模板纯文本提取到 must_haves JSON 的 `steps` 字段(结构化、可编程化) +- 通过 PromptSection 强约束确保 Agent 知道必须执行 + +### 7.2 wanman:消息优先级 + 持久化 + +wanman 使用 steer/normal 消息优先级区分重要性。action 类型的设计借鉴了这一思路: +- inform = normal(可忽略) +- action = steer(不可忽略) +- request = steer + 需要双向通信 + +### 7.3 OpenAI Agents SDK:handoff input_filter + +OpenAI Agents SDK 的 handoff 机制使用 `input_filter` 传递结构化数据。action 类型的 `context` 字段类似——在 Mail metadata 中携带结构化上下文,Agent 可直接使用而不需要从纯文本中解析。 + +### 7.4 Superpowers:Red Flags 表 + +Superpowers 使用 Red Flags 表防止 Agent 自合理化跳过约束。action 约束段中增加 Red Flag 示例(如"这个通知看看就行了"→错!),防止 Agent 将 action 降级为 inform 处理。 + +--- + +## §8. 向后兼容分析 + +### 8.1 现有 inform/request 不受影响 + +| 场景 | 影响 | 说明 | +|------|------|------| +| Agent 间手动发 inform Mail | ✅ 无影响 | `POST /api/mail` 不传 type,默认 inform | +| Agent 间手动发 request Mail | ✅ 无影响 | `POST /api/mail` type=request,现有逻辑 | +| 现有 inform Mail 的 verify | ✅ 无影响 | `verify_completion` 的 inform 分支不变 | +| 现有 request Mail 的 verify | ✅ 无影响 | `verify_completion` 的 request 分支不变 | + +### 8.2 _send_mail 参数兼容 + +`_send_mail` 新增参数 `mail_type="inform"` 作为默认值。现有所有调用不传此参数时,行为与之前完全一致。 + +```python +# 现有调用(不传 mail_type)→ 默认 inform,行为不变 +_send_mail("zhangfei-dev", title, text) + +# 新调用(传 mail_type="action") +_send_mail("zhangfei-dev", title, text, mail_type="action", action_type="ci_failure", steps=[...]) +``` + +### 8.3 PromptContext 新增字段 + +`action_steps` 和 `action_context` 使用 `field(default_factory=list/dict)`,不传时为空。现有 `PromptContext` 构造不受影响。 + +### 8.4 comments 表 CHECK 约束 + +新增 `action_report` 到 CHECK 约束。旧数据中的 comment_type 值都在现有枚举中,不受影响。migration 通过重建表处理。 + +**D17-3: comments 表 CHECK 约束改为去掉 CHECK 或新增 action_report,通过 migration 处理** + +### 8.5 Mail API(`POST /api/mail`)对外接口 + +外部调用者(Agent 发邮件)的接口不变。action 类型目前仅由 `_send_mail` 内部函数使用(toolchain 事件中枢)。Agent 间通信继续使用 inform/request。 + +**未来可选**:Mail API 开放 `type=action` 参数,让 Agent 也能发 action Mail。当前不做。 + +--- + +## §9. check_completion 与 ticker 适配 + +### 9.1 check_completion 方法 + +当前 `check_completion` 调用 `_check_reply`(为 request 设计)。action 类型不需要 ticker 检查(因为 action 的完成由 `post_complete` → `verify_completion` 处理)。 + +```python +def check_completion(self, task_id: str, db_path: Path) -> bool: + """ticker 级别的完成检查""" + performative = self._parse_performative(task_id, db_path) + if performative == "request": + return self._check_reply(task_id, db_path) + return False # inform 和 action 不需要 ticker 检查 +``` + +### 9.2 on_failure 复用 + +action verify 失败时,复用 request 的 `on_failure` 逻辑(标 failed + 通知发件人)。reason 传 `"no_action_report"`,`mail_notify` 的 `_REASON_MAP` 新增此 reason 的人话翻译。 + +--- + +## §10. 设计决策记录 + +### D17-1: action 完成验证采用 comment 执行报告机制 + +**决策**:Agent 通过 comments 表提交 `action_report` 类型评论作为执行证据。`verify_completion` 检查该评论是否存在。 + +**讨论的替代方案**: +- A(外部状态检查):太复杂,每个 action_type 需要不同检查逻辑 +- C(始终通过):等于 inform,失去意义 +- D(outcome 检查):只能检测 crash,不够 + +**理由**:方案 B 最简单且与现有架构一致。虽然不能防止"假装执行",但后续事件链会自然暴露问题。验证的首要目标是防止 Agent"没看到/忽略了"。 + +### D17-2: 除 PR 合并通知外,所有工具链事件从 inform 改为 action + +**决策**:9 种工具链场景中,8 种改为 action,仅 `review_merged` 保持 inform。 + +**理由**: +- 这些场景都需要 Agent 执行后续动作(修代码/审查/合并/排查) +- 当前 inform 语义直接矛盾于期望行为(Prompt 说"已阅即可") +- PR 合并是真正的 FYI,无需 Agent 行动 + +### D17-3: comments 表 CHECK 约束处理方式 + +**决策**:通过 migration 新增 `action_report` 到 CHECK 约束。或者直接去掉 CHECK 约束(CHECK 仅文档作用,去掉不影响功能)。 + +**理由**:SQLite 修改 CHECK 需要 `CREATE TABLE new → COPY → DROP → RENAME`。建议直接去掉 CHECK,在应用层校验。减少未来 migration 成本。 + +### D17-4: action 类型目前仅限 _send_mail 内部使用 + +**决策**:Mail API(`POST /api/mail`)不开放 `type=action` 参数。action 类型仅由 toolchain 事件中枢的 `_send_mail` 函数创建。 + +**理由**: +- action 的结构化字段(steps、action_type、context)需要调用方理解 +- Agent 间通信用 inform/request 足够 +- 避免 Agent 发 action Mail 给另一个 Agent 但不知道怎么写 steps + +### D17-5: 推翻 §16.0 D1 决策 + +**决策**:推翻 D1"不做第三种 task 类型"的决策,新增 action 类型。 + +**理由**: +- D1 假设模板内容足以引导 Agent 行动,但 inform 的 PromptSection 语义("已阅即可"、"不要执行任何状态转换命令")直接矛盾于期望行为 +- Agent 行为受 Prompt 控制,不是 description 内容。需要 action 类型来提供正确的 Prompt 语义 +- 实际运行已验证:Agent 确实把 Review 驳回/CI 失败通知当纯通知处理后就 done 了 + +--- + +## §11. 实施计划 + +### Step 1:基础设施(db + PromptContext + spawner 解析) + +| 子步骤 | 文件 | 内容 | +|--------|------|------| +| 1a | `db.py` | comments 表 CHECK 约束处理(migration) | +| 1b | `prompt_composer.py` | PromptContext 新增 `action_steps`、`action_context` 字段 | +| 1c | `spawner.py` | 解析 must_haves 时提取 action 字段 | + +风险:极低,纯新增。 + +### Step 2:mail_handler 改动 + +| 子步骤 | 文件 | 内容 | +|--------|------|------| +| 2a | `mail_handler.py` | `verify_completion` 新增 action 分支 | +| 2b | `mail_handler.py` | 新增 `_check_action_report` 方法 | +| 2c | `mail_handler.py` | `MailContextSection._render_action` 方法 | +| 2d | `mail_handler.py` | `MailConstraintsSection` action 分支 | +| 2e | `mail_handler.py` | `check_completion` request-only | + +风险:低,inform/request 路径不受影响。 + +### Step 3:toolchain_routes + 模板改动 + +| 子步骤 | 文件 | 内容 | +|--------|------|------| +| 3a | `toolchain_routes.py` | `_send_mail` 新增参数(mail_type / action_type / steps / context_data) | +| 3b | `toolchain_routes.py` | 8 个 handler 改为 action 类型 + 按场景传入定制 steps | +| 3c | `toolchain_routes.py` | `review_merged` 保持 inform | +| 3d | `templates/toolchain/*.md` | 重写 8 个模板为纯信息层(去除内嵌的步骤指引) | + +风险:中,需要逐个 handler 确认 steps 内容正确、逐个模板确认信息层干净。 + +### Step 4:测试 + 验证 + +| 子步骤 | 内容 | +|--------|------| +| 4a | 单元测试:verify_completion 三分支 | +| 4b | 单元测试:_check_action_report | +| 4c | 集成测试:toolchain webhook → action Mail → Agent 处理 → comment → done | +| 4d | 回归测试:inform/request 路径不变 | +| 4e | mail_notify 的 `_REASON_MAP` 新增 `no_action_report` | + +--- + +## §12. 风险评估 + +| 风险 | 概率 | 影响 | 缓解措施 | +|------|------|------|----------| +| Agent 不提交 action report | 中 | 高 | Prompt 强约束 + Red Flag 表 + verify 失败标 failed + 通知发件人 | +| Agent 提交虚假 action report | 低 | 中 | 后续事件链自然暴露(CI 不会通过、Reviewer 不会收到 Review) | +| 现有 inform/request 行为变化 | 低 | 高 | 充分回归测试 + 新参数默认值保持 inform | +| comments 表 migration 失败 | 低 | 中 | 备份数据库 + migration 脚本支持回滚 | +| Agent 混淆 action 和 request | 低 | 低 | Prompt 明确区分:"不需要回复此邮件,提交 action report 即可" | +| toolchain handler steps 写错 | 中 | 低 | 代码审查 + 集成测试验证 | + +--- + +## §13. 未来演进 + +### 13.1 Mail API 开放 action 类型 + +未来可考虑让 Agent 也能通过 `POST /api/mail` 发送 action Mail。需要: +- API 参数校验 steps 格式 +- 文档说明 action vs inform/request 的使用场景 + +### 13.2 外部状态验证(方案 A 补充) + +未来可选择性增加某些 action_type 的外部状态验证作为补充: +- `review_request`:检查 PR 是否有新的 Review +- `ci_failure`:检查 PR 是否有新 push +- `issue_assigned`:检查是否有关联 PR 创建 + +这是可选增强,不影响 D17-1 决策(方案 B 为主,方案 A 为可选补充)。 + +### 13.3 action 超时检测 + +参考 §15.4 的超时处置机制,action 类型也可引入超时检测: +- Agent 收到 action Mail 后 N 小时内未提交 action report → 重发提醒 +- 超时严重时 spawn 庞统介入 + +当前由 `verify_completion` 的 fail → on_failure → notify 机制处理,未来可增强。 + +--- + +## §14. 审查检查清单 + +- [ ] §1 问题陈述是否准确(当前 inform 硬编码导致流程断链) +- [ ] §2 action 类型定义是否清晰(语义、schema、行为矩阵) +- [ ] §3 验证机制选择是否合理(方案 B 的优劣分析) +- [ ] §4 场景分类是否正确(10 种场景的 type 分配) +- [ ] §5 影响范围是否完整(有无遗漏的文件,模板改动量是否合理) +- [ ] §6 PromptSection 渲染是否正确(五层架构、三分支、约束段、Red Flag 表) +- [ ] §6A 每个场景的模板内容(信息层)与 steps(指令层)是否严格分离 +- [ ] §6A Steps 是否为 API 调用级别(含 endpoint),而非目标级描述 +- [ ] §6A 每个场景的 action_report 建议格式是否合理 +- [ ] §6B 维度 1(Webhook Event 覆盖)是否有遗漏 +- [ ] §6B 维度 2(流程流转覆盖)是否有断链 +- [ ] §8 向后兼容是否充分(现有 inform/request 不受影响) +- [ ] §10 决策记录是否完整(D17-1 ~ D17-5) +- [ ] §11 实施计划是否可行(4 步渐进,模板重写已纳入)