Agent 状态栏

agent 打了 3 次电话后常数不清打了几次,又打第 4 次——因为“已打几次”这个知识没被提炼出来,而是散在原始记录里。状态栏把分散在上下文各处的隐式状态,提炼成末尾一条可直接检索的显式知识。

提示工程解决了“给模型什么样的静态指令”的问题。但在实际执行中,agent 还需要动态感知自身状态和任务进展——这就是 Agent 状态栏(Agent Status Bar)的用武之地。它是 agent 框架向模型同步各种动态状态的统一机制。

这个概念最好的类比是操作系统的状态栏。用手机时,屏幕顶部始终显示时间、电量、信号、通知数——这些不是 App 的主界面内容,但你随时可以瞥一眼掌握设备状态。Agent 状态栏对模型起完全相同的作用:它不是对话主体(不属于用户消息、模型输出或工具结果),而是框架在上下文末尾持续注入的状态摘要——“你已经打了 3 次电话”“当前时间是 10:30”“TODO 还剩 2 项”。系统提示词是入职时发的员工手册,一旦定下就不变;状态栏则像贴在屏幕边缘的实时仪表盘,随任务推进不断更新。

理论基础:只有一半的检索引擎

状态栏之所以有效,源于注意力机制的一个本质特性:上下文学习更像检索而非推理。一个形象的说法是:上下文窗口是一台只有一半的检索引擎。它“检索”的这一半非常强——你问什么,注意力就能从成千上万 token 里把相关记录捞出来;但它缺了另一半,没有“提炼层”:上下文里的东西从不会被自动数一遍、建索引、或就地总结成结论。任何“关于这些内容的结论”——一共多少条、有没有超标、进展到哪——模型每次要用都得从原始记录现算一遍,而现算的代价随上下文堆积量 N 一起上涨。

一个实际场景:系统提示词要求拨打每个商家不超过 3 次,但打了 3 次后 agent 经常数不清、又打第 4 次,甚至陷入循环。根源在于“已经打了几次”没被自动提炼,而是以原始通话记录散在 KV Cache 里。而当我们在每个电话的工具结果中直接加入“本次是第 3 次呼叫该商家”,模型就能立即发现已达限制。这种机制的本质是把分散在上下文各处的隐式状态,提炼为可直接使用的显式知识——以极低的额外 token,呈现原本需要扫描数千 token 才能获得的信息。此外,把关键元信息放在上下文末尾,在空间上更接近模型即将生成的新 token,因而获得更高的注意力权重。

这套“提前算好、直接查一眼”的做法有个统一的名字,叫上下文蒸馏(Context Distillation),状态栏是它最日常的形态。一个跨三类任务(计数、规则归纳、状态跟踪)、11 个模型、近 2.4 万次评测的基准量化了它:给弱模型配上提前算好的状态栏,最弱的几个准确率能涨 40 到 54 个百分点,一个 2B 的本地小模型甚至追平了不带状态栏的前沿大模型;强模型本来就答得对,省下的是效率——思考量、延迟、花费各降约一个数量级。最本质的变化是:不带状态栏时每次查询的思考量随上下文变长而持续增长,带上后变得基本恒定。一个细节:状态栏要写成 衣物:9 件(合格 7、次品 2) 这样能一眼定位的键值对,而不是一段大白话——散文写法反而更差,因为模型还得先把它读一遍、解析出来,等于又回到了“扫描”。

“提前算好”这件事,做对和做错是天壤之别。三条能直接照做的经验:

  1. 状态栏要用代码维护,别拿大模型去维护。 实验里一个 20 行的正则函数就能达到“标准答案”级的准确度;而让前沿大模型一次性读完整段历史、吐出统计,反而在多数格子上出错,把下游准确率拖得比根本不用状态栏还低——让 LLM 批量统计长历史,等于把“扫描整段上下文”原封不动搬了个家。能用代码算就用代码算;实在要用 LLM,也要逐条抽取、再由代码汇总,绝不让它一次性批量统计。
  2. 想删掉原始上下文之前,先确认状态栏覆盖了所有会被问到的问题。 状态栏是对原始上下文的一次有损投影,只算了你预想会被问到的维度。若够用(计数、状态跟踪这类任务就是如此),可以把原始记录整段删掉、只留状态栏,省下大把 token;可一旦有问题落到没算过的维度上,一条看着像样、实则答非所问的状态栏就成了理直气壮把模型带偏的“假权威”——论文里状态栏只存“两两组合”的计数、却去问“三者交叉”,只留状态栏时 Claude 的准确率从 100% 断崖跌到 7.6%。所以把“新增一种问法”当成一次改表结构:要么先给状态栏加字段,要么这次就别删原文。
  3. 把状态栏的准确率当成一线生产指标来盯。 模型几乎无条件地相信状态栏——你写“打了 3 次”它就当真,既不核对也不重算。这既是它有效的原因,也意味着一旦写错,错误会原样传进最终答案(容错空间不算小,数错 10% 以内收益还能保住大半,越过这条线就可能比不带还糟)。这也接上状态栏投毒风险:状态栏的信息越是来自对真实世界的可靠观测越好,绝不能来自可被外部污染的数据源。

更深一层:喂进模型想不出来的信息

提炼隐式状态、操纵注意力,解释了状态栏为什么好用;但还有更深的一层——状态栏之所以有效,根子上是因为它给模型喂进了它自己想不出来的信息

让模型变强通常有两条路:想得更久(更长的思维链)和试得更多(采样多个答案挑最好的)。但两条路有共同的天花板——都只在模型自己的脑子里打转,用同一套固定权重和同一段固定上下文,变不出上下文里原本没有的新信息,只能把已有信息重新排列组合。真正能突破天花板的是第三条路,也就是交互:模型先给出一个东西,让外部的“仪器”观察它在真实世界里的表现,再把观察写回上下文让模型修正。关键在于,这个观察是模型光靠想想不出来的——代码到底有没有通过测试、渲染出来的按钮有没有跑出屏幕、这一步操作后系统状态变成了什么样,这些是“跑一下、量一下”才知道的事实,携带着权重和上下文里都不存在的新信息。

状态栏正是这条原理最日常的落地:Harness 就是那台“仪器”,持续观察真实运行状态(打了几次电话、当前时间、任务进展、某工具是否报错),把观察压缩成一小段写回上下文。所以状态栏里最有价值的,往往不是模型本可以自己扫一遍数出来的东西(那只是替它省力),而是它根本无从推断的外部事实。这也给出一条设计原则:注入的信息越是来自对外部世界的真实观测,价值越高;若是拍脑袋编的、或来自可被污染的数据源,这台“仪器”就会读出错误的刻度,反而误导模型——这正对应前面的状态栏投毒风险。

站在这个视角看第一章演进弧线末端的 Loop 工程,会发现它本质上是把“交互”这条第三轴工程化:循环每转一圈之所以有真实进步,是因为验证环节把外部世界的观测写回了上下文,注入了模型自己想不出来的新信息;抽掉这一步,循环只是让模型把旧信息原地翻来覆去地重排。业界“循环的瓶颈在验证器,而不在模型”的共识说的正是同一件事——衡量改进的“尺子”本身必须扎根真实观测,否则循环会悄无声息地空转。

状态栏的构成

  • 任务规划:长任务里 agent 容易过分关注当前局部子任务,忘记原始诉求与核心约束。TODO 列表把任务分解为清晰步骤,放在轨迹末尾不断提醒当前进展与未来目标。
  • 事件的侧信道信息:为每个事件附加元数据——精确时间、地理位置、距上次回复的时间间隔等,帮助模型理解时序与环境背景。
  • 环境的当前状态:系统时间、工作目录、异常操作提醒(“该工具已被重复调用 N 次”),把隐式状态转成显式。
  • 可用能力清单:所有已安装 Skill 的元数据列表也走这条同一的末尾注入通道。

侧信道信息和能力清单一经添加就不再改变,对 KV Cache 友好;任务规划和环境状态是动态变化的,需要以特殊的 user 消息追加到末尾并不断更新。

在上下文中的位置

一个重要的实现细节:状态栏在 API 层面作为一条 user 角色的消息插入到上下文末尾——而不是修改开头的 system 消息。原因正是 KV Cache 约束:修改 system 消息会破坏整个前缀。这里的 user 角色只是 API 协议层面的技术选择,并不等同于“来自终端用户的输入”——框架借用 user 槽位,向模型注入自动生成的系统状态,用 <agent_status> 标签包裹以便模型识别其特殊性质。因为它在最末尾、紧邻即将生成的新 token,获得最高注意力权重;又因为是追加而非修改,前面所有已缓存内容都不受影响。

状态更新的两种实现与缓存代价

“追加不破坏缓存”只在单次注入时成立。状态会变——下一轮 TODO 完成一项、计数加一,状态消息就过时了。两种实现各有明确代价:

  • 每轮替换:每次调用前移除上一轮状态、在末尾追加最新状态。保证只有一份、永远最新;但移除旧状态会使其位置之后的缓存失效(失效范围限于最近几轮)。
  • 持久追加:状态一旦注入就永久留在轨迹中,每轮只在末尾追加新状态(Claude Code 的 <system-reminder> 即此)。对缓存完全友好;代价是陈旧状态会累积,要求模型自己关注“最新一条”。

经验法则:状态更新频繁且轨迹很长时选持久追加(反复的缓存失效代价远超陈旧状态占的 token);轨迹较短或单条状态很大时选每轮替换(末尾几轮的失效本就便宜,换来整洁无歧义)。

从读数到策略:物理时间感知

时间戳和工具计数看似两条互不相干的元信息,放在一起看却指向同一种更本质的能力——让 agent 感知物理时间并据此调节节奏。当下的 agent 无论你说“三分钟”还是“三十分钟”产出几乎没区别。这种缺失的能力可拆成三个轴:紧迫度(把力气匹配到时钟:紧就果断交付,宽裕就再往深挖)、坚持度(分清真墙和假墙:别对 410 Gone 的接口重试五次,也别搜两次没结果就断言查无此信息)、警觉度(把工具响应上的时间异常升级成值得追查的假设)。

但有一个最值得记住的发现:光把读数摆到模型面前,并不足以改变它的行为。在一个专门测量时间感的基准上,同一批任务放在四种条件下运行——什么都不给、只给原始时间戳、给时间戳外加一份“这些读数该怎么用”的操作手册、让 agent 自己上报节奏状态。结果相当反直觉:只给原始时间戳这一档,和什么都不给几乎没区别(相差不过两三个百分点);真正把通过率从一成出头拉到四五成的(幅度 +19 到 +49 个百分点),是那份操作手册。模型确实“看见”了 elapsed_ms=5000 expected_ms=500 这行读数,却不会自动据此改变节奏——它缺的不是读数,而是拿读数该怎么办的策略。这也不是某一家模型的毛病:跨四个厂商家族的六个模型(Claude、Gemini、GPT、Qwen),不加操作手册时通过率无一例外趴在一成出头的地板上,说明“缺时间感”是当前后训练普遍漏掉的一项控制,而非某个模型不够聪明。

所以一条真正管用的“节奏状态栏”,必须把读数(用了多久、这个工具慢不慢、这堵墙撞了几次)和一小段操作策略(时间紧就交付、慢调用要诊断、真墙就绕开)成对给出,缺一不可。

设计哲学

这套技术有一个实用的优点:所有元信息都以人类可读的形式出现在上下文里,开发者随时可以检查 agent 拿到了哪些信息、做了什么决定。更重要的是,它对模型没有侵入性——不需要微调,直接在任何语言模型上都能起效,可以一项项叠加着尝试。

工程实践

Zapvol 的状态栏落在两个规划工具上——write_todos 清单与 plan+advance 节拍器。二者都用代码确定性维护(对应上面第一条经验,不让 LLM 批量统计):write_todos 把任务分解成带状态(pending / in_progress / completed)的清单,plan+advance 以阶段节拍推进,二者的当前状态作为 <agent_status> 块追加到上下文末尾、每步随真实进展更新。待办清单同时是压缩地板摘要里 ## Current state 一节的来源——把“做了什么、还差什么”留在轨迹里,也留进摘要。

相关阅读

  • 动态提示词与 Skills——可用能力清单走的是同一条末尾注入通道。
  • KV Cache 设计——“动态信息追加末尾、静态信息保持不动”在状态栏场景的应用。
  • 上下文压缩——状态栏“加”结论、压缩“换”结论,是同一枚硬币的两面。

来源

  • Li, Bojie and Noah Shi. Distill, Don’t Retrieve: Inference-Time Context Distillation for LLM Agent Reasoning. 2026
  • Li, Bojie and Noah Shi. Agents That Sense Physical Time. 2026
  • Li, Bojie and Noah Shi. Interaction Scaling: Grounding the Third Axis of Test-Time Compute. 2026
这页有帮助吗?