Class: Silas::Tool

Inherits:
Object
  • Object
show all
Defined in:
lib/silas/tool.rb

Overview

Base class for agent tools. The keyword signature of #call IS the schema:

class Agent::Tools::IssueRefund < Silas::Tool
description "Refund an order."
param :amount, :integer, desc: "Pence"   # optional type refinement
approval :always                          # :never | :once | :always | lambda
transactional!                            # or at_most_once! (default) / idempotent!

:once approves ONE (tool, arguments) pair per session — an identical repeat call skips re-approval; different arguments park again. For graded gates (e.g. auto-approve under a threshold) use a lambda:

approval ->(session:, input:) { input[:amount] > 5000 ? :user_approval : :approved }

def call(order_id:, amount:, note: nil)
  ...
end
end

Tool identity is the FILENAME (app/agent/tools/issue_refund.rb -> "issue_refund"), eve's convention, enforced by the Registry.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#sessionObject

Execution context, set by the Ledger before #call.



92
93
94
# File 'lib/silas/tool.rb', line 92

def session
  @session
end

Class Method Details

.approval(policy = nil, &block) ⇒ Object Also known as: approval_policy



33
34
35
36
# File 'lib/silas/tool.rb', line 33

def approval(policy = nil, &block)
  @approval_policy = block || policy unless policy.nil? && block.nil?
  @approval_policy || :never
end

.at_most_once!Object



41
# File 'lib/silas/tool.rb', line 41

def at_most_once!  = @effect_mode = :at_most_once

.description(text = nil) ⇒ Object



24
25
26
27
# File 'lib/silas/tool.rb', line 24

def description(text = nil)
  @description = text if text
  @description || ""
end

.effect_modeObject



43
# File 'lib/silas/tool.rb', line 43

def effect_mode = @effect_mode || :at_most_once

.idempotent!Object



40
# File 'lib/silas/tool.rb', line 40

def idempotent!    = @effect_mode = :idempotent

.param(name, type = :string, desc: nil) ⇒ Object



29
30
31
# File 'lib/silas/tool.rb', line 29

def param(name, type = :string, desc: nil)
  param_refinements[name.to_sym] = { type: type.to_s, desc: desc }
end

.schemaObject

description:, input_schema: — provider-agnostic JSON schema.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/silas/tool.rb', line 50

def schema
  validate_signature!
  properties = {}
  required = []
  instance_method(:call).parameters.each do |kind, pname|
    refinement = param_refinements[pname] || {}
    properties[pname.to_s] = { "type" => refinement[:type] || "string" }
    properties[pname.to_s]["description"] = refinement[:desc] if refinement[:desc]
    required << pname.to_s if kind == :keyreq
  end
  {
    "name" => tool_name,
    "description" => description,
    "input_schema" => {
      "type" => "object",
      "properties" => properties,
      "required" => required
    }
  }
end

.tool_nameObject



45
46
47
# File 'lib/silas/tool.rb', line 45

def tool_name
  name.demodulize.underscore
end

.transactional!Object



39
# File 'lib/silas/tool.rb', line 39

def transactional! = @effect_mode = :transactional

.validate_signature!Object

Raises:



71
72
73
74
75
76
77
78
# File 'lib/silas/tool.rb', line 71

def validate_signature!
  raise Error, "#{name} must define #call" unless method_defined?(:call)

  kinds = instance_method(:call).parameters.map(&:first)
  if kinds.any? { |k| %i[req opt rest].include?(k) }
    raise Error, "#{name}#call must accept keyword arguments only — the signature is the schema"
  end
end

Instance Method Details

#approval_policyObject

Instance-side delegation so a resolved instance answers the Ledger.



88
# File 'lib/silas/tool.rb', line 88

def approval_policy = self.class.approval_policy

#callObject

Raises:

  • (NotImplementedError)


94
95
96
# File 'lib/silas/tool.rb', line 94

def call(**)
  raise NotImplementedError, "#{self.class}#call"
end

#effect_modeObject



89
# File 'lib/silas/tool.rb', line 89

def effect_mode     = self.class.effect_mode