Class: RubynCode::Autonomous::Daemon

Inherits:
Object
  • Object
show all
Defined in:
lib/rubyn_code/autonomous/daemon.rb

Overview

The GOLEM daemon — an always-on autonomous agent that cycles between working on tasks and polling for new work. The lifecycle is:

spawned  working  idle  shutting_down  stopped

Safety limits (max_runs, max_cost, idle_timeout) prevent runaway execution. Signal traps (SIGTERM, SIGINT) trigger graceful shutdown.

Unlike the REPL, the daemon runs a full Agent::Loop per task — meaning it can read files, write code, run specs, and use every tool available.

Constant Summary collapse

LIFECYCLE_STATES =

rubocop:disable Metrics/ClassLength – daemon lifecycle + retry + audit + cost

%i[spawned working idle shutting_down stopped].freeze
MAX_TASK_RETRIES =
3

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(agent_name:, role:, llm_client:, project_root:, task_manager:, mailbox:, max_runs: 100, max_cost: 10.0, poll_interval: 5, idle_timeout: 60, on_state_change: nil, on_task_complete: nil, on_task_error: nil, session_persistence: nil) ⇒ Daemon

Returns a new instance of Daemon.

Parameters:

  • agent_name (String)

    unique name for this daemon instance

  • role (String)

    the agent’s role / persona description

  • llm_client (LLM::Client)

    LLM API client

  • project_root (String)

    path to the project being worked on

  • task_manager (Tasks::Manager)

    task persistence layer

  • mailbox (Teams::Mailbox)

    message mailbox

  • max_runs (Integer) (defaults to: 100)

    maximum work cycles before auto-shutdown (default 100)

  • max_cost (Float) (defaults to: 10.0)

    maximum cumulative LLM cost in USD before auto-shutdown (default 10.0)

  • poll_interval (Numeric) (defaults to: 5)

    idle polling interval in seconds (default 5)

  • idle_timeout (Numeric) (defaults to: 60)

    seconds of idle before shutdown (default 60)

  • on_state_change (Proc, nil) (defaults to: nil)

    callback invoked with (old_state, new_state)

  • on_task_complete (Proc, nil) (defaults to: nil)

    callback invoked with (task, result_text)

  • on_task_error (Proc, nil) (defaults to: nil)

    callback invoked with (task, error)

  • session_persistence (Memory::SessionPersistence, nil) (defaults to: nil)

    optional audit trail persistence



37
38
39
40
41
42
43
44
45
46
47
# File 'lib/rubyn_code/autonomous/daemon.rb', line 37

def initialize( # rubocop:disable Metrics/ParameterLists
  agent_name:, role:, llm_client:, project_root:, task_manager:, mailbox:,
  max_runs: 100, max_cost: 10.0, poll_interval: 5, idle_timeout: 60,
  on_state_change: nil, on_task_complete: nil, on_task_error: nil,
  session_persistence: nil
)
  assign_core_attrs(agent_name:, role:, llm_client:, project_root:, task_manager:, mailbox:)
  assign_limits(max_runs:, max_cost:, poll_interval:, idle_timeout:)
  assign_callbacks_and_state(on_state_change, on_task_complete, on_task_error)
  @session_persistence = session_persistence
end

Instance Attribute Details

#agent_nameObject (readonly)

Returns the value of attribute agent_name.



21
22
23
# File 'lib/rubyn_code/autonomous/daemon.rb', line 21

def agent_name
  @agent_name
end

#roleObject (readonly)

Returns the value of attribute role.



21
22
23
# File 'lib/rubyn_code/autonomous/daemon.rb', line 21

def role
  @role
end

#runs_completedObject (readonly)

Returns the value of attribute runs_completed.



21
22
23
# File 'lib/rubyn_code/autonomous/daemon.rb', line 21

def runs_completed
  @runs_completed
end

#stateObject (readonly)

Returns the value of attribute state.



21
22
23
# File 'lib/rubyn_code/autonomous/daemon.rb', line 21

def state
  @state
end

#total_costObject (readonly)

Returns the value of attribute total_cost.



21
22
23
# File 'lib/rubyn_code/autonomous/daemon.rb', line 21

def total_cost
  @total_cost
end

Instance Method Details

#running?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'lib/rubyn_code/autonomous/daemon.rb', line 91

def running?
  %i[working idle].include?(@state)
end

#start!Symbol

Enters the work-idle-work cycle. Blocks the calling thread until the daemon shuts down (via safety limits, idle timeout, or #stop!).

Returns:

  • (Symbol)

    the final state (:stopped)



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
# File 'lib/rubyn_code/autonomous/daemon.rb', line 53

def start!
  install_signal_handlers!
  transition_to(:working)

  loop do
    break if @stop_requested
    break if safety_limit_reached?

    task = TaskClaimer.call(task_manager: @task_manager, agent_name: @agent_name)

    if task
      run_work_phase(task)
      @runs_completed += 1
    else
      result = run_idle_phase
      case result
      when :shutdown, :interrupted
        break
      when :resume
        transition_to(:working)
        next
      end
    end
  end

  shutdown!
end

#statusHash

Returns snapshot of daemon status.

Returns:

  • (Hash)

    snapshot of daemon status



96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/rubyn_code/autonomous/daemon.rb', line 96

def status
  {
    agent_name: @agent_name,
    role: @role,
    state: @state,
    runs_completed: @runs_completed,
    total_cost: @total_cost,
    max_runs: @max_runs,
    max_cost: @max_cost,
    stop_requested: @stop_requested
  }
end

#stop!void

This method returns an undefined value.

Requests a graceful shutdown. The daemon will finish its current work unit and then stop.



85
86
87
88
# File 'lib/rubyn_code/autonomous/daemon.rb', line 85

def stop!
  @stop_requested = true
  @idle_poller&.interrupt!
end