第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 Opus200K tokens150-180K(留 Buffer)
Claude 4.x Sonnet200K tokens150-180K
Claude 4.x Haiku200K tokens150-180K
GPT-4128K tokens100-120K

messages 数组增长

增长来源:

来源增长量示例
用户消息100-1000 tokens“帮我重构这个模块”
Model 响应500-2000 tokenstool_use + 分析内容
工具输出1000-10000 tokensRead 大文件、Bash 输出
System Prompt2000-5000 tokens工具定义、约束规则
CLAUDE.md1000-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 按需加载] end
graph TB
    subgraph Optimize["注入优化"]
        O1[Prompt Caching
缓存 System Prompt] O2[分段注入
只注入必要工具] O3[惰性加载
Skills 按需加载] end
graph TB
    subgraph Optimize["注入优化"]
        O1[Prompt Caching
缓存 System Prompt] O2[分段注入
只注入必要工具] O3[惰性加载
Skills 按需加载] end
graph 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 --> L4
graph TB
    subgraph Compression["四层压缩策略"]
        L1[Layer 1
滚动窗口
保留最近 N 条
成本: 低] L2[Layer 2
重要性过滤
保留关键决策
成本: 中] L3[Layer 3
摘要压缩
历史摘要为段落
成本: 高] L4[Layer 4
子 Agent 隔离
不泄露上下文
成本: 最高] end L1 --> L2 --> L3 --> L4
graph TB
    subgraph Compression["四层压缩策略"]
        L1[Layer 1
滚动窗口
保留最近 N 条
成本: 低] L2[Layer 2
重要性过滤
保留关键决策
成本: 中] L3[Layer 3
摘要压缩
历史摘要为段落
成本: 高] L4[Layer 4
子 Agent 隔离
不泄露上下文
成本: 最高] end L1 --> L2 --> L3 --> L4
graph 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 --> L4

Layer 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 tokens15K 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 tokens10K 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 数组messagesAmessagesB(独立)
上下文共享❌ 不共享❌ 不共享
历史泄露❌ 不泄露❌ 不泄露
结果返回✅ 只返回结果✅ 摘要或输出

实现:

# 子 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/>增强推理]

实现步骤:

  1. 列出 Skills 目录:System Prompt 包含 Skills 列表(只有名称和简要描述)
  2. Model 判断需要:Model 根据任务判断需要哪些 Skills
  3. 按需加载:读取需要的 Skills Markdown 文件
  4. 注入上下文:将 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[自动隔离]

触发阈值

ModelToken 阈值messages 长度阈值
Claude 4.x150K tokens50-100 条
GPT-4100K tokens30-50 条

上下文管理最佳实践

推荐策略

场景推荐策略说明
简单任务Layer 1 + 惰性加载滚动窗口足够
中等任务Layer 1 + Layer 2滚动窗口 + 重要性过滤
复杂任务Layer 1 + Layer 2 + 子 Agent滚动窗口 + 过滤 + 隔离
大型任务Layer 3 + 子 Agent摘要压缩 + 隔离

实现建议

优先级:

  1. 惰性加载:优先实现 Skills 按需加载
  2. 滚动窗口:Layer 1 基础实现
  3. 子 Agent 隔离:Layer 4 防止污染
  4. 重要性过滤:Layer 2 优化压缩
  5. 摘要压缩: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 --> P5
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 --> P5
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 --> P5
graph 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 的能力边界。



相关内容

如果这篇文章对你有帮助,欢迎请我喝杯咖啡 ☕
Cygnus Yang 支付宝支付宝
Cygnus Yang 微信微信