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/engine.rb,
lib/silas/errors.rb,
lib/silas/ledger.rb,
lib/silas/channel.rb,
lib/silas/sandbox.rb,
lib/silas/version.rb,
lib/silas/eval/dsl.rb,
lib/silas/registry.rb,
lib/silas/schedule.rb,
lib/silas/connection.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/engines/base.rb,
lib/silas/instructions.rb,
lib/silas/sandbox/null.rb,
lib/silas/tools/recall.rb,
app/models/silas/memory.rb,
lib/silas/agent_sdk/cli.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/message_builder.rb,
lib/silas/engines/ruby_llm.rb,
lib/silas/tools/load_skill.rb,
app/jobs/silas/schedule_job.rb,
lib/silas/engines/agent_sdk.rb,
lib/silas/schedule/compiler.rb,
lib/silas/subprocess_runner.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/agent_sdk/stream_parser.rb,
lib/silas/agent_sdk/version_guard.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/inbox/base_controller.rb,
app/models/concerns/silas/inbox/broadcastable.rb,
app/controllers/silas/channels/base_controller.rb,
lib/generators/silas/install/install_generator.rb,
app/controllers/silas/channels/slack_controller.rb,
app/controllers/silas/inbox/sessions_controller.rb,
app/controllers/silas/inbox/invocations_controller.rb,
app/controllers/silas/channels/approvals_controller.rb

Defined Under Namespace

Modules: AgentSdk, Budget, Channels, Engines, Eval, Generators, Inbox, Instructions, Ledger, Mcp, MessageBuilder, NestedRunner, Sandbox, Slack, StepRunner, SubprocessRunner, Tools Classes: Agent, AgentLoopJob, AgentMailbox, AgentScope, ApplicationRecord, BootGuardError, Channel, ChannelDeliveryJob, ChannelMailer, Chat, CheckpointInLedgerError, Configuration, Connection, Connections, DeadJobRescuerJob, Engine, Error, Event, 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.1.7"

Class Method Summary collapse

Class Method Details

.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:

Silas.agent.start(input: "...")            # the root app/agent
Silas.agent(:clerk).start(input: "...")    # a named staff member


150
151
152
153
154
# File 'lib/silas.rb', line 150

def agent(name = nil)
  return NamedAgent.new(named_agent_scope!(name)) if name

  current_scope&.agent || config.agent_override || (@agent ||= Agent.load)
end

.configObject



52
53
54
# File 'lib/silas.rb', line 52

def config
  @config ||= Configuration.new
end

.configure {|config| ... } ⇒ Object

Yields:



56
57
58
59
60
# File 'lib/silas.rb', line 56

def configure
  yield config
  config.validate!
  config
end

.current_scopeObject



199
200
201
# File 'lib/silas.rb', line 199

def current_scope
  ActiveSupport::IsolatedExecutionState[SCOPE_KEY]
end

.definitions_digestObject

The live definitions digest as a String (nil when none configured).



137
138
139
# File 'lib/silas.rb', line 137

def definitions_digest
  current_scope&.digest || config.definitions_digest&.call&.to_s.presence
end

.instructions_dirObject



141
142
143
# File 'lib/silas.rb', line 141

def instructions_dir
  current_scope&.dir || config.instructions_dir
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).

Returns:

  • (Boolean)


161
162
163
164
165
# File 'lib/silas.rb', line 161

def memory_enabled?
  config.memory && Memory.table_exists?
rescue ActiveRecord::NoDatabaseError, ActiveRecord::ConnectionNotEstablished
  false
end

.named_agent?(name) ⇒ Boolean

Returns:

  • (Boolean)


166
# File 'lib/silas.rb', line 166

def named_agent?(name) = named_agent_scopes.key?(name.to_s)

.named_agent_scope!(name) ⇒ Object



168
169
170
171
172
173
174
# File 'lib/silas.rb', line 168

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_scopesObject

Named-agent roster: { "name" => AgentScope }.



157
# File 'lib/silas.rb', line 157

def named_agent_scopes = config.named_agent_scopes&.call || {}

.reset_agent_memo!Object

after Registry.install! swaps dirs



98
# File 'lib/silas.rb', line 98

def reset_agent_memo! = (@agent = nil) # after Registry.install! swaps dirs

.reset_configuration!Object

for specs



62
63
64
65
66
67
# File 'lib/silas.rb', line 62

def reset_configuration! # for specs
  @config = nil
  @resolved_engine = nil
  @resolved_sandbox = nil
  @agent = nil
end

.resolved_engineObject

The inference adapter instance. config.engine may be a symbol (:ruby_llm, :agent_sdk) or any object responding to #execute_step (specs, custom).



102
103
104
105
106
107
108
109
110
# File 'lib/silas.rb', line 102

def resolved_engine
  @resolved_engine ||=
    case config.engine
    when :ruby_llm then Engines::RubyLLM.new
    when :agent_sdk then Engines::AgentSdk.new
    when Symbol then raise Error, "unknown engine #{config.engine.inspect}"
    else config.engine
    end
end

.resolved_sandboxObject



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/silas.rb', line 77

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.

Returns:

  • (Boolean)


71
72
73
74
75
# File 'lib/silas.rb', line 71

def sandbox_enabled?
  return false if [ :none, nil ].include?(config.sandbox)

  resolved_sandbox.enabled?
end

.schedulesObject



132
133
134
# File 'lib/silas.rb', line 132

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.



186
187
188
189
190
191
192
193
# File 'lib/silas.rb', line 186

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

.skillsObject



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

def skills
  current_scope&.skills || config.skills&.call || []
end

.subagent?(name) ⇒ Boolean

Returns:

  • (Boolean)


179
# File 'lib/silas.rb', line 179

def subagent?(name) = subagent_index.any? { |n, _| n == name.to_s }

.subagent_indexObject

Subagent roster: [[name, description], ...] (model-visible, so it's in the root digest via Delegate.description).



178
# File 'lib/silas.rb', line 178

def subagent_index = config.subagent_index&.call || []

.subagent_scope(name) ⇒ Object



180
# File 'lib/silas.rb', line 180

def subagent_scope(name) = config.subagent_scopes&.call&.fetch(name.to_s)

.tool_definitionsObject



124
125
126
# File 'lib/silas.rb', line 124

def tool_definitions
  current_scope&.definitions || config.tool_definitions&.call || []
end

.tool_resolverObject

---- 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.



119
120
121
122
# File 'lib/silas.rb', line 119

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.



206
207
208
209
210
211
212
# File 'lib/silas.rb', line 206

def with_agent_scope(scope)
  previous = ActiveSupport::IsolatedExecutionState[SCOPE_KEY]
  ActiveSupport::IsolatedExecutionState[SCOPE_KEY] = scope
  yield
ensure
  ActiveSupport::IsolatedExecutionState[SCOPE_KEY] = previous
end