smith-agents 0.4.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +139 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/LICENSE +21 -0
- data/README.md +226 -0
- data/Rakefile +14 -0
- data/UPSTREAM_PROPOSAL.md +141 -0
- data/docs/CONFIGURATION.md +123 -0
- data/docs/PATTERNS.md +492 -0
- data/docs/PERSISTENCE.md +169 -0
- data/docs/TOOLS_AND_GUARDRAILS.md +140 -0
- data/docs/workflow_claim.md +58 -0
- data/exe/smith +7 -0
- data/lib/generators/smith/install/install_generator.rb +22 -0
- data/lib/generators/smith/install/templates/smith.rb.tt +44 -0
- data/lib/smith/agent/lifecycle.rb +264 -0
- data/lib/smith/agent/registry.rb +128 -0
- data/lib/smith/agent.rb +259 -0
- data/lib/smith/artifacts/file.rb +59 -0
- data/lib/smith/artifacts/memory.rb +75 -0
- data/lib/smith/artifacts/scoped_store.rb +29 -0
- data/lib/smith/artifacts.rb +5 -0
- data/lib/smith/budget/ledger.rb +42 -0
- data/lib/smith/budget.rb +5 -0
- data/lib/smith/cli.rb +82 -0
- data/lib/smith/context/observation_masking.rb +19 -0
- data/lib/smith/context/session.rb +42 -0
- data/lib/smith/context/state_injection.rb +24 -0
- data/lib/smith/context.rb +61 -0
- data/lib/smith/doctor/check.rb +12 -0
- data/lib/smith/doctor/checks/baseline.rb +84 -0
- data/lib/smith/doctor/checks/configuration.rb +56 -0
- data/lib/smith/doctor/checks/durability.rb +103 -0
- data/lib/smith/doctor/checks/live.rb +55 -0
- data/lib/smith/doctor/checks/models_registry.rb +66 -0
- data/lib/smith/doctor/checks/openai_api_mode.rb +51 -0
- data/lib/smith/doctor/checks/persistence.rb +99 -0
- data/lib/smith/doctor/checks/persistence_capabilities.rb +60 -0
- data/lib/smith/doctor/checks/persistence_registry.rb +82 -0
- data/lib/smith/doctor/checks/rails.rb +39 -0
- data/lib/smith/doctor/checks/serialization.rb +78 -0
- data/lib/smith/doctor/installer.rb +103 -0
- data/lib/smith/doctor/printer.rb +62 -0
- data/lib/smith/doctor/report.rb +39 -0
- data/lib/smith/doctor.rb +53 -0
- data/lib/smith/errors.rb +191 -0
- data/lib/smith/event.rb +11 -0
- data/lib/smith/events/.keep +0 -0
- data/lib/smith/events/bus.rb +60 -0
- data/lib/smith/events/step_completed.rb +11 -0
- data/lib/smith/events/subscription.rb +24 -0
- data/lib/smith/events.rb +5 -0
- data/lib/smith/guardrails/runner.rb +44 -0
- data/lib/smith/guardrails/url_verifier.rb +7 -0
- data/lib/smith/guardrails.rb +35 -0
- data/lib/smith/models/inference.rb +199 -0
- data/lib/smith/models/normalizer.rb +186 -0
- data/lib/smith/models/profile.rb +39 -0
- data/lib/smith/models.rb +132 -0
- data/lib/smith/persistence_adapters/active_record_store.rb +99 -0
- data/lib/smith/persistence_adapters/cache_store.rb +79 -0
- data/lib/smith/persistence_adapters/memory.rb +105 -0
- data/lib/smith/persistence_adapters/rails_cache.rb +20 -0
- data/lib/smith/persistence_adapters/redis_store.rb +136 -0
- data/lib/smith/persistence_adapters/retry.rb +42 -0
- data/lib/smith/persistence_adapters.rb +112 -0
- data/lib/smith/pricing.rb +65 -0
- data/lib/smith/providers/openai/responses.rb +315 -0
- data/lib/smith/providers/openai/routing.rb +67 -0
- data/lib/smith/providers/openai/tools_extensions.rb +106 -0
- data/lib/smith/railtie.rb +9 -0
- data/lib/smith/tasks/doctor.rake +38 -0
- data/lib/smith/tool/budget_enforcement.rb +33 -0
- data/lib/smith/tool/capability_builder.rb +18 -0
- data/lib/smith/tool/capture.rb +22 -0
- data/lib/smith/tool/compatibility.rb +72 -0
- data/lib/smith/tool/policy.rb +40 -0
- data/lib/smith/tool.rb +171 -0
- data/lib/smith/tools/think.rb +25 -0
- data/lib/smith/tools/url_fetcher.rb +16 -0
- data/lib/smith/tools/web_search.rb +17 -0
- data/lib/smith/tools.rb +5 -0
- data/lib/smith/trace/logger.rb +46 -0
- data/lib/smith/trace/memory.rb +53 -0
- data/lib/smith/trace/open_telemetry.rb +57 -0
- data/lib/smith/trace.rb +89 -0
- data/lib/smith/types.rb +16 -0
- data/lib/smith/version.rb +5 -0
- data/lib/smith/workflow/artifact_integration.rb +41 -0
- data/lib/smith/workflow/budget_integration.rb +105 -0
- data/lib/smith/workflow/claim.rb +118 -0
- data/lib/smith/workflow/data_volume_policy.rb +36 -0
- data/lib/smith/workflow/deadline_enforcement.rb +100 -0
- data/lib/smith/workflow/deterministic_execution.rb +53 -0
- data/lib/smith/workflow/deterministic_step.rb +57 -0
- data/lib/smith/workflow/dsl.rb +223 -0
- data/lib/smith/workflow/durability.rb +369 -0
- data/lib/smith/workflow/evaluator_optimizer.rb +220 -0
- data/lib/smith/workflow/event_integration.rb +24 -0
- data/lib/smith/workflow/execution.rb +127 -0
- data/lib/smith/workflow/execution_frame.rb +166 -0
- data/lib/smith/workflow/guardrail_integration.rb +40 -0
- data/lib/smith/workflow/nested_execution.rb +69 -0
- data/lib/smith/workflow/orchestrator_worker.rb +145 -0
- data/lib/smith/workflow/parallel.rb +50 -0
- data/lib/smith/workflow/parallel_execution.rb +75 -0
- data/lib/smith/workflow/persistence.rb +358 -0
- data/lib/smith/workflow/pipeline.rb +117 -0
- data/lib/smith/workflow/router.rb +53 -0
- data/lib/smith/workflow/transition.rb +208 -0
- data/lib/smith/workflow.rb +555 -0
- data/lib/smith.rb +254 -0
- data/script/profile_tool_results.rb +94 -0
- data/sig/smith.rbs +4 -0
- metadata +258 -0
data/lib/smith.rb
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-configurable"
|
|
4
|
+
|
|
5
|
+
require_relative "smith/version"
|
|
6
|
+
|
|
7
|
+
module Smith
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
|
|
10
|
+
extend Dry::Configurable
|
|
11
|
+
|
|
12
|
+
# Artifact store (§4.7)
|
|
13
|
+
setting :artifact_store
|
|
14
|
+
setting :artifact_retention
|
|
15
|
+
setting :artifact_encryption, default: :none
|
|
16
|
+
setting :artifact_tenant_isolation, default: false
|
|
17
|
+
|
|
18
|
+
# Trace adapters (§4.8)
|
|
19
|
+
setting :trace_adapter
|
|
20
|
+
setting :trace_transitions, default: true
|
|
21
|
+
setting :trace_tool_calls, default: true
|
|
22
|
+
setting :trace_token_usage, default: true
|
|
23
|
+
setting :trace_cost, default: true
|
|
24
|
+
setting :trace_fields
|
|
25
|
+
setting :trace_content, default: false
|
|
26
|
+
setting :trace_retention
|
|
27
|
+
setting :trace_tenant_isolation, default: false
|
|
28
|
+
|
|
29
|
+
# Pricing (§4.5 — model-call cost computation)
|
|
30
|
+
setting :pricing, default: nil
|
|
31
|
+
|
|
32
|
+
# Persistence adapter for host durability verification (§doctor)
|
|
33
|
+
setting :persistence_adapter, default: nil
|
|
34
|
+
setting :persistence_options, default: {}.freeze
|
|
35
|
+
|
|
36
|
+
# Persistence TTL in Integer seconds. nil (default) means workflows
|
|
37
|
+
# persist indefinitely. Adapters that natively support TTL (Redis,
|
|
38
|
+
# CacheStore, Memory) pass this through; ActiveRecordStore TTL is
|
|
39
|
+
# deferred (would need an `expires_at` column + sweeper).
|
|
40
|
+
# Per-workflow `Workflow.persistence_ttl 1.day.to_i` DSL overrides this.
|
|
41
|
+
setting :persistence_ttl, default: nil
|
|
42
|
+
|
|
43
|
+
# Retry policy for transient persistence I/O failures.
|
|
44
|
+
# attempts: total attempts (including the first)
|
|
45
|
+
# base_delay: initial sleep between attempts, doubled each retry
|
|
46
|
+
# max_delay: cap on per-retry sleep
|
|
47
|
+
setting :persistence_retry_policy, default: { attempts: 3, base_delay: 0.1, max_delay: 1.0 }
|
|
48
|
+
|
|
49
|
+
# Test isolation: when true AND persistence_adapter is nil, Smith
|
|
50
|
+
# auto-selects the in-process Memory adapter. Lets specs avoid wiring
|
|
51
|
+
# Redis/Rails.cache in spec_helper.rb.
|
|
52
|
+
setting :test_mode, default: false
|
|
53
|
+
|
|
54
|
+
# RubyLLM model registry mode: nil/:bundled (default) or :database (§doctor)
|
|
55
|
+
setting :ruby_llm_model_registry, default: nil
|
|
56
|
+
|
|
57
|
+
# OpenAI API mode controls Smith's vendored /v1/responses routing
|
|
58
|
+
# for gpt-5 family + tools + reasoning_effort. :auto routes
|
|
59
|
+
# automatically when the combo is detected; :off disables routing
|
|
60
|
+
# (Smith's normalizer falls back to dropping incompatible tools).
|
|
61
|
+
#
|
|
62
|
+
# Default :auto reflects the "use both when possible" design intent.
|
|
63
|
+
# Smith ships the Responses adapter vendored from crmne/ruby_llm PR #770
|
|
64
|
+
# at a pinned SHA, so the routing path is operational for sync
|
|
65
|
+
# completions. Streaming over /v1/responses is not yet supported and
|
|
66
|
+
# raises NotImplementedError; hosts who need streaming with the
|
|
67
|
+
# (gpt-5 + tools + thinking) combo should set openai_api_mode = :off
|
|
68
|
+
# for graceful tool-dropping via chat-completions.
|
|
69
|
+
setting :openai_api_mode, default: :auto, constructor: lambda { |value|
|
|
70
|
+
unless %i[off auto].include?(value)
|
|
71
|
+
raise ArgumentError, "Smith.config.openai_api_mode must be :off or :auto, got #{value.inspect}"
|
|
72
|
+
end
|
|
73
|
+
value
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Trace gating for normalizer decision events. Hosts can opt out
|
|
77
|
+
# of the per-mutation event stream (the normalizer can emit several
|
|
78
|
+
# events per chat construction if multiple capabilities translate).
|
|
79
|
+
setting :trace_normalizer, default: true
|
|
80
|
+
|
|
81
|
+
# Logger (§7 — Ruby Logger, not Rails.logger)
|
|
82
|
+
setting :logger, default: nil
|
|
83
|
+
|
|
84
|
+
def self.artifacts
|
|
85
|
+
scoped_artifacts || config.artifact_store || (@_default_artifacts ||= Artifacts::Memory.new)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.artifacts=(store)
|
|
89
|
+
config.artifact_store = store
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.scoped_artifacts
|
|
93
|
+
Thread.current[:smith_scoped_artifacts]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.scoped_artifacts=(store)
|
|
97
|
+
Thread.current[:smith_scoped_artifacts] = store
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def self.persistence_adapter
|
|
101
|
+
raw_adapter = config.persistence_adapter
|
|
102
|
+
raw_options = config.persistence_options || {}
|
|
103
|
+
signature = persistence_signature(raw_adapter, raw_options, config.test_mode)
|
|
104
|
+
|
|
105
|
+
if defined?(@_persistence_adapter_signature) && @_persistence_adapter_signature == signature
|
|
106
|
+
return @_persistence_adapter
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@_persistence_adapter_signature = signature
|
|
110
|
+
@_persistence_adapter = resolve_persistence_adapter(raw_adapter, raw_options)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Test isolation auto-detect: when no adapter is configured AND
|
|
114
|
+
# test_mode is on, fall back to the in-process Memory adapter so spec
|
|
115
|
+
# suites don't need to wire Redis/Rails.cache in spec_helper.rb.
|
|
116
|
+
# Explicit adapter config always wins over this auto-detect.
|
|
117
|
+
def self.resolve_persistence_adapter(raw_adapter, raw_options)
|
|
118
|
+
return PersistenceAdapters.resolve(raw_adapter, **raw_options) if raw_adapter
|
|
119
|
+
return PersistenceAdapters::Memory.new if config.test_mode
|
|
120
|
+
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
private_class_method :resolve_persistence_adapter
|
|
124
|
+
|
|
125
|
+
def self.persistence_signature(adapter, options, test_mode)
|
|
126
|
+
[snapshot_value(adapter), snapshot_value(options), test_mode]
|
|
127
|
+
end
|
|
128
|
+
private_class_method :persistence_signature
|
|
129
|
+
|
|
130
|
+
def self.snapshot_value(value)
|
|
131
|
+
case value
|
|
132
|
+
when Hash
|
|
133
|
+
value.each_with_object({}) do |(key, nested), copy|
|
|
134
|
+
copy[snapshot_value(key)] = snapshot_value(nested)
|
|
135
|
+
end.freeze
|
|
136
|
+
when Array
|
|
137
|
+
value.map { |nested| snapshot_value(nested) }.freeze
|
|
138
|
+
when String
|
|
139
|
+
value.dup.freeze
|
|
140
|
+
else
|
|
141
|
+
value
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
private_class_method :snapshot_value
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Leaf modules (no internal dependencies)
|
|
148
|
+
require_relative "smith/types"
|
|
149
|
+
require_relative "smith/errors"
|
|
150
|
+
|
|
151
|
+
# Event system (depends on Types)
|
|
152
|
+
require_relative "smith/event"
|
|
153
|
+
require_relative "smith/events"
|
|
154
|
+
require_relative "smith/events/subscription"
|
|
155
|
+
require_relative "smith/events/bus"
|
|
156
|
+
require_relative "smith/events/step_completed"
|
|
157
|
+
|
|
158
|
+
# Budget (depends on Errors)
|
|
159
|
+
require_relative "smith/budget"
|
|
160
|
+
require_relative "smith/budget/ledger"
|
|
161
|
+
|
|
162
|
+
# Pricing (depends on Smith config)
|
|
163
|
+
require_relative "smith/pricing"
|
|
164
|
+
|
|
165
|
+
# Model capability registry + pattern-based inference (depends on Errors).
|
|
166
|
+
# Smith ships NO specific model_id declarations. Inference rules describe
|
|
167
|
+
# PROVIDER FAMILIES (Anthropic Opus 4.7+ adaptive, gpt-5 family
|
|
168
|
+
# responses-route, Gemini 2.5+ budget_tokens). Applications register
|
|
169
|
+
# Smith::Models.register(Profile.new(...)) overrides only for custom
|
|
170
|
+
# models. Loaded BEFORE Tool so Tools that declare `compatible_with` can
|
|
171
|
+
# resolve capability semantics, AND before Agent so the chat override
|
|
172
|
+
# can call Smith::Models.find_or_infer on first construction.
|
|
173
|
+
require_relative "smith/models/profile"
|
|
174
|
+
require_relative "smith/models"
|
|
175
|
+
require_relative "smith/models/inference"
|
|
176
|
+
require_relative "smith/models/normalizer"
|
|
177
|
+
|
|
178
|
+
# OpenAI /v1/responses routing prepend. Dormant until
|
|
179
|
+
# Smith.config.openai_api_mode = :auto (default :off). Full
|
|
180
|
+
# payload assembly (Smith::Providers::OpenAI::Responses) and tool
|
|
181
|
+
# format helpers (Smith::Providers::OpenAI::ToolsExtensions) are
|
|
182
|
+
# vendored from crmne/ruby_llm PR #770 at pinned SHA. They retire when
|
|
183
|
+
# the PR merges upstream (Smith bumps the ruby_llm dep + deletes the
|
|
184
|
+
# vendored files). The require order must keep the helpers (ToolsExtensions)
|
|
185
|
+
# loaded BEFORE Responses since Responses calls into ToolsExtensions,
|
|
186
|
+
# and BOTH must load before Routing since Routing dispatches to
|
|
187
|
+
# Responses.complete via `defined?(...)` guard.
|
|
188
|
+
require_relative "smith/providers/openai/tools_extensions"
|
|
189
|
+
require_relative "smith/providers/openai/responses"
|
|
190
|
+
require_relative "smith/providers/openai/routing"
|
|
191
|
+
|
|
192
|
+
# Trace adapters (no internal deps)
|
|
193
|
+
require_relative "smith/trace"
|
|
194
|
+
require_relative "smith/trace/memory"
|
|
195
|
+
require_relative "smith/trace/logger"
|
|
196
|
+
require_relative "smith/trace/open_telemetry"
|
|
197
|
+
|
|
198
|
+
# Artifact store (no internal deps)
|
|
199
|
+
require_relative "smith/artifacts"
|
|
200
|
+
require_relative "smith/artifacts/memory"
|
|
201
|
+
require_relative "smith/artifacts/file"
|
|
202
|
+
require_relative "smith/artifacts/scoped_store"
|
|
203
|
+
|
|
204
|
+
# Host persistence adapters (no internal deps)
|
|
205
|
+
require_relative "smith/persistence_adapters"
|
|
206
|
+
|
|
207
|
+
# Tool (depends on RubyLLM::Tool)
|
|
208
|
+
require_relative "smith/tool"
|
|
209
|
+
require_relative "smith/tools"
|
|
210
|
+
require_relative "smith/tools/web_search"
|
|
211
|
+
require_relative "smith/tools/url_fetcher"
|
|
212
|
+
require_relative "smith/tools/think"
|
|
213
|
+
|
|
214
|
+
# Guardrails and Context (no internal deps)
|
|
215
|
+
require_relative "smith/guardrails"
|
|
216
|
+
require_relative "smith/guardrails/runner"
|
|
217
|
+
require_relative "smith/guardrails/url_verifier"
|
|
218
|
+
require_relative "smith/context"
|
|
219
|
+
require_relative "smith/context/observation_masking"
|
|
220
|
+
require_relative "smith/context/state_injection"
|
|
221
|
+
require_relative "smith/context/session"
|
|
222
|
+
|
|
223
|
+
# Agent (depends on RubyLLM::Agent)
|
|
224
|
+
require_relative "smith/agent"
|
|
225
|
+
require_relative "smith/agent/lifecycle"
|
|
226
|
+
require_relative "smith/agent/registry"
|
|
227
|
+
|
|
228
|
+
# Workflow (Transition, DSL, Persistence, and Execution must load before Workflow)
|
|
229
|
+
require_relative "smith/workflow/transition"
|
|
230
|
+
require_relative "smith/workflow/dsl"
|
|
231
|
+
require_relative "smith/workflow/persistence"
|
|
232
|
+
require_relative "smith/workflow/durability"
|
|
233
|
+
require_relative "smith/workflow/guardrail_integration"
|
|
234
|
+
require_relative "smith/workflow/budget_integration"
|
|
235
|
+
require_relative "smith/workflow/event_integration"
|
|
236
|
+
require_relative "smith/workflow/artifact_integration"
|
|
237
|
+
require_relative "smith/workflow/data_volume_policy"
|
|
238
|
+
require_relative "smith/workflow/deadline_enforcement"
|
|
239
|
+
require_relative "smith/workflow/nested_execution"
|
|
240
|
+
require_relative "smith/workflow/evaluator_optimizer"
|
|
241
|
+
require_relative "smith/workflow/orchestrator_worker"
|
|
242
|
+
require_relative "smith/workflow/parallel_execution"
|
|
243
|
+
require_relative "smith/workflow/deterministic_step"
|
|
244
|
+
require_relative "smith/workflow/deterministic_execution"
|
|
245
|
+
require_relative "smith/workflow/execution"
|
|
246
|
+
require_relative "smith/workflow"
|
|
247
|
+
require_relative "smith/workflow/claim"
|
|
248
|
+
require_relative "smith/workflow/execution_frame"
|
|
249
|
+
require_relative "smith/workflow/pipeline"
|
|
250
|
+
require_relative "smith/workflow/router"
|
|
251
|
+
require_relative "smith/workflow/parallel"
|
|
252
|
+
|
|
253
|
+
# Conditional Rails integration
|
|
254
|
+
require_relative "smith/railtie" if defined?(Rails::Railtie)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Usage: bundle exec ruby script/profile_tool_results.rb
|
|
5
|
+
#
|
|
6
|
+
# Profiles tool_results handling at various scales:
|
|
7
|
+
# - snapshot cost (build_run_result deep copy)
|
|
8
|
+
# - JSON persistence round-trip (serialize + restore)
|
|
9
|
+
# - parallel collector contention
|
|
10
|
+
|
|
11
|
+
require "smith"
|
|
12
|
+
require "json"
|
|
13
|
+
|
|
14
|
+
def measure
|
|
15
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
16
|
+
yield
|
|
17
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build_workflow_with_entries(count, payload_size: 100)
|
|
21
|
+
klass = Class.new(Smith::Workflow) do
|
|
22
|
+
initial_state :idle
|
|
23
|
+
state :done
|
|
24
|
+
transition :finish, from: :idle, to: :done
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
workflow = klass.new
|
|
28
|
+
payload = { "data" => "x" * payload_size, "urls" => Array.new(5) { "https://example.com/#{_1}" } }
|
|
29
|
+
count.times { |i| workflow.instance_variable_get(:@tool_results) << { tool: "tool_#{i}", captured: payload.dup } }
|
|
30
|
+
[klass, workflow]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
puts "=" * 70
|
|
34
|
+
puts "Smith tool_results Performance Profile"
|
|
35
|
+
puts "=" * 70
|
|
36
|
+
|
|
37
|
+
# 1. Snapshot cost
|
|
38
|
+
puts "\n--- Snapshot Cost (build_run_result deep copy) ---"
|
|
39
|
+
[100, 1_000, 5_000].each do |count|
|
|
40
|
+
_klass, workflow = build_workflow_with_entries(count)
|
|
41
|
+
|
|
42
|
+
before_gc = GC.stat[:total_allocated_objects]
|
|
43
|
+
time = measure { workflow.run! }
|
|
44
|
+
after_gc = GC.stat[:total_allocated_objects]
|
|
45
|
+
|
|
46
|
+
printf " %5d entries: %.4fs, ~%d allocations\n", count, time, (after_gc - before_gc)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 2. JSON persistence round-trip
|
|
50
|
+
puts "\n--- JSON Persistence Round-Trip ---"
|
|
51
|
+
json = nil
|
|
52
|
+
[100, 1_000, 5_000].each do |count|
|
|
53
|
+
klass, workflow = build_workflow_with_entries(count)
|
|
54
|
+
|
|
55
|
+
serialize_time = measure { json = JSON.generate(workflow.to_state) }
|
|
56
|
+
restore_time = measure { klass.from_state(JSON.parse(json)) }
|
|
57
|
+
|
|
58
|
+
printf " %5d entries: serialize=%.4fs restore=%.4fs json_bytes=%d\n",
|
|
59
|
+
count, serialize_time, restore_time, json.bytesize
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# 3. Parallel collector contention
|
|
63
|
+
puts "\n--- Parallel Collector Contention ---"
|
|
64
|
+
[10, 50, 100].each do |branch_count|
|
|
65
|
+
klass = Class.new(Smith::Workflow) do
|
|
66
|
+
initial_state :idle
|
|
67
|
+
state :done
|
|
68
|
+
transition :finish, from: :idle, to: :done
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
workflow = klass.new
|
|
72
|
+
collector = workflow.send(:tool_result_collector)
|
|
73
|
+
threads = []
|
|
74
|
+
|
|
75
|
+
time = measure do
|
|
76
|
+
branch_count.times do |i|
|
|
77
|
+
threads << Thread.new do
|
|
78
|
+
5.times do |j|
|
|
79
|
+
collector.call({ tool: "branch_#{i}_call_#{j}", captured: { index: i, call: j } })
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
threads.each(&:join)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
total = workflow.instance_variable_get(:@tool_results).length
|
|
87
|
+
expected = branch_count * 5
|
|
88
|
+
|
|
89
|
+
status = total == expected ? "OK" : "LOSS (#{expected - total} missing)"
|
|
90
|
+
printf " %3d branches x 5 calls: %.4fs, %d/%d entries [%s]\n",
|
|
91
|
+
branch_count, time, total, expected, status
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
puts "\n#{"=" * 70}\nDone."
|
data/sig/smith.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: smith-agents
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.4.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Samuel Ralak
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ruby_llm
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.15'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.15'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: dry-types
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.7'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.7'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: dry-struct
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.6'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.6'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: dry-initializer
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.1'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.1'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: dry-configurable
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.0'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: dry-container
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0.11'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0.11'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: concurrent-ruby
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '1.2'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '1.2'
|
|
110
|
+
description: Smith is a workflow-first multi-agent orchestration library built on
|
|
111
|
+
RubyLLM. It provides state machine modeling, typed contracts, budget enforcement,
|
|
112
|
+
guardrails, and observability for agent workflows.
|
|
113
|
+
email:
|
|
114
|
+
- thesamuelralak@gmail.com
|
|
115
|
+
executables:
|
|
116
|
+
- smith
|
|
117
|
+
extensions: []
|
|
118
|
+
extra_rdoc_files: []
|
|
119
|
+
files:
|
|
120
|
+
- CHANGELOG.md
|
|
121
|
+
- CODE_OF_CONDUCT.md
|
|
122
|
+
- LICENSE
|
|
123
|
+
- README.md
|
|
124
|
+
- Rakefile
|
|
125
|
+
- UPSTREAM_PROPOSAL.md
|
|
126
|
+
- docs/CONFIGURATION.md
|
|
127
|
+
- docs/PATTERNS.md
|
|
128
|
+
- docs/PERSISTENCE.md
|
|
129
|
+
- docs/TOOLS_AND_GUARDRAILS.md
|
|
130
|
+
- docs/workflow_claim.md
|
|
131
|
+
- exe/smith
|
|
132
|
+
- lib/generators/smith/install/install_generator.rb
|
|
133
|
+
- lib/generators/smith/install/templates/smith.rb.tt
|
|
134
|
+
- lib/smith.rb
|
|
135
|
+
- lib/smith/agent.rb
|
|
136
|
+
- lib/smith/agent/lifecycle.rb
|
|
137
|
+
- lib/smith/agent/registry.rb
|
|
138
|
+
- lib/smith/artifacts.rb
|
|
139
|
+
- lib/smith/artifacts/file.rb
|
|
140
|
+
- lib/smith/artifacts/memory.rb
|
|
141
|
+
- lib/smith/artifacts/scoped_store.rb
|
|
142
|
+
- lib/smith/budget.rb
|
|
143
|
+
- lib/smith/budget/ledger.rb
|
|
144
|
+
- lib/smith/cli.rb
|
|
145
|
+
- lib/smith/context.rb
|
|
146
|
+
- lib/smith/context/observation_masking.rb
|
|
147
|
+
- lib/smith/context/session.rb
|
|
148
|
+
- lib/smith/context/state_injection.rb
|
|
149
|
+
- lib/smith/doctor.rb
|
|
150
|
+
- lib/smith/doctor/check.rb
|
|
151
|
+
- lib/smith/doctor/checks/baseline.rb
|
|
152
|
+
- lib/smith/doctor/checks/configuration.rb
|
|
153
|
+
- lib/smith/doctor/checks/durability.rb
|
|
154
|
+
- lib/smith/doctor/checks/live.rb
|
|
155
|
+
- lib/smith/doctor/checks/models_registry.rb
|
|
156
|
+
- lib/smith/doctor/checks/openai_api_mode.rb
|
|
157
|
+
- lib/smith/doctor/checks/persistence.rb
|
|
158
|
+
- lib/smith/doctor/checks/persistence_capabilities.rb
|
|
159
|
+
- lib/smith/doctor/checks/persistence_registry.rb
|
|
160
|
+
- lib/smith/doctor/checks/rails.rb
|
|
161
|
+
- lib/smith/doctor/checks/serialization.rb
|
|
162
|
+
- lib/smith/doctor/installer.rb
|
|
163
|
+
- lib/smith/doctor/printer.rb
|
|
164
|
+
- lib/smith/doctor/report.rb
|
|
165
|
+
- lib/smith/errors.rb
|
|
166
|
+
- lib/smith/event.rb
|
|
167
|
+
- lib/smith/events.rb
|
|
168
|
+
- lib/smith/events/.keep
|
|
169
|
+
- lib/smith/events/bus.rb
|
|
170
|
+
- lib/smith/events/step_completed.rb
|
|
171
|
+
- lib/smith/events/subscription.rb
|
|
172
|
+
- lib/smith/guardrails.rb
|
|
173
|
+
- lib/smith/guardrails/runner.rb
|
|
174
|
+
- lib/smith/guardrails/url_verifier.rb
|
|
175
|
+
- lib/smith/models.rb
|
|
176
|
+
- lib/smith/models/inference.rb
|
|
177
|
+
- lib/smith/models/normalizer.rb
|
|
178
|
+
- lib/smith/models/profile.rb
|
|
179
|
+
- lib/smith/persistence_adapters.rb
|
|
180
|
+
- lib/smith/persistence_adapters/active_record_store.rb
|
|
181
|
+
- lib/smith/persistence_adapters/cache_store.rb
|
|
182
|
+
- lib/smith/persistence_adapters/memory.rb
|
|
183
|
+
- lib/smith/persistence_adapters/rails_cache.rb
|
|
184
|
+
- lib/smith/persistence_adapters/redis_store.rb
|
|
185
|
+
- lib/smith/persistence_adapters/retry.rb
|
|
186
|
+
- lib/smith/pricing.rb
|
|
187
|
+
- lib/smith/providers/openai/responses.rb
|
|
188
|
+
- lib/smith/providers/openai/routing.rb
|
|
189
|
+
- lib/smith/providers/openai/tools_extensions.rb
|
|
190
|
+
- lib/smith/railtie.rb
|
|
191
|
+
- lib/smith/tasks/doctor.rake
|
|
192
|
+
- lib/smith/tool.rb
|
|
193
|
+
- lib/smith/tool/budget_enforcement.rb
|
|
194
|
+
- lib/smith/tool/capability_builder.rb
|
|
195
|
+
- lib/smith/tool/capture.rb
|
|
196
|
+
- lib/smith/tool/compatibility.rb
|
|
197
|
+
- lib/smith/tool/policy.rb
|
|
198
|
+
- lib/smith/tools.rb
|
|
199
|
+
- lib/smith/tools/think.rb
|
|
200
|
+
- lib/smith/tools/url_fetcher.rb
|
|
201
|
+
- lib/smith/tools/web_search.rb
|
|
202
|
+
- lib/smith/trace.rb
|
|
203
|
+
- lib/smith/trace/logger.rb
|
|
204
|
+
- lib/smith/trace/memory.rb
|
|
205
|
+
- lib/smith/trace/open_telemetry.rb
|
|
206
|
+
- lib/smith/types.rb
|
|
207
|
+
- lib/smith/version.rb
|
|
208
|
+
- lib/smith/workflow.rb
|
|
209
|
+
- lib/smith/workflow/artifact_integration.rb
|
|
210
|
+
- lib/smith/workflow/budget_integration.rb
|
|
211
|
+
- lib/smith/workflow/claim.rb
|
|
212
|
+
- lib/smith/workflow/data_volume_policy.rb
|
|
213
|
+
- lib/smith/workflow/deadline_enforcement.rb
|
|
214
|
+
- lib/smith/workflow/deterministic_execution.rb
|
|
215
|
+
- lib/smith/workflow/deterministic_step.rb
|
|
216
|
+
- lib/smith/workflow/dsl.rb
|
|
217
|
+
- lib/smith/workflow/durability.rb
|
|
218
|
+
- lib/smith/workflow/evaluator_optimizer.rb
|
|
219
|
+
- lib/smith/workflow/event_integration.rb
|
|
220
|
+
- lib/smith/workflow/execution.rb
|
|
221
|
+
- lib/smith/workflow/execution_frame.rb
|
|
222
|
+
- lib/smith/workflow/guardrail_integration.rb
|
|
223
|
+
- lib/smith/workflow/nested_execution.rb
|
|
224
|
+
- lib/smith/workflow/orchestrator_worker.rb
|
|
225
|
+
- lib/smith/workflow/parallel.rb
|
|
226
|
+
- lib/smith/workflow/parallel_execution.rb
|
|
227
|
+
- lib/smith/workflow/persistence.rb
|
|
228
|
+
- lib/smith/workflow/pipeline.rb
|
|
229
|
+
- lib/smith/workflow/router.rb
|
|
230
|
+
- lib/smith/workflow/transition.rb
|
|
231
|
+
- script/profile_tool_results.rb
|
|
232
|
+
- sig/smith.rbs
|
|
233
|
+
homepage: https://github.com/samuelralak/smith
|
|
234
|
+
licenses:
|
|
235
|
+
- MIT
|
|
236
|
+
metadata:
|
|
237
|
+
source_code_uri: https://github.com/samuelralak/smith
|
|
238
|
+
changelog_uri: https://github.com/samuelralak/smith/blob/main/CHANGELOG.md
|
|
239
|
+
bug_tracker_uri: https://github.com/samuelralak/smith/issues
|
|
240
|
+
rubygems_mfa_required: 'true'
|
|
241
|
+
rdoc_options: []
|
|
242
|
+
require_paths:
|
|
243
|
+
- lib
|
|
244
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
245
|
+
requirements:
|
|
246
|
+
- - ">="
|
|
247
|
+
- !ruby/object:Gem::Version
|
|
248
|
+
version: 3.2.0
|
|
249
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
250
|
+
requirements:
|
|
251
|
+
- - ">="
|
|
252
|
+
- !ruby/object:Gem::Version
|
|
253
|
+
version: '0'
|
|
254
|
+
requirements: []
|
|
255
|
+
rubygems_version: 4.0.3
|
|
256
|
+
specification_version: 4
|
|
257
|
+
summary: Workflow-first multi-agent orchestration for Ruby
|
|
258
|
+
test_files: []
|