From b1254cf6f93018d5eb8d53451ae06a3d105d5139 Mon Sep 17 00:00:00 2001 From: "ilblackdragon@gmail.com" Date: Sun, 22 Mar 2026 22:56:21 -0700 Subject: [PATCH] feat(engine): wire reflection pipeline + trace analysis into thread lifecycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After every thread completes, ThreadManager now automatically runs: 1. Retrospective trace analysis (non-LLM, always): - Detects 8 issue categories (tool errors, code errors, missing outputs, excessive steps, hallucination risk, etc.) - Logs issues at warn level when found 2. Trace file recording (when ENGINE_V2_TRACE=1): - Writes full JSON trace to engine_trace_{timestamp}.json 3. LLM reflection (when enable_reflection=true): - Calls reflection pipeline to produce Summary, Lesson, Issue docs - Saves docs to store for future context retrieval - Enabled by default in the bridge router All three run inside the spawned tokio task after exec.run() completes, before saving the final thread state. No external wiring needed. Removed duplicate trace recording from the router — it's now handled by ThreadManager automatically. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/ironclaw_engine/src/runtime/manager.rs | 42 ++++++++++++++++++- src/bridge/router.rs | 16 ++++--- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/crates/ironclaw_engine/src/runtime/manager.rs b/crates/ironclaw_engine/src/runtime/manager.rs index 5d11b634..07132716 100644 --- a/crates/ironclaw_engine/src/runtime/manager.rs +++ b/crates/ironclaw_engine/src/runtime/manager.rs @@ -156,11 +156,51 @@ impl ThreadManager { // Spawn background task let store_for_task = Arc::clone(&self.store); + let llm_for_reflection = Arc::clone(&self.llm); let handle = tokio::spawn(async move { let mut exec = exec_loop; let result = exec.run().await; debug!(thread_id = %thread_id, "thread execution finished"); - // Save final thread state to store (for trace recording) + + // Run retrospective trace analysis (non-LLM, always runs) + let trace = crate::executor::trace::build_trace(&exec.thread); + if !trace.issues.is_empty() { + crate::executor::trace::log_trace_summary(&trace); + } + + // Write trace file if enabled + if crate::executor::trace::is_trace_enabled() { + crate::executor::trace::write_trace(&trace); + } + + // Run LLM reflection if enabled and thread completed + if exec.thread.config.enable_reflection + && (exec.thread.state == crate::types::thread::ThreadState::Completed + || exec.thread.state == crate::types::thread::ThreadState::Done) + { + debug!(thread_id = %thread_id, "running reflection pipeline"); + match crate::reflection::reflect(&exec.thread, &llm_for_reflection).await { + Ok(reflection) => { + debug!( + thread_id = %thread_id, + docs = reflection.docs.len(), + tokens = reflection.tokens_used.total(), + "reflection complete" + ); + for doc in &reflection.docs { + let _ = store_for_task.save_memory_doc(doc).await; + } + } + Err(e) => { + tracing::warn!( + thread_id = %thread_id, + "reflection failed: {e}" + ); + } + } + } + + // Save final thread state to store let _ = store_for_task.save_thread(&exec.thread).await; result }); diff --git a/src/bridge/router.rs b/src/bridge/router.rs index 24c58114..5c7a4129 100644 --- a/src/bridge/router.rs +++ b/src/bridge/router.rs @@ -168,7 +168,10 @@ pub async fn handle_with_engine( content, state.default_project_id, &message.user_id, - ThreadConfig::default(), + ThreadConfig { + enable_reflection: true, + ..ThreadConfig::default() + }, ) .await .map_err(|e| { @@ -222,14 +225,9 @@ pub async fn handle_with_engine( .record_thread_outcome(conv_id, thread_id, &outcome) .await; - // Trace recording + retrospective analysis - if ironclaw_engine::executor::trace::is_trace_enabled() - && let Ok(Some(thread)) = state.store.load_thread(thread_id).await - { - let trace = ironclaw_engine::executor::trace::build_trace(&thread); - ironclaw_engine::executor::trace::log_trace_summary(&trace); - ironclaw_engine::executor::trace::write_trace(&trace); - } + // Note: trace recording, retrospective analysis, and LLM reflection + // all run automatically inside ThreadManager after the thread completes. + // See crates/ironclaw_engine/src/runtime/manager.rs. // Convert outcome to response match outcome {