5.3 KiB
5.3 KiB
Mail 失败通知设计
版本:v1.1-draft | 日期:2026-06-07 | 作者:庞统 | 状态:待评审
1. 背景
当前 Mail 失败后没有通知发件人。F2 修复让所有 _mark_task("failed") 统一 @pangtong-fujunshi,但这对 Mail 不合理:
- Mail 是 A→B 点对点通信,失败应通知发件人 A,不是庞统
- Mail 唯一特有的失败——no_reply_found(幻觉门控,Agent 没回复 request)——没有任何通知
失败场景分类
| 触发点 | 场景 | 当前通知 | 应该通知谁 |
|---|---|---|---|
_mark_task("failed") |
spawn 级失败:crashed、auth_failed、crash_limit、timeout 等 | @pangtong(F2) | 发件人(不再 @pangtong) |
_mail_auto_complete no_reply_found |
Agent 正常退出但没回复 request 类邮件 | 无 | 发件人 |
2. 设计方案
2.1 核心机制
Mail 最终标 failed 时,daemon 以 系统发件人(system) 身份给 from_agent 发一封 inform 邮件,告知原因和处理建议。
走标准 POST /api/mail 流程,新增 system 为合法发件人。
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")
触发点 2:_mark_task 中 status=="failed" 且是 _mail 项目时(spawner.py)
- 位置:现有 F2 @pangtong 逻辑处
- 改动:当项目是
_mail时,不 @pangtong,改为调用notify_mail_failed通知发件人 - 当项目不是
_mail时,F2 @pangtong 逻辑不变
2.4 实现位置
新增 src/daemon/mail_notify.py,统一函数 notify_mail_failed:
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 通知邮件内容
| 字段 | 值 |
|---|---|
| from | "system" |
| to | from_agent(原邮件发件人) |
| type | "inform" |
| title | "[投递失败] {原邮件title}" |
| must_haves.system_notify | true(防递归标记) |
| must_haves.in_reply_to | 原邮件 task_id |
通知正文按 reason 区分:
| reason | 正文模板 | 处理建议 |
|---|---|---|
no_reply_found |
"你发送的 request 邮件「{title}」未被回复" | "建议重发邮件,或通过黑板任务方式联系" |
auth_failed |
"邮件「{title}」投递时认证失败" | "需检查 Agent 配置,联系姜维排查" |
crash_limit |
"邮件「{title}」投递时多次崩溃" | "系统异常,建议稍后重试" |
task_timeout |
"邮件「{title}」处理超时" | "建议重发或通过其他方式联系" |
| 其他 | "邮件「{title}」投递失败(原因:{reason})" | "建议联系庞统排查" |
2.6 防递归
# 防递归:系统通知邮件失败不再发通知
meta = json.loads(original_task.must_haves) if original_task.must_haves else {}
if meta.get("system_notify"):
return # 系统邮件失败,不再递归通知
2.7 from_agent 获取
原 Mail 的发件人存在两个等价位置:
task.assigned_bymust_havesJSON 的"from"字段
优先用 assigned_by(无需解析 JSON),fallback 解析 must_haves。
3. 改动清单
3.1 新增文件
| 文件 | 内容 | 预估行数 |
|---|---|---|
src/daemon/mail_notify.py |
notify_mail_failed 函数 |
~60 行 |
3.2 修改文件
| 文件 | 改动 | 预估行数 |
|---|---|---|
src/daemon/dispatcher.py |
_mail_auto_complete 标 failed 后调 notify |
~5 行 |
src/daemon/spawner.py |
_mark_task failed 分支:_mail 项目不 @pangtong,改调 notify |
~10 行 |
src/api/mail_routes.py |
允许 system 作为合法发件人(from 校验) |
~3 行 |
总计约 80 行,4 个文件。
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 | 发件人收到 system 通知邮件 |
| spawn 崩溃导致 Mail failed | Agent spawn crash → _mark_task failed | 发件人收到 system 通知,不 @pangtong |
| Task failed | Task spawn crash → _mark_task failed | @pangtong(F2 不变) |
| 系统通知邮件本身失败 | 通知邮件也 crash | 不再递归(system_notify=true) |
| inform 邮件正常完成 | Agent 收到 inform → 直接 done | 不触发通知 |