use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use supercode_harness::reduce::handoff::{build_handoff, ObjectiveSource};
use supercode_harness::reduce::summarize::SpanSummarizer;
use supercode_harness::reduce::{self, ReductionKind, ReductionLog, ReductionPolicy};
use supercode_harness::server::RpcEngine;
use supercode_harness::session::{Session, SessionFormat};
use supercode_harness::sidecar::SidecarWriter;
use supercode_harness::store::SessionStore;
use supercode_harness::{
Agent, ChatMessage, ChatRequest, Config, FrontendRuntime, FunctionCall, Provider, Role,
ToolCall, Usage,
};
fn tool_call_msg(id: &str, name: &str, args: serde_json::Value) -> 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: name.to_string(),
arguments: args.to_string(),
},
}]),
tool_call_id: None,
name: None,
metadata: Default::default(),
}
}
fn temp_dir(tag: &str) -> PathBuf {
static N: AtomicUsize = AtomicUsize::new(0);
let dir = std::env::temp_dir().join(format!(
"supercode-tr9-reduce-handoff-{tag}-{}-{}",
std::process::id(),
N.fetch_add(1, Ordering::SeqCst)
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
fn filler(len: usize) -> String {
(0..len).map(|i| (b'a' + (i % 26) as u8) as char).collect()
}
struct PlainReplies {
calls: AtomicUsize,
}
struct CapturingPlainReply {
request: Arc<Mutex<Option<ChatRequest>>>,
}
#[async_trait]
impl Provider for CapturingPlainReply {
async fn complete(
&self,
req: &ChatRequest,
_on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
) -> supercode_harness::Result<(ChatMessage, Usage)> {
*self.request.lock().unwrap() = Some(req.clone());
Ok((
ChatMessage::assistant(format!("reply 0: {}", filler(200))),
Usage::default(),
))
}
}
#[async_trait]
impl Provider for PlainReplies {
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);
Ok((
ChatMessage::assistant(format!("reply {n}: {}", filler(200))),
Usage::default(),
))
}
}
async fn build_fixture_sidecar(dir: &Path, n_turns: usize) -> (PathBuf, Session) {
let sidecar_path = dir.join("fixture.sidecar.jsonl");
let config = Config::builder()
.cwd(dir.to_path_buf())
.system_prompt("you are a careful coding agent")
.build();
let mut agent = Agent::with_provider(
config,
Box::new(PlainReplies {
calls: AtomicUsize::new(0),
}),
);
let empty_session = Session::from_claude_code_str("").unwrap();
let writer = SidecarWriter::create(&sidecar_path, &empty_session).unwrap();
agent.set_recorder(writer);
agent.set_reduction_policy(ReductionPolicy::default());
for i in 0..n_turns {
agent
.send(format!(
"turn {i}: investigate src/mod_{i}.rs — {}",
filler(300)
))
.await
.unwrap();
}
let sidecar =
Session::from_sidecar_str(&std::fs::read_to_string(&sidecar_path).unwrap()).unwrap();
(sidecar_path, sidecar)
}
#[tokio::test]
async fn dev01_handoff_view_is_at_most_10_percent_of_original_tokens() {
let dir = temp_dir("dev01");
let (_path, sidecar) = build_fixture_sidecar(&dir, 40).await;
let result = build_handoff(
&sidecar.messages,
&["3".to_string()],
4,
Some("finish the migration"),
false,
None,
)
.unwrap();
let full_tok = supercode_harness::tokens::estimate_view_tokens(&sidecar.messages);
let view_tok = supercode_harness::tokens::estimate_view_tokens(&result.view);
assert!(full_tok > 0);
assert!(
(view_tok as f64) <= 0.10 * (full_tok as f64),
"view {view_tok} must be <=10% of full {full_tok}"
);
let view_text = result
.view
.iter()
.filter_map(|m| m.content.clone())
.collect::<Vec<_>>()
.join("\n");
assert!(view_text.contains("=== HANDOFF ==="));
assert!(view_text.contains("OBJECTIVE: finish the migration"));
std::fs::remove_dir_all(&dir).ok();
}
#[tokio::test]
async fn dev02_every_gap_expands_byte_exact_and_export_is_unaffected() {
let dir = temp_dir("dev02");
let (sidecar_path, sidecar) = build_fixture_sidecar(&dir, 30).await;
let sidecar_str = std::fs::read_to_string(&sidecar_path).unwrap();
let before_export = reduce::export_session(&sidecar_str, SessionFormat::Codex).unwrap();
let result =
build_handoff(&sidecar.messages, &["2".to_string()], 3, None, false, None).unwrap();
assert!(!result.gap_ids.is_empty());
let after_export = reduce::export_session(&sidecar_str, SessionFormat::Codex).unwrap();
assert_eq!(before_export, after_export);
assert_eq!(after_export.matches("sc-reduced").count(), 0);
reduce::verify_log(&result.log, &sidecar).expect("verify_log must pass clean");
let inverted = reduce::invert(&result.view, &result.log, &sidecar).unwrap();
assert_eq!(inverted.len(), sidecar.messages.len() + 1);
assert!(inverted[0]
.content
.as_deref()
.unwrap()
.contains("=== HANDOFF ==="));
for (restored, original) in inverted[1..].iter().zip(&sidecar.messages) {
assert_eq!(restored.content, original.content);
assert_eq!(restored.role, original.role);
}
for id in &result.gap_ids {
let outcome =
reduce::rehydrate::expand_reduction(&result.log, &sidecar.messages, None, id, None)
.unwrap();
assert!(!outcome.content.is_empty());
assert!(
!outcome.content.contains("HANDOFF"),
"expanded content must be the genuine original, never banner text"
);
}
std::fs::remove_dir_all(&dir).ok();
}
#[tokio::test]
async fn frontend_and_headless_reduced_continuations_are_disk_identical_and_invertible() {
let dir = temp_dir("frontend-headless-parity");
let (source_path, sidecar) = build_fixture_sidecar(&dir, 5).await;
let source_before = std::fs::read(&source_path).unwrap();
let handoff = build_handoff(&sidecar.messages, &[], 2, None, false, None).unwrap();
assert!(!handoff.log.reductions.is_empty());
let headless_store = SessionStore::open(dir.join("headless-store")).unwrap();
let frontend_store = SessionStore::open(dir.join("frontend-store")).unwrap();
let name = "continued";
let headless_capture = Arc::new(Mutex::new(None));
let frontend_capture = Arc::new(Mutex::new(None));
let fixed_timestamp = "2026-07-19T12:00:00.000Z";
let make_agent = |sidecar_path: &Path, request: Arc<Mutex<Option<ChatRequest>>>| {
let mut agent = Agent::with_provider(
Config::builder().cwd(dir.clone()).build(),
Box::new(CapturingPlainReply { request }),
);
agent.load_session(sidecar.clone());
agent.set_recorder(
SidecarWriter::create_with_timestamp(sidecar_path, &sidecar, fixed_timestamp).unwrap(),
);
agent.set_reduction_policy(ReductionPolicy {
clear_turns_older_than: None,
..ReductionPolicy::default()
});
agent.set_reduction_log(handoff.log.clone());
agent
};
let mut headless = make_agent(&headless_store.sidecar_path(name), headless_capture.clone());
let headless_reply = headless.send("same reduced continuation").await.unwrap();
let headless_log = headless.reduction_log().clone();
headless_store
.save_reduction_log(name, &headless_log)
.unwrap();
let runtime = RpcEngine::new_named(
make_agent(&frontend_store.sidecar_path(name), frontend_capture.clone()),
"reduced-frontend",
None,
);
let frontend_reply =
FrontendRuntime::submit(runtime.as_ref(), "same reduced continuation".into())
.await
.unwrap();
let frontend_log = runtime
.finalize_with(|agent| agent.reduction_log().clone())
.await;
frontend_store
.save_reduction_log(name, &frontend_log)
.unwrap();
assert_eq!(frontend_reply, headless_reply);
let headless_request = headless_capture.lock().unwrap().take().unwrap();
let frontend_request = frontend_capture.lock().unwrap().take().unwrap();
assert_eq!(frontend_request, headless_request);
let reduced_stubs = frontend_request
.messages
.iter()
.filter_map(|message| message.content.as_deref())
.filter_map(reduce::stub::parse)
.collect::<Vec<_>>();
assert!(!reduced_stubs.is_empty());
for (_, id, _) in &reduced_stubs {
assert!(frontend_log
.reductions
.iter()
.any(|reduction| reduction.id == *id));
}
assert_eq!(
std::fs::read(frontend_store.sidecar_path(name)).unwrap(),
std::fs::read(headless_store.sidecar_path(name)).unwrap()
);
assert_eq!(
std::fs::read(dir.join("frontend-store/continued.reduction.json")).unwrap(),
std::fs::read(dir.join("headless-store/continued.reduction.json")).unwrap()
);
assert_eq!(std::fs::read(&source_path).unwrap(), source_before);
let disk_sidecar = frontend_store.load_sidecar(name).unwrap().unwrap();
let reloaded = Session::from_sidecar_str(&disk_sidecar).unwrap();
let reloaded_log: ReductionLog = frontend_store.load_reduction_log(name).unwrap().unwrap();
reduce::verify_log(&reloaded_log, &reloaded).unwrap();
let policy = ReductionPolicy {
clear_turns_older_than: None,
..ReductionPolicy::default()
};
let (reduced_view, reprojected_log) = reduce::project(&reloaded, &policy, &reloaded_log);
assert_eq!(reprojected_log, reloaded_log);
assert!(reduced_view.iter().any(|message| {
message
.content
.as_deref()
.is_some_and(|content| content.contains(reduce::REDUCTION_SENTINEL))
}));
assert_eq!(
reduce::invert(&reduced_view, &reloaded_log, &reloaded).unwrap(),
reloaded.messages
);
let headless_disk_sidecar = headless_store.load_sidecar(name).unwrap().unwrap();
let headless_export =
reduce::export_session(&headless_disk_sidecar, SessionFormat::ClaudeCode).unwrap();
let frontend_export = reduce::export_session(&disk_sidecar, SessionFormat::ClaudeCode).unwrap();
assert_eq!(frontend_export, headless_export);
assert!(!frontend_export.contains(reduce::REDUCTION_SENTINEL));
let export_reloaded = Session::from_claude_code_str(&frontend_export).unwrap();
assert_eq!(
export_reloaded
.messages
.iter()
.map(|message| (message.role, message.content.as_deref()))
.collect::<Vec<_>>(),
reloaded
.messages
.iter()
.map(|message| (message.role, message.content.as_deref()))
.collect::<Vec<_>>()
);
std::fs::remove_dir_all(&dir).ok();
}
#[tokio::test]
async fn dev03_system_prompt_and_last_k_turns_are_always_kept() {
let dir = temp_dir("dev03");
let (_path, sidecar) = build_fixture_sidecar(&dir, 25).await;
let result = build_handoff(&sidecar.messages, &[], 5, None, false, None).unwrap();
let len = sidecar.messages.len();
for idx in (len - 5)..len {
assert!(
result.kept_indices.contains(&idx),
"last-5 index {idx} must always be kept: {:?}",
result.kept_indices
);
}
let last_user_content = sidecar
.messages
.iter()
.rev()
.find(|m| m.role == Role::User)
.and_then(|m| m.content.clone())
.unwrap();
let view_text = result
.view
.iter()
.filter_map(|m| m.content.clone())
.collect::<Vec<_>>()
.join("\n");
assert!(view_text.contains(&last_user_content));
std::fs::remove_dir_all(&dir).ok();
}
struct FailingSummarizer;
impl SpanSummarizer for FailingSummarizer {
fn summarize(&self, _span_text: &str) -> supercode_harness::reduce::ReductionResult<String> {
Err(supercode_harness::reduce::ReductionError::new(
"injected failure",
))
}
fn model_id(&self) -> &str {
"failing-model"
}
}
struct WorkingSummarizer;
impl SpanSummarizer for WorkingSummarizer {
fn summarize(&self, span_text: &str) -> supercode_harness::reduce::ReductionResult<String> {
Ok(format!("drafted objective over {} chars", span_text.len()))
}
fn model_id(&self) -> &str {
"drafting-model"
}
}
#[tokio::test]
async fn dev05_draft_objective_off_by_default_falls_back_and_drafts_when_enabled() {
let dir = temp_dir("dev05");
let (_path, sidecar) = build_fixture_sidecar(&dir, 20).await;
let off = build_handoff(
&sidecar.messages,
&[],
3,
None,
false,
Some(&WorkingSummarizer),
)
.unwrap();
assert_eq!(off.objective_source, ObjectiveSource::Fallback);
assert_eq!(off.objective_text, None);
assert!(!off.view[0]
.content
.as_deref()
.unwrap()
.contains("OBJECTIVE:"));
let failing = build_handoff(
&sidecar.messages,
&[],
3,
None,
true,
Some(&FailingSummarizer),
)
.unwrap();
assert_eq!(failing.objective_source, ObjectiveSource::Fallback);
assert_eq!(failing.objective_text, None);
let drafted = build_handoff(
&sidecar.messages,
&[],
3,
None,
true,
Some(&WorkingSummarizer),
)
.unwrap();
assert_eq!(drafted.objective_source, ObjectiveSource::Drafted);
assert!(drafted
.objective_text
.as_deref()
.unwrap()
.contains("drafted objective over"));
let user_wins = build_handoff(
&sidecar.messages,
&[],
3,
Some("do the actual thing"),
true,
Some(&WorkingSummarizer),
)
.unwrap();
assert_eq!(user_wins.objective_source, ObjectiveSource::UserSupplied);
assert_eq!(
user_wins.objective_text.as_deref(),
Some("do the actual thing")
);
std::fs::remove_dir_all(&dir).ok();
}
const SECRET: &str = "the rollback token is RB-4471-XQ";
struct HandoffDemoProvider {
calls: AtomicUsize,
}
#[async_trait]
impl Provider for HandoffDemoProvider {
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 == 0 {
let body = serde_json::to_string(&req.messages).unwrap();
assert!(
body.contains("=== HANDOFF ==="),
"the handoff banner must be present in the request view: {body}"
);
assert!(
!body.contains("RB-4471-XQ"),
"the secret must not be directly visible after handoff"
);
assert!(req.tools.iter().any(|t| t.name == "sidecar_search"));
return Ok((
tool_call_msg(
"s1",
"sidecar_search",
serde_json::json!({"query": "rollback token"}),
),
Usage::default(),
));
}
if n == 1 {
let last = req.messages.last().unwrap();
assert_eq!(last.role, Role::Tool);
let result: reduce::rehydrate::SidecarSearchResult =
serde_json::from_str(last.content.as_deref().unwrap()).unwrap();
assert_eq!(result.matches.len(), 1, "{result:?}");
return Ok((
tool_call_msg(
"e1",
"expand_reduction",
serde_json::json!({"reduction_id": result.matches[0].reduction_id}),
),
Usage::default(),
));
}
let last = req.messages.last().unwrap();
assert_eq!(last.role, Role::Tool);
let expanded = last.content.clone().unwrap_or_default();
assert!(expanded.contains("RB-4471-XQ"), "{expanded}");
Ok((
ChatMessage::assistant("The rollback token is RB-4471-XQ."),
Usage::default(),
))
}
}
#[tokio::test]
async fn dev04_tier2_demo_continuation_drills_into_a_handoff_dropped_turn() {
let dir = temp_dir("dev04-demo");
let n_filler = 5;
let (_path, sidecar) = build_fixture_sidecar_with_secret(&dir, n_filler).await;
let result = build_handoff(&sidecar.messages, &[], 2, None, false, None).unwrap();
assert!(
result
.log
.reductions
.iter()
.any(|r| matches!(r.kind, ReductionKind::TurnsCleared { .. })),
"the secret's turn must be handoff-cleared"
);
let banner = result.view[0].content.clone().unwrap();
let config = Config::builder()
.cwd(dir.to_path_buf())
.system_prompt(banner)
.build();
let mut agent = Agent::with_provider(
config,
Box::new(HandoffDemoProvider {
calls: AtomicUsize::new(0),
}),
);
agent.load_session(sidecar.clone());
agent.set_reduction_policy(ReductionPolicy {
clear_turns_older_than: None,
..ReductionPolicy::default()
});
agent.set_reduction_log(result.log.clone());
let reply = agent.send("What was the rollback token?").await.unwrap();
assert_eq!(reply, "The rollback token is RB-4471-XQ.");
let workspace_target = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target");
std::fs::create_dir_all(&workspace_target).ok();
let transcript_path = workspace_target.join("tr9-handoff-demo-transcript.jsonl");
agent.save_transcript(&transcript_path).unwrap();
assert!(transcript_path.exists());
eprintln!(
"TR-9 dev/04 demo transcript archived at {}",
transcript_path.display()
);
std::fs::remove_dir_all(&dir).ok();
}
async fn build_fixture_sidecar_with_secret(dir: &Path, n_filler: usize) -> (PathBuf, Session) {
let sidecar_path = dir.join("secret.sidecar.jsonl");
let config = Config::builder()
.cwd(dir.to_path_buf())
.system_prompt("you are a careful coding agent")
.build();
let mut agent = Agent::with_provider(
config,
Box::new(PlainReplies {
calls: AtomicUsize::new(0),
}),
);
let empty_session = Session::from_claude_code_str("").unwrap();
let writer = SidecarWriter::create(&sidecar_path, &empty_session).unwrap();
agent.set_recorder(writer);
agent.set_reduction_policy(ReductionPolicy::default());
agent
.send(format!("Please remember this: {SECRET}"))
.await
.unwrap();
for i in 0..n_filler {
agent
.send(format!("filler turn {i}, just say ack"))
.await
.unwrap();
}
let sidecar =
Session::from_sidecar_str(&std::fs::read_to_string(&sidecar_path).unwrap()).unwrap();
(sidecar_path, sidecar)
}