## Agent 不是知道得越多越好

假设你请一位新同事修复支付 Bug，却一次性把公司五年的聊天记录、所有代码仓库、完整监控日志和几十份旧规范都塞给他。他获得的信息很多，但真正有用的线索反而更难找到。

Agent 也一样。

上下文窗口再大，也不等于应该把所有内容放进去。模型做判断时真正需要的是：与当前任务相关、来源清楚、足够新鲜、优先级明确，而且长度可控的信息。

负责选择、组织和维护这些信息的系统，就是 Harness 的第二个核心部分：**上下文引擎**。

如果把模型比作厨师，上下文引擎不是仓库，而是备菜台。仓库里可以有一万种食材，但当前这道菜只应该摆上真正要用的那几样。

本文是《[把 Harness 讲清楚：Agent 背后的运行系统](https://www.ittt.cc/articles/15)》八个核心部分的第二篇。

## 什么是上下文

上下文是一次模型调用时，模型实际能够看到的全部信息。它通常包含：

- 系统规则和身份说明；
- 当前用户请求；
- 最近的对话历史；
- 任务契约和当前计划；
- 项目规范与相关文件；
- 工具定义和工具结果；
- 长期记忆或用户偏好；
- 当前环境、时间和运行状态。

上下文并不是数据库中“已经保存”的全部内容。只有被选中并组装进本次请求的内容，才会影响模型判断。

所以，上下文工程的核心问题不是“我们拥有什么数据”，而是“在这一刻，模型应该看到什么”。

## 上下文引擎的五项工作

### 发现

根据任务找到候选信息，例如相关文件、目录内规则、历史决策、接口文档和最近的工具结果。

### 筛选

移除与当前任务无关、重复、过期或可信度不足的内容。

### 排序

让不可违背的系统规则优先于项目建议，让当前任务信息优先于陈旧历史，让直接证据优先于模型推断。

### 压缩

当任务变长时，把已经完成的探索过程压缩成事实、决策和未解决问题，而不是永久保留全部原始输出。

### 标注来源

明确一段信息来自用户、项目文件、工具结果、外部网页，还是 Agent 自己的推断。来源不同，可信度和可执行权限也不同。

## 一次上下文组装的流程

```text
任务契约 + 当前步骤
          ↓
    生成信息需求
          ↓
检索文件、记忆、规则和工具结果
          ↓
去重、过滤、检查新鲜度和权限
          ↓
按优先级与 token 预算排序
          ↓
必要时摘要或截取
          ↓
组装本次模型请求
          ↓
记录“模型实际看到了什么”
```

最后一步很重要。没有可追踪的上下文快照，出现错误时就无法判断到底是模型推理错了，还是 Harness 给了错误资料。

## 上下文不是越多越好的三个原因

### 噪声会稀释重点

当真正关键的三条规则埋在几万行日志中，模型更容易忽略它们。长上下文解决了容量问题，却没有自动解决注意力分配问题。

### 过期信息会制造冲突

旧文档说接口字段叫 `userId`，新代码已经改为 `accountId`。如果两份内容同时出现却没有时间和来源标记，模型很可能选择错误版本。

### 上下文有真实成本

更多 token 意味着更高延迟和费用。频繁变化的长前缀还会降低缓存复用。上下文引擎需要把信息价值当作有限预算来分配。

## 给信息建立优先级

可以为候选内容计算一个简单分数：

```text
score = 相关性 × 新鲜度 × 可信度 × 必要性 - token 成本
```

这不是必须采用的数学公式，而是一种思维方式。

- **相关性**：是否直接帮助当前步骤；
- **新鲜度**：是否仍然有效；
- **可信度**：来自源码、工具事实，还是未经验证的推断；
- **必要性**：不提供它是否会违反规则或导致错误；
- **成本**：加入后占用多少上下文空间。

系统规则和安全边界不应只靠分数竞争，它们属于必须保留项。评分更适合在大量候选资料之间做选择。

## 最小上下文选择器

下面用伪代码表达一个基础实现：

```python
def build_context(task, step, budget):
    required = load_required_rules(task)
    candidates = []

    candidates += search_workspace(step.query)
    candidates += search_memory(task.objective)
    candidates += recent_tool_results(task.id)

    candidates = remove_duplicates(candidates)
    candidates = reject_expired_or_unauthorized(candidates)
    candidates = rank_by_relevance(candidates, step)

    selected = list(required)
    remaining = budget - token_count(required)

    for item in candidates:
        rendered = compress_if_needed(item, remaining)
        if token_count(rendered) <= remaining:
            selected.append(rendered)
            remaining -= token_count(rendered)

    return attach_source_metadata(selected)
```

真实系统还需要处理取消、检索失败、敏感信息脱敏和缓存，但这段代码已经表达了核心：先放必须信息，再用剩余预算选择最有价值的内容。

## 长任务为什么需要压缩

Agent 连续工作几十轮后，历史里会积累大量搜索结果、失败尝试和重复讨论。如果把它们原样带入每一轮，请求会越来越慢，也越来越容易跑偏。

压缩的目标不是简单缩短文本，而是保留未来决策仍然需要的状态：

```text
原始历史
├─ 尝试过哪些方案
├─ 每次工具的完整输出
├─ 大量中间讨论
└─ 已经解决的问题
          ↓ 压缩
任务状态摘要
├─ 已确认事实
├─ 已采用决策及原因
├─ 已修改对象
├─ 尚未解决的问题
├─ 不得重复的失败方案
└─ 下一步与完成标准
```

好的摘要应当可以让一个没有看过前文的 Agent 接手，而不会重复破坏性操作或遗失关键约束。

## 记忆与上下文不是同一个东西

记忆是可能在未来被再次使用的信息存储，上下文是本轮真正提供给模型的内容。

例如，系统记住用户偏好使用中文，这是记忆；当前请求组装时把“使用中文回答”加入系统提示词，才成为上下文。

两者之间应该有检索和准入层。不能因为某条信息曾经被保存，就让它永久出现在每一次请求中。长期记忆还应支持来源、过期时间、用户更正和删除。

## 工具结果怎样进入上下文

工具返回内容往往很大。上下文引擎不应该把数据库全部行、完整构建日志或整份网页无条件回灌给模型。

更稳妥的做法是：

1. 工具保留完整规范结果，便于程序处理和审计；
2. 为模型生成有长度上限的视图；
3. 明确标注是否截断，以及完整数据的位置；
4. 只在模型确实需要时继续读取某个片段。

例如搜索工具可以返回总命中数、按文件分组的前 20 条结果和 `truncated: true`，而不是假装展示的是完整结果。

## 必须区分事实、规则和推断

把不同来源混在一段自然语言里，是上下文系统常见的危险做法。

更清楚的结构是：

```text
[SYSTEM RULE]
生产环境禁止自动部署。

[USER REQUEST]
修复登录错误并准备发布说明。

[TOOL FACT]
测试 auth/session.test.ts 已通过，退出码为 0。

[AGENT INFERENCE]
根因可能是刷新令牌并发更新，需要继续验证。
```

工具输出也不应天然被当作指令。网页、邮件和仓库文件中都可能出现“忽略之前规则”之类的文本，它们是待分析数据，不是可以覆盖系统规则的新命令。

## 常见的失败方式

### 一次性读取整个仓库

看起来信息充分，实际浪费预算，并让关键文件失去突出度。应该先搜索和建立地图，再按需深入。

### 只做语义相似度检索

语义相似不等于权威。旧设计稿可能比当前源码更像用户问题，却已经失效。排序必须结合时间、来源和文件层级。

### 摘要时丢掉否定条件

“允许修改代码，但禁止更改数据库”如果被压缩成“允许修改项目”，风险会被放大。安全约束和未完成事项不应被模糊摘要。

### 不记录截断

模型看到前 100 行却以为看到了完整文件，会做出错误结论。所有截断视图都应该显式说明范围。

### 把模型推断写成永久事实

未经工具或用户验证的猜测，应该保持推断状态。只有获得证据后才能升级为事实。

## 从原型开始怎么做

可以先建立四层上下文：

1. 固定系统与安全规则；
2. 当前任务契约；
3. 最近对话和工具结果；
4. 按当前步骤检索出的项目资料。

然后为每一层设置明确预算，记录来源与截断信息。任务超过一定轮次时，把旧历史压缩成结构化状态摘要，并保留原始日志供审计，而不是继续塞给模型。

## 上下文引擎检查清单

- 模型本轮真正需要解决什么问题？
- 必须始终保留的规则有哪些？
- 候选资料是否检查了来源、新鲜度和权限？
- 是否去除了重复与无关内容？
- 工具结果过大时是否提供渐进式读取？
- 截断和摘要是否明确标记？
- 是否区分用户输入、系统规则、工具事实和 Agent 推断？
- 长任务摘要是否保留关键决策、未完成事项和禁止条件？
- 能否还原某次模型调用实际看到的上下文？

## 最后理解上下文引擎

模型能力决定了它“能不能想明白”，上下文引擎决定了它“拿什么来想”。

很多看似模型不够聪明的问题，实际是系统给了错误、过期或过量的信息。一个成熟 Harness 不追求把上下文窗口塞满，而是像一位可靠的研究助理：在正确的时间，把正确的证据摆到桌面上，并清楚说明每条信息从哪里来。

一句话总结：**上下文的质量，通常比上下文的数量更重要。**
