第13章 上下文管理 —— 自动注入、压缩与惰性加载
目录
上一篇我们剖析了权限系统的三级边界。本章我们看上下文管理——防止溢出、压缩历史、惰性加载。
上下文管理是什么
一句话定义:管理 messages 数组的增长——防止溢出、压缩历史、按需加载知识。
Context Management = messages 数组管理 定位:Harness 的管理层核心,防止上下文窗口溢出
图表
代码
graph LR
Messages[messages 数组
持续增长] -->|接近上限| Overflow[上下文溢出
超出窗口限制]
Overflow -->|触发| Compress[上下文压缩
释放空间]
Compress -->|继续| Loop[继续循环]graph LR
Messages[messages 数组
持续增长] -->|接近上限| Overflow[上下文溢出
超出窗口限制]
Overflow -->|触发| Compress[上下文压缩
释放空间]
Compress -->|继续| Loop[继续循环]graph LR
Messages[messages 数组
持续增长] -->|接近上限| Overflow[上下文溢出
超出窗口限制]
Overflow -->|触发| Compress[上下文压缩
释放空间]
Compress -->|继续| Loop[继续循环]graph LR
Messages[messages 数组<br/>持续增长] -->|接近上限| Overflow[上下文溢出<br/>超出窗口限制]
Overflow -->|触发| Compress[上下文压缩<br/>释放空间]
Compress -->|继续| Loop[继续循环]核心机制:
- 上下文注入:自动注入 System Prompt、CLAUDE.md
- 上下文压缩:四层压缩策略,防止溢出
- 惰性加载:Skills 按需加载,不前置塞入
- 子 Agent 隔离:子 Agent 上下文不污染主 Agent
上下文窗口限制
Model 上下文窗口
| Model | 上下文窗口 | 典型 messages 数组上限 |
|---|---|---|
| Claude 4.x Opus | 200K tokens | 150-180K(留 Buffer) |
| Claude 4.x Sonnet | 200K tokens | 150-180K |
| Claude 4.x Haiku | 200K tokens | 150-180K |
| GPT-4 | 128K tokens | 100-120K |
messages 数组增长
增长来源:
| 来源 | 增长量 | 示例 |
|---|---|---|
| 用户消息 | 100-1000 tokens | “帮我重构这个模块” |
| Model 响应 | 500-2000 tokens | tool_use + 分析内容 |
| 工具输出 | 1000-10000 tokens | Read 大文件、Bash 输出 |
| System Prompt | 2000-5000 tokens | 工具定义、约束规则 |
| CLAUDE.md | 1000-3000 tokens | 项目约定 |
增长计算:
messages 增长 = 用户消息 + Model 响应 + 工具输出 + System Prompt
示例:
10 次循环 × (500 + 1000 + 5000) = 65000 tokens
加上 System Prompt + CLAUDE.md = 70000 tokens上下文注入
自动注入内容
System Prompt 注入:
| 内容 | 注入时机 | Token 数量 |
|---|---|---|
| 工具定义 Schema | 每次循环 | 2000-5000 tokens |
| 约束规则 | 每次循环 | 500-1000 tokens |
| 格式要求 | 每次循环 | 200-500 tokens |
图表
代码
graph LR
System[System Prompt
工具定义 + 约束] -->|自动注入| Messages[messages 数组]
Messages -->|发送| Model[Model 推理]graph LR
System[System Prompt
工具定义 + 约束] -->|自动注入| Messages[messages 数组]
Messages -->|发送| Model[Model 推理]graph LR
System[System Prompt
工具定义 + 约束] -->|自动注入| Messages[messages 数组]
Messages -->|发送| Model[Model 推理]graph LR
System[System Prompt<br/>工具定义 + 约束] -->|自动注入| Messages[messages 数组]
Messages -->|发送| Model[Model 推理]CLAUDE.md 注入:
| 内容 | 注入时机 | Token 数量 |
|---|---|---|
| 项目架构 | 每次循环 | 500-1000 tokens |
| 编码约定 | 每次循环 | 200-500 tokens |
| 注意事项 | 每次循环 | 100-300 tokens |
注入位置:
# System Prompt + CLAUDE.md 注入
messages = [
{"role": "system", "content": system_prompt + claude_md},
{"role": "user", "content": user_input},
# ... 历史消息
]注入优化
问题:System Prompt 占用大量 tokens,每次循环重复发送。
优化方案:
| 优化方法 | 效果 | 代价 |
|---|---|---|
| Prompt Caching | 缓存 System Prompt | 需模型支持 |
| 分段注入 | 只注入必要工具定义 | 可能缺少工具 |
| 惰性加载 | Skills 按需加载 | 需要判断逻辑 |
图表
代码
graph TB
subgraph Optimize["注入优化"]
O1[Prompt Caching
缓存 System Prompt]
O2[分段注入
只注入必要工具]
O3[惰性加载
Skills 按需加载]
endgraph TB
subgraph Optimize["注入优化"]
O1[Prompt Caching
缓存 System Prompt]
O2[分段注入
只注入必要工具]
O3[惰性加载
Skills 按需加载]
endgraph TB
subgraph Optimize["注入优化"]
O1[Prompt Caching
缓存 System Prompt]
O2[分段注入
只注入必要工具]
O3[惰性加载
Skills 按需加载]
endgraph TB
subgraph Optimize["注入优化"]
O1[Prompt Caching<br/>缓存 System Prompt]
O2[分段注入<br/>只注入必要工具]
O3[惰性加载<br/>Skills 按需加载]
end上下文压缩
四层压缩策略
“上下文总会满, 要有办法腾地方” —— 四层压缩策略,便宜的先跑贵的后跑
图表
代码
graph TB
subgraph Compression["四层压缩策略"]
L1[Layer 1
滚动窗口
保留最近 N 条
成本: 低]
L2[Layer 2
重要性过滤
保留关键决策
成本: 中]
L3[Layer 3
摘要压缩
历史摘要为段落
成本: 高]
L4[Layer 4
子 Agent 隔离
不泄露上下文
成本: 最高]
end
L1 --> L2 --> L3 --> L4graph TB
subgraph Compression["四层压缩策略"]
L1[Layer 1
滚动窗口
保留最近 N 条
成本: 低]
L2[Layer 2
重要性过滤
保留关键决策
成本: 中]
L3[Layer 3
摘要压缩
历史摘要为段落
成本: 高]
L4[Layer 4
子 Agent 隔离
不泄露上下文
成本: 最高]
end
L1 --> L2 --> L3 --> L4graph TB
subgraph Compression["四层压缩策略"]
L1[Layer 1
滚动窗口
保留最近 N 条
成本: 低]
L2[Layer 2
重要性过滤
保留关键决策
成本: 中]
L3[Layer 3
摘要压缩
历史摘要为段落
成本: 高]
L4[Layer 4
子 Agent 隔离
不泄露上下文
成本: 最高]
end
L1 --> L2 --> L3 --> L4graph TB
subgraph Compression["四层压缩策略"]
L1[Layer 1<br/>滚动窗口<br/>保留最近 N 条<br/>成本: 低]
L2[Layer 2<br/>重要性过滤<br/>保留关键决策<br/>成本: 中]
L3[Layer 3<br/>摘要压缩<br/>历史摘要为段落<br/>成本: 高]
L4[Layer 4<br/>子 Agent 隔离<br/>不泄露上下文<br/>成本: 最高]
end
L1 --> L2 --> L3 --> L4Layer 1:滚动窗口
一句话定义:保留最近 N 条消息,丢弃旧消息。
实现:
# 滚动窗口实现
def rolling_window(messages, keep_last_n=20):
# 保留 System Prompt
system_prompt = messages[0] if messages[0]["role"] == "system" else None
# 保留最近 N 条消息
recent_messages = messages[-keep_last_n:]
# 构建新 messages
if system_prompt:
return [system_prompt] + recent_messages
else:
return recent_messages压缩效果:
| 压缩前 | 压缩后 | 效果 |
|---|---|---|
| 100 条消息 | 20 条消息 | 减少 80% |
优点:
- ✅ 实现简单
- ✅ 执行速度快
- ✅ 成本低
缺点:
- ❌ 可能丢失重要历史
- ❌ 无法保留关键决策
Layer 2:重要性过滤
一句话定义:保留关键决策和重要消息,丢弃次要消息。
判断标准:
| 重要性 | 判断标准 | 保留/丢弃 |
|---|---|---|
| 高 | Model 的关键决策(tool_use) | ✅ 保留 |
| 高 | 用户的重要指令 | ✅ 保留 |
| 中 | 工具执行结果(小输出) | ⚠️ 可保留 |
| 低 | 工具执行结果(大输出) | ❌ 丢弃或摘要 |
| 低 | Model 的中间推理 | ❌ 丢弃 |
实现:
# 重要性过滤实现
def importance_filter(messages):
filtered = []
for msg in messages:
# 保留 System Prompt
if msg["role"] == "system":
filtered.append(msg)
continue
# 保留用户消息
if msg["role"] == "user" and not is_tool_result(msg):
filtered.append(msg)
continue
# 保留 Model 的 tool_use 决策
if msg["role"] == "assistant" and has_tool_use(msg):
filtered.append(msg)
continue
# 工具结果:只保留摘要
if is_tool_result(msg):
# 大输出:摘要
if token_count(msg["content"]) > 1000:
filtered.append({
"role": "user",
"content": summarize(msg["content"])
})
else:
filtered.append(msg)
return filtered压缩效果:
| 压缩前 | 压缩后 | 效果 |
|---|---|---|
| 50K tokens | 15K tokens | 减少 70% |
Layer 3:摘要压缩
一句话定义:将历史消息摘要为摘要段落,大幅压缩空间。
摘要格式:
## 历史摘要
### 任务进展
- 已读取文件:/src/app.ts、/src/utils.ts
- 已修改文件:/src/app.ts(添加了函数 validate)
- 已执行测试:npm test(通过)
### 关键决策
1. 决定使用 Glob 搜索所有 .ts 文件
2. 决定先读取 app.ts 理解主结构
3. 决定添加 validate 函数验证输入
### 遇到的问题
- Edit 时 old_string 不唯一(已修复)
### 当前状态
- 任务进行中,需要继续添加测试实现:
# 摘要压缩实现
def summarize_messages(messages):
# 提取关键信息
files_read = extract_files_read(messages)
files_modified = extract_files_modified(messages)
decisions = extract_decisions(messages)
problems = extract_problems(messages)
current_state = extract_current_state(messages)
# 构建摘要
summary = f"""
## 历史摘要
### 任务进展
- 已读取文件:{files_read}
- 已修改文件:{files_modified}
- 已执行测试:已执行
### 关键决策
{decisions}
### 遇到的问题
{problems}
### 当前状态
{current_state}
"""
# 替换历史消息为摘要
return [
messages[0], # System Prompt
{"role": "user", "content": summary},
messages[-1] # 最新消息
]压缩效果:
| 压缩前 | 压缩后 | 效果 |
|---|---|---|
| 100K tokens | 10K tokens | 减少 90% |
优点:
- ✅ 压缩效果最好
- ✅ 保留关键信息
缺点:
- ❌ 执行成本高(需要 Model 摘要)
- ❌ 可能丢失细节
Layer 4:子 Agent 隔离
一句话定义:子 Agent 使用独立上下文,不污染主 Agent。
“大任务拆小, 每个小任务干净的上下文” —— 子 Agent 自己干活,只把结果带回来
图表
代码
graph TB
Main[主 Agent
messages 数组A
主上下文] -->|派发
Agent(prompt)| Sub[子 Agent
messages 数组B
隔离上下文]
Sub -->|执行
独立上下文| Task[完成任务]
Task -->|返回结果
不泄露历史| Result[只返回结果
摘要或输出]
Result -->|追加| Main
Sub[上下文隔离
不污染主 Agent]graph TB
Main[主 Agent
messages 数组A
主上下文] -->|派发
Agent(prompt)| Sub[子 Agent
messages 数组B
隔离上下文]
Sub -->|执行
独立上下文| Task[完成任务]
Task -->|返回结果
不泄露历史| Result[只返回结果
摘要或输出]
Result -->|追加| Main
Sub[上下文隔离
不污染主 Agent]graph TB
Main[主 Agent
messages 数组A
主上下文] -->|派发
Agent(prompt)| Sub[子 Agent
messages 数组B
隔离上下文]
Sub -->|执行
独立上下文| Task[完成任务]
Task -->|返回结果
不泄露历史| Result[只返回结果
摘要或输出]
Result -->|追加| Main
Sub[上下文隔离
不污染主 Agent]graph TB
Main[主 Agent<br/>messages 数组A<br/>主上下文] -->|派发<br/>Agent(prompt)| Sub[子 Agent<br/>messages 数组B<br/>隔离上下文]
Sub -->|执行<br/>独立上下文| Task[完成任务]
Task -->|返回结果<br/>不泄露历史| Result[只返回结果<br/>摘要或输出]
Result -->|追加| Main
Sub[上下文隔离<br/>不污染主 Agent]隔离机制:
| 维度 | 主 Agent | 子 Agent |
|---|---|---|
| messages 数组 | messagesA | messagesB(独立) |
| 上下文共享 | ❌ 不共享 | ❌ 不共享 |
| 历史泄露 | ❌ 不泄露 | ❌ 不泄露 |
| 结果返回 | ✅ 只返回结果 | ✅ 摘要或输出 |
实现:
# 子 Agent 隔离实现
def spawn_subagent(prompt, subagent_type):
# 子 Agent 使用独立 messages 数组
sub_messages = [
{"role": "system", "content": subagent_system_prompt},
{"role": "user", "content": prompt}
]
# 子 Agent 执行
sub_response = run_agent_loop(sub_messages)
# 只返回结果(不泄露历史)
return sub_response.content # 只返回最终结果压缩效果:
| 效果 | 说明 |
|---|---|
| 防止污染 | 子 Agent 上下文不泄露 |
| 保持干净 | 主 Agent 上下文不受影响 |
| 自动隔离 | 派发子 Agent 自动隔离 |
惰性加载
Skills 按需加载
“用到时再加载, 别全塞 prompt 里” —— 技能先列目录,用到时再展开
设计原理:
图表
代码
graph LR
SkillsList[Skills 列表
只有目录信息] -->|Model 判断需要| Load[加载 Skills
读取 Markdown]
Load -->|注入| Context[上下文
增强推理]graph LR
SkillsList[Skills 列表
只有目录信息] -->|Model 判断需要| Load[加载 Skills
读取 Markdown]
Load -->|注入| Context[上下文
增强推理]graph LR
SkillsList[Skills 列表
只有目录信息] -->|Model 判断需要| Load[加载 Skills
读取 Markdown]
Load -->|注入| Context[上下文
增强推理]graph LR
SkillsList[Skills 列表<br/>只有目录信息] -->|Model 判断需要| Load[加载 Skills<br/>读取 Markdown]
Load -->|注入| Context[上下文<br/>增强推理]实现步骤:
- 列出 Skills 目录:System Prompt 包含 Skills 列表(只有名称和简要描述)
- Model 判断需要:Model 根据任务判断需要哪些 Skills
- 按需加载:读取需要的 Skills Markdown 文件
- 注入上下文:将 Skills 内容注入 messages 数组
# Skills 按需加载实现
def list_skills():
# Skills 目录(简要描述)
return """
可用 Skills:
- python-patterns: Python 编程模式(用到 Python 时加载)
- testing-guide: 测试指南(写测试时加载)
- api-design: API 设计规范(设计 API 时加载)
"""
def load_skill(skill_name):
# 读取 Skills Markdown 文件
skill_path = f"skills/{skill_name}.md"
return read_file(skill_path)
# Model 判断需要后加载
if need_skill("testing-guide"):
skill_content = load_skill("testing-guide")
messages.append({"role": "user", "content": skill_content})压缩效果:
| 压缩前 | 压缩后 | 效果 |
|---|---|---|
| 全部 Skills(20K tokens) | 按需加载(2K tokens) | 减少 90% |
上下文压缩触发时机
触发条件
| 触发时机 | 检测方法 | 触发动作 |
|---|---|---|
| Token 计数接近上限 | Token counter | 自动触发压缩 |
| messages 数组过长 | Length threshold | 自动触发压缩 |
| 用户手动触发 | /compact 命令 | 手动触发压缩 |
| 子 Agent 派发时 | 自动隔离 | 自动隔离上下文 |
图表
代码
graph LR
Messages[messages 数组] --> Check{检查条件}
Check -->|Token > 阈值| Trigger[触发压缩]
Check -->|长度 > 阈值| Trigger
Check -->|用户命令| Trigger
Check -->|派发子 Agent| Isolate[自动隔离]graph LR
Messages[messages 数组] --> Check{检查条件}
Check -->|Token > 阈值| Trigger[触发压缩]
Check -->|长度 > 阈值| Trigger
Check -->|用户命令| Trigger
Check -->|派发子 Agent| Isolate[自动隔离]graph LR
Messages[messages 数组] --> Check{检查条件}
Check -->|Token > 阈值| Trigger[触发压缩]
Check -->|长度 > 阈值| Trigger
Check -->|用户命令| Trigger
Check -->|派发子 Agent| Isolate[自动隔离]graph LR
Messages[messages 数组] --> Check{检查条件}
Check -->|Token > 阈值| Trigger[触发压缩]
Check -->|长度 > 阈值| Trigger
Check -->|用户命令| Trigger
Check -->|派发子 Agent| Isolate[自动隔离]触发阈值
| Model | Token 阈值 | messages 长度阈值 |
|---|---|---|
| Claude 4.x | 150K tokens | 50-100 条 |
| GPT-4 | 100K tokens | 30-50 条 |
上下文管理最佳实践
推荐策略
| 场景 | 推荐策略 | 说明 |
|---|---|---|
| 简单任务 | Layer 1 + 惰性加载 | 滚动窗口足够 |
| 中等任务 | Layer 1 + Layer 2 | 滚动窗口 + 重要性过滤 |
| 复杂任务 | Layer 1 + Layer 2 + 子 Agent | 滚动窗口 + 过滤 + 隔离 |
| 大型任务 | Layer 3 + 子 Agent | 摘要压缩 + 隔离 |
实现建议
优先级:
- 惰性加载:优先实现 Skills 按需加载
- 滚动窗口:Layer 1 基础实现
- 子 Agent 隔离:Layer 4 防止污染
- 重要性过滤:Layer 2 优化压缩
- 摘要压缩:Layer 3 最后实现(成本高)
图表
代码
graph TB
subgraph Priority["实现优先级"]
P1[1. 惰性加载
Skills 按需加载]
P2[2. 滚动窗口
Layer 1 基础]
P3[3. 子 Agent 隔离
防止污染]
P4[4. 重要性过滤
Layer 2 优化]
P5[5. 摘要压缩
Layer 3 高成本]
end
P1 --> P2 --> P3 --> P4 --> P5graph TB
subgraph Priority["实现优先级"]
P1[1. 惰性加载
Skills 按需加载]
P2[2. 滚动窗口
Layer 1 基础]
P3[3. 子 Agent 隔离
防止污染]
P4[4. 重要性过滤
Layer 2 优化]
P5[5. 摘要压缩
Layer 3 高成本]
end
P1 --> P2 --> P3 --> P4 --> P5graph TB
subgraph Priority["实现优先级"]
P1[1. 惰性加载
Skills 按需加载]
P2[2. 滚动窗口
Layer 1 基础]
P3[3. 子 Agent 隔离
防止污染]
P4[4. 重要性过滤
Layer 2 优化]
P5[5. 摘要压缩
Layer 3 高成本]
end
P1 --> P2 --> P3 --> P4 --> P5graph TB
subgraph Priority["实现优先级"]
P1[1. 惰性加载<br/>Skills 按需加载]
P2[2. 滚动窗口<br/>Layer 1 基础]
P3[3. 子 Agent 隔离<br/>防止污染]
P4[4. 重要性过滤<br/>Layer 2 优化]
P5[5. 摘要压缩<br/>Layer 3 高成本]
end
P1 --> P2 --> P3 --> P4 --> P5本章小结
- 上下文管理定义:管理 messages 数组增长,防止溢出
- 上下文注入:System Prompt + CLAUDE.md 自动注入
- 四层压缩策略:滚动窗口(Layer 1)→ 重要性过滤(Layer 2)→ 摘要压缩(Layer 3)→ 子 Agent 隔离(Layer 4)
- 滚动窗口:保留最近 N 条消息,丢弃旧消息
- 重要性过滤:保留关键决策,丢弃次要消息
- 摘要压缩:历史摘要为段落,减少 90%
- 子 Agent 隔离:独立上下文,不污染主 Agent
- 惰性加载:Skills 按需加载,不前置塞入
- 触发时机:Token 阈值、长度阈值、用户命令、子 Agent 派发
- 最佳实践:惰性加载 → 滚动窗口 → 子 Agent 隔离 → 重要性过滤 → 摘要压缩
预告:下一篇我们深入 MCP 集成——扩展 Harness 的能力边界。
相关内容
- 第01章 Agent 的 70 年简史 —— 从符号推理到深度学习
- 第02章 LLM 时代的 Agent 革命 —— 2023-2026 爆发期
- 第03章 Harness 概念溯源 —— "模型是大脑,Harness是身体"
- 第04章 Harness 核心架构 —— 四层模型详解
- 第05章 Harness vs Agent vs Model —— 三者关系辨析
如果这篇文章对你有帮助,欢迎请我喝杯咖啡 ☕
支付宝
微信