ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LLM Agent长任务上下文:显式状态如何替代对话历史

LLM Agent长任务上下文:显式状态如何替代对话历史 在使用 Claude Code 这类 AI 编码助理时一个高频搜索问题是“对话历史怎么保存”。不少开发者担心任务执行到一半或是当天工作结束后后续还能不能接着上一次的上下文继续推进。Google 新研究 SKILL.state 给出的方向不是继续改进“历史保存”而是直接提出用显式状态state替代对话历史不再让模型每次都从头阅读一长串消息而是把真正影响下一步决策的信息抽出来变成结构化状态。这篇文章会围绕这条思路展开先说明为什么对话历史撑不住长任务再介绍显式状态的设计方式和最小工程示例最后给出验证、排错和落地建议。SKILL.state 目前能看到的公开信息还比较有限因此在官方论文或技术报告发布前本文不会去复述它的实现细节更多是把“显式状态替代对话历史”这个核心思想拆开结合 LLM Agent 上下文工程的常见做法还原成一套可以学习、可以实验、可以迁移到业务项目里的方法。1. 先理解 SKILL.state 要解决什么问题1.1 对话历史和显式状态不是一回事在 LLM 应用里“对话历史”通常是指用户和模型之间的多轮消息记录可能还包含工具调用参数、工具返回结果、报错堆栈和代码片段。它的最大优点是天然、完整只要把消息按时间顺序拼起来就能还原曾经的交互过程。但完整并不等于好用。显式状态则是另一种思路。它不保留所有消息而是保留“当前任务是什么、已经做到哪一步、下一步可以做什么、哪些约束不能破坏、刚才发生了什么错误”等结构化信息。听上去很像传统软件开发里的状态机或工作流快照本质也确实更接近状态管理而不是文本存档。SKILL.state 的关键主张是在需要完成多步任务的 Agent 场景里可以用这样的显式状态作为后续决策的主要上下文替代把整段对话历史反复塞给模型的做法。1.2 为什么历史完整不代表上下文可靠这里需要区分三层概念系统保存了什么可能保存了全部历史。模型看到了什么可能因上下文窗口限制只看到了部分历史。模型决策依赖什么模型从可见历史里提取出的关键事实可能已经受了位置偏差和无关内容干扰。一旦把三层区分开就能看出一个问题。保存层做得再完整只要模型消费层仍然依赖原始消息序列决策质量就受制于文本顺序、历史长度和中间噪音。比如用户在第 2 轮说“绝对不要修改数据库连接池配置”Agent 执行到第 30 轮时这段约束早就淹没在大量编译日志和工具输出里模型很可能在生成补丁时把连接池参数顺手改了。SKILL.state 的思路就是把类似“不要修改连接池配置”这样的约束显式提取成状态字段。这样不管中间发生过多少轮无关讨论决策时都能稳定读到这条约束不必靠模型从长历史里“回忆”。1.3 对话历史、上下文摘要与显式状态的差异为了看清 SKILL.state 的定位可以对比三种上下文方案。方案存储内容决策时喂给模型的内容优点缺点完整对话历史原始消息、工具结果、日志从开始到当前的整段文本信息保真度高调试时可以回看token 成本越来越高容易受无关内容干扰上下文摘要用自然语言压缩后的历史总结摘要在前必要时拼接少量历史相比完整历史更省 token摘要质量依赖生成时机关键词一旦丢失就不可恢复显式状态结构化字段、进度、约束、检查点状态对象和必要证据片段信息可控、可恢复、可审计需要设计状态 schema有建模成本从这张表可以看出SKILL.state 更像第三种方案的强化版本。它不只是“把历史压缩一下”而是改变 Agent 的记忆模型从“记住发生过什么”变成“记住当前是什么状态”。1.4 SKILL.state 适合哪些场景显式状态并不是所有场景都需要。如果只是短对话、单轮问答对话历史就是最简单可靠的方案强行引入状态层反而增加复杂度。真正值得使用显式状态的场景通常包含以下几个特征任务步骤多需要跨多次调用累积进度。执行过程依赖工具结果比如读写文件、执行测试、调用 API。任务可能中断需要在某个检查点保存并恢复。出问题后需要排查“哪一步决策错了”而不只是“哪段提示词没写对”。在 AI 编码助理、自动化运维 Agent、测试修复 Agent 这类工具中这几个特征几乎同时出现。这也解释了为什么“Claude Code 怎么保存对话历史”会被反复搜索因为使用者真正需要的不是把聊天记录存下来而是让工具在长任务、多文件、多轮修改中不丢上下文。SKILL.state 指向的正是更工程化的答案。2. 对话历史在长任务中的瓶颈决定了状态化改造的方向2.1 token 不是线性增长而是被反复消费假设一个 Agent 要修复三个文件中的编译错误。最简单的做法是把历史完整保存每执行一步都把之前的消息拼进 prompt再让模型生成下一步动作。先做一个估算。每次工具调用产生的消息如果平均 500 token完成 50 个步骤后单次决策可能需要读取至少 25000 token 的历史。如果上下文窗口是 128k早期还能承受但任务继续拉长后要么超出窗口限制要么不得不丢弃早期内容。更要紧的是历史会被反复“复读”。Agent 每往前走一步都要重新处理一遍此前所有消息而不是只处理增量。同一个结论在历史里出现三次模型就会消耗三份注意力。这既增加延迟也增加成本。对上下文窗口也不宜抱太高期待。大上下文只解决了“装得下”的问题没有解决“用得好”的问题。当关键约束出现在一万 token 之前中间又堆满编译输出时模型仍然可能漏读。2.2 早期错误会随着历史一路累积对话历史里的错误信息有一个麻烦它不会自动失效。假设第一次修改时生成了错误补丁历史中保留了这个补丁。如果没有显式状态标记“该方案已被否定原因是测试失败”模型在后续生成时仍然可能参考这个错误补丁做出相似修改。再严重一些错误一旦被历史固定下来可能会像“先入为主”的前提一样参与后续所有推理导致 Agent 在一条错误方案上反复打转。显式状态可以更干净地处理这件事。状态里可以只记录“验证通过的决策”同时把失败方案留在日志文件里备查但不放进决策上下文。失败信息不是被删除而是从“影响决策的上下文”降级为“排查时可查询的日志”。2.3 只看截断历史会破坏关键约束有人可能会说历史太长就做截断只保留最近 N 轮。这种做法的代价是明显的重要的早期决定很容易被窗口挤出。很多编码任务里最关键的信息恰恰出现在早期。例如用户定义了技术选型约束。产品需求里有一条不可变规则。架构师确定了本次改动不允许触碰的模块边界。这些信息通常只会出现一次且很少在后续讨论里被重复强调。用朴素截断策略保存历史最容易被丢掉的就是这些“只说过一次但非常重要”的内容。相比之下显式状态需要专门设计字段来承接这类信息。只要状态 schema 里有constraints字段恢复执行时就有机会重新读取约束而不是依赖消息是否恰好落在截断范围内。2.4 从“记录消息”到“维护状态”的转变回到 SKILL.state 的标题可以看出研究想做的不是让历史保存得更久而是改变记忆单元。传统方案里记忆单元是“消息”。显式状态方案里记忆单元是“字段”。一个字段天然具备以下特性可更新完成一步就把pending中的项移到completed。可校验恢复任务时可以检查字段是否完整。可审计前后两个状态放在一起能看到发生了什么变化。可选择决策时只把相关字段送入 prompt而不需要传送全部文本。开发者可以把状态对象理解成 Agent 的地图。车辆没有地图也可以慢慢摸索但只要道路中断、绕路或者需要恢复航线一张随时更新的地图远比完整的行车日志更高效。3. 显式状态机制的设计思路3.1 第一步先定义状态 schema状态设计不能等代码写完之后再做而必须在 Agent 动手执行任务前明确。否则实现过程中会不断修改字段造成状态文件与业务逻辑脱节。一个面向编码或运维 Agent 的通用状态结构通常需要包含以下内容{ state_version: 1, status: in_progress, goal: 修复 app.py 中的语法错误并运行测试, constraints: [ 不要修改 import 语句, 不要更改公共函数签名 ], current_file: src/app.py, completed_steps: [ 定位语法错误所在行 ], pending_steps: [ 生成修复补丁, 运行 pytest, 检查是否引入回归 ], last_decision: { action: read_file, target: src/app.py, purpose: 定位语法错误 }, evidence_buffer: [ { type: tool_result, source: read_file, summary: 第 18 行存在未闭合的括号, raw_ref: logs/step_0003_tool_result.txt } ], error_log: [] }这些字段各有用途字段类型含义state_versionint状态结构版本号用于迁移兼容statusstringpending、in_progress、done、failedgoalstring当前任务的最终目标constraintsstring[]不允许违反的边界条件current_filestring当前正在处理的文件或资源completed_stepsstring[]已经完成且验证过的步骤pending_stepsstring[]尚未执行或等待重试的步骤last_decisionobject最近一次决策的动作、目标和目的evidence_bufferobject[]最近少量工具执行摘要带原始日志引用error_logarray错误原因和重试记录设计 schema 时有一条原则值得记住只有会被后续决策直接使用的信息才放进状态。纯粹的过程噪音比如某次命令的完整输出应该放在日志目录并只在状态里保存路径引用。3.2 第二步设计状态更新机制而不是到处赋值状态混乱的根源往往不是 schema 不够而是写代码的人在任何位置都修改状态字段。今天这里加一个state.note明天那里加一个state.temp_result很快就无法判断哪个字段可信。稳定的做法是定义状态更新事件。常见的更新节点包括收到用户新指令先更新 goal并把与旧目标冲突的 pending 项清理掉。工具返回成功从工具结果中提取关键结论追加到 completed_steps。工具返回失败把失败摘要写入 error_log并重排 pending_steps。发生分支选择把选择的依据写入 last_decision。完成检查点将当前状态持久化到磁盘或状态存储。下面用一个简化代码示例体现这种思路。这里的代码不是为了展示某个产品的官方实现只是演示一个可运行的状态化 Agent 骨架。import json from pathlib import Path from typing import Optional class AgentState: def __init__( self, state_version: int, goal: str, constraints: list[str], pending_steps: list[str], current_file: Optional[str] None, ) - None: self.state_version state_version self.status in_progress self.goal goal self.constraints constraints self.completed_steps: list[str] [] self.pending_steps pending_steps self.current_file current_file self.last_decision {} self.evidence_buffer: list[dict] [] self.error_log: list[dict] [] def to_json(self, path: Path) - None: payload { state_version: self.state_version, status: self.status, goal: self.goal, constraints: self.constraints, current_file: self.current_file, completed_steps: self.completed_steps, pending_steps: self.pending_steps, last_decision: self.last_decision, evidence_buffer: self.evidence_buffer, error_log: self.error_log, } path.write_text(json.dumps(payload, ensure_asciiFalse, indent2)) classmethod def load(cls, path: Path) - AgentState: payload json.loads(path.read_text(encodingutf-8)) state cls( state_versionpayload[state_version], goalpayload[goal], constraintspayload[constraints], pending_stepspayload[pending_steps], current_filepayload.get(current_file), ) state.status payload[status] state.completed_steps payload[completed_steps] state.last_decision payload.get(last_decision, {}) state.evidence_buffer payload.get(evidence_buffer, []) state.error_log payload.get(error_log, []) return state为什么用state_version因为状态结构一定会演化。现在只有一个constraints数组未来可能需要拆成code_constraints和deploy_constraints已有的状态文件必须能识别旧版本并迁移否则现场恢复时就会出现字段缺失。3.3 第三步把状态写进决策循环状态文件本身不会发挥作用真正关键的是后续决策时如何读取。设计时的核心要求是模型每次决策都要优先暴露状态对象中的结构化信息。将完整历史全部塞入再让状态对象成为可选附件这不是“用状态替代历史”只是多了一个历史副本。一个参考性的决策循环如下def build_decision_prompt(state: AgentState) - str: # 这里只使用结构化状态不再拼接完整历史文本。 # 在真实项目中可以调用封装好的大模型接口。 prompt f 当前目标{state.goal} 状态{state.status} 已完成{state.completed_steps} 待完成{state.pending_steps} 硬性约束{state.constraints} 当前文件{state.current_file} 最近一次决策{json.dumps(state.last_decision, ensure_asciiFalse)} 最近少量执行摘要{json.dumps(state.evidence_buffer, ensure_asciiFalse)} 请输出下一步动作。 return prompt这段代码想说明的核心是决策 prompt 的构成不依赖“之前所有对话消息”而是依赖少量高价值状态字段。如果业务场景确实需要参考某些历史细节可以通过 evidence_buffer 引用日志路径或者在需要时按需加载少量原始记录。这种“按需引用原始证据”和“凡事全量带上历史”有本质区别。注意状态替代历史的边界要把握住。不是所有原始信息都不能看而是不要让模型默认消费全部历史。原始日志应该保留作为排查证据而不是每次决策都完整重放。3.4 与“Claude Code 怎么保存对话历史”这件事的关联很多开发者搜索“claude code怎么保存对话历史”背后其实有两个需求。第一个需求是“备份会话内容”。这个需求用传统的会话导出、历史文件保存就可以满足。第二个需求是“任务中断后还能按同样的上下文恢复执行”。这个需求比备份复杂得多。如果只保存对话消息Agent 恢复时仍要重新阅读大量文本。如果在保存历史之外还能以结构化状态形式维护一份“当前目标、已完成、待完成、约束、错误记录”那么恢复效率会明显不同。SKILL.state 给工程实践的启示正在这里与其把精力全花在“把对话历史完整导出”不如同样重视“把任务状态做结构化沉淀”。两者可以并行前者用于审计和回放后者用于继续执行。4. 用最小工程示例理解状态化 Agent4.1 先设定一个可验证的场景为了不让状态设计停留在口头上下面用一个非常小的场景演示整个流程。任务修复app.py里的语法错误并运行测试验收。 约束不允许修改任何 import 语句。第一次执行时Agent 没有历史从空状态开始。执行到某一步后手动中断。第二次执行时Agent 不读取第一次的对话记录只加载状态文件再根据pending_steps继续执行。这种设置可以清楚看出显式状态和对话历史的区别。4.2 准备演示文件先构造一个最简单的待修复文件。import os import sys def load_config(): return {env: os.getenv(APP_ENV, dev)} def main(): config load_config( print(config) if __name__ __main__: main()这里的语法错误在第 9 行load_config(缺少右括号。任务执行者需要先定位这个错误再修复它。4.3 初始化状态并按状态执行下面用一个模拟的 Agent 类演示主流程。为了让代码无需 API Key 也能运行这里使用基于规则的“伪决策函数”替代真实模型调用重点展示状态读写逻辑。from pathlib import Path def run_action(action: str, state: AgentState) - None: if action locate_syntax_error: state.current_file app.py state.completed_steps.append(定位 app.py 语法错误) state.evidence_buffer.append({ type: tool_result, summary: 第 9 行 load_config 调用缺少右括号, raw_ref: logs/locate_result.txt, }) state.pending_steps.remove(定位 app.py 语法错误) elif action apply_fix: # 真实项目中应由模型生成补丁这里直接演示结果。 state.completed_steps.append(修复缺失的右括号) state.evidence_buffer.append({ type: patch, summary: 在第 9 行末尾补上右括号, raw_ref: logs/patch_example.diff, }) state.pending_steps.remove(应用修复补丁) elif action run_test: state.completed_steps.append(运行 pytest 验收) state.evidence_buffer.append({ type: test_result, summary: pytest passed, raw_ref: logs/test_result.txt, }) state.pending_steps.remove(运行 pytest) if not state.pending_steps: state.status done def decide_action(state: AgentState) - str: if not state.pending_steps: return noop # 在真实项目中这里应该调用大模型读取状态并返回下一个 action。 if 定位 app.py 语法错误 in state.pending_steps: return locate_syntax_error if 应用修复补丁 in state.pending_steps: return apply_fix return run_test def main() - None: state_path Path(agent_state.json) if state_path.exists(): state AgentState.load(state_path) print(检测到已有状态从检查点继续执行。) else: state AgentState( state_version1, goal修复 app.py 语法错误并运行测试, constraints[不允许修改 import 语句], pending_steps[ 定位 app.py 语法错误, 应用修复补丁, 运行 pytest, ], ) print(未检测到状态文件创建新任务状态。) max_steps 5 for _ in range(max_steps): if state.status done: break action decide_action(state) if action noop: break run_action(action, state) state.to_json(state_path) print(最终状态, state.status) print(state 文件位置, state_path) if __name__ __main__: main()这段代码有几个地方值得说明。第一状态持久化发生在每步 action 完成后。这样即使执行崩溃已有进度也不会丢。第二第二次启动时代码先检查本地是否存在agent_state.json一旦存在就不再从零初始化任务而是恢复旧状态。第三决策函数只访问 state 对象不依赖任何轮对话消息。这就是“用显式状态驱动执行”的最小形态。4.4 运行并观察状态文件变化运行第一次python stateful_agent.py预期看到未检测到状态文件创建新任务状态。 最终状态done state 文件位置agent_state.json然后查看状态文件cat agent_state.json内容类似{ state_version: 1, status: done, goal: 修复 app.py 语法错误并运行测试, constraints: [ 不允许修改 import 语句 ], current_file: app.py, completed_steps: [ 定位 app.py 语法错误, 修复缺失的右括号, 运行 pytest 验收 ], pending_steps: [], last_decision: {}, evidence_buffer: [ { type: tool_result, summary: 第 9 行 load_config 调用缺少右括号, raw_ref: logs/locate_result.txt } ], error_log: [] }要验证“中断后恢复”可以手动改动状态文件。比如把status改为in_progress并把一条completed_steps中的项移回pending_steps。再次运行脚本时程序会读取本地状态而不是新建任务。注意真实工程里状态文件不能手动乱改。这里只是测试恢复逻辑。生产环境必须把状态存储当成数据库记录来维护写入操作需要原子性、权限控制与审计。4.5 这个示例和完整历史方案的本质差别如果使用完整历史方案同样的“中断后继续执行”可能这样处理读取之前所有终端消息把全部文本拼起来让模型判断接下来要做什么。如果历史有 200 条其中夹杂大量错误日志模型需要先剔除噪声再推断当前进度。使用显式状态方案时进度和约束被直接刻在completed_steps、pending_steps、constraints字段里。执行器只需要把状态序列化到 prompt 即可继续任务。这就是它更适合长任务的根本原因。5. 验证与排错怎么知道状态方案是否有效5.1 不要只看最终是否成功评估一项上下文改造是否有效单看一两次“最终成功”远远不够。建议建立横向和纵向两组观察维度。维度说明具体观测指标完成度任务是否正常结束同一批任务的完成率资源消耗状态方案是否更省平均每步 token 消耗、平均耗时恢复能力中断后能否续跑注入中断后的恢复成功率错误鲁棒性失败后能否纠正平均重试次数、纠正后的成功率可解释性问题能否定位状态 diff 能否直接指出错误发生点更推荐的做法是准备一批结构相近的生产任务把完整历史 Agent 和显式状态 Agent 各跑一轮再比较上面的指标。没有基线就切换方案很难判断改动带来的是收益还是偶然结果。5.2 状态变更日志应该成为主要排错入口在传统对话历史方案里排查“Agent 为什么做错”比较困难因为需要翻聊天记录还原模型当时的上下文。在显式状态方案里排查入口应该切换到状态变更记录。推荐在每次保存状态前打印或记录前后状态差异[STATE_CHANGE] step3 before.statusin_progress after.statusin_progress actionapply_fix completed_steps: [...] - [...] pending_steps: [...] - [...] evidence_buffer.last.summary在第 9 行末尾补上右括号有了这类日志也可以把记录写入 JSON Lines 文件后续用脚本分析{time: 2025-01-01T10:00:01Z, action: locate_syntax_error, diff: [completed_steps 1, evidence_buffer 1]} {time: 2025-01-01T10:00:02Z, action: apply_fix, diff: [pending_steps -1, completed_steps 1, evidence_buffer 1]}如果某一步决策异常先看 last_decision 的 action 和依据再看 evidence_buffer 是否有足够信息。这样的排查路径比阅读全量历史短得多。5.3 常见故障排查链路下面给出状态化 Agent 容易踩到的故障现象及排查顺序。故障现象检查顺序检查方式可能原因处理建议中断后恢复Agent 重新从头开始1. 状态文件是否存在2. 文件更新时间是否在当前执行前查看agent_state.json检查 completed_steps 是否有内容状态文件路径不一致或恢复逻辑没有生效统一状态路径并在启动时打印“加载状态”或“新建状态”状态保存了但后续动作仍像失忆1. 决策 prompt 是否真的读取了 state2. 是否仍然把完整历史排在 state 前打印 build_decision_prompt 输出状态只是备用模型仍被全量历史主导将状态对象设为决策 prompt 的主上下文并控制原始历史使用状态文件覆盖了已完成进度1. 是否存在并发写入2. 保存是否在每步 action 后触发查看写入日志检查是否有两个进程写同一文件多进程并发执行后写覆盖先写引入文件锁或换成数据库/存储服务使用版本号控制覆盖pending_steps 为空但 status 不是 done1. 状态更新逻辑是否少写 status2. 是否有异常提前退出检查状态文件中的 status 值状态分支更新不完整在保存状态前执行内部校验强制要求字段间一致性状态字段丢失比如 constraints 为空1. 状态 schema 版本2. 旧状态文件加载是否经过迁移检查 state_version 和字段是否存在schema 版本没有迁移旧状态不兼容新代码增加 state_version 和 migrate 函数旧状态加载时先升级5.4 从学习环境到生产环境状态管理的要求完全不同本地学习环境里把状态存成 JSON 文件完全够用。手动修改字段、重跑脚本、观察变化可以帮助快速建立直觉。生产环境就不同了。状态可能被多个 Agent 并发读写状态文件可能因为进程崩溃处于半写入状态模型决策可能输出了不可达的下一步动作。生产落地至少要补上以下几块状态存储从本地 JSON 换到数据库、Redis 等支持事务的存储。原子写入一次状态更新要么全部成功要么全部失败。版本控制每次更新都增加版本号防止旧状态覆盖新状态。审计日志记录谁在什么时候基于什么状态执行了什么动作。校验规则保存前检查必填字段、枚举值、状态流转是否合法。监控告警状态长时间不更新、重试过多、状态回退失败时及时告警。注意学习环境里最容易忽视的是“状态与真实环境是否一致”。如果状态文件记录“已修复”但工作区文件已经被外部改动覆盖Agent 直接跳过检查就会带着过期假设继续执行。生产环境执行前需要增加校验步骤比如按当前文件摘要确认状态仍然成立。6. 落地建议状态化改造的几个关键判断6.1 落地前先回答三个问题很多人看完状态化思路后会急着把所有 Agent 都改成状态驱动。但改造前应该先回答三个问题。第一个问题任务是否需要跨多轮累积上下文如果不需要对话历史足够用。第二个问题状态字段能否被清晰定义如果任务高度开放无法归纳出约束、进度、下一步强行定义状态只会把模型塞进一个过窄的框架。第三个问题状态与真实环境能否保持一致如果 Agent 修改的文件、数据库、配置等外部资源经常被其他系统改动状态必须设计校验机制不能凭空假设“状态文件说完成了就是完成了”。把这三个问题放在业务里验证后再动手比盲目重构要稳妥。6.2 可复用的状态化改造检查清单在项目里落地显式状态机制时可以参考下面的清单逐项核对是否定义了可读的状态 schema而不是临时字典。是否保存了任务目标。是否保存了硬性约束并且这些约束不会因为上下文过长被稀释。是否区分了已完成步骤和待完成步骤。是否记录了最近一次决策包含 action、目标、目的。工具结果是否被提取成摘要并进入 evidence_buffer。完整原始日志是否保留在独立目录状态里只保存引用。是否在关键检查点持久化状态。是否有状态版本号和迁移机制。决策 prompt 是否优先使用状态对象而不是完整历史。是否记录了状态变更 diff便于排查。是否有并发写入控制。是否有状态内容校验逻辑。恢复执行前是否校验状态与真实环境一致。这个清单既是开发补充参考也可以作为代码评审的检查项。6.3 常见坑不要把状态设计成“第二份历史”状态化改造中最典型的问题是把状态对象当成一个大型 JSON 垃圾桶把所有对话消息、工具输出、代码片段全部塞进去。这种做法既没有解决 token 膨胀又破坏了状态的稳定性。正确的做法是只把“决策所需的最小充分信息”放进去。如果一个字段不是下一步动作直接依赖的就不应该出现在状态里。如果它只是排查需要可以放到日志。如果它是模型的完整输出可以按需引用文件路径。第二个常见坑是不给状态加版本。状态 schema 一旦在运行中的项目里被修改旧状态文件就可能失效。解决办法是维护state_version版本变化时执行迁移逻辑。第三个常见坑是状态更新不幂等。同一事件被重复执行时completed_steps 可能被追加两次。解决方式是为每步任务生成唯一 ID执行前先判断该 ID 是否已经出现在 completed_steps 中。第四个常见坑是只在 Agent 结束时保存状态。如果任务中途崩溃所有现场一次性丢失。正确做法是在每个关键步骤完成后保存并让状态保存逻辑尽量轻量。第五个常见坑是状态与完整历史混用后状态形同虚设。模型看到了一个状态对象又看到了 50 条历史消息模型并不知道应该优先相信哪个。实际实现时要么让状态对象成为唯一上下文要么严格限定历史引用范围并明确告诉模型以结构化状态为准。6.4 扩展方向从状态化 Agent 到技能复用理解 SKILL.state 之后可以继续向两个方向深入。第一个方向是状态压缩与自动提炼。现在状态字段主要由开发者手工定义。更成熟的方法是用模型从多轮对话中自动提炼状态生成 schema 后再由开发者审校。这样可以把状态化和具身 Agent 结合减少新任务的建模成本。第二个方向是状态的可迁移性。任务完成后goal、constraints、completed_steps组合在一起本质上就是一个“已经跑通的操作过程”。如果能对状态做脱敏和泛化它就可以沉淀成可复用的技能模板下次遇到类似任务时Agent 可以基于旧状态结构生成新的执行计划而不必从头探索。对普通开发者而言这一步离生产业务比较远。现阶段最值得做的事情是选择一个中等复杂度的 Agent 任务先记录完整历史方案下的 token 消耗和失败率再按本文提到的状态 schema 做一版显式状态方案对比两类指标。SKILL.state 的思路是否适用于自己的业务会很快得到答案。
返回列表