Skip to main content

codetether_rlm/
capability.rs

1//! Per-tool output capability classification (issue #231 item 6).
2//!
3//! Consulted by [`crate::RlmRouter::should_route`] before any
4//! routing decision. Only [`OutputCapability::BulkSummarizable`] tools
5//! are ever destructively summarized; everything else is left intact.
6
7use std::collections::HashSet;
8
9/// What RLM is allowed to do with a tool's output.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum OutputCapability {
12    /// Bulk web/aggregate output where a prose summary is an
13    /// acceptable lossy projection.
14    BulkSummarizable,
15    /// Content-carrying output whose exact bytes may be needed later.
16    ExactContent,
17    /// Unknown tool. Treated as [`OutputCapability::ExactContent`].
18    Unknown,
19}
20
21/// Tools eligible for destructive RLM routing (see module docs).
22pub(super) fn rlm_eligible_tools() -> &'static HashSet<&'static str> {
23    static TOOLS: std::sync::OnceLock<HashSet<&'static str>> = std::sync::OnceLock::new();
24    TOOLS.get_or_init(|| ["webfetch", "websearch", "batch"].into_iter().collect())
25}
26
27/// Tools whose output carries exact bytes the agent may need later.
28fn rlm_exact_content_tools() -> &'static HashSet<&'static str> {
29    static TOOLS: std::sync::OnceLock<HashSet<&'static str>> = std::sync::OnceLock::new();
30    TOOLS.get_or_init(|| {
31        [
32            "read",
33            "grep",
34            "bash",
35            "glob",
36            "ls",
37            "edit",
38            "write",
39            "session_recall",
40            "notebook_read",
41            "notebook_edit",
42        ]
43        .into_iter()
44        .collect()
45    })
46}
47
48/// Classify a tool by name. `session_context` is the pseudo-tool used
49/// by the compression pass and is the only summary-producing caller.
50pub fn output_capability(tool_id: &str) -> OutputCapability {
51    if tool_id == "session_context" || rlm_eligible_tools().contains(tool_id) {
52        OutputCapability::BulkSummarizable
53    } else if rlm_exact_content_tools().contains(tool_id) {
54        OutputCapability::ExactContent
55    } else {
56        OutputCapability::Unknown
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::{OutputCapability, output_capability};
63
64    #[test]
65    fn classifies_known_tools() {
66        assert_eq!(
67            output_capability("webfetch"),
68            OutputCapability::BulkSummarizable
69        );
70        assert_eq!(
71            output_capability("session_context"),
72            OutputCapability::BulkSummarizable
73        );
74        assert_eq!(output_capability("read"), OutputCapability::ExactContent);
75        assert_eq!(output_capability("bash"), OutputCapability::ExactContent);
76        assert_eq!(
77            output_capability("session_recall"),
78            OutputCapability::ExactContent
79        );
80        assert_eq!(
81            output_capability("brand_new_tool"),
82            OutputCapability::Unknown
83        );
84    }
85}