从一到无穷大 #82:Agent Memory Eval 中的 Harness 选择

前面的博客里,我提出 Agent Memory 系统在发版前要先做离线评估。把一次评估写成最简单的公式:

text 复制代码
Score = f(Memory, Harness, Model, Task, Environment)

前文已经固定了 Memory、Model、Task 和 Environment。这一篇只看剩下的 Harness 变量:不同 Harness 在哪里调用 Memory,以及怎样选择,才能尽量减少它对 Memory 评估结果的干扰。

1 Agent Memory 调用链与 Harness 介入点

1.1 Agent lifecycle 与读取调用点

带 Tool Loop 的 Agent 在一个用户请求内可能多次调用模型。第一次模型响应如果要求执行 Tool,Harness 会运行 Tool,把结果加入状态,再发起下一次模型请求,直到模型给出最终答案。把这段循环展开,大致是下面这样:

text 复制代码
Session bootstrap
  → Turn admission
  → Pre-model step
  → 模型响应
      ├─ 返回最终答案 → Turn Stop
      └─ 请求 Tool → Tool 执行 → Post-tool observation
                                  → Pre-model step → 模型响应 ...
  → 下一 Turn
  → Session End

条件分支:
  step → Subagent / handoff
  step → Compact / resume

Session 覆盖一次完整任务或会话,其中可以包含多个 Turn。Turn 从一条用户 Prompt 开始,到 Agent 返回一次最终答案结束。Step 则对应一次模型请求;只要中间发生 Tool 调用,一个 Turn 就会有多个 Step。

Hook 是 Harness 在这些状态转换处暴露的扩展点。PostToolUse 属于 Tool 返回后的 lifecycle 事件,pre-model middleware 拦截每一次模型请求,MCP 则把 Memory 暴露成 Agent Loop 内可调用的 Tool。三者都可能让模型看到同一条 Memory,调用频率和决策者却不同,这正是下面需要对齐的变量。

沿着这条 lifecycle,对离线评估,我认为五个核心边界加两个条件边界已经够用:

生命周期边界 触发频率 Memory 在这里做什么 额外进入分数的 Harness 能力
Session bootstrap 每个新建或恢复的 Session 一次 加载项目规则、全局 Memory、上一轮摘要或索引 启动时延、陈旧内容、首个请求是否等待 Hook
Turn admission 每条用户 Prompt 一次 用当前 Prompt 构造 query,检索后加入本轮首个 model request Prompt 解析、超时策略、首轮注入 schema
Pre-model step 每次模型请求前一次,包括 Tool continuation 根据当前完整状态重新检索、去重并组装 model input 调用频率、Top-K、token budget、重复注入控制
Agent Loop 内的 Memory Tool 由模型按需调用 通过 MCP、普通 Tool 或文件执行 searchgetadd 是否触发、query generation、Tool choice 与错误恢复
Post-tool observation 每次 Tool 或每个 Tool batch 后 用错误栈、文件路径和命令输出补查 Memory,结果在下一次 model request 可见 事件选择、并行 Tool 聚合、延迟一个 step 的可见性
Subagent / handoff(条件边界) 每次 Agent 切换或派生时 决定向子 Agent 传哪些 namespace、摘要与任务相关 Memory Agent 间隔离、继承策略与跨 Agent 泄漏
Compact / resume(条件边界) 上下文压缩或恢复时 在旧上下文丢失前保存状态,在新上下文组装后重新注入 摘要损失、异步一致性与恢复时点

这七个边界不是七次顺序调用。Agent Loop 会在 pre-model、Memory Tool 和 post-tool 之间循环;短单 Agent Benchmark 只需要前五项,多 Agent 任务再打开 handoff,长上下文任务再打开 compact/resume。Session 或 Turn 级注入主要测检索与消费;MCP 模式还测模型能否意识到需要历史信息;pre-model step 再叠加一次频率策略。调用点本身已经是被测系统的一部分。

1.2 Memory 构造方式与可见时间

这里把 Memory 构造定义为从原始 message、Tool Result 或任务事件中生成候选 Memory。

候选 Memory 可以在三个不同位置构造。

  1. 模型主动调用 memory_add 模型在当前 Step 判断一段信息值得保留,生成 memory_add(content, scope, ...) 的 Tool 参数。写入意图和候选内容在 Tool call 时形成,Memory 服务随后完成规范化、去重与持久化。模型没有调用,Memory 就不会产生;这条路径把是否值得记、写什么以及 Tool-use 能力一起放进评估结果。

  2. Harness 根据 lifecycle 触发。 Harness 在 User Prompt、Tool 返回、Turn Stop、PreCompact 或 Session End 等边界调用 writer,把当时的 message 和状态交给抽取器。模型不负责决定是否写入,构造时点由 Hook 配置决定。Writer 可以阻塞当前流程,也可以由 Hook 启动异步任务;异步只改变完成时间,触发来源仍然是对应的 lifecycle 事件。

  3. 独立的 message 抽取服务。 Writer 消费已经落盘的 transcript 或 message stream,在 Agent 运行之外构造 Memory,再向 Harness 提供冻结 snapshot 或只读的 search/get 接口。此时 Harness 只负责读取,Memory 构造发生在 message 提交之后、下一项任务读取之前。它可以实时消费,也可以批量处理,边界由 message offset、watermark 和 snapshot version 决定,不再依赖某个 Agent Hook。

Harness 触发的 writer 可以走 hot path,也可以转入 background。LangGraph 的文档直接区分这两种写法:hot path 让新 Memory 更早可见,同时增加当前请求延迟;background 不阻塞主链路,下一项任务开始时却可能还没有写完。1 独立 message 抽取服务始终位于 Agent Loop 之外,它自己的区别是实时消费还是批量处理。

2 业界 Harness 的 Memory 调用方式

我把几套常见实现放到同一口径下,资料核验截至 2026 年 8 月 17 日。三张表的纵轴都只放 Harness,不再把 Mem0 + Claude Code 这类集成组合当成一种 Harness。Mem0 决定 Memory 怎样接进宿主,表格这里先回答更基础的问题:宿主本身有没有这个入口。

2.1 读取调用点矩阵

Harness Session bootstrap Turn admission Pre-model step Memory Tool Post-tool Subagent / handoff Compact / resume
Claude Code 23 2 2 34 2 23 2
Cursor 56 6 6 7 6 6 6
DeepSeek Harness 8 8 8 9 8 8 8
AutoGen 1011 1011 11 1011 11 1011 1011
LangGraph 12 12 1213 13 12 14 15
OpenAI Agents SDK 1617 16 17 18 1718 17 16
Codex 1920 19 19 19 19 19 19

这里判断的是入口,不是默认行为。例如 Cursor 有 beforeSubmitPrompt,但该 Hook 只能校验 Prompt,没有 model-visible context 输出,所以 Turn admission 仍是 ;它的 postToolUse 明确支持 additional_context,Post-tool 才是 6 我拉取 DeepSeek Harness 47f9438 反查事件表后,把 Session bootstrap 从 改成了 :代码中确实有 agent/session-start,其 source 还区分 startupresumeclearcompact8

2.2 写入调用点矩阵

Harness 模型主动调用 memory_add Harness 根据 lifecycle 触发 独立 message 抽取服务
Claude Code 34 2 3
Cursor 57 6 521
DeepSeek Harness 9 8 89
AutoGen 1011 1011 10
LangGraph 13 112 1
OpenAI Agents SDK 18 1618 1618
Codex 19 19 20

这张表只回答构造路径能不能成立。模型主动写入既可以是原生 Memory Tool,也可以是 Harness 暴露的 MCP 或普通 Tool;lifecycle 写入表示 Hook、middleware、node 或 Runner 在边界调用 writer;独立抽取则要求 writer 在 Agent Loop 之外消费 transcript 或 message stream。一个 lifecycle Hook 即使异步启动 writer,仍属于第二列,不会因为跑在后台就自动变成第三列。

2.3 调用决策者与离线评估影响

Harness 读取决策者 写入决策者 主要评估影响
Claude Code Hook / 模型23 Hook / 模型23 默认 Memory 需清场3
Cursor Harness / 模型567 Hook / 模型 / 后台服务5621 异步可见性621
DeepSeek Harness Plugin / 模型89 Plugin / 模型89 Step 调用频率8
AutoGen AssistantAgent / 模型1011 应用 / 模型1011 Memory 刷新频率11
LangGraph Middleware / node / 模型1213 Middleware / node / 模型11213 Graph 拓扑1214
OpenAI Agents SDK Runner / input filter / 模型161718 Runner / 应用 / 模型1618 Session history 混入16
Codex Hook / 模型19 Hook / 模型 / 后台服务1920 后台写入延迟20

我按来源反查后还保留了两个不太对称、但对评估重要的边界。Cursor 的 beforeSubmitPromptsubagentStartpreCompact 都没有 Memory context 输出,所以不能因为 Hook 名字存在就标成 ;AutoGen 的 update_context() 不会在 Tool continuation 前再次执行,所以 Turn admission 是 ,Pre-model step 仍是 611 OpenAI Agents SDK 的 on_tool_end 本身只是 observer,但可以把查询结果写入 run context,再由下一次 call_model_input_filter 注入,因此 Post-tool 作为组合路径标成 1718

3 Harness 对 Memory 任务验证的影响变量

3.1 任务验证与对照实验

这里的任务验证,是让 Agent 在固定环境中完成一项可判定的任务,再由 verifier 检查最终结果。编码任务可以运行隐藏测试,RCA 任务可以检查根因和证据链,Tool-use 任务可以检查环境中的最终状态。离线评估用不同实验组之间的任务结果差估计 Memory 的价值;单条召回是否相关属于组件指标。

一套最小实验可以包含下面五组。代号用于与这个系列的上一篇文章对齐,单独读本文时看实验组名称就够了。

实验组 模型可以看到的长期 Memory 它回答的问题
无长期 Memory(B0 不提供长期 Memory 没有 Memory 时能完成多少任务
当前版本(C0 线上版本的检索结果 当前 Memory 系统的任务表现
候选版本(C1 待发布版本的检索结果 新版本是否带来稳定增量
Oracle evidence(O2 预先标注的最小充分证据 检索完全正确时模型能做到什么程度
错误 Memory(N2 陈旧或相似但错误的证据 错误 Memory 是否让结果比不用 Memory 更差

主比较发生在当前版本和候选版本之间。无长期 Memory 组给出 Agent 自己完成任务的基线,Oracle evidence 把检索问题拿掉,用来观察模型消费 Memory 的上限;错误 Memory 组低于无长期 Memory 组,就出现了 Negative Transfer。

完整矩阵没有必要跟着每次开发迭代重跑。Agent 任务验证需要实际调用模型并执行 Tool Loop,成本比单纯的检索评估高很多。日常开发只跑候选版本(C1),每次保留任务级结果和运行指纹,观察它相对上一版候选的变化;B0C0O2N2 只要 Harness、模型、任务、环境和 Memory snapshot 没变,就复用最近一次结果。准备发版时,再把五组在同一配置下全量重跑一次,发版结论只使用这批同时生成的结果。这样开发阶段控制成本,发版阶段仍保留严格的横向可比性。

举个例子,同一个跨租户缓存错误可以在五组里各跑一遍,隐藏测试统一检查 tenant isolation。当前版本每个 Turn 只注入一次,候选版本却在每个 Step 都重新检索,即使候选版本通过了更多测试,也无法确认增量来自 Memory 算法。Harness 已经改变了检索频率、可用证据和上下文预算。

3.2 Harness 影响变量

换一个调用点,就换了被测系统。MemoHarness 把 context、tools、orchestration、memory、decoding 和 output 都列为 Harness 配置;Anthropic 也把运行 Agent 的 Harness 与负责出题、隔离和判分的 Evaluation Harness 分开讨论。2223 为了让实验组之间的分数可以归因到 Memory,下面这些 Harness 变量必须固定,或者至少进入实验记录。

影响变量 它怎样改变 Memory 对任务结果的贡献 离线评估中固定或记录什么
读取调用点 决定 Memory 在 Session、Turn、每个 Step 还是 Tool 后进入模型268121719 trigger_point
读取决策者 模型自主调用会额外测试触发判断与 Tool-use 能力391318 harnessmodel
查询输入 只用 User Prompt 与使用完整 state、Tool Result 会产生不同召回81013 原始 query 与 query builder
模型在哪里看到检索结果 检索结果可以进入下一次请求的 developer message、由 state 渲染进 Prompt,或在模型主动查询后作为 Memory Tool Result 返回261719 developer_message / state / tool_result
调用频率 Turn 级一次与每个 Step 一次会改变命中率、成本和重复内容8111217 每 Turn 调用次数与去重规则
上下文预算 Top-K、token cap 和截断位置会改变模型实际看到的证据31922 Top-K、token cap 与截断规则
谁负责生成候选 Memory 模型调用 memory_add、Harness 在 lifecycle 事件后调用 writer、独立抽取服务消费 message stream,三条路径会产生不同的 Memory1320 model_tool / harness_writer / message_extractor
写入可见时间 异步 Hook 或后台抽取可能赶不上下一项任务读取162021 captured_atcommitted_atvisible_at
状态边界 原生 Memory、Session history、Subagent 继承与 compact summary 会污染实验组314151620 Session、namespace、workspace 与 snapshot
失败语义 Timeout、重试和静默降级会把 Memory 故障伪装成正常的 no-memory 样本2681923 状态码、timeout、retry 与 fallback

模型看到检索结果的位置,以实际发给模型的 request 为准。Memory 已经写入 Agent state,但 Prompt 模板没有读取,模型仍然看不到;Memory 作为 Tool Result 返回,则意味着模型已经先做出了一次调用决定。候选 Memory 的生成方,要看写入意图从哪里发起:memory_add 来自模型,lifecycle writer 来自 Harness,message extractor 位于 Agent Loop 之外。三种方式需要分别评估。

发版前的主结果应该让当前版本和候选版本使用完全相同的 Harness 配置,Memory 实现是唯一变量。模型自主调用 MCP 也值得测,但它回答的是另一件事:Memory 与模型、Skill、Tool schema 和 Harness 组合以后,真实 Agent Loop 还能保留多少收益。两个结果都有效,不能混成一个分数。

4 离线评估 Harness 的选择

到这里,Harness 的选择可以收得很窄。第 3 节列出这些影响变量,目的是把 Harness 对 Memory 分数的贡献显式化。只要其中的读取、写入、状态边界和失败处理在实验组之间固定,任何harness原则上都能承担同一种归因实验。Harness 在这里是一组实验条件。

落到实践,我会看四个条件。

  1. Memory 调用链可以按业务配置。 很多上层业务 Agent 本来就是团队自己搓的,有的在 Turn 开始时自动检索,有的等 Tool 返回后再查,还有的只把 Memory 暴露成 MCP。底层评估平台需要把读取调用点、查询输入、模型在哪里看到检索结果、调用频率、候选 Memory 由谁生成以及写入可见时间都做成配置。每个大业务建立一份独立 Profile,按需开启对应的评估;同一业务的实验组继续固定这份 Profile。这样才能让一个评估平台兼容五花八门的业务调用方式。

  2. 模型可以配置。 Memory 的收益会随模型变化,Harness 应该通过配置切换 provider 和 model,不需要重写 Agent Loop。每次运行仍要固定模型版本、推理参数和 adapter。

  3. 原生 Memory 可以关闭。 评估时只保留被测 Memory,宿主自动读写的长期 Memory 必须关闭。Session history 和 compact summary 虽然不等于长期 Memory,也要固定边界,避免它们替实验组保存历史信息。

  4. 代码可以审计。 开源最好。这样才能确认 lifecycle 的真实顺序、最终 model request、失败后的 fallback,并把代码固定到 commit 和 lockfile。业务内部代码只要能够查看和固定,也满足同一个要求。

按这个口径,DeepSeek Harness 适合作为公开参考。它的 Plugin 和 Profile 可以承载不同业务的 Memory 调用链,provider 和 model 可以通过配置选择;官方提供的第三方 Memory 只是默认关闭的 MCP overlay,默认组合里没有 Memory Server。8924 代码采用 MIT License,也可以固定到具体 commit。四个条件都能覆盖。它仍处于 Developer Preview,实际评估还需要锁住 lockfile。24

DeepSeek Harness 的优势落在可配置和可审计,不在某种特殊的 Memory 调用方式。实际落地时,可以为每个大业务把自己的 Memory 调用链固化成一个 Profile,评估按业务加载对应 Profile;新增业务只增加配置,不修改评估 Runner。同一业务的实验组始终使用同一份 Profile,Harness 带来的变量才不会混进 Memory 版本差异。

5 参考资料

1 LangChain Docs: Memory overview

2 Claude Code Docs: Hooks reference

3 Claude Code Docs: How Claude remembers your project

4 Claude Code Docs: Extend Claude Code

5 Cursor Docs: Memories(旧版详情页,当前重定向到 Rules)

6 Cursor Docs: Hooks

7 Cursor Docs: Model Context Protocol

8 DeepSeek Harness, commit 47f9438: Architecture, Core lifecycle, Plugin tutorial, Tool runtime

9 DeepSeek Harness: MCP Memory Examples, commit 47f9438

10 Microsoft AutoGen: Memory and RAG

11 AutoGen AssistantAgent, commit 027ecf0

12 LangChain Docs: Custom middleware

13 LangChain Docs: Long-term memory

14 LangGraph Docs: Use subgraphs

15 LangGraph Docs: Persistence

16 OpenAI Agents SDK: Sessions

17 OpenAI Agents SDK: Running agents

18 OpenAI Agents SDK: Agents and lifecycle hooks

19 OpenAI Codex Docs: Hooks

20 OpenAI Codex Docs: Memories

21 Cursor Changelog 1.2: Memories GA

22 Huang et al., MemoHarness: Agent Harnesses That Learn from Experience, 2026

23 Anthropic: Demystifying evals for AI agents

24 DeepSeek Harness, commit 47f9438: README, Plugin Config Catalog

相关推荐
探长785 小时前
DeepSeek 涨价,V4 已经用不起了:Harness + 私有化部署的丝滑体验
deepseek·harness·私有化部署,前缀缓存
徐且行6 小时前
DeepSeek Harness初体验:入门、安装与自定义插件
深度学习·自然语言处理·llm·agents·deepseek·harness
水水不水啊7 小时前
DeepSeek自带的一些插件的功能介绍
笔记·ai·deepseek·harness·dsh
一个处女座的程序猿1 天前
Agent之Harness:deepseek-harness的简介、安装和使用方法、案例应用之详细攻略
deepseek·harness
aimmon1 天前
DeepSeek Harness 初探:1、一切皆插件的 Agent 框架
二次开发·ai大模型·deepseek·harness
小马过河R1 天前
Graph Engineering 深度解析:模型越强,越需要给它画好“地图”
人工智能·langchain·graph·ai工程化·harness·驾驭工程
墨心@1 天前
阶段 4:事件总线
人工智能·语言模型·大语言模型·agent·codex·harness
skywalk81631 天前
硬核移植实录:在 FreeBSD 15.1 上从零跑起 DeepSeek 智能体 harness(附完整踩坑手册)
人工智能·freebsd·deepseek·harness
小小工匠1 天前
Harness - 03 怎么做 Harness Engineering
harness