1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
//! Token-use optimizer: wires CompressionLevel into the PostToolUse relay.
//!
//! Why: compression decisions and config live in one place so the HTTP API,
//! MCP tool, and dashboard all share the same tuning.
//! What: OptimizerConfig (level + per-tool overrides) and
//! optimize_tool_output() which the hook relay calls before push_hook_event.
//! Test: `cargo test -p trusty-mpm-daemon optimizer` verifies that
//! PostToolUse payloads are rewritten when level >= Trim.
use crate::core::compress::{CompressionLevel, compress_output};
use serde_json::Value;
/// Honest description of what the token-use optimizer actually compresses.
///
/// Why: `tm optimizer status` and the dashboard would otherwise imply that a
/// `trim`/`caveman` level reduces the live token usage of every session. It
/// does not. Compression runs only on the copy of tool output that enters
/// trusty-mpm's observability history (see [`optimize_tool_output`], invoked
/// from the hook-ingestion path *before* the ring buffer) and on
/// tm-mediated / MCP-proxied tool calls. A *native* Claude Code session — the
/// common case, where tm provisions the session but Claude Code runs its own
/// tool loop — returns built-in tool results (Bash, Read, …) directly to the
/// model before any `PostToolUse` hook can rewrite them, so its live context
/// tokens are unaffected. Surfacing this scope keeps `optimizer status`
/// truthful (issue #1944).
/// What: a one-line human-readable scope string embedded in the `/optimizer`
/// response and printed by the CLI status command.
/// Test: `scope_note_names_native_limitation`; e2e `optimizer_default_config`.
pub const OPTIMIZER_SCOPE_NOTE: &str = "compression applies only to tool output copied into \
trusty-mpm's observability history (dashboard, event feed, compacted session log) and to \
tm-mediated / MCP-proxied tool calls; it does NOT reduce live token usage for native Claude \
Code sessions, whose built-in tools return results directly to the model before any \
PostToolUse hook can rewrite them (see issue #1944)";
/// Per-tool compression override. Keyed by tool name (e.g. "Bash", "Read").
pub type ToolOverrides = std::collections::HashMap<String, CompressionLevel>;
/// Tuning for the token-use optimizer.
///
/// Why: a single struct lets the HTTP API, MCP backend, and CLI share one
/// view of how aggressively tool outputs should be compressed.
/// What: a default level applied to all tools plus per-tool overrides and a
/// flag controlling redundant-read suppression.
/// Test: `optimize_uses_tool_override`, `get_optimizer_returns_default`.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
pub struct OptimizerConfig {
/// Default compression level for all tool outputs.
pub default_level: CompressionLevel,
/// Per-tool overrides (tool name → level).
pub tool_overrides: ToolOverrides,
/// Whether to suppress redundant Read calls (same path already in window).
pub suppress_redundant_reads: bool,
}
impl Default for OptimizerConfig {
fn default() -> Self {
Self {
default_level: CompressionLevel::Trim,
tool_overrides: ToolOverrides::new(),
suppress_redundant_reads: true,
}
}
}
/// On-disk shape of `optimizer.toml` (the framework hook policy file).
///
/// Why: the human-edited policy file uses a friendly `[default] / [tools]`
/// layout that differs from the wire/serde shape of [`OptimizerConfig`]; a
/// dedicated mirror type keeps the file format decoupled from the runtime
/// struct.
/// What: `[default].level` plus a `[tools]` table of name → level overrides.
/// Test: `config_loads_from_toml_file`.
#[derive(Debug, serde::Deserialize)]
struct OptimizerToml {
#[serde(default)]
default: OptimizerTomlDefault,
#[serde(default)]
tools: std::collections::HashMap<String, TomlLevel>,
}
/// A `CompressionLevel` parsed case-insensitively from the policy file.
///
/// Why: per-tool overrides in `[tools]` accept the same friendly capitalized
/// names as `[default].level`; a newtype lets the same lenient parser apply.
/// What: wraps `CompressionLevel`, deserializing via [`de_level`].
/// Test: `config_loads_from_toml_file`.
#[derive(Debug, Clone, Copy)]
struct TomlLevel(CompressionLevel);
impl<'de> serde::Deserialize<'de> for TomlLevel {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
de_level(deserializer).map(TomlLevel)
}
}
/// `[default]` table of `optimizer.toml`.
#[derive(Debug, serde::Deserialize)]
struct OptimizerTomlDefault {
#[serde(default = "default_trim", deserialize_with = "de_level")]
level: CompressionLevel,
}
impl Default for OptimizerTomlDefault {
fn default() -> Self {
Self {
level: CompressionLevel::Trim,
}
}
}
/// `serde` default for the `[default].level` field — `Trim`.
fn default_trim() -> CompressionLevel {
CompressionLevel::Trim
}
/// Case-insensitively parse a compression level from the policy file.
///
/// Why: the human-edited `optimizer.toml` uses friendly capitalized names
/// (`"Trim"`, `"Caveman"`) while `CompressionLevel`'s wire form is snake_case;
/// accepting either keeps the documented file format working.
/// What: lowercases the string then defers to `CompressionLevel`'s serde impl.
/// Test: `config_loads_from_toml_file`.
fn de_level<'de, D>(deserializer: D) -> Result<CompressionLevel, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let raw = String::deserialize(deserializer)?;
let lowered = raw.to_ascii_lowercase();
CompressionLevel::deserialize(serde::de::value::StrDeserializer::<D::Error>::new(&lowered))
}
impl OptimizerConfig {
/// Compression level for a given tool name.
///
/// Why: callers need the effective level after overrides are resolved.
/// What: returns the per-tool override if present, else `default_level`.
/// Test: `optimize_uses_tool_override`.
pub fn level_for(&self, tool: &str) -> CompressionLevel {
self.tool_overrides
.get(tool)
.copied()
.unwrap_or(self.default_level)
}
/// Load an optimizer config from a framework `optimizer.toml` file.
///
/// Why: the optimizer policy is framework-managed — it lives on disk under
/// `~/.trusty-mpm/framework/hooks/` and is edited directly (or reset via
/// `trusty-mpm install --force`), not mutated through the API.
/// What: reads `path`, parses the `[default]`/`[tools]` layout, and maps it
/// onto an [`OptimizerConfig`]. A missing file yields
/// `Ok(OptimizerConfig::default())` so an un-installed framework still
/// runs; a malformed file yields an error.
/// Test: `config_loads_from_toml_file`, `config_missing_file_is_default`.
pub fn load_from_file(path: &std::path::Path) -> anyhow::Result<Self> {
let raw = match std::fs::read_to_string(path) {
Ok(raw) => raw,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Ok(Self::default());
}
Err(e) => return Err(e.into()),
};
let parsed: OptimizerToml = toml::from_str(&raw)?;
let tool_overrides = parsed
.tools
.into_iter()
.map(|(tool, level)| (tool, level.0))
.collect();
Ok(Self {
default_level: parsed.default.level,
tool_overrides,
suppress_redundant_reads: true,
})
}
}
/// Apply compression to a PostToolUse hook payload.
///
/// Why: the relay calls this before pushing to the ring buffer so downstream
/// consumers (dashboard, Telegram) and the compacted session history all see
/// the trimmed output.
/// What: looks for `payload["output"]` (string field), compresses it, updates
/// the payload in place. Returns compression stats.
/// Test: `optimize_rewrites_large_output`, `optimize_skips_small_output`.
pub fn optimize_tool_output(
config: &OptimizerConfig,
tool_name: &str,
payload: &mut Value,
) -> crate::core::compress::CompressionStats {
let level = config.level_for(tool_name);
if level == CompressionLevel::Off {
return Default::default();
}
if let Some(output) = payload.get("output").and_then(Value::as_str) {
let (compressed, stats) = compress_output(output, level);
payload["output"] = Value::String(compressed);
stats
} else {
Default::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::compress::TRIM_THRESHOLD_BYTES;
use serde_json::json;
fn big_output() -> String {
(0..500)
.map(|i| format!("line {i} with some padding content"))
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn optimize_rewrites_large_output_at_trim_level() {
let cfg = OptimizerConfig::default(); // Trim
let original = big_output();
assert!(original.len() > TRIM_THRESHOLD_BYTES);
let mut payload = json!({ "tool": "Bash", "output": original.clone() });
let stats = optimize_tool_output(&cfg, "Bash", &mut payload);
assert!(stats.saved_bytes() > 0);
let rewritten = payload["output"].as_str().unwrap();
assert!(rewritten.len() < original.len());
}
#[test]
fn optimize_skips_small_output_at_trim_level() {
let cfg = OptimizerConfig::default();
let mut payload = json!({ "tool": "Bash", "output": "tiny output" });
let stats = optimize_tool_output(&cfg, "Bash", &mut payload);
assert_eq!(stats.saved_bytes(), 0);
assert_eq!(payload["output"], "tiny output");
}
#[test]
fn optimize_uses_tool_override() {
let mut overrides = ToolOverrides::new();
overrides.insert("Bash".into(), CompressionLevel::Caveman);
let cfg = OptimizerConfig {
tool_overrides: overrides,
..OptimizerConfig::default()
};
// Default level stays Trim; Bash is overridden to Caveman.
assert_eq!(cfg.level_for("Read"), CompressionLevel::Trim);
assert_eq!(cfg.level_for("Bash"), CompressionLevel::Caveman);
let mut payload = json!({ "tool": "Bash", "output": "small" });
optimize_tool_output(&cfg, "Bash", &mut payload);
// Caveman replaces even tiny outputs with a summary.
assert!(payload["output"].as_str().unwrap().contains("suppressed"));
}
#[test]
fn optimize_off_level_is_passthrough() {
let cfg = OptimizerConfig {
default_level: CompressionLevel::Off,
..OptimizerConfig::default()
};
let original = big_output();
let mut payload = json!({ "tool": "Bash", "output": original.clone() });
let stats = optimize_tool_output(&cfg, "Bash", &mut payload);
assert_eq!(stats.saved_bytes(), 0);
assert_eq!(payload["output"], original);
}
#[test]
fn optimize_caveman_always_replaces() {
let cfg = OptimizerConfig {
default_level: CompressionLevel::Caveman,
..OptimizerConfig::default()
};
let mut payload = json!({ "tool": "Read", "output": "x" });
optimize_tool_output(&cfg, "Read", &mut payload);
assert!(payload["output"].as_str().unwrap().contains("suppressed"));
}
#[test]
fn optimize_missing_output_field_is_noop() {
let cfg = OptimizerConfig::default();
let mut payload = json!({ "tool": "Bash" });
let stats = optimize_tool_output(&cfg, "Bash", &mut payload);
assert_eq!(stats.saved_bytes(), 0);
}
#[test]
fn config_loads_from_toml_file() {
// The framework `[default]/[tools]` policy layout must map onto an
// OptimizerConfig with the declared default level and per-tool overrides.
use std::io::Write;
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("optimizer.toml");
let mut file = std::fs::File::create(&path).unwrap();
writeln!(
file,
"[default]\nlevel = \"Caveman\"\n\n[tools]\nRead = \"Off\""
)
.unwrap();
let cfg = OptimizerConfig::load_from_file(&path).unwrap();
assert_eq!(cfg.default_level, CompressionLevel::Caveman);
assert_eq!(cfg.level_for("Read"), CompressionLevel::Off);
assert_eq!(cfg.level_for("Bash"), CompressionLevel::Caveman);
}
#[test]
fn scope_note_names_native_limitation() {
// The scope note must make the native-session limitation explicit so
// `optimizer status` cannot be read as "compression on for everything".
let note = OPTIMIZER_SCOPE_NOTE.to_ascii_lowercase();
assert!(note.contains("native"));
assert!(note.contains("does not") || note.contains("not reduce"));
assert!(note.contains("#1944"));
}
#[test]
fn config_missing_file_is_default() {
// A missing policy file (framework not installed) is not an error — it
// yields the default config so the daemon still runs.
let dir = tempfile::tempdir().unwrap();
let cfg = OptimizerConfig::load_from_file(&dir.path().join("absent.toml")).unwrap();
assert_eq!(cfg.default_level, OptimizerConfig::default().default_level);
}
}