第 13 章 上下文管理
本章导读:当一个长任务把上下文"撑爆"——Agent 读代码、跑测试、改文件,消息一轮轮堆上去,模型快看不到最关键的信息,成本也随轮次上涨——就需要有人持续打理模型每一轮的输入。本章沿着一次任务的执行过程讲 Harness 的上下文管理——每轮推理如何准备模型输入(指令、历史、任务状态、参考材料的分层组织)、如何用 contextSource(动态业务来源,接入点见第 11 章)接入会变化的业务信息、如何用任务列表跟进长程任务,以及请求变长时的大结果卸载、预算控制、对话压缩与溢出兜底,最后是用 ContextManifest(上下文构建清单)检查模型实际拿到了什么。读完本章,你可以让长任务的模型输入始终"够用且不越界",并能排查"模型为什么没看到某条信息"。
长任务把上下文撑爆
需要打理每轮模型输入"] --> B["上下文管理
准备足以继续工作的输入"] B --> C["每轮推理准备输入
指令 · 历史 · 任务状态 · 材料"] B --> D["接入会变化的业务信息
contextSource"] B --> E["请求变长时
卸载 · 预算 · 压缩"] B --> F["检查模型实际拿到了什么
ContextManifest 清单"] C --> G["收益
模型输入够用且不越界
能排查模型为何没看到信息"] D --> G E --> G F --> G
本章导览图
你什么时候需要它
- 长任务把上下文撑爆。 修复一个问题要读代码、改文件、跑测试,几十轮下来消息越堆越多:全部照发,token 开销大、还可能超出模型的上下文窗口;简单截断,模型又忘了前面的结论。框架默认的大结果卸载与对话压缩处理的就是这件事(见"请求变长时"一节)。
- 模型需要"此刻"的业务事实。 构建状态、审批进度、剩余额度这类会变的数据,不能靠任务开始时拼进提示词——用 contextSource 让每轮推理前实时读取(见"接入会变化的业务信息"一节)。
- 说不清"模型为什么没看到某条信息"。 是没加载、被预算省略,还是来源读取失败?用 ContextManifest 逐轮核对(见"检查模型实际拿到了什么"一节)。
与已学内容的关系和边界:第 8 章解决"会话状态放在哪里、如何保存恢复";本章解决这些状态与各类材料每轮如何组织成模型输入、历史如何压缩——正如第 8 章结尾所说,两边合起来才是"上下文"这个词的完整含义。工作区(第 12 章)提供材料本身,本章决定这些材料如何与预算打交道。
用户让 Agent 修复一个问题时,Agent 往往需要先读代码、拟定方案、修改文件,再运行测试。每次决定下一步之前,模型都需要知道:用户要什么、已经做过什么、刚才的工具返回了什么,以及还有哪些工作没完成。任务越长,这些信息越多;把全部内容一直放进请求,既增加开销,也可能超出模型的上下文窗口。
上下文管理就是在这个过程中,持续为模型准备足以继续工作的输入。HarnessAgent 以 ReActAgent 的推理与工具执行循环为基础,内置了材料加载、任务状态展示、预算控制和历史压缩。普通 ReActAgent 的循环相同,但不会自动安装这套 Harness 策略。
大多数应用可以先使用默认配置。本章沿着一次任务的执行过程,说明什么时候需要补充业务信息、如何让 Agent 跟进长程任务,以及上下文变长后框架如何处理。
从一次推理开始理解上下文
一次 call 可能包含多轮模型推理和工具调用。Harness 在每次请求模型之前重新准备上下文,而不是在任务开始时拼一次提示词,之后一直沿用。
这里需要区分"保存的状态"和"发给模型的内容"。AgentState 保存会话历史、任务、计划模式等工作状态;模型收到的是从这些状态和外部材料中组织出的本轮输入。RuntimeContext 则携带用户、会话等调用信息,其中任意一个属性都不会自动变成模型提示。关于状态如何保存和恢复,见第 8 章(上下文)。
下面以修复问题的助手为例。应用启动时配置共享 Builder,每次请求创建一个 Agent;保持稳定的用户、Agent 和会话身份,才能延续同一段对话。model 是已经配置好的模型(见第 6 章)。
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.harness.agent.HarnessAgent;
import java.nio.file.Path;
HarnessAgent.Builder builder = HarnessAgent.builder()
.name("项目助手")
.agentId("project-assistant")
.model(model)
.workspace(Path.of("/data/project-workspace"));
// 每次请求创建实例,执行结束后关闭。
try (HarnessAgent agent = builder.build()) {
RuntimeContext ctx = RuntimeContext.builder()
.userId("alice").sessionId("fix-login-001").build();
Msg input = Msg.builder().role(MsgRole.USER)
.textContent("修复登录失败的问题,完成后运行相关测试。").build();
Msg reply = agent.call(input, ctx).block();
System.out.println(reply.getTextContent());
}
后续示例都是对这个 builder 的配置,应在应用启动时、第一次 build() 之前完成。上下文管理不要求使用 AgentSession;需要任务在页面关闭后继续执行、运行中补充要求或中断后续做时,再参考第 19 章(会话日志与恢复)。
开始调用:准备规则与已有材料
修复问题之前,Agent 首先要了解项目约定。稳定的角色和行为规则可以写在 sysPrompt 或工作区的 AGENTS.md 中,也可以通过 instruction 添加可信的业务指令:
builder.instruction("test-policy", "修改代码后运行相关测试;无法运行时说明原因。");
这些规则进入 System。Harness 还会加入内置工作原则、环境信息,以及当前模式的行为规则。工作区中的 MEMORY.md、知识材料和额外上下文文件则作为参考资料提供,不会因为来自文件就成为 System 指令。
工作区材料在一次 Agent 调用开始时加载。因此,执行期间修改 AGENTS.md 或 MEMORY.md,不会自动改变本次调用已经加载的材料;下一次调用才会重新读取。若某项业务数据需要在工具执行后立即刷新,应使用下一节的动态来源。
每轮推理:让模型看到当前需要的信息
假设 Agent 已经读过代码,并得到一次失败的测试结果。下一轮请求模型时,Harness 会按照下面的顺序组织输入:
| 顺序 | 内容 | 修复问题时的例子 |
|---|---|---|
| System | 合并后的稳定指令、项目规则和模式规则 | 项目编码约定、修改后需要运行测试 |
| 对话历史 | 用户输入、Assistant 消息、工具调用与结果;必要时包含历史摘要 | 用户报告的问题、已读代码、刚才的测试结果 |
| 当前状态 | 从任务、计划和动态来源生成的临时消息 | 当前待办、计划模式、最新构建状态 |
| 参考材料 | 工作区记忆、知识资料和动态参考资料 | 项目背景、相关文档片段 |
工具 Schema 随请求单独提供,也占用上下文预算。模型能否选择某个工具,取决于这一轮暴露的工具集合;工具能否真正执行,还由权限系统检查。
任务与计划状态分别以 TASK_STATE、RUNTIME_STATE 等标记展示,业务材料和参考资料放在 HARNESS_CONTEXT 中。它们是临时组织出的模型输入,不会每轮重复追加到持久化对话历史。应用通过 API 提供内容即可,不需要自行拼接这些标记。
这也给出了控制模型可见内容的几个入口:稳定规则放进指令,最新业务事实由动态来源读取,长篇资料按相关性选择片段,工具则通过工具配置和权限控制。来源读取时仍应按用户和会话校验访问权限。 将内容标为"参考资料"、设置优先级或关闭某项状态展示,都不会清除已经写入历史的内容,也不能替代权限检查。
接入会变化的业务信息
例如,修复助手需要在每次推理时了解当前构建状态。可以注册一个 contextSource;下面先用固定值演示返回结构,实际应用中将它替换为按当前用户和会话查询业务服务的逻辑:
import io.agentscope.harness.agent.context.ContextBlock;
import java.util.List;
import reactor.core.publisher.Mono;
builder.contextSource("build-status", request -> Mono.just(List.of(
ContextBlock.runtime("latest", "当前构建:测试失败,登录模块有 2 项失败。")
)));
注册和 build() 不会执行查询。Harness 在每次推理请求准备时调用来源,因此工具完成工作后,下一轮可以读到更新的数据。来源收到的 ContextRequest 包含 agentId、userId、sessionId、模型调用 callId 和用途 purpose,可以据此选择数据,但业务服务仍需自行鉴权。
使用 ContextBlock.runtime 提供当前事实,例如审批状态或剩余额度;使用 ContextBlock.reference 提供文档、检索片段等参考材料。动态来源不能创建 System 指令。需要稳定业务规则时,使用前面的 instruction。
对于参考资料,可以附上版本并设置取舍优先级:
builder.contextSource("project-guide", request -> Mono.just(List.of(
ContextBlock.reference("login", "登录接口约定:会话过期后返回 401。")
.withRevision("guide-v3")
.withPriority(20)
)));
预算不足时,同一类可选材料中较低优先级的内容先被省略。如果某块材料缺失就不能正确工作,可以调用 .required(),要求构建时保留它;但过大的必需材料仍可能导致整个请求超出预算。优先传入精简、相关的内容,不要将整份业务数据库标为必需上下文。
来源失败时如何处理
如果构建状态是作出决定的必要依据,查询失败就应停止准备请求。动态来源默认采用这一行为,超时为 5 秒。对于可有可无的推荐材料,可以允许失败时跳过:
import io.agentscope.harness.agent.context.SourceFailurePolicy;
import java.time.Duration;
builder.contextSource("related-notes", request -> Mono.just(List.of(
ContextBlock.reference("note", "历史排查记录:优先检查会话过期处理。")
)), options -> options
.timeout(Duration.ofSeconds(2))
.onFailure(SourceFailurePolicy.OMIT));
OMIT 会省略这次失败的来源,不会悄悄沿用上次读取的旧值;它也不能用于包含 required 块的来源。重复 ID、非法块等结构错误仍会报错。
来源应只读,并能安全地重复调用。一次执行可能准备多个模型请求,回退或重新准备请求时也可能再次读取;不要在这里扣费、更新订单或执行其他业务动作。共享 Builder 中的来源回调和它依赖的客户端,也应支持并发访问。
动态来源的返回约定
ContextSource.load(ContextRequest) 返回 Mono<List<ContextBlock>>。没有相关内容时返回空列表;Mono.empty() 不等同于"没有材料",会作为来源加载失败处理。
来源名必须唯一,同一来源中的块 ID 也必须唯一。未指定 revision 时使用内容哈希,便于诊断同一块资料是否发生变化。多个来源按注册顺序加载;取消后的外部查询能否及时结束,取决于业务客户端的取消支持。
工具执行之后:持续跟进长程任务
修复登录问题可能经历"定位原因、调整实现、补充测试、验证结果"多个步骤。仅靠很早以前的一条用户消息或一段计划,模型容易忘记哪些已经完成。可以开启任务列表,让 Agent 用 todo_write 更新工作进度:
builder.enableTaskList();
Todo 保存在结构化任务状态中。最新的完整 Todo 工具回执仍在历史里时,Harness 会避免重复展示同一份列表;旧回执被裁剪或摘要后,会从当前任务状态重新生成 TASK_STATE。这样,即使早期对话不再逐字发送,模型仍能看到当前进度。
如果工作需要先确定方案再执行,可以结合计划模式(见第 17 章):计划文件保存较完整的方案,Todo 跟踪正在做的步骤,当前模式和计划文件路径帮助模型知道接下来应如何行动。框架不会自动把 PLAN.md 中的段落同步成 Todo,二者需要由 Agent 的工具操作或应用明确维护。
业务对完成标准有更严格要求时,还可以展示任务目标、已确认要求和验证结果:
import io.agentscope.harness.agent.context.TaskContextOptions;
builder.taskContext(TaskContextOptions.builder()
.includeRequirements()
.includeVerificationResults()
.build());
这两个开关默认关闭,彼此独立;开启任意一个后也会展示已设置的任务范围和目标。它们决定模型能看到什么,并不会自动从用户消息中提取要求或自动执行验证。应用通过 TaskContextState.beginTask 设置目标,可信业务逻辑确认或拒绝要求,并显式调用 VerificationService 记录检查结果。需要模型提出候选要求时,可以额外使用 allowRequirementProposals();提出候选不等于批准。
例如,"补充登录测试"这一条 Todo 已完成,只能说明该工作项被标记完成;测试是否通过,应看实际验证结果。代码之后再次变化,旧的验证结论也可能不再适用,需要应用更新被验证对象的版本并重新检查。不要仅根据 Todo 全部完成就判定整个业务任务成功。
请求变长时:先减少负担,再压缩历史
随着代码片段和测试输出不断累积,上下文大小也会增长。Harness 默认启用大工具结果卸载与对话压缩,并在最终模型请求中一起计算消息、工具 Schema 等内容的预算。通常不需要先写配置,遇到明确的成本、窗口或保留需求时再调整。
把大工具结果留在文件中,按需读取
一次测试可能输出很长的日志。准备下一轮模型请求时,Harness 会把超大的工具结果写入 Workspace Filesystem,在上下文中保留首尾预览和文件位置,模型需要细节时再通过 read_file 分段读取。这个文件可以位于本地或分布式后端,取决于文件系统配置(见第 12 章)。
默认阈值为 80,000 字符,预览保留首尾各 2,000 字符,文件位于工作区逻辑路径 large_tool_results/ 下。需要更小的预览时,可以调整:
import io.agentscope.harness.agent.memory.compaction.ToolResultEvictionConfig;
builder.toolResultEviction(ToolResultEvictionConfig.builder()
.maxResultChars(40000)
.previewChars(1000)
.build());
read_file、write_file、edit_file、memory_search、memory_get 和 session_search 默认不参与这种卸载。execute 等工具仍可卸载;读取工具本身应合理分页,避免一次读回全部内容又占满窗口。
为必要信息和模型输出留出空间
处理大结果后,Harness 会检查整体预算。空间不足时,先省略可选的记忆和知识材料,再省略其他可选材料;保留 System 与标为 required 的块,并为任务状态和工具 Schema 等内容计入开销。剩下的空间才是对话历史可以使用的预算。
默认预算根据模型报告的窗口大小,为输出和安全余量预留空间;模型没有报告窗口时,输入预算回退到 64,000 token。估算不是供应商的精确计数,尤其是包含图片等内容时,应给实际请求留有余量。若希望主动限制输入成本,可以配置:
import io.agentscope.harness.agent.context.ContextPolicy;
import io.agentscope.harness.agent.context.ContextTokenEstimator;
builder.contextPolicy(new ContextPolicy(
24000, // 最大输入预算
4096, // 为模型输出预留
1024, // 安全余量
ContextTokenEstimator.approximate(),
manifest -> System.out.println("本轮估算 token:" + manifest.estimatedInputTokens())));
最终输入上限还会受到模型窗口与输出设置约束。maxContextTokens 是工作区材料的加载预算,不是整个模型请求的总上限,不要用它替代 contextPolicy。
保留近期工作,把早期对话整理成摘要
选定材料后,Harness 根据留给历史的空间处理对话。它会先做不需要模型的轻量处理,例如裁剪较旧的大量工具输出;如果仍满足压缩条件,再调用模型将较早的对话整理为摘要,保留近期消息供后续推理使用。
默认摘要包含会话目标、重要结论、文件或其他产物、下一步工作。对于修复任务,模型可以据此知道之前定位了什么、修改过哪些文件、还需要验证什么,而不必每轮重新阅读完整的早期日志。
默认在对话达到 50 条消息,或达到当前可用的对话 token 预算时触发摘要检查;近期保留范围按对话预算动态计算。若希望更早压缩,并按消息条数保留近期内容,可以这样配置:
import io.agentscope.harness.agent.memory.compaction.CompactionConfig;
builder.compaction(CompactionConfig.builder()
.triggerMessages(30)
.keepTokens(0) // 使用消息条数决定保留范围
.keepMessages(10)
.build());
keepMessages 仅在 keepTokens(0) 时生效。压缩还会维护工具调用与结果的配对,保留范围不能把一次工具交互随意切开。摘要默认使用 Agent 主模型,也可以通过 CompactionConfig.builder().model(summaryModel) 指定辅助模型;自定义 summaryPrompt 时需保留 {messages} 占位符。压缩参数的完整速查表见第 14 章(记忆)。
进一步减少旧工具参数和结果的开销
默认的工具结果裁剪会保护最近约 40,000 token 的工具输出;更早的可裁剪内容累计达到约 20,000 token 时,将旧结果缩为预览。这与单个大结果写入文件是两种处理:裁剪不会为每条旧结果额外生成可回读文件,需要完整事实时应查询 Session Log(见第 19 章)。
对于 write_file 等参数很长的工具,还可以在摘要前开启参数截断:
import io.agentscope.harness.agent.memory.compaction.CompactionConfig.TruncateArgsConfig;
builder.compaction(CompactionConfig.builder()
.truncateArgs(TruncateArgsConfig.builder()
.maxArgLength(2000)
.truncationText("... [truncated] ...")
.build())
.build());
它只处理保留范围之外的旧消息,不改变已经执行的工具参数。maxArgLength 是触发替换的长度阈值;超过阈值的字符串会保留前 20 个字符并附加截断提示,而不是保留完整的 2,000 个字符。此项默认关闭,开启前应确认应用不依赖模型逐字回看这些旧参数。
超出窗口时如何继续
发送前,Harness 会检查最终预算以及工具调用与结果的配对。检查通过、准备期间状态也没有发生冲突,才会采用压缩后的工作历史并发出请求。如果 System、必需材料或其他内容仍然放不下,会抛出 ContextBudgetExceededException,此时需要缩小材料、工具定义或调整预算。
估算通过后,供应商仍可能报告上下文溢出。压缩未禁用时,默认原生 Session Log 模式会尝试强制压缩,并在同一次执行中重新发起这一轮推理一次;它不会因此重新提交用户任务,也不会把之前已经完成的工具从头再执行。如果无法进一步压缩或重试仍失败,错误会返回给调用方。
继续任务:哪些信息会重新读取
完成一次工具执行后,新的调用与结果进入对话历史,下一轮重新走上面的准备过程。不同内容的更新时间取决于它的来源:
| 内容 | 何时更新 |
|---|---|
AGENTS.md、MEMORY.md 和工作区知识材料 |
下一次 Agent 调用开始时重新加载 |
contextSource 业务来源 |
每次准备推理请求时重新读取 |
| Todo、要求、验证和计划模式 | 工具或应用先更新状态,下一轮准备时读取当前快照 |
| 工具调用与结果 | 执行后加入历史,供下一轮推理使用 |
模型回退或重试如果重新经过请求准备,也会重新读取动态来源;不能把这一点等同于所有底层网络重试都会重新加载。摘要和长期记忆提取使用辅助模型请求,不会递归加载这套动态业务来源或重复展示任务状态。
摘要、长期记忆和完整历史各用在什么地方
压缩后的工作历史用于"接着做"。Todo、计划模式和权限等结构化状态独立保存,不会被当作普通对话一起摘要掉;子 Agent 的任务记录也独立保存,但已经进入对话的子 Agent 工具结果仍会参与上下文处理。
长期记忆用于"以后还值得记住"。默认摘要前会尝试从待压缩部分提取长期事实到 Memory,供后续会话使用。摘要和记忆提取都可能遗漏细节,不能代替完整记录,具体配置见第 14 章(记忆)。
完整执行历史用于"回看当时发生了什么"。默认原生 Session Log 保存消息与执行事实,压缩只改变模型继续工作的上下文,不会删除这些已提交记录。需要精确回看一段输出时,Agent 可以在工具已启用且权限允许的情况下使用 session_history、session_search,也可以通过 session_list 查找历史会话。应用展示历史、恢复页面或从 checkpoint 继续时,应使用会话日志 API(见第 19 章),而不是把摘要当成完整聊天记录。
检查模型实际拿到了什么
当模型似乎没看到某条信息时,可以用 ContextManifest 检查这次请求准备了哪些材料、用了多少预算,以及哪些内容被省略。它是 Harness 自动生成的上下文构建清单,不包含提示词正文,也不需要业务代码自行创建。
一次 agent.call() 可能经历多轮推理和工具执行;每次准备模型请求都会产生各自的 Manifest。因此,要检查的是出现问题的那一轮,而不是只看整个任务的最后一条记录。validation = passed 表示上下文准备通过,并不表示模型请求已成功,或模型一定采用了其中的信息。
在应用中获取清单
最直接的方式是在构建 Agent 前,为 ContextPolicy 配置观察回调。下面沿用前文的预算,打印每轮结果、保留的材料以及处理记录;用该 builder.build() 创建 Agent 后,正常调用 call 或消费 streamEvents 即会触发回调。
import io.agentscope.harness.agent.context.ContextManifest;
import io.agentscope.harness.agent.context.ContextPolicy;
import io.agentscope.harness.agent.context.ContextTokenEstimator;
builder.contextPolicy(new ContextPolicy(
24000, 4096, 1024,
ContextTokenEstimator.approximate(),
(ContextManifest manifest) -> {
System.out.printf("call=%s purpose=%s validation=%s tokens=%d/%d%n",
manifest.callId(), manifest.purpose(), manifest.validation(),
manifest.estimatedInputTokens(), manifest.inputLimit());
manifest.items().forEach(item -> System.out.printf(
" source=%s revision=%s placement=%s%n",
item.sourceId(), item.revision(), item.placement()));
manifest.transforms().forEach(change -> System.out.println(" " + change));
}));
contextPolicy(...) 会替换整份配置;已有自定义预算时,请保留原来的数值和估算器,只替换回调。共享 Builder 时回调也会被复用,应保证线程安全、尽快返回;生产环境可将这些元数据交给应用的观测系统,不要在回调中执行耗时操作。
从清单定位问题
先看 validation() 和预算,再按 sourceId() 找具体材料,通常就能区分"没有加载"和"加载后被省略"。
| 读取什么 | 如何理解 |
|---|---|
callId()、purpose()、model() |
标识这次模型请求准备及其用途、模型。callId 不是 sessionId、turnId 或 runId |
estimatedInputTokens()、inputLimit() |
整个请求的估算输入量与最终可用输入上限;不是供应商账单中的实际 token 用量。countingMethod() 和 exact() 说明计数方式及是否精确 |
items() |
本轮保留的消息、工具 Schema 和材料元数据。材料可通过 sourceId、revision、contentHash 核对来源与版本,通过 placement 查看放置位置;消息和工具条目的版本、位置可能为空 |
transforms() |
准备期间做过的处理,例如 tool_result_offload(大结果卸载)、history_compaction(历史压缩处理),以及材料省略或来源失败记录 |
validation() |
passed 为准备通过;budget_exceeded 为超出预算;invalid_tool_pairs 为工具调用与结果配对异常;state_conflict / history_conflict 为准备期间状态或历史发生冲突;build_failed 为其他构建失败 |
例如,前文注册的 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 调用。下面展示一次完整的订阅;业务中的文本、工具等事件仍按原来的方式处理。
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.event.CustomEvent;
import io.agentscope.core.message.UserMessage;
import io.agentscope.harness.agent.HarnessAgent;
import io.agentscope.harness.agent.context.ContextManifest;
RuntimeContext ctx = RuntimeContext.builder()
.userId("alice").sessionId("fix-login-001").build();
try (HarnessAgent agent = builder.build()) {
agent.streamEvents(new UserMessage("继续排查登录问题"), ctx)
.doOnNext(event -> {
if (event instanceof CustomEvent custom
&& "context_build".equals(custom.getName())
&& custom.getValue().get("context_manifest")
instanceof ContextManifest manifest) {
System.out.printf("session=%s call=%s validation=%s%n",
ctx.getSessionId(), manifest.callId(), manifest.validation());
}
})
.blockLast();
}
context_build 在上下文准备成功或失败时都会发出,先于实际模型请求。上面的类型判断适用于 SDK 进程内的实时事件;事件经过 JSON 存储或传输后,应按 JSON 对象读取 context_manifest。
默认原生 Session Log 会把它保存为 context/build。通过 SessionEvent.data() 解码记录载荷后,清单位于其中的 event.value.context_manifest。需要事后排查某个会话时,通过 session.log() 或 agent.sessionLog(ctx) 读取记录,并结合日志中的会话、turn 和 run 信息定位,具体见第 19 章(会话日志与恢复)。Manifest 只保存元数据,不能用来重放完整提示词或恢复会话;来源 ID 也可能包含文件名,导出诊断记录时应按业务需要控制访问范围。
如果可选材料的默认取舍顺序不适合业务,可以进一步配置 contextSelectionPolicy;它只决定可省略材料的顺序,不能移除 System 或 required 内容。通常先精简来源返回值、减少无关工具 Schema,再调整这类策略,会更容易理解和维护。
本章小结
这一章沿着一次任务的执行过程,走完了上下文管理的完整链路:
- 开始调用:稳定规则经
instruction与AGENTS.md进 System,工作区材料作为参考资料在调用开始时加载; - 每轮推理:输入按 System、对话历史、当前状态、参考材料四层组织;会变化的业务事实用
contextSource每轮实时读取,失败时按策略停止或省略; - 工具执行后:任务列表让模型用
todo_write跟进长程进度,Todo 完成、要求确认、验证通过是三件不同的事; - 请求变长:大工具结果先写入文件、上下文只留首尾预览与文件位置;可选材料按预算省略;早期对话压缩为摘要;真溢出时强制压缩并在同一次执行中重试一次;
- 排查:
ContextManifest记录每轮保留了什么、省略了什么——先看validation(),再按sourceId()与transforms()区分没有加载和加载后被省略。
长期事实如何从对话中沉淀为跨会话的记忆、压缩参数的完整速查表,见下一章(第 14 章,记忆)。
验收清单——读到这里,你应该能:
- ☐ 说出一次调用中模型输入的四个层级(System、对话历史、当前状态、参考材料)及其排列顺序;
- ☐ 注册一个
contextSource提供实时业务事实,并说出它与instruction在稳定性上的分工; - ☐ 解释为什么动态来源必须只读、能安全地重复调用,以及
OMIT与默认失败行为的区别; - ☐ 调整大结果卸载阈值与压缩触发参数,说出两者分别解决什么问题、为什么不互相替代;
- ☐ 遇到
ContextBudgetExceededException时,说出缩小请求体积的三个方向; - ☐ 用
ContextManifest的items()与transforms()区分材料没有加载和加载后被预算省略两种情况。
相关文档
- 第 12 章(工作区与文件系统):组织项目规则、知识和记忆文件。
- 第 17 章(计划模式):先形成方案,再推进执行。
- 第 8 章(上下文):访问、保存和恢复结构化状态。
- 第 14 章(记忆):跨会话积累长期事实。
- 第 19 章(会话日志与恢复):后台任务、完整历史与中断续做。