silas 0.2.0 → 0.3.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -1
  3. data/README.md +44 -0
  4. data/app/controllers/concerns/silas/api/serialization.rb +63 -0
  5. data/app/controllers/silas/api/base_controller.rb +24 -0
  6. data/app/controllers/silas/api/v1/approvals_controller.rb +35 -0
  7. data/app/controllers/silas/api/v1/sessions_controller.rb +39 -0
  8. data/app/controllers/silas/api/v1/streams_controller.rb +117 -0
  9. data/app/controllers/silas/api/v1/turns_controller.rb +32 -0
  10. data/app/controllers/silas/inbox/sessions_controller.rb +16 -3
  11. data/app/controllers/silas/inbox/turns_controller.rb +19 -0
  12. data/app/helpers/silas/inbox/trace_helper.rb +5 -0
  13. data/app/models/silas/session.rb +6 -0
  14. data/app/models/silas/turn.rb +9 -0
  15. data/app/views/layouts/silas/inbox.html.erb +6 -0
  16. data/app/views/silas/inbox/invocations/_invocation.html.erb +2 -2
  17. data/app/views/silas/inbox/sessions/index.html.erb +11 -3
  18. data/app/views/silas/inbox/steps/_step.html.erb +3 -0
  19. data/app/views/silas/inbox/turns/_header.html.erb +12 -1
  20. data/config/routes.rb +23 -1
  21. data/db/migrate/20260725000001_add_provider_to_silas_steps.rb +13 -0
  22. data/lib/generators/silas/install/install_generator.rb +1 -0
  23. data/lib/generators/silas/install/templates/agent.yml +10 -1
  24. data/lib/generators/silas/install/templates/initializer.rb +5 -4
  25. data/lib/generators/silas/install/templates/ruby_llm.rb +6 -0
  26. data/lib/silas/agent.rb +4 -0
  27. data/lib/silas/chat.rb +2 -0
  28. data/lib/silas/configuration.rb +30 -34
  29. data/lib/silas/doctor.rb +155 -0
  30. data/lib/silas/engines/ruby_llm.rb +13 -2
  31. data/lib/silas/eval/assertions.rb +18 -0
  32. data/lib/silas/eval/transcript.rb +1 -0
  33. data/lib/silas/inbox/cost.rb +37 -13
  34. data/lib/silas/ledger.rb +8 -1
  35. data/lib/silas/message_builder.rb +9 -2
  36. data/lib/silas/named_agent.rb +1 -0
  37. data/lib/silas/registry.rb +18 -8
  38. data/lib/silas/step_runner.rb +12 -0
  39. data/lib/silas/version.rb +1 -1
  40. data/lib/silas.rb +9 -0
  41. data/lib/tasks/silas_doctor.rake +17 -0
  42. metadata +11 -2
@@ -100,13 +100,20 @@ module Silas
100
100
  end
101
101
 
102
102
  # Stable across boots for the same agent definition; changes when any tool
103
- # schema (incl. the delegate roster + remote connection tools), or skill
104
- # description, changes.
103
+ # schema (incl. the delegate roster + remote connection tools), skill
104
+ # description, or final_answer schema changes.
105
+ #
106
+ # final_answer is appended ONLY when present: schema-less agents keep a
107
+ # byte-identical digest across upgrades, so turns parked over a deploy
108
+ # never fail NondeterminismError for a key they don't use.
105
109
  def digest
106
- Digest::SHA256.hexdigest(JSON.generate({
107
- tools: definitions,
108
- skills: skills.map { |s| [ s.name, s.description ] }
109
- }))
110
+ payload = { tools: definitions, skills: skills.map { |s| [ s.name, s.description ] } }
111
+ payload[:final_answer] = root_agent.final_answer if root_agent.final_answer.present?
112
+ Digest::SHA256.hexdigest(JSON.generate(payload))
113
+ end
114
+
115
+ def root_agent
116
+ @root_agent ||= Silas::Agent.load(root: @root)
110
117
  end
111
118
 
112
119
  # --- named agents (app/agents/<name>/ — the staff pattern) ---------------
@@ -192,9 +199,12 @@ module Silas
192
199
  builtins["handoff"] = Silas::Tools::Handoff if named && named_agent_dirs.size > 1
193
200
  resolver = ->(n) { (tools[n] || builtins.fetch(n)).new }
194
201
  definitions = (tools.values + builtins.values).map(&:schema)
195
- digest = Digest::SHA256.hexdigest(JSON.generate(tools: definitions, skills: skills.map { |s| [ s.name, s.description ] }))
202
+ loaded_agent = agent || Silas::Agent.load(dir: dir)
203
+ payload = { tools: definitions, skills: skills.map { |s| [ s.name, s.description ] } }
204
+ payload[:final_answer] = loaded_agent.final_answer if loaded_agent.final_answer.present?
205
+ digest = Digest::SHA256.hexdigest(JSON.generate(payload))
196
206
 
197
- Silas::AgentScope.new(name: name, dir: dir, agent: agent || Silas::Agent.load(dir: dir),
207
+ Silas::AgentScope.new(name: name, dir: dir, agent: loaded_agent,
198
208
  resolver: resolver, definitions: definitions, digest: digest, skills: skills)
199
209
  end
200
210
  end
@@ -22,6 +22,7 @@ module Silas
22
22
  stop_reason: result.stop_reason,
23
23
  terminal: result.terminal?,
24
24
  model: turn_model(turn),
25
+ provider: provider_for(turn_model(turn)),
25
26
  input_tokens: result.usage&.dig(:input_tokens),
26
27
  output_tokens: result.usage&.dig(:output_tokens)
27
28
  )
@@ -58,6 +59,7 @@ module Silas
58
59
  messages: MessageBuilder.call(turn, upto_index: index),
59
60
  tools: Silas.tool_definitions,
60
61
  model: turn_model(turn),
62
+ final_answer: Silas.agent.final_answer,
61
63
  limits: { max_steps: Silas.agent.max_steps }
62
64
  }
63
65
 
@@ -109,5 +111,15 @@ module Silas
109
111
  def turn_model(_turn)
110
112
  Silas.agent.model
111
113
  end
114
+
115
+ # The provider RubyLLM's own resolution picks for this model id, stamped
116
+ # on the row so cost lookups price against (model, provider) forever
117
+ # after — the registry's tie-break can change; the row shouldn't. nil for
118
+ # ids the installed registry doesn't know (fakes, custom engines).
119
+ def provider_for(model)
120
+ ::RubyLLM.models.find(model).provider
121
+ rescue StandardError
122
+ nil
123
+ end
112
124
  end
113
125
  end
data/lib/silas/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Silas
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.1"
3
3
  end
data/lib/silas.rb CHANGED
@@ -43,6 +43,7 @@ require "silas/instructions"
43
43
  require "silas/step_runner"
44
44
  require "silas/eval" # after engines (ScriptedEngine < Engines::Base)
45
45
  require "silas/chat"
46
+ require "silas/doctor"
46
47
 
47
48
  module Silas
48
49
  class << self
@@ -50,9 +51,17 @@ module Silas
50
51
  @config ||= Configuration.new
51
52
  end
52
53
 
54
+ # Reconfiguring re-resolves the engine and sandbox. Without this the
55
+ # memos below outlive the config that produced them: a second
56
+ # `Silas.configure { |c| c.engine = ... }` in one process kept serving the
57
+ # FIRST engine. That silently broke multi-scenario `silas:eval` runs —
58
+ # every scenario after the first ran the first one's script and still
59
+ # reported pass/fail as if it hadn't. Re-resolution is cheap (one `.new`).
53
60
  def configure
54
61
  yield config
55
62
  config.validate!
63
+ @resolved_engine = nil
64
+ @resolved_sandbox = nil
56
65
  config
57
66
  end
58
67
 
@@ -0,0 +1,17 @@
1
+ namespace :silas do
2
+ desc "Diagnose common Silas misconfigurations (provider key, queue adapter, model, migrations, tools, rescuer, cable, auth)"
3
+ task doctor: :environment do
4
+ glyph = { pass: "\e[32m✓\e[0m", warn: "\e[33m!\e[0m", fail: "\e[31m✗\e[0m" }
5
+ checks = Silas::Doctor.run
6
+
7
+ puts "silas:doctor — #{Silas::VERSION}"
8
+ checks.each do |check|
9
+ puts " #{glyph[check.status]} #{check.label}#{" — #{check.detail}" if check.detail.present?}"
10
+ end
11
+
12
+ failed = checks.count { |c| c.status == :fail }
13
+ warned = checks.count { |c| c.status == :warn }
14
+ puts "\n#{failed} failure(s), #{warned} warning(s)"
15
+ exit 1 if failed.positive?
16
+ end
17
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: silas
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel St Paul
@@ -103,7 +103,7 @@ description: 'An agent framework where your Rails app is the chassis: app/agent/
103
103
  convention, durable runs on Active Job Continuations, exactly-once tool execution
104
104
  via a transactional ledger, approvals that park at zero compute.'
105
105
  email:
106
- - daniel@zerogravity.co.uk
106
+ - danielstpaul@hotmail.com
107
107
  executables: []
108
108
  extensions: []
109
109
  extra_rdoc_files: []
@@ -111,6 +111,12 @@ files:
111
111
  - CHANGELOG.md
112
112
  - LICENSE
113
113
  - README.md
114
+ - app/controllers/concerns/silas/api/serialization.rb
115
+ - app/controllers/silas/api/base_controller.rb
116
+ - app/controllers/silas/api/v1/approvals_controller.rb
117
+ - app/controllers/silas/api/v1/sessions_controller.rb
118
+ - app/controllers/silas/api/v1/streams_controller.rb
119
+ - app/controllers/silas/api/v1/turns_controller.rb
114
120
  - app/controllers/silas/channels/approvals_controller.rb
115
121
  - app/controllers/silas/channels/base_controller.rb
116
122
  - app/controllers/silas/channels/slack_controller.rb
@@ -154,6 +160,7 @@ files:
154
160
  - db/migrate/20260716000002_add_cancel_requested_to_silas_turns.rb
155
161
  - db/migrate/20260721000001_create_silas_memories.rb
156
162
  - db/migrate/20260724000001_drop_agent_sdk_columns_from_silas_turns.rb
163
+ - db/migrate/20260725000001_add_provider_to_silas_steps.rb
157
164
  - lib/generators/silas/install/install_generator.rb
158
165
  - lib/generators/silas/install/templates/agent.yml
159
166
  - lib/generators/silas/install/templates/bin_ci
@@ -176,6 +183,7 @@ files:
176
183
  - lib/silas/connection.rb
177
184
  - lib/silas/connections.rb
178
185
  - lib/silas/delta_buffer.rb
186
+ - lib/silas/doctor.rb
179
187
  - lib/silas/engine.rb
180
188
  - lib/silas/engines/base.rb
181
189
  - lib/silas/engines/ruby_llm.rb
@@ -218,6 +226,7 @@ files:
218
226
  - lib/silas/tools/run_code.rb
219
227
  - lib/silas/version.rb
220
228
  - lib/tasks/silas_chat.rake
229
+ - lib/tasks/silas_doctor.rake
221
230
  - lib/tasks/silas_eval.rake
222
231
  - lib/tasks/silas_schedules.rake
223
232
  homepage: https://github.com/danielstpaul/silas