Module: Xeno::Standalone

Defined in:
lib/xeno/standalone.rb,
lib/xeno/standalone/local_secret.rb,
lib/xeno/standalone/model_refresh.rb

Class Method Summary collapse

Class Method Details

.boot!Object

Boot: initialize the app, then bring the database up to date — the engine's migrations plus Solid Queue's tables (loaded straight from the solid_queue gem's install template).



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/xeno/standalone.rb', line 73

def boot!
  ENV["RAILS_ENV"] ||= "development"
  FileUtils.mkdir_p(StandaloneApplication::APP_ROOT.join("storage"))

  app = Rails.application || StandaloneApplication.instance
  unless app.initialized?
    app.initialize!
    app.routes.draw do
      mount Xeno::Engine => "/agent"
      root to: redirect("/agent/dev")
    end
  end

  migrate!
  refresh_models!
  write_recurring_config!
  Rails.application
end

.local_secret(root) ⇒ Object

A persisted local secret so sessions survive restarts without asking anyone to manage SECRET_KEY_BASE for a dev-grade standalone app. Owner-only (0600) — it signs cookies/sessions (S6); pre-existing looser files are tightened on read.

Lives apart from xeno/standalone so tests can exercise it without defining the Rails::Application (which self-registers on inheritance).



15
16
17
18
19
20
21
22
23
24
# File 'lib/xeno/standalone/local_secret.rb', line 15

def local_secret(root)
  path = root.join("storage", ".local_secret")
  FileUtils.mkdir_p(path.dirname)
  if path.file?
    File.chmod(0o600, path)
    return path.read.strip
  end

  SecureRandom.hex(64).tap { |secret| File.write(path, secret, perm: 0o600) }
end

.migrate!Object



112
113
114
115
116
117
118
119
# File 'lib/xeno/standalone.rb', line 112

def migrate!
  ActiveRecord::MigrationContext.new([ Xeno::Engine.root.join("db", "migrate").to_s ]).migrate

  connection = ActiveRecord::Base.lease_connection
  unless connection.table_exists?("solid_queue_jobs")
    load queue_schema_path
  end
end

.puma_config_pathObject

The gem-owned puma config used by xeno server — Solid Queue runs inside Puma (SOLID_QUEUE_IN_PUMA), one process total.



128
129
130
# File 'lib/xeno/standalone.rb', line 128

def puma_config_path
  File.expand_path("standalone/puma.rb", __dir__)
end

.queue_schema_pathObject



121
122
123
124
# File 'lib/xeno/standalone.rb', line 121

def queue_schema_path
  spec = Gem::Specification.find_by_name("solid_queue")
  File.join(spec.gem_dir, "lib", "generators", "solid_queue", "install", "templates", "db", "queue_schema.rb")
end

.refresh_models!Object

First boot only (the registry table is empty): pull the live model catalog from the configured providers so current model ids resolve without a manual RubyLLM.models.refresh!. Loading the definition first runs agent.rb's RubyLLM.configure — the refresh needs those provider keys. Never fatal, never repeated once the table has rows; skippable with XENO_SKIP_MODEL_REFRESH=1 (deterministic scripts, offline boots).



11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/xeno/standalone/model_refresh.rb', line 11

def refresh_models!
  return if ENV["XENO_SKIP_MODEL_REFRESH"]

  Xeno.definition
  store = RubyLLM.config.model_registry_store
  return unless store.respond_to?(:none?) && store.none?

  Rails.logger.info "xeno: first boot — refreshing the model registry"
  RubyLLM.models.refresh!
  Rails.logger.info "xeno: model registry ready (#{store.count} models)"
rescue StandardError => error
  Rails.logger.warn "xeno: model registry refresh failed (#{error.class}: #{error.message}). " \
                    "Continuing; run RubyLLM.models.refresh! manually if a model fails to resolve."
end

.write_recurring_config!Object

Solid Queue's recurring machinery is how the reaper (always) and the compiled schedules (outside development) fire in standalone mode. The file lives under storage/ (gitignored) so the scaffold stays four visible files; the env var — read by Solid Queue's supervisor, which starts after config.ru has loaded — points the scheduler at it.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/xeno/standalone.rb', line 97

def write_recurring_config!
  section = { Xeno::Schedules::REAPER_KEY => Xeno::Schedules.reaper_entry }
  unless Rails.env.development?
    Xeno.definition.schedules.each do |name, schedule|
      section["#{Xeno::Schedules::MANAGED_PREFIX}#{name}"] = {
        "class" => "Xeno::ScheduleJob", "args" => [ name ], "schedule" => schedule.cron
      }
    end
  end

  path = StandaloneApplication::APP_ROOT.join("storage", "recurring.yml")
  File.write(path, YAML.dump({ Rails.env.to_s => section }))
  ENV["SOLID_QUEUE_RECURRING_SCHEDULE"] ||= File.join("storage", "recurring.yml")
end