Class: Silas::Configuration
- Inherits:
-
Object
- Object
- Silas::Configuration
- Defined in:
- lib/silas/configuration.rb
Instance Attribute Summary collapse
-
#agent_override ⇒ Object
Subagents: the roster (name+description) and per-name scope builders, plus the active-agent overrides swapped in during a nested run.
-
#agent_sdk_claude_bin ⇒ Object
:agent_sdk engine knobs.
-
#agent_sdk_cli_version_range ⇒ Object
:agent_sdk engine knobs.
-
#agent_sdk_mcp_host ⇒ Object
:agent_sdk engine knobs.
-
#agent_sdk_mcp_timeout_ms ⇒ Object
:agent_sdk engine knobs.
-
#agent_sdk_model ⇒ Object
:agent_sdk engine knobs.
-
#approval_ttl ⇒ Object
How long a parked approval (or in-doubt invocation) waits before expiring.
-
#around_model_call ⇒ Object
Optional hook wrapping every model call — e.g.
-
#auth ⇒ Object
Auth mode for :agent_sdk — :api_key or :oauth (subscription).
-
#channel_resolver ⇒ Object
Channels: name -> Channel subclass (wired by the Registry).
-
#default_model ⇒ Object
Default model when agent.yml doesn't specify one.
-
#definitions_digest ⇒ Object
Injectable seams until/beyond the Registry: name -> tool object, the tool schema list for the model, and the definitions digest for the nondeterminism guard.
-
#engine ⇒ Object
Inference engine seam: :ruby_llm (Phase 1) or :agent_sdk (stub).
-
#eval_dir ⇒ Object
Evals: where *_eval.rb scenarios live, and an optional custom LLM grader.
-
#eval_grader ⇒ Object
Evals: where *_eval.rb scenarios live, and an optional custom LLM grader.
-
#inbox_actor ⇒ Object
Inbox (mountable UI at /silas/inbox).
-
#inbox_auth ⇒ Object
Inbox (mountable UI at /silas/inbox).
-
#inbox_public_read ⇒ Object
Inbox (mountable UI at /silas/inbox).
-
#inbox_streaming ⇒ Object
Force-disable live broadcasting even when turbo-rails is present (nil = auto).
-
#instructions_dir ⇒ Object
Subagents: the roster (name+description) and per-name scope builders, plus the active-agent overrides swapped in during a nested run.
-
#isolate_steps ⇒ Object
Continuation isolation for loop steps.
-
#max_steps ⇒ Object
Hard cap on model calls per turn (agent.yml can lower it per-agent).
-
#mcp_client_factory ⇒ Object
Connections: a test seam for injecting a fake MCP client ->(connection){ client }.
-
#model_prices ⇒ Object
Inbox (mountable UI at /silas/inbox).
-
#queue_name ⇒ Object
Active Job queue for agent turns.
-
#sandbox ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run.
-
#sandbox_cpus ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run.
-
#sandbox_docker_bin ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run.
-
#sandbox_image ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run.
-
#sandbox_memory ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run.
-
#sandbox_network ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run.
-
#sandbox_pids ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run.
-
#sandbox_timeout ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run.
-
#sandbox_workdir ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run.
-
#schedules ⇒ Object
Injectable seams until/beyond the Registry: name -> tool object, the tool schema list for the model, and the definitions digest for the nondeterminism guard.
-
#skills ⇒ Object
Injectable seams until/beyond the Registry: name -> tool object, the tool schema list for the model, and the definitions digest for the nondeterminism guard.
- #slack_bot_token ⇒ Object
- #slack_signing_secret ⇒ Object
-
#subagent_index ⇒ Object
Subagents: the roster (name+description) and per-name scope builders, plus the active-agent overrides swapped in during a nested run.
-
#subagent_scopes ⇒ Object
Subagents: the roster (name+description) and per-name scope builders, plus the active-agent overrides swapped in during a nested run.
-
#tool_definitions ⇒ Object
Injectable seams until/beyond the Registry: name -> tool object, the tool schema list for the model, and the definitions digest for the nondeterminism guard.
-
#tool_resolver ⇒ Object
Injectable seams until/beyond the Registry: name -> tool object, the tool schema list for the model, and the definitions digest for the nondeterminism guard.
Instance Method Summary collapse
-
#boot_guard! ⇒ Object
PLAN.md §1, non-negotiable: raise if subscription OAuth is configured while an API key is present — the key would silently win and bill credits.
-
#initialize ⇒ Configuration
constructor
A new instance of Configuration.
- #validate! ⇒ Object
-
#warn_unsafe_queue_adapter! ⇒ Object
Silas's durability and exactly-once guarantees rest on the queue adapter: a turn's jobs must run one-at-a-time and continuation retries must run in a FRESH execution after the prior one committed.
Constructor Details
#initialize ⇒ Configuration
Returns a new instance of Configuration.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/silas/configuration.rb', line 64 def initialize @engine = :ruby_llm @auth = :api_key @default_model = "claude-sonnet-5" @queue_name = :default @around_model_call = nil @approval_ttl = 7.days @max_steps = 25 @isolate_steps = true @tool_resolver = nil @tool_definitions = nil @definitions_digest = nil @skills = nil @schedules = nil @subagent_index = nil @subagent_scopes = nil @agent_override = nil @instructions_dir = nil @channel_resolver = nil @slack_signing_secret = nil @slack_bot_token = nil @agent_sdk_claude_bin = "claude" @agent_sdk_model = nil # falls back to the agent model; must be a CLI-accepted id @agent_sdk_mcp_host = "127.0.0.1" @agent_sdk_mcp_timeout_ms = 15_000 @agent_sdk_cli_version_range = ">= 2.1.150, < 3" @eval_dir = "test/agent_evals" @eval_grader = nil @sandbox = :none @sandbox_image = nil @sandbox_network = "none" @sandbox_memory = "512m" @sandbox_cpus = "1" @sandbox_pids = 256 @sandbox_workdir = "/workspace" @sandbox_docker_bin = "docker" @sandbox_timeout = 30 @inbox_auth = ->(controller) { controller.head :not_found } # deny by default @inbox_public_read = false @inbox_actor = ->(controller) { controller.try(:current_user)&.try(:email) || "inbox" } # Current Claude rates as cost-units per 1k tokens (1e6 units = $1): # Sonnet $3/$15, Opus $15/$75, Haiku $1/$5 per million tokens. @model_prices = { "claude-sonnet-5" => { in: 3000, out: 15_000 }, "claude-opus-4-8" => { in: 15_000, out: 75_000 }, "claude-haiku-4-5-20251001" => { in: 1000, out: 5000 } } end |
Instance Attribute Details
#agent_override ⇒ Object
Subagents: the roster (name+description) and per-name scope builders, plus the active-agent overrides swapped in during a nested run.
28 29 30 |
# File 'lib/silas/configuration.rb', line 28 def agent_override @agent_override end |
#agent_sdk_claude_bin ⇒ Object
:agent_sdk engine knobs.
34 35 36 |
# File 'lib/silas/configuration.rb', line 34 def agent_sdk_claude_bin @agent_sdk_claude_bin end |
#agent_sdk_cli_version_range ⇒ Object
:agent_sdk engine knobs.
34 35 36 |
# File 'lib/silas/configuration.rb', line 34 def agent_sdk_cli_version_range @agent_sdk_cli_version_range end |
#agent_sdk_mcp_host ⇒ Object
:agent_sdk engine knobs.
34 35 36 |
# File 'lib/silas/configuration.rb', line 34 def agent_sdk_mcp_host @agent_sdk_mcp_host end |
#agent_sdk_mcp_timeout_ms ⇒ Object
:agent_sdk engine knobs.
34 35 36 |
# File 'lib/silas/configuration.rb', line 34 def agent_sdk_mcp_timeout_ms @agent_sdk_mcp_timeout_ms end |
#agent_sdk_model ⇒ Object
:agent_sdk engine knobs.
34 35 36 |
# File 'lib/silas/configuration.rb', line 34 def agent_sdk_model @agent_sdk_model end |
#approval_ttl ⇒ Object
How long a parked approval (or in-doubt invocation) waits before expiring. eve parks forever; Silas expires (parked-forever ghosts are a bug, not a feature).
16 17 18 |
# File 'lib/silas/configuration.rb', line 16 def approval_ttl @approval_ttl end |
#around_model_call ⇒ Object
Optional hook wrapping every model call — e.g. ruby_llm-resilience:
config.around_model_call = ->(ctx, &call) { RubyLLM::Resilience.chain(:anthropic) { call.() } }
13 14 15 |
# File 'lib/silas/configuration.rb', line 13 def around_model_call @around_model_call end |
#auth ⇒ Object
Auth mode for :agent_sdk — :api_key or :oauth (subscription).
6 7 8 |
# File 'lib/silas/configuration.rb', line 6 def auth @auth end |
#channel_resolver ⇒ Object
Channels: name -> Channel subclass (wired by the Registry). Slack creds default to credentials.dig(:silas, :slack, ...); nil disables Slack.
31 32 33 |
# File 'lib/silas/configuration.rb', line 31 def channel_resolver @channel_resolver end |
#default_model ⇒ Object
Default model when agent.yml doesn't specify one.
8 9 10 |
# File 'lib/silas/configuration.rb', line 8 def default_model @default_model end |
#definitions_digest ⇒ Object
Injectable seams until/beyond the Registry: name -> tool object, the tool schema list for the model, and the definitions digest for the nondeterminism guard. The Registry wires real defaults at boot.
25 26 27 |
# File 'lib/silas/configuration.rb', line 25 def definitions_digest @definitions_digest end |
#engine ⇒ Object
Inference engine seam: :ruby_llm (Phase 1) or :agent_sdk (stub).
4 5 6 |
# File 'lib/silas/configuration.rb', line 4 def engine @engine end |
#eval_dir ⇒ Object
Evals: where *_eval.rb scenarios live, and an optional custom LLM grader.
48 49 50 |
# File 'lib/silas/configuration.rb', line 48 def eval_dir @eval_dir end |
#eval_grader ⇒ Object
Evals: where *_eval.rb scenarios live, and an optional custom LLM grader.
48 49 50 |
# File 'lib/silas/configuration.rb', line 48 def eval_grader @eval_grader end |
#inbox_actor ⇒ Object
Inbox (mountable UI at /silas/inbox). inbox_auth — deny-by-default lambda; the host renders/head-404s to DENY and passes by NOT rendering (resilience pattern). inbox_public_read — reads render for anyone; approve/decline still gated. inbox_actor — controller -> identity string (approved_by/decline by:). model_prices — model id -> out: cost-units per 1k tokens, where 1_000_000 units = $1 (so a $3/M-token input rate is 3000 units/1k). Cost.format divides by 1e6.
44 45 46 |
# File 'lib/silas/configuration.rb', line 44 def inbox_actor @inbox_actor end |
#inbox_auth ⇒ Object
Inbox (mountable UI at /silas/inbox). inbox_auth — deny-by-default lambda; the host renders/head-404s to DENY and passes by NOT rendering (resilience pattern). inbox_public_read — reads render for anyone; approve/decline still gated. inbox_actor — controller -> identity string (approved_by/decline by:). model_prices — model id -> out: cost-units per 1k tokens, where 1_000_000 units = $1 (so a $3/M-token input rate is 3000 units/1k). Cost.format divides by 1e6.
44 45 46 |
# File 'lib/silas/configuration.rb', line 44 def inbox_auth @inbox_auth end |
#inbox_public_read ⇒ Object
Inbox (mountable UI at /silas/inbox). inbox_auth — deny-by-default lambda; the host renders/head-404s to DENY and passes by NOT rendering (resilience pattern). inbox_public_read — reads render for anyone; approve/decline still gated. inbox_actor — controller -> identity string (approved_by/decline by:). model_prices — model id -> out: cost-units per 1k tokens, where 1_000_000 units = $1 (so a $3/M-token input rate is 3000 units/1k). Cost.format divides by 1e6.
44 45 46 |
# File 'lib/silas/configuration.rb', line 44 def inbox_public_read @inbox_public_read end |
#inbox_streaming ⇒ Object
Force-disable live broadcasting even when turbo-rails is present (nil = auto).
46 47 48 |
# File 'lib/silas/configuration.rb', line 46 def inbox_streaming @inbox_streaming end |
#instructions_dir ⇒ Object
Subagents: the roster (name+description) and per-name scope builders, plus the active-agent overrides swapped in during a nested run.
28 29 30 |
# File 'lib/silas/configuration.rb', line 28 def instructions_dir @instructions_dir end |
#isolate_steps ⇒ Object
Continuation isolation for loop steps. true in production (persistence per step — the durability contract); specs may disable for inline runs.
21 22 23 |
# File 'lib/silas/configuration.rb', line 21 def isolate_steps @isolate_steps end |
#max_steps ⇒ Object
Hard cap on model calls per turn (agent.yml can lower it per-agent).
18 19 20 |
# File 'lib/silas/configuration.rb', line 18 def max_steps @max_steps end |
#mcp_client_factory ⇒ Object
Connections: a test seam for injecting a fake MCP client ->(connection){ client }.
50 51 52 |
# File 'lib/silas/configuration.rb', line 50 def mcp_client_factory @mcp_client_factory end |
#model_prices ⇒ Object
Inbox (mountable UI at /silas/inbox). inbox_auth — deny-by-default lambda; the host renders/head-404s to DENY and passes by NOT rendering (resilience pattern). inbox_public_read — reads render for anyone; approve/decline still gated. inbox_actor — controller -> identity string (approved_by/decline by:). model_prices — model id -> out: cost-units per 1k tokens, where 1_000_000 units = $1 (so a $3/M-token input rate is 3000 units/1k). Cost.format divides by 1e6.
44 45 46 |
# File 'lib/silas/configuration.rb', line 44 def model_prices @model_prices end |
#queue_name ⇒ Object
Active Job queue for agent turns.
10 11 12 |
# File 'lib/silas/configuration.rb', line 10 def queue_name @queue_name end |
#sandbox ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run. Docker knobs are inert unless :docker.
53 54 55 |
# File 'lib/silas/configuration.rb', line 53 def sandbox @sandbox end |
#sandbox_cpus ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run. Docker knobs are inert unless :docker.
53 54 55 |
# File 'lib/silas/configuration.rb', line 53 def sandbox_cpus @sandbox_cpus end |
#sandbox_docker_bin ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run. Docker knobs are inert unless :docker.
53 54 55 |
# File 'lib/silas/configuration.rb', line 53 def sandbox_docker_bin @sandbox_docker_bin end |
#sandbox_image ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run. Docker knobs are inert unless :docker.
53 54 55 |
# File 'lib/silas/configuration.rb', line 53 def sandbox_image @sandbox_image end |
#sandbox_memory ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run. Docker knobs are inert unless :docker.
53 54 55 |
# File 'lib/silas/configuration.rb', line 53 def sandbox_memory @sandbox_memory end |
#sandbox_network ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run. Docker knobs are inert unless :docker.
53 54 55 |
# File 'lib/silas/configuration.rb', line 53 def sandbox_network @sandbox_network end |
#sandbox_pids ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run. Docker knobs are inert unless :docker.
53 54 55 |
# File 'lib/silas/configuration.rb', line 53 def sandbox_pids @sandbox_pids end |
#sandbox_timeout ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run. Docker knobs are inert unless :docker.
53 54 55 |
# File 'lib/silas/configuration.rb', line 53 def sandbox_timeout @sandbox_timeout end |
#sandbox_workdir ⇒ Object
Sandbox: :none (default, code exec off) | :docker | any object responding to #run. Docker knobs are inert unless :docker.
53 54 55 |
# File 'lib/silas/configuration.rb', line 53 def sandbox_workdir @sandbox_workdir end |
#schedules ⇒ Object
Injectable seams until/beyond the Registry: name -> tool object, the tool schema list for the model, and the definitions digest for the nondeterminism guard. The Registry wires real defaults at boot.
25 26 27 |
# File 'lib/silas/configuration.rb', line 25 def schedules @schedules end |
#skills ⇒ Object
Injectable seams until/beyond the Registry: name -> tool object, the tool schema list for the model, and the definitions digest for the nondeterminism guard. The Registry wires real defaults at boot.
25 26 27 |
# File 'lib/silas/configuration.rb', line 25 def skills @skills end |
#slack_bot_token ⇒ Object
60 61 62 |
# File 'lib/silas/configuration.rb', line 60 def slack_bot_token @slack_bot_token || Rails.application.credentials.dig(:silas, :slack, :bot_token) end |
#slack_signing_secret ⇒ Object
56 57 58 |
# File 'lib/silas/configuration.rb', line 56 def slack_signing_secret @slack_signing_secret || Rails.application.credentials.dig(:silas, :slack, :signing_secret) end |
#subagent_index ⇒ Object
Subagents: the roster (name+description) and per-name scope builders, plus the active-agent overrides swapped in during a nested run.
28 29 30 |
# File 'lib/silas/configuration.rb', line 28 def subagent_index @subagent_index end |
#subagent_scopes ⇒ Object
Subagents: the roster (name+description) and per-name scope builders, plus the active-agent overrides swapped in during a nested run.
28 29 30 |
# File 'lib/silas/configuration.rb', line 28 def subagent_scopes @subagent_scopes end |
#tool_definitions ⇒ Object
Injectable seams until/beyond the Registry: name -> tool object, the tool schema list for the model, and the definitions digest for the nondeterminism guard. The Registry wires real defaults at boot.
25 26 27 |
# File 'lib/silas/configuration.rb', line 25 def tool_definitions @tool_definitions end |
#tool_resolver ⇒ Object
Injectable seams until/beyond the Registry: name -> tool object, the tool schema list for the model, and the definitions digest for the nondeterminism guard. The Registry wires real defaults at boot.
25 26 27 |
# File 'lib/silas/configuration.rb', line 25 def tool_resolver @tool_resolver end |
Instance Method Details
#boot_guard! ⇒ Object
PLAN.md §1, non-negotiable: raise if subscription OAuth is configured while an API key is present — the key would silently win and bill credits. The inverse also holds: :agent_sdk runs --bare (API-key auth only), so api_key mode needs a key present.
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/silas/configuration.rb', line 122 def boot_guard! if engine == :agent_sdk && auth == :oauth && ENV["ANTHROPIC_API_KEY"].present? raise BootGuardError, "engine :agent_sdk with auth: :oauth is configured, but ANTHROPIC_API_KEY exists " \ "in the environment. The key would silently override subscription OAuth and drain " \ "API credits. Unset ANTHROPIC_API_KEY or switch to auth: :api_key." end if engine == :agent_sdk && auth == :api_key && ENV["ANTHROPIC_API_KEY"].blank? raise BootGuardError, "engine :agent_sdk uses --bare (API-key auth only) but ANTHROPIC_API_KEY is not set." end warn_unsafe_queue_adapter! end |
#validate! ⇒ Object
113 114 115 116 |
# File 'lib/silas/configuration.rb', line 113 def validate! boot_guard! self end |
#warn_unsafe_queue_adapter! ⇒ Object
Silas's durability and exactly-once guarantees rest on the queue adapter: a turn's jobs must run one-at-a-time and continuation retries must run in a FRESH execution after the prior one committed. The in-process Async adapter (ActiveJob's dev default) breaks both — it runs a re-enqueued continuation on a thread pool concurrently with the original, which double-executes steps and violates the single-active-turn invariant (a re-run model call mints new tool_call ids the ledger cannot dedup). Solid Queue — or any durable, serializing, DB-backed adapter — is required to run agents; the synchronous :inline adapter is safe for scripts and demos (no durability).
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/silas/configuration.rb', line 147 def warn_unsafe_queue_adapter! return unless defined?(::ActiveJob::Base) name = ::ActiveJob::Base.queue_adapter.class.name.to_s return unless name.include?("AsyncAdapter") = "[Silas] ActiveJob is using the in-process Async queue adapter. " \ "This runs continuation retries on a thread pool concurrently with " \ "the original job, which double-executes agent steps and breaks " \ "exactly-once tool effects. Use :solid_queue (production) or " \ ":inline (scripts/demos). See Silas DEPLOY.md." (defined?(::Rails) && ::Rails.logger ? ::Rails.logger.warn() : nil) || Kernel.warn() rescue StandardError # A diagnostic must never break boot. nil end |