context engineering — JIT 检索与 context rot
context engineering — JIT 检索与 context rot
日期: 2026-07-24 阶段: Phase 2 - AI-native 参考架构 标签: #context-engineering #jit-retrieval #context-rot
核心问题
P2 到现在,orchestrator(orchestratorAgent.ts)的子 agent 已经能并行检索、能记账(Budget)。但有一个被普遍误信的直觉没被挑战过:「上下文窗口越大,把越多资料塞进去越好」。Gemini 2.5 给百万 token、Claude 给 200K——很容易得出「先把 312 篇笔记 + 全部 trace + 全部 AML 案例预载进去,模型自己挑」的结论。
今天证明这是错的,而且是系统性地错。两个问题:
- 为什么塞满上下文反而降召回? 这不是工程 bug,是 transformer 架构的固有代价。要量化它(context rot 曲线),并讲清机制(n² 注意力 + 长序列训练数据稀疏)。
- JIT(just-in-time)检索怎么缓解? 即「不预载、给 agent 轻量引用(路径/查询/链接),运行时按需取」——它换来了什么、又付出了什么(运行时探索更慢)。
对 AML Copilot 这是合规质量问题:一份 SAR 的事实核查若被淹没在 100K token 的无关 trace 里,judge 拿到的就是「召回退化」的上下文,校准好的 κ 也救不回来——garbage context in, garbage verdict out。
关键内容
A. context rot:召回随输入长度退化的机制与数据
Anthropic 的定义直白(Effective context engineering for AI agents, 2025-09):
"As the number of tokens in the context window increases, the model's ability to accurately recall information from that context decreases."
机制有三层,不是单一原因:
- n² 注意力预算被摊薄。transformer 对 n 个 token 产生 n² 对两两关系,每个 token 必须 attend 到其余所有 token。Anthropic 把上下文称为「有限资源,有边际递减回报」——「LLMs, like humans, have a limited attention budget」。token 越多,注意力被摊得越稀。
- 长序列训练数据稀疏。模型在长序列上见的训练数据更少,处理「跨整个上下文的依赖」的专用参数也更少——这是数据分布问题,不是推理时能修的。
- 语义近似的干扰项主动误导。Chroma 的 Context Rot 研究(2025)测了 18 个前沿模型(GPT-4.1 / Claude 4 / Gemini 2.5 / Qwen3),发现「语义相似但无关的内容会主动误导模型」,退化幅度超过「单纯长度」能解释的部分。
量化 context rot(Chroma 2025 关键发现,原文口径):
| 现象 | 数据 | 含义 |
|---|---|---|
| 普适退化 | 18/18 模型随输入变长性能下降 | 无一幸免,且「非均匀、常出人意料」 |
| 单个干扰项 | 哪怕只加 1 个 distractor,相对「needle only」基线就掉 | 干扰项「在幻觉回答里出现最频繁」 |
| needle-问题相似度 | 相似度低的 needle,随长度退化更快(PG essays 0.445–0.775 区间) | 检索目标与问题措辞越不像,长上下文越埋没它 |
| 位置效应(Liu 2023 + Chroma) | 上下文 <50% 满时 U 形(首尾好中间差);>50% 满时偏向近端 | 「lost in the middle」随填充率变形 |
| 模型差异 | Claude 系幻觉率最低,GPT 系在有干扰项时最高 | 退化幅度模型相关,但方向一致 |
把它画成召回曲线(示意,纵轴=正确召回率,横轴=上下文填充 token 数,定性形状取自 Chroma「非均匀单调下降」结论):
召回率
1.0 ┤●●●○ 短上下文≈满分(needle-in-a-haystack 经典任务)
│ ○○○ 几千 token 起肉眼可见下滑
0.7 ┤ ○○○○ 加入 1 个语义近似干扰项 → 整条曲线下移
│ ○○○○○ >50% 满后偏向近端,中段证据被埋
0.4 ┤ ○○○○○○ 长尾:advertised 200K,effective 远小于此
└───┬────┬────┬────┬────┬──► 上下文 token
1K 10K 50K 100K 200K
反直觉洞察①(把所有上下文塞进窗口反而降召回):直觉是「窗口够大就全塞,省得漏」。但 context rot 说明有效上下文(effective context)远小于广告上下文(advertised window)——200K 的窗口不代表第 199K 个 token 还能被准确召回。更狠的是:塞进去的「语义近似但无关」的资料(比如另一个相似 SAR 的 trace)会主动拉低对目标证据的召回,比留白还糟。上下文不是越多越好,是越精越好;多余的 token 不是中性的,是负资产。
B. JIT 检索 vs 预载:用轻量引用换运行时按需加载
Anthropic 给的解法是 just-in-time(JIT)检索——把「检索时机」从「推理前一次性预载」推迟到「推理中按需触发」:
「maintain lightweight identifiers (file paths, stored queries, web links, etc.) and use these references to dynamically load data into context at runtime using tools.」
Claude Code 是范例:它对大数据库做分析时,写定向查询 + 用 head/tail 这类 Bash 命令切片,「without ever loading the full data objects into context」——agent 自己导航、自己取,全量数据从不进上下文。
JIT vs 预载(pre-inference / pre-loading)对比:
| 维度 | 预载(pre-loading) | JIT(按需检索) |
|---|---|---|
| 何时取上下文 | 推理前一次性全取 | 推理中由 agent 用工具按需取 |
| 进窗口的内容 | 全量原始数据 | 仅轻量引用(路径/查询/链接)+ 命中片段 |
| context rot 暴露 | 高——全量挤占注意力预算 | 低——只载相关片段 |
| 首字延迟 | 低(数据已在手) | 高——运行时探索是多轮工具调用 |
| 失败模式 | 召回被淹没、成本爆炸 | agent「乱用工具/死胡同探索」浪费上下文 |
| Anthropic 原话 | — | 「runtime exploration is slower than retrieving pre-computed data」 |
没有免费午餐——Anthropic 明确两条代价:(1)「runtime exploration is slower」;(2)「opinionated and thoughtful engineering is required」防止 agent 用错工具、陷入死胡同空耗上下文。所以官方推荐混合策略:
「retrieve some data up front for speed, and pursu[e] further autonomous exploration at its discretion.」
Claude Code 自身就是混合体:CLAUDE.md 启动即载(高频、稳定、小),glob/grep 让它运行时 JIT 取文件(海量、按需、大)。
JIT 决策伪代码(把「该不该预载」变成一条规则):
def should_preload(item):
# 预载只给「高频 × 稳定 × 小」的内容;其余走 JIT
if item.access_frequency == 'every_turn' and item.is_stable and item.tokens < SMALL:
return True # 例:系统约束、pinned facts、AML rubric
return False # 例:312 篇笔记全文、全量 trace、案例库 → 留引用,JIT 取
context = [x for x in candidates if should_preload(x)] # 瘦上下文
# 其余仅注入「索引/工具签名」,让 agent 运行时自取
tools = [hybridSearch, getCaseTrace, getNoteByPath]
这正是 RAG 的本质:RAG 就是 JIT 检索的一种——不预载全语料,给 agent 一个检索工具,命中片段才进上下文。hybridSearch.ts 的「over-retrieve per lane(pool=k×4)再 RRF 融合到 k」就是在控制「进窗口的量」。
C. 四技术合体:JIT 检索如何接进 orchestrator
Anthropic 列的四个上下文管理技术不是并列选项,是协同的一套,对应到本仓库已有/将建的部件:
| Anthropic 技术 | 做什么 | 本项目落点 |
|---|---|---|
| JIT retrieval | 留引用、运行时按需取 | hybridSearch.ts(命中才进窗口)+ 子 agent 工具 |
| compaction | 近上限时摘要历史,保留架构决策、丢冗余输出 | contextBuilder.ts 的 summary 分支(>阈值触发 summarizeMessages) |
| structured note-taking | agent 维护外部持久记忆(NOTES.md),重置后恢复 | pinnedFactsStore.ts(pinned facts 跨 turn 持久) |
| sub-agent | 专职子 agent 处理聚焦任务,回传 1000–2000 token 压缩摘要 | orchestratorAgent.ts 子 agent 仅回 { text },不回原始检索结果 |
关键串联:orchestratorAgent.ts 的子 agent execute 现在只 return { text: r.text }——这本身就是「sub-agent 压缩摘要」:检索的原始命中(可能上千 token)留在子 agent 的局部上下文里不回传给 lead,lead 只拿到浓缩结论。这把 context rot 挡在了 lead 的上下文之外。contextBuilder.ts 的 totalTokens <= summaryThresholdTokens 分支则是 compaction 的触发器。
反直觉洞察②(JIT 的瓶颈不是「取不到」,是「取太多/取错」):上线 JIT 后,团队第一反应是担心「agent 漏检关键证据」。但 Anthropic 的实践警告恰恰相反——主要失败模式是 agent 乱用工具、陷入死胡同探索,把上下文喂满了「探索过程的噪声」。所以 JIT 不是「给个检索工具就完事」,必须配预算闸(
Budget.assertCanToolCall)+ 工具描述精确化,否则 JIT 会用「探索噪声」复刻它本想消灭的 context rot。
设计要点/决策表
| 要点 | 决策 | 理由 |
|---|---|---|
| 默认载入策略 | JIT 优先,仅「高频×稳定×小」预载 | effective context ≪ advertised window,预载全量=自找退化 |
| 预载白名单 | 系统约束、pinned facts、AML rubric | 每轮都用、稳定、token 小 |
| JIT 通道 | hybridSearch + 案例/笔记按路径取 | 命中片段才进窗口,over-retrieve 后融合裁剪到 k |
| 子 agent 回传 | 仅压缩摘要(≤2000 token),不回原始命中 | 把 context rot 挡在 lead 上下文外 |
| 防 JIT 退化 | 预算闸 + 精确工具描述 | 主要失败是「乱探索」而非「漏检」 |
| 混合策略 | CLAUDE.md/约束预载 + RAG 工具 JIT | 对齐 Anthropic「up front for speed + autonomous exploration」 |
对本项目的落地
orchestratorPrompt.ts注入 JIT 原则:在 ORCHESTRATOR_SYSTEM 增一段「Context discipline」——明确「不要求子 agent 回传原始检索结果,只回结论性摘要;需要细节时再派一次定向子查询」。这把「sub-agent 压缩摘要」从隐式行为升级为显式契约。contextBuilder.ts的 compaction 已就位但需补一条:当前 summary 分支保留「最后 3 条 message」+ summary。补一条规则——架构决策类消息(如已确认的 SAR 判定)应进 pinned facts 而非被 summarize 抹平(呼应 Anthropic「compaction 保留架构决策、丢冗余输出」)。具体:在pinnedFactsStore.ts增一类kind: 'decision',compaction 时优先保活。hybridSearch.ts的 pool 是 JIT 的量控旋钮:pool = max(k*4, 20)over-retrieve 后 RRF 裁到 k——这正是「按需取、只让相关片段进窗口」。Day 41 将在此基础上做自适应迭代检索(多跳/查询改写),让 JIT 从「单次命中」升级为「迭代逼近」。- AML 落点:SAR 事实核查走 JIT——judge 不预载全案 trace,而是按
caseId用工具取该案 trace 切片(类比 Claude Code 的head/tail)。src/aml/observability.ts的 trace 导出应支持「按 caseId 切片取」而非「全量导出再喂」,避免一份核查被无关案例的 trace 淹没。 - 诚实标注:上述
kind:'decision'pinned 类、orchestratorPrompt 的 context discipline 段为 W6 设计动作;AML trace 切片接口为 P3 上线前的运营增强,本周仅落原则与接口签名,不谎称已实现完整 JIT 调度器。
参考资料
- Anthropic — Effective context engineering for AI agents:context rot 定义、attention budget/n² 机制、JIT 检索(轻量引用+运行时取)、Claude Code 的 head/tail 范例、混合策略、四技术(JIT/compaction/note-taking/sub-agent)(2025-09)
- Chroma — Context Rot: How Increasing Input Tokens Impacts LLM Performance:18 模型普适退化、单干扰项即掉、needle-问题相似度低退化更快、>50% 满偏近端、Claude 幻觉率最低 (2025)
- the-decoder — Anthropic claims context engineering beats prompt engineering:context engineering 取代 prompt engineering 的转向解读 (2025-09)
- Morph / Redis — Context Rot 工程解读:effective context ≪ advertised window、实践缓解清单 (2025-2026)
- 本仓库
src/agent/orchestrator/orchestratorAgent.ts(子 agent 仅回 text=压缩摘要)、src/agent/memory/contextBuilder.ts(summary=compaction)、src/agent/rag/hybridSearch.ts(pool over-retrieve)、src/agent/memory/pinnedFactsStore.ts(2026-06)
SOTA 检查 (2026-06-11)
- JIT 检索 + context rot 是 2026-06 的活口径,非过时:Anthropic 2025-09 文是当前 context engineering 的事实基准,Cognition 同期「context engineering 是 agent 工程师的 #1 工作」被反复引用;本日 WebSearch 未见推翻「effective context ≪ advertised window」的结论。
- 百万 token 窗口没有让 context rot 过时:Gemini 2.5 / Llama 4 等长窗口模型仍受 Chroma 验证的退化曲线约束——窗口大≠有效上下文大,2026 仍是 live 的踩坑点(atlan《LLM Context Window Limitations in 2026》口径一致)。
- 新进展(待跟踪):2026 年初出现 LOCA-bench(可控极端上下文增长基准)、AgentSwing(自适应并行上下文管理路由)、《Everything is Context: Agentic File System Abstraction》(arXiv 2512)——把「文件系统当上下文」推得更远,本质仍是 JIT。若 P3 要做长程 agent,应评估这些基准量化本项目的 effective context 边界。
- 过时认知警示:「窗口越大塞越多越好」「RAG 已被长上下文取代」均为 2024 旧论,2025-2026 实证(Chroma)已证伪——RAG/JIT 因 context rot 反而更不可替代。
- 下一步:Day 41 把单次 JIT 升级为自适应迭代检索(查询改写/多跳/防重复),接进
hybridSearch.ts。