Class: Rubino::Config::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/config/configuration.rb

Overview

Central configuration object providing typed accessors for all config sections. Wraps the raw hash loaded by Config::Loader with convenient method access.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(raw: nil, home_path: nil) ⇒ Configuration

Returns a new instance of Configuration.



10
11
12
13
# File 'lib/rubino/config/configuration.rb', line 10

def initialize(raw: nil, home_path: nil)
  @home_path = home_path
  @raw = raw || load_from_file
end

Instance Attribute Details

#rawObject (readonly)

Returns the value of attribute raw.



8
9
10
# File 'lib/rubino/config/configuration.rb', line 8

def raw
  @raw
end

Instance Method Details

#agent_api_max_retriesObject



191
192
193
# File 'lib/rubino/config/configuration.rb', line 191

def agent_api_max_retries
  dig("agent", "api_max_retries")
end

#agent_budget_extension_prompt?Boolean

At the iteration cap, prompt to continue/summarize/abort (#399). Defaults to true; an explicit false forces the old always-summarize behaviour. Independent of TTY — the headless guarantee lives in @ui.select returning nil, not here.

Returns:

  • (Boolean)


176
177
178
179
# File 'lib/rubino/config/configuration.rb', line 176

def agent_budget_extension_prompt?
  v = dig("agent", "budget_extension_prompt")
  v.nil? ? Defaults.dig("agent", "budget_extension_prompt") : v == true
end

#agent_budget_extension_stepObject

The “+N” one budget extension grants. nil/blank ⇒ max_tool_iterations, so an extension doubles the per-turn runway (the Cline/Roo “reset the counter, keep context” amount). Coerced to a positive Integer; a bad value falls back to the iteration cap.



185
186
187
188
189
# File 'lib/rubino/config/configuration.rb', line 185

def agent_budget_extension_step
  raw = dig("agent", "budget_extension_step")
  n = Integer(raw, exception: false)
  n&.positive? ? n : agent_max_tool_iterations
end

#agent_disabled_toolsetsObject



195
196
197
# File 'lib/rubino/config/configuration.rb', line 195

def agent_disabled_toolsets
  dig("agent", "disabled_toolsets") || []
end

#agent_max_tool_iterationsObject

Iteration/time caps fall back to the built-in defaults when the config value is nil/missing (e.g. ‘config set agent.max_tool_iterations nil`, whose writer coerces “nil” -> nil). A bare nil here would crash every turn in IterationBudget’s numeric comparisons (#139).



164
165
166
# File 'lib/rubino/config/configuration.rb', line 164

def agent_max_tool_iterations
  dig("agent", "max_tool_iterations") || Defaults.dig("agent", "max_tool_iterations")
end

#agent_max_turn_secondsObject



168
169
170
# File 'lib/rubino/config/configuration.rb', line 168

def agent_max_turn_seconds
  dig("agent", "max_turn_seconds") || Defaults.dig("agent", "max_turn_seconds")
end

#agent_max_turnsObject

– Agent section –



156
157
158
# File 'lib/rubino/config/configuration.rb', line 156

def agent_max_turns
  dig("agent", "max_turns")
end

#approvals_modeObject

– Security section –



383
384
385
# File 'lib/rubino/config/configuration.rb', line 383

def approvals_mode
  dig("approvals", "mode")
end

#approvals_readonly_commandsObject

Extra command names / leading-token prefixes merged into the built-in read-only set (Security::ReadonlyCommands::SAFE_COMMANDS).



408
409
410
# File 'lib/rubino/config/configuration.rb', line 408

def approvals_readonly_commands
  dig("approvals", "readonly_commands") || []
end

#approvals_wait_timeoutObject

Seconds a run blocks on a human approval/clarification before the gate gives up and AUTO-DENIES (freeing the worker thread). nil = wait indefinitely (interruptible only by an explicit stop). Used by ApprovalGate as its default await deadline so an abandoned approval never parks a server worker for the whole window (W1).



392
393
394
395
396
397
# File 'lib/rubino/config/configuration.rb', line 392

def approvals_wait_timeout
  raw = dig("approvals", "wait_timeout_seconds")
  return nil if raw.nil?

  raw.to_f
end

#auto_allow_readonly?Boolean

Auto-allow provably read-only shell commands (ls, cat, grep, git log, …) without an approval prompt. Default ON (key absent = on); the hardline floor and permissions:deny still precede it.

Returns:

  • (Boolean)


402
403
404
# File 'lib/rubino/config/configuration.rb', line 402

def auto_allow_readonly?
  dig("approvals", "auto_allow_readonly") != false
end

#auxiliary_compression_configObject

– Auxiliary section –



450
451
452
# File 'lib/rubino/config/configuration.rb', line 450

def auxiliary_compression_config
  dig("auxiliary", "compression") || {}
end

#auxiliary_config(task) ⇒ Object

Generic accessor for auxiliary task config blocks. Returns {} when the task isn’t defined, so callers can chain .dig safely.



460
461
462
# File 'lib/rubino/config/configuration.rb', line 460

def auxiliary_config(task)
  dig("auxiliary", task.to_s) || {}
end

#auxiliary_vision_configObject



454
455
456
# File 'lib/rubino/config/configuration.rb', line 454

def auxiliary_vision_config
  dig("auxiliary", "vision") || {}
end

#compression_enabled?Boolean

– Compression section –

Returns:

  • (Boolean)


281
282
283
# File 'lib/rubino/config/configuration.rb', line 281

def compression_enabled?
  dig("compression", "enabled") == true
end

#compression_max_summary_tokensObject



301
302
303
# File 'lib/rubino/config/configuration.rb', line 301

def compression_max_summary_tokens
  dig("compression", "max_summary_tokens")
end

#compression_preserve_tool_pairs?Boolean

Returns:

  • (Boolean)


305
306
307
# File 'lib/rubino/config/configuration.rb', line 305

def compression_preserve_tool_pairs?
  dig("compression", "preserve_tool_pairs") == true
end

#compression_protect_first_nObject



293
294
295
# File 'lib/rubino/config/configuration.rb', line 293

def compression_protect_first_n
  dig("compression", "protect_first_n")
end

#compression_protect_last_nObject



297
298
299
# File 'lib/rubino/config/configuration.rb', line 297

def compression_protect_last_n
  dig("compression", "protect_last_n")
end

#compression_target_ratioObject



289
290
291
# File 'lib/rubino/config/configuration.rb', line 289

def compression_target_ratio
  dig("compression", "target_ratio")
end

#compression_thresholdObject



285
286
287
# File 'lib/rubino/config/configuration.rb', line 285

def compression_threshold
  dig("compression", "threshold")
end

#confirm_policyObject

Effective shell prompt policy and SOLE source of truth (item 7): the legacy security.require_confirmation_for_shell alias was REMOVED — no back-compat mapping. :dangerous_only (DEFAULT — safe shell commands run unprompted; only DangerousPatterns matches prompt) or :confirm_all (every not-otherwise-allowed shell command prompts). An unset or unrecognized value falls back to the seeded :dangerous_only default. A config that still carries the removed key is NOT silently honored — Validator.warnings flags it at load + in ‘rubino doctor`.



420
421
422
423
424
425
# File 'lib/rubino/config/configuration.rb', line 420

def confirm_policy
  raw = dig("security", "confirm_policy")
  return raw.to_sym if %w[confirm_all dangerous_only].include?(raw.to_s)

  :dangerous_only
end

#database_pathObject

– Database section – Resolves the sqlite path. The DEFAULT (sentinel) follows the resolved home so RUBINO_HOME relocates the DB alongside config/.env/skills, avoiding the split brain where config went to the isolated home but the DB to the real ~/.rubino (issue #96). An EXPLICIT database.path in config.yml wins and is expanded verbatim.



38
39
40
41
42
43
44
45
# File 'lib/rubino/config/configuration.rb', line 38

def database_path
  path = dig("database", "path")
  if path == Defaults::DEFAULT_DATABASE_PATH
    File.join(resolved_home, "rubino.sqlite3")
  else
    File.expand_path(path)
  end
end

#dig(*keys) ⇒ Object

– Generic access –



476
477
478
# File 'lib/rubino/config/configuration.rb', line 476

def dig(*keys)
  @raw.dig(*keys)
end

#display_input_max_rowsObject

Cap on the chat input’s visual rows (display.input_max_rows). Falls back to the composer default for nil/zero/garbage so a bad value can never collapse or unbound the input block.



84
85
86
87
# File 'lib/rubino/config/configuration.rb', line 84

def display_input_max_rows
  value = dig("display", "input_max_rows").to_i
  value.positive? ? value : UI::BottomComposer::MAX_INPUT_ROWS
end

#display_statusbar?Boolean

The status bar under the chat input (display.statusbar, default true). Only an explicit false disables it.

Returns:

  • (Boolean)


68
69
70
# File 'lib/rubino/config/configuration.rb', line 68

def display_statusbar?
  dig("display", "statusbar") != false
end

#display_streaming?Boolean

– Display section –

Returns:

  • (Boolean)


62
63
64
# File 'lib/rubino/config/configuration.rb', line 62

def display_streaming?
  dig("display", "streaming") == true
end

#display_tool_output_preview_linesObject

Transcript preview budget for tool output (display.tool_output_preview_lines): head lines shown before the “… +N lines (full output → context)” marker. 0 = no collapse (full dump). Display-only — the model-facing output is untouched.



76
77
78
79
# File 'lib/rubino/config/configuration.rb', line 76

def display_tool_output_preview_lines
  value = dig("display", "tool_output_preview_lines")
  value.nil? ? 3 : value.to_i
end

#doom_loop_hard_stop?Boolean

– Doom-loop guard (#414) – Default WARN-not-block (hard_stop false): a tripped detector surfaces a warning to the model but does not deny the call.

Returns:

  • (Boolean)


144
145
146
# File 'lib/rubino/config/configuration.rb', line 144

def doom_loop_hard_stop?
  dig("doom_loop", "hard_stop") == true
end

#doom_loop_thresholdObject

Identical-consecutive-call threshold. Falls back to the detector default when absent/garbage so a bad config value can’t disable the guard.



150
151
152
153
# File 'lib/rubino/config/configuration.rb', line 150

def doom_loop_threshold
  n = Integer(dig("doom_loop", "threshold"), exception: false)
  n && n >= 2 ? n : Security::DoomLoopDetector::DEFAULT_THRESHOLD
end

#jobs_max_attemptsObject



365
366
367
# File 'lib/rubino/config/configuration.rb', line 365

def jobs_max_attempts
  dig("jobs", "max_attempts")
end

#jobs_modeObject

– Jobs section –



357
358
359
# File 'lib/rubino/config/configuration.rb', line 357

def jobs_mode
  dig("jobs", "mode")
end

#jobs_poll_intervalObject



361
362
363
# File 'lib/rubino/config/configuration.rb', line 361

def jobs_poll_interval
  dig("jobs", "poll_interval")
end

#memory_auto_extract?Boolean

Returns:

  • (Boolean)


314
315
316
# File 'lib/rubino/config/configuration.rb', line 314

def memory_auto_extract?
  dig("memory", "auto_extract") == true
end

#memory_auto_extract_intervalObject

Throttle interval (in turns) for memory.auto_extract (#412). Returns a positive Integer; nil/<=1 (or absent) ⇒ 1 = every turn. The lifecycle only enqueues ExtractMemoryJob when turns-since-last >= this.



321
322
323
# File 'lib/rubino/config/configuration.rb', line 321

def memory_auto_extract_interval
  positive_interval(dig("memory", "auto_extract_interval"))
end

#memory_char_limitObject



325
326
327
# File 'lib/rubino/config/configuration.rb', line 325

def memory_char_limit
  dig("memory", "memory_char_limit")
end

#memory_enabled?Boolean

– Memory section –

Returns:

  • (Boolean)


310
311
312
# File 'lib/rubino/config/configuration.rb', line 310

def memory_enabled?
  dig("memory", "enabled") == true
end

#memory_ingest_char_limitObject

Ingest/store budget for the live memory set, decoupled from the injection budget (‘memory_char_limit`). `nil` => unbounded ingest.



352
353
354
# File 'lib/rubino/config/configuration.rb', line 352

def memory_ingest_char_limit
  dig("memory", "ingest_char_limit")
end

#memory_user_char_limitObject



346
347
348
# File 'lib/rubino/config/configuration.rb', line 346

def memory_user_char_limit
  dig("memory", "user_char_limit")
end

#model_context_lengthObject



24
25
26
# File 'lib/rubino/config/configuration.rb', line 24

def model_context_length
  dig("model", "context_length")
end

#model_defaultObject

– Model section –



16
17
18
# File 'lib/rubino/config/configuration.rb', line 16

def model_default
  dig("model", "default")
end

#model_providerObject



20
21
22
# File 'lib/rubino/config/configuration.rb', line 20

def model_provider
  dig("model", "provider")
end

#model_supports_vision?Boolean

Returns true when the primary model can ingest images directly. Honours an explicit ‘model.supports_vision` override; otherwise falls back to ContentBuilder’s name-pattern heuristic. Used by VisionTool to decide whether to expose itself (no point delegating if the primary can see).

Returns:

  • (Boolean)


468
469
470
471
472
473
# File 'lib/rubino/config/configuration.rb', line 468

def model_supports_vision?
  raw = dig("model", "supports_vision")
  return raw == true unless raw.nil?

  LLM::ContentBuilder.supports_vision?(model_default.to_s)
end

#model_temperatureObject



28
29
30
# File 'lib/rubino/config/configuration.rb', line 28

def model_temperature
  dig("model", "temperature")
end

#notifications_bell?Boolean

Returns:

  • (Boolean)


122
123
124
# File 'lib/rubino/config/configuration.rb', line 122

def notifications_bell?
  dig("notifications", "bell") != false
end

#notifications_commandObject



126
127
128
129
# File 'lib/rubino/config/configuration.rb', line 126

def notifications_command
  value = dig("notifications", "command").to_s
  value.empty? ? nil : value
end

#notifications_enabled?Boolean

– Notifications section (UI::Notifier: attention bell + hook) – enabled/bell are on unless explicitly false; command is nil unless a non-empty string is set; min_turn_seconds falls back to the default.

Returns:

  • (Boolean)


118
119
120
# File 'lib/rubino/config/configuration.rb', line 118

def notifications_enabled?
  dig("notifications", "enabled") != false
end

#notifications_min_turn_secondsObject



131
132
133
134
# File 'lib/rubino/config/configuration.rb', line 131

def notifications_min_turn_seconds
  value = dig("notifications", "min_turn_seconds")
  (value.nil? ? Defaults.dig("notifications", "min_turn_seconds") : value).to_f
end

#paste_collapse_charsObject

A paste with MORE than this many CHARACTERS also collapses to a placeholder, even on a single line — a big one-line paste (a long token, URL, minified JSON) would otherwise flood the composer because the line-count trigger never fired. Falls back for nil/zero/garbage.



102
103
104
105
# File 'lib/rubino/config/configuration.rb', line 102

def paste_collapse_chars
  value = dig("paste", "collapse_chars").to_i
  value.positive? ? value : UI::PasteStore::DEFAULT_COLLAPSE_CHARS
end

#paste_collapse_linesObject

– Paste section (UI::PasteStore: the file-backed paste pipeline) – A paste with MORE than this many lines collapses to a “[Pasted text #N +M lines]” placeholder in the composer (expanded to the full body at send). Falls back for nil/zero/garbage.



93
94
95
96
# File 'lib/rubino/config/configuration.rb', line 93

def paste_collapse_lines
  value = dig("paste", "collapse_lines").to_i
  value.positive? ? value : UI::PasteStore::DEFAULT_COLLAPSE_LINES
end

#paste_file_threshold_tokensObject

A paste estimated above this many tokens (chars/4, the same rule compaction uses) overflows to <home>/sessions/<id>/paste_N.txt and the message carries a read-tool pointer instead of the content.



110
111
112
113
# File 'lib/rubino/config/configuration.rb', line 110

def paste_file_threshold_tokens
  value = dig("paste", "file_threshold_tokens").to_i
  value.positive? ? value : UI::PasteStore::DEFAULT_THRESHOLD_TOKENS
end

#paths_homeObject

– Paths section –



48
49
50
# File 'lib/rubino/config/configuration.rb', line 48

def paths_home
  dig("paths", "home")
end

#prompts_environment_enabled?Boolean

Returns:

  • (Boolean)


248
249
250
251
252
253
# File 'lib/rubino/config/configuration.rb', line 248

def prompts_environment_enabled?
  # Default to on when the key is absent — env injection is the cheap
  # win we don't want a forgetful config.yml to disable accidentally.
  value = dig("prompts", "environment", "enabled")
  value.nil? || value == true
end

#prompts_environment_extra_utilitiesObject



255
256
257
# File 'lib/rubino/config/configuration.rb', line 255

def prompts_environment_extra_utilities
  Array(dig("prompts", "environment", "extra_utilities")).map(&:to_s)
end

#prompts_override_for(role) ⇒ Object

Returns the override string for a given role name, or nil if the built-in default prompt should be used.



261
262
263
264
265
266
267
# File 'lib/rubino/config/configuration.rb', line 261

def prompts_override_for(role)
  value = dig("prompts", "overrides", role.to_s)
  return nil if value.nil?

  text = value.to_s.strip
  text.empty? ? nil : text
end

#prompts_preambleObject

– Prompts section – The customer-facing preamble prepended to every assembled system prompt. nil/empty disables the layer.



240
241
242
243
244
245
246
# File 'lib/rubino/config/configuration.rb', line 240

def prompts_preamble
  value = dig("prompts", "preamble")
  return nil if value.nil?

  text = value.to_s.strip
  text.empty? ? nil : text
end

#provider_config(name) ⇒ Object

– Providers section –



445
446
447
# File 'lib/rubino/config/configuration.rb', line 445

def provider_config(name)
  dig("providers", name.to_s) || {}
end

#reload!Object



489
490
491
# File 'lib/rubino/config/configuration.rb', line 489

def reload!
  @raw = load_from_file
end

#run_idle_event_timeoutObject

– Run lifecycle section – Returns Float seconds (or nil to disable). EventsOperation uses this to bound how long a “running” row can go without producing a new event before the watchdog promotes it to failed.



273
274
275
276
277
278
# File 'lib/rubino/config/configuration.rb', line 273

def run_idle_event_timeout
  raw = dig("run", "idle_event_timeout")
  return nil if raw.nil?

  raw.to_f
end

#security_command_allowlistObject

The pre-approved command allowlist, always returned as an Array.

YAML lets a user write ‘command_allowlist: git status` (a scalar) where a sequence was meant. The matcher (CommandAllowlist#allowlist_token_lists) calls #filter_map on this value; a bare String would raise an unhandled NoMethodError out of the approval path (a crash, not the clean fail-closed contract — CFG-R3-1). Coerce a scalar to a single-entry array and drop any nil so the matcher always receives a well-formed list.



435
436
437
438
439
440
441
442
# File 'lib/rubino/config/configuration.rb', line 435

def security_command_allowlist
  raw = dig("security", "command_allowlist")
  case raw
  when Array then raw
  when nil then []
  else [raw]
  end
end

#set(*keys, value) ⇒ Object



480
481
482
483
484
485
486
487
# File 'lib/rubino/config/configuration.rb', line 480

def set(*keys, value)
  hash = @raw
  keys[0..-2].each do |key|
    hash[key] ||= {}
    hash = hash[key]
  end
  hash[keys.last] = value
end

#skills_auto_distill?Boolean

Post-turn skill distillation. Defaults to true (skills feature on + distill key absent ⇒ distill on), mirroring memory_auto_extract? as the gate for an aux-spending background job. Turning skills off disables it too, since there is no point distilling skills that won’t be loaded.

Returns:

  • (Boolean)


333
334
335
336
337
338
# File 'lib/rubino/config/configuration.rb', line 333

def skills_auto_distill?
  return false unless dig("skills", "enabled") != false

  value = dig("skills", "auto_distill")
  value.nil? || value == true
end

#skills_auto_distill_intervalObject

Throttle interval (in turns) for skills.auto_distill (#414). Mirrors memory_auto_extract_interval. nil/<=1 ⇒ every eligible turn.



342
343
344
# File 'lib/rubino/config/configuration.rb', line 342

def skills_auto_distill_interval
  positive_interval(dig("skills", "auto_distill_interval"))
end

#streaming_enabled?Boolean

– Streaming section –

Returns:

  • (Boolean)


137
138
139
# File 'lib/rubino/config/configuration.rb', line 137

def streaming_enabled?
  dig("streaming", "enabled") == true
end

#tasks_ask_parent_timeoutObject

Bound (seconds) a BLOCKING ask_parent waits for an answer before the child self-heals and proceeds with its best judgement (S5a). Reuses the approval-gate timeout convention — a sane upper bound, never “forever” —so an abandoned ask never parks the child’s thread indefinitely. Default 900.



233
234
235
# File 'lib/rubino/config/configuration.rb', line 233

def tasks_ask_parent_timeout
  dig("tasks", "ask_parent_timeout") || Defaults.dig("tasks", "ask_parent_timeout")
end

#tasks_max_children_per_nodeObject

Maximum number of LIVE direct children a single node (the human/top-level or one subagent) may have at once. Default 3.



211
212
213
# File 'lib/rubino/config/configuration.rb', line 211

def tasks_max_children_per_node
  dig("tasks", "max_children_per_node") || Defaults.dig("tasks", "max_children_per_node")
end

#tasks_max_concurrent_totalObject

Hard global ceiling on the total number of LIVE subagents across the whole tree, so depth × fan-out cannot blow past the process’s thread/cost budget. Default 8.



218
219
220
# File 'lib/rubino/config/configuration.rb', line 218

def tasks_max_concurrent_total
  dig("tasks", "max_concurrent_total") || Defaults.dig("tasks", "max_concurrent_total")
end

#tasks_max_depthObject

– Tasks / nested-subagent caps – Maximum nesting depth for the ‘task` delegation tree. depth 0 is a human/top-level-spawned child; the cap bounds how deep a chain of subagents-spawning-subagents may go. Default 2 ⇒ human→child→grandchild. Falls back to the built-in default when missing/nil so the numeric caps in BackgroundTask#reserve never crash on a bare nil.



205
206
207
# File 'lib/rubino/config/configuration.rb', line 205

def tasks_max_depth
  dig("tasks", "max_depth") || Defaults.dig("tasks", "max_depth")
end

#tasks_max_live_probes_per_childObject

Per-child budget for BILLED live probes (‘probe(live:true)`). Over budget, the model is steered to the FREE live:false snapshot. Free snapshots are unlimited. Default 5.



225
226
227
# File 'lib/rubino/config/configuration.rb', line 225

def tasks_max_live_probes_per_child
  dig("tasks", "max_live_probes_per_child") || Defaults.dig("tasks", "max_live_probes_per_child")
end

#tool_enabled?(name) ⇒ Boolean

– Tools section –

Returns:

  • (Boolean)


370
371
372
# File 'lib/rubino/config/configuration.rb', line 370

def tool_enabled?(name)
  dig("tools", name.to_s) == true
end

#tool_output_max_bytesObject



374
375
376
# File 'lib/rubino/config/configuration.rb', line 374

def tool_output_max_bytes
  dig("tool_output", "max_bytes")
end

#tool_output_max_linesObject



378
379
380
# File 'lib/rubino/config/configuration.rb', line 378

def tool_output_max_lines
  dig("tool_output", "max_lines")
end

#ui_adapterObject

– UI section –



53
54
55
# File 'lib/rubino/config/configuration.rb', line 53

def ui_adapter
  dig("ui", "adapter")
end

#ui_verbose?Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/rubino/config/configuration.rb', line 57

def ui_verbose?
  dig("ui", "verbose") == true
end