Skip to main content

j_agent/context/
policy.rs

1//! 上下文优先级策略 — 统一源头
2//!
3//! 把原先散落在多处的"工具重要性"声明收敛到本模块:
4//! - `context/compact.rs::BUILTIN_EXEMPT_TOOLS`(micro_compact 豁免)
5//! - `context/window.rs::MessageUnit::priority`(窗口优先级)
6//! - `context/window.rs::has_exempt_tool`(Stage 2 豁免保底)
7//!
8//! 每个工具声明一份 `ToolContextPolicy`(tier + retention),
9//! micro_compact / window / UI 全部查询 `policy_for()` 获取策略,
10//! 而不是各自维护列表。新增工具时只需在下方 match 里加一行即可同步所有路径。
11//!
12//! 所有工具名称通过 `tools::tool_names` 模块的常量引用,避免硬编码字符串。
13//!
14//! 参考用户诉求:
15//! > 用户消息 > 重要 Tool 消息(PLAN、WORKTREE、ASK、LOADSKILL)> 帮手消息 > 其他工具消息
16//! > 其中 PLAN/WORKTREE/ASK/LOADSKILL 的 tool call 与 tool result 可根据特点进行重要性保留
17
18use crate::tools::tool_names;
19
20/// 消息优先级层级(数值越小优先级越高,用于 window 选择)
21///
22/// - `System`:始终保留(不计配额)
23/// - `User`:用户输入,兜底保证最新一条必保留
24/// - `KeyTool`:承载决策/协议/工作流状态的关键工具,Stage 2 豁免保底
25/// - `Assistant`:帮手纯文字回复
26/// - `RegularTool`:常规执行类工具,预算紧张时优先丢弃
27#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
28pub enum ContextTier {
29    System = 0,
30    User = 1,
31    KeyTool = 2,
32    Assistant = 3,
33    RegularTool = 4,
34}
35
36impl ContextTier {
37    /// 数值形式的优先级(与原 `MessageUnit::priority` 对齐,越小优先级越高)
38    pub fn priority(self) -> u8 {
39        self as u8
40    }
41}
42
43/// 保留策略 — micro_compact / auto_compact 根据此决定如何处理历史 tool result
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum RetentionPolicy {
46    /// 永不压缩:micro_compact 跳过、window 在 Stage 2 豁免保底保留
47    /// 适用于承载决策/协议/技能指令等不可丢失内容的工具
48    AlwaysPreserve,
49    /// 压缩为简短占位符 `[Previous: used X]`(当前 micro_compact 默认行为)
50    Placeholder,
51}
52
53/// 工具的上下文处理策略
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct ToolContextPolicy {
56    pub tier: ContextTier,
57    pub retention: RetentionPolicy,
58}
59
60impl ToolContextPolicy {
61    const fn key_preserve() -> Self {
62        Self {
63            tier: ContextTier::KeyTool,
64            retention: RetentionPolicy::AlwaysPreserve,
65        }
66    }
67    const fn regular_placeholder() -> Self {
68        Self {
69            tier: ContextTier::RegularTool,
70            retention: RetentionPolicy::Placeholder,
71        }
72    }
73}
74
75/// KeyTool + AlwaysPreserve 的工具名单(静态常量,UI 展示用)
76///
77/// 所有名称均引用 `tool_names` 常量,与各工具的 `NAME` 定义自动对齐。
78/// 新增 KeyTool 时必须同时更新此列表和 `policy_for()` 中的 match 分支。
79/// 与 `policy_for()` 保持一致通过 debug_assert 校验(见 tests)。
80pub const KEY_TOOL_NAMES: &[&str] = &[
81    // 用户明确点名的"重要 Tool"
82    tool_names::ENTER_PLAN_MODE,
83    tool_names::EXIT_PLAN_MODE,
84    tool_names::ENTER_WORKTREE,
85    tool_names::EXIT_WORKTREE,
86    tool_names::ASK,
87    tool_names::LOAD_SKILL,
88    // 工作流/任务记账 — 承载 todo/子任务状态
89    tool_names::TODO_WRITE,
90    tool_names::TODO_READ,
91    tool_names::TASK,
92    // 协作类 — 承载 teammate/subagent 会话上下文
93    tool_names::AGENT,
94    tool_names::SEND_MESSAGE,
95    tool_names::TEAMMATE,
96];
97
98/// 查询指定工具的上下文策略
99///
100/// 未知工具按 RegularTool + Placeholder 处理(最保守策略)。
101pub fn policy_for(tool_name: &str) -> ToolContextPolicy {
102    match tool_name {
103        // Key tools — Always preserve
104        tool_names::ENTER_PLAN_MODE
105        | tool_names::EXIT_PLAN_MODE
106        | tool_names::ENTER_WORKTREE
107        | tool_names::EXIT_WORKTREE
108        | tool_names::ASK
109        | tool_names::LOAD_SKILL
110        | tool_names::TODO_WRITE
111        | tool_names::TODO_READ
112        | tool_names::TASK
113        | tool_names::AGENT
114        | tool_names::SEND_MESSAGE
115        | tool_names::TEAMMATE => ToolContextPolicy::key_preserve(),
116
117        // 其他所有工具(Bash/Read/Write/Edit/Glob/Grep/WebFetch/WebSearch/
118        // Browser/ComputerUse/TaskOutput/WorkDone/Compact/RegisterHook 等)
119        _ => ToolContextPolicy::regular_placeholder(),
120    }
121}
122
123/// 便捷查询:工具是否为 KeyTool(AlwaysPreserve)
124pub fn is_key_tool(tool_name: &str) -> bool {
125    policy_for(tool_name).retention == RetentionPolicy::AlwaysPreserve
126}
127
128/// 便捷查询:工具所属 tier
129#[cfg(test)]
130pub fn tier_for(tool_name: &str) -> ContextTier {
131    policy_for(tool_name).tier
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn key_tool_list_matches_policy() {
140        // KEY_TOOL_NAMES 必须与 policy_for() 返回 AlwaysPreserve 的工具一致
141        for name in KEY_TOOL_NAMES {
142            assert!(
143                is_key_tool(name),
144                "KEY_TOOL_NAMES 中的 {} 应返回 AlwaysPreserve",
145                name
146            );
147        }
148    }
149
150    #[test]
151    fn regular_tools_are_placeholder() {
152        for name in [
153            tool_names::SHELL,
154            tool_names::READ,
155            tool_names::WRITE,
156            tool_names::EDIT,
157            tool_names::GLOB,
158            tool_names::GREP,
159            tool_names::WEB_FETCH,
160        ] {
161            let p = policy_for(name);
162            assert_eq!(
163                p.tier,
164                ContextTier::RegularTool,
165                "{} 应为 RegularTool",
166                name
167            );
168            assert_eq!(
169                p.retention,
170                RetentionPolicy::Placeholder,
171                "{} 应为 Placeholder",
172                name
173            );
174        }
175    }
176
177    #[test]
178    fn unknown_tool_fallback_regular() {
179        let p = policy_for("SomeNewFutureTool");
180        assert_eq!(p.tier, ContextTier::RegularTool);
181        assert_eq!(p.retention, RetentionPolicy::Placeholder);
182    }
183
184    #[test]
185    fn tier_priority_ordering() {
186        assert!(ContextTier::System.priority() < ContextTier::User.priority());
187        assert!(ContextTier::User.priority() < ContextTier::KeyTool.priority());
188        assert!(ContextTier::KeyTool.priority() < ContextTier::Assistant.priority());
189        assert!(ContextTier::Assistant.priority() < ContextTier::RegularTool.priority());
190    }
191
192    #[test]
193    fn user_mentioned_key_tools_are_preserved() {
194        // 用户明确点名的 4 个重要工具必须是 KeyTool
195        for name in [
196            tool_names::ENTER_PLAN_MODE,
197            tool_names::ENTER_WORKTREE,
198            tool_names::ASK,
199            tool_names::LOAD_SKILL,
200        ] {
201            assert!(is_key_tool(name), "{} 必须是 KeyTool", name);
202        }
203    }
204}