Bug Description
Core::Base#execute overrides BaseAgent#execute but does not set Thread.current[:ruby_llm_agents_caller_context], so tools never receive the execution context. This means context is always nil inside any RubyLLM::Agents::Tool subclass, and agent params (like workspace_path) are inaccessible.
Versions
ruby_llm-agents 3.11.0 (also present in 3.10.0)
ruby_llm 1.13.2
- Ruby 4.0.1
Root Cause
BaseAgent#execute (base_agent.rb:810) correctly wraps the LLM call:
def execute(context)
previous_context = Thread.current[:ruby_llm_agents_caller_context]
Thread.current[:ruby_llm_agents_caller_context] = context
# ... build_client, execute_llm_call ...
ensure
Thread.current[:ruby_llm_agents_caller_context] = previous_context
end
Core::Base#execute (core/base.rb:74) overrides this without calling super and without setting the thread-local:
def execute(context)
@execution_started_at = context.started_at || Time.current
run_callbacks(:before, context)
client = build_client(context)
response = execute_llm_call(client, context)
# ... no Thread.current assignment anywhere ...
end
Since RubyLLM::Agents::Base includes Core::Base, method resolution picks Core::Base#execute over BaseAgent#execute. The thread-local is never set.
Impact
In RubyLLM::Agents::Tool#call (tool.rb:69):
pipeline_context = Thread.current[:ruby_llm_agents_caller_context]
@context = pipeline_context ? ToolContext.new(pipeline_context) : nil
pipeline_context is always nil, so:
context is nil in all tools
context.workspace_path (or any agent param) raises or returns nil
- Tool execution tracking (
start_tool_tracking) is skipped (no execution_id)
Reproduction
class MyTool < RubyLLM::Agents::Tool
description "Debug tool"
param :path, desc: "A path", required: true
def execute(path:)
puts "context: #{context.inspect}" # => nil
puts "context.workspace_path: #{context&.workspace_path}" # => nil
"done"
end
end
class MyAgent < RubyLLM::Agents::Base
param :workspace_path, required: true
param :query, required: true
tools [MyTool]
system "You are a helpful assistant."
user "{query}"
end
MyAgent.call(query: "Use MyTool with path 'test'", workspace_path: "/tmp/test")
# => MyTool#execute runs but context is nil
Workaround
Override execute in your ApplicationAgent to set the thread-local:
class ApplicationAgent < RubyLLM::Agents::Base
def execute(context)
previous = Thread.current[:ruby_llm_agents_caller_context]
Thread.current[:ruby_llm_agents_caller_context] = context
super
ensure
Thread.current[:ruby_llm_agents_caller_context] = previous
end
end
Suggested Fix
Add the thread-local context assignment to Core::Base#execute, or have it call super to inherit the wrapping from BaseAgent#execute.
Bug Description
Core::Base#executeoverridesBaseAgent#executebut does not setThread.current[:ruby_llm_agents_caller_context], so tools never receive the execution context. This meanscontextis alwaysnilinside anyRubyLLM::Agents::Toolsubclass, and agent params (likeworkspace_path) are inaccessible.Versions
ruby_llm-agents3.11.0 (also present in 3.10.0)ruby_llm1.13.2Root Cause
BaseAgent#execute(base_agent.rb:810) correctly wraps the LLM call:Core::Base#execute(core/base.rb:74) overrides this without callingsuperand without setting the thread-local:Since
RubyLLM::Agents::BaseincludesCore::Base, method resolution picksCore::Base#executeoverBaseAgent#execute. The thread-local is never set.Impact
In
RubyLLM::Agents::Tool#call(tool.rb:69):pipeline_contextis alwaysnil, so:contextisnilin all toolscontext.workspace_path(or any agent param) raises or returns nilstart_tool_tracking) is skipped (noexecution_id)Reproduction
Workaround
Override
executein yourApplicationAgentto set the thread-local:Suggested Fix
Add the thread-local context assignment to
Core::Base#execute, or have it callsuperto inherit the wrapping fromBaseAgent#execute.