From dd0ec612a3023336e3ececfc73c3bfe9355fc7c3 Mon Sep 17 00:00:00 2001 From: hardy30894 Date: Thu, 22 Oct 2026 22:07:29 -0400 Subject: [PATCH 1/2] fix: read a tool result as the model saw it The Claude Code reader wrote a tool result's content array out as raw JSON, so text results carried a wrapper the model never sees and images and PDFs carried their base64 as text. On real sessions that made tool output look seven times larger than it is, inflating cache-savings and the audit. Text blocks are now joined and an image or document is a short placeholder. --- crates/sources/src/claude_code.rs | 41 ++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/crates/sources/src/claude_code.rs b/crates/sources/src/claude_code.rs index de57bb5..56bb4c9 100644 --- a/crates/sources/src/claude_code.rs +++ b/crates/sources/src/claude_code.rs @@ -218,7 +218,7 @@ fn segments_from(content: Content, tool_names: &mut HashMap) -> id: tool_use_id, }, original: None, - effective: value_to_text(content), + effective: tool_result_text(content), }); } Block::Unknown => {} @@ -227,6 +227,23 @@ fn segments_from(content: Content, tool_names: &mut HashMap) -> segments } +// A tool result as the model read it: text blocks joined, an image or document named by a short +// placeholder rather than its base64 payload, which no text codec could compress anyway. +fn tool_result_text(content: serde_json::Value) -> String { + let serde_json::Value::Array(blocks) = content else { + return value_to_text(content); + }; + blocks + .into_iter() + .map(|block| match block["type"].as_str() { + Some("text") => block["text"].as_str().unwrap_or_default().to_string(), + Some(kind @ ("image" | "document")) => format!("[{kind}]"), + _ => block.to_string(), + }) + .collect::>() + .join("\n") +} + fn value_to_text(value: serde_json::Value) -> String { match value { serde_json::Value::String(s) => s, @@ -338,3 +355,25 @@ impl From for Billing { } } } + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn a_tool_result_reads_as_the_model_saw_it() { + let content = json!([ + { "type": "text", "text": "line one" }, + { "type": "image", "source": { "type": "base64", "data": "iVBORw0KGgoAAAANSUhEUg" } }, + { "type": "text", "text": "line two" } + ]); + assert_eq!(tool_result_text(content), "line one\n[image]\nline two"); + } + + #[test] + fn a_plain_string_result_is_unchanged() { + assert_eq!(tool_result_text(json!("ok\n")), "ok\n"); + assert_eq!(tool_result_text(serde_json::Value::Null), ""); + } +} From beb26446751029402ce606c623c18abeaba6f5c1 Mon Sep 17 00:00:00 2001 From: hardy30894 Date: Thu, 22 Oct 2026 22:07:29 -0400 Subject: [PATCH 2/2] feat: secondwind eval replays recorded sessions through the optimizer Per tool and command: blocks kept inline or offloaded, how often the model later needed what an offload hid, and net tokens. A hidden token counts only if the model used it before another result supplied it again. Every block is checked faithful independently of the optimizer's gate, and --blocks writes one line per result with its context size and remaining turns for policy studies. --- README.md | 1 + crates/cli/src/main.rs | 138 ++++++++++ crates/cli/tests/eval.rs | 105 +++++++ crates/optimize/src/counterfactual.rs | 380 ++++++++++++++++++++++++++ 4 files changed, 624 insertions(+) create mode 100644 crates/cli/tests/eval.rs diff --git a/README.md b/README.md index ce89394..933bbf1 100644 --- a/README.md +++ b/README.md @@ -348,6 +348,7 @@ Lossless *and* cheap is easy to get wrong. A few things that make it hold: | `secondwind check` | verify the install and agent integration | | `secondwind optimize ` | compress one tool-output block and print the result | | `secondwind verify ` | re-check a compressed wire against its fidelity certificate | +| `secondwind eval` | replay your recorded sessions through the optimizer: tokens sent, reopens, net savings and faithfulness per tool and command | | `secondwind exec -- ` | compress one command's output, shell-filter style (`-c '