docs: Mail 失败通知设计 v1.1 — system 发件人 + Mail 不 @pangtong
Deploy / ci (push) Waiting to run
Deploy / deploy (push) Blocked by required conditions
Deploy / notify-deploy-failure (push) Blocked by required conditions

This commit is contained in:
cfdaily
2026-06-07 01:15:59 +08:00
parent cef76a72e0
commit 6465a6c43f
+60 -48
View File
@@ -1,65 +1,73 @@
# Mail 失败通知设计
> 版本:v1.0-draft | 日期:2026-06-07 | 作者:庞统 | 状态:待确认
> 版本:v1.1-draft | 日期:2026-06-07 | 作者:庞统 | 状态:待评审
## 1. 背景
当前 Mail 失败后没有通知发件人。F2 修复让所有 `_mark_task("failed")` 统一 @pangtong-fujunshi,但这对 Mail 不合理:
- Mail 是 A→B 点对点通信,失败应通知发件人 A
- F2 覆盖的是 spawn 级失败(crashed/auth_failed 等),属系统基础设施问题,@pangtong 合理
- **但 Mail 唯一特有的失败——no_reply_found(幻觉门控,Agent 没回复 request)——没有任何通知**
- Mail 是 A→B 点对点通信,失败应通知发件人 A,不是庞统
- **Mail 唯一特有的失败——no_reply_found(幻觉门控,Agent 没回复 request)——没有任何通知**
### 失败场景分类
| 触发点 | 场景 | 当前通知 | 应该通知谁 |
|--------|------|---------|-----------|
| `_mark_task("failed")` | spawn 级失败:crashed、auth_failed、crash_limit、timeout 等 | @pangtongF2 | 两者都该通知:庞统(系统问题)+ 发件人(投递失败 |
| `_mark_task("failed")` | spawn 级失败:crashed、auth_failed、crash_limit、timeout 等 | @pangtongF2 | 发件人(不再 @pangtong |
| `_mail_auto_complete` no_reply_found | Agent 正常退出但没回复 request 类邮件 | 无 | 发件人 |
## 2. 设计方案
**核心机制**Mail 最终标 failed 时,daemon 给 from_agent 发一封系统 inform 邮件,告知原因和处理建议。
### 2.1 核心机制
### 2.1 实现位置
Mail 最终标 failed 时,daemon 以 **系统发件人(`system`** 身份给 from_agent 发一封 inform 邮件,告知原因和处理建议。
新增 `src/daemon/mail_notify.py`,统一函数 `notify_mail_failed`
**走标准 `POST /api/mail` 流程**,新增 `system` 为合法发件人。
```python
def notify_mail_failed(db_path: Path, original_mail_id: str, reason: str, detail: dict = None):
"""Mail 失败后给发件人发系统通知邮件
直接操作 Blackboard 创建 task,不走 POST /api/mail(避免 A9 校验)。
"""
```
### 2.2 系统发件人
### 2.2 触发点
新增 `system` 作为 Mail 的特殊发件人:
-`mail_routes.py``VALID_AGENTS` 或校验逻辑中,允许 `from="system"`
- `system` 不是真实 Agent,没有 main session,只用于发送通知
- 防递归:通知邮件的 must_haves 中加 `"system_notify": true`,触发前检查该标记
### 2.3 触发点
**触发点 1**`_mail_auto_complete` 中 no_reply_found 标 failed 之后(dispatcher.py
- 位置:标 failed 成功后、return 之前
- 调用:`notify_mail_failed(db_path, task_id, "no_reply_found")`
- from_agent 从 must_haves 解析
**触发点 2**`_mark_task` 中 status=="failed" 且是 _mail 项目时(spawner.py
- 位置:现有 F2 @pangtong 逻辑之后
- 条件:需要判断 task 是否属于 `_mail` 项目
- 调用:`notify_mail_failed(db_path, task_id, reason)`
- from_agent 同样从 must_haves 解析
- 位置:现有 F2 @pangtong 逻辑
- **改动**:当项目是 `_mail` 时,**不 @pangtong**,改为调用 `notify_mail_failed` 通知发件人
- 当项目不是 `_mail` 时,F2 @pangtong 逻辑不变
### 2.3 通知邮件内容
### 2.4 实现位置
通知邮件字段
新增 `src/daemon/mail_notify.py`,统一函数 `notify_mail_failed`
```python
def notify_mail_failed(db_path: Path, original_mail_id: str, reason: str, detail: dict = None):
"""Mail 失败后以 system 身份给发件人发通知邮件
走标准 POST /api/mail 流程(from=system)。
防递归:检查原邮件 must_haves.system_notify,为 true 则跳过。
"""
```
内部调用 `send_mail(from="system", to=from_agent, type="inform", ...)`
### 2.5 通知邮件内容
| 字段 | 值 |
|------|------|
| task_type | `"mail"` |
| assigned_by | `"daemon"` |
| assignee | from_agent(原邮件发件人) |
| status | `"pending"`(走正常投递流程,Agent 会收到) |
| from | `"system"` |
| to | from_agent(原邮件发件人) |
| type | `"inform"` |
| title | `"[投递失败] {原邮件title}"` |
| must_haves.type | `"inform"` |
| must_haves.system_notify | `true`(防递归标记) |
| must_haves.in_reply_to | 原邮件 task_id |
@@ -73,11 +81,7 @@ def notify_mail_failed(db_path: Path, original_mail_id: str, reason: str, detail
| `task_timeout` | "邮件「{title}」处理超时" | "建议重发或通过其他方式联系" |
| 其他 | "邮件「{title}」投递失败(原因:{reason}" | "建议联系庞统排查" |
### 2.4 防递归
系统通知邮件本身也可能失败(spawn 崩溃等),必须防止无限递归:
**方案**:通知邮件的 must_haves 中加 `"system_notify": true`。在触发点检查时,如果原邮件的 must_haves 含 `system_notify: true`,跳过不再发通知。
### 2.6 防递归
```python
# 防递归:系统通知邮件失败不再发通知
@@ -86,7 +90,7 @@ if meta.get("system_notify"):
return # 系统邮件失败,不再递归通知
```
### 2.5 from_agent 获取
### 2.7 from_agent 获取
原 Mail 的发件人存在两个等价位置:
- `task.assigned_by`
@@ -94,30 +98,38 @@ if meta.get("system_notify"):
优先用 `assigned_by`(无需解析 JSON),fallback 解析 must_haves。
## 3. 不改的
## 3. 改动清单
| 项目 | 原因 |
|------|------|
| F2 @pangtong 逻辑 | spawn 级失败仍需要庞统知道,保留 |
| `POST /api/mail` | 不新增"系统发件人"角色,通知直接操作 Blackboard |
| no_reply_found 的标 failed 逻辑 | 只在标 failed 之后加通知,不改判定逻辑 |
| inform 类型邮件的完成逻辑 | inform 直接标 done,不存在 no_reply_found 场景 |
### 3.1 新增文件
## 4. 改动范围
| 文件 | 内容 | 预估行数 |
|------|------|---------|
| `src/daemon/mail_notify.py` | `notify_mail_failed` 函数 | ~60 行 |
### 3.2 修改文件
| 文件 | 改动 | 预估行数 |
|------|------|---------|
| `src/daemon/mail_notify.py`(新增) | `notify_mail_failed` 函数 | ~60 行 |
| `src/daemon/dispatcher.py` | `_mail_auto_complete` 标 failed 后调 notify | ~5 行 |
| `src/daemon/spawner.py` | `_mark_task` failed 分支_mail 判断 + 调 notify | ~10 行 |
| `src/daemon/spawner.py` | `_mark_task` failed 分支`_mail` 项目不 @pangtong,改调 notify | ~10 行 |
| `src/api/mail_routes.py` | 允许 `system` 作为合法发件人(from 校验) | ~3 行 |
总计约 75 行,3 个文件。
总计约 80 行,4 个文件。
## 5. 验证方式
### 3.3 不改的
| 项目 | 原因 |
|------|------|
| F2 @pangtong 对 Task 的逻辑 | Task failed 仍 @pangtong,只对 Mail 不适用 |
| no_reply_found 的标 failed 逻辑 | 只在标 failed 之后加通知,不改判定逻辑 |
| inform 类型邮件的完成逻辑 | inform 直接标 done,不存在 no_reply_found 场景 |
## 4. 验证方式
| 场景 | 验证 | 预期 |
|------|------|------|
| request 邮件无回复 | Agent 不回复 → _mail_auto_complete 标 failed | 发件人收到系统通知邮件 |
| spawn 崩溃导致 Mail failed | Agent spawn crash → _mark_task failed | 发件人收到通知 + 庞统收到 @mention |
| request 邮件无回复 | Agent 不回复 → _mail_auto_complete 标 failed | 发件人收到 system 通知邮件 |
| spawn 崩溃导致 Mail failed | Agent spawn crash → _mark_task failed | 发件人收到 system 通知,**不** @pangtong |
| Task failed | Task spawn crash → _mark_task failed | @pangtongF2 不变) |
| 系统通知邮件本身失败 | 通知邮件也 crash | 不再递归(system_notify=true |
| inform 邮件正常完成 | Agent 收到 inform → 直接 done | 不触发通知 |