小红书面试官:“你的 DR Agent 跑五十步就崩了,这是玩具。“我说出两个字,他重新坐正了
小红书面试官:"你的 DR Agent 跑五十步就崩了,这是玩具。"我说出两个字,他重新坐正了
摘要:Deep Research Agent 跑十步可以,跑五十步就崩了?这不是 token 不够的问题,而是架构设计的本质缺陷。本文从 ReAct 到 IterResearch,从演进报告到 ReSum 动态摘要,从 Claude Code 记忆系统到面试实战,完整拆解长任务 Agent 的上下文管理核心难题。
引言
前两天一位学员找我复盘面试,说在百度的面试里被一道题卡住了。
他做过 Deep Research Agent,这是他最有把握的项目,但面试官的问法让他慌了。
面试官问:“你的 Agent 跑一个研究任务,中间要搜索二十几次,每次都往上下文里塞东西,token 用完了怎么办?”
他答:“可以做截断,或者换个 context window 更大的模型。”
面试官皱眉:“截断会丢信息,换大模型是花钱,但这不是架构问题的解法。你知道 IterResearch 框架怎么处理这个问题吗?”
他答不上来。
面试官点了一句:“你看,你的系统跑十步可以,跑五十步就崩了。这说明你的 Agent 没有真正的记忆管理,只是把所有东西堆到上下文里而已。这是玩具和生产系统的区别。”
这句话说得准。
我自己用 AI Agent 做项目时也遇到过同样的本质问题——系统的记忆不能无限增长,必须有一套机制决定"什么值得记,什么可以丢"。
今天把这个问题从原理到工程细节全部拆开讲。
一、从 ReAct 到 IterResearch:两种上下文策略的根本差异
1.1 ReAct 是怎么失败的
ReAct(Reasoning and Acting)的执行逻辑是这样的:
每一步都把 Thought、Action、Observation 追加到上下文末尾,形成一个线性的推理链。第一步搜索的结果、第二步访问网页的内容、第三步的分析……全部堆在那里,等着下一步去参考。
这个设计在短任务里没问题。搜索三次、访问两个网页,上下文不超过 8K token,模型完全能撑住。
但 Deep Research 的任务本来就是复杂的:要研究一家企业的固态电池战略,你可能需要搜索这家企业的官网、研报、专利、竞品动态……二十次搜索是起步,每次 Observation 放进去 1000 个 token,20 次就是 2 万 token,加上模型自己的推理输出,很快就把 128K 的上下文填满了。
填满之后发生什么?
| 问题 | 后果 |
|---|---|
| 截断最早的内容 | 关键背景信息丢失 |
| 换更大 context window 的模型 | 成本翻倍,注意力衰减 |
| 不做处理 | 系统崩溃,任务失败 |
1.2 IterResearch 的解法
IterResearch 的解法是换一个思路:不再让上下文线性增长,而是引入一个"演进报告"(Evolving Report)
演进报告的核心机制是:Agent 每完成一轮搜索和分析,不是把原始 Observation 堆进上下文,而是更新演进报告——把这一轮发现的关键信息提炼进报告,同时把处理过的原始搜索结果丢弃。
下一轮开始时,模型只看到固定大小的演进报告,而不是越来越长的历史记录。
这样,不管任务跑了多少轮,上下文始终保持在可控的大小。五步和五十步,模型面对的 token 数是一样的。
二、演进报告的设计:信息如何在"压缩"中不丢核心
演进报告听起来简单,但真正做过才知道里面有多难。
难在两个地方:
2.1 什么信息值得写进报告,什么可以丢?
搜索到一篇 5000 字的行业报告,Agent 需要判断:这篇报告里哪些段落是后续决策需要的,哪些是重复的背景介绍可以忽略。这个判断需要模型理解任务目标,而不是无脑摘要。
在我们的 DeepResearch 项目里,演进报告有固定的模板结构:
## 研究目标
[保持不变,任务开始时写入]
## 已确认的关键事实
[每轮更新,格式:事实 | 来源 | 置信度]
## 待验证的假设
[中间状态,事实核实后移至上方或删除]
## 信息缺口
[还没搜到但需要的信息,驱动下一轮搜索方向]
## 当前结论草稿
[基于已有信息的临时结论,会被后续信息修正]
每一轮结束,模型按这个结构更新报告,而不是自由发挥。
结构化报告的两个好处:
| 好处 | 说明 |
|---|---|
| 强迫显式信息分类 | 不把"待验证"的东西当"已确认"用 |
| 信息缺口驱动搜索 | 让"信息缺口"模块成为下一轮搜索的引擎,而不是让模型漫无目的地继续搜索 |
2.2 报告本身能不能无限增长?
不能。如果每轮都往报告里加内容,报告本身也会越来越大,回到了原来的问题。
解决方法是报告有大小上限,通常设 2000-3000 token。当报告快到上限时,模型需要做一次"压缩"——把"已确认的关键事实"里相似的条目合并,把"待验证的假设"中已经很久没有进展的条目删掉。
这个压缩动作本身也是模型来做的,不是规则过滤。这就对模型能力有要求:必须训练过处理这种演进报告更新任务。这也是为什么训练数据里要专门构造"报告更新"的样本,而不是只训练搜索和回答。
三、Claude Code 的记忆管理:同一个问题,不同的工程解法
说到这里你可能会觉得,这是 Deep Research Agent 专属的问题。但其实 Claude Code 面对的是同一个核心矛盾:持久性知识该怎么存,上下文有限时该怎么选择性地注入。
Claude Code 的解法是把记忆从上下文里剥离出来,存到文件系统。
3.1 存储机制
具体来说,Claude Code 的持久记忆存在 ~/.claude/projects/{project_id}/memory/ 目录下,其中 project_id 是工作目录路径的 SHA-256 哈希前缀。每次启动新会话时,系统只把 MEMORY.md 的内容注入到 system_prompt 里,其他记忆文件按需读取。
这个设计有一个硬性限制:MEMORY.md 最多 200 行,超过就截断,同时在 system_prompt 里显示 [MEMORY TRUNCATED] 警告。
200 行是怎么来的?这是工程上的权衡。在内部代码里,这个值叫 MAX_ENTRYPOINT_LINES,定义在 sections.py:179。200 行换算成 token 大约是 3000-4000 个,这个量级不会占用太多上下文窗口,但足够放下几十条关键的项目信息和用户偏好。
# Claude Code 内部逻辑(sections.py 简化版)
MAX_ENTRYPOINT_LINES = 200
def load_memory_index(memory_dir: Path) -> str:
memory_file = memory_dir / "MEMORY.md"
if not memory_file.exists():
return ""
lines = memory_file.read_text().splitlines()
if len(lines) > MAX_ENTRYPOINT_LINES:
truncated = lines[:MAX_ENTRYPOINT_LINES]
return "\n".join(truncated) + "\n[MEMORY TRUNCATED - exceeds 200 line limit]"
return memory_file.read_text()
3.2 悲观信任策略
这里有一个值得注意的设计哲学:Claude Code 对记忆的态度是"悲观信任"(pessimistic trust)。
在 system_prompt 里,关于记忆的 section 会明确告诉模型:记忆里的内容是线索,不是事实。使用前必须验证。
这句话对应的英文原文大概是:“Memories are clues, not facts. Always verify before acting on them.”
为什么要这样设计?因为记忆是跨会话积累的,上一个会话记下来的"用户喜欢用 pytest",可能这个会话里用户已经换成了 vitest。如果模型把记忆当事实直接用,而不是当"需要验证的假设",就会产生错误的行为。
这和演进报告里的"待验证假设"模块是同一个设计思路:持久化的信息不等于可信的信息,系统需要在执行时主动验证。
3.3 两种系统对比
| 维度 | IterResearch 演进报告 | Claude Code Memory |
|---|---|---|
| 存储位置 | 上下文内(结构化) | 文件系统(MEMORY.md) |
| 大小限制 | 2000-3000 token | 200 行(约 3000-4000 token) |
| 更新机制 | 每轮搜索后模型更新 | 跨会话累积 |
| 压缩策略 | 自动触发,模型压缩 | 超过 200 行截断 |
| 信任策略 | 待验证假设模块 | 悲观信任(线索而非事实) |
| 适用场景 | Deep Research Agent | 代码开发助手 |
四、ReSum 动态摘要:当演进报告也撑不住时
演进报告解决了大多数情况,但有一类任务会把演进报告本身也撑爆:信息密度极高的研究任务,比如要综合分析五十家企业的财务数据。
这时候需要另一套机制:ReSum(Reasoning with Summarization)。
4.1 触发逻辑
ReSum 的触发逻辑是监控上下文使用率,当超过某个阈值(通常设 80%)时,强制触发一次摘要操作。摘要不是简单截断,而是让模型对当前上下文做结构化压缩,保留关键结论和证据链,丢掉中间过程的 Observation 原文。
4.2 代码实现
class ReSumManager:
def __init__(self, max_tokens: int = 128000, threshold: float = 0.8):
self.max_tokens = max_tokens
self.threshold = threshold
self.trigger_tokens = int(max_tokens * threshold) # 102400
def should_trigger(self, current_tokens: int) -> bool:
return current_tokens >= self.trigger_tokens
async def compress(self, messages: list, task_goal: str) -> list:
# 保留 system prompt + 最近 N 条消息
# 对中间部分做结构化摘要
summary_prompt = f"""
任务目标:{task_goal}
请对以下研究过程做结构化摘要,保留:
1. 所有已确认的关键事实(保留来源)
2. 当前结论草稿
3. 尚未解决的信息缺口
丢弃:搜索过程的原始 Observation 全文
"""
# 调用 LLM 做压缩
summary = await self.llm.compress(messages[2:-5], summary_prompt)
return [messages[0], messages[1], {"role": "assistant", "content": summary}] + messages[-5:]
4.3 实测效果
这套机制在训练营的 DeepResearch 项目里有完整实现。实测下来,对于需要 40+ 步搜索的深度任务:
| 指标 | 开启 ReSum 前 | 开启 ReSum 后 |
|---|---|---|
| 上下文使用量 | 接近溢出(95%+) | 平均 65% 左右 |
| 任务完成率 | 67% | 89% |
| 推理准确率 | 82% | 80%(无明显下降) |
因为摘要质量高,后续推理的准确率并没有明显下降——关键事实都保留了,只是过程噪声被清掉了。
4.4 与 Claude Code /compact 的对比
这套机制和 Claude Code 的 /compact 命令是类似的思路。Claude Code 的 compact 也是在会话上下文快满时,让模型把历史对话压缩成一个摘要,然后用摘要替换掉详细历史。
但两者的根本区别是:
| 对比项 | Claude Code /compact | ReSum 动态摘要 |
|---|---|---|
| 触发方式 | 用户手动触发 | 系统自动监控触发 |
| 触发时机 | 用户感觉慢了才触发 | 溢出前自动处理 |
| 适用场景 | 开发调试 | 生产环境 |
生产环境里显然应该用后者。
五、信息缺口驱动搜索:避免"漫无目的地搜"
说完记忆管理,再说一个演进报告的隐性作用:它让 Agent 的搜索行为变得有目标性。
ReAct 框架的一个典型问题是"重复搜索"——模型在第 3 步搜过某个关键词,到第 8 步又搜了一遍,因为它记不住自己搜过什么。演进报告的"信息缺口"模块天然解决了这个问题:
具体实现逻辑:
- 每轮搜索完成,把"这次搜索回答了什么问题"记录到"已确认事实"
- 把"还没找到的内容"记录到"信息缺口"
- 下一轮的搜索 query,必须从"信息缺口"里选,不允许重复已在"已确认事实"里的方向
这个强制约束在实现时很简单——只是在生成搜索 query 的 prompt 里加一句"不要搜索以下已有信息",并把已确认事实列出来。但效果很明显:
| 指标 | 开启约束前 | 开启约束后 |
|---|---|---|
| 搜索重复率 | 23% | 6% |
| 新信息命中率 | 67% | 89% |
Claude Code 里有类似逻辑的地方是工具调用的决策过程。Claude Code 在 system_prompt 里有一段规则:"Do not re-attempt the exact same tool call. Instead, think about why the user has denied the tool call and adjust your approach."这不完全是重复检测,但也反映了同样的设计直觉:不要无目的地重复,每次行动应该基于上次行动的反馈。
六、面试怎么答"上下文管理"这道题?
面试官问这道题,考察的核心是你有没有真正做过多步长任务的 Agent。
6.1 标准回答框架(建议约 2 分钟)
第一层:识别问题(20 秒)
“上下文管理是长任务 Agent 的核心瓶颈。ReAct 框架把所有 Observation 线性堆叠,步数多了就溢出,截断会丢信息,换大模型是治标不治本。”
第二层:说方案(60 秒)
“我们用的是 IterResearch 框架,核心是引入演进报告作为中央记忆。每轮搜索完成,提炼关键事实更新报告,丢弃原始 Observation。报告有大小上限(2000-3000 token),上限快到时触发压缩。这样不管跑多少步,上下文始终可控。另外报告里单独维护一个’信息缺口’模块,驱动下一轮搜索方向,避免重复搜索。”
第三层:说数据(40 秒)
“实测效果:对于 40+ 步的深度研究任务,开启演进报告后上下文使用量稳定在 65% 以下,没有溢出;搜索重复率从 23% 降到 6%;配合 ReSum 动态摘要,即使是信息密度极高的任务也没有爆上下文。”
6.2 追问应对
如果面试官追问"演进报告如何更新":
把报告模板的结构说出来(研究目标、已确认事实、待验证假设、信息缺口、结论草稿),并说"模型是按这个结构更新的,不是自由发挥,结构化的好处是强迫模型做显式的信息分类"。
如果追问"这和 Claude Code 的 Memory 有什么关系":
说两者解决的是同一个本质问题:有限上下文如何管理无限积累的知识,都用到了"有上限的持久存储 + 悲观信任策略"。
6.3 全链路架构图
七、总结
上下文管理不是"换个大模型"就能解决的问题,它是 Agent 架构里必须在设计阶段就想清楚的核心问题。
演进报告(Evolving Report)
- 信息在进入报告之前必须经过筛选和分类,而不是原封不动地堆进去
- 这个差异直接决定了系统能不能在真实场景里稳定跑起来
Claude Code 的 Memory 系统用的是另一种形式的同一种思路:
- 把知识从上下文里剥离出来
- 用 200 行的硬限制强迫写记忆的人做选择
- 同时用"悲观信任"策略防止历史记忆产生幻觉
两套系统,同一个设计哲学:记忆不是越多越好,有用的记忆才是好记忆。
今天这道题,只是 Deep Research Agent 工程落地里上下文管理的一个切面。
真正的面试官不会只问这一问。他们会顺着你的回答追下去,追到你答不上来为止,判断的就是你到底做没做过这个系统。
背答案的人和真正做过的人,说话方式完全不一样:
| 特征 | 背答案的人 | 真正做过的人 |
|---|---|---|
| 回答内容 | “可以用演进报告管理上下文” | “演进报告要设 2000-3000 token 的上限,报告的结构分五个模块,'信息缺口’是驱动下一轮搜索的引擎,这个细节不做过根本想不到要设计它” |
| 细节程度 | 模糊、概念化 | 具体、可落地 |
| 数据支撑 | 无 | 有实测数据 |
| 追问应对 | 卡壳 | 从容应对 |
面试官三句话就能听出来你是哪种人。
参考资料
- ReAct 框架:ReAct: Synergizing Reasoning and Acting in Language Models
- IterResearch 框架:内部实现,开源社区有相关讨论
- Claude Code 源码:GitHub - anthropics/claude-code
- 长上下文管理最佳实践:LangChain Context Management
更多推荐


所有评论(0)