mod snapshot {
use crate::brain::agent::service::AgentService;
use crate::brain::provider::{ContentBlock, Message, Role};
fn tool_use_msg(name: &str, input: &str) -> Message {
Message {
role: Role::Assistant,
content: vec![ContentBlock::ToolUse {
id: "tu_1".into(),
name: name.into(),
input: serde_json::json!({ "arg": input }),
}],
}
}
fn tool_result_msg(content: &str) -> Message {
Message {
role: Role::User,
content: vec![ContentBlock::ToolResult {
tool_use_id: "tu_1".into(),
content: content.into(),
is_error: None,
}],
}
}
#[test]
fn empty_messages_returns_empty() {
let result = AgentService::format_recent_messages(&[], 8);
assert!(result.is_empty());
}
#[test]
fn formats_user_and_assistant_text() {
let msgs = vec![
Message::user("Hello agent"),
Message::assistant("Hello user"),
];
let result = AgentService::format_recent_messages(&msgs, 8);
assert!(result.contains("**User**: Hello agent"));
assert!(result.contains("**Assistant**: Hello user"));
}
#[test]
fn formats_system_messages() {
let msgs = vec![Message::system("You are helpful")];
let result = AgentService::format_recent_messages(&msgs, 8);
assert!(result.contains("**System**: You are helpful"));
}
#[test]
fn formats_tool_use_blocks() {
let msgs = vec![tool_use_msg("read_file", "src/main.rs")];
let result = AgentService::format_recent_messages(&msgs, 8);
assert!(result.contains("**Assistant**: [tool_use: read_file("));
assert!(result.contains("src/main.rs"));
}
#[test]
fn formats_tool_result_blocks() {
let msgs = vec![tool_result_msg("fn main() {}")];
let result = AgentService::format_recent_messages(&msgs, 8);
assert!(result.contains("**User**: [tool_result: fn main() {}]"));
}
#[test]
fn formats_image_blocks() {
let msgs = vec![Message {
role: Role::User,
content: vec![ContentBlock::Image {
source: crate::brain::provider::ImageSource::Base64 {
media_type: "image/png".into(),
data: "abc".into(),
},
}],
}];
let result = AgentService::format_recent_messages(&msgs, 8);
assert!(result.contains("**User**: [image]"));
}
#[test]
fn truncates_long_text_at_500_chars() {
let long_text = "x".repeat(800);
let msgs = vec![Message::user(long_text)];
let result = AgentService::format_recent_messages(&msgs, 8);
assert!(result.contains("[truncated]"));
assert!(!result.contains(&"x".repeat(501)));
}
#[test]
fn truncates_long_tool_result_at_300_chars() {
let long_content = "y".repeat(500);
let msgs = vec![tool_result_msg(&long_content)];
let result = AgentService::format_recent_messages(&msgs, 8);
assert!(result.contains("[truncated]"));
assert!(!result.contains(&"y".repeat(301)));
}
#[test]
fn truncates_long_tool_use_input_at_200_chars() {
let long_input = "z".repeat(400);
let msgs = vec![tool_use_msg("big_tool", &long_input)];
let result = AgentService::format_recent_messages(&msgs, 8);
assert!(result.contains("big_tool("));
}
#[test]
fn respects_n_limit_takes_last_n() {
let msgs: Vec<Message> = (0..10)
.map(|i| Message::user(format!("msg_{}", i)))
.collect();
let result = AgentService::format_recent_messages(&msgs, 3);
assert!(!result.contains("msg_6"));
assert!(result.contains("msg_7"));
assert!(result.contains("msg_8"));
assert!(result.contains("msg_9"));
}
#[test]
fn n_larger_than_messages_returns_all() {
let msgs = vec![Message::user("only one")];
let result = AgentService::format_recent_messages(&msgs, 100);
assert!(result.contains("only one"));
}
#[test]
fn mixed_content_blocks_in_single_message() {
let msgs = vec![Message {
role: Role::User,
content: vec![
ContentBlock::Text {
text: "Check this file".into(),
},
ContentBlock::ToolResult {
tool_use_id: "tu_1".into(),
content: "file contents here".into(),
is_error: None,
},
],
}];
let result = AgentService::format_recent_messages(&msgs, 8);
assert!(result.contains("**User**: Check this file"));
assert!(result.contains("**User**: [tool_result: file contents here]"));
}
#[test]
fn realistic_tool_loop_sequence() {
let msgs = vec![
Message::user("Fix the bug in main.rs"),
tool_use_msg("read_file", "src/main.rs"),
tool_result_msg("fn main() { panic!() }"),
Message::assistant("I see the issue, let me fix it"),
tool_use_msg("edit_file", "src/main.rs"),
tool_result_msg("File updated successfully"),
Message::assistant("Done, the bug is fixed"),
];
let result = AgentService::format_recent_messages(&msgs, 8);
assert!(result.contains("Fix the bug"));
assert!(result.contains("read_file"));
assert!(result.contains("panic!()"));
assert!(result.contains("I see the issue"));
assert!(result.contains("edit_file"));
assert!(result.contains("File updated"));
assert!(result.contains("bug is fixed"));
}
}
mod compaction_e2e {
use crate::brain::agent::context::AgentContext;
use crate::brain::agent::service::AgentService;
use crate::brain::provider::{ContentBlock, Message};
use uuid::Uuid;
fn simulate_compaction(messages: Vec<Message>, keep_budget: usize) -> AgentContext {
let session_id = Uuid::new_v4();
let mut context = AgentContext::new(session_id, 100_000);
for msg in messages {
context.add_message(msg);
}
let recent_snapshot = AgentService::format_recent_messages(&context.messages, 8);
let summary = "## Current Task\nUser is fixing a bug.\n## Files Modified\n- src/main.rs";
let summary_with_context = if recent_snapshot.is_empty() {
summary.to_string()
} else {
format!(
"{}\n\n## Recent Message Pairs (pre-compaction snapshot)\n\
The following are the last messages before compaction \
— use them to understand the current task state and decide what context to reload.\n\n{}",
summary, recent_snapshot
)
};
context.compact_with_summary(summary_with_context, keep_budget);
context
}
#[test]
fn compaction_includes_snapshot_in_summary_message() {
let msgs = vec![
Message::user("Fix the bug"),
Message::assistant("Looking at it now"),
Message::user("Check main.rs"),
Message::assistant("Found the issue"),
];
let context = simulate_compaction(msgs, 80_000);
let first = &context.messages[0];
if let Some(ContentBlock::Text { text }) = first.content.first() {
assert!(text.contains("CONTEXT COMPACTION"));
assert!(text.contains("Current Task"));
assert!(text.contains("Recent Message Pairs"));
assert!(text.contains("Fix the bug"));
assert!(text.contains("Found the issue"));
} else {
panic!("First message should be text compaction summary");
}
}
#[test]
fn compaction_keeps_recent_messages_after_summary() {
let msgs: Vec<Message> = (0..10)
.map(|i| Message::user(format!("message_{}", i)))
.collect();
let context = simulate_compaction(msgs, 80_000);
assert_eq!(context.messages.len(), 11);
if let Some(ContentBlock::Text { text }) = context.messages.last().unwrap().content.first()
{
assert!(text.contains("message_9"));
} else {
panic!("Last message should be message_9");
}
}
#[test]
fn compaction_snapshot_captures_tool_use_sequence() {
let msgs = vec![
Message::user("Deploy the app"),
Message {
role: crate::brain::provider::Role::Assistant,
content: vec![ContentBlock::ToolUse {
id: "tu_1".into(),
name: "bash".into(),
input: serde_json::json!({"command": "cargo build"}),
}],
},
Message {
role: crate::brain::provider::Role::User,
content: vec![ContentBlock::ToolResult {
tool_use_id: "tu_1".into(),
content: "Build succeeded".into(),
is_error: None,
}],
},
Message::assistant("Build complete, deploying now"),
];
let context = simulate_compaction(msgs, 80_000);
if let Some(ContentBlock::Text { text }) = context.messages[0].content.first() {
assert!(text.contains("tool_use: bash"));
assert!(text.contains("cargo build"));
assert!(text.contains("Build succeeded"));
assert!(text.contains("Deploy the app"));
} else {
panic!("Summary should contain tool snapshot");
}
}
#[test]
fn compaction_with_no_messages_still_works() {
let context = simulate_compaction(vec![], 80_000);
assert_eq!(context.messages.len(), 1);
if let Some(ContentBlock::Text { text }) = context.messages[0].content.first() {
assert!(text.contains("Current Task"));
assert!(!text.contains("Recent Message Pairs"));
}
}
#[test]
fn compaction_recalculates_token_count() {
let msgs: Vec<Message> = (0..20)
.map(|i| Message::user(format!("Long message {} {}", i, "x".repeat(300))))
.collect();
let session_id = Uuid::new_v4();
let mut context = AgentContext::new(session_id, 100_000);
for msg in msgs {
context.add_message(msg);
}
let tokens_before = context.token_count;
context.compact_with_summary("Brief summary".to_string(), 500);
assert!(context.token_count < tokens_before);
assert!(context.token_count > 0);
}
#[test]
fn compaction_snapshot_truncates_large_messages() {
let huge_text = "z".repeat(1000);
let msgs = vec![Message::user(huge_text.clone())];
let context = simulate_compaction(msgs, 200);
if let Some(ContentBlock::Text { text }) = context.messages[0].content.first() {
assert!(text.contains("[truncated]"));
assert!(!text.contains(&huge_text));
}
}
#[test]
fn compaction_snapshot_only_last_8_even_with_many_messages() {
let msgs: Vec<Message> = (0..20)
.map(|i| Message::user(format!("msg_{}", i)))
.collect();
let context = simulate_compaction(msgs, 4);
if let Some(ContentBlock::Text { text }) = context.messages[0].content.first() {
assert!(text.contains("msg_12"));
assert!(text.contains("msg_19"));
assert!(!text.contains("msg_0:"));
assert!(!text.contains("msg_5:"));
}
}
}
mod no_truncation {
use crate::brain::agent::context::AgentContext;
use crate::brain::provider::Message;
use uuid::Uuid;
#[test]
fn context_has_no_trim_to_target() {
let session_id = Uuid::new_v4();
let mut context = AgentContext::new(session_id, 100_000);
for i in 0..50 {
context.add_message(Message::user(format!("Message {} {}", i, "x".repeat(500))));
}
let tokens_before = context.token_count;
let messages_before = context.messages.len();
assert_eq!(context.messages.len(), messages_before);
assert_eq!(context.token_count, tokens_before);
assert!(messages_before == 50);
}
#[test]
fn high_usage_context_preserves_all_messages() {
let session_id = Uuid::new_v4();
let max_tokens = 10_000;
let mut context = AgentContext::new(session_id, max_tokens);
let mut i = 0;
while (context.token_count as f64 / max_tokens as f64) < 0.85 {
context.add_message(Message::user(format!("msg_{} {}", i, "data".repeat(100))));
i += 1;
}
let message_count = context.messages.len();
let usage_pct = (context.token_count as f64 / max_tokens as f64) * 100.0;
assert!(usage_pct > 80.0);
assert_eq!(context.messages.len(), message_count);
if let Some(crate::brain::provider::ContentBlock::Text { text }) =
context.messages[0].content.first()
{
assert!(text.contains("msg_0"));
} else {
panic!("First message should be msg_0");
}
}
}
mod post_compaction_instruction {
#[test]
fn instruction_does_not_contain_load_all() {
let pre_loop_instruction = "\
[SYSTEM: Context was auto-compacted. The summary above includes a snapshot \
of recent messages before compaction.\n\
POST-COMPACTION PROTOCOL (follow in order):\n\
1. Read the compaction summary and the recent message snapshot to understand \
the current task, tools in use, and what you were doing.\n\
2. If the summary references older context you don't have in the snapshot, use \
`session_search` with specific keywords to find those messages.\n\
3. If you need specific brain context, selectively load ONLY the relevant \
brain file (e.g. TOOLS.md, SOUL.md, USER.md). NEVER use name=\"all\".\n\
4. Continue the task immediately. Do NOT repeat completed work. \
Do NOT ask the user for instructions — you have everything you need.]";
assert!(!pre_loop_instruction.contains("name=\"all\" to reload"));
assert!(pre_loop_instruction.contains("NEVER use name=\"all\""));
assert!(pre_loop_instruction.contains("selectively load ONLY"));
assert!(pre_loop_instruction.contains("recent message snapshot"));
assert!(pre_loop_instruction.contains("session_search"));
assert!(!pre_loop_instruction.contains("git status"));
}
#[test]
fn mid_loop_instruction_tells_agent_to_continue() {
let mid_loop_instruction = "\
[SYSTEM: Context was auto-compacted mid-loop. The summary above includes \
a snapshot of recent messages. POST-COMPACTION PROTOCOL:\n\
1. Review the summary and snapshot to understand current task state.\n\
2. Use `session_search` with keywords from the summary if you need older \
context not in the snapshot.\n\
3. Continue the task immediately. Do NOT repeat completed work. \
Do NOT ask for instructions.]";
assert!(mid_loop_instruction.contains("Continue the task immediately"));
assert!(mid_loop_instruction.contains("Do NOT repeat completed work"));
assert!(mid_loop_instruction.contains("snapshot of recent messages"));
assert!(!mid_loop_instruction.contains("name=\"all\""));
assert!(mid_loop_instruction.contains("session_search"));
assert!(!mid_loop_instruction.contains("git status"));
}
}
#[cfg(test)]
mod banner_strip {
use crate::brain::agent::service::AgentService;
#[test]
fn strips_banner_prefix_keeps_summary_body() {
let mut s = String::from(
"[CONTEXT COMPACTION — The conversation was automatically compacted. \
Below is a structured summary of everything before this point.]\n\
\n\
1. Chronological Analysis\n\
• User asked about real estate flow.\n",
);
AgentService::strip_compaction_banner(&mut s);
assert!(!s.starts_with("[CONTEXT COMPACTION"));
assert!(s.starts_with("1. Chronological Analysis"));
assert!(s.contains("real estate flow"));
}
#[test]
fn noop_when_no_banner() {
let original =
"Plain assistant or user text without the compaction marker prefix.".to_string();
let mut s = original.clone();
AgentService::strip_compaction_banner(&mut s);
assert_eq!(s, original);
}
#[test]
fn noop_when_banner_lacks_double_newline_separator() {
let original = "[CONTEXT COMPACTION — broken format on one line]".to_string();
let mut s = original.clone();
AgentService::strip_compaction_banner(&mut s);
assert_eq!(s, original);
}
#[test]
fn imitation_double_bracket_is_left_alone() {
let mut s = "[[CONTEXT COMPACTION — fake from model]]\n\nbody from model".to_string();
AgentService::strip_compaction_banner(&mut s);
assert!(s.starts_with("[[CONTEXT COMPACTION"));
}
}