diff --git a/docs/design/architecture-v3.0.md b/docs/design/architecture-v3.0.md index 5a5186d..b001571 100644 --- a/docs/design/architecture-v3.0.md +++ b/docs/design/architecture-v3.0.md @@ -957,6 +957,98 @@ route(task_info, action_type): **当前 Mail Tab**: 是 moziplus v2 内置的飞鸽传书功能(`_mail` 虚拟项目),是全新的实现,与 sanguo_mail 无关。两者不是同一个系统。 +### 12.3 Mail API 防御 + Prompt 约束 📋 + +> 来源:#02 Main Session + Delegation 设计过程中发现的 Mail 自环问题 + +#### 问题 + +Agent 回复邮件时把 `to` 写成自己,导致自环(mail-1780056763652 事件)。根因: +1. **API 层无防御**:`send_mail` 不校验 `to` 的合法性,不自动推断回复收件人 +2. **Prompt 层无约束**:回复模板中 `to` 字段要求 Agent 手动填写,Agent 容易写错 + +#### API 层防御(`mail_routes.py` `send_mail`) + +| # | 场景 | 校验逻辑 | 错误处理 | +|---|------|---------|----------| +| A1 | `from` 缺失/为空 | 必填校验 | 400:"`from` 必填" | +| A2 | `to` 缺失/为空(非回复) | 必填校验 | 400:"`to` 必填" | +| A3 | `from == to`(自环) | 防自环 | 400:"不能给自己发邮件" | +| A4 | `to` 对应的 Agent 不存在 | 从注册 Agent 列表校验 | 400:"`{to}` 不是有效的 Agent" | +| A5 | `in_reply_to` 指向不存在的邮件 | 原邮件存在性校验 | 400:"回复的邮件 `{id}` 不存在" | +| A6 | `in_reply_to` 存在,`to` 与原邮件 `from` 不一致 | **自动纠正** | 用原邮件 `assigned_by` 替换 `to`,响应中提示 | +| A7 | `in_reply_to` 存在,`to` 未传 | **自动填充** | 从原邮件取 `assigned_by` 作为 `to` | +| A8 | `in_reply_to` 存在,`from` 不是原邮件 `to` | 允许(第三方参与会话) | 正常处理 | + +**A4 校验方式**:从 ticker 的 `self.agents` 或 Agent 注册表获取有效 Agent ID 列表。硬编码 6 个 Agent(zhangfei-dev / guanyu-dev / zhaoyun-data / jiangwei-infra / pangtong-fujunshi / simayi-challenger)作为初始方案,后续改为从配置读取。 + +**自动纠正逻辑**:A6 + A7 合并处理——有 `in_reply_to` 时,`to` 不传或传错都自动从原邮件取 `assigned_by`(原始发件者)。 + +#### Prompt 层约束(`spawner.py` 模板) + +**当前问题**: +1. 回复模板中 curl 命令的 JSON 双花括号转义,Agent 容易写错 +2. 没有告诉 Agent "to 会自动推断",Agent 手动填写容易出错 +3. 没有给 Agent "发新邮件" 的模板,Agent 自己猜格式 +4. 没有列出有效 Agent ID,Agent 可能给不存在的 agent 发邮件 + +**改动**: + +```python +MAIL_REQUEST_TEMPLATE = """你收到一封飞鸽传书,需要你处理并回复。 + +发件者: {from_agent} +主题: {title} +内容: {text} + +### 如何回复发件者 + +curl -s -X POST http://localhost:8083/api/mail \\ + -H 'Content-Type: application/json' \\ + -d '{{"from": "{agent_id}", "in_reply_to": "{task_id}", "title": "回复: {title}", "text": "你的回复内容"}}' + +⚠️ 不需要填 "to",系统自动回复给发件者。 + +### 如何给其他人发新邮件 + +curl -s -X POST http://localhost:8083/api/mail \\ + -H 'Content-Type: application/json' \\ + -d '{{"from": "{agent_id}", "to": "对方agent-id", "title": "标题", "text": "正文", "type": "inform"}}' + +⚠️ to 必须是有效的 agent id(zhangfei-dev / guanyu-dev / zhaoyun-data / jiangwei-infra / pangtong-fujunshi / simayi-challenger) +⚠️ 纯通知用 type=inform,需要对方回复不填 type(默认 request) +⚠️ 不能给自己发邮件 +⚠️ 不要执行任何状态转换命令(标 working/done/review/failed 等),系统会自动处理。 +""" + +MAIL_INFORM_TEMPLATE = """你收到一封飞鸽传书(纯通知)。 + +发件者: {from_agent} +主题: {title} +内容: {text} + +已阅即可。如需回复,同上用 in_reply_to 回复发件者。 +⚠️ 不要执行任何状态转换命令。 +""" +``` + +**关键改动**: +1. 回复模板去掉 `to` 字段,告诉 Agent "系统自动回复给发件者" +2. 新增"给其他人发新邮件"模板,列出所有有效 Agent ID +3. 三条 ⚠️ 覆盖三种错误场景(自环 / 幻觉 agent / 状态误操作) +4. inform 模板也加上回复指引 + +#### 涉及文件 + +| 文件 | 改动 | +|------|------| +| `src/api/mail_routes.py` | `send_mail` 加 A1-A8 校验 + 自动推断 | +| `src/daemon/spawner.py` | `MAIL_REQUEST_TEMPLATE` + `MAIL_INFORM_TEMPLATE` 更新 | + +#### 状态 + +📋 设计完成,待评审 → 实施 → 验收 + --- ## §13. 经验沉淀