--- name: agent-state-bar description: Agent 陷入无限循环、数不清工具调用次数、遗忘 TODO 或目标偏离、长任务中思考 token 持续膨胀时使用——Agent 状态栏机制、作为 user 消息注入末尾的 KV Cache 理由、每轮替换与持久追加两种实现及成本模型、五种状态栏技术(时间戳、工具计数器、TODO、详细错误、系统状态)与维护铁律。 --- # Agent 状态栏:通过元信息增强 Agent 轨迹管理 提示工程给的是静态指令,而 Agent 执行中还需要动态感知自身状态与任务进展。**Agent 状态栏**把任务进度、环境变化、工具调用计数等运行时状态整理成结构化摘要,由框架在上下文末尾持续注入。类比手机屏幕顶部始终显示时间、电量、信号——模型每次生成新回复时都能「瞥一眼」,据此做出更准确的决策。 ## 何时使用 - Agent 反复执行相同工具调用、陷入无限循环(如超过次数限制仍在拨打) - 模型数不清「已经调用了几次」「还剩几项 TODO」,违反显式约束 - 长任务中每次迭代的思考 token 量随上下文变长而持续增长 - Agent 过分关注局部子任务,忘记用户原始诉求和核心约束 - 小模型需要接近前沿模型的任务遵循能力 - 设计状态消息的注入方式与更新策略 ## 核心原则 - **状态栏不是对话主体内容**:它不属于用户消息、模型输出或工具结果,而是框架自动生成的状态摘要,注入在上下文**最末尾**,紧邻模型即将生成的新 token。 - **理论基础是「上下文学习是检索而非推理」**:模型擅长查找,不擅长在一次前向传播中主动归纳统计。「已经打了几次电话」这类知识以原始记录形式分散在上下文里,模型每次决策都要花额外思考 token 扫描重算,效率极低且错误率高。 - **本质是把隐式状态提炼为显式知识**:原始轨迹高度冗余,大量 token 中只含少量关键状态信息;状态栏以极低的额外 token 成本,呈现原本需要扫描数千 token 才能获得的信息。 - **显式操纵注意力分配**:长上下文中早期目标和关键约束容易被后续工具结果淹没;结构化元信息放在末尾,空间上更接近新 token,获得更高注意力权重——一种「强制性的注意力引导」。 - **实测收益**:提供提前算好的状态栏后,较小开源模型的准确率可以接近前沿大模型;每次迭代的思考 token 量、延迟和花费均降低约一个数量级。不带状态栏时思考量随上下文变长**持续增长**,带上后**基本恒定**。 - **状态栏是上下文压缩技术之一**:它用代码确定性地维护「关于轨迹的结论」,与 LLM 驱动的压缩互补。 - **无侵入性**:所有元信息以人类可读形式出现在上下文里,开发者随时可检查;不需要微调,直接在任何语言模型上起效。 ## 实践模式 ### 1. 注入位置:一条 user 角色的消息,放在最末尾 ```text 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: " 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) " } ← 框架注入的状态栏 ] ``` - **为什么是 user 角色而不是改 system**:修改 system 消息会破坏整个前缀的缓存。这里的 user 角色只是 API 协议层面的技术选择,**不等同于「来自终端用户的输入」**——Harness 借用这个消息槽位,挂载框架自动生成的系统状态信息。 - 用 `` 标签包裹,便于模型识别其特殊性质。 - 因为是追加而非修改,前面所有已缓存内容都不受影响。 ### 2. 状态栏的三类构成 - **任务规划**:TODO 列表把复杂多步骤任务分解为清晰步骤,放在轨迹末尾,不断提醒模型当前进展和后续目标,确保行动与总体规划一致(防止只关注局部子任务)。 - **事件的侧信道信息(Side-channel)**:为每个事件附加元数据——精确时间、地理位置、距上次 Agent 回复的时间间隔。通常随对应事件一起追加。 - **环境当前状态的观察摘要**:系统时间、工作目录、异常操作提醒(「该工具已被重复调用 N 次」)、以及从隐式状态到显式观察的转换。随任务推进不断更新。 ### 3. 状态更新的两种实现与缓存代价 **实现一:每轮替换**——每次 API 调用前移除上一轮状态消息,在末尾追加最新状态。保证永远只有一份最新状态,但移除旧状态会使其位置之后的所有缓存失效(与「动态时间戳」同一失效机制,区别仅在于状态消息位于末尾,失效范围只覆盖上次注入后新增的消息,通常是一轮,整个前缀仍可复用)。 **实现二:持久追加**——状态消息一旦注入就永久留在轨迹中,每轮只在末尾追加新状态。Claude Code 的 `` 即此方式,历史状态保留在 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 轨迹管理」