SKILL.state 这个方向,解决的是大模型智能体在长任务里越跑越慢、越跑越容易跑偏的问题。Google 新论文提出用显式执行状态替代不断增长的智能体对话历史,简单说,就是不再让智能体把所有聊天记录一次性塞进上下文,而是把任务进度、变量、结果、错误信息统一抽成一份结构化状态,每次只带当前状态继续推进。这个思路对正在做智能体开发、研究 Agent 工作流、在 Dify 或 Coze 上搭建智能体的同学,值得认真看一遍。
先说结论:这是一个典型的工程化思路,不是单纯靠更强模型解决长任务,而是通过改变信息组织方式来控住上下文、降成本、提升稳定性。下面按“问题在哪、思路是什么、怎么落地、怎么验证、还有哪些边界”拆开讲。
1. 智能体对话历史为什么正在成为瓶颈
1.1 历史越长,模型越容易“记了又忘”
做过智能体开发的人都清楚,目前绝大多数 Agent 的运行方式,是把系统提示词、用户输入、历史对话、工具返回结果全部拼在一起,交给大模型推理。任务简单时没问题,一问一答,上下文很干净。可一旦任务变成多步骤执行,比如让智能体先抓数据、再清洗、再汇总、再生成报告,历史就会一层一层堆上去。
我实测过一个八步左右的文档处理任务,前三步还很正常,到第六七步时,模型开始重复执行已经完成的步骤,甚至把某一步的输出当成另一步的输入。原因不复杂:上下文太长之后,模型对“当前到底在哪一步”的感知会变弱。它不是记不住,而是被大量历史消息稀释了。
对话历史越长,还会带来三个直接问题。
第一是 token 成本上升。每一步都携带全部历史,累积消耗会非常快。任务越复杂,历史越膨胀,钱花得越冤枉。
第二是延迟上升。每次请求的输入长度变大,模型处理时间会明显增加。对用户来说,就是智能体“越用越卡”。
第三是状态一致性变差。智能体每一步的判断都依赖前面的对话内容,但对话内容里既有关键信息,也有大量无关交互。模型并不知道哪些真实有效,容易被噪音带偏。
1.2 截断、摘要、RAG 都不能根治问题
很多人遇到上下文太长,第一反应是截断。把最早的对话砍掉,只保留最近几轮。这个做法在短任务里有效,但在长任务里很危险。早期关键信息一旦被截掉,智能体就会失忆,后面只能靠猜。
第二种常见方案是摘要。用大模型把过去的对话总结成一小段,再放进上下文。摘要确实能压缩长度,但代价是细节丢失。比如“原始文件路径”“已经修正过的错误记录”“用户明确提出的格式要求”,这些信息一旦被概括掉,后面就会出问题。而且摘要本身也是一次模型调用,也有成本和延迟。
第三种方案是 RAG,也就是检索增强。很多同学遇到 Agent 记不住,就想着把历史塞进向量数据库,用相似度检索找回来。这个思路适合外部知识,比如文档库、知识库、FAQ。但它不适合任务执行过程的内部状态。因为执行状态不是“知识”,而是“当前进度”。进度信息具有很强的时序性和因果关系,向量检索没法保证每次都把最关键的那一步找出来。
截断、摘要、RAG 本质都是“在历史视角下做压缩”。它们都没有回答一个问题:智能体执行任务时,到底哪些信息必须存在,哪些信息可以丢掉。
SKILL.state 的切入点就在这里。
2. SKILL.state 的核心思路:用显式状态替代历史
2.1 从“重放对话”到“读取状态”
对话历史记录的是“发生了什么”。它是一份流水账,里面有用户说的话、模型回复、工具调用、报错信息、中间结果。这些东西混在一起,模型每次都要自己判断哪些重要。
显式执行状态记录的是“现在是什么情况”。它是一份结构化快照,包含任务目标、当前步骤、已完成步骤、待办步骤、关键产物、错误记录。模型每次调用时,不需要把整本流水账读一遍,只需要读这份快照。
这个转变很关键。
在传统模式里,一个执行了十步的任务,第十一步的输入可能是几千甚至上万 token 的对话历史。在状态化模式里,第十一步的输入只是一份可能几百 token 的状态对象。上下文长度不再随任务步数无限增长,而是保持在一个相对固定的范围内。
从“重放历史”变成“读取状态”,本质上是把智能体的记忆从隐性记忆切换成显式状态。记忆不再依赖模型从历史里推理出来,而是直接以结构化字段的形式交给模型。
2.2 显式执行状态包含什么
按照目前公开的方向,一个典型的显式执行状态至少应该包含四类信息。
状态对象的第一类是任务元信息。包括任务 ID、任务目标、创建时间、优先级。它用来区分不同任务,也方便系统在多个任务之间做调度和恢复。
第二类是执行进度。当前执行到哪个步骤,哪些步骤已经完成,哪些步骤还在等待,哪些步骤失败过。这类信息是状态对象的核心,它让模型知道自己“站在哪里”。
第三类是产物和变量。已经生成的文件路径、中间结果、关键指标、用户偏好设置。这些信息不需要以对话形式重复出现,只需要用字段保存。下游步骤要用的时候,直接读取字段即可。
第四类是错误和重试信息。最近一次错误是什么,发生在哪个步骤,已经重试了几次。这类信息对调试和恢复非常重要。传统历史模式下,错误信息可能已经被后续对话淹没;状态化模式下,错误信息始终挂在状态对象上,不会被遗忘。
下面给一个简单的状态对象示例,方便理解:
{ "task_id": "sales_report_20250701", "goal": "整理本月销售数据并生成日报", "current_stage": "clean_data", "completed_stages": ["collect_data", "validate_input"], "pending_stages": ["aggregate", "summarize", "render"], "artifacts": { "raw_file": "/data/july.csv", "validated_rows": 12580, "output_path": "/output/report.md" }, "errors": [], "retry_count": 0 }这份状态对象里没有任何完整对话历史,只有当前必需的执行事实。每一步执行完后,系统更新对应的字段,然后进入下一步。
2.3 与 Dify、Coze 平台上“变量”的关系
如果你用过 Dify、Coze 这类智能体平台,会发现里面已经有变量、全局参数、会话上下文、节点输入输出等概念。这些能力和显式执行状态非常接近,但很多人在实际搭建时并没有真正用起来。
常见做法是:在工作流里加几个节点,让大模型自己记着前面节点的结果,在提示词里写“根据前面的对话继续执行”。这样确实能跑,但本质上还是在依赖历史上下文。一旦节点一多,模型就会开始“乱接话”。
更接近 SKILL.state 的做法,是把关键信息全部落到节点变量或全局变量里。每个节点只读取自己需要的变量,而不是直接去看前面所有节点的原始输出。这样即使某个节点失败重跑,也不会污染整个执行上下文。
换句话说,很多智能体平台已经提供了状态化的“硬件设施”,缺的是状态建模的“使用习惯”。
3. 不靠论文源码,也能落地这个思路
3.1 先定义任务状态结构
论文源码公开与否,其实不影响我们把这个思路用到自己的项目里。因为显式执行状态本质上是一个软件工程方案,核心是自己定义状态结构。
第一步,把任务拆成固定步骤。比如“销售日报生成”可以拆成:收集数据、校验数据、清洗数据、汇总指标、生成文案、渲染文件。每个步骤都对应一个状态字段。
第二步,定义每个步骤的输入和输出。输入可以来自用户、来自上游产物、来自配置文件。输出要明确落到哪个字段,存到哪个文件或数据库。
第三步,确定状态对象由谁维护。可以由代码维护,也可以由智能体自己返回新的状态。我建议前几步先由代码维护,等逻辑稳定了再让模型尝试修改状态字段。否则模型一改状态,字段值很容易不符合预期。
状态结构不需要一开始就设计得很复杂。可以先从最核心的几个字段开始:当前步骤、已完成列表、待办列表、关键产物、错误记录。跑通了再逐步增加字段。
3.2 用结构化状态跑通一个最小任务
用一个 Python 类来管理执行状态,是比较轻量的做法。
from dataclasses import dataclass, field from typing import Any, Dict, List @dataclass class ExecutionState: task_id: str goal: str current_step: str = "" completed: List[str] = field(default_factory=list) pending: List[str] = field(default_factory=list) artifacts: Dict[str, Any] = field(default_factory=dict) errors: List[str] = field(default_factory=list) def mark_done(self, step: str, result: Any): if step in self.pending: self.pending.remove(step) self.completed.append(step) self.artifacts[step] = result if self.pending: self.current_step = self.pending[0] else: self.current_step = "done"这个类做的事情很简单:记录当前步骤,完成一步就把它从待办移到已完成,并把结果存到产物字典里。用 dataclass 的好处是结构清晰、容易序列化成 JSON、可以落数据库。
每次调用模型前,把状态对象转成 JSON 放进提示词。模型完成当前步骤后,返回结构化结果,代码更新状态。这样模型不需要记住整段历史,它只需要知道当前状态。
如果你不想写 Python,也可以用 JSON 文件、Redis、数据库表来存储状态。重点不是技术选型,而是不要让状态散落在对话历史里。
3.3 状态化 Prompt 怎么写
状态化改造之后,Prompt 也要跟着调整。传统 Prompt 会写“根据以上对话继续执行”,状态化 Prompt 应该写成“根据当前状态执行当前步骤”。
一个比较通用的模板是这样:
你是任务执行代理。 任务目标:{goal} 当前执行状态:{state} 当前步骤:{current_step} 请只处理当前步骤,不要提前执行后续步骤。 完成后,返回: 1. 当前步骤的结果 2. 更新后的状态对象这个模板的核心是限制模型行为。它不要求模型回忆历史,而是要求模型聚焦当前步骤。很多长任务跑偏,不是因为模型不够聪明,而是因为在历史里看到了太多冗余信息。状态化 Prompt 直接把冗余信息拿掉,模型反而更专注。
这里有一个细节:更新后的状态对象,最好由代码校验,而不是完全信任模型。比如 pending 列表里有三个步骤,模型返回时却移除了两个,代码需要检查这是否符合预期。校验不通过就重试或告警。
3.4 从单任务扩展到批量任务
单任务跑通之后,批量任务才是真正拉开差距的地方。
批量任务的核心是状态隔离。每个任务必须有自己的 task_id,有自己的状态对象,不能共用同一个状态实例。很多批量任务的 bug,都是因为多个任务写到了同一个变量或同一个文件里,导致状态互相覆盖。
建议把状态对象持久化到外部存储。简单场景用 SQLite,复杂场景用 Redis 或 PostgreSQL。持久化的目的有两个:一是任务中断后可以恢复,二是方便查看任务执行的完整轨迹。
失败重试也要基于状态来设计。任务失败时,先读取状态对象,确认失败步骤。然后只重新执行失败步骤,而不是把整个任务重跑一遍。这样可以节省大量时间和 token。
批量任务还需要考虑输出命名。每个任务的输出文件,建议包含 task_id 或时间戳,避免被覆盖。状态对象里保存输出路径,后续汇总阶段再统一读取。
4. 状态化改造后的验证、评估与排查
4.1 先看这几个指标
状态化改造不是把代码写出来就行,还需要用指标验证它真的有效。我建议重点看四个指标。
第一个是上下文长度曲线。用全历史模式和状态化模式分别跑同一个多步骤任务,记录每一步的输入 token 数量。全历史模式通常是线性增长,状态化模式应该保持在一个相对平台期。如果状态化之后 token 仍然快速增长,说明状态对象里塞了太多不必要的内容。
第二个是任务成功率。把任务重复跑多遍,统计最终完成目标的比例。长任务尤其要看“步骤顺序是否稳定”,也就是模型有没有跳过步骤、重复步骤、顺序颠倒。
第三个是恢复能力。在任务执行到中途时手动中断,然后从状态对象恢复,看它能否从断点继续,而不是从头开始。这个指标直接反映状态化方案是否真正落地。
第四个是资源占用。状态对象存储在内存、数据库、文件系统中的大小,以及任务执行过程中的内存变化。正常情况下,状态对象应该远小于同等步数的对话历史体积。
4.2 状态丢失和状态冲突怎么排查
状态化改造之后,常见的问题不再是上下文溢出,而是状态本身出了问题。我这里列几条排查顺序。
第一步,先看状态是否更新。如果任务重复执行同一个步骤,先检查代码是否调用了 mark_done,或者模型返回的更新后状态是否被正确写入。很多时候是状态对象没有回写到存储,导致下一次读取到旧状态。
第二步,看状态是否有冲突。多个任务共用同一个状态文件、同一个全局变量时,容易出现交叉污染。排查时先看 task_id 是否唯一,再看读写状态的地方是否存在并发问题。
第三步,看状态对象是否过大。如果状态里塞了完整的历史记录、长文本内容、Base64 图片,状态对象本身会变得很大,上下文长度问题会重新出现。状态对象应该只保存“路径、摘要、关键字段”,而不是保存全量原始数据。
第四步,看模型是否忽略了状态。如果提示词已经给了当前状态,模型仍然输出历史内容,那可能是状态放在提示词里不够醒目,或者格式太复杂。可以简化状态字段,把当前步骤放到最前面。
4.3 哪些场景不适合硬套状态化
状态化不是万能的。有些场景仍然需要完整的对话历史。
比如开放式的头脑风暴场景。用户和智能体来回讨论想法,每一步的可能方向都依赖于前面讨论的细节。这种场景下,如果把历史压缩成状态,反而会丢失讨论脉络。
再比如需要严格保留原文表述的任务。法律文书、合同审阅、文学创作等场景,用户往往需要看到完整的前后文,而不是摘要。强行用状态对象替换历史,产出质量会下降。
还有一种情况是状态对象本身非常复杂。比如每一步都有大量中间产物,每个产物都需要完整保存。如果状态对象已经膨胀到和历史差不多大,那状态化的优势就不存在了。这时候应该重新做任务拆解,把大步骤拆成更小的独立任务,而不是继续往状态对象里塞东西。
判断标准很简单:如果任务执行过程是“有明确步骤、有清晰产物、可以断点续跑”,就适合状态化。如果任务是“自由探索、强依赖上下文语气和细节”,就不适合。
5. 从一篇论文到工程实践:落地建议
5.1 先做小规模原型,不要直接重构
看完论文思路后,最忌讳的是立刻把所有智能体工作流全部改成状态化。更稳妥的做法是选一个重复性最高的任务做小规模原型。
比如周报生成、销售数据整理、多文档归纳,这些任务步骤固定、产物明确,非常适合状态化。
先用代码搭一个最小状态管理器,再挑三条真实任务跑一遍。重点看两个东西:一是状态化之后模型的输出质量有没有下降,二是 token 成本和任务耗时的变化。如果小规模验证效果好,再考虑接入正式流程。
如果只是学习,不需要一开始就追求完美。把状态对象用 JSON 文件保存,把提示词模板写好,已经能明显感受到上下文变短带来的变化。
5.2 在成熟智能体平台上怎么试
如果你主要使用 Dify、Coze 这类平台,不写代码,也可以实现简化版的状态化。
思路是这样的:把工作流中的关键节点输出,全部写入变量或全局参数。后续节点只引用这些变量,而不是引用“会话历史”。比如一个做销售分析的智能体,收集节点把原始文件路径写入变量,清洗节点把清洗后的数据行数写入变量,汇总节点只读取这两个变量。
这种做法的好处是,每个节点都只依赖自己需要的变量,不依赖模型从前面的对话中自己找信息。平台已经支持变量和状态记忆,关键是你有没有主动把信息“落下”来。
在平台上做状态化还有个额外好处:调试时可以直接看变量值,不用翻聊天记录。任务失败时,也能更快定位到是哪个节点的输入出了问题。
5.3 多智能体协同时的状态同步问题
状态化思路在多智能体场景下更有价值。
多智能体系统里,每个子智能体如果都携带完整对话历史,整个系统的 token 消耗会非常恐怖。更合理的做法是,把全局状态放在一个共享状态库,每个子智能体只需要读写自己负责的那部分状态。
比如一个“数据采集智能体”负责读取文件,一个“数据分析智能体”负责统计指标,一个“报告智能体”负责生成文案。它们之间不需要共享全部对话,只需要共享任务状态和关键产物。数据采集智能体完成采集后,把文件路径写入状态库。分析智能体读取路径,处理完把结果写入另一个字段。报告智能体最后读取汇总字段。
这种模式下,每个智能体的上下文都保持在很小的范围,任务却可以无缝衔接。多智能体真正需要的,不是多一个对话窗口,而是统一的状态同步协议。
5.4 我建议的关注顺序
整个思路真正落地时,最该盯住的不是模型选型,也不是 Prompt 技巧,而是状态结构的设计、持久化和恢复机制。
先定义状态字段,再写状态更新逻辑。先保证状态能存下来,再考虑让模型直接修改状态。先跑通一个任务,再扩展批量。每一步都围绕“显式执行状态”来组织,历史对话反而变成了辅助信息。
SKILL.state 本质上是在提醒我们:智能体不需要记住所有东西,它只需要知道自己现在在哪、要去哪、手里已经有什么。把这个事实用结构化的方式固定下来,长任务才会真正稳定下来。
如果你正在做智能体开发,遇到长任务容易跑偏、上下文很快用完、成本一直压不下去,可以试试这个思路。不一定要等论文源码,先从一个任务的状态对象开始改,大概率会有立竿见影的体感变化。