diff --git a/crates/ironclaw_engine/src/executor/trace.rs b/crates/ironclaw_engine/src/executor/trace.rs index a2396737..37afdb2e 100644 --- a/crates/ironclaw_engine/src/executor/trace.rs +++ b/crates/ironclaw_engine/src/executor/trace.rs @@ -33,9 +33,25 @@ pub struct ExecutionTrace { pub messages: Vec, pub events: Vec, pub issues: Vec, + pub reflection: Option, pub timestamp: chrono::DateTime, } +/// Reflection results captured in the trace. +#[derive(Debug, Serialize)] +pub struct ReflectionTrace { + pub docs: Vec, + pub tokens_used: u64, +} + +/// A single doc produced by reflection, for the trace. +#[derive(Debug, Serialize)] +pub struct ReflectionDocRecord { + pub doc_type: String, + pub title: String, + pub content: String, +} + /// A message in the trace with role labeling. #[derive(Debug, Serialize)] pub struct MessageRecord { @@ -96,6 +112,7 @@ pub fn build_trace(thread: &Thread) -> ExecutionTrace { messages, events: thread.events.clone(), issues, + reflection: None, timestamp: Utc::now(), } } @@ -126,6 +143,22 @@ pub fn write_trace(trace: &ExecutionTrace) -> Option { } } +/// Attach reflection results to a trace. +pub fn attach_reflection(trace: &mut ExecutionTrace, result: &crate::reflection::ReflectionResult) { + trace.reflection = Some(ReflectionTrace { + docs: result + .docs + .iter() + .map(|d| ReflectionDocRecord { + doc_type: format!("{:?}", d.doc_type), + title: d.title.clone(), + content: d.content.clone(), + }) + .collect(), + tokens_used: result.tokens_used.total(), + }); +} + /// Print a summary of the trace to the log. pub fn log_trace_summary(trace: &ExecutionTrace) { info!( @@ -162,6 +195,24 @@ pub fn log_trace_summary(trace: &ExecutionTrace) { ), } } + + if let Some(ref refl) = trace.reflection { + info!( + thread_id = %trace.thread_id, + docs = refl.docs.len(), + tokens = refl.tokens_used, + "=== Reflection ===" + ); + for doc in &refl.docs { + let preview: String = doc.content.chars().take(200).collect(); + let truncated = if doc.content.len() > 200 { "..." } else { "" }; + info!( + doc_type = %doc.doc_type, + title = %doc.title, + " {preview}{truncated}" + ); + } + } } // ── Retrospective analysis ────────────────────────────────── @@ -241,14 +292,14 @@ fn analyze_trace(thread: &Thread) -> Vec { .messages .iter() .any(|m| m.role == crate::types::message::MessageRole::ActionResult); - let has_tool_result_system_msg = thread + // Check if tool outputs are visible in the message history (any role). + // The engine adds tool results as system messages with "[tool_name result]" + // or "[tool_name error]" prefixes. + let has_tool_output_in_messages = thread .messages .iter() - .any(|m| { - m.role == crate::types::message::MessageRole::System - && m.content.contains("[") && m.content.contains("result]") - }); - if has_tool_results && !has_tool_result_system_msg { + .any(|m| m.content.contains(" result]") || m.content.contains(" error]")); + if has_tool_results && !has_tool_output_in_messages { issues.push(TraceIssue { severity: IssueSeverity::Warning, category: "missing_tool_output".into(),