Improve research tool error reporting for scout agent failures

When the scout agent fails (e.g., context window exhaustion), now:
- Captures both stdout and stderr from the scout process
- Detects context window exhaustion errors with specific patterns
- Provides detailed, actionable error messages to the user
- Shows suggestions for how to work around the issue
- Includes technical details (exit code, error output) for debugging

Handles two failure modes:
1. Scout agent exits with non-zero status
2. Scout agent exits successfully but doesn't produce valid report markers

Both cases now surface clear error messages instead of cryptic failures.
This commit is contained in:
Dhanji R. Prasanna
2026-01-10 20:50:43 +11:00
parent 9bef7753bf
commit 86709834e2

View File

@@ -143,6 +143,12 @@ fn truncate_command_snippet(cmd: &str, max_len: usize) -> String {
}
}
/// Error patterns that indicate context window exhaustion
const CONTEXT_ERROR_PATTERNS: &[&str] = &[
"context length", "context_length_exceeded", "maximum context", "token limit",
"too many tokens", "exceeds the model", "context window", "max_tokens",
];
pub async fn execute_research<W: UiWriter>(
tool_call: &ToolCall,
ctx: &mut ToolContext<'_, W>,
@@ -181,10 +187,24 @@ pub async fn execute_research<W: UiWriter>(
let stdout = child.stdout.take()
.ok_or_else(|| anyhow::anyhow!("Failed to capture scout agent stdout"))?;
// Also capture stderr for error messages
let stderr = child.stderr.take()
.ok_or_else(|| anyhow::anyhow!("Failed to capture scout agent stderr"))?;
let mut reader = BufReader::new(stdout).lines();
let mut all_output = Vec::new();
// Collect all lines, showing only translated progress messages
// Spawn a task to collect stderr
let stderr_handle = tokio::spawn(async move {
let mut stderr_reader = BufReader::new(stderr).lines();
let mut stderr_output = Vec::new();
while let Some(line) = stderr_reader.next_line().await.ok().flatten() {
stderr_output.push(line);
}
stderr_output
});
// Collect stdout lines, showing only translated progress messages
while let Some(line) = reader.next_line().await? {
all_output.push(line.clone());
@@ -195,18 +215,90 @@ pub async fn execute_research<W: UiWriter>(
}
}
// Collect stderr output
let stderr_output = stderr_handle.await.unwrap_or_default();
// Wait for the process to complete
let status = child.wait().await
.map_err(|e| anyhow::anyhow!("Failed to wait for scout agent: {}", e))?;
if !status.success() {
return Ok(format!("❌ Scout agent failed with exit code: {:?}", status.code()));
// Build detailed error message
let exit_code = status.code().map(|c| c.to_string()).unwrap_or_else(|| "unknown".to_string());
let full_output = all_output.join("\n");
let stderr_text = stderr_output.join("\n");
// Check for context window exhaustion
let combined_output = format!("{} {}", full_output, stderr_text).to_lowercase();
let is_context_error = CONTEXT_ERROR_PATTERNS.iter()
.any(|pattern| combined_output.contains(pattern));
if is_context_error {
let error_msg = format!(
"❌ **Scout Agent Error: Context Window Exhausted**\n\n\
The research query required more context than the model supports.\n\n\
**Suggestions:**\n\
- Try a more specific, narrower query\n\
- Break the research into smaller sub-questions\n\
- Use a model with a larger context window\n\n\
**Technical Details:**\n\
Exit code: {}\n\
{}",
exit_code,
if !stderr_text.is_empty() { format!("Error output: {}", stderr_text.chars().take(500).collect::<String>()) } else { String::new() }
);
ctx.ui_writer.println(&error_msg);
return Ok(error_msg);
}
// Generic error with details
let error_msg = format!(
"❌ **Scout Agent Failed**\n\n\
Exit code: {}\n\n\
{}{}",
exit_code,
if !stderr_text.is_empty() { format!("**Error output:**\n{}\n\n", stderr_text.chars().take(1000).collect::<String>()) } else { String::new() },
if all_output.len() > 0 { format!("**Last output lines:**\n{}", all_output.iter().rev().take(10).rev().cloned().collect::<Vec<_>>().join("\n")) } else { String::new() }
);
ctx.ui_writer.println(&error_msg);
return Ok(error_msg);
}
// Join all output and extract the report between markers
let full_output = all_output.join("\n");
let report = extract_report(&full_output)?;
let report = match extract_report(&full_output) {
Ok(r) => r,
Err(e) => {
// Check if this looks like a context exhaustion issue
let combined = format!("{} {}", full_output, stderr_output.join(" ")).to_lowercase();
let is_context_error = CONTEXT_ERROR_PATTERNS.iter()
.any(|pattern| combined.contains(pattern));
let error_msg = if is_context_error {
format!(
"❌ **Scout Agent Error: Context Window Exhausted**\n\n\
The scout agent ran out of context before completing the research report.\n\n\
**Suggestions:**\n\
- Try a more specific, narrower query\n\
- Break the research into smaller sub-questions\n\n\
**Technical Details:**\n\
{}",
e
)
} else {
format!(
"❌ **Scout Agent Error: Report Extraction Failed**\n\n\
{}\n\n\
The scout agent completed but did not produce a valid report.\n\
This may indicate the agent encountered an error during research.",
e
)
};
ctx.ui_writer.println(&error_msg);
return Ok(error_msg);
}
};
// Print the research brief to the console for scrollback reference
// The report is printed without stripping ANSI codes to preserve formatting