HarnessAgent 以 ReActAgent 的推理与工具执行循环为基础,内置了材料加载、任务状态展示、预算控制和历史压缩。普通 ReActAgent 的循环相同,但不会自动安装这套 Harness 策略。
大多数应用可以先使用默认配置。本页沿着一次任务的执行过程,说明什么时候需要补充业务信息、如何让 Agent 跟进长程任务,以及上下文变长后框架如何处理。
从一次推理开始理解上下文
一次call 可能包含多轮模型推理和工具调用。Harness 在每次请求模型之前重新准备上下文,而不是在任务开始时拼一次提示词,之后一直沿用。
这里需要区分“保存的状态”和“发给模型的内容”。AgentState 保存会话历史、任务、计划模式等工作状态;模型收到的是从这些状态和外部材料中组织出的本轮输入。RuntimeContext 则携带用户、会话等调用信息,其中任意一个属性都不会自动变成模型提示。关于状态如何保存和恢复,见上下文与 AgentState。
下面以修复问题的助手为例。应用启动时配置共享 Builder,每次请求创建一个 Agent;保持稳定的用户、Agent 和会话身份,才能延续同一段对话。model 是已经配置好的模型。
builder 的配置,应在应用启动时、第一次 build() 之前完成。上下文管理不要求使用 AgentSession;需要任务在页面关闭后继续执行、运行中补充要求或中断后续做时,再参考会话操作、事件与恢复。
开始调用:准备规则与已有材料
修复问题之前,Agent 首先要了解项目约定。稳定的角色和行为规则可以写在sysPrompt 或工作区的 AGENTS.md 中,也可以通过 instruction 添加可信的业务指令:
MEMORY.md、知识材料和额外上下文文件则作为参考资料提供,不会因为来自文件就成为 System 指令。
工作区材料在一次 Agent 调用开始时加载。因此,执行期间修改 AGENTS.md 或 MEMORY.md,不会自动改变本次调用已经加载的材料;下一次调用才会重新读取。若某项业务数据需要在工具执行后立即刷新,应使用下一节的动态来源。
每轮推理:让模型看到当前需要的信息
假设 Agent 已经读过代码,并得到一次失败的测试结果。下一轮请求模型时,Harness 会按照下面的顺序组织输入:
工具 Schema 随请求单独提供,也占用上下文预算。模型能否选择某个工具,取决于这一轮暴露的工具集合;工具能否真正执行,还由权限系统检查。
任务与计划状态分别以
TASK_STATE、RUNTIME_STATE 等标记展示,业务材料和参考资料放在 HARNESS_CONTEXT 中。它们是临时组织出的模型输入,不会每轮重复追加到持久化对话历史。应用通过 API 提供内容即可,不需要自行拼接这些标记。
这也给出了控制模型可见内容的几个入口:稳定规则放进指令,最新业务事实由动态来源读取,长篇资料按相关性选择片段,工具则通过工具配置和权限控制。来源读取时仍应按用户和会话校验访问权限。 将内容标为“参考资料”、设置优先级或关闭某项状态展示,都不会清除已经写入历史的内容,也不能替代权限检查。
接入会变化的业务信息
例如,修复助手需要在每次推理时了解当前构建状态。可以注册一个contextSource;下面先用固定值演示返回结构,实际应用中将它替换为按当前用户和会话查询业务服务的逻辑:
build() 不会执行查询。Harness 在每次推理请求准备时调用来源,因此工具完成工作后,下一轮可以读到更新的数据。来源收到的 ContextRequest 包含 agentId、userId、sessionId、模型调用 callId 和用途 purpose,可以据此选择数据,但业务服务仍需自行鉴权。
使用 ContextBlock.runtime 提供当前事实,例如审批状态或剩余额度;使用 ContextBlock.reference 提供文档、检索片段等参考材料。动态来源不能创建 System 指令。需要稳定业务规则时,使用前面的 instruction。
对于参考资料,可以附上版本并设置取舍优先级:
.required(),要求构建时保留它;但过大的必需材料仍可能导致整个请求超出预算。优先传入精简、相关的内容,不要将整份业务数据库标为必需上下文。
来源失败时如何处理
如果构建状态是作出决定的必要依据,查询失败就应停止准备请求。动态来源默认采用这一行为,超时为 5 秒。对于可有可无的推荐材料,可以允许失败时跳过:OMIT 会省略这次失败的来源,不会悄悄沿用上次读取的旧值;它也不能用于包含 required 块的来源。重复 ID、非法块等结构错误仍会报错。
来源应只读,并能安全地重复调用。一次执行可能准备多个模型请求,回退或重新准备请求时也可能再次读取;不要在这里扣费、更新订单或执行其他业务动作。共享 Builder 中的来源回调和它依赖的客户端,也应支持并发访问。
动态来源的返回约定
动态来源的返回约定
ContextSource.load(ContextRequest) 返回 Mono<List<ContextBlock>>。没有相关内容时返回空列表;Mono.empty() 不等同于“没有材料”,会作为来源加载失败处理。来源名必须唯一,同一来源中的块 ID 也必须唯一。未指定 revision 时使用内容哈希,便于诊断同一块资料是否发生变化。多个来源按注册顺序加载;取消后的外部查询能否及时结束,取决于业务客户端的取消支持。工具执行之后:持续跟进长程任务
修复登录问题可能经历“定位原因、调整实现、补充测试、验证结果”多个步骤。仅靠很早以前的一条用户消息或一段计划,模型容易忘记哪些已经完成。可以开启任务列表,让 Agent 用todo_write 更新工作进度:
TASK_STATE。这样,即使早期对话不再逐字发送,模型仍能看到当前进度。
如果工作需要先确定方案再执行,可以结合 Plan Mode:计划文件保存较完整的方案,Todo 跟踪正在做的步骤,当前模式和计划文件路径帮助模型知道接下来应如何行动。框架不会自动把 PLAN.md 中的段落同步成 Todo,二者需要由 Agent 的工具操作或应用明确维护。
业务对完成标准有更严格要求时,还可以展示任务目标、已确认要求和验证结果:
TaskContextState.beginTask 设置目标,可信业务逻辑确认或拒绝要求,并显式调用 VerificationService 记录检查结果。需要模型提出候选要求时,可以额外使用 allowRequirementProposals();提出候选不等于批准。
例如,“补充登录测试”这一条 Todo 已完成,只能说明该工作项被标记完成;测试是否通过,应看实际验证结果。代码之后再次变化,旧的验证结论也可能不再适用,需要应用更新被验证对象的版本并重新检查。不要仅根据 Todo 全部完成就判定整个业务任务成功。
请求变长时:先减少负担,再压缩历史
随着代码片段和测试输出不断累积,上下文大小也会增长。Harness 默认启用大工具结果卸载与对话压缩,并在最终模型请求中一起计算消息、工具 Schema 等内容的预算。通常不需要先写配置,遇到明确的成本、窗口或保留需求时再调整。把大工具结果留在文件中,按需读取
一次测试可能输出很长的日志。准备下一轮模型请求时,Harness 会把超大的工具结果写入 Workspace Filesystem,在上下文中保留首尾预览和文件位置,模型需要细节时再通过read_file 分段读取。这个文件可以位于本地或分布式后端,取决于 Filesystem 配置。
默认阈值为 80,000 字符,预览保留首尾各 2,000 字符,文件位于工作区逻辑路径 large_tool_results/ 下。需要更小的预览时,可以调整:
read_file、write_file、edit_file、memory_search、memory_get 和 session_search 默认不参与这种卸载。execute 等工具仍可卸载;读取工具本身应合理分页,避免一次读回全部内容又占满窗口。
为必要信息和模型输出留出空间
处理大结果后,Harness 会检查整体预算。空间不足时,先省略可选的记忆和知识材料,再省略其他可选材料;保留 System 与标为required 的块,并为任务状态和工具 Schema 等内容计入开销。剩下的空间才是对话历史可以使用的预算。
默认预算根据模型报告的窗口大小,为输出和安全余量预留空间;模型没有报告窗口时,输入预算回退到 64,000 token。估算不是供应商的精确计数,尤其是包含图片等内容时,应给实际请求留有余量。若希望主动限制输入成本,可以配置:
maxContextTokens 是工作区材料的加载预算,不是整个模型请求的总上限,不要用它替代 contextPolicy。
保留近期工作,把早期对话整理成摘要
选定材料后,Harness 根据留给历史的空间处理对话。它会先做不需要模型的轻量处理,例如裁剪较旧的大量工具输出;如果仍满足压缩条件,再调用模型将较早的对话整理为摘要,保留近期消息供后续推理使用。 默认摘要包含会话目标、重要结论、文件或其他产物、下一步工作。对于修复任务,模型可以据此知道之前定位了什么、修改过哪些文件、还需要验证什么,而不必每轮重新阅读完整的早期日志。 默认在对话达到 50 条消息,或达到当前可用的对话 token 预算时触发摘要检查;近期保留范围按对话预算动态计算。若希望更早压缩,并按消息条数保留近期内容,可以这样配置:keepMessages 仅在 keepTokens(0) 时生效。压缩还会维护工具调用与结果的配对,保留范围不能把一次工具交互随意切开。摘要默认使用 Agent 主模型,也可以通过 CompactionConfig.builder().model(summaryModel) 指定辅助模型;自定义 summaryPrompt 时需保留 {messages} 占位符。
进一步减少旧工具参数和结果的开销
进一步减少旧工具参数和结果的开销
默认的工具结果裁剪会保护最近约 40,000 token 的工具输出;更早的可裁剪内容累计达到约 20,000 token 时,将旧结果缩为预览。这与单个大结果写入文件是两种处理:裁剪不会为每条旧结果额外生成可回读文件,需要完整事实时应查询 Session Log。对于 它只处理保留范围之外的旧消息,不改变已经执行的工具参数。
write_file 等参数很长的工具,还可以在摘要前开启参数截断:maxArgLength 是触发替换的长度阈值;超过阈值的字符串会保留前 20 个字符并附加截断提示,而不是保留完整的 2,000 个字符。此项默认关闭,开启前应确认应用不依赖模型逐字回看这些旧参数。超出窗口时如何继续
发送前,Harness 会检查最终预算以及工具调用与结果的配对。检查通过、准备期间状态也没有发生冲突,才会采用压缩后的工作历史并发出请求。如果 System、必需材料或其他内容仍然放不下,会抛出ContextBudgetExceededException,此时需要缩小材料、工具定义或调整预算。
估算通过后,供应商仍可能报告上下文溢出。压缩未禁用时,默认原生 Session Log 模式会尝试强制压缩,并在同一次执行中重新发起这一轮推理一次;它不会因此重新提交用户任务,也不会把之前已经完成的工具从头再执行。如果无法进一步压缩或重试仍失败,错误会返回给调用方。
继续任务:哪些信息会重新读取
完成一次工具执行后,新的调用与结果进入对话历史,下一轮重新走上面的准备过程。不同内容的更新时间取决于它的来源:
模型回退或重试如果重新经过请求准备,也会重新读取动态来源;不能把这一点等同于所有底层网络重试都会重新加载。摘要和长期记忆提取使用辅助模型请求,不会递归加载这套动态业务来源或重复展示任务状态。
摘要、长期记忆和完整历史各用在什么地方
压缩后的工作历史用于“接着做”。Todo、计划模式和权限等结构化状态独立保存,不会被当作普通对话一起摘要掉;子 Agent 的任务记录也独立保存,但已经进入对话的子 Agent 工具结果仍会参与上下文处理。 长期记忆用于“以后还值得记住”。默认摘要前会尝试从待压缩部分提取长期事实到 Memory,供后续会话使用。摘要和记忆提取都可能遗漏细节,不能代替完整记录,具体配置见记忆。 完整执行历史用于“回看当时发生了什么”。默认原生 Session Log 保存消息与执行事实,压缩只改变模型继续工作的上下文,不会删除这些已提交记录。需要精确回看一段输出时,Agent 可以在工具已启用且权限允许的情况下使用session_history、session_search,也可以通过 session_list 查找历史会话。应用展示历史、恢复页面或从 checkpoint 继续时,应使用会话日志 API,而不是把摘要当成完整聊天记录。
检查模型实际拿到了什么
当模型似乎没看到某条信息时,可以用ContextManifest 检查这次请求准备了哪些材料、用了多少预算,以及哪些内容被省略。它是 Harness 自动生成的上下文构建清单,不包含提示词正文,也不需要业务代码自行创建。
一次 agent.call() 可能经历多轮推理和工具执行;每次准备模型请求都会产生各自的 Manifest。因此,要检查的是出现问题的那一轮,而不是只看整个任务的最后一条记录。validation = passed 表示上下文准备通过,并不表示模型请求已成功,或模型一定采用了其中的信息。
在应用中获取清单
最直接的方式是在构建 Agent 前,为ContextPolicy 配置观察回调。下面沿用前文的预算,打印每轮结果、保留的材料以及处理记录;用该 builder.build() 创建 Agent 后,正常调用 call 或消费 streamEvents 即会触发回调。
contextPolicy(...) 会替换整份配置;已有自定义预算时,请保留原来的数值和估算器,只替换回调。共享 Builder 时回调也会被复用,应保证线程安全、尽快返回;生产环境可将这些元数据交给应用的观测系统,不要在回调中执行耗时操作。
从清单定位问题
先看validation() 和预算,再按 sourceId() 找具体材料,通常就能区分“没有加载”和“加载后被省略”。
例如,前文注册的
project-guide 来源返回了 login 块,它在清单中的 ID 是 source/project-guide/login。如果在 items() 中找到它,版本是 guide-v3,且校验通过,说明这份约定已经纳入本轮准备好的请求;若回答仍不符合约定,应进一步检查内容和指令是否清晰。
如果没有找到它,就看 transforms():出现 omitted_for_budget:source/project-guide/login,说明材料因预算不足被省略,可以缩短材料或调整预算、优先级;出现 source_unavailable:source/project-guide/:...,说明配置为 OMIT 的来源读取失败,应检查数据源或超时。两种记录都没有时,再检查来源是否返回空列表、是否到了刷新时机,以及本轮请求是否会加载该来源。
构建过早失败时,清单可能没有条目,token 数和上限为 -1、计数方式为 unavailable,表示尚未获得统计结果。这时应结合调用抛出的异常排查,不能据此认定所有材料都被预算策略省略了。
在事件流和会话日志中查看
如果应用已经消费streamEvents,也可以直接在现有流中处理 context_build 自定义事件,无需额外发起一次 Agent 调用。下面展示一次完整的订阅;业务中的文本、工具等事件仍按原来的方式处理。
context_build 在上下文准备成功或失败时都会发出,先于实际模型请求。上面的类型判断适用于 SDK 进程内的实时事件;事件经过 JSON 存储或传输后,应按 JSON 对象读取 context_manifest。
默认原生 Session Log 会把它保存为 context/build。通过 SessionEvent.data() 解码记录载荷后,清单位于其中的 event.value.context_manifest。需要事后排查某个会话时,通过 session.log() 或 agent.sessionLog(ctx) 读取记录,并结合日志中的会话、turn 和 run 信息定位,具体见会话操作、事件与恢复。Manifest 只保存元数据,不能用来重放完整提示词或恢复会话;来源 ID 也可能包含文件名,导出诊断记录时应按业务需要控制访问范围。
如果可选材料的默认取舍顺序不适合业务,可以进一步配置 contextSelectionPolicy;它只决定可省略材料的顺序,不能移除 System 或 required 内容。通常先精简来源返回值、减少无关工具 Schema,再调整这类策略,会更容易理解和维护。
相关文档
- 工作区:组织项目规则、知识和记忆文件。
- 计划模式:先形成方案,再推进执行。
- 上下文与 AgentState:访问、保存和恢复结构化状态。
- 记忆:跨会话积累长期事实。
- 会话操作、事件与恢复:后台任务、完整历史与中断续做。