# Agent 设计基础

> 通用理论，与具体业务无关。出现的业务例子只为帮助理解，不构成实现约定。
> 目的：在动手设计任何一个 agent 之前，先有一套判断手段的共同标准。

| | |
|---|---|
| **状态** | 理论基础（Foundation） |
| **适用** | 任何以 LLM 为决策核心、通过工具与系统交互的 agent |
| **不覆盖** | 具体业务流程、模型选型、成本核算、UI 形态 |
| **用法** | 后续业务设计文档可直接引用本文编号（如「依 P4」「见 §6」） |

---

## 〇、总纲

> **模型负责决策，环境负责事实。**

让模型自主决定「下一步做什么」——这是 agent 的全部价值，不要用流程编排把它替换掉。
但它报出的每个事实，都必须**指得到来源**：来自某次工具返回，而不是来自它自己。

这两句不矛盾：给模型一把计算器、让它自己决定何时使用，比替它规定调用顺序**更自主**，也更可靠。

> ⚠️ 这条的判据是「**有没有锚**」，不是「能不能算」。
> 模型的算力在快速变强，但「引用了没有来源的数」「跨轮沿用旧条件下的数」跟算力无关 —— 那是上下文机制的性质，不会随模型变强而消失。

---

## 一、Agent 是什么

**定义：一个在受限上下文中、通过动作改变环境、依靠反馈收敛的循环。**

```
        ┌──────────────────────────────────────────────┐
        │                   上下文                      │ ← 控制面 I
        │   系统提示词 · 对话历史 · 工具结果 · 任务状态    │
        └───────────────────────┬──────────────────────┘
                                ↓
                            [  模型  ]
                                ↓
                     文本 ──────────→ 给人看
                     动作 ──┐
                            ↓
        ┌──────────────────────────────────────────────┐
        │                动作空间 / 工具                 │ ← 控制面 II
        └───────────────────────┬──────────────────────┘
                                ↓
        ┌──────────────────────────────────────────────┐
        │           环境（DB · 文件 · 外部系统）          │
        └───────────────────────┬──────────────────────┘
                                │
                    反馈 ───────┘                        ← 控制面 III
                     │
                     └────→ 回到上下文，进入下一轮

    控制面 IV（权限 · 人工闸门 · 回滚 · trace）横跨全程
```

**三个必须认清的事实：**

1. **「要不要调工具」和「下一个字写什么」是同一个动作。** 没有独立的决策层，工具调用只是一段结构化输出。因此**没有任何机制保证模型该调用时一定会调用**。
2. **没有反馈的工具调用不是 agent**，只是带副作用的补全。反馈回路是 agent 区别于一次性生成的本质。
3. **上下文每轮完整重建，且单向污染。** 错误信息进去后不会自行消失。

---

## 二、四个控制面

设计 agent，能动的只有四样东西。所有流行概念都是其中某一面的实现。

| | 控制面 | 决定了 | 典型投入不足程度 |
|---|---|---|---|
| **I** | **上下文** — 模型唯一的输入 | 它知道什么 | 中 |
| **II** | **动作空间** — 模型唯一改变世界的通道 | 它能做什么 | 低（大家都在堆工具） |
| **III** | **反馈回路** — 环境给它的真话 | 它能否自我纠正 | **高（最常被忽略）** |
| **IV** | **控制与恢复** — 谁能停、谁能撤 | 出错的代价 | 中 |
| **贯穿** | **评测** — 是否在变好 | 能否长期维护 | **高** |

> **四面不是互斥分区，是四个提问角度。** 同一段代码可以同时出现在两面甚至三面（例：工具的返回值同属 II 与 III；trace 同属 III 与 IV）。
> 分面的价值在于**检查时不漏项**，不在于分类学的纯洁。删掉任何一面，都会整类地漏掉该问的问题。

### 控制面 I · 上下文

**性质**：稀缺、每轮重建、单向污染。窗口变大**不解决**污染，反而更糟——没有任何力量逼你清理。

**手段**
- **摘要 + 指针**：工具返回 `{id, 摘要}`，明细留在库里，需要时再取（详见下）
- **状态外置**：长任务的进度与中间产物放持久层，上下文只是本轮工作台
- **渐进披露**：知识分层，用到才加载
- **只读子 agent**：需要翻大量材料时派出去翻，只带结论回主上下文（**这是多 agent 唯一无争议的用途**）
- **上下文卫生**：失败的工具调用只留错误摘要，丢弃原始垃圾输出（**应在源头做，见 §7⑤**）

**判据：按量级决定，不要按类别一刀切。**

| 量级 | 策略 |
|---|---|
| 几十条 / 几千 token 以内 | **给原文** |
| 几十到数百 | 结构化摘要 + 指针 |
| 数百以上 | 只给数量 + 指针，明细走界面 |
| **量级未知** | **先取数量，再决定取不取** |

> ⚠️ 别默认「摘要总是更好」。摘要是**有损**的，而且损失的往往正是后面要追问的那部分；
> 原文还有两个隐性好处：追问时不必再取一次，以及**可以被重新解读**（同一份材料，下一个问题看的是完全不同的角度）。
> 「先数量后明细」这条应当落进工具设计：**每个列表类工具都要有「只回数量」的轻量对应物。**

**明细留在库里的理由，按重要性**：① 上下文只进不出，放进去的东西之后每一轮都在场、都在争注意力——指针不是为了省地方，是**让数据有「离开」的机会**；② 在上下文里模型就会去引用它、基于它计算，而这恰是它不可靠之处；③ 上下文里是快照，指针取回的是当前值；④ 成本与延迟；⑤ 跨会话与多消费者（UI 不读上下文）。

#### 摘要 + 指针必须成对

摘要是**索引**，不是原文的替代品。类比 `ls` 与 `cat`——不会把整块磁盘读进内存再决定看哪个文件。

摘要要可用，必须携带**可展开信号**，且它分布在两个位置：

| 信号 | 所在位置 | 例 |
|---|---|---|
| **id** | 上一个工具的**返回值** | `{cohortId: "c_123"}` |
| **数量** | 上一个工具的**返回值** | `{total: 420}` |
| **可取维度** | 下一个工具的 **schema** | `取明细(cohortId, filter?, page?)` |
| **数据决定的维度** | 返回值（schema 写不死） | `{doctors: ["张","李","王"]}` |

缺任一项即退化：有 id 无取用工具 → 拿不到；有工具无 id → 不知取哪批；无数量 → 无法判断该不该展开。

**「追问都有谁」不是这个模式的破绽，而是它的正常路径**，且通常只需取一部分（「李医生那几个」→ 带筛选取 5 条，而非预先塞入 400 条）。
若答案是要给人看的名单，最优路径根本不经过模型：工具返回 `forUI`，前端渲染，模型只回一句「已列出」。

### 控制面 II · 动作空间

**手段（按杠杆从高到低）**

| 手段 | 说明 |
|---|---|
| **工具描述即提示词** | 同一条规则写在工具描述里，遵守率高于写在系统提示词里——它出现在决策点上。**能下沉到 schema 的规则就别留在 prompt 里** |
| **错误消息即提示词** | 报错要写成模型能据此纠正的形态。`参数无效，可用值 [...]，不确定请传 UNSPECIFIED` ≫ `400 Bad Request` |
| **枚举穷尽 + 兜底格** | 缺一个合法取值，模型必然猜、且静默。永远保留 `UNSPECIFIED`，服务端对它的处理是**反问**而非报错 |
| **参数即攻击面** | 模型不传就不可能出错的参数，不该是参数（见 §6） |
| **幂等 + 事务边界** | 模型会重复调用（重试、误判失败），写工具必须带幂等键 |
| **粒度 = 用户会说的一句话** | 太细则模型要自己编排，太粗则失去灵活性 |
| **三路返回值** | `forModel`（极简）/ `forUI`（完整结构化）/ `trace`（审计） |
| **高频专用 + 长尾兜底** | 专用工具的名字就是意图，选错空间小；但没有兜底，未预见的需求只能回「做不到」。兜底工具的参数必须是**枚举化的维度**，⛔ 不能是自由查询语句 |
| **`_guide` 随返回值下发** | 只在特定场景才需要的解读规范，挂在该工具的返回值上，⛔ 别常驻在工具说明或提示词里。这是按需加载最便宜的形态：零往返、必然到达、用不到时零成本 |

#### 附：怎么保证「该调的时候真的调」

§1 已述——没有任何机制天然保证模型该调用时会调用。按强度排：

| | 手段 | 强度 |
|---|---|---|
| ① | **数据不在上下文里** | **最强（P4 ①级）**。它想不调都编不出来，因为凑答案的材料不存在 |
| ② | **`tool_choice` 强制本轮必须调（或指定调哪个）** | 强。硬约束，不依赖自觉 |
| ③ | **结构化输出强制 schema** | 强。本质同 ② |
| ④ | **出口校验 + 驳回重试** | 中。检测到「期望工具调用却只有文本」则重来；重试是追加，不破坏缓存 |
| ⑤ | **提示词写「必须先查再答」** | 最弱 |

②③ 的前提是**你事先知道这一轮必须调工具**，而 agent 的价值恰在于它自己判断——所以只适合明确节点（如「用户已点确认，本轮必须提交」），不能一刀切。

**并且：模型不调工具经常是对的**（闲聊、追问已有信息、该停下来问人）。
所以目标不是「保证每轮都调」，而是**让它不调时无法产出看似正确的答案**——即 ①。

**怎么发现「该调没调」**：离线靠 golden set 断言工具序列（P8）；在线可埋一个粗检测——本轮输出含数字或名单，却整轮未调用任何数据类工具 → 告警。

### 控制面 III · 反馈回路

**核心命题：给模型验证手段，比给它规则有效得多。** 而且这是**给能力**，不削弱自主性。

> **III 在实现上大部分寄生于 II** —— 下列工具都住在动作空间里，走同一条 tool-call 通道。
> 单列一面，是因为它问的是不同的问题：**II 问「它能做什么」，III 问「它做完之后知不知道结果」。**
> 实际价值：列工具清单时人人都会想到「圈定、提案、提交」，**没人会自动想到「提交完怎么验证」**。

**并非所有反馈都是新工具**——很大一部分是既有工具的返回值设计：

| 反馈来源 | 是新工具吗 |
|---|---|
| 写工具自身的返回值（成功/失败/实际影响了什么/冲突在哪） | ❌ 是**返回值设计** |
| 错误消息 | ❌ 同上，且是最高频的反馈 |
| 被动信号（超时、权限拒绝、schema 校验失败） | ❌ 环境推送，模型未主动调 |
| 人的动作（在确认单上删掉了 3 个人） | ❌ 极强的信号，但不是工具 |
| dry_run / explain / validate / read-back | ✅ 是 |

所以「加强反馈」的第一步通常不是加工具，而是**把现有工具的返回值与错误消息写好**。

**标准四件套**

| 工具 | 作用 |
|---|---|
| `dry_run` | 看将要发生什么，不落库 |
| `explain(实体)` | 确定性引擎给出理由，模型只能查、不能编 |
| `validate(方案)` | 冲突检测，返回可读的纠正信息 |
| **read-back** | 提交后能查到真实结果，而不是假设成功 |

**补充手段**
- 自洽采样：关键决策跑 N 次比对，不一致则交人。只用在关键点，成本可控
- 对抗校验：第二次调用专门去**反驳**第一次的结论。对解释类输出有效，数值类应当用程序校验

**判据**：每加一个写工具，问一句「模型怎么知道它做对了」。答不上来就先别加。

#### III 与 IV 的分界

> **如果模型完全不配合——不读反馈、乱调工具、张口就编——哪一面还有效？**

| | 服务对象 | 模型不配合时 |
|---|---|---|
| **III 反馈** | **模型**（知道结果 → 自纠） | **全部失效**。前提是模型会读、会改 |
| **IV 控制与恢复** | **人 / 系统**（不发生、被看见、能撤销） | **完全有效**。权限拒绝、人工闸门、回滚都不征求模型意见 |

**III 是协作式的，IV 是强制式的。** 对到 P4：III 大体停在 ③ 级，IV 才是 ①② 级。

反馈还有一路的接收者根本不是模型：

| 路径 | 接收者 | 闭环在哪 |
|---|---|---|
| 返回值 / 错误 → 上下文 | 模型 | 会话内自纠 |
| trace / 评测 / 离线分析 | **开发者** | 版本之间改设计 |

### 控制面 IV · 控制与恢复

**目标不是不出错，是让出错变便宜。**

- **写屏障**：不可逆动作必须经人确认
- **回滚**：批量动作可整体撤销
- **trace + 确定性重放**：存下每次运行的工具序列并能重放——这是调试与评测的**前置基础设施**，先有它才有后面一切
- **权限隔离**：只读与可写工具分离；身份相关参数由会话解析
- **灰度**：新提示词 / 新 schema 先在小比例流量上跑
- **提示词与工具 schema 同版本**：schema 变更必须带着 prompt 一起走，否则漂移是必然

---

## 三、原则

| 编号 | 原则 | 要点 |
|---|---|---|
| **P1** | **先分控制面，再选手段** | 概念先归位，再问「我这一面的问题有多严重、有数据吗」 |
| **P2** | **模型决策，环境供事实** | 凡是能被计算或查询的，不经模型产出 |
| **P3** | **能力靠工具与反馈，可靠靠结构** | 提示词是最弱的杠杆，永远最后用 |
| **P4** | **保证强度分三级** | ① 让错误不可能 ≫ ② 让错误可见可撤 ≫ ③ 校验规则（见 §4） |
| **P5** | **区分「弥补模型不足」与「业务本身要求」** | 前者随模型变强应当拆除，后者永远保留。**每条措施都要标明属于哪类**，否则三年后没人敢删任何东西 |
| **P6** | **防御有成本，少而狠** | 规则越多，单条遵守率越低。堆防御会直接削弱你想发挥的能力 |
| **P7** | **安全边界不能靠提示词** | 身份是会话的属性，由登录态决定，不由模型或提示词决定（见 §6） |
| **P8** | **没有评测，质量是随机游走** | 判定标准是**工具调用序列与参数**，不是文字 |

### P6 的具体代价

| 防御手段 | 副作用 |
|---|---|
| 提示词堆到几十条规则 | 遵守的条数反而下降，新规则挤掉旧规则 |
| 硬性输出模板 | 模型转向「填格子」，不再思考内容 |
| 按阶段限制可用工具 | 走错一步后**无法自纠**，因为纠错工具被藏起来了 |
| 输出不合格就自动重试 | 掩盖问题，让你看不到真实失败率 |

---

## 四、保证的三个等级

```
     ▲  强
     │   ①  结构上不可能        改的是结构，不依赖模型配合
     │                          例：数字不进上下文 → 不可能复述错
     │                              身份不做参数   → 不可能越权
     │
     │   ②  可见 · 可撤          不阻止错误，把事故降级为噪音
     │                          例：回滚、diff 确认、trace
     │
     │   ③  校验 · 规则          只拦得住你**预想到**的错误
     ▼  弱                      而漂移与幻觉的定义就是「以你没想到的方式出错」
```

**推论**：一个措施如果只能靠 ③ 实现，通常说明上游的工具设计有问题。

---

## 五、规则放哪：五个容器

同一条「模型该怎么做」的知识，有四个可能的落点。**优先级从高到低：**

| 优先级 | 容器 | 何时用 | 成本 |
|---|---|---|---|
| 1 | **引擎代码** | 规则是确定性的 | 零上下文成本，模型无需知道 |
| 2 | **工具返回值** | 引导「下一步」、动态提示 | 用完即弃，出现在决策点上，改动最便宜 |
| 3 | **工具描述** | 该工具专属的约束 | 只在工具清单里付一次费 |
| 4 | **按需加载的知识包** | 大量、低频、分支性知识 | 不加载时为零 |
| 5 | **系统提示词** | 每一轮都必须为真的东西 | **最贵——所有会话所有轮次都付费** |

> 注意：KV 缓存能抵掉系统提示词的**算力**成本，抵不掉它的**注意力**成本。
> 「反正命中缓存了，多写几条无所谓」是错的——P6 的代价与缓存无关。

### 判断顺序

**第一刀：这是规则还是判断？**

> 把这段话删掉，模型行为变得**不可预测**（而非仅仅变差）→ 它是规则，进代码。
> 删掉后模型仍能工作、只是变糙 → 它是提示词。

例：「A 类优先分给甲」删掉后结果不确定 → 规则，进引擎。
　　「拿不准就问，不要猜」删掉后只是变糙 → 提示词。

**第二刀：常驻还是按需？**

> **渐进披露的收益 = 不加载的概率 × 体积。**
> 概率接近 0 时收益为负——白搭一次往返、多一个失败点。
> **主线任务常驻，分支知识按需。**

**第三刀：加载方式与管理方式可以拆开。**

「独立文件、可 review、可版本化、可灰度」和「按需加载」是两个独立属性。
业务规则变更快，**可以只要前者**：管理上独立成文件，加载上仍然常驻。

---

## 六、身份、角色与「多 agent」

### 判据

```
                    两个角色之间有安全边界吗？
                （即：能看到的数据 / 能做的动作不同）
                              │
              ┌───────────────┴───────────────┐
             是                                否
              │                                 │
      ┌───────┴────────┐              这只是话题差异，同一会话即可
      │  独立会话       │                        │
      │  独立工具清单   │                 量大且低频？
      │  独立数据范围   │              ┌─────────┴─────────┐
      │  （登录态决定） │             是                    否
      └────────────────┘              │                     │
                                  按需加载              常驻提示词
```

### 为什么同一会话不能切换身份

**上下文是单向的。** 高权限会话中已载入的数据，不会因为一句「你现在是低权限角色」而消失。**提示词不是删除操作，也从来不是安全边界（P7）。**

### 「两个 agent」这个说法要拆开

| 维度 | 是否需要分 |
|---|---|
| 会话 | **必须分** |
| 工具清单 | **必须分**（由登录态下发，看不见比看见被拒更安全） |
| 数据范围 | **必须分**（下推到查询条件，而非查全量再过滤） |
| 系统提示词 | 分（但这是其中最不重要的一条） |
| 模型 | 不用分 |
| 代码实现 | 不用分 |

**结论：同一套 agent 实现，按身份参数化。** 这不是「多 agent 编排」——编排指 agent 之间互相调用；不同身份之间**不需要通信**，需要协作时走业务对象，不走 agent 消息。

### 身份与权限的四条硬规则

1. **身份随每次调用传递，不随连接建立。** 一个连接可能服务多个用户。
2. **授权在工具内部执行，不在 agent 层。**
3. **数据范围下推到查询语句。** 「查全量再过滤」漏一处就泄露；「条件里就带范围」漏一处只是查不到——**失败方向不同**。
4. **工具清单本身按身份下发。**

> 判据：**如果模型不传某个参数，越权就不可能发生——那这个参数就不该是参数。**
> 顺带的好处：模型不用猜，少一个必错的空格。

---

## 七、上下文工程的五个维度

前四维描述**单轮里放了什么**，第五维描述**跨轮之间怎么变**。

| | 维度 | 手段 |
|---|---|---|
| ① | **放什么**（选择） | 渐进披露、摘要 + 指针、按需取 |
| ② | **不放什么**（排除） | 明细不入、失败结果只留摘要、状态外置 |
| ③ | **放在哪**（位置） | 注意力不均匀，长上下文中段利用最差；当前任务状态应靠近末尾 |
| ④ | **以什么形式放**（表示） | 同一份数据用 JSON / 表格 / 散文，模型的理解与引用准确度不同。**结构化数据用紧凑结构，判断依据用自然语言，不要混** |
| ⑤ | **什么时候变**（稳定性） | KV 缓存友好性：**只追加、不修改**，动态内容后置 |

多数团队只做了 ① 和 ②。**④ 几乎零成本；⑤ 直接决定成本与延迟的数量级。**

### ③ 的边界：能控制的位置有限

一次请求由三块构成，落点由服务端排定：`system` → `tools` → `messages`。
**你能调整顺序的只有 `system` 内部与 `messages` 内部**，`tools` 的位置不归你管。

推论：**工具定义天然占据靠前且固定的位置**——这是「规则下沉到工具描述比留在系统提示词里有效」的机制之一（另见 §5）。

### ⑤ KV 缓存：最常被忽略的一维

**机制**：缓存按**前缀**匹配。上下文从头到某处完全一致，这一段的计算即可复用；**任何一个 token 变化，从该处往后全部失效**。

Agent 的特征恰恰是「同一个长前缀被反复发送几十轮」，所以命中与否是**数量级**的差距，延迟与成本同时受影响。

**由此推出的硬约束：按稳定性排序，越稳定越靠前。**

```
        ┌────────────────────┐
   稳定 │ 系统提示词           │  几乎不变
        │ 工具定义             │  按身份固定
        │ 长期知识 / 技能包     │  半稳定
        │ 对话历史             │  只追加
   易变 │ 当轮输入             │  每次变
        └────────────────────┘
             ↑ 命中止于第一个变化点，其后全部重算
```

**四条实操规则**

1. **系统提示词里不要放动态内容。** 一个当前时间戳、一个随机 id，就足以让整个前缀每轮失效——最常见也最昂贵的坑。动态内容一律后置到 `messages` 末尾。
2. **工具清单要稳定。** 按身份下发是对的（同一身份内固定），但**不要每轮动态增删工具**。这也是不推荐「按阶段限制可用工具」的第二个理由（第一个是堵死自纠，见 P6）。
3. **只追加，不修改历史。**
4. **不要重排历史。**

### 一个真实张力：上下文卫生 ⇄ 缓存友好

| | 目标 | 手段 |
|---|---|---|
| 上下文卫生 | 别放脏东西 | 清理历史中的垃圾 |
| 缓存友好 | 别改已放的东西 | 历史只追加 |

**二者在「去噪」这件事上直接冲突**：在第 k 条消息上动手术，第 k 条之后的缓存全部作废。

**解法：在源头治理，不在历史上治理。** 工具返回时就只给摘要形态（错误只回错误码与原因，不回原始堆栈），历史从一开始就是干净的，此后无需再改。

> 这条同时解释了为什么「摘要 + 指针」优于「先全量塞入、事后清理」：
> 前者天然满足只追加，后者必然要修改历史。

### 多轮：必须带全脉络，但要分层带

多轮对话丢掉工具调用历史，模型下一轮就不知道自己查过什么、拿到过什么——它会重复调用，也会拿上一轮**自己说过的话**当唯一依据（那正是「背出成品句子」的温床）。

所以「带全」和「别污染」不是二选一，是分层：

| 内容 | 回传什么 |
|---|---|
| 工具调用 | 工具名 + 关键参数 + **结果摘要** |
| 大载荷（名单、明细） | id + 一句话 |
| 失败 | 错因一句，⛔ 不带原始堆栈 |

这就是「摘要 + 指针」用在历史上。

### 去噪 ≠ 压缩

| | 靠什么 | 成本 | 何时做 |
|---|---|---|---|
| **去噪** | 规则 / 纯代码 | 极低、无损 | 现在，且应在源头 |
| **压缩** | 模型总结 | 高、有损、破坏缓存 | 长任务出现后再说 |

**组装上下文的是代码，不是模型。** 每轮 `messages` 里放什么完全由代码决定，去噪不需要任何模型参与（P2 的一个实例）；只有「压缩 / 总结」才必须动用模型，因为它需要理解语义。

长任务的正解也不是压缩历史，而是**状态外置**——进度写入持久层，历史丢弃亦可继续。

### 与提示词工程 / harness 的关系

- **提示词工程 ⊂ 上下文工程。** 系统提示词就是上下文里最固定的一块。这解释了为什么规则会互相挤——它们在争同一个资源。
- **harness ⊄ 上下文工程。** harness 在上下文之外，作用于动作与后果（控制面 II / III / IV 中「弥补模型不足」的部分）。

### 关于窗口大小

大窗口消除的是**容量**问题，不消除：

- **污染**——错误信息不会自行离开
- **注意力衰减**——放得下 ≠ 用得好
- **成本与延迟**——每轮重发全部上下文，多轮是二次增长
- **缓存失效的代价反而更大**——前缀越长，一次失效要重算的越多。窗口越大，⑤ 越重要

---

## 八、外部记忆

**它解决的唯一问题：没有 schema 的事实。**

| 事实类型 | 存放 | 谁来判定写入 |
|---|---|---|
| 有 schema 的（偏好、配置、规则） | **持久层 + 工具读写** | 明确的业务规则 |
| 无 schema 的（任意形状的经验） | 文件式记忆 | 模型自己——**因此难以信任** |

**判据**：当你发现有东西「想记但没地方存」，那才是外部记忆的信号。而这通常说明你缺一张表——**先考虑建表**。

如果系统面对的是一个 schema 已知的世界，「记忆」这件事已被持久层完整吸收，不需要额外机制。

**无论存在哪里，三件事缺一不可：**

1. **写入门槛** —— 模型只能提议，落库由规则或人确认。让模型自由写入「我学到了 X」，会把一次偶然固化成规则，且**没有 diff 可看**
2. **可见可删** —— 看不见的个性化是最难排查的 bug 来源
3. **失效策略** —— 带时间戳与来源，读出时告知「这是何时记录的」

**反模式：经验型记忆。** 「上次这样做效果好，以后都这样」——不可调试，出问题时无法定位是哪条记忆学坏的。正确做法是把它变成**有 schema 的规则记录**。

---

## 九、流行术语甄别

| 术语 | 归属 | 判定 | 说明 |
|---|---|---|---|
| **工具 / function calling** | II | **地基** | 不是可选项。一切能力最终归结为工具调用 |
| **MCP** | II 的**封装形式** | **看入口规划** | 对模型而言 MCP 工具与原生工具**完全无法区分**，所有工具设计原则原样适用。它解决的是「工具跨进程、跨 host 复用」。**单一入口时是净开销；计划开放能力或接入外部生态时应提前统一**——这是接口决策，改期成本随时间指数上升。**真正的代价在鉴权（见 §6）**，而非多一层 |
| **Skills / 技能包** | I | **拆开用** | 捆了两件独立的事：独立文件管理、按需加载。**可以只要前者**（§5 第三刀）。核心思想是渐进披露，不必拘泥形式 |
| **记忆** | I | **必须拆三类** | 会话内状态 / 用户偏好 / 学到的经验。第三类现阶段不建议做（§8） |
| **多 agent 编排** | I + 并行 | **限用** | 它解决的是**上下文隔离与并行**，不是「智能不够」。唯一无争议的用法是只读子 agent。按角色分不属于编排（§6） |
| **RAG / 向量检索** | I | **看数据形态** | 解决「海量**非结构化**文本中找相关片段」。对**结构化数据**使用向量检索是退化：更慢、更不准、无法解释、无法精确过滤——查询语言本身就是检索手段。即便面对自由文本，也应先试全文检索，向量是最后手段 |
| **反思 / 自我批评循环** | III | **需外部反馈才有效** | 没有环境真话的自我批评收益很小，容易自我确认 |
| **思维链 / 推理** | — | **有效但未经验证** | 提高正确率，但它仍然只是 token，没有被任何东西检验 |

**统一筛子——任何手段进门前三问：**

1. 它解决的是哪个控制面的问题？
2. 我这个问题现在有多严重？**有数据吗？**
3. 有没有更便宜、更结构性的解法？

第 2 问答不上来，就是在赶时髦。

---

## 十、设计检查清单

> 这是**过一遍**用的，不是**全打勾**用的。每项的必要性与时机见 §11。

### 动作空间
- [ ] 每个工具的粒度约等于用户会说的一句话
- [ ] 所有枚举穷尽合法值，且含 `UNSPECIFIED` 兜底
- [ ] 身份 / 范围类参数由会话解析，**不出现在 schema 中**
- [ ] 写工具幂等，带幂等键
- [ ] 错误消息写成「模型可据此纠正」的形态
- [ ] 返回值分离 forModel / forUI / trace
- [ ] 能下沉到工具描述的规则，已从系统提示词移出；只在特定场景需要的解读规范下沉到 `_guide`
- [ ] 高频场景有专用工具，长尾有一个**枚举化参数**的兜底工具
- [ ] 每个列表类工具都有「只回数量」的轻量对应物
- [ ] 给模型的是**结构化事实**，不是成品句子

### 反馈
- [ ] 每个写工具都有对应的验证手段（dry_run / validate / read-back）
- [ ] 解释类问题由确定性来源回答，模型不得自行编写理由

### 上下文
- [ ] 明细**按量级**决定给原文还是给 id + 摘要；量级未知时先取数量
- [ ] 摘要携带完整的**可展开信号**（id + 数量 + 可取维度）
- [ ] 多轮会话带全脉络，且工具历史是**分层**回传（名字 + 参数 + 结果摘要）
- [ ] 任务状态存在于上下文之外
- [ ] 失败的工具结果**在源头**就只返回错误摘要（而非事后清理历史）
- [ ] 工具返回值的**表示形式**经过设计（结构化 vs 自然语言）

### KV 缓存
- [ ] 系统提示词内**无动态内容**（时间戳、随机 id、每轮变化的状态）
- [ ] 同一身份的工具清单在会话内固定，不逐轮增删
- [ ] 历史只追加，不修改、不重排
- [ ] 上下文按稳定性排序（稳定在前，易变在后）

### 控制与恢复
- [ ] 不可逆动作有人工闸门
- [ ] 批量动作可回滚
- [ ] 全链路 trace 可重放
- [ ] 提示词与工具 schema 同版本发布
- [ ] **每条 harness 措施标注了属于「弥补模型」还是「业务要求」（P5）**

### 评测
- [ ] 存在固定的回归集，断言**工具调用序列与参数**
- [ ] 改提示词 / 改 schema / 换模型前后都会跑

---

## 十一、优先级与裁剪

> **本文是检查清单，不是施工图。** 每一条的正确用法是「问一遍」，不是「都做」。
> 不做某一条完全可以——**但要知道自己没做，以及缺口在哪**。
> 全套照做就是过度设计，代价不只是工时：**过早的抽象会锁死你还没理解的问题。**

### 分档依据：后补代价

| 档 | 后补代价 | 何时做 |
|---|---|---|
| **一** | 重构（要动所有工具 / 是安全边界 / 不做就是盲的） | **开工前必须定** |
| **二** | 补一段代码 | 随功能增量做 |
| **三** | 无（不做也没缺口） | **等信号再做**，提前做即过度设计 |

### 第一档 · 地基

| 项 | 后补为什么疼 |
|---|---|
| **P2 模型不产出可计算的事实** | 要改所有工具的返回值形态 |
| **身份 / 范围由会话解析，不做工具参数** | 安全边界，后改是重构 |
| **摘要 + 指针**（返回值形态） | 同上，改的是所有返回值 |
| **写屏障**（不可逆动作过人） | 业务要求，不是防御 |
| **trace + 可重放** | 没有它，后面所有调试与评测都是盲的 |
| **系统提示词内无动态内容** | 一行代码的事，不做则每轮白烧一次全量计算 |

这一档成本都不高，但**都是接口决策**。

### 第二档 · 随功能增量做

工具描述与错误消息打磨 · 枚举兜底格 · 写工具配验证手段（按需，不必四件套齐全）· 幂等键 · 回滚 · 评测集（可从 5 条起步）· 上下文按稳定性排序 · 返回值表示形式

**原则：每加一个写工具，同时回答「模型怎么知道它做对了」——但答案可以只是「返回值里带上实际影响」，不必是一整套工具。**

### 第三档 · 等信号

| 手段 | 触发信号 |
|---|---|
| 只读子 agent | 单次任务的材料已挤占主上下文，且这些材料只为得出一个结论 |
| 上下文压缩 | 任务生命周期确实长过一个窗口，且**状态外置已做**仍不够 |
| 按需加载知识包 | 某类知识体积大，且半数以上会话用不到 |
| 外部记忆 | 出现「想记但没有表可放」的事实（先考虑建表） |
| 自洽采样 | 某关键决策的不一致率**已被测出来** |
| 对抗校验 / judge | 解释类输出被发现编造，且程序无法校验 |
| `tool_choice` 强制 | 存在明确的「本轮必须调某工具」节点 |
| 在线埋点告警 | 已有 golden set，且线上出现过凭空编造 |
| MCP | 出现第二个入口，或已列入能力开放计划 |
| RAG / 向量 | 检索目标确实是非结构化文本，且全文检索已试过不够 |

### 投入顺序

```
  1. trace + 重放          ← 没有它，后面全是盲调
  2. 工具设计（II）         ← 杠杆最大：描述、错误消息、枚举、参数面
  3. 反馈（III）            ← 先做返回值，再考虑专门的验证工具
  4. 控制与恢复（IV）       ← 与业务风险同步推进
  5. 评测集（贯穿）         ← 尽早建立，可以很小
  6. 上下文工程（I）        ← 缓存排序与表示形式先做（便宜、立竿见影）
                              容量类优化等规模成为瓶颈再说
```

### 过度设计的自查信号

- 在为一个**从未被测量过**的问题写防御
- 一个措施只能靠 P4 ③ 级实现，而你还没回头看上游的工具设计
- 同一个问题上叠了两层以上防御
- 提示词规则已超二十条，还在加（P6）
- 为「将来可能」建的抽象，当下只有一个实现

---

## 十二、人 · Agent · 程序

本章把前面的原则放回三方协作的全景里。**Agent 不是一个独立系统，它是三方分工中的一层。**

### 三方的比较优势

| | 擅长 | 不可靠之处 | 成本结构 |
|---|---|---|---|
| **程序** | 确定性、可重复、可测试、快、可审计 | 只能处理**预想到**的情况 | 一次性开发 |
| **模型** | 理解模糊输入、编排、消歧、解释 | 不稳定、会编造、算不准、无法审计 | 每次调用都付费 |
| **人** | 价值判断、承担责任、掌握系统外的信息 | 慢、贵、会疲劳、注意力有限 | **最稀缺的资源** |

### 一次完整任务的流转

```
   人                       Agent（模型）              程序（确定性）
   ─────────                ─────────────             ──────────────
      │                          │                          │
  ①   │  意图（自然语言）          │                          │
      ├─────────────────────────>│                          │
      │                          │ ② 理解 → 结构化意图        │
      │                          ├─────────────────────────>│
      │                          │                          │ ③ 查询 · 计算
      │                          │                          │    校验 · 排程
      │                          │<─────────────────────────┤
      │                          │ ④ 摘要 + 指针（不含明细）  │
      │                          │                          │
      │                          │ ⑤ 够了吗？不够 → 回 ②      │
      │                          │                          │
  ⑥   │      明细 · 表格 · diff（绕过模型，直接渲染）          │
      │<────────────────────────────────────────────────────┤
      │                          │                          │
  ⑦   │  解释（只说为什么，   <────┤                          │
      │   不复述数字）            │                          │
      │                          │                          │
  ⑧   │  决策：确认 / 修改 / 否决                             │
      ├────────────────────────────────────────────────────>│
      │                          │                          │ ⑨ 落库
      │                          │                          │  （人点了才发生）
      │<────────────────────────────────────────────────────┤
  ⑩   │  回执 · 可撤销 · trace                                │
```

**四个关键点**

| | |
|---|---|
| **⑥ 明细绕过模型** | 大载荷从程序直达人。这是 P2 在架构上的样子——不是「叮嘱模型别复述」，是**让它没有可复述的东西**。⚠️ 小数据不必绕（见 §2 量级判据），绕了反而牺牲追问能力 |
| **⑦ 给事实，不给成品句子** | 服务端给结构化事实，措辞由模型组织。成品句子有固定形状，模型学会形状就能**凭空背出来**且看不出真假；而且「照抄」浪费了模型唯一不可替代的能力。写作规范集中写一处，⛔ 别散在每个工具的说明里 |
| **⑧⑨ 落库由人触发** | 不是模型触发。模型至多把提案推到「待确认」 |
| **②⑦ 是模型仅有的两处贡献** | 入口的理解、出口的解释。**中间它是编排者，不是生产者** |
| **⑤ 是自主性所在** | 够不够、要不要再查一轮、要不要反问——这一步不该被流程编排替代（§0） |

### 分工判据

```
                    能写成确定性规则吗？
                            │
                ┌───────────┴───────────┐
               能                       不能
                │                        │
           【 程序 】              错了要紧 / 不可撤吗？
        （即便模型也会做，                │
          仍然交给程序）        ┌─────────┴─────────┐
                              否                   是
                               │                    │
                          【 模型 】      模型准备 → 【 人 】
                                        （压缩到可判断的规模）

        独立于以上的一条：需要有人担责吗？ → 只能是【 人 】
```

**第一问的常见错误**：因为「模型也能做」就交给模型。判据是**能不能写成规则**，不是**谁能做**。

### 三方互为对方兜底

```
                    ┌───────────────┐
                    │      人        │  判断 · 担责 · 场外信息
                    └───┬───────┬───┘
                        │       │
              ┌─────────┘       └─────────┐
              │                           │
       ┌──────┴───────┐           ┌───────┴──────┐
       │     模型      │◄────────►│     程序      │
       │  理解 · 编排   │          │  执行 · 校验   │
       └──────────────┘          └──────────────┘
```

| 从 → 到 | 提供什么 |
|---|---|
| 人 → 模型 | 意图、纠偏、终审 |
| 模型 → 人 | **把万级压缩到可判断的十级**、解释、拿不准时反问、**引导下一步**（见下） |
| 模型 → 程序 | 结构化意图、调用参数 |
| 程序 → 模型 | 事实、验证手段、可据以纠正的错误消息 |
| 人 → 程序 | 确认、配置规则 |
| 程序 → 人 | 明细直渲、diff、回执、可撤销 |

**各自的失败被谁接住**

| 谁的失败 | 表现 | 谁接住 |
|---|---|---|
| 程序 | 遇到没预想到的情况 → 报错或僵住 | **模型**（它恰好擅长未预见输入） |
| 模型 | 编造、算错、漏步骤 | **程序**（不给它算的机会 + 校验）**+ 人**（终审） |
| 人 | 疲劳、批量盲点确认、注意力耗尽 | **程序**（默认值安全、diff 高亮变化、可撤销）**+ 模型**（把规模压到可判断） |

### 引导：模型 → 人 这条边上最容易做错的一项

**为什么必须有**：工具清单只有模型看得见，而人不读文档——**引导是能力披露的主要通道**。没有引导，等于让人自己猜系统能做什么。

**为什么必须设计而非放任**：

| 风险 | 表现 |
|---|---|
| **引导是权力** | 被建议的选项会成为默认路径，多数人顺着走 |
| **可能编造不存在的能力** | 「要我导出 PDF 吗」——但并无此工具。幻觉出现在引导位上尤其有害：人会答应，然后失败 |
| **仪式化** | 不论有无实质下一步都追加一句 → 噪音，且钝化人对真正需要决策处的敏感 |
| **责任模糊** | 「是它建议我这么做的」——回到责任不可委托 |

**分工：候选由程序给，措辞由模型给，决定权在人。**

```
   程序 ──→ nextActions[]   基于当前状态确定性计算
                             只含真实存在的动作
             │
   模型 ──→ 从中挑选最相关的 1–2 条，用人话说出来
             │
   人   ──→ 选，或不选
```

三个副产品：**不会编造能力**（候选来自程序）；**不会仪式化**（程序知道当前状态是否真有下一步，没有就不给）；**责任仍在人**（引导只是选项）。

这也是 §5「工具返回值是第 2 优先容器」最典型的用法。

**引导 ≠ 反问**：反问的触发条件是模型**不确定**（应调 clarify）；引导的触发条件是模型**确定**，但人有选择余地。同一条边，触发条件相反。

**强度分级，越强越该收归程序：**

| 强度 | 形态 | 归谁 |
|---|---|---|
| 弱 | 陈述可能性（「还可以按医生筛」） | 模型可自由行使 |
| 中 | 建议（「建议先核对 X」） | 模型行使，但需真实依据 |
| 强 | **预选默认值** | **程序的职责**，且必须是安全侧默认——这已经是在替人做决定 |

#### 强引导：把引导节点做成数据

当流程有明确的决策点时，比起让模型每轮临场判断该提什么，更好的做法是**把节点固化成确定性数据**，随结果一起下发：

```ts
{ key, severity,
  title,    // 一句话说清是什么
  why,      // 一句事实
  default:  { label, isNoop: true },   // 不动会怎样
  options:  [{ label, intent, args }] }  // 能做什么
```

五条规则：

1. **只陈述事实，不给建议。** `why` 是「他们的专属客服已排满」，不是「建议你铺平」。选项中性并列——给建议就是替人做决定。
2. **默认永远是 no-op。** 这样「一条不点、直接完成」在任何情况下都是安全的。
3. **有后果的节点必须全亮，⛔ 不许截断。** 藏起一条，用户就不知道有东西卡着。
   防噪音靠**分层**不靠丢弃：**需处置**（不管就有后果）全部展开并按 `severity` 排序；**提示**（纯信息）默认折叠。排序规则必须确定性。
4. **每次重算，不做增量。** 增量会漂，而漂了不报错。
5. **不是关卡。** 不阻塞用户直接完成，自由通道始终开着——所以它不违 B1。
6. **选项做成可直接点击执行的动作**，⛔ 不是让人照着打字；自由输入只作兜底。
   打字 → 模型理解 → 翻译成动作，这条链每一环都可能出错；点击是**确定性**的，动作直接给出，没有理解环节。

**三方各取所需**：
- **界面** —— 渲染成可点的行，**只给标题 + 按钮**（能做什么）
- **模型** —— 拿 `title / why / defaultLabel` 组织语言，**负责解释**（为什么 / 不管会怎样）；⛔ 不给它 options 的技术细节
- **人** —— 决定

> ⚠️ 两边都写全，用户会把同一件事读两遍（实测）。分工：**界面管动作，对话管解释**。
> ⛔ 但按钮不能搬进对话 —— 打字 → 模型理解 → 翻译成动作，每一环都可能错；点击没有理解环节。
**两个入口必须收敛到同一个 `intent` 契约**——界面点击与「用户直接说」走同一条路，这样「界面能做的模型也能做」是结构保证，不是提示词里的一句叮嘱。

> 附带收益：节点数组天然就是那段话的**结构骨架**（未处置的节点 = 「要你定的」那一块）。
> 靠提示词要求模型分小节是 P4 ③ 级；由数据决定结构是 ① 级。

#### 「下一步做什么」应当是确定性可答的

```
下一步 = 当前状态 + 未处置的节点 + 该状态的默认路径
```

用户随时问「接下来呢」，答案由状态机推出，⛔ 不由模型即兴发挥。
两个后果都很值钱：**永远不会指向系统做不到的事**（否则就是 G2 说的编造能力）；**永远不会漏掉卡住的东西**。

### 责任不可委托

模型不能被追究、不能被解雇、不能被起诉，**因此它不能承担责任**。

推论：**写屏障的存在理由不是「模型可能出错」，而是「必须有人负责」。** 这是 P5 里典型的「业务本身要求」——模型再强也不该拆除。

同理：「这是 AI 决定的」不是一个可用的解释。任何对外生效的动作，都必须能追溯到一个具体的人的确认。

### 把人放在他真正能判断的位置

**让人审 400 条 = 没有人在审。** 这不是人的问题，是分工设计的问题。

人的注意力是三方中最稀缺的资源，必须花在**只有人能做的判断**上：

| 不该给人的 | 该给人的 |
|---|---|
| 逐条核对明细 | 这一批该不该做 |
| 手写筛选条件 | 这个口径对不对 |
| 记住上下文 | 这个例外要不要放行 |

对应到工程：**每一步都要收敛规模**，让最终落到人面前的判断数量级降到十位以内。

### 三方比例会变，结构不变

模型变强，**改变的是「模型 ↔ 人」的边界**——更多原本需要人判断的事可以交给模型。

**但它不改变「模型 ↔ 程序」的边界**，因为那条线的依据是「能不能写成规则」与「确定性 / 成本」，与模型强弱无关：再强的模型也不该用来做计数和排序。

**也不改变「人」在责任层的位置**，那条线的依据是问责，不是能力。

### 反模式

| 反模式 | 后果 |
|---|---|
| 让模型做程序的活（算数、排序、去重、分页） | 慢、贵、不可靠，且不可审计 |
| 让程序做模型的活（正则 / 关键词去理解自然语言意图） | 脆，一变就崩 |
| 让人做程序的活（人肉核对批量数据） | 疲劳性错误，且规模一大就形同虚设 |
| 让人做模型的活（人肉拼筛选条件） | 慢，而且这正是 agent 该消化掉的部分 |
| 让模型担责 | 无法追究，且是合规风险 |
| 引导完全交给模型自由发挥 | 会编造不存在的能力；会退化成每轮一句的仪式 |
| 把人放在错误的规模上 | 表面有人把关，实际无人把关 |

---

## 附：本文的边界

- 本文描述的是**架构层面**的判断标准，不含任何具体实现。
- 对流行术语的判定是**依场景而定的结论**，不是普适裁决；场景变化（如开放能力、引入非结构化数据、agent 承担更多角色）时应重新评估。
- LLM 能力演进很快，其中标注为「弥补模型不足」（P5）的措施，应当定期复查是否仍有必要。
