# 上下文选择与渐进式披露

## 问题

每轮都急切加载所有可用上下文的 Agent 运行时会支付复利般的税：token 成本随目录大小增长，启动延迟随信息源数量扩展，并发 Agent 重复相同的昂贵 I/O。模型收到数千个它从不阅读的 token，而它实际需要的数据却迟到或根本不到。如果不进行刻意选择，上下文窗口就变成了垃圾填埋场——技术上是满的，实际上是空的。

这些问题出现在任何维护不断增长的技能目录、从多个持久化存储读取或服务并发请求的 Agent 中。并非特定于任何单一运行时。

## 黄金法则

### 懒加载优于急切加载

在上下文变得相关的时刻加载它，而非会话开始的时刻。急切注入所有内容"以防万一"意味着每次 API 调用都为模型可能永远不使用的 token 付费。推迟加载意味着只在模型实际激活能力时才付费。即时获取的一次性延迟几乎总是比膨胀 prompt 的每轮成本更廉价。

### 三层渐进式披露

将每个上下文来源结构化为三层递增成本：

1. **元数据**（每项约 100 token）：名称、描述和触发提示。始终存在。这是"这是否相关？"的信号。
2. **指令**（有界，如 < 5000 token）：激活能力的完整正文。仅在激活时加载。
3. **资源**（无界）：参考文件、脚本、数据资产。仅在 Agent 明确请求时加载。

发现成本（第一层）随目录大小扩展，但执行成本（第二三层）按每个激活项是恒定的。保持发现廉价使你可以维护一个大目录而无需在每轮为其付费。

### 备忘化 promise，而非结果

当多个调用者同时请求相同的昂贵上下文时，备忘化进行中的 promise 使所有调用者共享单次 I/O 操作。仅备忘化已解析的值会创建一个窗口，让并发调用者各自看到缓存为空并启动重复工作。promise 本身就是去重的键。

### 在变更处失效，而非定时器

基于时间或响应式订阅的缓存过期要么提供过时数据，要么重建过于频繁。相反，在每个已知的变更点放置显式的失效调用。这维护起来更费力——每条新的写入路径都必须添加自己的失效——但它保证缓存恰好与最近一次刻意更改一样新鲜。

## 适用场景

- 技能或能力目录在增长，常驻 prompt 正变得昂贵。
- 启动延迟高，因为上下文在每轮都急切构建。
- 多个并发 Agent 或调用重复相同的昂贵 I/O。
- 你需要支持大目录而不线性增加每轮 token 成本。
- 上下文来源跨越多个可能重叠的目录、存储或服务。

## 权衡

| 决策 | 收益 | 代价 |
|---|---|---|
| 懒加载 | 低空闲 token 成本 | 首次激活有一次往返延迟 |
| 渐进式披露（三层） | 大目录保持廉价 | 模型无法推理未激活的能力 |
| promise 备忘化 | 并发下无重复 I/O | 缓存逻辑比简单值缓存更微妙 |
| 在变更处手动失效 | 只在必要时重建 | 每条新写入路径都必须包含自己的失效调用 |
| 发现层的 token 预算 | 目录成本有界且可预测 | 每个条目必须适配紧凑的字符上限 |
| 基于路径的条件激活 | 领域特定技能在相关之前保持休眠 | 激活状态必须在进程重启后存活或被重新评估 |

## 实现模式

- 将每个昂贵的上下文构建器包装在备忘化的异步函数中。存储 promise 本身使并发调用者共享同一进行中的请求，而非各自发起重复 I/O。
- 为每个备忘化构建器暴露一个命名的缓存清除调用。仅从已知的变更处理器调用它——永远不要从通用观察者或轮询循环调用。
- 对于能力目录，仅从元数据字段（名称、描述、触发提示）计算估计的 token 数。完整正文存储在延迟闭包中，仅在激活时获取。
- 将发现层总预算限制在上下文窗口的固定比例（如约 1%），每个条目限制在紧凑的字符上限。
- 使用路径条件元数据门控领域特定能力：能力保持休眠直到会话中触及匹配的文件或目录。
- 通过规范路径去重多来源加载。在插入活跃集之前解析符号链接并规范化——原始路径的字符串比较会产生假阴性，而虚拟或网络文件系统上的 inode 比较不可靠。
- 将共享状态标记为深度不可变。在排除点而非静默地记录从不可变性包装器中排除的字段（如函数类型字段）。

## 踩坑指南

**变更后的过时上下文。** 因为缓存从不自动过期，任何外部变更（新文件、切换的标志、更新的存储）在缓存被显式清除之前都不可见。每个变更点都必须调用对应的失效。漏掉一个意味着模型在会话余下时间都在过时数据上操作。

**无 promise 备忘化的并发初始化竞态。** 如果两个调用同时触发相同的提取且都看到缓存为空，两者都会尝试相同的 I/O。只有一个会干净地成功；另一个可能失败或产生损坏的输出。备忘化 promise 彻底消除竞态。

**过早披露膨胀每一轮。** 将完整能力正文注入系统 prompt 意味着每次 API 调用都为模型可能永远不使用的 token 付费。有 20 多个能力时，这可能在第一条用户消息到达之前就消耗上下文预算的很大份额。

**条件能力必须跟踪激活状态。** 被路径条件门控的能力在触及匹配文件之前是休眠的。如果激活状态丢失（如进程重启），能力必须根据当前上下文重新评估——否则它会从会话中静默消失。

**去重必须使用规范路径，而非原始路径。** 当涉及符号链接、挂载或多个搜索目录时，同一文件可以出现在不同路径下。原始路径的字符串比较会漏掉重复项；比较前解析为规范路径。

## Claude Code 实证

Claude Code 的上下文系统是这些原则的生产实现，跨多个来源目录管理不断增长的技能目录：

**备忘化加手动失效，而非响应式订阅。** 系统上下文构建器和用户上下文构建器都是备忘化的异步函数。缓存在整个进程生命周期内持久化。当某些东西必须改变时——例如系统 prompt 注入被更新——运行时在那个特定变更点显式调用缓存清除。这是一个刻意的选择：上下文从缓存服务廉价，重建昂贵。在每次状态更新时重建的响应式模型将完全违背缓存的目的。

**并发初始化的 promise 备忘化。** 内置技能提取存储提取 promise 本身而非已解析的值。如果两次技能调用在启动期间竞争，两者等待同一 promise。没有这一设计，两者都会尝试同时写入相同的提取文件，第二个会失败或损坏输出。

**token 计数作为渐进式披露的门控指标。** 技能加载器仅从名称、描述和触发提示字段计算估计的 token 数——从不从完整 markdown 正文。正文存储在延迟闭包中，仅在调用时获取。这干净地分离了"这是否相关？"的信号（廉价，始终存在）和"这个技能做什么？"的内容（昂贵，延迟加载）。总列表预算限制在上下文窗口的大约百分之一，每个条目限制在约 250 个字符。

**跨多来源目录的 realpath 去重。** 从管理的、用户、项目和额外目录加载技能时，同一技能文件可能出现在符号链接路径下。加载器在插入已见集之前将每个文件解析为其规范路径。团队选择规范路径比较而非 inode 比较，因为 inode 在虚拟和网络文件系统上不可靠。

**深度不可变的共享状态。** 主应用状态被包装在深度不可变性类型中，以防止跨并发 Agent 的意外原地修改。任务状态被排除在不可变性包装器之外，因为它包含包装器无法处理的函数类型——但排除在排除点有文档记录，而非静默丢弃。
