Class: Xeno::Tool

Inherits:
RubyLLM::Tool
  • Object
show all
Defined in:
lib/xeno/tool.rb

Overview

Tools

A tool is something the agent can do besides talk: look up an order, charge a card, file a ticket. Each tool is a class under agent/tools/, and the model decides when to call it based on the description and parameters you declare:

# agent/tools/get_weather.rb
class Xeno::Tools::GetWeather < Xeno::Tool
description "Return current weather for a city."
parameter :city, description: "City name"

def execute(city:)
  { city: city, condition: "Sunny" }
end
end

The file name is the tool's name on the wire — get_weather.rb is callable as get_weather — and whatever execute returns becomes the tool result the model reads next.

Beyond a plain RubyLLM::Tool, a xeno tool runs inside a durable session: it can keep working state in #state, see the #session it runs in, and declare an ::approval policy so a human signs off before anything irreversible happens.

Direct Known Subclasses

AskQuestion

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#sessionObject

The session this call is running in — set by the runner before call. nil when the tool is exercised outside a session (unit tests calling Tool.new.call directly).



60
61
62
# File 'lib/xeno/tool.rb', line 60

def session
  @session
end

Class Method Details

.approval(policy = nil) ⇒ Object

The human-in-the-loop gate. Gate anything irreversible or externally visible.

approval :never    # default: runs without asking
approval :once     # asks the first time in a session, then remembered
approval :always   # asks on every call
approval ->(ctx) { ctx.principal&.dig("role") != "admin" }

The lambda receives an ApprovalContext (session, turn, tool_name, arguments, principal); truthy means approval is required.



45
46
47
48
49
50
# File 'lib/xeno/tool.rb', line 45

def approval(policy = nil)
  @approval = policy unless policy.nil?
  return @approval if defined?(@approval) && @approval

  superclass.respond_to?(:approval) ? superclass.approval : :never
end

.approval_declared?Boolean

:nodoc:

Returns:

  • (Boolean)


52
53
54
55
# File 'lib/xeno/tool.rb', line 52

def approval_declared? # :nodoc:
  (defined?(@approval) && !@approval.nil?) ||
    (superclass.respond_to?(:approval_declared?) && superclass.approval_declared?)
end

.tool_nameObject

The runtime name the model calls this tool by, taken from the path: agent/tools/get_weather.rb defines Xeno::Tools::GetWeather, and the demodulized class name matches the file basename. No suffix stripping: a charge_card_tool.rb must stay callable as charge_card_tool or the slug-keyed approval lookup misses.



32
33
34
# File 'lib/xeno/tool.rb', line 32

def tool_name
  name.demodulize.underscore
end

Instance Method Details

#stateObject

The session-scoped KV store: JSON-typed values that survive restarts, never cross sessions, and are cleared by reset. Working state, not long-term memory.

def execute(city:)
searches = state.get("searches").to_i + 1
state.update("searches" => searches, "last_city" => city)
...
end

Raises:



70
71
72
73
74
# File 'lib/xeno/tool.rb', line 70

def state
  raise Xeno::Error, "session state is only available while running inside a session" unless session

  session.state
end