supercode-harness 0.4.6

The optional native Supercode agent and tool harness
Documentation
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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
//! PARITY-18 (re-fix, D5) — regression tests for the context guard: the
//! previous landing (`0113185`) shipped ZERO tests for a P0 billing gate.
//! These exercise the actual guard DECISION at both layers it now runs at
//! (D4: the CLI's one-shot preflight AND `Agent::run_loop`'s per-send
//! check), the `model_context_limit` reference table, the unknown-model
//! fail-safe floor, and `reduce::reduce_to_fit`'s termination/monotonicity
//! contract — not just the pure-function token-math tests already added to
//! `crates/harness/src/tokens.rs`.

use std::sync::atomic::{AtomicUsize, Ordering};

use async_trait::async_trait;
use supercode_harness::reduce::{self, ReductionLog, ReductionPolicy};
use supercode_harness::tokens;
use supercode_harness::{
    model_context_limit, Agent, ChatMessage, ChatRequest, Config, Error, Provider, Usage,
    UNKNOWN_MODEL_CONTEXT_FLOOR,
};

// ---------------------------------------------------------------------------
// model_context_limit table lookups + unknown-model fail-safe floor
// ---------------------------------------------------------------------------

#[test]
fn known_models_resolve_their_documented_limits() {
    assert_eq!(model_context_limit("z-ai/glm-5.2"), Some(1_048_576));
    assert_eq!(
        model_context_limit("deepseek/deepseek-v4-flash"),
        Some(1_048_576)
    );
    assert_eq!(model_context_limit("openai/gpt-5"), Some(400_000));
    assert_eq!(
        model_context_limit("anthropic/claude-haiku-4-5"),
        Some(200_000)
    );
}

#[test]
fn unrecognized_model_returns_none_not_a_guess() {
    assert_eq!(model_context_limit("totally/unknown-model-slug"), None);
    assert_eq!(model_context_limit(""), None);
}

#[test]
fn unknown_model_floor_is_never_larger_than_any_known_limit() {
    // "Unknown" must never be assumed to have MORE headroom than any known
    // model — the floor has to be the smallest (or smaller) of every
    // documented limit, or an unrecognized slug could sail past a real
    // model's actual window.
    for slug in [
        "z-ai/glm-5.2",
        "deepseek/deepseek-v4-flash",
        "deepseek/deepseek-v4-pro",
        "google/gemini-2.5-pro",
        "meta-llama/llama-4-maverick",
        "openai/gpt-5.5",
        "openai/gpt-5",
        "anthropic/claude-opus-4-8",
        "anthropic/claude-sonnet-4-6",
        "anthropic/claude-haiku-4-5",
    ] {
        let known = model_context_limit(slug).unwrap_or_else(|| panic!("{slug} should be known"));
        assert!(
            UNKNOWN_MODEL_CONTEXT_FLOOR <= known,
            "fail-safe floor ({UNKNOWN_MODEL_CONTEXT_FLOOR}) must not exceed {slug}'s real limit ({known})"
        );
    }
}

// ---------------------------------------------------------------------------
// reduce_to_fit: termination + monotonicity
// ---------------------------------------------------------------------------

fn big_text_messages(n: usize, bytes_each: usize) -> Vec<ChatMessage> {
    (0..n)
        .map(|i| {
            if i % 2 == 0 {
                ChatMessage::user("x".repeat(bytes_each))
            } else {
                ChatMessage::assistant("y".repeat(bytes_each))
            }
        })
        .collect()
}

/// Build a `fits` closure over raw [`tokens::estimate_view_tokens`] alone —
/// mirrors what a `target_tokens: u64` parameter used to do, for tests that
/// only care about `reduce_to_fit`'s termination/monotonicity/determinism
/// properties and don't need the real system-prompt+tools+guard coupling
/// (that coupling is what `reduce_to_fit_rescues_...` below exercises).
fn target_fits(target_tokens: u64) -> impl Fn(&[ChatMessage]) -> bool {
    move |view: &[ChatMessage]| tokens::estimate_view_tokens(view) <= target_tokens
}

#[test]
fn reduce_to_fit_terminates_and_never_returns_something_worse_than_the_base_attempt() {
    let msgs = big_text_messages(40, 500);
    let policy = ReductionPolicy::default();
    let (base_view, _) = reduce::project_messages(&msgs, &policy, &ReductionLog::default());
    let base_tokens = tokens::estimate_view_tokens(&base_view);

    // A `fits` that can never be satisfied for plain text (nothing here is
    // tool output/images, so aggressive levels can't shrink it much) —
    // `reduce_to_fit` must still terminate promptly (this call returning
    // synchronously IS the termination proof: `MAX_AGGRESSIVE_LEVELS` bounds
    // the byte-knob escalation loop and the D6 turn-clearing loop is bounded
    // by `MIN_CLEAR_TURNS_WINDOW`) and never hand back something bigger than
    // the very first (un-tightened) attempt.
    let (view, _log, _policy) =
        reduce::reduce_to_fit(&msgs, &policy, &ReductionLog::default(), |_| false);
    let tokens_out = tokens::estimate_view_tokens(&view);
    assert!(
        tokens_out <= base_tokens,
        "reduce_to_fit must never do worse than the base (untightened) projection: {tokens_out} > {base_tokens}"
    );
}

#[test]
fn reduce_to_fit_is_deterministic() {
    let msgs = big_text_messages(20, 300);
    let policy = ReductionPolicy::default();
    let (view_a, log_a, _) =
        reduce::reduce_to_fit(&msgs, &policy, &ReductionLog::default(), target_fits(100));
    let (view_b, log_b, _) =
        reduce::reduce_to_fit(&msgs, &policy, &ReductionLog::default(), target_fits(100));
    assert_eq!(
        tokens::estimate_view_tokens(&view_a),
        tokens::estimate_view_tokens(&view_b)
    );
    assert_eq!(log_a.reductions.len(), log_b.reductions.len());
}

#[test]
fn reduce_to_fit_reaching_target_stops_escalating_further() {
    // A target that the BASE (untightened) projection already satisfies —
    // `reduce_to_fit` must return that first attempt's token count exactly
    // (no gratuitous over-tightening once `fits` is already satisfied).
    let msgs = big_text_messages(4, 50);
    let policy = ReductionPolicy::default();
    let (base_view, _) = reduce::project_messages(&msgs, &policy, &ReductionLog::default());
    let base_tokens = tokens::estimate_view_tokens(&base_view);
    let (view, _, _) = reduce::reduce_to_fit(
        &msgs,
        &policy,
        &ReductionLog::default(),
        target_fits(base_tokens + 10_000),
    );
    assert_eq!(tokens::estimate_view_tokens(&view), base_tokens);
}

#[test]
fn reduce_to_fit_escalates_to_turn_clearing_for_text_heavy_sessions() {
    // D6: a session made entirely of plain user/assistant text (no big tool
    // output/images) — `tighten`'s byte knobs have nothing to shrink, so
    // BEFORE the D6 fix `reduce_to_fit` could only ever return the base
    // (unreduced) projection for content like this, no matter how far over
    // target it was. After the fix, the A10 turn-clearing escalation phase
    // must actually shrink it.
    let msgs = big_text_messages(60, 400); // ~60 * ~410 bytes/msg wire form
    let policy = ReductionPolicy::default();
    let (base_view, _) = reduce::project_messages(&msgs, &policy, &ReductionLog::default());
    let base_tokens = tokens::estimate_view_tokens(&base_view);

    // A target well below the base (byte-knob-only) tokens, but reachable
    // via turn-clearing.
    let target = base_tokens / 4;
    let (view, log, applied_policy) = reduce::reduce_to_fit(
        &msgs,
        &policy,
        &ReductionLog::default(),
        target_fits(target),
    );
    let view_tokens = tokens::estimate_view_tokens(&view);

    assert!(
        view_tokens < base_tokens,
        "text-heavy session must reduce further than the base attempt once D6's turn-clearing \
         escalation is available: {view_tokens} vs base {base_tokens}"
    );
    assert!(
        applied_policy.clear_turns_older_than.is_some(),
        "the applied policy should show turn-clearing engaged"
    );
    assert!(
        log.reductions.iter().any(|r| matches!(
            r.kind,
            supercode_harness::reduce::ReductionKind::TurnsCleared { .. }
        )),
        "the reduction log should record a TurnsCleared entry"
    );
}

#[test]
fn reduce_to_fit_never_overrides_an_explicit_clear_turns_older_than() {
    // If the CALLER already set an explicit `clear_turns_older_than` (e.g.
    // mirroring `Agent::maybe_compact`'s own choice), `reduce_to_fit`'s D6
    // escalation must never override it with a different threshold.
    let msgs = big_text_messages(60, 400);
    let policy = ReductionPolicy {
        clear_turns_older_than: Some(30),
        ..ReductionPolicy::default()
    };
    let (_view, _log, applied_policy) =
        reduce::reduce_to_fit(&msgs, &policy, &ReductionLog::default(), |_| false);
    assert_eq!(applied_policy.clear_turns_older_than, Some(30));
}

#[test]
fn reduce_to_fit_d6_escalation_reaches_the_true_floor_regardless_of_session_length() {
    // PARITY-18 v3 — before this fix, the D6 turn-clearing loop only ran
    // `MAX_AGGRESSIVE_LEVELS` (5) halvings starting from `full_msgs.len()`,
    // so any session over ~128 messages bottomed out well above
    // `MIN_CLEAR_TURNS_WINDOW` (a 3,000-message session stopped at 93, never
    // approaching 4) — "maximal reduction" wasn't actually maximal. With an
    // unsatisfiable `fits`, the escalation must now walk all the way down to
    // the floor.
    //
    // `reduce_to_fit`'s "best so far" bookkeeping only ADOPTS a candidate
    // threshold when it strictly improves on the previous best
    // (`view_tokens < best_tokens`); `compute_clear_range` floors
    // `keep_recent` at `(threshold / 2).max(2)`, which already bottoms out
    // at `keep_recent == 2` for any `threshold <= 5` — so threshold 5 and
    // `MIN_CLEAR_TURNS_WINDOW` (4) can legitimately tie and the recorded
    // `clear_turns_older_than` may be 5, not 4 (both give the identical
    // view). The correctness property this test asserts is therefore not
    // "the applied threshold equals 4 exactly" but "the ACHIEVED reduction
    // is exactly as good as directly applying the true floor" — i.e. the
    // escalation actually reached the floor's result, not the old cap's.
    let msgs = big_text_messages(3_000, 40);
    let policy = ReductionPolicy::default();

    // What a DIRECT application of the true floor threshold produces — the
    // comparison point the pre-fix (level-count-bounded) escalation could
    // never reach for a session this long.
    let mut floor_policy = policy.clone();
    floor_policy.clear_turns_older_than = Some(4); // mirrors reduce.rs's private MIN_CLEAR_TURNS_WINDOW
    let (floor_view, _) = reduce::project_messages(&msgs, &floor_policy, &ReductionLog::default());
    let floor_tokens = tokens::estimate_view_tokens(&floor_view);

    // What the OLD, level-count-bounded escalation would have bottomed out
    // at for this session: 5 halvings from `full_msgs.len()` (3,000) —
    // 1500, 750, 375, 187, 93 — landing at 93, nowhere near the floor.
    let mut old_bounded_policy = policy.clone();
    old_bounded_policy.clear_turns_older_than = Some(93);
    let (old_bounded_view, _) =
        reduce::project_messages(&msgs, &old_bounded_policy, &ReductionLog::default());
    let old_bounded_tokens = tokens::estimate_view_tokens(&old_bounded_view);
    assert!(
        floor_tokens < old_bounded_tokens,
        "test fixture invariant broken: the true floor ({floor_tokens} tok) should reduce \
         further than the old level-count-bounded stop ({old_bounded_tokens} tok) — re-derive \
         this fixture if it doesn't"
    );

    let (view, _log, _applied_policy) =
        reduce::reduce_to_fit(&msgs, &policy, &ReductionLog::default(), |_| false);
    let achieved_tokens = tokens::estimate_view_tokens(&view);

    assert_eq!(
        achieved_tokens, floor_tokens,
        "reduce_to_fit must reach the TRUE floor's reduction ({floor_tokens} tok) for a long \
         session when nothing satisfies `fits`, not stop early at the old level-count-bounded \
         halving ({old_bounded_tokens} tok) — achieved {achieved_tokens} tok"
    );
}

// ---------------------------------------------------------------------------
// PARITY-18 v3 — THE regression test: reducer target vs. guard acceptance
// boundary coherence. This is the test the bug report demanded: it must
// FAIL against v2 (commit 6793dff), where `reduce_to_fit` took a
// `target_tokens: u64` derived independently of `tokens::context_guard`'s
// real acceptance formula. Ported to v2's API, this fixture's `base_view`
// already satisfies v2's stop condition (`estimate_view_tokens(view) <=
// target_tokens`) at level 0 — v2 would return it immediately, un-escalated
// — while the REAL guard (system prompt + tool schemas + this view, margin
// + reserve applied) still refuses it. That is the false refusal: a session
// v2's reducer calls "done" that the guard then sends back as exit 1.
// ---------------------------------------------------------------------------

#[test]
fn reduce_to_fit_rescues_sessions_in_the_false_refusal_band_between_v2_target_and_guard_acceptance()
{
    // Real system prompt + real builtin tool schemas — not stand-ins — so
    // the `fits` closure below measures the exact same wire request
    // `tokens::context_guard` would measure for a live `resume --reduced`.
    let config = Config::builder()
        .model("anthropic/claude-haiku-4-5")
        .build();
    let provider = Box::new(CountingProvider {
        calls: AtomicUsize::new(0),
    });
    let agent = Agent::with_provider(config, provider);
    let system_msg = agent.history()[0].clone();
    let tool_schemas = agent.tool_schemas();

    let context_limit = model_context_limit("anthropic/claude-haiku-4-5")
        .expect("claude-haiku-4-5 must be a known model for this fixture");
    assert_eq!(context_limit, 200_000);
    // v2's independently-derived stop condition: raw view tokens <=
    // (context_limit - reserve), with NO knowledge of system prompt, tools,
    // or the guard's 5/4 margin at all.
    let v2_target_tokens = context_limit - tokens::CONTEXT_RESPONSE_RESERVE_TOKENS; // 183,616

    let real_guard_fits = |view: &[ChatMessage]| {
        let mut candidate = Vec::with_capacity(view.len() + 1);
        candidate.push(system_msg.clone());
        candidate.extend_from_slice(view);
        tokens::context_guard(&candidate, &tool_schemas, context_limit).0
    };

    // Search for the false-refusal fixture directly rather than hardcoding
    // a message count: grow a text-heavy (no tool output/images to
    // byte-tighten — forces D6) session until its BASE (unreduced, level-0)
    // projected view sits inside the band v2 got wrong — under
    // `v2_target_tokens` (so v2 declares it done at level 0) but still
    // refused by the real guard (so it is a genuine false refusal, not just
    // an under-target session the guard also happens to accept).
    let policy = ReductionPolicy::default();
    let mut n = 200usize;
    let (full_msgs, base_view, base_tokens) = loop {
        let candidate_msgs = big_text_messages(n, 400);
        let (view, _) =
            reduce::project_messages(&candidate_msgs, &policy, &ReductionLog::default());
        let view_tokens = tokens::estimate_view_tokens(&view);
        assert!(
            view_tokens <= v2_target_tokens,
            "fixture search overshot v2_target_tokens ({v2_target_tokens}) at n={n} \
             ({view_tokens} tok) without ever finding a false-refusal case — the false-refusal \
             band may have moved; re-derive this fixture"
        );
        if !real_guard_fits(&view) {
            break (candidate_msgs, view, view_tokens);
        }
        n += 25;
    };

    // Confirm the fixture actually reproduces the bug: v2's stop condition
    // is satisfied by the untouched base view...
    assert!(
        base_tokens <= v2_target_tokens,
        "fixture must land at/under v2's target so v2's reducer would call it done at level 0: \
         base_tokens={base_tokens}, v2_target_tokens={v2_target_tokens}"
    );
    // ...but the real guard, on the real wire request, refuses that same
    // view — this IS the false refusal the Fable review found.
    assert!(
        !real_guard_fits(&base_view),
        "fixture must reproduce the false refusal: the real guard must reject the view v2's \
         reducer would already consider finished"
    );

    // v3: reduce_to_fit driven by the REAL guard as `fits` — the fix under
    // test. It must keep escalating (byte-knob tightening, then D6
    // turn-clearing) past where v2 stopped, until the guard actually
    // accepts.
    let (rescued_view, _log, _policy) = reduce::reduce_to_fit(
        &full_msgs,
        &policy,
        &ReductionLog::default(),
        real_guard_fits,
    );

    assert!(
        real_guard_fits(&rescued_view),
        "v3 must RESCUE a session in the false-refusal band: reduce_to_fit's stopping condition \
         must be the SAME boundary as context_guard's acceptance, not v2's looser \
         target_tokens — a session reduce_to_fit says it reduced must be a session the guard \
         will actually send"
    );
    assert!(
        tokens::estimate_view_tokens(&rescued_view) < base_tokens,
        "the rescue must have actually reduced further than v2's (false) 'done' point"
    );
}

// ---------------------------------------------------------------------------
// D1/D4 — the guard is a SESSION INVARIANT: a mock-provider agent that
// passes turn 1 but would exceed the limit on turn 2 must be refused on
// turn 2, not just checked once up front.
// ---------------------------------------------------------------------------

/// Always answers with a small text reply — no tool calls, so each `send`
/// is exactly one round-trip. Records how many times it was actually
/// invoked (PARITY-18 D3's "was a request really issued" contract, probed
/// indirectly: if the guard refuses turn 2 in-process, this must never be
/// called a second time).
struct CountingProvider {
    calls: AtomicUsize,
}

#[async_trait]
impl Provider for CountingProvider {
    async fn complete(
        &self,
        _req: &ChatRequest,
        _on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
    ) -> supercode_harness::Result<(ChatMessage, Usage)> {
        self.calls.fetch_add(1, Ordering::SeqCst);
        Ok((ChatMessage::assistant("ok"), Usage::default()))
    }
}

#[tokio::test]
async fn per_turn_guard_refuses_a_later_turn_that_would_exceed_the_limit() {
    let config = Config::builder().model("test/model").build();
    let provider = Box::new(CountingProvider {
        calls: AtomicUsize::new(0),
    });
    let mut agent = Agent::with_provider(config, provider);

    // Arm the guard with a limit that comfortably fits turn 1 (a short
    // system prompt + a short user message + the builtin tool schemas) but
    // cannot fit turn 1 AND a second big user message on top of it —
    // proving D4: the guard is re-checked on EVERY send, not only the
    // first.
    let turn1_tools = agent.tool_schemas();
    let turn1_projected = tokens::estimate_request_tokens(agent.history(), &turn1_tools);
    // A limit just above what turn 1 alone (margin-adjusted + reserve)
    // needs, but far below what turn 1 + a ~50KB second message would need.
    let limit =
        tokens::with_guard_margin(turn1_projected) + tokens::CONTEXT_RESPONSE_RESERVE_TOKENS + 200;
    agent.set_context_limit(limit);

    let first = agent.send("hi").await;
    assert!(
        first.is_ok(),
        "turn 1 should fit under the armed limit: {first:?}"
    );

    let big = "z".repeat(50_000);
    let second = agent.send(big).await;
    assert!(
        second.is_err(),
        "turn 2 (which pushes history well past the armed limit) must be refused, not silently sent"
    );
    match second.unwrap_err() {
        Error::ContextLimitExceeded { context_limit, .. } => {
            assert_eq!(context_limit, limit);
        }
        other => panic!("expected ContextLimitExceeded, got: {other:?}"),
    }
}

#[tokio::test]
async fn initial_guard_refusal_does_not_spend_the_user_message() {
    let config = Config::builder().model("test/model").build();
    let provider = Box::new(CountingProvider {
        calls: AtomicUsize::new(0),
    });
    let mut agent = Agent::with_provider(config, provider);
    let before = serde_json::to_string(agent.history()).unwrap();
    let before_log = agent.reduction_log().clone();
    agent.set_context_limit(tokens::CONTEXT_RESPONSE_RESERVE_TOKENS + 1_000);

    let result = agent.send("x".repeat(100_000)).await;
    assert!(
        matches!(result, Err(Error::ContextLimitExceeded { .. })),
        "oversized initial turn must fail at the local guard: {result:?}"
    );
    assert_eq!(
        serde_json::to_string(agent.history()).unwrap(),
        before,
        "a locally-refused prompt must not enter canonical history"
    );
    assert_eq!(
        agent.reduction_log(),
        &before_log,
        "a locally-refused prompt must not mutate reduction state"
    );
    assert!(
        !agent.request_issued(),
        "a local refusal must never reach the provider"
    );
}

#[tokio::test]
async fn per_turn_guard_is_a_noop_when_never_armed() {
    // Default behavior (no `set_context_limit` call): sending never gets
    // refused by the guard, regardless of size — this is every non-`resume
    // --reduced` caller today, and must see zero behavior change.
    let config = Config::builder().model("test/model").build();
    let provider = Box::new(CountingProvider {
        calls: AtomicUsize::new(0),
    });
    let mut agent = Agent::with_provider(config, provider);
    let big = "z".repeat(200_000);
    let result = agent.send(big).await;
    assert!(
        result.is_ok(),
        "an unarmed agent must never refuse on guard grounds: {result:?}"
    );
}

// ---------------------------------------------------------------------------
// D1 — overhead accounting: tool schemas are a real, nonzero contributor.
// ---------------------------------------------------------------------------

#[test]
fn agent_tool_schemas_are_a_nonzero_share_of_the_real_request() {
    let config = Config::builder().model("test/model").build();
    let provider = Box::new(CountingProvider {
        calls: AtomicUsize::new(0),
    });
    let agent = Agent::with_provider(config, provider);
    let tools = agent.tool_schemas();
    assert!(
        !tools.is_empty(),
        "a fresh agent should advertise its builtin tools"
    );
    let messages_only = tokens::estimate_view_tokens(agent.history());
    let with_tools = tokens::estimate_request_tokens(agent.history(), &tools);
    assert!(
        with_tools > messages_only,
        "the tool-schema array must be counted on top of the messages, not ignored (D1)"
    );
}