Class: Agentd::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/agentd/runner.rb

Overview

Runs an agentic loop: Ollama (native /api/chat) + agentd.link MCP tools.

Usage:

runner = Agentd::Runner.new(
  api_key:  "your-relay-api-key",
  model:    "gemma3:1b",            # any ollama model with tool support
  endpoint: "http://localhost:3000", # agentd
  ollama:   "http://localhost:11434" # ollama
)
runner.run("Summarise agentd.link and publish it as a note.")

Constant Summary collapse

MAX_ITERATIONS =
20

Instance Method Summary collapse

Constructor Details

#initialize(api_key:, model: "gemma3:1b", endpoint: Agentd.endpoint, ollama: "http://localhost:11434", system_prompt: nil, verbose: false) ⇒ Runner

Returns a new instance of Runner.



19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/agentd/runner.rb', line 19

def initialize(api_key:, model: "gemma3:1b",
               endpoint: Agentd.endpoint,
               ollama: "http://localhost:11434",
               system_prompt: nil,
               verbose: false)
  @api_key       = api_key
  @model         = model
  @relay         = Client.new(api_key:, endpoint:)
  @ollama_url    = ollama.chomp("/")
  @system_prompt = system_prompt || default_system_prompt
  @verbose       = verbose
end

Instance Method Details

#run(task) ⇒ Object

Raises:



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/agentd/runner.rb', line 32

def run(task)
  tools    = fetch_tools
  messages = [
    { role: "system", content: @system_prompt },
    { role: "user",   content: task }
  ]

  debug "Starting task: #{task}"
  debug "Tools available: #{tools.map { |t| t.dig("function", "name") }.join(", ")}"

  MAX_ITERATIONS.times do |i|
    debug "\n--- Turn #{i + 1} ---"
    response = chat_stream(messages, tools)
    message  = response["message"]
    messages << { role: message["role"], content: message["content"] }

    tool_calls = message["tool_calls"]

    if tool_calls.nil? || tool_calls.empty?
      # Final response was already streamed to stdout — just add trailing newline
      $stdout.puts
      return message["content"].to_s.strip
    end

    tool_calls.each do |call|
      name = call.dig("function", "name")
      args = call.dig("function", "arguments") || {}
      args = JSON.parse(args) if args.is_a?(String)

      if @verbose
        $stderr.puts "#{name}(#{args.inspect})"
      else
        $stderr.print "#{name}... "
        $stderr.flush
      end

      result = execute_tool(name, args)

      if @verbose
        $stderr.puts "#{result.inspect}"
      else
        $stderr.puts ""
      end

      messages << { role: "tool", content: result.to_json }
    end
  end

  raise Error, "Exceeded #{MAX_ITERATIONS} iterations without a final response"
end