Module: Legion::CLI::Chat::Subagent

Defined in:
lib/legion/cli/chat/subagent.rb

Constant Summary collapse

MAX_CONCURRENCY =
3
TIMEOUT =

5 minutes

300

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.max_concurrencyObject

Returns the value of attribute max_concurrency.



19
20
21
# File 'lib/legion/cli/chat/subagent.rb', line 19

def max_concurrency
  @max_concurrency
end

.timeoutObject

Returns the value of attribute timeout.



19
20
21
# File 'lib/legion/cli/chat/subagent.rb', line 19

def timeout
  @timeout
end

Class Method Details

.at_capacity?Boolean

Returns:

  • (Boolean)


73
74
75
# File 'lib/legion/cli/chat/subagent.rb', line 73

def at_capacity?
  @mutex.synchronize { @running.length >= @max_concurrency }
end

.configure(max_concurrency: MAX_CONCURRENCY, timeout: TIMEOUT) ⇒ Object



21
22
23
24
25
# File 'lib/legion/cli/chat/subagent.rb', line 21

def configure(max_concurrency: MAX_CONCURRENCY, timeout: TIMEOUT)
  @max_concurrency = max_concurrency
  @timeout = timeout
  @running = []
end

.configure_from_settingsObject



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/legion/cli/chat/subagent.rb', line 27

def configure_from_settings
  mc = begin
    Legion::Settings.dig(:chat, :subagent, :max_concurrency)
  rescue StandardError => e
    Legion::Logging.warn("Subagent#configure_from_settings max_concurrency read failed: #{e.message}") if defined?(Legion::Logging)
    nil
  end
  to = begin
    Legion::Settings.dig(:chat, :subagent, :timeout)
  rescue StandardError => e
    Legion::Logging.warn("Subagent#configure_from_settings timeout read failed: #{e.message}") if defined?(Legion::Logging)
    nil
  end
  @max_concurrency = mc || MAX_CONCURRENCY
  @timeout = to || TIMEOUT
end

.runningObject



65
66
67
# File 'lib/legion/cli/chat/subagent.rb', line 65

def running
  @mutex.synchronize { @running.map { |a| { id: a[:id], task: a[:task], elapsed: Time.now - a[:started_at] } } }
end

.running_countObject



69
70
71
# File 'lib/legion/cli/chat/subagent.rb', line 69

def running_count
  @mutex.synchronize { @running.length }
end

.spawn(task:, model: nil, provider: nil, on_complete: nil) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/legion/cli/chat/subagent.rb', line 44

def spawn(task:, model: nil, provider: nil, on_complete: nil)
  return { error: "Max concurrency reached (#{@max_concurrency}). Wait for a subagent to finish." } if at_capacity?

  agent_id = "agent-#{Time.now.strftime('%H%M%S')}-#{rand(1000)}"

  thread = Thread.new do
    result = run_headless(task: task, model: model, provider: provider)
    @mutex.synchronize { @running.delete_if { |a| a[:id] == agent_id } }
    on_complete&.call(agent_id, result)
  rescue StandardError => e
    Legion::Logging.error("Subagent#spawn thread error for #{agent_id}: #{e.message}") if defined?(Legion::Logging)
    @mutex.synchronize { @running.delete_if { |a| a[:id] == agent_id } }
    on_complete&.call(agent_id, { error: e.message })
  end

  entry = { id: agent_id, task: task, thread: thread, started_at: Time.now }
  @mutex.synchronize { @running << entry }

  { id: agent_id, status: 'running', task: task }
end

.wait_all(timeout: @timeout || TIMEOUT) ⇒ Object



77
78
79
80
81
82
83
84
85
# File 'lib/legion/cli/chat/subagent.rb', line 77

def wait_all(timeout: @timeout || TIMEOUT)
  deadline = Time.now + timeout
  @running.each do |agent|
    remaining = deadline - Time.now
    break if remaining <= 0

    agent[:thread]&.join(remaining)
  end
end