smith-agents 0.4.2 → 0.4.4
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 +4 -4
- data/CHANGELOG.md +81 -3
- data/README.md +45 -2
- data/UPSTREAM_PROPOSAL.md +19 -0
- data/docs/PATTERNS.md +41 -1
- data/lib/smith/agent/lifecycle.rb +30 -10
- data/lib/smith/agent/registry/introspection.rb +27 -0
- data/lib/smith/agent/registry.rb +22 -17
- data/lib/smith/agent/registry_binding.rb +43 -0
- data/lib/smith/agent.rb +3 -4
- data/lib/smith/doctor/checks/models_registry.rb +25 -14
- data/lib/smith/doctor/checks/persistence_capabilities.rb +37 -29
- data/lib/smith/version.rb +1 -1
- data/lib/smith/workflow/agent_result.rb +26 -0
- data/lib/smith/workflow/branch_env.rb +24 -0
- data/lib/smith/workflow/budget_integration.rb +2 -0
- data/lib/smith/workflow/deterministic_execution.rb +1 -1
- data/lib/smith/workflow/deterministic_step.rb +32 -4
- data/lib/smith/workflow/durability.rb +38 -12
- data/lib/smith/workflow/evaluator_optimizer.rb +5 -8
- data/lib/smith/workflow/event_integration.rb +3 -3
- data/lib/smith/workflow/execution.rb +2 -0
- data/lib/smith/workflow/fanout_execution.rb +2 -0
- data/lib/smith/workflow/graph/contract_helpers.rb +65 -0
- data/lib/smith/workflow/graph/fanout_contract.rb +140 -0
- data/lib/smith/workflow/graph/nested_readiness_diagnostics.rb +98 -0
- data/lib/smith/workflow/graph/optimization_contract.rb +96 -0
- data/lib/smith/workflow/graph/orchestration_contract.rb +83 -0
- data/lib/smith/workflow/graph/runtime_binding_diagnostic_builder.rb +124 -0
- data/lib/smith/workflow/graph/runtime_binding_diagnostics.rb +114 -0
- data/lib/smith/workflow/graph/runtime_readiness.rb +63 -0
- data/lib/smith/workflow/graph/runtime_readiness_metrics.rb +87 -0
- data/lib/smith/workflow/graph/runtime_readiness_report.rb +65 -0
- data/lib/smith/workflow/graph/targets.rb +5 -0
- data/lib/smith/workflow/graph/transition_diagnostics.rb +7 -0
- data/lib/smith/workflow/graph/transition_snapshot.rb +61 -20
- data/lib/smith/workflow/graph.rb +16 -1
- data/lib/smith/workflow/graph_dsl.rb +4 -0
- data/lib/smith/workflow/guardrail_integration.rb +20 -3
- data/lib/smith/workflow/nested_execution.rb +5 -4
- data/lib/smith/workflow/optimization_state.rb +13 -0
- data/lib/smith/workflow/orchestration_state.rb +13 -0
- data/lib/smith/workflow/orchestrator_worker.rb +3 -15
- data/lib/smith/workflow/parallel/cancellation_signal.rb +21 -0
- data/lib/smith/workflow/parallel.rb +6 -15
- data/lib/smith/workflow/parallel_execution.rb +2 -0
- data/lib/smith/workflow/persistence.rb +37 -6
- data/lib/smith/workflow/router.rb +15 -4
- data/lib/smith/workflow/run_result.rb +54 -0
- data/lib/smith/workflow/transition.rb +90 -4
- data/lib/smith/workflow/usage_entry.rb +30 -0
- data/lib/smith/workflow/worker_execution.rb +13 -0
- data/lib/smith/workflow.rb +40 -136
- data/lib/smith.rb +9 -0
- metadata +21 -1
|
@@ -5,7 +5,8 @@ module Smith
|
|
|
5
5
|
class Transition
|
|
6
6
|
attr_reader :name, :from, :to, :agent_name, :agent_opts, :success_transition, :failure_transition,
|
|
7
7
|
:router_config, :workflow_class, :optimization_config, :orchestrator_config,
|
|
8
|
-
:fanout_config, :retry_config, :deterministic_block, :deterministic_kind
|
|
8
|
+
:fanout_config, :retry_config, :deterministic_block, :deterministic_kind,
|
|
9
|
+
:deterministic_routes
|
|
9
10
|
|
|
10
11
|
def initialize(name, from:, to:, &)
|
|
11
12
|
@name = name
|
|
@@ -32,8 +33,12 @@ module Smith
|
|
|
32
33
|
def route(agent_name, routes:, confidence_threshold:, fallback:)
|
|
33
34
|
validate_route_conflicts!
|
|
34
35
|
|
|
35
|
-
@agent_name = agent_name
|
|
36
|
-
@router_config = {
|
|
36
|
+
@agent_name = normalize_agent_name!(agent_name, "router")
|
|
37
|
+
@router_config = {
|
|
38
|
+
routes: normalize_router_routes!(routes),
|
|
39
|
+
confidence_threshold: normalize_confidence_threshold!(confidence_threshold),
|
|
40
|
+
fallback: normalize_transition_reference!(fallback, "router fallback")
|
|
41
|
+
}
|
|
37
42
|
end
|
|
38
43
|
|
|
39
44
|
def workflow(klass)
|
|
@@ -104,12 +109,13 @@ module Smith
|
|
|
104
109
|
end
|
|
105
110
|
|
|
106
111
|
%i[compute run].each do |method_name|
|
|
107
|
-
define_method(method_name) do
|
|
112
|
+
define_method(method_name) do |routes: nil, &block|
|
|
108
113
|
validate_deterministic_conflicts!
|
|
109
114
|
raise WorkflowError, "#{method_name} requires a block" unless block
|
|
110
115
|
|
|
111
116
|
@deterministic_block = block
|
|
112
117
|
@deterministic_kind = method_name
|
|
118
|
+
@deterministic_routes = normalize_deterministic_routes!(routes)
|
|
113
119
|
end
|
|
114
120
|
end
|
|
115
121
|
|
|
@@ -241,6 +247,57 @@ module Smith
|
|
|
241
247
|
normalized.freeze
|
|
242
248
|
end
|
|
243
249
|
|
|
250
|
+
def normalize_agent_name!(agent_name, label)
|
|
251
|
+
value = agent_name.to_s.strip
|
|
252
|
+
raise WorkflowError, "#{label} agent must not be blank" if value.empty?
|
|
253
|
+
|
|
254
|
+
value.to_sym
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def normalize_router_routes!(routes)
|
|
258
|
+
raise WorkflowError, "router routes must be a Hash" unless routes.is_a?(Hash)
|
|
259
|
+
raise WorkflowError, "router routes must not be empty" if routes.empty?
|
|
260
|
+
|
|
261
|
+
routes.each_with_object({}) do |(route_key, transition_name), map|
|
|
262
|
+
key = normalize_router_route_key!(route_key)
|
|
263
|
+
raise WorkflowError, "router route :#{key} is duplicated" if map.key?(key)
|
|
264
|
+
|
|
265
|
+
map[key] = normalize_transition_reference!(transition_name, "router route :#{key}")
|
|
266
|
+
end.freeze
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def normalize_router_route_key!(route_key)
|
|
270
|
+
value = route_key.to_s.strip
|
|
271
|
+
raise WorkflowError, "router route keys must not be blank" if value.empty?
|
|
272
|
+
|
|
273
|
+
value.to_sym
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def normalize_confidence_threshold!(threshold)
|
|
277
|
+
numeric = Float(threshold)
|
|
278
|
+
return numeric if numeric >= 0.0 && numeric <= 1.0
|
|
279
|
+
|
|
280
|
+
raise WorkflowError, "router confidence_threshold must be a number in 0.0..1.0"
|
|
281
|
+
rescue TypeError, ArgumentError
|
|
282
|
+
raise WorkflowError, "router confidence_threshold must be a number in 0.0..1.0"
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def normalize_transition_reference!(transition_name, label)
|
|
286
|
+
case transition_name
|
|
287
|
+
when Symbol
|
|
288
|
+
raise WorkflowError, "#{label} transition must not be blank" if transition_name.to_s.empty?
|
|
289
|
+
|
|
290
|
+
transition_name
|
|
291
|
+
when String
|
|
292
|
+
value = transition_name.strip
|
|
293
|
+
raise WorkflowError, "#{label} transition must not be blank" if value.empty?
|
|
294
|
+
|
|
295
|
+
value.freeze
|
|
296
|
+
else
|
|
297
|
+
raise WorkflowError, "#{label} transition must be String or Symbol"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
244
301
|
def normalize_fanout_branch_key!(branch_key)
|
|
245
302
|
key = branch_key.to_s.strip
|
|
246
303
|
raise WorkflowError, "fan_out branch keys must not be blank" if key.empty?
|
|
@@ -278,6 +335,35 @@ module Smith
|
|
|
278
335
|
validate_non_negative_numeric!(:max_delay, max_delay) unless max_delay.nil?
|
|
279
336
|
end
|
|
280
337
|
|
|
338
|
+
def normalize_deterministic_routes!(routes)
|
|
339
|
+
return nil if routes.nil?
|
|
340
|
+
raise WorkflowError, "deterministic routes must be an Array" unless routes.is_a?(Array)
|
|
341
|
+
raise WorkflowError, "deterministic routes must not be empty" if routes.empty?
|
|
342
|
+
|
|
343
|
+
routes.each_with_object([]) do |route, list|
|
|
344
|
+
name = normalize_deterministic_route!(route)
|
|
345
|
+
raise WorkflowError, "deterministic route #{name.inspect} is duplicated" if list.include?(name)
|
|
346
|
+
|
|
347
|
+
list << name
|
|
348
|
+
end.freeze
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def normalize_deterministic_route!(route)
|
|
352
|
+
case route
|
|
353
|
+
when Symbol
|
|
354
|
+
raise WorkflowError, "deterministic route names must not be blank" if route.to_s.empty?
|
|
355
|
+
|
|
356
|
+
route
|
|
357
|
+
when String
|
|
358
|
+
value = route.strip
|
|
359
|
+
raise WorkflowError, "deterministic route names must not be blank" if value.empty?
|
|
360
|
+
|
|
361
|
+
value.freeze
|
|
362
|
+
else
|
|
363
|
+
raise WorkflowError, "deterministic route names must be String or Symbol"
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
281
367
|
def validate_non_negative_numeric!(name, value)
|
|
282
368
|
numeric = Float(value)
|
|
283
369
|
return if numeric >= 0.0
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
# One row per agent provider call. `usage_id` is a UUID generated at
|
|
6
|
+
# recording time and stable across persist/restore so hosts can use it as an
|
|
7
|
+
# idempotency anchor.
|
|
8
|
+
# rubocop:disable Style/RedundantStructKeywordInit
|
|
9
|
+
UsageEntry = Struct.new(
|
|
10
|
+
:usage_id,
|
|
11
|
+
:agent_name,
|
|
12
|
+
:model,
|
|
13
|
+
:input_tokens,
|
|
14
|
+
:output_tokens,
|
|
15
|
+
:cost,
|
|
16
|
+
:attempt_kind,
|
|
17
|
+
:recorded_at,
|
|
18
|
+
keyword_init: true
|
|
19
|
+
) do
|
|
20
|
+
def self.from_h(hash)
|
|
21
|
+
sym = hash.transform_keys(&:to_sym)
|
|
22
|
+
filtered = sym.slice(*members)
|
|
23
|
+
filtered[:agent_name] = filtered[:agent_name].to_sym if filtered[:agent_name].is_a?(String)
|
|
24
|
+
filtered[:attempt_kind] = filtered[:attempt_kind].to_sym if filtered[:attempt_kind].is_a?(String)
|
|
25
|
+
new(**filtered)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
# rubocop:enable Style/RedundantStructKeywordInit
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Smith
|
|
6
|
+
class Workflow
|
|
7
|
+
WorkerExecution = Struct.new(:execution_id, :task, :output) do
|
|
8
|
+
def self.run(worker_class, task, schema, budget_runner)
|
|
9
|
+
new(SecureRandom.uuid, task, budget_runner.call(worker_class, task, schema))
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/smith/workflow.rb
CHANGED
|
@@ -6,6 +6,11 @@ require "securerandom"
|
|
|
6
6
|
require "set"
|
|
7
7
|
require "time"
|
|
8
8
|
|
|
9
|
+
require_relative "workflow/agent_result"
|
|
10
|
+
require_relative "workflow/branch_env"
|
|
11
|
+
require_relative "workflow/run_result"
|
|
12
|
+
require_relative "workflow/usage_entry"
|
|
13
|
+
|
|
9
14
|
module Smith
|
|
10
15
|
class Workflow
|
|
11
16
|
include DSL
|
|
@@ -21,113 +26,6 @@ module Smith
|
|
|
21
26
|
|
|
22
27
|
DEFAULT_MAX_TRANSITIONS = 100
|
|
23
28
|
|
|
24
|
-
# `keyword_init: true` is mandatory: `build_run_result` constructs
|
|
25
|
-
# the result via keyword arguments. Plain Ruby Structs treat the
|
|
26
|
-
# kwargs hash as the first positional field, silently leaving the
|
|
27
|
-
# remaining fields nil — verified empirically. The `keyword_init`
|
|
28
|
-
# flag routes kwargs to the right fields. `usage_entries` is the
|
|
29
|
-
# 10th field, added in this slice for hadithi billing.
|
|
30
|
-
RunResult = Struct.new(:state, :output, :steps, :total_cost, :total_tokens, :context, :session_messages,
|
|
31
|
-
:tool_results, :outcome, :usage_entries, keyword_init: true) do
|
|
32
|
-
def done?
|
|
33
|
-
state == :done
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def failed?
|
|
37
|
-
state == :failed
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def terminal_output
|
|
41
|
-
output
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def outcome_kind
|
|
45
|
-
outcome&.dig(:kind)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def outcome_payload
|
|
49
|
-
outcome&.dig(:payload)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def last_error
|
|
53
|
-
steps.reverse.map { |step| step[:error] }.compact.first
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def failed_transition
|
|
57
|
-
failure_detail&.fetch(:transition)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def failure_detail
|
|
61
|
-
failed_step = steps.reverse.find { |step| step[:error] }
|
|
62
|
-
return nil unless failed_step
|
|
63
|
-
|
|
64
|
-
{
|
|
65
|
-
transition: failed_step[:transition],
|
|
66
|
-
from: failed_step[:from],
|
|
67
|
-
to: failed_step[:to],
|
|
68
|
-
error: failed_step[:error]
|
|
69
|
-
}
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# keyword_init: true so adding new fields in future schema versions
|
|
74
|
-
# remains backward-compatible: `from_response` and `from_h` can fill
|
|
75
|
-
# extra fields without breaking existing call sites that pass the
|
|
76
|
-
# historical positional args. Reading old persisted records into a
|
|
77
|
-
# newer Struct shape: from_h slices to current members (unknown keys
|
|
78
|
-
# silently dropped; missing keys default to nil).
|
|
79
|
-
AgentResult = Struct.new(
|
|
80
|
-
:content, :input_tokens, :output_tokens, :cost, :model_used,
|
|
81
|
-
keyword_init: true
|
|
82
|
-
) do
|
|
83
|
-
def self.from_response(response, content, model_used: nil)
|
|
84
|
-
new(
|
|
85
|
-
content: content,
|
|
86
|
-
input_tokens: response.respond_to?(:input_tokens) ? response.input_tokens : nil,
|
|
87
|
-
output_tokens: response.respond_to?(:output_tokens) ? response.output_tokens : nil,
|
|
88
|
-
cost: nil,
|
|
89
|
-
model_used: model_used
|
|
90
|
-
)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def usage_known?
|
|
94
|
-
!input_tokens.nil? && !output_tokens.nil?
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# One row per agent provider call. `usage_id` is a UUID generated
|
|
99
|
-
# at recording time and stable across persist/restore — hadithi
|
|
100
|
-
# uses it as the idempotency anchor on `usage_events.smith_usage_id`.
|
|
101
|
-
# Includes `to_h`/`from_h` for JSON serialization (plain Struct
|
|
102
|
-
# JSON-encodes to `"#<struct ...>"` — useless).
|
|
103
|
-
#
|
|
104
|
-
# keyword_init: true gives forward/backward compatibility:
|
|
105
|
-
# - Adding a new field: old persisted hashes restore cleanly (new
|
|
106
|
-
# field defaults to nil).
|
|
107
|
-
# - Reading new persisted hashes with an older Smith version: from_h
|
|
108
|
-
# slices to known members (unknown keys silently dropped).
|
|
109
|
-
UsageEntry = Struct.new(
|
|
110
|
-
:usage_id,
|
|
111
|
-
:agent_name,
|
|
112
|
-
:model,
|
|
113
|
-
:input_tokens,
|
|
114
|
-
:output_tokens,
|
|
115
|
-
:cost,
|
|
116
|
-
:attempt_kind,
|
|
117
|
-
:recorded_at,
|
|
118
|
-
keyword_init: true
|
|
119
|
-
) do
|
|
120
|
-
def self.from_h(hash)
|
|
121
|
-
sym = hash.transform_keys(&:to_sym)
|
|
122
|
-
filtered = sym.slice(*members)
|
|
123
|
-
# Symbolize :agent_name and :attempt_kind for backward compat
|
|
124
|
-
# with callers that consume the field as Symbol.
|
|
125
|
-
filtered[:agent_name] = filtered[:agent_name].to_sym if filtered[:agent_name].is_a?(String)
|
|
126
|
-
filtered[:attempt_kind] = filtered[:attempt_kind].to_sym if filtered[:attempt_kind].is_a?(String)
|
|
127
|
-
new(**filtered)
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
29
|
# Reconstruct Smith error classes from `@last_failed_step` snapshots.
|
|
132
30
|
# Order matters: more-specific subclasses first, so a real DSF doesn't
|
|
133
31
|
# get caught by the WorkflowError handler. Each lambda preserves the
|
|
@@ -167,24 +65,6 @@ module Smith
|
|
|
167
65
|
RETRYABLE_BEARING_FAMILIES = %w[deterministic_step_failure tool_guardrail_failed].freeze
|
|
168
66
|
private_constant :RETRYABLE_BEARING_FAMILIES
|
|
169
67
|
|
|
170
|
-
# keyword_init: true for forward/backward compat (see UsageEntry).
|
|
171
|
-
BranchEnv = Struct.new(
|
|
172
|
-
:prepared_input, :guardrail_sources, :scoped_store, :branch_estimates, :deadline,
|
|
173
|
-
keyword_init: true
|
|
174
|
-
) do
|
|
175
|
-
def setup_thread
|
|
176
|
-
Smith::Tool.current_guardrails = guardrail_sources
|
|
177
|
-
Smith::Tool.current_deadline = deadline
|
|
178
|
-
Smith.scoped_artifacts = scoped_store
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def teardown_thread
|
|
182
|
-
Smith::Tool.current_guardrails = nil
|
|
183
|
-
Smith::Tool.current_deadline = nil
|
|
184
|
-
Smith.scoped_artifacts = nil
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
68
|
attr_reader :state, :last_prepared_input, :session_messages, :ledger
|
|
189
69
|
|
|
190
70
|
def initialize(context: {}, ledger: nil, created_at: nil)
|
|
@@ -239,12 +119,13 @@ module Smith
|
|
|
239
119
|
end
|
|
240
120
|
|
|
241
121
|
def advance!
|
|
242
|
-
|
|
243
|
-
|
|
122
|
+
ensure_transition_budget!
|
|
123
|
+
@step_work_started = false
|
|
244
124
|
|
|
245
125
|
transition = resolve_transition
|
|
246
126
|
return if transition.nil?
|
|
247
127
|
|
|
128
|
+
@step_work_started = true
|
|
248
129
|
step_result = execute_step(transition)
|
|
249
130
|
@step_count += 1
|
|
250
131
|
@updated_at = Time.now.utc.iso8601
|
|
@@ -274,11 +155,11 @@ module Smith
|
|
|
274
155
|
end
|
|
275
156
|
|
|
276
157
|
def done?
|
|
277
|
-
|
|
158
|
+
state_named?(:done)
|
|
278
159
|
end
|
|
279
160
|
|
|
280
161
|
def failed?
|
|
281
|
-
|
|
162
|
+
state_named?(:failed)
|
|
282
163
|
end
|
|
283
164
|
|
|
284
165
|
def record_persisted_key!(key)
|
|
@@ -360,17 +241,40 @@ module Smith
|
|
|
360
241
|
true
|
|
361
242
|
end
|
|
362
243
|
|
|
244
|
+
def ensure_transition_budget!
|
|
245
|
+
max = self.class.max_transitions || DEFAULT_MAX_TRANSITIONS
|
|
246
|
+
raise MaxTransitionsExceeded if @step_count >= max
|
|
247
|
+
end
|
|
248
|
+
|
|
363
249
|
def resolve_transition
|
|
364
250
|
if @next_transition_name
|
|
365
251
|
name = @next_transition_name
|
|
366
252
|
@next_transition_name = nil
|
|
367
|
-
self.class.find_transition(name) ||
|
|
253
|
+
transition = self.class.find_transition(name) ||
|
|
368
254
|
raise(UnresolvedTransitionError.new(name, self.class, @state))
|
|
255
|
+
validate_transition_origin!(transition)
|
|
256
|
+
transition
|
|
369
257
|
else
|
|
370
258
|
self.class.transitions_from(@state).first
|
|
371
259
|
end
|
|
372
260
|
end
|
|
373
261
|
|
|
262
|
+
def validate_transition_origin!(transition)
|
|
263
|
+
expected = transition.from
|
|
264
|
+
return if expected.nil? || expected == @state
|
|
265
|
+
|
|
266
|
+
raise WorkflowError,
|
|
267
|
+
"transition :#{transition.name} cannot run from state :#{@state}; expected :#{expected}"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def step_work_started?
|
|
271
|
+
@step_work_started == true
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def state_named?(name)
|
|
275
|
+
@state == name || @state.to_s == name.to_s
|
|
276
|
+
end
|
|
277
|
+
|
|
374
278
|
def build_run_result(steps)
|
|
375
279
|
# `output` derivation matches existing semantics on fresh runs
|
|
376
280
|
# (last non-nil step output via `compact.first`). Terminal-restore
|
|
@@ -416,8 +320,8 @@ module Smith
|
|
|
416
320
|
# Skip const_get for retryable-bearing families. An unknown
|
|
417
321
|
# subclass with a message-only constructor would const_get
|
|
418
322
|
# successfully but discard the snapshot's `retryable`/`kind`/
|
|
419
|
-
# `details` (defaults to nil), and
|
|
420
|
-
#
|
|
323
|
+
# `details` (defaults to nil), and host retry classification
|
|
324
|
+
# could misclassify a retryable failure as terminal.
|
|
421
325
|
# Family fallback rebuilds the parent class with kwargs intact.
|
|
422
326
|
family_fallback(snap)
|
|
423
327
|
else
|
|
@@ -436,9 +340,9 @@ module Smith
|
|
|
436
340
|
# symbols; JSON round-trip stringifies them; coerce back to
|
|
437
341
|
# match fresh-run shape exactly.
|
|
438
342
|
{
|
|
439
|
-
transition: snap[:transition]
|
|
440
|
-
from: snap[:from]
|
|
441
|
-
to: snap[:to]
|
|
343
|
+
transition: normalize_transition_name(snap[:transition]),
|
|
344
|
+
from: normalize_state_name(snap[:from]),
|
|
345
|
+
to: normalize_state_name(snap[:to]),
|
|
442
346
|
error: error
|
|
443
347
|
}
|
|
444
348
|
end
|
|
@@ -519,7 +423,7 @@ module Smith
|
|
|
519
423
|
# `Struct#dup` is shallow — it shares mutable string fields between
|
|
520
424
|
# the original and the duplicate. Smith's existing snapshot helpers
|
|
521
425
|
# (`snapshot_context`, etc.) also use this round-trip pattern; the
|
|
522
|
-
#
|
|
426
|
+
# usage-facing RunResult must not alias mutable workflow state.
|
|
523
427
|
# Same rule applies to nested-workflow rollup (see
|
|
524
428
|
# `nested_execution.rb`).
|
|
525
429
|
def snapshot_usage_entries
|
data/lib/smith.rb
CHANGED
|
@@ -224,12 +224,20 @@ require_relative "smith/context/session"
|
|
|
224
224
|
# Agent (depends on RubyLLM::Agent)
|
|
225
225
|
require_relative "smith/agent"
|
|
226
226
|
require_relative "smith/agent/lifecycle"
|
|
227
|
+
require_relative "smith/agent/registry_binding"
|
|
227
228
|
require_relative "smith/agent/registry"
|
|
228
229
|
|
|
229
230
|
# Workflow (Transition, DSL, Persistence, and Execution must load before Workflow)
|
|
230
231
|
require_relative "smith/workflow/transition"
|
|
231
232
|
require_relative "smith/workflow/graph"
|
|
232
233
|
require_relative "smith/workflow/graph_dsl"
|
|
234
|
+
require_relative "smith/workflow/run_result"
|
|
235
|
+
require_relative "smith/workflow/agent_result"
|
|
236
|
+
require_relative "smith/workflow/usage_entry"
|
|
237
|
+
require_relative "smith/workflow/branch_env"
|
|
238
|
+
require_relative "smith/workflow/optimization_state"
|
|
239
|
+
require_relative "smith/workflow/orchestration_state"
|
|
240
|
+
require_relative "smith/workflow/worker_execution"
|
|
233
241
|
require_relative "smith/workflow/dsl"
|
|
234
242
|
require_relative "smith/workflow/persistence"
|
|
235
243
|
require_relative "smith/workflow/durability"
|
|
@@ -254,6 +262,7 @@ require_relative "smith/workflow/execution_frame"
|
|
|
254
262
|
require_relative "smith/workflow/pipeline"
|
|
255
263
|
require_relative "smith/workflow/router"
|
|
256
264
|
require_relative "smith/workflow/parallel"
|
|
265
|
+
require_relative "smith/workflow/parallel/cancellation_signal"
|
|
257
266
|
require_relative "smith/workflow/parallel/cancellation"
|
|
258
267
|
|
|
259
268
|
# Conditional Rails integration
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smith-agents
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Ralak
|
|
@@ -135,6 +135,8 @@ files:
|
|
|
135
135
|
- lib/smith/agent.rb
|
|
136
136
|
- lib/smith/agent/lifecycle.rb
|
|
137
137
|
- lib/smith/agent/registry.rb
|
|
138
|
+
- lib/smith/agent/registry/introspection.rb
|
|
139
|
+
- lib/smith/agent/registry_binding.rb
|
|
138
140
|
- lib/smith/artifacts.rb
|
|
139
141
|
- lib/smith/artifacts/file.rb
|
|
140
142
|
- lib/smith/artifacts/memory.rb
|
|
@@ -206,7 +208,9 @@ files:
|
|
|
206
208
|
- lib/smith/types.rb
|
|
207
209
|
- lib/smith/version.rb
|
|
208
210
|
- lib/smith/workflow.rb
|
|
211
|
+
- lib/smith/workflow/agent_result.rb
|
|
209
212
|
- lib/smith/workflow/artifact_integration.rb
|
|
213
|
+
- lib/smith/workflow/branch_env.rb
|
|
210
214
|
- lib/smith/workflow/budget_integration.rb
|
|
211
215
|
- lib/smith/workflow/claim.rb
|
|
212
216
|
- lib/smith/workflow/data_volume_policy.rb
|
|
@@ -221,12 +225,22 @@ files:
|
|
|
221
225
|
- lib/smith/workflow/execution_frame.rb
|
|
222
226
|
- lib/smith/workflow/fanout_execution.rb
|
|
223
227
|
- lib/smith/workflow/graph.rb
|
|
228
|
+
- lib/smith/workflow/graph/contract_helpers.rb
|
|
224
229
|
- lib/smith/workflow/graph/diagnostic.rb
|
|
230
|
+
- lib/smith/workflow/graph/fanout_contract.rb
|
|
225
231
|
- lib/smith/workflow/graph/metrics.rb
|
|
232
|
+
- lib/smith/workflow/graph/nested_readiness_diagnostics.rb
|
|
233
|
+
- lib/smith/workflow/graph/optimization_contract.rb
|
|
234
|
+
- lib/smith/workflow/graph/orchestration_contract.rb
|
|
226
235
|
- lib/smith/workflow/graph/reachability.rb
|
|
227
236
|
- lib/smith/workflow/graph/reachability_diagnostics.rb
|
|
228
237
|
- lib/smith/workflow/graph/reference.rb
|
|
229
238
|
- lib/smith/workflow/graph/report.rb
|
|
239
|
+
- lib/smith/workflow/graph/runtime_binding_diagnostic_builder.rb
|
|
240
|
+
- lib/smith/workflow/graph/runtime_binding_diagnostics.rb
|
|
241
|
+
- lib/smith/workflow/graph/runtime_readiness.rb
|
|
242
|
+
- lib/smith/workflow/graph/runtime_readiness_metrics.rb
|
|
243
|
+
- lib/smith/workflow/graph/runtime_readiness_report.rb
|
|
230
244
|
- lib/smith/workflow/graph/state_diagnostics.rb
|
|
231
245
|
- lib/smith/workflow/graph/targets.rb
|
|
232
246
|
- lib/smith/workflow/graph/transition_diagnostics.rb
|
|
@@ -235,15 +249,21 @@ files:
|
|
|
235
249
|
- lib/smith/workflow/graph_dsl.rb
|
|
236
250
|
- lib/smith/workflow/guardrail_integration.rb
|
|
237
251
|
- lib/smith/workflow/nested_execution.rb
|
|
252
|
+
- lib/smith/workflow/optimization_state.rb
|
|
253
|
+
- lib/smith/workflow/orchestration_state.rb
|
|
238
254
|
- lib/smith/workflow/orchestrator_worker.rb
|
|
239
255
|
- lib/smith/workflow/parallel.rb
|
|
240
256
|
- lib/smith/workflow/parallel/cancellation.rb
|
|
257
|
+
- lib/smith/workflow/parallel/cancellation_signal.rb
|
|
241
258
|
- lib/smith/workflow/parallel_execution.rb
|
|
242
259
|
- lib/smith/workflow/persistence.rb
|
|
243
260
|
- lib/smith/workflow/pipeline.rb
|
|
244
261
|
- lib/smith/workflow/retry_execution.rb
|
|
245
262
|
- lib/smith/workflow/router.rb
|
|
263
|
+
- lib/smith/workflow/run_result.rb
|
|
246
264
|
- lib/smith/workflow/transition.rb
|
|
265
|
+
- lib/smith/workflow/usage_entry.rb
|
|
266
|
+
- lib/smith/workflow/worker_execution.rb
|
|
247
267
|
- script/profile_tool_results.rb
|
|
248
268
|
- sig/smith.rbs
|
|
249
269
|
homepage: https://github.com/samuelralak/smith
|