ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

OpenClaw 3.7 ContextEngine插件接口深度拆解:Agent上下文管理架构重构

OpenClaw 3.7 ContextEngine插件接口深度拆解:Agent上下文管理架构重构 OpenClaw 3.7 的更新公告出来时比起工具调用效率提升、Agent 运行稳定性这些常规优化我第一眼锁定的其实是 ContextEngine 插件接口。原因很简单过去一个月我一直在帮团队调一个对话型 Agent核心问题就出在“上下文到底怎么拼”这件事上。一开始是提示词里塞历史记录后来是工具返回结果直接 append 进上下文再后来加了记忆模块、知识库检索代码里到处是context_builder.py、mem_context.py、format_tool_result()这种散装函数。每次改上下文结构就得小心翼翼地把整套主流程翻一遍。所以当我看到 ContextEngine 把上下文管理从硬编码改造成插件化接口时第一反应是这个思路解决的不只是“上下文整理”问题而是整个 Agent 上下文管理架构的重新组织。这篇文章就来逐层拆一下它的接口设计、源码实现思路以及我实际接入时的踩坑记录。适合正在做 Agent 框架、或准备把现有硬编码上下文逻辑迁移成插件化方案的朋友参考。1. ContextEngine 要解决的痛点1.1 Agent 上下文管理的现状与硬编码危机大多数 Agent 项目跑到一定规模后上下文管理一定是最让人头疼的部分。我见过太多项目初期图省事直接把所有信息在一层build_system_prompt()函数里搞定用户输入、历史记录、上一轮工具返回、角色设定、业务规则……全都在这个函数里用字符串拼接。这个函数会随着功能增加越滚越大最后变成几百行甚至上千行的“上帝函数”。这还没完。拼出来的上下文要面对三个现实问题一是 token 预算上下文放进模型前必须估算 token 数超出就裁掉一部分历史二是内容优先级当预算不够时到底先砍哪块是砍多轮前的对话还是砍工具返回的细节三是来源管理每新增一个信息源比如加了向量数据库检索结果就要在build_system_prompt()里手动加一段拼接、再手动控制它在上下文中的位置。这三个问题叠加在一起就是典型的硬编码危机——明明是很清晰的管道流水线问题却被写成了一团 grep 不到出处的字符串处理逻辑。ContextEngine 的插件接口之所以值得拆就是因为它相当于把这些条理画出来了每个信息源是一个插件每个插件负责产出“上下文颗粒”最后由引擎统一调度、按策略组装、按预算裁剪。写业务的人不再需要关心上下文是第几行被拼进去的。1.2 硬编码架构的问题清单我盘了一下硬编码方式下最常见的五个问题基本可以覆盖 90% 的 Agent 项目痛苦来源改动高风险改上下文拼接逻辑相当于改核心链路任何一处顺序调整都可能影响模型输出。哪怕只是把一个工具返回从“第 3 段”移到“第 5 段”模型可能就开始答非所问。多项目难复用项目 A 里写好的记忆模块、检索模块到项目 B 里因为上下文拼接方式不同基本没法直接搬。最后大家只能各自复制粘贴然后各自维护出三套不同实现。排障不直观上下文拼接顺序混乱时一旦出现模型输出异常很难定位是哪一段内容导致。日志里只有一行prompt ...很难看清每个内容块的边界和来源。预算控制粗糙很多实现只做“超过就截断末尾”也就是把最老的历史丢掉。这其实非常糟糕因为很多时候最该被裁的是某次工具调用的长返回而不是更早但更关键的决策记忆。扩展入口不统一新增信息源时没有统一的注入点只能四处找哪里能塞字符串。最后发现同一个信息可能在三个地方被插入上下文内容重复甚至互相矛盾。这些问题说小也小说大也大。模型输出的质量有很大比例取决于上下文内容的组织方式。如果上下文结构混乱指令遵循能力再强也白搭。2. OpenClaw 3.7 的整体设计与架构亮点2.1 管道式上下文三个层次的模块布局ContextEngine 的架构亮点在于它把“上下文”从一个字符串变量重新定义为一条管道。这是我拆源码时最大的感受。它把处理流程分成三个层次。最外层是采集层所有信息来源都通过插件暴露给引擎中间是组装层引擎拿到各插件产出的上下文颗粒后根据优先级、标签、预算做融合与排序最内层是注入层负责把组装好的上下文树渲染成最终发送给模型的 prompt 结构。这三个层次是单向依赖的采集层不知道组装层怎么合并组装层不知道注入层怎么渲染。这样设计带来的直接收益是可以单独替换任何一层。比如我如果觉得默认的预算裁剪策略不合适可以只替换组装层里的某个策略组件而不用动插件。如果我想让某个信息源只在特定场景下出现也只需要控制插件在采集层的启停。这个设计很像 Unix 管道哲学每个程序做一件事组合起来做一件大事。ContextEngine 里的插件就是一个个过滤器与生产者引擎负责把它们串起来。从此上下文管理不再是一个“全局状态”而是一条每次请求都会重新计算的数据流。2.2 ContextEngine 在 Agent 运行时中的位置我画过一张运行时数据流图ContextEngine 夹在 Agent 主循环和 LLM 调用之间这种位置非常微妙。先看主循环大概是什么样接收用户输入 - 调用 Agent 决定下一步动作 - 执行工具 - 返回结果 - 再调 Agent 决定下一步……在这个循环里每轮都要调用 LLM而每轮调用前必须有上下文。ContextEngine 做的工作就是在每一轮调用 LLM 之前收集当前状态的所有信息源生成当前轮的上下文。它对外暴露的不是build_prompt()这类方法而是一组事件。比如on_user_msg、on_tool_result、on_before_render。插件可以订阅这些事件在合适的时机把内容塞进上下文。引擎自己则会在on_before_render事件后把所有收集到的上下文颗粒做组装和预算校准最后输出可渲染结构。用一个生活类比来说ContextEngine 像供水系统插件像是不同的水源地。水源地负责把水送进管网collect()管网里有水表预算控制、净化器内容脱敏和过滤、闸门优先级控制最后通过水龙头注入层送到用户面前。如果哪一路水源的水质不行不需要拆了整个管网只需要关掉那个水源地的闸门或者换上新的净化器。3. ContextEngine 插件接口源码级逐层拆解3.1 接口入口定义协议类、基类与核心数据结构进入源码层面最先要看的自然是插件该如何被定义。ContextEngine 提供的核心抽象是一个协议类不要直接把它和普通 Python ABC 的抽象基类搞混它更像是一个“结构约定”# openclaw/context/plugin.py from typing import Protocol, Optional, Any from dataclasses import dataclass, field dataclass class ContextGrain: node_id: str origin: str content: Any priority: int 100 lifespan: str turn # turn / session / task meta: dict field(default_factorydict) class AbstractContextProvider(Protocol): name: str version: str priority: int 100 enabled: bool True async def collect(self, scene: ContextScene) - list[ContextGrain]: 采集当前场景下的上下文颗粒 ...每次 Agent 主循环推进到新场景时引擎会调用所有启用插件的collect()方法。返回值是一组ContextGrain也就是上下文的最小单元。node_id是全局唯一的节点名origin标记来自哪个插件priority决定融合时的权重lifespan决定这个信息要存活多久——是一次对话轮次就过期还是要持续整个 session甚至长期驻留在跨会话记忆中。选择用协议类而不是抽象基类是有讲究的。在 Python 中协议类只需要实现约定的方法和属性即可通过类型检查不需要强制继承某个基类。这让插件作者可以用任何自己习惯的方式组织内部代码只要在入口实现collect()就行。这种“去框架感”的设计对插件生态的繁荣很重要因为不是每个开发者都愿意为一个插件去继承一堆框架类。3.2 上下文折叠与层级融合机制有了颗粒数据接下来就是怎么把它们组织成上下文。ContextEngine 采用树状结构的上下文树而不是线性列表。每一轮生成的 prompt本质上是一棵有层级的树根节点之下可以拆成system、memory、tool_result、task_hint等不同分支{ root: { children: [ { node: sys.instructions, origin: core, content: 你是……, priority: 0 }, { node: mem.episodic, origin: mem0, content: [昨天用户……, 用户偏好……], priority: 30 }, { node: act.tool_result, origin: tool.executor, content: {action: search, result: ...}, priority: 60 } ] } }这个树结构最大的好处是支持“折叠”。折叠不是简单丢弃而是把一个分支整体压缩成一条摘要或者用一个元数据标记代替正文。比如 tool_result 分支内容特别长但其中真正关键的只是“搜索结果为 3 条有效记录”那么折叠之后可以表现为一行提示词[工具返回search3 条结果详情见附件]。这样既保留了信息存在感又不会占据大量 token。折叠规则通过每个节点的 priority 和 lifespan 来决定。lifespan 为turn的节点是最容易被折叠的因为它只对当前轮有信息价值下一轮就过时了session级节点可以多存活几轮task级的节点则是在整个任务完成前尽量保留原样。融合机制也有意思。多个插件可能产出相同的node_id引擎会在融合阶段做去重与合并。合并策略不是简单拼接而是按照 priority 取高者获胜同时把低优先级的内容折叠进meta中备用。如果记忆插件和历史插件都产出了“用户想买一台相机”这条信息那么引擎不会在 prompt 里重复两遍而是保留来自高优先级插件的版本并把另一个来源在meta中标注方便后续溯源。3.3 插件注册与加载机制插件接口设计得再好如果没有一套顺畅的注册加载机制生态也起不来。ContextEngine 的插件加载做得很“现代 Python 项目”的味道。它支持两种加载方式。第一种是目录扫描框架在启动时扫描配置指定目录下的插件文件夹例如plugins/或site-packages/openclaw_plugins/识别每个目录下的manifest.json和插件入口函数。每个插件目录结构大致长这样my-ctx-plugin/ ├── manifest.json ├── plugin.py └── README.mdmanifest.json是插件的声明文件{ name: my-ctx-plugin, version: 1.0.0, entry: plugin.py:MyContextProvider, priority: 50, enabled: true, dependencies: [openclaw3.7, requests] }第二种方式是代码内注册适合那些希望把插件逻辑直接嵌入主工程的团队。在框架初始化时只需要调用注册函数就能接入from openclaw.context import registry registry.register(MyContextProvider())注册之后引擎会校验插件的版本和依赖判断是否需要异步初始化然后将其挂到事件总线上。事件总线是插件与引擎通信的通道。插件不需要知道引擎内部怎么实现预算控制、怎么渲染上下文只需要在对应事件触发时提供数据。这里有一个我比较欣赏的细节插件加载器会做一次沙箱检测。如果插件 manifest 中声明了较高权限的钩子比如可以读取会话原始输入、或可以修改其他插件生成的 ContextGrain加载器会强制要求显式开启dangerous: true。这样做是为了避免恶意插件或误配置插件在不知情的情况下干预整个上下文结构。对于团队协作场景这个安全边界非常实用。3.4 回调接口与事件流ContextEngine 的另一大特色是事件驱动的回调机制。插件不只是被动地在collect()时被调用还能主动订阅生命周期事件。常见事件包括事件名触发时机典型用途实现参考on_session_start新会话建立时注册会话级元数据、初始化临时记忆会话标签插件on_user_msg收到用户新消息时写入用户偏好、触发关键词提取用户画像插件on_tool_result工具调用返回时对工具输出做剪裁、摘要、标注工具结果压缩插件on_before_render上下文树渲染成字符串前检查敏感信息、执行最后的裁剪脱敏插件、预算守卫on_over_budgettoken 预算超限时决定哪些 branch 优先折叠/移除动态记忆遗忘插件on_after_renderprompt 发送给模型后记录 token 使用数据、做分析用量审计插件on_session_close会话结束时把必要信息写入长期存储会话归档插件我实际接入时最常用的就是on_tool_result和on_over_budget。因为工具返回经常是把一整份数据库查询结果或文档全文塞进来如果不在事件里做剪裁上下文很快就会爆炸。而on_over_budget则是一个非常精细的控制点我可以拿到当前上下文树的完整状态然后基于业务逻辑决定先砍哪个分支而不是让框架一刀切地丢掉最早的历史。这套事件总线设计让插件之间也能互相通信。插件 A 可以在on_user_msg里提取关键词并在事件总线上发一个自定义事件插件 B 订阅该事件后决定是否把相关文档拉进上下文。这种松散耦合适合同一 Agent 需要多信息来源协作的场景。4. 从源码看 ContextEngine 的实现细节与妙处4.1 Token 预算分配的源码剖析仔细看源码里的预算分配逻辑你会发现它不是一个简单的 token 计数器。相反它把上下文拆成几个独立窗口每个窗口有各自的配额上限# openclaw/context/budget.py BUDGET_WINDOWS { instruction_window: 0.15, # 系统指令、角色设定 core_memory_window: 0.15, # 长期记忆核心片段 history_window: 0.40, # 近期对话历史 tool_window: 0.20, # 工具返回与执行状态 reserve_window: 0.10 # 预留余量避免超限 }上述比例是框架默认值并非死规则。实际计算时引擎会把reserve_window作为安全边际。一旦其他窗口总和超过总预算的 80%就会触发预裁剪逻辑。这个 80% 阈值的设置是有讲究的模型输入接口的实际 token 计数和我们在代码中预估的 token 数往往有偏差预留 10% 到 20% 的余量可以让系统在真实调用时不至于报错。每个窗口内部预算并不是均匀分配的而是根据优先级加权。比如history_window里有最近 20 轮对话但每轮长短差异很大引擎会计算每轮的重要性评分。评分参考维度包括消息是否包含工具调用、是否带有用户明确的反馈、消息时间远近以及该轮之后是否发生过上下文结构的重大变化。分值低的整轮消息会被折叠成一行摘要例如“用户在此之前询问过项目排期”。我曾经自己实现过类似的 token 控制写出来的版本只能做“从尾部截断”结果长任务跑到后面早期重要的决策记录全被裁掉了。ContextEngine 这种按窗口、按评分裁剪的模式明显更适合真实业务场景。4.2 上下文裁剪与压缩的触发链源码里更精妙的是裁剪和压缩的触发链它不是一次性的而是分阶段逐级降级。第一阶段是“软裁剪”当 token 预估超出预算时先将 lifespan 为turn的工具结果节点替换成摘要。比如一次搜索返回了 2000 token 的 JSON软裁剪会把它缩减成“搜索返回 5 条结果与用户问题最相关的 2 条分别为……”大约 200 token。这一步不会影响对话主线的完整性。第二阶段是“硬裁剪”当软裁剪后仍然超限就轮到历史对话记录中的低评分消息。这些消息会从上下文树中移除不再保留原文只留下一个占位标记说明此处曾有一段内容被裁剪。占位标记的作用是让 LLM 知道上下文里存在不可见的历史避免模型误以为用户从未说过某些话。第三阶段是“压缩回溯”如果硬裁剪后还差一点引擎会调用一个轻量级摘要模型或规则模板把最早的一段完整历史压缩成一两句摘要然后放回core_memory_window。这个压缩过程不是把旧内容丢掉而是把旧内容变成新记忆继续参与后续决策。我把这三个阶段理解成先去掉次要的、再删掉易过时的、最后把重要的变成摘要。层级分明每一步都有明确的触发条件。而且这几步都发生在on_before_render事件之前所以插件可以通过监听事件知道自己产出的内容被如何对待方便排查。4.3 持久化与快照机制很多 Agent 框架不太在意上下文持久化ContextEngine 在这方面做得很扎实。每一轮上下文渲染前引擎都会生成一份快照存储上下文树的结构、各节点的来源与优先级、token 估算结果。快照默认写入本地磁盘索引并和会话 ID 关联。为什么需要快照因为 Agent 运行中一旦出现崩溃或者用户主动中断恢复时最怕“上下文状态丢失”。传统做法是直接把最近一轮的历史记录重新塞回去但如果历史记录里已经有被裁剪和折叠的节点恢复出来的上下文就是残缺的。ContextEngine 的快照机制解决这个问题。每轮快照里不仅包含最终渲染的字符串还包含生成这个字符串的上下文树。恢复时引擎会根据快照重建上下文树然后重新执行一遍裁剪与压缩流程。这样恢复出来的上下文和崩溃前几乎一致只是裁剪结果可能因 token 预算调整略有不同。在实现上快照是异步写的不会阻塞主链路。磁盘索引用了类似 WAL 写前日志的方式先写临时文件再原子重命名。索引结构也比较轻量每个快照一行元数据加一个内容引用不会因为快照太多而拖慢启动速度。5. 实操在自己的 Agent 中接入 ContextEngine 插件接口5.1 快速开始最小插件模板说了这么多架构总得动手写一下。我以一个最简单的插件为例展示如何接入 ContextEngine 并让它在运行时被加载。假设我想做一个“会话环境信息”插件负责在上下文中加入当前时间、用户所在时区、设备类型等信息。这个信息量很小但很影响模型回答的上下文感知能力。# plugin.py from openclaw.context import ContextGrain, AbstractContextProvider from datetime import datetime class EnvInfoProvider: name env_info version 1.0.0 priority 5 enabled True async def collect(self, scene): now datetime.now() return [ ContextGrain( node_idsys.env_time, originself.name, contentf当前时间{now.isoformat()}, priority5, lifespanturn, ) ]注意这个实现并没有继承AbstractContextProvider基类因为协议类只要求实现约定的属性和方法即可。我有一次就是因为没写version属性插件一直加载失败后来排查日志才发现 manifest 校验要求版本号必须存在。所以实现的属性一个都不能少。启用插件有两种方式放进自动扫描目录或者显式注册。自动扫描时manifest.json 需要在和 plugin.py 同级目录。构建启动时指定插件目录参数即可claw run --agent my-agent --plugins ./plugins启动日志里能看到一行plugin registered: env_info1.0.0, priority5这就说明插件加载成功了。5.2 实战案例给 Agent 接入外部知识库接下来是一个更有实战价值的场景给 Agent 接入外部知识库让上下文能按需扩展。我的做法是写一个knowledge_retriever插件订阅on_user_msg事件在用户提问时判断是否需要检索知识库。需要时把检索结果作为ContextGrain注入上下文。class KnowledgeRetriever: name knowledge_retriever version 1.0.0 priority 30 def __init__(self, client, embedding_model): self.client client self.embedding_model embedding_model async def on_user_msg(self, event): need_retrieval self._should_retrieve(event.user_message) if not need_retrieval: return hits self.client.search(event.user_message, top_k3) return [ ContextGrain( node_idrag.search_result, originself.name, content[hit.document for hit in hits], priority40, lifespanturn, meta{query: event.user_message, score: hits[0].score}, ) ]接入之后的关键体验是知识库检索结果不再是每次都必须出现在 prompt 里而是“需要时才出现”。这带来一个质变上下文体积大幅下降token 消耗减少同时模型回答的精确度提升。因为不需要再在一大堆无关知识中挑选答案。这里有一个思路易混淆的点哪些信息应该直接进上下文哪些信息应该通过检索通道按需获取我的判断标准很简单——如果信息越过一半的对话轮次仍然有用就放在长期记忆插件里如果信息只对当前这一轮有决定意义就通过检索插件注入如果信息是当前轮的工具返回结果那就走工具结果通道。这样拆分后上下文树的结构非常清晰排障时看哪个节点来源不明一眼就能定位到插件。5.3 迁移自现有硬编码分阶段策略如果你已经在做一个硬编码上下文的 Agent 项目不建议一次性把所有逻辑推倒重来。更稳妥的方式是分阶段迁移。阶段一保留旧的build_system_prompt()逻辑同时把它包成一个“兼容插件”。这个插件在collect()时调用旧代码返回的结果统一包装成一个名为legacy.context的 ContextGrain。这样其他新插件可以先接入 ContextEngine 体系旧逻辑暂时不动系统整体可运行。阶段二把旧逻辑里容易拆分的部分逐块拆出来。比如角色设定移到一个role_config插件工具返回格式化移到一个tool_formatter插件。每拆一块就要确认它在 ContextEngine 下的输出与旧系统一致然后通过节点优先级控制顺序。阶段三当旧代码拆得差不多后停用legacy.context兼容插件让新插件完全接管上下文组装。此时 ContextEngine 的完整能力——裁剪、折叠、事件回调、快照恢复——才真正起效。这个迁移过程中我踩过最大的坑是拆出的第一个小插件输出内容顺序和旧系统不同导致模型回答风格变化明显。后来我把所有 ContextGrain 都加上priority和node_id并且专门写了一个顺序校验测试比对新旧两版上下文中各节点的顺序。确保顺序一致后再继续拆下一个模块终于没有出幺蛾子。6. 常见问题与排查技巧实录6.1 插件注册后没有生效日志也看不到这是最常遇到的问题。排查时我按三步走先看 manifest.json 是否合法注意entry字段必须要写成plugin.py:ClassName的格式再看插件目录是否在扫描范围内如果用的是代码内注册需要确认注册代码在主流程启动时确实执行了最后看插件版本和 OpenClaw 版本是否兼容ContextEngine 接口在 3.7 有较大调整如果插件是用旧接口写的方法名运行时会被静默跳过。有一个小技巧直接在入口注册函数里加一行调试日志或者在collect()里打logger.info能快速确认插件是否被调起。不要依赖框架文档里的“启动成功”日志因为那只能说明装载器看到了插件不代表运行时一定会调用它。6.2 ContextGrain 大小超限代码里设定了单颗上下文颗粒最大字节数默认是 80000 个字符。如果你从外部拉了一个很长的文档很容易超限。解决办法是把长内容拆分。可以用内置的TextSplitter把文档切成多个 segments每个 segment 作为一个独立节点。拆分后要注意node_id不能重复我在实现时用的是rag.docs.{chunk_index}这种命名。6.3 上下文顺序变化导致结果漂移这是接入初期最容易遇到的“玄学”问题。模型对 prompt 顺序非常敏感同一个信息从第 2 段挪到第 6 段答案可能完全不同。我在迁移时的做法为每个插件设定明确的优先级和排序规则。ContextEngine 默认是 priority 升序排列priority 相同的情况下按照插件注册顺序排。为了稳定我会给所有自定义插件显式指定一个间隔较大的 priority例如 10、20、30避免默认排序造成不确定性。如果你的项目里已经有顺序敏感的 prompt建议在迁移后跑一遍回归测试集把旧系统和新系统的输出做 diff。不要只看最终的答案文字还要看中间步骤。否则你很难判断是上下文顺序问题还是插件内容本身裁减变了。6.4 上下文污染与跨会话泄漏这个坑非常隐蔽。我遇到的情况是会话 A 里用户设置了“喜欢用表格回答”插件把偏好写进了长期记忆结果会话 B 里用户没提过这个偏好模型还是用表格回答。原因就是插件产生的 ContextGrain lifespan 被设成了session甚至task而这两个生命周期跨越多轮对话容易被带进下一个会话。解决办法是检查每个插件的lifespan设置。默认值建议都设为turn除非确实有必要让信息在更长范围内保留。如果确实需要跨会话持久化不要通过 ContextGrain 传递而是写入独立的长期记忆库并在新会话启动时明确通过加载逻辑决定是否调出。6.5 长对话反复摘要导致上下文“罗生门”压缩回溯机制在长对话中会反复摘要早期历史。如果摘要模型质量不稳定可能越摘要越偏离原义。我最开始遇到的情况是对话跑到第 40 轮后模型对第 3 轮用户意图的理解出现偏差因为它看到的只是一句摘要而摘要已经把关键约束丢失了。排查方法是在快照索引里对比不同轮次的core_memory_window内容。如果发现摘要越变越短但关键约束词丢了说明摘要模型给的信息权重不对。解决办法有两个在摘要触发前提高核心节点的 priority或者给核心约束设置protected标记让它们在压缩回溯时被保留原样而不进入摘要模型。7. 几个容易被忽略的真实心得说几个不是文档里会写得特别细致、但实际用起来影响很大的点。第一插件目录的命名和节点命名最好保持严格一致。ContextEngine 的索引系统会按origin字段做溯源统计如果插件目录叫user_memory但节点 origin 写的是memory_user排查时你会看到所有日志里节点来源对不上非常头疼。第二在on_over_budget里做裁剪时不要只想着“砍内容”。有时候反而应该“加内容”——比如补充一条系统提示“请注意之前有一段工具返回过长已被压缩为摘要”反而能让模型更好地利用剩余上下文。这个反直觉的操作我在一个电商客服 Agent 上实测过效果出乎意料地好。第三如果团队里有多个 Agent 项目建议把公共插件抽成一个共享包。比如脱敏插件、日志插件、时间插件这些几乎每个项目都需要。ContextEngine 的插件加载机制天然支持外部包只要在 manifest 里声明dependencies就能统一复用。我见过三个项目各自写了一套“时间插件”实现一模一样却互相不通用这其实挺浪费的。最后再分享一个我在迁移中形成的小习惯每次新增插件前先画一下它会在上下文树里产生哪些节点、在哪个生命周期生效、预计占多少 token。这个一页纸的设计比将来反复调代码省力得多。上下文管理已经不是“能拼就行”的活儿了它本质上是信息架构。把信息当作架构来设计Agent 的稳定性才会有质的提升。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进