silas 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +41 -0
  3. data/LICENSE +21 -0
  4. data/README.md +135 -0
  5. data/app/controllers/silas/channels/approvals_controller.rb +34 -0
  6. data/app/controllers/silas/channels/base_controller.rb +22 -0
  7. data/app/controllers/silas/channels/slack_controller.rb +58 -0
  8. data/app/controllers/silas/inbox/base_controller.rb +37 -0
  9. data/app/controllers/silas/inbox/invocations_controller.rb +44 -0
  10. data/app/controllers/silas/inbox/sessions_controller.rb +16 -0
  11. data/app/helpers/silas/inbox/trace_helper.rb +42 -0
  12. data/app/jobs/silas/agent_loop_job.rb +95 -0
  13. data/app/jobs/silas/channel_delivery_job.rb +62 -0
  14. data/app/jobs/silas/dead_job_rescuer_job.rb +31 -0
  15. data/app/jobs/silas/schedule_job.rb +22 -0
  16. data/app/mailboxes/silas/agent_mailbox.rb +35 -0
  17. data/app/mailers/silas/channel_mailer.rb +17 -0
  18. data/app/models/concerns/silas/inbox/broadcastable.rb +86 -0
  19. data/app/models/silas/application_record.rb +5 -0
  20. data/app/models/silas/session.rb +36 -0
  21. data/app/models/silas/step.rb +33 -0
  22. data/app/models/silas/tool_invocation.rb +78 -0
  23. data/app/models/silas/turn.rb +58 -0
  24. data/app/views/layouts/silas/inbox.html.erb +91 -0
  25. data/app/views/silas/channel_mailer/approval.text.erb +9 -0
  26. data/app/views/silas/channels/approvals/done.html.erb +2 -0
  27. data/app/views/silas/channels/approvals/error.html.erb +2 -0
  28. data/app/views/silas/channels/approvals/show.html.erb +9 -0
  29. data/app/views/silas/inbox/invocations/_approval_card.html.erb +11 -0
  30. data/app/views/silas/inbox/invocations/_invocation.html.erb +11 -0
  31. data/app/views/silas/inbox/sessions/_cost.html.erb +1 -0
  32. data/app/views/silas/inbox/sessions/index.html.erb +23 -0
  33. data/app/views/silas/inbox/sessions/show.html.erb +20 -0
  34. data/app/views/silas/inbox/steps/_step.html.erb +8 -0
  35. data/app/views/silas/inbox/turns/_header.html.erb +7 -0
  36. data/app/views/silas/inbox/turns/_turn.html.erb +8 -0
  37. data/config/routes.rb +19 -0
  38. data/db/migrate/20260714000001_create_silas_tables.rb +78 -0
  39. data/db/migrate/20260715000001_add_channel_outbound_markers.rb +8 -0
  40. data/db/migrate/20260715000002_add_agent_sdk_columns_to_turns.rb +9 -0
  41. data/db/migrate/20260715000003_add_parent_session_to_silas_sessions.rb +8 -0
  42. data/lib/generators/silas/install/install_generator.rb +81 -0
  43. data/lib/generators/silas/install/templates/agent.yml +9 -0
  44. data/lib/generators/silas/install/templates/bin_ci +7 -0
  45. data/lib/generators/silas/install/templates/channel_email.rb +18 -0
  46. data/lib/generators/silas/install/templates/channel_slack.rb +19 -0
  47. data/lib/generators/silas/install/templates/example_eval.rb +22 -0
  48. data/lib/generators/silas/install/templates/example_schedule.md +11 -0
  49. data/lib/generators/silas/install/templates/example_skill.md +8 -0
  50. data/lib/generators/silas/install/templates/example_tool.rb +12 -0
  51. data/lib/generators/silas/install/templates/initializer.rb +16 -0
  52. data/lib/generators/silas/install/templates/instructions.md +4 -0
  53. data/lib/silas/agent.rb +33 -0
  54. data/lib/silas/agent_scope.rb +6 -0
  55. data/lib/silas/agent_sdk/cli.rb +59 -0
  56. data/lib/silas/agent_sdk/stream_parser.rb +86 -0
  57. data/lib/silas/agent_sdk/version_guard.rb +26 -0
  58. data/lib/silas/budget.rb +42 -0
  59. data/lib/silas/channel.rb +69 -0
  60. data/lib/silas/configuration.rb +164 -0
  61. data/lib/silas/connection.rb +77 -0
  62. data/lib/silas/connections.rb +52 -0
  63. data/lib/silas/engine.rb +36 -0
  64. data/lib/silas/engines/agent_sdk.rb +75 -0
  65. data/lib/silas/engines/base.rb +30 -0
  66. data/lib/silas/engines/ruby_llm.rb +136 -0
  67. data/lib/silas/errors.rb +26 -0
  68. data/lib/silas/eval/assertions.rb +107 -0
  69. data/lib/silas/eval/driver.rb +47 -0
  70. data/lib/silas/eval/dsl.rb +55 -0
  71. data/lib/silas/eval/grader.rb +37 -0
  72. data/lib/silas/eval/result.rb +28 -0
  73. data/lib/silas/eval/runner.rb +38 -0
  74. data/lib/silas/eval/scripted_engine.rb +39 -0
  75. data/lib/silas/eval/transcript.rb +22 -0
  76. data/lib/silas/eval.rb +35 -0
  77. data/lib/silas/inbox/cost.rb +58 -0
  78. data/lib/silas/inbox.rb +23 -0
  79. data/lib/silas/instructions.rb +55 -0
  80. data/lib/silas/ledger.rb +178 -0
  81. data/lib/silas/mcp/client.rb +86 -0
  82. data/lib/silas/mcp/handler.rb +89 -0
  83. data/lib/silas/mcp/server.rb +134 -0
  84. data/lib/silas/message_builder.rb +63 -0
  85. data/lib/silas/nested_runner.rb +41 -0
  86. data/lib/silas/registry.rb +155 -0
  87. data/lib/silas/sandbox/docker.rb +67 -0
  88. data/lib/silas/sandbox/null.rb +17 -0
  89. data/lib/silas/sandbox.rb +15 -0
  90. data/lib/silas/schedule/compiler.rb +52 -0
  91. data/lib/silas/schedule.rb +109 -0
  92. data/lib/silas/skill.rb +21 -0
  93. data/lib/silas/slack.rb +55 -0
  94. data/lib/silas/step_runner.rb +98 -0
  95. data/lib/silas/subprocess_runner.rb +41 -0
  96. data/lib/silas/tool.rb +93 -0
  97. data/lib/silas/tools/delegate.rb +42 -0
  98. data/lib/silas/tools/load_skill.rb +25 -0
  99. data/lib/silas/tools/run_code.rb +21 -0
  100. data/lib/silas/version.rb +3 -0
  101. data/lib/silas.rb +144 -0
  102. data/lib/tasks/silas_eval.rake +20 -0
  103. data/lib/tasks/silas_schedules.rake +33 -0
  104. metadata +232 -0
data/lib/silas/tool.rb ADDED
@@ -0,0 +1,93 @@
1
+ module Silas
2
+ # Base class for agent tools. The keyword signature of #call IS the schema:
3
+ #
4
+ # class Agent::Tools::IssueRefund < Silas::Tool
5
+ # description "Refund an order."
6
+ # param :amount, :integer, desc: "Pence" # optional type refinement
7
+ # approval :always # :never | :once | :always | lambda
8
+ # transactional! # or at_most_once! (default) / idempotent!
9
+ #
10
+ # def call(order_id:, amount:, note: nil)
11
+ # ...
12
+ # end
13
+ # end
14
+ #
15
+ # Tool identity is the FILENAME (app/agent/tools/issue_refund.rb -> "issue_refund"),
16
+ # eve's convention, enforced by the Registry.
17
+ class Tool
18
+ class << self
19
+ def description(text = nil)
20
+ @description = text if text
21
+ @description || ""
22
+ end
23
+
24
+ def param(name, type = :string, desc: nil)
25
+ param_refinements[name.to_sym] = { type: type.to_s, desc: desc }
26
+ end
27
+
28
+ def approval(policy = nil, &block)
29
+ @approval_policy = block || policy unless policy.nil? && block.nil?
30
+ @approval_policy || :never
31
+ end
32
+ alias approval_policy approval
33
+
34
+ def transactional! = @effect_mode = :transactional
35
+ def idempotent! = @effect_mode = :idempotent
36
+ def at_most_once! = @effect_mode = :at_most_once
37
+
38
+ def effect_mode = @effect_mode || :at_most_once
39
+
40
+ def tool_name
41
+ name.demodulize.underscore
42
+ end
43
+
44
+ # {name:, description:, input_schema:} — provider-agnostic JSON schema.
45
+ def schema
46
+ validate_signature!
47
+ properties = {}
48
+ required = []
49
+ instance_method(:call).parameters.each do |kind, pname|
50
+ refinement = param_refinements[pname] || {}
51
+ properties[pname.to_s] = { "type" => refinement[:type] || "string" }
52
+ properties[pname.to_s]["description"] = refinement[:desc] if refinement[:desc]
53
+ required << pname.to_s if kind == :keyreq
54
+ end
55
+ {
56
+ "name" => tool_name,
57
+ "description" => description,
58
+ "input_schema" => {
59
+ "type" => "object",
60
+ "properties" => properties,
61
+ "required" => required
62
+ }
63
+ }
64
+ end
65
+
66
+ def validate_signature!
67
+ raise Error, "#{name} must define #call" unless method_defined?(:call)
68
+
69
+ kinds = instance_method(:call).parameters.map(&:first)
70
+ if kinds.any? { |k| %i[req opt rest].include?(k) }
71
+ raise Error, "#{name}#call must accept keyword arguments only — the signature is the schema"
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def param_refinements
78
+ @param_refinements ||= {}
79
+ end
80
+ end
81
+
82
+ # Instance-side delegation so a resolved instance answers the Ledger.
83
+ def approval_policy = self.class.approval_policy
84
+ def effect_mode = self.class.effect_mode
85
+
86
+ # Execution context, set by the Ledger before #call.
87
+ attr_accessor :session
88
+
89
+ def call(**)
90
+ raise NotImplementedError, "#{self.class}#call"
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,42 @@
1
+ module Silas
2
+ module Tools
3
+ # Built-in. Added to the ROOT toolset only when subagents exist; NOT given to
4
+ # subagents (depth-1, no recursion). The whole delegation is ONE external
5
+ # effect: at_most_once! means a crash mid-run parks the parent invocation
6
+ # in-doubt (the exactly-once boundary), never silently re-runs.
7
+ class Delegate < Silas::Tool
8
+ at_most_once!
9
+ approval :never
10
+ param :subagent, :string, desc: "Which subagent to run (one of the names listed above)."
11
+ param :input, :string, desc: "Full, self-contained task — the subagent shares NONE of your context."
12
+
13
+ def self.tool_name = "delegate"
14
+
15
+ # Dynamic description enumerates the roster, so the roster lands in the ROOT
16
+ # digest (schema -> Registry#definitions -> digest). Adding a subagent
17
+ # mid-turn => NondeterminismError, exactly like a skill change.
18
+ def self.description
19
+ roster = Silas.subagent_index
20
+ base = "Delegate a self-contained subtask to a specialized subagent that runs with its own " \
21
+ "instructions, tools, and a fresh context, then returns its final answer."
22
+ return base if roster.empty?
23
+
24
+ base + "\n\nAvailable subagents:\n" + roster.map { |n, d| "- #{n}: #{d}" }.join("\n")
25
+ end
26
+
27
+ # `session` is the PARENT session, set by the Ledger before #call.
28
+ def call(subagent:, input:)
29
+ return { "error" => "unknown subagent #{subagent.inspect}" } unless Silas.subagent?(subagent)
30
+
31
+ nested = Silas::Session.create!(agent_name: subagent, parent_session_id: session.id,
32
+ metadata: { "delegated_from" => session.id })
33
+ turn = Silas::NestedRunner.run(nested, input: input)
34
+ if turn.completed?
35
+ { "session_id" => nested.id, "answer" => turn.answer_text }
36
+ else
37
+ { "session_id" => nested.id, "error" => "subagent did not finish (#{turn.status}: #{turn.failure_reason})" }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ module Silas
2
+ module Tools
3
+ # The built-in progressive-disclosure tool (eve's mechanism): skill
4
+ # descriptions are advertised in the instructions; the body loads on
5
+ # demand and — because the body IS the tool result — rides the ledger and
6
+ # replays byte-identically for free. Loading also registers the skill on
7
+ # the session so subsequent turns inline it directly.
8
+ class LoadSkill < Tool
9
+ description "Load the full playbook for a named skill. Use when a task matches a skill description."
10
+ transactional!
11
+
12
+ def self.tool_name = "load_skill"
13
+
14
+ def call(name:)
15
+ skill = Silas.skills.find { |s| s.name == name }
16
+ return { "error" => "unknown skill #{name.inspect}" } unless skill
17
+
18
+ unless session.loaded_skills.include?(name)
19
+ session.update!(loaded_skills: session.loaded_skills + [ name ])
20
+ end
21
+ { "skill" => name, "content" => skill.body }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ module Silas
2
+ module Tools
3
+ # Built-in, added to the toolset only when a sandbox is configured. Runs a
4
+ # shell command through the configured sandbox. at_most_once! — a sandbox
5
+ # exec is an external effect, so a crash mid-run parks it in-doubt.
6
+ class RunCode < Tool
7
+ description "Run a shell command in an ephemeral, network-isolated sandbox container. " \
8
+ "Use for untrusted or model-generated code."
9
+ param :command, :string, desc: "Shell command run via /bin/sh -c inside the sandbox."
10
+ param :timeout, :integer, desc: "Max wall-clock seconds before the container is killed."
11
+ at_most_once!
12
+
13
+ def self.tool_name = "run_code"
14
+
15
+ def call(command:, timeout: nil)
16
+ result = Silas.resolved_sandbox.run(command, timeout: timeout || Silas.config.sandbox_timeout)
17
+ { "stdout" => result.stdout, "stderr" => result.stderr, "exit_status" => result.exit_status }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module Silas
2
+ VERSION = "0.1.1"
3
+ end
data/lib/silas.rb ADDED
@@ -0,0 +1,144 @@
1
+ # Active Job (Continuations) and Active Record are the chassis — load them
2
+ # even in --minimal host apps that skipped the railties.
3
+ require "active_job/railtie" if defined?(::Rails::Railtie)
4
+ require "active_record/railtie" if defined?(::Rails::Railtie)
5
+
6
+ require "silas/version"
7
+ require "silas/errors"
8
+ require "silas/configuration"
9
+ require "silas/ledger"
10
+ require "silas/tool"
11
+ require "silas/skill"
12
+ require "silas/schedule"
13
+ require "silas/schedule/compiler"
14
+ require "silas/agent_scope"
15
+ require "silas/tools/delegate"
16
+ require "silas/nested_runner"
17
+ require "silas/sandbox"
18
+ require "silas/tools/run_code"
19
+ require "silas/channel"
20
+ require "silas/slack"
21
+ require "silas/mcp/client"
22
+ require "silas/connection"
23
+ require "silas/connections"
24
+ require "silas/inbox"
25
+ require "silas/inbox/cost"
26
+ require "silas/budget"
27
+ require "silas/registry"
28
+ require "silas/agent"
29
+ require "silas/tools/load_skill"
30
+ require "silas/engines/base"
31
+ require "silas/agent_sdk/version_guard"
32
+ require "silas/agent_sdk/stream_parser"
33
+ require "silas/agent_sdk/cli"
34
+ require "silas/mcp/handler"
35
+ require "silas/mcp/server"
36
+ require "silas/engines/agent_sdk"
37
+ require "ruby_llm"
38
+ require "silas/engines/ruby_llm"
39
+ require "silas/message_builder"
40
+ require "silas/instructions"
41
+ require "silas/step_runner"
42
+ require "silas/subprocess_runner"
43
+ require "silas/eval" # after engines (ScriptedEngine < Engines::Base)
44
+
45
+ module Silas
46
+ class << self
47
+ def config
48
+ @config ||= Configuration.new
49
+ end
50
+
51
+ def configure
52
+ yield config
53
+ config.validate!
54
+ config
55
+ end
56
+
57
+ def reset_configuration! # for specs
58
+ @config = nil
59
+ @resolved_engine = nil
60
+ @resolved_sandbox = nil
61
+ @agent = nil
62
+ end
63
+
64
+ def sandbox_enabled? = ![ :none, nil ].include?(config.sandbox)
65
+
66
+ def resolved_sandbox
67
+ @resolved_sandbox ||=
68
+ case config.sandbox
69
+ when :none, nil then Sandbox::Null.new
70
+ when :docker
71
+ Sandbox::Docker.new(image: config.sandbox_image, network: config.sandbox_network,
72
+ memory: config.sandbox_memory, cpus: config.sandbox_cpus,
73
+ pids: config.sandbox_pids, workdir: config.sandbox_workdir,
74
+ docker_bin: config.sandbox_docker_bin)
75
+ when Symbol then raise Error, "unknown sandbox #{config.sandbox.inspect}"
76
+ else config.sandbox
77
+ end
78
+ end
79
+
80
+ def reset_agent_memo! = (@agent = nil) # after Registry.install! swaps dirs
81
+
82
+ # The inference adapter instance. config.engine may be a symbol (:ruby_llm,
83
+ # :agent_sdk) or any object responding to #execute_step (specs, custom).
84
+ def resolved_engine
85
+ @resolved_engine ||=
86
+ case config.engine
87
+ when :ruby_llm then Engines::RubyLLM.new
88
+ when :agent_sdk then Engines::AgentSdk.new
89
+ when Symbol then raise Error, "unknown engine #{config.engine.inspect}"
90
+ else config.engine
91
+ end
92
+ end
93
+
94
+ def tool_resolver
95
+ config.tool_resolver or raise Error, "no tool resolver configured (Registry boots one; specs must inject)"
96
+ end
97
+
98
+ def tool_definitions
99
+ config.tool_definitions&.call || []
100
+ end
101
+
102
+ def skills
103
+ config.skills&.call || []
104
+ end
105
+
106
+ def schedules
107
+ config.schedules&.call || []
108
+ end
109
+
110
+ # The active agent definition. config.agent_override is set during a nested
111
+ # subagent run; otherwise it's the root app/agent.
112
+ def agent
113
+ config.agent_override || (@agent ||= Agent.load)
114
+ end
115
+
116
+ # Subagent roster: [[name, description], ...] (model-visible, so it's in the
117
+ # root digest via Delegate.description).
118
+ def subagent_index = config.subagent_index&.call || []
119
+ def subagent?(name) = subagent_index.any? { |n, _| n == name.to_s }
120
+ def subagent_scope(name) = config.subagent_scopes&.call&.fetch(name.to_s)
121
+
122
+ # Run a block with a subagent's scope swapped in as the active globals,
123
+ # restoring afterward. Synchronous/depth-1 — safe because delegation runs
124
+ # inline on one thread while the parent loop is paused in the delegate tool.
125
+ def with_agent_scope(scope)
126
+ saved = {
127
+ tool_resolver: config.tool_resolver, tool_definitions: config.tool_definitions,
128
+ definitions_digest: config.definitions_digest, skills: config.skills,
129
+ agent_override: config.agent_override, instructions_dir: config.instructions_dir
130
+ }
131
+ config.tool_resolver = scope.resolver
132
+ config.tool_definitions = -> { scope.definitions }
133
+ config.definitions_digest = -> { scope.digest }
134
+ config.skills = -> { scope.skills }
135
+ config.agent_override = scope.agent
136
+ config.instructions_dir = scope.dir
137
+ yield
138
+ ensure
139
+ saved.each { |k, v| config.public_send("#{k}=", v) }
140
+ end
141
+ end
142
+ end
143
+
144
+ require "silas/engine" if defined?(::Rails::Engine)
@@ -0,0 +1,20 @@
1
+ namespace :silas do
2
+ desc "Run agent evals as a deploy gate (exit 1 on failure). SILAS_EVAL_MODE=real for live; EVAL=regex to filter."
3
+ task eval: :environment do
4
+ ok = Silas::Eval::Runner.run(
5
+ dir: ENV["SILAS_EVAL_DIR"] || Silas.config.eval_dir,
6
+ pattern: (Regexp.new(ENV["EVAL"]) if ENV["EVAL"]),
7
+ mode: ENV["SILAS_EVAL_MODE"]&.to_sym
8
+ )
9
+ exit(1) unless ok
10
+ end
11
+
12
+ namespace :eval do
13
+ desc "List discovered eval scenarios without running them"
14
+ task list: :environment do
15
+ Silas::Eval.reset!
16
+ Dir[File.join(Silas.config.eval_dir, "**/*_eval.rb")].sort.each { |f| load f }
17
+ Silas::Eval.scenarios.each { |s| puts " [#{s.mode}] #{s.name}" }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ namespace :silas do
2
+ desc "Compile app/agent/schedules/** into config/recurring.yml (env: RAILS_ENV or ENV[SILAS_SCHEDULE_ENV] or production)"
3
+ task schedules: :environment do
4
+ Silas::Registry.install!(root: Rails.root)
5
+ env = ENV["SILAS_SCHEDULE_ENV"] || "production"
6
+ schedules = Silas.schedules
7
+ Silas::Schedule::Compiler.write!(schedules, root: Rails.root, env: env)
8
+ if schedules.empty?
9
+ puts "silas: no schedules found under app/agent/schedules/ (managed entries cleared for #{env})"
10
+ else
11
+ puts "silas: compiled #{schedules.size} schedule(s) into config/recurring.yml [#{env}]:"
12
+ schedules.sort_by(&:name).each { |s| puts " #{s.recurring_key} #{s.cron} (#{s.kind}) -> #{s.name}" }
13
+ end
14
+ end
15
+
16
+ namespace :schedules do
17
+ desc "Report drift between discovered schedules and config/recurring.yml"
18
+ task list: :environment do
19
+ Silas::Registry.install!(root: Rails.root)
20
+ env = ENV["SILAS_SCHEDULE_ENV"] || "production"
21
+ drift = Silas::Schedule::Compiler.drift(Silas.schedules, root: Rails.root, env: env)
22
+ puts "silas schedules [#{env}]:"
23
+ puts " discovered: #{Silas.schedules.map(&:name).sort.join(', ').then { |s| s.empty? ? '(none)' : s }}"
24
+ unless drift[:uncompiled].empty?
25
+ puts " NOT COMPILED (run `bin/rails silas:schedules`): #{drift[:uncompiled].join(', ')}"
26
+ end
27
+ unless drift[:orphaned].empty?
28
+ puts " ORPHANED in recurring.yml (file deleted): #{drift[:orphaned].join(', ')}"
29
+ end
30
+ puts " in sync." if drift[:uncompiled].empty? && drift[:orphaned].empty?
31
+ end
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,232 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: silas
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel St Paul
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '8.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ruby_llm
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rspec-rails
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '7.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '7.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: sqlite3
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: pg
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 1.5.0
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 1.5.0
82
+ - !ruby/object:Gem::Dependency
83
+ name: solid_queue
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '1.2'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '1.2'
96
+ description: 'An agent framework where your Rails app is the chassis: app/agent/ directory
97
+ convention, durable runs on Active Job Continuations, exactly-once tool execution
98
+ via a transactional ledger, approvals that park at zero compute.'
99
+ email:
100
+ - daniel@zerogravity.co.uk
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - CHANGELOG.md
106
+ - LICENSE
107
+ - README.md
108
+ - app/controllers/silas/channels/approvals_controller.rb
109
+ - app/controllers/silas/channels/base_controller.rb
110
+ - app/controllers/silas/channels/slack_controller.rb
111
+ - app/controllers/silas/inbox/base_controller.rb
112
+ - app/controllers/silas/inbox/invocations_controller.rb
113
+ - app/controllers/silas/inbox/sessions_controller.rb
114
+ - app/helpers/silas/inbox/trace_helper.rb
115
+ - app/jobs/silas/agent_loop_job.rb
116
+ - app/jobs/silas/channel_delivery_job.rb
117
+ - app/jobs/silas/dead_job_rescuer_job.rb
118
+ - app/jobs/silas/schedule_job.rb
119
+ - app/mailboxes/silas/agent_mailbox.rb
120
+ - app/mailers/silas/channel_mailer.rb
121
+ - app/models/concerns/silas/inbox/broadcastable.rb
122
+ - app/models/silas/application_record.rb
123
+ - app/models/silas/session.rb
124
+ - app/models/silas/step.rb
125
+ - app/models/silas/tool_invocation.rb
126
+ - app/models/silas/turn.rb
127
+ - app/views/layouts/silas/inbox.html.erb
128
+ - app/views/silas/channel_mailer/approval.text.erb
129
+ - app/views/silas/channels/approvals/done.html.erb
130
+ - app/views/silas/channels/approvals/error.html.erb
131
+ - app/views/silas/channels/approvals/show.html.erb
132
+ - app/views/silas/inbox/invocations/_approval_card.html.erb
133
+ - app/views/silas/inbox/invocations/_invocation.html.erb
134
+ - app/views/silas/inbox/sessions/_cost.html.erb
135
+ - app/views/silas/inbox/sessions/index.html.erb
136
+ - app/views/silas/inbox/sessions/show.html.erb
137
+ - app/views/silas/inbox/steps/_step.html.erb
138
+ - app/views/silas/inbox/turns/_header.html.erb
139
+ - app/views/silas/inbox/turns/_turn.html.erb
140
+ - config/routes.rb
141
+ - db/migrate/20260714000001_create_silas_tables.rb
142
+ - db/migrate/20260715000001_add_channel_outbound_markers.rb
143
+ - db/migrate/20260715000002_add_agent_sdk_columns_to_turns.rb
144
+ - db/migrate/20260715000003_add_parent_session_to_silas_sessions.rb
145
+ - lib/generators/silas/install/install_generator.rb
146
+ - lib/generators/silas/install/templates/agent.yml
147
+ - lib/generators/silas/install/templates/bin_ci
148
+ - lib/generators/silas/install/templates/channel_email.rb
149
+ - lib/generators/silas/install/templates/channel_slack.rb
150
+ - lib/generators/silas/install/templates/example_eval.rb
151
+ - lib/generators/silas/install/templates/example_schedule.md
152
+ - lib/generators/silas/install/templates/example_skill.md
153
+ - lib/generators/silas/install/templates/example_tool.rb
154
+ - lib/generators/silas/install/templates/initializer.rb
155
+ - lib/generators/silas/install/templates/instructions.md
156
+ - lib/silas.rb
157
+ - lib/silas/agent.rb
158
+ - lib/silas/agent_scope.rb
159
+ - lib/silas/agent_sdk/cli.rb
160
+ - lib/silas/agent_sdk/stream_parser.rb
161
+ - lib/silas/agent_sdk/version_guard.rb
162
+ - lib/silas/budget.rb
163
+ - lib/silas/channel.rb
164
+ - lib/silas/configuration.rb
165
+ - lib/silas/connection.rb
166
+ - lib/silas/connections.rb
167
+ - lib/silas/engine.rb
168
+ - lib/silas/engines/agent_sdk.rb
169
+ - lib/silas/engines/base.rb
170
+ - lib/silas/engines/ruby_llm.rb
171
+ - lib/silas/errors.rb
172
+ - lib/silas/eval.rb
173
+ - lib/silas/eval/assertions.rb
174
+ - lib/silas/eval/driver.rb
175
+ - lib/silas/eval/dsl.rb
176
+ - lib/silas/eval/grader.rb
177
+ - lib/silas/eval/result.rb
178
+ - lib/silas/eval/runner.rb
179
+ - lib/silas/eval/scripted_engine.rb
180
+ - lib/silas/eval/transcript.rb
181
+ - lib/silas/inbox.rb
182
+ - lib/silas/inbox/cost.rb
183
+ - lib/silas/instructions.rb
184
+ - lib/silas/ledger.rb
185
+ - lib/silas/mcp/client.rb
186
+ - lib/silas/mcp/handler.rb
187
+ - lib/silas/mcp/server.rb
188
+ - lib/silas/message_builder.rb
189
+ - lib/silas/nested_runner.rb
190
+ - lib/silas/registry.rb
191
+ - lib/silas/sandbox.rb
192
+ - lib/silas/sandbox/docker.rb
193
+ - lib/silas/sandbox/null.rb
194
+ - lib/silas/schedule.rb
195
+ - lib/silas/schedule/compiler.rb
196
+ - lib/silas/skill.rb
197
+ - lib/silas/slack.rb
198
+ - lib/silas/step_runner.rb
199
+ - lib/silas/subprocess_runner.rb
200
+ - lib/silas/tool.rb
201
+ - lib/silas/tools/delegate.rb
202
+ - lib/silas/tools/load_skill.rb
203
+ - lib/silas/tools/run_code.rb
204
+ - lib/silas/version.rb
205
+ - lib/tasks/silas_eval.rake
206
+ - lib/tasks/silas_schedules.rake
207
+ homepage: https://github.com/danielstpaul/silas
208
+ licenses:
209
+ - MIT
210
+ metadata:
211
+ homepage_uri: https://github.com/danielstpaul/silas
212
+ source_code_uri: https://github.com/danielstpaul/silas
213
+ changelog_uri: https://github.com/danielstpaul/silas/blob/main/CHANGELOG.md
214
+ rubygems_mfa_required: 'true'
215
+ rdoc_options: []
216
+ require_paths:
217
+ - lib
218
+ required_ruby_version: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '3.2'
223
+ required_rubygems_version: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - ">="
226
+ - !ruby/object:Gem::Version
227
+ version: '0'
228
+ requirements: []
229
+ rubygems_version: 3.6.9
230
+ specification_version: 4
231
+ summary: Durable AI agents on the Rails you already run.
232
+ test_files: []