auto-sync: 2026-05-29 20:49:44

This commit is contained in:
cfdaily
2026-05-29 20:49:44 +08:00
parent 7efcedec42
commit a85450e3f3
+48 -22
View File
@@ -27,7 +27,7 @@
| v2.7.2 | 2026-05-26 | 防重复调用 & 防无限续杯 + counter v2.1 |
| v3.0 | 2026-05-28 | **本版**PRD 3.0 对齐 + 源码回溯 + 完整现状记录 |
| v3.0.1 | 2026-05-28 | 补充6个讨论话题(§6.5/§10.4~10.6+ 新增§21 T阶段规划(T1~T6 |
| v3.0.2 | 2026-05-29 | §12.3 Mail API 防御 + Prompt 约束设计;§6.5 补充 spawn 改造方向 |
| v3.0.2 | 2026-05-29 | §12.3 Mail API 防御 + Prompt 约束设计(纳入司马懿评审意见);§6.5 补充 spawn 改造方向 |
### 1.2 v3.0 定位
@@ -969,6 +969,7 @@ route(task_info, action_type):
### 12.3 Mail API 防御 + Prompt 约束 📋
> 来源:#02 Main Session + Delegation 设计过程中发现的 Mail 自环问题
> 司马懿评审:mail-1780058519676 回复(M1/M2/S1-S3/A9),已纳入
#### 问题
@@ -976,22 +977,46 @@ Agent 回复邮件时把 `to` 写成自己,导致自环(mail-1780056763652
1. **API 层无防御**`send_mail` 不校验 `to` 的合法性,不自动推断回复收件人
2. **Prompt 层无约束**:回复模板中 `to` 字段要求 Agent 手动填写,Agent 容易写错
#### 设计约束
1. **Mail 是 Agent 间 1 对 1 通信**`from``to` 都必须是有效 Agent`from="user"` 不允许(用户收不到邮件)
2. **严格 1 对 1**。禁止第三方插入会话。多 Agent 协作走 Task(广播/mention
3. **有效 Agent 列表**统一维护一份,API 校验和 Prompt 模板共用同一数据源
#### 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` | 允许(第三方参与会话) | 正常处理 |
**校验执行顺序**(按此顺序逐条校验,任一失败立即返回 400):
**A4 校验方式**:从 ticker 的 `self.agents` 或 Agent 注册表获取有效 Agent ID 列表。硬编码 6 个 Agentzhangfei-dev / guanyu-dev / zhaoyun-data / jiangwei-infra / pangtong-fujunshi / simayi-challenger)作为初始方案,后续改为从配置读取。
| 顺序 | # | 场景 | 校验逻辑 | 错误处理 |
|------|---|------|---------|----------|
| 1 | A1 | `from` 缺失/为空 | 必填校验 | 400"`from` 必填" |
| 2 | A9 | `from` 不是有效 Agent | 从注册 Agent 列表校验 | 400"`from` 不是有效的 Agent" |
| 3 | A5 | `in_reply_to` 指向不存在的邮件 | 原邮件存在性校验 | 400:"回复的邮件不存在" |
| 4 | A6/A7 | `in_reply_to` 存在时自动纠正 `to` | 自动从原邮件取 `assigned_by`(原发件者)作为 `to` | 静默纠正,响应中加 `auto_corrected` 提示 |
| 5 | A2 | `to` 缺失/为空(非回复) | 必填校验 | 400:"`to` 必填" |
| 6 | A3 | `from == to`(自环) | 防自环 | 400:"不能给自己发邮件" |
| 7 | A4 | `to` 不是有效 Agent | 从注册 Agent 列表校验 | 400"`to` 不是有效的 Agent" |
| 8 | A8 | `in_reply_to` 存在,`from` 不是原邮件的 `assignee``assigned_by` | 严格 1 对 1:只有原邮件的双方能回复 | 400:"只有邮件的发送者或接收者可以回复" |
| 9 | A10 | `text``description` 都为空 | 正文非空校验 | 400:"邮件正文不能为空" |
**自动纠正逻辑**A6 + A7 合并处理——有 `in_reply_to` 时,`to` 不传或传错都自动从原邮件取 `assigned_by`(原始发件者)。
**关键设计说明**
- **A6/A7 自动纠正**:有 `in_reply_to` 时,无论 Agent 传什么 `to`,都自动从原邮件取 `assigned_by`(黑板字段,即原发件者)作为回复的 `to`。回复方向固定为 reply → original sender,不支持自定义收件人
- **A8 严格 1 对 1**:只有原邮件的 `assigned_by`(发件者)和 `assignee`(收件者)可以回复。其他人想参与需开新邮件,不能用 `in_reply_to` 插入别人的对话
- **A9 `from` 校验**`from` 必须是有效 Agent。现有代码 `body.get("from", "user")` 的默认值 `"user"` 需移除,改为必填
- **A4/A9 有效 Agent 列表**:同一数据源(从 ticker `self.agents` 或配置读取)。Prompt 模板也用此列表的 `{valid_agents}` 变量替换
**A6 自动纠正响应格式**
```json
{
"ok": true,
"mail_id": "mail-xxx",
"auto_corrected": {"field": "to", "original": "simayi-challenger", "corrected": "pangtong-fujunshi"}
}
```
让调用者知道 `to` 被纠正了,Agent 下次可以避免同样的错误。
#### Prompt 层约束(`spawner.py` 模板)
@@ -999,7 +1024,7 @@ Agent 回复邮件时把 `to` 写成自己,导致自环(mail-1780056763652
1. 回复模板中 curl 命令的 JSON 双花括号转义,Agent 容易写错
2. 没有告诉 Agent "to 会自动推断"Agent 手动填写容易出错
3. 没有给 Agent "发新邮件" 的模板,Agent 自己猜格式
4. 没有列出有效 Agent IDAgent 可能给不存在的 agent 发邮件
4. Agent ID 列表硬编码在模板字符串里,加 Agent 要改代码
**改动**
@@ -1024,7 +1049,7 @@ 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 idzhangfei-dev / guanyu-dev / zhaoyun-data / jiangwei-infra / pangtong-fujunshi / simayi-challenger
⚠️ to 必须是有效的 agent id: {valid_agents}
⚠️ 纯通知用 type=inform,需要对方回复不填 type(默认 request
⚠️ 不能给自己发邮件
⚠️ 不要执行任何状态转换命令(标 working/done/review/failed 等),系统会自动处理。
@@ -1036,27 +1061,28 @@ MAIL_INFORM_TEMPLATE = """你收到一封飞鸽传书(纯通知)。
主题: {title}
内容: {text}
已阅即可。如需回复,同上用 in_reply_to 回复发件者。
已阅即可。如需回复,用 in_reply_to 回复发件者(不需要填 to
⚠️ 不要执行任何状态转换命令。
"""
```
**关键改动**
1. 回复模板去掉 `to` 字段,告诉 Agent "系统自动回复给发件者"
2. 新增"给其他人发新邮件"模板,列出所有有效 Agent ID
3. 三条 ⚠️ 覆盖三种错误场景(自环 / 幻觉 agent / 状态误操作)
4. inform 模板也加上回复指引
2. 新增"给其他人发新邮件"模板
3. Agent ID 列表用 `{valid_agents}` 运行时替换(和 A4/A9 同一数据源),不硬编码
4. 三条 ⚠️ 覆盖三种错误场景(自环 / 幻觉 agent / 状态误操作)
5. inform 模板也加上回复指引
#### 涉及文件
| 文件 | 改动 |
|------|------|
| `src/api/mail_routes.py` | `send_mail` 加 A1-A8 校验 + 自动推断 |
| `src/daemon/spawner.py` | `MAIL_REQUEST_TEMPLATE` + `MAIL_INFORM_TEMPLATE` 更新 |
| `src/api/mail_routes.py` | `send_mail` 加 A1-A10 校验 + 自动推断 + 有效 Agent 列表 |
| `src/daemon/spawner.py` | 模板更新 + `{valid_agents}` 运行时替换 |
#### 状态
📋 设计完成,待评审 → 实施 → 验收
📋 设计已纳入司马懿评审意见,待确认 → 实施 → 验收
---