如何设计 Agent 上下文管理系统:从踩坑到体系化的实践指南

如何设计 Agent 上下文管理系统:从踩坑到体系化的实践指南

做多 Agent 系统时,最先遇到的不是模型能力问题,而是上下文管理问题------聊几轮就"失忆"、工具结果撑爆窗口、每次新会话都要从头解释项目背景。本文从实际开发中遇到的问题出发,结合对 Claude Code、Codex CLI、OpenClaw 等开源项目的源码研究,总结出一套 Agent 上下文管理的设计方法论。

为什么上下文管理是 Agent 的核心基础设施

大多数团队对"上下文不够用"的反应是选更大窗口的模型或写更短的 Prompt。但实际做下来会发现,上下文管理不是提示词工程,而是运行时基础设施------它涉及缓存策略、压缩协议、记忆持久化、消息规范化、并发调度等多个工程子系统的协作。

我在做多 Agent 视频生产系统时,最初的架构只关注了 Agent 编排和工具链设计,上下文管理几乎是"能跑就行"。结果上线后遇到一系列问题:

  • ScriptAgent 产出的分镜表传到下游 ClipAgent 时,中间轮次的工具调用结果已经把窗口撑满了
  • 每次新会话都要重新注入项目的资产库结构、标签体系等背景信息
  • 多个 Agent 并行执行工具时,结果回灌的顺序不确定,导致上下文混乱

这些问题迫使我系统性地研究了业界方案。以下是提炼出的设计框架。


一、上下文组装:分层缓存比"一股脑塞进去"省一半钱

问题

每次 API 调用都要把系统指令、用户偏好、项目规范、对话历史、工具定义打包发送。这些信息的变化频率完全不同------系统指令几乎不变,用户偏好每人不同但会话内稳定,工具执行结果每轮都变。如果揉在一起发送,任何一处变化都会导致整段 Prompt Cache 失效。

设计方案:三路分离

核心思路是按变化频率拆分上下文,让每路独立缓存
三路上下文分离
System Prompt

(几乎不变)

全局共享缓存
User Context

(每用户不同,会话内稳定)

用户级缓存
Session Context

(每轮可能变化)

体量最小,不缓存
API 调用

实现要点:

  1. System Prompt 全局共享:工具定义、角色设定、输出格式约束放在这里。所有用户的首次调用都能命中同一份缓存。关键约束:这一层的内容一旦上线就不要频繁改动,任何改动都会导致全量缓存失效。
  2. User Context 用户级缓存:用户偏好、项目规范(比如 CLAUDE.md 这类文件)放在这里。同一用户的会话内保持稳定。
  3. Session Context 不缓存但控制体量:当前轮次的 git diff、工具执行结果等放在这里。因为每轮都变所以不缓存,但要严格控制体量。

成本收益:以 Anthropic 的 Prompt Cache 定价为例,缓存命中的 token 成本是未命中的 1/10。如果 System Prompt 占输入 token 的 60%(这在工具定义多的 Agent 中很常见),三路分离后仅这一项就能降低约 50% 的输入成本。

对比参考

  • Codex CLI 做了基线 diff(reference_context_item),避免每轮全量重发,但上下文整块发送,没有显式区分缓存边界
  • OpenClawContextEngine 接口可插拔性好,对 Anthropic 提供商会自动启用 Prompt Cache,但更像是副效果而非设计目标

踩坑提醒

System Prompt 的"稳定性"需要制度保障。建议在 Code Review 流程中加一条检查:这次改动是否影响了 System Prompt 的缓存稳定部分。可以用命名约定(如 STABLE_ 前缀)标记不应频繁修改的配置段。


二、上下文压缩:先轻后重,别上来就调 AI 做摘要

问题

长对话一定会撑爆上下文窗口。关键问题不是"要不要压缩",而是用多重的代价压缩。很多系统只有一档压缩------调 AI 生成摘要,既慢(额外一次 API 调用)又贵(摘要本身消耗 token),而且摘要质量不可控。

设计方案:四级渐进式压缩

从最轻到最重依次尝试,只有当前级别不够用时才升级到下一级:
不够用
不够用
不够用
Level 1

Snip 裁剪

直接删除最旧的消息
Level 2

Micro Compact

清空旧工具结果

但保留调用记录
Level 3

Context Collapse

折叠上下文视图

保留结构骨架
Level 4

Auto Compact

调 AI 生成摘要

信息保留最好但最贵

其中 ROI 最高的是 Level 2:Micro Compact。

它不是删消息,而是只清空旧的工具返回结果内容,但保留工具调用记录。效果是:模型仍然知道"我之前读过 config.yaml 这个文件",只是看不到具体内容了。如果后续需要,模型会主动再次调用工具读取。

这种"保留骨架、清空内容"的策略,在信息保留和空间回收之间取得了非常好的平衡。在我的实践中,仅这一步就能回收 40-60% 的窗口空间,因为工具返回结果(尤其是文件读取)往往是上下文中最大的部分。

Level 4 的熔断保护也很重要:如果 AI 摘要连续失败(比如 3 次),应该熔断而不是无限重试。Claude Code 的数据显示,不加熔断的情况下,异常会话可能触发数千次无效的摘要请求。

对比参考

  • Codex CLITruncationPolicy 控制工具输出截断,但只有"截断/不截断"两档,没有渐进中间态
  • OpenClaw 有一个值得学习的细节:摘要时的标识符保留策略------UUID、哈希、URL 在摘要过程中原样保留。这很实用,因为标识符一旦被摘要改写,后续引用就会断链

踩坑提醒

压缩触发的时机很关键。不要等到窗口真的满了才压缩------那时候已经来不及了(AI 摘要本身也需要上下文空间)。建议在窗口使用率达到 70-80% 时就开始 Level 1,留出足够的缓冲区。


三、记忆体系:让 Agent 不再是"每次都失忆的实习生"

问题

上下文窗口只管当前会话。会话结束或被压缩后,之前积累的知识就丢了。用户下次开会话,又要从头解释项目结构、编码规范、测试框架。没有记忆的 Agent 用一万次还是第一次。

设计方案:按生命周期分层的记忆体系

记忆生命周期(从短到长)
瞬时记忆

Tool Result

可被压缩回收
会话级记忆

Session Memory

自动提取结构化笔记
项目级记忆

Auto Memory

后台 Agent 提取持久知识
团队级记忆

Team Memory

多人共享,服务端同步
永久级记忆

配置文件(如 CLAUDE.md

用户手动维护,版本控制

优先实现的是 Session Memory,它的 ROI 最高:

每轮结束后,用轻量模型自动提取结构化笔记------当前在做什么、关键文件路径、遇到了什么错误。当对话被压缩时,这些笔记注入摘要中,确保关键信息不因压缩丢失。

这是记忆体系和压缩体系的交汇点------Session Memory 的存在让 Level 4 压缩的信息损失大幅降低。

Auto Memory(项目级记忆)的设计约束:

不是什么都值得记住。建议用封闭类型约束"什么该存":

  1. 用户角色:偏好、习惯
  2. 方法论偏好:测试框架选择、代码风格
  3. 进行中的工作:当前任务上下文
  4. 外部系统指针:文档链接、API 地址

一个重要原则:可从代码或 git 推导的信息不应保存 。比如"项目用了 React 18"------这从 package.json 就能看到,存到记忆里只会随版本升级而过时。

记忆的召回也需要设计:

不是把所有记忆都塞进上下文,而是按相关性选择:

  1. 配置文件全文始终注入(体量小、价值高)
  2. 编辑文件时沿目录树触发相关记忆
  3. 每轮用轻量模型做语义相关性筛选(比关键词匹配更准,能理解用户意图做跨领域关联)

对比参考

  • Codex CLI 在记忆维度几乎空白------只有 JSONL 消息历史和 AGENTS.md 项目指令,没有自动提取和跨会话知识管理
  • OpenClawmemory-lancedb 插件提供了向量搜索的召回路径,可插拔设计是正确方向,但缺少自动整合机制

踩坑提醒

记忆和压缩的交互需要特别注意:压缩清除旧附件后,记忆的去重状态应该重置,让同一份记忆文件可以在后续轮次被重新召回。否则会出现"记忆存在但永远不会被注入"的 bug。


四、消息规范化:模型比你想象的更在意消息格式

问题

从本地消息到 API 调用,中间的转换远不是 JSON.stringify 那么简单。Agent 的消息流里可能混着系统注入的元信息、孤立的 thinking 块、重复的 user 消息、无效的工具引用。模型对消息顺序和格式是敏感的------一条放错位置的系统消息,可能比写错一段 Prompt 更影响输出质量。

设计方案:标准化管线 + 统一附件协议

消息规范化不需要很复杂,但需要覆盖以下检查点:
原始消息流
去除孤立 thinking 块
合并连续同角色消息
修复工具调用/结果配对
确保首条是 user 消息
注入附件
token 预算检查
干净的 API 请求

附件系统是一个容易被忽视但很有价值的设计。把额外上下文(文件内容、记忆、状态信息、Hook 结果)通过统一协议注入当前轮次:
统一附件协议
文件附件

@提及 / IDE 选中
记忆附件

配置文件 / 语义召回
状态附件

Agent 列表变化 / 工具断连
Hook 附件

pre-commit 结果
统一注入接口
当前轮次完整上下文

好处是:任何子系统都可以通过同一接口向当前轮次补充上下文,而不是各自找一个"缝隙"塞信息。这让系统的可扩展性好得多。


五、工具执行并行:把延迟藏在模型输出的空隙里

问题

Agent 的一个典型轮次是:模型输出 → 执行工具 → 结果回灌 → 模型继续输出。如果工具串行执行,用户会感觉到明显的"卡顿-输出-卡顿-输出"节奏。

设计方案:按工具语义决定并发策略

关键判断:并发策略不由模型决定,而由工具语义决定。
流式并行时序
边输出边执行
边输出边执行
模型流式输出...
Read A ⬆
Grep B ⬆
等待 A,B 完成
Edit C(独占)
结果回灌
工具并发分类
只读工具

Read / Grep / Glob

可互相并行
修改型工具

Edit / Write / Bash

必须独占执行

更进一步,可以把 memory prefetch 和相关文件预加载藏在流式输出和工具执行的空隙里------不是每轮同步阻塞等待,而是利用空闲时间预取下一步大概率需要的信息。

感知延迟比绝对延迟更重要。 用户看到模型在打字时,工具已经在后台跑了------这种体验差距不是靠模型速度能弥补的。


六、关键参数调优:先埋点,后调参

上下文管理中有很多需要选择的参数:压缩触发阈值、输出 token 上限、续写次数限制等。

不要凭直觉定参数,要用数据。 建议至少埋以下四个点:

埋点 用途
压缩触发次数 / 会话 判断窗口大小是否匹配业务场景
压缩失败次数 决定熔断阈值
输出 token 分布(p50/p95/p99) 决定默认 Cap 和升级阈值
续写次数分布 决定最大续写次数和递减收益检测

输出 token 管理的两阶段策略:

  1. 默认 Cap 设低(如 8K):绝大多数请求在这个范围内完成,节省 slot 资源
  2. 截断后静默升级:如果 8K 不够,自动升级到更大限制(如 64K),对模型透明
  3. 递减收益检测:如果连续 3 次续写,每次增量不足 500 token,说明模型已经没有实质性内容了,停止续写

七、子系统协作:不是各做各的

前面讲的每一层都不是孤立的。真正形成体系的关键,是这些子系统之间的协作:
上下文生命周期
下一轮构建时

召回相关记忆
压缩后记忆去重重置

同一记忆可再次召回
Session Memory

注入压缩摘要
Memory prefetch

藏在工具执行空隙

  1. 构建

三路汇聚 + 附件注入
2. 执行

流式推理 + 工具并行
3. 压缩

四级渐进 + 边界标记
4. 持久化

五层记忆 + 语义召回

三条关键协作链路:

  1. 压缩后记忆重生:压缩清除旧附件后,记忆去重状态重置,同一份记忆可以再次被召回
  2. Session Memory 辅助压缩:压缩前获取结构化会话笔记,注入摘要,确保关键信息不因压缩丢失
  3. 预取藏在空隙里:Memory prefetch 叠在流式输出和工具执行期间执行,不阻塞主流程

总结:上下文管理的优先级路线图

如果你正在做自己的 Agent 系统,建议按以下优先级逐步建设:
P0:消息规范化

  • 三路缓存分离

(上线前必须有)
P1:Micro Compact

  • Snip 裁剪

(长对话必须有)
P2:Session Memory

  • 压缩联动

(体验质变)
P3:Auto Compact

  • 熔断保护

(极端场景兜底)
P4:完整记忆体系

  • 工具并行

(精细化运营)

核心观点:上下文管理不是"以后优化"的事。 消息格式、缓存结构、压缩协议一旦跑起来就很难改。模型会越来越强,但上下文运行时的工程质量决定了 Agent 能不能在长会话、多工具、复杂任务场景下稳定交付。

相关推荐
数据知道11 小时前
claw-code 源码分析:Turn Loop 里的工程细节——多轮对话如何在移植期保持可测试、可回放?
服务器·数据库·ai·claude code
HIT_Weston11 小时前
35、【Agent】【OpenCode】本地代理(脚本实现)
人工智能·agent·opencode
belldeep11 小时前
AI: llama.cpp 编译成功后,入门教程
python·ai·llama·llama-cpp
gao_tjie11 小时前
JetBrains IDE + Luma MCP:为您的项目生成 AI 视频
ai
数据知道11 小时前
claw-code 源码分析:不调用大模型也能练会话——`QueryEnginePort` 如何把状态机、停止条件与审计位摆对?
python·ai·claude code
数据知道11 小时前
claw-code 源码分析:Transcript / Session Store——智能体「运行史」数据结构怎样才算可运维?
运维·数据结构·python·ai·claude code
数据知道11 小时前
claw-code 源码分析:权限拒绝不是补丁——工具调用链上如何做 `PermissionDenial` 级设计才像成熟产品?
ai·web·claw code
Flandern111111 小时前
Go程序员学习AI大模型项目实战:从环境管理到核心架构抽象
人工智能·python·学习·ai·golang
Thomas.Sir11 小时前
第十二章:RAG知识库开发之【RAG的预检索和后检索:核心优化策略与实践】
人工智能·python·ai·rag·预检索·后检索