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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
//! Acceptance test closing a coverage gap in TR-2 (`.volter/tracker/markdown/TR-2.md`)
//! flagged by adversarial review: every existing `DuplicateOutput` test
//! (`reduce_dedup.rs`) mints AND inverts against the SAME in-memory `Session`
//! (`project`/`project_messages` called directly on a hand-built message
//! vector). None of them drive `DuplicateOutput` through the path the
//! control guarantor actually re-runs: live `Agent` -> recorder(disk
//! sidecar) -> **reload from disk** -> offline `verify_log`/`invert`.
//!
//! THE GAP: TR-12's regression (`tr12_cap_supersession.rs`) proved that a
//! hash minted from an in-memory copy of `history` can silently diverge from
//! what a reloaded sidecar recomputes, once `cap_tool_output` mutates the
//! bytes behind the mint site. `DuplicateOutput`'s mint site
//! (`reduce.rs`'s TR-2 pass, run from `Agent::build_request_messages` over
//! `self.history[1..]`) sits in exactly the same position in the pipeline —
//! nothing in the existing TR-2 suite would have caught an equivalent
//! divergence for `DuplicateOutput` specifically, because none of those
//! tests ever touch a recorder, a sidecar file, or a disk reload. This test
//! fills that gap, reusing the exact harness idiom
//! `tr12_cap_supersession.rs` established (`Agent::with_parts` + a scripted
//! `Provider`, `SidecarWriter`/`SessionStore`, offline reload via
//! `Session::from_sidecar_str`, `verify_log` + `invert`).
//!
//! dev/01: a live agent calls a tool that returns the SAME distinctive
//! \>256B payload five times across turns (default
//! `protect_last_n_tool_results = 3` leaves the two OLDEST occurrences
//! eligible, so the second becomes a `DuplicateOutput` of the first). The
//! sidecar and reduction log are persisted and then reloaded from disk via
//! `SessionStore` (the exact primitive `cli/main.rs`'s
//! `show-reductions`/`convert`/`inspect` use) — never the live
//! `Agent`/in-memory `Session`. `verify_log` and `invert` both run OFFLINE
//! against that reloaded state and must pass clean, with `invert` restoring
//! the full original payload byte-exact for the duplicate.
//!
//! dev/04 (bonus, disk-path variant of `reduce_dedup.rs`'s
//! `duplicate_still_resolves_after_the_canonical_is_later_truncated_by_a7`):
//! after the duplicate is established, the policy is tightened (much lower
//! A7 trigger) and one more turn runs, causing the CANONICAL occurrence
//! (never itself a dedup target) to be independently truncated by A7. The
//! already-established `DuplicateOutput` reduction must reproduce verbatim
//! (prefix stability) and — reloaded from disk again — must still invert to
//! the full original bytes, independent of the canonical's now-reduced
//! state.
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use async_trait::async_trait;
use supercode_harness::reduce::{
invert, project_messages, verify_log, ReductionKind, ReductionPolicy,
};
use supercode_harness::session::Session;
use supercode_harness::sidecar::SidecarWriter;
use supercode_harness::store::SessionStore;
use supercode_harness::{
Agent, ChatMessage, ChatRequest, Config, FunctionCall, Provider, Role, ToolCall, Usage,
};
fn temp_dir(tag: &str) -> PathBuf {
static N: AtomicUsize = AtomicUsize::new(0);
let dir = std::env::temp_dir().join(format!(
"supercode-tr2-guarantor-{tag}-{}-{}",
std::process::id(),
N.fetch_add(1, Ordering::SeqCst)
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
/// A tool ("dup_tool") that always returns the SAME fixed payload,
/// regardless of how many times it's called — the content-hash dedup
/// candidate.
struct DupTool(String);
#[async_trait]
impl supercode_harness::tools::Tool for DupTool {
fn name(&self) -> &str {
"dup_tool"
}
fn description(&self) -> &str {
"x"
}
fn parameters(&self) -> serde_json::Value {
serde_json::json!({"type": "object"})
}
async fn execute(
&self,
_a: serde_json::Value,
_c: &supercode_harness::tools::ToolContext,
) -> supercode_harness::Result<String> {
Ok(self.0.clone())
}
}
fn tool_call_msg(id: &str) -> ChatMessage {
ChatMessage {
role: Role::Assistant,
content: None,
content_parts: None,
tool_calls: Some(vec![ToolCall {
id: id.to_string(),
kind: "function".to_string(),
function: FunctionCall {
name: "dup_tool".to_string(),
arguments: "{}".to_string(),
},
}]),
tool_call_id: None,
name: None,
metadata: Default::default(),
}
}
/// Calls `dup_tool` on the first `n_tool_calls` model turns (all within the
/// SAME `Agent::send`, since `run_loop` re-invokes the provider immediately
/// after executing each tool call); every turn after that is a plain text
/// reply with no tool calls, which is what lets a single `send` (or a
/// second, later one) end the loop and return.
struct RepeatedToolThenPlain {
calls: AtomicUsize,
n_tool_calls: usize,
}
#[async_trait]
impl Provider for RepeatedToolThenPlain {
async fn complete(
&self,
_req: &ChatRequest,
_on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
) -> supercode_harness::Result<(ChatMessage, Usage)> {
let n = self.calls.fetch_add(1, Ordering::SeqCst);
if n < self.n_tool_calls {
Ok((tool_call_msg(&format!("c{n}")), Usage::default()))
} else {
Ok((
ChatMessage::assistant(format!("done {n}")),
Usage::default(),
))
}
}
}
/// dev/01 + dev/04 (guarantor-path regression, TR-2 coverage-gap closure): a
/// live agent run with a recorder + `ReductionPolicy` both active produces a
/// `DuplicateOutput` reduction; `verify_log` AND `invert` then run OFFLINE
/// against the sidecar and reduction log reloaded from disk (never the live
/// `Agent`/in-memory `Session`). Both must pass clean, and `invert` must
/// restore the full original bytes byte-exact for the duplicate. The policy
/// is then tightened and one more turn runs so the canonical occurrence is
/// independently truncated by A7 (dev/04) — the duplicate must still
/// resolve byte-exact from a fresh disk reload afterward.
#[tokio::test]
async fn duplicate_output_survives_live_agent_disk_reload_offline_verify_and_invert() {
let dir = temp_dir("dev01-dev04");
let store_dir = dir.join("store");
let store = SessionStore::open(&store_dir).unwrap();
let name = "tr2-dev01";
let sidecar_path = store.sidecar_path(name);
// A distinctive payload comfortably above `duplicate_output_min_bytes`
// (256B default) but below the default A7 trigger (8,192B), so the
// FIRST send's dedup pass fires cleanly with nothing else in play.
let mut original = "D".repeat(4_000);
original.push_str("TR2-DEDUP-NEEDLE");
let pad = 5_000 - original.len();
original.push_str(&"e".repeat(pad));
assert_eq!(original.len(), 5_000);
assert!(original.len() > ReductionPolicy::default().duplicate_output_min_bytes);
assert!(original.len() < ReductionPolicy::default().tool_output_trigger_bytes);
let config = Config::builder().cwd(dir.clone()).build();
let mut reg = supercode_harness::tools::ToolRegistry::new();
reg.register(DupTool(original.clone()));
// Five identical `dup_tool` calls: with the default
// `protect_last_n_tool_results = 3`, the two oldest (occurrences 1 and
// 2) are the only ones ever eligible; occurrence 1 becomes canonical,
// occurrence 2 becomes `DuplicateOutput`. Occurrences 3-5 stay in the
// protected tail forever (positional, not turn-count based), so no
// further dedup candidates ever appear once the tool-call count stops
// growing.
let mut agent = Agent::with_parts(
config,
Box::new(RepeatedToolThenPlain {
calls: AtomicUsize::new(0),
n_tool_calls: 5,
}),
reg,
);
let empty_session = Session::from_claude_code_str("").unwrap();
let writer = SidecarWriter::create(&sidecar_path, &empty_session).unwrap();
agent.set_recorder(writer);
let policy1 = ReductionPolicy::default();
agent.set_reduction_policy(policy1.clone());
// A single `send` drives all 5 tool calls (each is answered immediately
// by the next provider turn, inside `run_loop`) and returns once the
// provider finally replies with plain text.
let reply = agent.send("investigate").await.unwrap();
assert!(reply.starts_with("done"), "unexpected reply: {reply}");
let log1 = agent.reduction_log().clone();
let dups: Vec<_> = log1
.reductions
.iter()
.filter(|r| matches!(r.kind, ReductionKind::DuplicateOutput { .. }))
.collect();
assert_eq!(
dups.len(),
1,
"exactly one of the five identical calls must be eligible to dedup \
under the default protected tail: {:?}",
log1.reductions
);
let dup_id = dups[0].id.clone();
let dup_idx = dups[0].ptr.addr.index;
let canonical_idx = match dups[0].kind {
ReductionKind::DuplicateOutput { canonical, .. } => canonical.index,
_ => unreachable!(),
};
// Persist the reduction log the way the CLI does (`sessions
// show-reductions`/`convert`/`inspect` all call `store.save_reduction_log`
// via the resume/chat path).
store.save_reduction_log(name, &log1).unwrap();
// --- OFFLINE from here: fresh reads from disk, no live Agent/Session ---
{
let sidecar_jsonl = store
.load_sidecar(name)
.unwrap()
.expect("sidecar must exist on disk");
let sidecar = Session::from_sidecar_str(&sidecar_jsonl).unwrap();
let log_reloaded = store
.load_reduction_log(name)
.unwrap()
.expect("reduction log must exist on disk");
// The exact primitive `cli/main.rs`'s `show-reductions`/`convert`/
// `inspect` all call before doing anything else with a reduced
// session.
verify_log(&log_reloaded, &sidecar)
.expect("verify_log must pass clean against the reloaded-from-disk sidecar");
let (final_view, reprojected_log) =
project_messages(&sidecar.messages, &policy1, &log_reloaded);
assert_eq!(
reprojected_log, log_reloaded,
"re-projecting from the reloaded sidecar with its own log must not invent new reductions"
);
let inverted = invert(&final_view, &log_reloaded, &sidecar)
.expect("invert must pass clean against the reloaded-from-disk sidecar");
assert_eq!(inverted.len(), sidecar.messages.len());
for (a, b) in inverted.iter().zip(&sidecar.messages) {
assert_eq!(a.role, b.role);
assert_eq!(a.content, b.content);
}
// Byte-exact restore of the duplicate AND the canonical, both from
// the disk-reloaded sidecar.
assert_eq!(
inverted[dup_idx].content.as_deref(),
Some(original.as_str()),
"invert must restore the duplicate occurrence byte-exact from disk"
);
assert_eq!(
inverted[canonical_idx].content.as_deref(),
Some(original.as_str()),
"invert must restore the canonical occurrence byte-exact from disk"
);
}
// --- dev/04: tighten the policy so the canonical (never itself a dedup
// target) is independently truncated by A7 on a later turn; the
// duplicate must survive prefix-stable and still resolve from disk. ---
let policy2 = ReductionPolicy {
tool_output_trigger_bytes: 100,
tool_output_keep_bytes: 32,
// Unchanged from policy1 (default 3): occurrences 3-5 stay
// protected, so this pass introduces no NEW dedup candidates —
// only the canonical's own truncation.
protect_last_n_tool_results: 3,
..ReductionPolicy::default()
};
agent.set_reduction_policy(policy2.clone());
// One more turn (provider replies plain text immediately, n=5 already
// consumed the tool-call budget) forces a fresh `build_request_messages`
// call under the tightened policy, minting the canonical's truncation.
let reply2 = agent.send("one more thing").await.unwrap();
assert!(reply2.starts_with("done"), "unexpected reply: {reply2}");
let log2 = agent.reduction_log().clone();
let canonical_reduction = log2
.reductions
.iter()
.find(|r| r.ptr.addr.index == canonical_idx)
.expect("the canonical must now have its own reduction");
assert!(
matches!(
canonical_reduction.kind,
ReductionKind::ToolOutputTruncated { .. }
),
"the canonical must now be independently truncated by A7: {:?}",
canonical_reduction.kind
);
let dup2 = log2
.reductions
.iter()
.find(|r| r.id == dup_id)
.expect("the duplicate reduction must survive the canonical's truncation");
assert_eq!(
dup2, dups[0],
"the duplicate reduction must reproduce verbatim (prefix stability)"
);
store.save_reduction_log(name, &log2).unwrap();
// --- OFFLINE again: fresh disk reload after the canonical's truncation ---
let sidecar_jsonl = store
.load_sidecar(name)
.unwrap()
.expect("sidecar must exist on disk");
let sidecar = Session::from_sidecar_str(&sidecar_jsonl).unwrap();
let log_reloaded = store
.load_reduction_log(name)
.unwrap()
.expect("reduction log must exist on disk");
verify_log(&log_reloaded, &sidecar).expect(
"verify_log must pass clean against the reloaded-from-disk sidecar \
even after the canonical's truncation",
);
let (final_view, _reprojected_log) =
project_messages(&sidecar.messages, &policy2, &log_reloaded);
let inverted = invert(&final_view, &log_reloaded, &sidecar)
.expect("invert must pass clean against the reloaded-from-disk sidecar");
// The duplicate still inverts to the full original bytes, independent of
// the canonical having just been truncated by A7.
assert_eq!(
inverted[dup_idx].content.as_deref(),
Some(original.as_str()),
"invert must restore the duplicate byte-exact from disk even after \
the canonical is A7-truncated"
);
// The canonical also still inverts byte-exact (A7 truncation is
// reversible too).
assert_eq!(
inverted[canonical_idx].content.as_deref(),
Some(original.as_str()),
"invert must restore the now-truncated canonical byte-exact from disk"
);
std::fs::remove_dir_all(&dir).ok();
}