Skip to main content

lean_ctx/core/terse/
mod.rs

1//! # Terse Compression Engine
2//!
3//! Unified, 4-layer token compression pipeline that replaces the legacy
4//! `compress_terse`/`compress_ultra` functions and the `TerseAgent` prompt system.
5//!
6//! ## Architecture
7//!
8//! ```text
9//! Tool Output → [Layer 1: Deterministic] → [Layer 2: Residual] → Compressed Output
10//! MCP Tools List → [Layer 4: Description Terse] → Compact Descriptions
11//! ```
12//!
13//! ## Layers
14//!
15//! - **Layer 1** (`engine.rs`): Deterministic output compression — surprisal scoring,
16//!   content/function word filtering, domain dictionaries, quality gate.
17//! - **Layer 2** (`residual.rs`): Pattern-aware post-terse — applies after pattern
18//!   compression, avoids double-compression, tracks attribution.
19//! - **Layer 4** (`mcp_compress.rs`): MCP description compression — shrinks tool
20//!   descriptions, lazy-load stubs, on-demand expansion.
21
22pub mod counter;
23pub mod dictionaries;
24pub mod engine;
25pub mod mcp_compress;
26pub mod pipeline;
27pub mod quality;
28pub mod residual;
29pub mod scoring;
30
31/// Tools whose textual output is read content the agent edits against and must
32/// therefore be returned byte-for-byte.
33const READ_FAMILY: &[&str] = &[
34    "ctx_read",
35    "ctx_multi_read",
36    "ctx_smart_read",
37    "ctx_compress",
38    "ctx_overview",
39];
40
41/// Whether a tool's output must be returned verbatim and so must never pass
42/// through the prose terse pipeline (#404).
43///
44/// Returns true for any read-family tool — those already apply their own
45/// mode-aware, structure-preserving compression, and a `full`/`lines:` read
46/// promises complete, edit-against-able content — and, as defense in depth, for
47/// any call whose `mode` is itself a verbatim mode (`full`, `raw`, `lines:N-M`).
48/// The mode arm protects a *future* read tool or caller by construction, even
49/// before it is added to `READ_FAMILY`. Shared by the MCP post-processor
50/// (`skip_terse`) and the CLI `read` command so both paths stay byte-exact.
51#[must_use]
52pub fn is_verbatim_read(name: &str, mode: Option<&str>) -> bool {
53    if READ_FAMILY.contains(&name) {
54        return true;
55    }
56    mode.is_some_and(|m| m == "full" || m == "raw" || m.starts_with("lines:"))
57}
58
59/// Result of a compression pipeline run with full attribution.
60#[derive(Debug, Clone)]
61pub struct TerseResult {
62    pub output: String,
63    pub tokens_before: u32,
64    pub tokens_after: u32,
65    pub savings_pct: f32,
66    pub layers_applied: Vec<&'static str>,
67    pub pattern_savings: u32,
68    pub terse_savings: u32,
69    pub quality_passed: bool,
70}
71
72impl TerseResult {
73    pub fn passthrough(text: String, tokens: u32) -> Self {
74        Self {
75            output: text,
76            tokens_before: tokens,
77            tokens_after: tokens,
78            savings_pct: 0.0,
79            layers_applied: Vec::new(),
80            pattern_savings: 0,
81            terse_savings: 0,
82            quality_passed: true,
83        }
84    }
85}
86
87#[cfg(test)]
88mod verbatim_read_tests {
89    use super::is_verbatim_read;
90
91    #[test]
92    fn read_family_is_always_verbatim() {
93        for name in [
94            "ctx_read",
95            "ctx_multi_read",
96            "ctx_smart_read",
97            "ctx_compress",
98            "ctx_overview",
99        ] {
100            // Even an intentionally-lossy mode like `signatures` is exempt: the
101            // read tool applies its own structure-preserving compression and the
102            // generic prose terse layer must never run on top of it (#404).
103            assert!(is_verbatim_read(name, Some("signatures")), "{name}");
104            assert!(is_verbatim_read(name, None), "{name}");
105        }
106    }
107
108    #[test]
109    fn verbatim_modes_protect_any_tool() {
110        // Defense-in-depth: a future read tool/caller using a verbatim mode is
111        // protected by construction, before it joins the name list.
112        for mode in ["full", "raw", "lines:1-40", "lines:10-10"] {
113            assert!(is_verbatim_read("ctx_future_reader", Some(mode)), "{mode}");
114        }
115    }
116
117    #[test]
118    fn non_read_lossy_modes_stay_eligible() {
119        for mode in ["map", "aggressive", "entropy", "signatures"] {
120            assert!(
121                !is_verbatim_read("ctx_search", Some(mode)),
122                "non-read {mode} must remain terse-eligible"
123            );
124        }
125        assert!(!is_verbatim_read("ctx_shell", None));
126    }
127}