Module: Silas
- Defined in:
- lib/silas.rb,
lib/silas/chat.rb,
lib/silas/eval.rb,
lib/silas/tool.rb,
lib/silas/agent.rb,
lib/silas/inbox.rb,
lib/silas/skill.rb,
lib/silas/slack.rb,
lib/silas/budget.rb,
lib/silas/doctor.rb,
lib/silas/engine.rb,
lib/silas/errors.rb,
lib/silas/ledger.rb,
lib/silas/channel.rb,
lib/silas/sandbox.rb,
lib/silas/version.rb,
lib/silas/webhook.rb,
lib/silas/eval/dsl.rb,
lib/silas/registry.rb,
lib/silas/schedule.rb,
lib/silas/compactor.rb,
lib/silas/connection.rb,
lib/silas/deprecator.rb,
lib/silas/inbox/cost.rb,
lib/silas/mcp/client.rb,
lib/silas/mcp/server.rb,
app/models/silas/step.rb,
app/models/silas/turn.rb,
lib/silas/agent_scope.rb,
lib/silas/connections.rb,
lib/silas/eval/driver.rb,
lib/silas/eval/grader.rb,
lib/silas/eval/result.rb,
lib/silas/eval/runner.rb,
lib/silas/mcp/handler.rb,
lib/silas/named_agent.rb,
lib/silas/step_runner.rb,
lib/silas/delta_buffer.rb,
lib/silas/instructions.rb,
lib/silas/sandbox/null.rb,
lib/silas/tools/recall.rb,
app/models/silas/memory.rb,
lib/silas/adapters/base.rb,
lib/silas/configuration.rb,
lib/silas/nested_runner.rb,
lib/silas/tools/handoff.rb,
app/models/silas/session.rb,
lib/silas/sandbox/docker.rb,
lib/silas/tools/delegate.rb,
lib/silas/tools/remember.rb,
lib/silas/tools/run_code.rb,
lib/silas/eval/assertions.rb,
lib/silas/eval/transcript.rb,
lib/silas/instrumentation.rb,
lib/silas/message_builder.rb,
lib/silas/tools/load_skill.rb,
app/jobs/silas/schedule_job.rb,
app/models/silas/compaction.rb,
lib/silas/adapters/ruby_llm.rb,
lib/silas/schedule/compiler.rb,
lib/silas/tools/ask_question.rb,
app/jobs/silas/agent_loop_job.rb,
lib/silas/eval/scripted_engine.rb,
app/mailers/silas/channel_mailer.rb,
app/models/silas/tool_invocation.rb,
app/mailboxes/silas/agent_mailbox.rb,
lib/silas/inbox/delta_broadcaster.rb,
app/jobs/silas/channel_delivery_job.rb,
app/jobs/silas/dead_job_rescuer_job.rb,
app/models/silas/application_record.rb,
app/helpers/silas/inbox/trace_helper.rb,
app/controllers/silas/api/base_controller.rb,
app/controllers/silas/inbox/base_controller.rb,
app/controllers/silas/inbox/turns_controller.rb,
app/controllers/silas/api/v1/turns_controller.rb,
app/models/concerns/silas/inbox/broadcastable.rb,
app/controllers/silas/channels/base_controller.rb,
lib/generators/silas/channel/channel_generator.rb,
lib/generators/silas/install/install_generator.rb,
app/controllers/silas/api/v1/streams_controller.rb,
app/controllers/silas/channels/slack_controller.rb,
app/controllers/silas/inbox/sessions_controller.rb,
app/controllers/concerns/silas/api/serialization.rb,
app/controllers/silas/api/v1/sessions_controller.rb,
app/controllers/silas/api/v1/approvals_controller.rb,
app/controllers/silas/inbox/invocations_controller.rb,
app/controllers/silas/channels/approvals_controller.rb
Defined Under Namespace
Modules: Adapters, Api, Budget, Channels, Compactor, Engines, Eval, Generators, Inbox, Instructions, Instrumentation, Ledger, Mcp, MessageBuilder, NestedRunner, Sandbox, Slack, StepRunner, Tools, Webhook Classes: Agent, AgentLoopJob, AgentMailbox, AgentScope, ApplicationRecord, BootGuardError, Channel, ChannelDeliveryJob, ChannelMailer, Chat, CheckpointInLedgerError, Compaction, Configuration, Connection, Connections, DeadJobRescuerJob, DeltaBuffer, Doctor, Engine, Error, Event, LogSubscriber, Memory, NamedAgent, NondeterminismError, Registry, SandboxDisabledError, SandboxError, Schedule, ScheduleJob, Session, Skill, Step, Tool, ToolInvocation, Turn, TurnInProgressError
Constant Summary collapse
- SCOPE_KEY =
---- scope switching -----------------------------------------------------
:silas_agent_scope- VERSION =
"0.5.0"
Class Method Summary collapse
-
.agent(name = nil) ⇒ Object
The active agent definition — or, given a name, a handle for a NAMED agent (app/agents/
/) whose sessions run under that agent's scope:. - .config ⇒ Object
-
.configure {|config| ... } ⇒ Object
Reconfiguring re-resolves the engine and sandbox.
- .current_scope ⇒ Object
-
.definitions_digest ⇒ Object
The live definitions digest as a String (nil when none configured).
-
.deprecator ⇒ Object
One deprecator for the whole gem, so hosts can control the noise the way they control Rails' own:.
- .instructions_dir ⇒ Object
-
.instrument(event, **payload, &block) ⇒ Object
Silas.instrument(:tool, tool: "issue_refund") { ... }.
- .logger ⇒ Object
-
.memory_enabled? ⇒ Boolean
Memory is on when configured AND the table exists (upgrade-safe: an app that hasn't run the 0.1.7 migration simply doesn't advertise the tools).
- .named_agent?(name) ⇒ Boolean
- .named_agent_scope!(name) ⇒ Object
-
.named_agent_scopes ⇒ Object
Named-agent roster: { "name" => AgentScope }.
-
.reset_agent_memo! ⇒ Object
after Registry.install! swaps dirs.
-
.reset_configuration! ⇒ Object
for specs.
-
.resolved_adapter ⇒ Object
The inference adapter instance.
-
.resolved_engine ⇒ Object
Renamed in 0.4: "engine" meant two unrelated things (the Rails engine at Silas::Engine, and the inference backend), which is exactly the collision ActiveJob avoids with QueueAdapters.
- .resolved_sandbox ⇒ Object
-
.sandbox_enabled? ⇒ Boolean
A configured-but-disabled backend (e.g. Hermetic.null) must not register run_code, so the object's own enabled? is the final word.
- .schedules ⇒ Object
-
.scope_for_session(session) ⇒ Object
The scope a session's turns must run under: nil for the root agent, otherwise the named-agent or subagent scope matching session.agent_name.
- .skills ⇒ Object
- .subagent?(name) ⇒ Boolean
-
.subagent_index ⇒ Object
Subagent roster: [[name, description], ...] (model-visible, so it's in the root digest via Delegate.description).
- .subagent_scope(name) ⇒ Object
- .tool_definitions ⇒ Object
-
.tool_resolver ⇒ Object
---- scope-aware readers ------------------------------------------------- Every reader consults the active AgentScope first (named agent or subagent), falling back to the boot-time config the Registry installed.
-
.with_agent_scope(scope) ⇒ Object
Run a block under an AgentScope.
Class Method Details
.agent(name = nil) ⇒ Object
173 174 175 176 177 |
# File 'lib/silas.rb', line 173 def agent(name = nil) return NamedAgent.new(named_agent_scope!(name)) if name current_scope&.agent || config.agent_override || (@agent ||= Agent.load) end |
.config ⇒ Object
56 57 58 |
# File 'lib/silas.rb', line 56 def config @config ||= Configuration.new end |
.configure {|config| ... } ⇒ Object
Reconfiguring re-resolves the engine and sandbox. Without this the
memos below outlive the config that produced them: a second
Silas.configure { |c| c.engine = ... } in one process kept serving the
FIRST engine. That silently broke multi-scenario silas:eval runs —
every scenario after the first ran the first one's script and still
reported pass/fail as if it hadn't. Re-resolution is cheap (one .new).
66 67 68 69 70 71 72 |
# File 'lib/silas.rb', line 66 def configure yield config config.validate! @resolved_adapter = nil @resolved_sandbox = nil config end |
.current_scope ⇒ Object
222 223 224 |
# File 'lib/silas.rb', line 222 def current_scope ActiveSupport::IsolatedExecutionState[SCOPE_KEY] end |
.definitions_digest ⇒ Object
The live definitions digest as a String (nil when none configured).
160 161 162 |
# File 'lib/silas.rb', line 160 def definitions_digest current_scope&.digest || config.definitions_digest&.call&.to_s.presence end |
.deprecator ⇒ Object
One deprecator for the whole gem, so hosts can control the noise the way they control Rails' own:
Silas.deprecator.behavior = :raise # or :warn (default), :silence
Rails registers it in application.deprecators (see Silas::Engine), which
means config.active_support.report_deprecations = false silences Silas
along with everything else, and a host can opt into raising in CI.
Every deprecation message names BOTH the replacement and the version it disappears in — a warning you can't act on is just noise.
13 14 15 |
# File 'lib/silas/deprecator.rb', line 13 def self.deprecator @deprecator ||= ActiveSupport::Deprecation.new("2.0", "Silas") end |
.instructions_dir ⇒ Object
164 165 166 |
# File 'lib/silas.rb', line 164 def instructions_dir current_scope&.dir || config.instructions_dir end |
.instrument(event, **payload, &block) ⇒ Object
Silas.instrument(:tool, tool: "issue_refund") { ... }
49 50 51 |
# File 'lib/silas/instrumentation.rb', line 49 def self.instrument(event, **payload, &block) Instrumentation.instrument(event, **payload, &block) end |
.logger ⇒ Object
56 57 58 |
# File 'lib/silas/instrumentation.rb', line 56 def self.logger @@logger ||= (defined?(Rails) && Rails.logger) || ActiveSupport::Logger.new($stdout) end |
.memory_enabled? ⇒ Boolean
Memory is on when configured AND the table exists (upgrade-safe: an app that hasn't run the 0.1.7 migration simply doesn't advertise the tools).
184 185 186 187 188 |
# File 'lib/silas.rb', line 184 def memory_enabled? config.memory && Memory.table_exists? rescue ActiveRecord::NoDatabaseError, ActiveRecord::ConnectionNotEstablished false end |
.named_agent?(name) ⇒ Boolean
189 |
# File 'lib/silas.rb', line 189 def named_agent?(name) = named_agent_scopes.key?(name.to_s) |
.named_agent_scope!(name) ⇒ Object
191 192 193 194 195 196 197 |
# File 'lib/silas.rb', line 191 def named_agent_scope!(name) named_agent_scopes.fetch(name.to_s) do known = named_agent_scopes.keys raise Error, "unknown agent #{name.inspect}" \ "#{known.any? ? " (known: #{known.join(', ')})" : " — no app/agents/ directories found"}" end end |
.named_agent_scopes ⇒ Object
Named-agent roster: { "name" => AgentScope }.
180 |
# File 'lib/silas.rb', line 180 def named_agent_scopes = config.named_agent_scopes&.call || {} |
.reset_agent_memo! ⇒ Object
after Registry.install! swaps dirs
110 |
# File 'lib/silas.rb', line 110 def reset_agent_memo! = (@agent = nil) # after Registry.install! swaps dirs |
.reset_configuration! ⇒ Object
for specs
74 75 76 77 78 79 |
# File 'lib/silas.rb', line 74 def reset_configuration! # for specs @config = nil @resolved_adapter = nil @resolved_sandbox = nil @agent = nil end |
.resolved_adapter ⇒ Object
The inference adapter instance. config.adapter may be :ruby_llm or any object responding to #execute_step (specs, custom).
114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/silas.rb', line 114 def resolved_adapter @resolved_adapter ||= case config.adapter when :ruby_llm then Adapters::RubyLLM.new when :agent_sdk raise Error, "the :agent_sdk adapter was removed in Silas 0.2 — the claude -p " \ "subprocess integration is gone (its subscription-auth rationale was " \ "unreachable). Use adapter :ruby_llm, the production path." when Symbol then raise Error, "unknown adapter #{config.adapter.inspect}" else config.adapter end end |
.resolved_engine ⇒ Object
Renamed in 0.4: "engine" meant two unrelated things (the Rails engine at Silas::Engine, and the inference backend), which is exactly the collision ActiveJob avoids with QueueAdapters. Removed in 2.0.
130 131 132 133 |
# File 'lib/silas.rb', line 130 def resolved_engine Silas.deprecator.warn("Silas.resolved_engine is deprecated; use Silas.resolved_adapter") resolved_adapter end |
.resolved_sandbox ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/silas.rb', line 89 def resolved_sandbox @resolved_sandbox ||= case config.sandbox when :none, nil then Sandbox::Null.new when :docker Sandbox::Docker.new(image: config.sandbox_image, network: config.sandbox_network, memory: config.sandbox_memory, cpus: config.sandbox_cpus, pids: config.sandbox_pids, workdir: config.sandbox_workdir, docker_bin: config.sandbox_docker_bin) when Symbol then raise Error, "unknown sandbox #{config.sandbox.inspect}" else # A hermetic backend drops straight in (its Result is a superset of # ours). Auto-arm its ledger guard so a sandbox exec inside a ledger # transaction fails loud — same posture as our own Docker adapter. if defined?(Hermetic::Backends::Base) && config.sandbox.is_a?(Hermetic::Backends::Base) require "hermetic/silas" end config.sandbox end end |
.sandbox_enabled? ⇒ Boolean
A configured-but-disabled backend (e.g. Hermetic.null) must not register run_code, so the object's own enabled? is the final word.
83 84 85 86 87 |
# File 'lib/silas.rb', line 83 def sandbox_enabled? return false if [ :none, nil ].include?(config.sandbox) resolved_sandbox.enabled? end |
.schedules ⇒ Object
155 156 157 |
# File 'lib/silas.rb', line 155 def schedules config.schedules&.call || [] end |
.scope_for_session(session) ⇒ Object
The scope a session's turns must run under: nil for the root agent, otherwise the named-agent or subagent scope matching session.agent_name. Fails loud on an unknown name — a session pointing at a deleted agent directory must never silently run with the root agent's tools.
209 210 211 212 213 214 215 216 |
# File 'lib/silas.rb', line 209 def scope_for_session(session) name = session.agent_name.to_s return nil if name.empty? || name == "agent" named_agent_scopes[name] || config.subagent_scopes&.call&.[](name) or raise Error, "session #{session.id} belongs to agent #{name.inspect}, " \ "but no app/agents/#{name} or app/agent/subagents/#{name} exists" end |
.skills ⇒ Object
151 152 153 |
# File 'lib/silas.rb', line 151 def skills current_scope&.skills || config.skills&.call || [] end |
.subagent?(name) ⇒ Boolean
202 |
# File 'lib/silas.rb', line 202 def subagent?(name) = subagent_index.any? { |n, _| n == name.to_s } |
.subagent_index ⇒ Object
Subagent roster: [[name, description], ...] (model-visible, so it's in the root digest via Delegate.description).
201 |
# File 'lib/silas.rb', line 201 def subagent_index = config.subagent_index&.call || [] |
.subagent_scope(name) ⇒ Object
203 |
# File 'lib/silas.rb', line 203 def subagent_scope(name) = config.subagent_scopes&.call&.fetch(name.to_s) |
.tool_definitions ⇒ Object
147 148 149 |
# File 'lib/silas.rb', line 147 def tool_definitions current_scope&.definitions || config.tool_definitions&.call || [] end |
.tool_resolver ⇒ Object
---- scope-aware readers ------------------------------------------------- Every reader consults the active AgentScope first (named agent or subagent), falling back to the boot-time config the Registry installed. The scope lives in IsolatedExecutionState — per-thread AND per-fiber (Falcon-safe), so concurrent jobs running different agents in one process can never see each other's tools.
142 143 144 145 |
# File 'lib/silas.rb', line 142 def tool_resolver current_scope&.resolver || config.tool_resolver or raise Error, "no tool resolver configured (Registry boots one; specs must inject)" end |
.with_agent_scope(scope) ⇒ Object
Run a block under an AgentScope. Nestable (delegation inside a named agent restores the outer scope on exit) and isolated per execution context — no global config is mutated, so concurrent jobs are safe.
229 230 231 232 233 234 235 |
# File 'lib/silas.rb', line 229 def with_agent_scope(scope) previous = ActiveSupport::IsolatedExecutionState[SCOPE_KEY] ActiveSupport::IsolatedExecutionState[SCOPE_KEY] = scope yield ensure ActiveSupport::IsolatedExecutionState[SCOPE_KEY] = previous end |