Files
sanguo_moziplus_v2/docs/design/mail-failure-notification.md
T
cfdaily cd1c1f76f4
Deploy / ci (push) Waiting to run
Deploy / deploy (push) Blocked by required conditions
Deploy / notify-deploy-failure (push) Blocked by required conditions
auto-sync: 2026-06-07 08:34:07
2026-06-07 08:34:07 +08:00

6.6 KiB
Raw Blame History

Mail 失败通知设计

版本:v1.3 | 日期: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 等 @pangtongF2 发件人(不再 @pangtong
_mail_auto_complete no_reply_found Agent 正常退出但没回复 request 类邮件 发件人

所有 Mail failed 都通知发件人,不管 reason。

2. 设计方案

2.1 核心机制

Mail 最终标 failed 时,daemon 以 系统发件人(system 身份给 from_agent 发一封 inform 邮件,告知原因和处理建议。

2.2 系统发件人

新增 system 作为 Mail 的特殊发件人:

  • 不暴露给外部 APIsystem 只允许 daemon 内部调用
  • 实现方式mail_notify.py 直接调用 send_mail 的内部函数(_do_send_mail),不走 FastAPI HTTP 路由
  • system 不是真实 Agent,没有 main session,只用于发送通知
  • 防递归:通知邮件的 must_haves 中加 "system_notify": true,触发前检查该标记

2.3 判断 _mail 项目

_mark_task 签名只有 (db_path, task_id, status, detail),没有 project_id。从 db_path 推断:

def _is_mail_project(db_path: Path) -> bool:
    path_str = str(db_path)
    return "/_mail/" in path_str or path_str.endswith("_mail.db")

覆盖两种 _mail db_pathdata/_mail/blackboard.db(新)和 data/_mail.db(旧)。

2.4 触发点

触发点 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 逻辑处
  • 改动:当 _is_mail_project(db_path) 时,不 @pangtong,改为调用 notify_mail_failed
  • 当不是 _mail 项目时,F2 @pangtong 逻辑不变

2.5 实现位置

新增 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 身份给发件人发通知邮件
    
    直接调用 _do_send_mail 内部函数,不走 HTTP。
    防递归:检查原邮件 must_haves.system_notify,为 true 则跳过。
    """

2.6 通知邮件内容

字段
from "system"
to from_agent(原邮件发件人)
type "inform"
title "[投递失败] {原邮件title}"
must_haves.system_notify true(防递归标记)
must_haves.in_reply_to 原邮件 task_id

通知正文使用统一模板,把所有可能的失败原因和建议放在一封邮件里:

你的邮件投递失败了。

📧 原始邮件:「{title}」
👤 收件人:{to_agent}
❌ 失败原因:{reason}

常见失败原因及处理建议:
• no_reply_found:收件人未回复。建议重发邮件,或通过黑板任务方式联系
• auth_failed:收件人认证失败。需检查 Agent 配置,联系姜维(jiangwei-infra)排查
• crash_limit:收件人处理时多次崩溃。系统异常,建议稍后重试
• task_timeout:处理超时。建议重发或通过其他方式联系
• 其他原因:建议联系副军师(pangtong-fujunshi)排查

——系统自动通知

实现时 {reason} 替换为实际 reason 字符串,其余排查指引固定不变。 | 其他 | "邮件「{title}」投递失败(原因:{reason}" | "建议联系副军师(pangtong-fujunshi)排查" |

2.7 防递归

系统通知邮件本身也可能失败(spawn 崩溃等),必须防止无限递归:

# 防递归:系统通知邮件失败不再发通知
meta = json.loads(original_task.must_haves) if original_task.must_haves else {}
if meta.get("system_notify"):
    return  # 系统邮件失败,不再递归通知

递归链验证:

  1. 原邮件 crash → _mark_task(failed)_is_mail_project=true → 不 @pangtong → notify_mail_failed
  2. 通知邮件(from=system)也 crash → _mark_task(failed)_is_mail_project=truenotify_mail_failed → 检查 system_notify=true → 跳过

2.8 from_agent 获取

原 Mail 的发件人存在两个等价位置:

  • task.assigned_by
  • must_haves JSON 的 "from" 字段

优先用 assigned_by(无需解析 JSON),fallback 解析 must_haves。

3. 改动清单

3.1 新增文件

文件 内容 预估行数
src/daemon/mail_notify.py notify_mail_failed + _is_mail_project ~65 行

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 拆出 _do_send_mail 内部函数供 daemon 调用,valid_agents 校验对内部调用跳过 from 检查 ~15 行

总计约 95 行,4 个文件。

3.3 不改的

项目 原因
F2 @pangtong 对 Task 的逻辑 Task failed 仍 @pangtong,只对 Mail 不适用
no_reply_found 的标 failed 逻辑 只在标 failed 之后加通知,不改判定逻辑
inform 类型邮件的完成逻辑 inform 直接标 done,不存在 no_reply_found 场景
外部 API 的 from 校验 system 不走 HTTP 路由,外部无法伪造

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 @pangtongF2 不变)
系统通知邮件本身失败 通知邮件也 crash 不再递归(system_notify=true
inform 邮件正常完成 Agent 收到 inform → 直接 done 不触发通知
inform 邮件 crash inform 的 spawn 崩溃 发件人收到 system 通知(crash 也通知)