From 643f9890dff28a549223b1d5cabca454ba791032 Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Wed, 18 Mar 2026 23:07:49 -0400 Subject: [PATCH] api: fix sync wrapper to be safe from any calling context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run the async API call on a dedicated thread with its own tokio runtime so it works whether called from a sync context or from within an existing tokio runtime (daemon). Also drops the log closure capture issue — uses a simple eprintln fallback since the closure can't cross thread boundaries. Co-Authored-By: Claude Opus 4.6 (1M context) --- poc-memory/src/agents/api.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/poc-memory/src/agents/api.rs b/poc-memory/src/agents/api.rs index 73dab21..acdffe1 100644 --- a/poc-memory/src/agents/api.rs +++ b/poc-memory/src/agents/api.rs @@ -99,17 +99,23 @@ pub async fn call_api_with_tools( Err(format!("agent exceeded {} tool turns", max_turns)) } -/// Synchronous wrapper — creates a tokio runtime and blocks. -/// Used by the existing sync call path in knowledge.rs. +/// Synchronous wrapper — runs the async function on a dedicated thread +/// with its own tokio runtime. Safe to call from any context. pub fn call_api_with_tools_sync( agent: &str, prompt: &str, log: &dyn Fn(&str), ) -> Result { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(|e| format!("tokio runtime: {}", e))?; - - rt.block_on(call_api_with_tools(agent, prompt, log)) + // Run on a new thread to avoid conflicts with any existing runtime + let agent = agent.to_string(); + let prompt = prompt.to_string(); + std::thread::scope(|s| { + s.spawn(|| { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|e| format!("tokio runtime: {}", e))?; + rt.block_on(call_api_with_tools(&agent, &prompt, &|msg| eprintln!("[api] {}", msg))) + }).join().unwrap() + }) }