Agent 状态栏:通过元信息增强 Agent 轨迹管理
提示工程给的是静态指令,而 Agent 执行中还需要动态感知自身状态与任务进展。Agent 状态栏把任务进度、环境变化、工具调用计数等运行时状态整理成结构化摘要,由框架在上下文末尾持续注入。类比手机屏幕顶部始终显示时间、电量、信号——模型每次生成新回复时都能「瞥一眼」,据此做出更准确的决策。
何时使用
- Agent 反复执行相同工具调用、陷入无限循环(如超过次数限制仍在拨打)
- 模型数不清「已经调用了几次」「还剩几项 TODO」,违反显式约束
- 长任务中每次迭代的思考 token 量随上下文变长而持续增长
- Agent 过分关注局部子任务,忘记用户原始诉求和核心约束
- 小模型需要接近前沿模型的任务遵循能力
- 设计状态消息的注入方式与更新策略
核心原则
- 状态栏不是对话主体内容:它不属于用户消息、模型输出或工具结果,而是框架自动生成的状态摘要,注入在上下文最末尾,紧邻模型即将生成的新 token。
- 理论基础是「上下文学习是检索而非推理」:模型擅长查找,不擅长在一次前向传播中主动归纳统计。「已经打了几次电话」这类知识以原始记录形式分散在上下文里,模型每次决策都要花额外思考 token 扫描重算,效率极低且错误率高。
- 本质是把隐式状态提炼为显式知识:原始轨迹高度冗余,大量 token 中只含少量关键状态信息;状态栏以极低的额外 token 成本,呈现原本需要扫描数千 token 才能获得的信息。
- 显式操纵注意力分配:长上下文中早期目标和关键约束容易被后续工具结果淹没;结构化元信息放在末尾,空间上更接近新 token,获得更高注意力权重——一种「强制性的注意力引导」。
- 实测收益:提供提前算好的状态栏后,较小开源模型的准确率可以接近前沿大模型;每次迭代的思考 token 量、延迟和花费均降低约一个数量级。不带状态栏时思考量随上下文变长持续增长,带上后基本恒定。
- 状态栏是上下文压缩技术之一:它用代码确定性地维护「关于轨迹的结论」,与 LLM 驱动的压缩互补。
- 无侵入性:所有元信息以人类可读形式出现在上下文里,开发者随时可检查;不需要微调,直接在任何语言模型上起效。
实践模式
1. 注入位置:一条 user 角色的消息,放在最末尾
messages: [
{ role: "system", content: "You are a customer service assistant..." } ← 固定,KV Cache 已缓存
{ role: "user", content: "Help me cancel my Xfinity plan" }
{ role: "assistant", content: null, tool_calls: [...] } ← 第 1 轮决策
{ role: "tool", content: "Call log..." }
{ role: "assistant", content: null, tool_calls: [...] } ← 第 2 轮决策
{ role: "tool", content: "Call log..." },
{ role: "user", content: "Can you call them again to follow up?" },
{ role: "user", content: "<agent_status>
Current State:
- phone_call invoked 3 times (Xfinity: 3/3 max)
- Current time: 2025-09-14 10:30:45
- TODO: [1] Cancel plan (in_progress)
</agent_status>" } ← 框架注入的状态栏
]
- 为什么是 user 角色而不是改 system:修改 system 消息会破坏整个前缀的缓存。这里的 user 角色只是 API 协议层面的技术选择,不等同于「来自终端用户的输入」——Harness 借用这个消息槽位,挂载框架自动生成的系统状态信息。
- 用
<agent_status> 标签包裹,便于模型识别其特殊性质。
- 因为是追加而非修改,前面所有已缓存内容都不受影响。
2. 状态栏的三类构成
- 任务规划:TODO 列表把复杂多步骤任务分解为清晰步骤,放在轨迹末尾,不断提醒模型当前进展和后续目标,确保行动与总体规划一致(防止只关注局部子任务)。
- 事件的侧信道信息(Side-channel):为每个事件附加元数据——精确时间、地理位置、距上次 Agent 回复的时间间隔。通常随对应事件一起追加。
- 环境当前状态的观察摘要:系统时间、工作目录、异常操作提醒(「该工具已被重复调用 N 次」)、以及从隐式状态到显式观察的转换。随任务推进不断更新。
3. 状态更新的两种实现与缓存代价
实现一:每轮替换——每次 API 调用前移除上一轮状态消息,在末尾追加最新状态。保证永远只有一份最新状态,但移除旧状态会使其位置之后的所有缓存失效(与「动态时间戳」同一失效机制,区别仅在于状态消息位于末尾,失效范围只覆盖上次注入后新增的消息,通常是一轮,整个前缀仍可复用)。
实现二:持久追加——状态消息一旦注入就永久留在轨迹中,每轮只在末尾追加新状态。Claude Code 的 <system-reminder> 即此方式,历史状态保留在 transcript 中从不删改。对缓存完全友好(只追加不修改,前缀始终稳定),代价是陈旧状态累积,既占 token 又要求模型自己关注最新一条。
选择规则:
- 状态很小、两次更新间产生的消息很多、会话长度受控 → 选实现二(保留旧状态通常比反复重算长后缀便宜)
- 状态较大、更新频繁或轨迹很长 → 选实现一(只使上次注入后的短后缀失效,同时避免陈旧状态持续累积)
粗略成本模型:设每条状态 S token,两次更新间新增后缀 R token,预计更新 N 次,缓存输入单价为普通输入的 α 倍:
C_替换 ≈ (N-1) * (1-α) * R
C_追加 ≈ α * S * N * (N-1) / 2
→ 当 α*S*N/2 < (1-α)*R 时倾向实现二,否则倾向实现一
该估算未计上下文占用和陈旧状态带来的歧义,实际选择还应结合服务商缓存计费与实测命中率。
4. 五种状态栏技术(实验 2-9)
- 时间戳跟踪:以
[2025-09-14 10:30:45] 格式作为前缀添加到用户消息和工具响应中(不是放在系统提示词里,否则破坏 KV Cache)。让 Agent 理解时序关系,也为调试和审计提供信息;配合时间模拟可理解「昨天的文件」和「今天的修改」。
- 工具调用计数器:维护全局字典记录每个工具被调用次数,响应中标注
Tool call #3 for 'read_file'。显式计数触发模型的模式识别:第一次失败后检查路径,第二次失败后列出目录,第三次主动放弃并找替代方案。深层价值是隐式的成本感知。
- TODO 列表管理:提供
rewrite_todo_list 和 update_todo_status 两个工具,每项含唯一标识符、内容、状态(pending / in_progress / completed / cancelled)和时间戳。借鉴 Manus「通过复述操纵注意力」理念。实验数据:启用 TODO 的 Agent 平均 15 次迭代完成任务,禁用时需 21 次且经常遗漏子任务。
- 详细错误信息:四层内容——错误类型和描述、完整参数的 JSON、调用栈信息、针对性修复建议(如 FileNotFoundError 时建议验证路径、检查工作目录、使用绝对路径)。启用后 Agent 在错误场景找到替代方案的成功率从 60% 提升到 95%,从盲目重试转变为有针对性地分析。
- 系统状态感知:注入当前时间、工作目录、操作系统类型、Shell 环境和 Python 版本。工作目录跟踪尤其关键——Agent 执行
cd 后自动更新;OS 信息让 Agent 做平台相关决策(Linux 用 apt、macOS 用 brew)。
这些技术单独使用效果有限,组合起来会产生涌现效应:时间戳 + 工具计数器让 Agent 理解操作的频率和时间分布;TODO + 系统状态让 Agent 根据环境调整策略;详细错误 + 工具计数器让 Agent 多次失败后不仅改变策略,还理解失败原因。
常见陷阱
- 把时间戳写进 system prompt:直接破坏 KV Cache 前缀,必须作为消息前缀或末尾状态注入。
- 让 LLM 一次性批量统计状态:模型几乎无条件地相信状态栏——你写「打了 3 次电话」,它就当真是 3 次,不会自己重算;而 LLM 做数量统计本来就容易出错。状态栏尽量用代码维护,实在要用 LLM,也要逐条抽取、再由代码汇总。
- 忽视状态栏投毒风险:状态栏信息被模型高度信任,一旦摘要内容来自可被外部污染的数据源(如把外部网页片段直接写进状态栏),这种信任会被反向利用。
- 状态栏够用就整段删掉原始记录:状态栏是对原始上下文的有损投影,只提前算了「你预想会被问到」的维度。计数、状态跟踪这类任务可以只保留状态栏以节省大量 token;但只要有一个问题涉及状态栏未计算的维度,仅保留状态栏就会导致准确率断崖式下降。删除前确认没有未覆盖的查询维度。
- 状态消息无限累积从不清理:实现二的代价,陈旧状态既占 token 又制造歧义,会话长度失控时应切换到实现一。
- 用状态栏替代真正的任务规划:TODO 列表需要配套工具支持状态流转,只在提示词里写「请跟踪进度」不够。
配套代码
chapter2/system-hint/ — 实验 2-9(agent-status-bar 框架):实现时间戳、工具计数器、TODO 列表、详细错误、系统状态五种状态栏技术,可独立开关;python main.py --mode preview 无需 API key 即可对比有无状态栏时模型看到的上下文差异;python run_experiment_2_8.py 跑冻结的对照实验。
深度阅读
book/chapter2.md「Agent 状态栏:通过元信息增强 Agent 轨迹管理」