From 212fe9817a09c2afd7581c006485e9aaf0df612a Mon Sep 17 00:00:00 2001 From: "ilblackdragon@gmail.com" Date: Thu, 26 Mar 2026 23:54:33 -0700 Subject: [PATCH] fix(engine): resolve tool names as callable stubs in CodeAct runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When LLM-generated code calls `mission_list()` or any tool function, Monty's Python execution model first resolves the name (`mission_list`) as a NameLookup before invoking it as a FunctionCall. The NameLookup handler always returned Undefined, causing NameError before the function call could dispatch to the effect executor. Fix: before starting the Monty VM, collect all known tool names from the effect executor's available_actions(). In the NameLookup handler, if the name matches a known tool, return a MontyObject::Function stub instead of Undefined. Monty then yields FunctionCall for the stub, which dispatches to the normal tool execution pipeline. This enables CodeAct code to call any registered tool as a Python function: mission_list(), mission_create(), routine_list(), web_search(), memory_search(), etc. — all without explicit imports or __execute_action__ boilerplate. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../ironclaw_engine/src/executor/scripting.rs | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/crates/ironclaw_engine/src/executor/scripting.rs b/crates/ironclaw_engine/src/executor/scripting.rs index 731517fd..72dccee1 100644 --- a/crates/ironclaw_engine/src/executor/scripting.rs +++ b/crates/ironclaw_engine/src/executor/scripting.rs @@ -253,6 +253,18 @@ pub async fn execute_code( // Build context variables including persisted state from prior steps let (input_names, input_values) = build_context_inputs(thread, persisted_state); + // Collect known tool names so NameLookup can return callable stubs. + // Without this, `mission_list()` in code raises NameError because Monty + // resolves the name before calling it, and Undefined → NameError. + let active_leases = leases.active_for_thread(thread.id).await; + let known_actions: std::collections::HashSet = effects + .available_actions(&active_leases) + .await + .unwrap_or_default() + .into_iter() + .map(|a| a.name) + .collect(); + // Parse and compile (wrap in catch_unwind — Monty 0.0.x can panic) let runner = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { MontyRun::new(code.to_string(), "step.py", input_names) @@ -458,12 +470,23 @@ pub async fn execute_code( RunProgress::NameLookup(lookup) => { let name = lookup.name.clone(); - debug!(name = %name, "Monty: unresolved name"); + + // If the name matches a known tool, return a callable Function + // stub so Monty yields FunctionCall (dispatched to the effect + // executor) instead of raising NameError. + let result = if known_actions.contains(&name) { + debug!(name = %name, "Monty: resolved as tool function"); + NameLookupResult::Value(MontyObject::Function { + name: name.clone(), + docstring: None, + }) + } else { + debug!(name = %name, "Monty: unresolved name"); + NameLookupResult::Undefined + }; + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - lookup.resume( - NameLookupResult::Undefined, - PrintWriter::Collect(&mut stdout), - ) + lookup.resume(result, PrintWriter::Collect(&mut stdout)) })) { Ok(Ok(p)) => progress = p, Ok(Err(e)) => { @@ -566,6 +589,12 @@ async fn handle_llm_query( messages.push(ThreadMessage::system(format!( "You are a sub-agent. Answer concisely based on the context.\n\n{ctx}" ))); + } else { + // Some providers (e.g. OpenAI Codex Responses API) require a system + // message / instructions field. Always include one. + messages.push(ThreadMessage::system( + "You are a helpful sub-agent. Answer concisely.", + )); } messages.push(ThreadMessage::user(prompt)); @@ -654,6 +683,10 @@ async fn handle_llm_query_batched( messages.push(ThreadMessage::system(format!( "You are a sub-agent. Answer concisely.\n\n{ctx}" ))); + } else { + messages.push(ThreadMessage::system( + "You are a helpful sub-agent. Answer concisely.", + )); } messages.push(ThreadMessage::user(prompt)); llm.complete(&messages, &[], &config).await