smith-agents 0.11.0 → 0.12.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 +4 -4
- data/CHANGELOG.md +698 -0
- data/README.md +621 -15
- data/UPSTREAM_PROPOSAL.md +1 -0
- data/docs/CONFIGURATION.md +80 -14
- data/docs/PATTERNS.md +30 -13
- data/docs/PERSISTENCE.md +733 -5
- data/docs/TOOLS_AND_GUARDRAILS.md +24 -3
- data/lib/generators/smith/install/install_generator.rb +63 -2
- data/lib/generators/smith/install/templates/create_workflow_states.rb.tt +31 -0
- data/lib/generators/smith/install/templates/smith.rb.tt +39 -8
- data/lib/generators/smith/install/templates/workflow_state.rb.tt +19 -0
- data/lib/smith/agent/completion.rb +32 -6
- data/lib/smith/agent/completion_usage_recording.rb +23 -14
- data/lib/smith/agent/content_bytes.rb +98 -0
- data/lib/smith/agent/content_parts.rb +149 -0
- data/lib/smith/agent/invocation_preparation.rb +37 -14
- data/lib/smith/agent/lifecycle.rb +16 -5
- data/lib/smith/agent/observed_model.rb +29 -0
- data/lib/smith/agent/output_contract_check.rb +53 -0
- data/lib/smith/agent/output_integrity.rb +137 -0
- data/lib/smith/agent/output_policy.rb +92 -0
- data/lib/smith/agent/output_recovery.rb +129 -0
- data/lib/smith/agent/provider_attempt.rb +6 -0
- data/lib/smith/agent/provider_call_timing.rb +13 -4
- data/lib/smith/agent/provider_completion.rb +35 -32
- data/lib/smith/agent/provider_failure_handling.rb +5 -1
- data/lib/smith/agent/provider_request.rb +36 -0
- data/lib/smith/agent/provider_streaming.rb +155 -0
- data/lib/smith/agent/provider_usage.rb +70 -3
- data/lib/smith/agent/usage_entry_recording.rb +4 -2
- data/lib/smith/agent/usage_traces.rb +9 -2
- data/lib/smith/agent/usage_tracking.rb +64 -51
- data/lib/smith/agent.rb +5 -0
- data/lib/smith/artifacts/active_record_store.rb +79 -0
- data/lib/smith/artifacts/file.rb +45 -18
- data/lib/smith/artifacts/memory.rb +47 -36
- data/lib/smith/artifacts/s3.rb +126 -0
- data/lib/smith/artifacts/scoped_store.rb +55 -7
- data/lib/smith/artifacts/store_rules.rb +49 -0
- data/lib/smith/artifacts.rb +98 -1
- data/lib/smith/batch/message_shape.rb +36 -0
- data/lib/smith/batch.rb +166 -0
- data/lib/smith/budget/amount.rb +25 -0
- data/lib/smith/budget/continuation.rb +153 -0
- data/lib/smith/budget/host_ledger.rb +137 -0
- data/lib/smith/budget/ledger.rb +4 -2
- data/lib/smith/budget/ledger_state_transition.rb +7 -5
- data/lib/smith/budget/request_hold.rb +211 -0
- data/lib/smith/budget/spent_reservation.rb +18 -0
- data/lib/smith/child_run.rb +41 -0
- data/lib/smith/child_runs_failed.rb +56 -0
- data/lib/smith/content.rb +96 -0
- data/lib/smith/context/session.rb +14 -2
- data/lib/smith/context/state_injection.rb +10 -5
- data/lib/smith/context.rb +28 -0
- data/lib/smith/doctor/checks/artifacts.rb +103 -0
- data/lib/smith/doctor/checks/configuration.rb +0 -11
- data/lib/smith/doctor/checks/durability.rb +20 -18
- data/lib/smith/doctor/checks/state_model.rb +63 -0
- data/lib/smith/doctor/installer.rb +9 -2
- data/lib/smith/doctor.rb +4 -0
- data/lib/smith/errors.rb +249 -2
- data/lib/smith/idempotency_key/canonical.rb +74 -0
- data/lib/smith/idempotency_key.rb +145 -0
- data/lib/smith/launchers/inline.rb +46 -0
- data/lib/smith/message.rb +76 -0
- data/lib/smith/models/tool_routing.rb +47 -44
- data/lib/smith/output_failure.rb +130 -0
- data/lib/smith/persistence_adapters/active_record_exact_store.rb +43 -0
- data/lib/smith/pricing/catalog_validation.rb +91 -0
- data/lib/smith/pricing.rb +95 -64
- data/lib/smith/providers/anthropic/batches.rb +116 -0
- data/lib/smith/providers/batch_exchange.rb +77 -0
- data/lib/smith/providers/openai/responses.rb +45 -20
- data/lib/smith/providers/output_limit.rb +68 -0
- data/lib/smith/providers/request_holds.rb +139 -0
- data/lib/smith/providers/response_facts.rb +108 -0
- data/lib/smith/providers/schema_constraint_check.rb +141 -0
- data/lib/smith/providers/schema_constraints.rb +87 -0
- data/lib/smith/providers/schema_rules.rb +66 -0
- data/lib/smith/providers/stop_reason.rb +39 -0
- data/lib/smith/providers/stream_facts.rb +192 -0
- data/lib/smith/response_memo.rb +152 -0
- data/lib/smith/setting_constructors.rb +47 -0
- data/lib/smith/streaming/json_field.rb +136 -0
- data/lib/smith/streaming/json_string.rb +91 -0
- data/lib/smith/streaming/session.rb +77 -0
- data/lib/smith/streaming/think_tags.rb +67 -0
- data/lib/smith/streaming.rb +64 -0
- data/lib/smith/testing/checks.rb +58 -0
- data/lib/smith/testing/hooks.rb +85 -0
- data/lib/smith/testing/minitest.rb +37 -0
- data/lib/smith/testing/rspec.rb +52 -0
- data/lib/smith/testing/stub.rb +110 -0
- data/lib/smith/testing.rb +158 -0
- data/lib/smith/tool/scoped_context.rb +9 -12
- data/lib/smith/tool.rb +14 -0
- data/lib/smith/trace/logger.rb +1 -1
- data/lib/smith/trace/memory.rb +1 -1
- data/lib/smith/trace/open_telemetry.rb +1 -1
- data/lib/smith/trace.rb +11 -4
- data/lib/smith/untrusted.rb +74 -0
- data/lib/smith/version.rb +2 -2
- data/lib/smith/workflow/active_clock.rb +61 -0
- data/lib/smith/workflow/agent_result.rb +37 -0
- data/lib/smith/workflow/artifact_integration.rb +57 -7
- data/lib/smith/workflow/await_step.rb +42 -0
- data/lib/smith/workflow/batch_charges.rb +74 -0
- data/lib/smith/workflow/batch_execution.rb +149 -0
- data/lib/smith/workflow/batch_holds.rb +73 -0
- data/lib/smith/workflow/batch_wait.rb +134 -0
- data/lib/smith/workflow/budget_integration.rb +11 -5
- data/lib/smith/workflow/budget_pause.rb +51 -0
- data/lib/smith/workflow/child_run_declarations.rb +127 -0
- data/lib/smith/workflow/child_run_execution.rb +150 -0
- data/lib/smith/workflow/child_run_results.rb +55 -0
- data/lib/smith/workflow/composite/effects.rb +37 -6
- data/lib/smith/workflow/composite/error.rb +1 -1
- data/lib/smith/workflow/composite/error_evidence.rb +9 -9
- data/lib/smith/workflow/composite/input.rb +11 -4
- data/lib/smith/workflow/context_keys.rb +77 -0
- data/lib/smith/workflow/deadline_enforcement.rb +1 -1
- data/lib/smith/workflow/deterministic_execution.rb +61 -10
- data/lib/smith/workflow/deterministic_step.rb +55 -6
- data/lib/smith/workflow/dsl.rb +59 -0
- data/lib/smith/workflow/durability.rb +139 -30
- data/lib/smith/workflow/evaluator_optimizer.rb +12 -6
- data/lib/smith/workflow/event_integration.rb +9 -0
- data/lib/smith/workflow/execution.rb +30 -23
- data/lib/smith/workflow/execution_binding_resolution.rb +13 -0
- data/lib/smith/workflow/failure_reconstructor.rb +27 -4
- data/lib/smith/workflow/failure_record.rb +6 -1
- data/lib/smith/workflow/failure_record_validator.rb +11 -4
- data/lib/smith/workflow/fanout_execution.rb +5 -8
- data/lib/smith/workflow/graph/batch_diagnostic.rb +83 -0
- data/lib/smith/workflow/graph/branch_limit_diagnostic.rb +41 -0
- data/lib/smith/workflow/graph/budget_ledger_diagnostic.rb +126 -0
- data/lib/smith/workflow/graph/durable_retry_diagnostic.rb +50 -0
- data/lib/smith/workflow/graph/execution_successors.rb +16 -2
- data/lib/smith/workflow/graph/inherited_untrusted_diagnostics.rb +77 -0
- data/lib/smith/workflow/graph/isolated_input_diagnostic.rb +50 -0
- data/lib/smith/workflow/graph/nested_readiness_diagnostics.rb +43 -4
- data/lib/smith/workflow/graph/output_key_diagnostic.rb +53 -0
- data/lib/smith/workflow/graph/output_schema_diagnostic.rb +49 -0
- data/lib/smith/workflow/graph/retry_policy_diagnostic.rb +1 -0
- data/lib/smith/workflow/graph/run_launcher_diagnostic.rb +30 -0
- data/lib/smith/workflow/graph/runtime_binding_diagnostic_builder.rb +9 -3
- data/lib/smith/workflow/graph/runtime_binding_diagnostics.rb +7 -3
- data/lib/smith/workflow/graph/runtime_readiness_metrics.rb +8 -1
- data/lib/smith/workflow/graph/runtime_readiness_report_builder.rb +9 -2
- data/lib/smith/workflow/graph/runtime_readiness_traversal.rb +7 -4
- data/lib/smith/workflow/graph/stream_diagnostic.rb +95 -0
- data/lib/smith/workflow/graph/transition_contract.rb +19 -0
- data/lib/smith/workflow/graph/transition_contract_attributes.rb +25 -2
- data/lib/smith/workflow/graph/transition_contract_configurations.rb +24 -3
- data/lib/smith/workflow/graph/transition_diagnostics.rb +11 -24
- data/lib/smith/workflow/graph/transition_snapshot.rb +40 -39
- data/lib/smith/workflow/graph/untrusted_input_diagnostic.rb +57 -0
- data/lib/smith/workflow/graph/waiting_child_diagnostic.rb +24 -0
- data/lib/smith/workflow/graph.rb +12 -0
- data/lib/smith/workflow/guarded_step_execution.rb +48 -3
- data/lib/smith/workflow/message_batch.rb +6 -0
- data/lib/smith/workflow/message_roles.rb +25 -0
- data/lib/smith/workflow/message_value_normalizer.rb +2 -1
- data/lib/smith/workflow/nested_execution.rb +47 -4
- data/lib/smith/workflow/orchestrator_worker.rb +9 -3
- data/lib/smith/workflow/parallel_execution.rb +10 -0
- data/lib/smith/workflow/persistence.rb +136 -13
- data/lib/smith/workflow/remembered_calls.rb +92 -0
- data/lib/smith/workflow/retry_execution.rb +68 -9
- data/lib/smith/workflow/retry_wait.rb +134 -0
- data/lib/smith/workflow/split_step_persistence/checkpoint.rb +3 -0
- data/lib/smith/workflow/split_step_persistence/checkpoint_state.rb +7 -0
- data/lib/smith/workflow/split_step_persistence/composite_branch_execution.rb +4 -1
- data/lib/smith/workflow/split_step_persistence/composite_preparation.rb +3 -1
- data/lib/smith/workflow/split_step_persistence/dispatch_claim.rb +1 -0
- data/lib/smith/workflow/split_step_persistence/preparation.rb +5 -0
- data/lib/smith/workflow/split_step_persistence/state_snapshot.rb +1 -0
- data/lib/smith/workflow/step_completion.rb +36 -7
- data/lib/smith/workflow/step_context.rb +21 -0
- data/lib/smith/workflow/step_io.rb +132 -0
- data/lib/smith/workflow/step_stream.rb +52 -0
- data/lib/smith/workflow/thread_context_snapshot.rb +2 -0
- data/lib/smith/workflow/transition.rb +267 -13
- data/lib/smith/workflow/transition_actionability.rb +2 -0
- data/lib/smith/workflow/usage_entry.rb +24 -3
- data/lib/smith/workflow/wait_completion.rb +131 -0
- data/lib/smith/workflow/wait_execution.rb +180 -0
- data/lib/smith/workflow/wait_record.rb +157 -0
- data/lib/smith/workflow.rb +113 -17
- data/lib/smith.rb +101 -45
- metadata +88 -1
|
@@ -104,9 +104,26 @@ module Smith
|
|
|
104
104
|
return false if fallback_age.nil? || fallback_age < threshold_seconds
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
+
stuck_payload?(payload, now, threshold_seconds)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# A waiting run is not stuck until its deadline has passed by the
|
|
111
|
+
# threshold without the host expiring the wait; a run that waits is
|
|
112
|
+
# never terminal.
|
|
113
|
+
def stuck_payload?(payload, now, threshold_seconds)
|
|
114
|
+
wait_deadline = payload_wait_deadline(payload)
|
|
115
|
+
return (now - wait_deadline) > threshold_seconds if wait_deadline
|
|
116
|
+
|
|
107
117
|
!terminal_in_payload?(payload)
|
|
108
118
|
end
|
|
109
119
|
|
|
120
|
+
def payload_wait_deadline(payload)
|
|
121
|
+
deadline = JSON.parse(payload).dig("awaiting", "deadline_at")
|
|
122
|
+
deadline && Time.iso8601(deadline)
|
|
123
|
+
rescue JSON::ParserError, ArgumentError, TypeError
|
|
124
|
+
nil
|
|
125
|
+
end
|
|
126
|
+
|
|
110
127
|
def heartbeat_age(persistence_key:, adapter: Smith.persistence_adapter)
|
|
111
128
|
raise WorkflowError, "persistence_adapter is not configured" if adapter.nil?
|
|
112
129
|
|
|
@@ -121,6 +138,20 @@ module Smith
|
|
|
121
138
|
age_from_payload_updated_at(payload, Time.now.utc)
|
|
122
139
|
end
|
|
123
140
|
|
|
141
|
+
# Delivers `event` to the run stored under `key` (see
|
|
142
|
+
# Workflow#resume!) and stores the result with the version check.
|
|
143
|
+
# Returns `:resumed`, or `:stale` when no run is stored or it does not
|
|
144
|
+
# await this event and token.
|
|
145
|
+
def resume!(key, event:, token:, payload:, adapter: Smith.persistence_adapter)
|
|
146
|
+
stored_wait_call(key, adapter:, done: :resumed) { |workflow| workflow.resume!(event:, token:, payload:) }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Ends the stored run's wait if its deadline has passed at `now` (see
|
|
150
|
+
# Workflow#expire_wait!). The host's scheduler decides when to call it.
|
|
151
|
+
def expire_wait!(key, token:, now: Time.now.utc, adapter: Smith.persistence_adapter)
|
|
152
|
+
stored_wait_call(key, adapter:, done: :expired) { |workflow| workflow.expire_wait!(token:, now:) }
|
|
153
|
+
end
|
|
154
|
+
|
|
124
155
|
def run_persisted!(key: nil, context: {}, adapter: Smith.persistence_adapter, clear: :done, on_step: nil, **kwargs)
|
|
125
156
|
clear_policy = normalize_clear_policy(clear)
|
|
126
157
|
resolved_key = resolved_persistence_key(key:, context:)
|
|
@@ -133,6 +164,53 @@ module Smith
|
|
|
133
164
|
|
|
134
165
|
private
|
|
135
166
|
|
|
167
|
+
# Exactly one completion of a wait must win, so the store must check
|
|
168
|
+
# versions. A run caught mid-step (strict mode) can still take an
|
|
169
|
+
# early delivery for the await it is entering; anything else about it
|
|
170
|
+
# is refused as an unfinished step.
|
|
171
|
+
def stored_wait_call(key, adapter:, done:, &)
|
|
172
|
+
resolved_key = explicit_persistence_key!(key)
|
|
173
|
+
store = persistence_adapter!(adapter)
|
|
174
|
+
unless Smith::PersistenceAdapters.supports?(store, :store_versioned)
|
|
175
|
+
raise WorkflowError, "resume! and expire_wait! need a persistence adapter with store_versioned"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
payload = fetch_persisted_payload(resolved_key, adapter: store)
|
|
179
|
+
return :missing unless payload
|
|
180
|
+
|
|
181
|
+
stored_wait_result(JSON.parse(payload), resolved_key, store, done, &)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def stored_wait_result(state, key, store, done)
|
|
185
|
+
workflow, in_progress = restore_for_wait(state)
|
|
186
|
+
workflow.instance_variable_set(:@persistence_key, key)
|
|
187
|
+
mark_batch_completion!(workflow, key, store, in_progress)
|
|
188
|
+
result = yield(workflow)
|
|
189
|
+
raise in_progress if in_progress && result != :resumed && result != :failed
|
|
190
|
+
|
|
191
|
+
workflow.clear_step_in_progress!
|
|
192
|
+
workflow.persist!(key, adapter: store) if [done, :failed].include?(result)
|
|
193
|
+
result
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# A batch completion makes provider calls (a follow-up, a retry over
|
|
197
|
+
# the wire, a new batch), so in strict mode it runs as a marked step:
|
|
198
|
+
# stored as under way first, and a run caught in one is unfinished.
|
|
199
|
+
def mark_batch_completion!(workflow, key, store, in_progress)
|
|
200
|
+
return unless workflow.send(:batch_waiting?)
|
|
201
|
+
raise in_progress if in_progress
|
|
202
|
+
return unless idempotency_mode == :strict
|
|
203
|
+
|
|
204
|
+
workflow.mark_step_in_progress!
|
|
205
|
+
workflow.persist!(key, adapter: store)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def restore_for_wait(state)
|
|
209
|
+
[from_state(state), nil]
|
|
210
|
+
rescue Smith::StepInProgressOnRestore => e
|
|
211
|
+
[allocate.tap { |workflow| workflow.send(:restore_state, state, allow_step_in_progress: true) }, e]
|
|
212
|
+
end
|
|
213
|
+
|
|
136
214
|
def fetch_persisted_payload(key, adapter:)
|
|
137
215
|
persistence_adapter!(adapter).fetch(key)
|
|
138
216
|
end
|
|
@@ -158,25 +236,19 @@ module Smith
|
|
|
158
236
|
parsed = JSON.parse(payload)
|
|
159
237
|
state_name = parsed["state"] || parsed[:state]
|
|
160
238
|
class_name = parsed["class"] || parsed[:class]
|
|
161
|
-
next_transition = parsed["next_transition_name"] || parsed[:next_transition_name]
|
|
162
|
-
|
|
163
239
|
return false unless state_name && class_name
|
|
164
240
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
!klass.transition_from?(state) && next_transition.nil?
|
|
241
|
+
next_transition = parsed["next_transition_name"] || parsed[:next_transition_name]
|
|
242
|
+
terminal_payload_state?(Object.const_get(class_name), state_name, next_transition)
|
|
168
243
|
rescue JSON::ParserError, NameError, NoMethodError
|
|
169
244
|
false
|
|
170
245
|
end
|
|
171
246
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return symbolized if states.include?(symbolized)
|
|
178
|
-
|
|
179
|
-
state_name
|
|
247
|
+
# A state the class no longer knows can never advance or restore, so
|
|
248
|
+
# it is not terminal: an old one reports as stuck.
|
|
249
|
+
def terminal_payload_state?(klass, state_name, next_transition)
|
|
250
|
+
state = klass.resolve_state_name(state_name)
|
|
251
|
+
!state.nil? && !klass.transition_from?(state) && next_transition.nil?
|
|
180
252
|
end
|
|
181
253
|
|
|
182
254
|
def persistence_adapter!(adapter)
|
|
@@ -192,7 +264,7 @@ module Smith
|
|
|
192
264
|
raise WorkflowError, "persistence key is required unless workflow defines persistence_key" unless builder
|
|
193
265
|
|
|
194
266
|
derived = if builder.arity == 1
|
|
195
|
-
builder.call(context)
|
|
267
|
+
builder.call(ContextKeys.normalize_context(context))
|
|
196
268
|
else
|
|
197
269
|
builder.call
|
|
198
270
|
end
|
|
@@ -241,17 +313,19 @@ module Smith
|
|
|
241
313
|
end
|
|
242
314
|
|
|
243
315
|
def run_persisted!(key = nil, adapter: Smith.persistence_adapter, on_step: nil)
|
|
244
|
-
return build_run_result([]) if
|
|
316
|
+
return build_run_result([]) if nothing_to_advance?
|
|
245
317
|
|
|
246
318
|
resolved_key = resolve_persistence_key!(key)
|
|
247
319
|
steps = []
|
|
320
|
+
mark_active_step_start!(true)
|
|
248
321
|
persist!(resolved_key, adapter:)
|
|
249
322
|
|
|
250
|
-
until
|
|
323
|
+
until nothing_to_advance?
|
|
251
324
|
ensure_transition_budget!
|
|
252
325
|
|
|
253
326
|
if strict_idempotency?
|
|
254
|
-
mark_step_in_progress!
|
|
327
|
+
mark_step_in_progress! unless replay_safe_pending_step?
|
|
328
|
+
mark_active_step_start!(true)
|
|
255
329
|
persist!(resolved_key, adapter:)
|
|
256
330
|
end
|
|
257
331
|
|
|
@@ -264,6 +338,7 @@ module Smith
|
|
|
264
338
|
steps << step if step
|
|
265
339
|
|
|
266
340
|
clear_step_in_progress!
|
|
341
|
+
mark_active_step_start!(!nothing_to_advance?)
|
|
267
342
|
persist!(resolved_key, adapter:)
|
|
268
343
|
invoke_on_step_callback(step, on_step) if step
|
|
269
344
|
end
|
|
@@ -272,17 +347,15 @@ module Smith
|
|
|
272
347
|
end
|
|
273
348
|
|
|
274
349
|
def advance_persisted!(key = nil, adapter: Smith.persistence_adapter, on_step: nil)
|
|
275
|
-
return if
|
|
350
|
+
return if nothing_to_advance?
|
|
276
351
|
|
|
277
352
|
resolved_key = resolve_persistence_key!(key)
|
|
278
353
|
ensure_transition_budget!
|
|
279
|
-
mark_step_in_progress! if strict_idempotency?
|
|
354
|
+
mark_step_in_progress! if strict_idempotency? && !replay_safe_pending_step?
|
|
355
|
+
mark_active_step_start!(true)
|
|
280
356
|
persist!(resolved_key, adapter:)
|
|
281
357
|
step = advance!
|
|
282
|
-
|
|
283
|
-
persist!(resolved_key, adapter:) if step
|
|
284
|
-
invoke_on_step_callback(step, on_step) if step
|
|
285
|
-
step
|
|
358
|
+
finish_persisted_advance!(step, resolved_key, adapter, on_step)
|
|
286
359
|
rescue StandardError
|
|
287
360
|
clear_pre_step_marker!(resolved_key, adapter:) if defined?(resolved_key) && resolved_key
|
|
288
361
|
raise
|
|
@@ -296,12 +369,7 @@ module Smith
|
|
|
296
369
|
payload = JSON.generate(to_state.merge(persistence_version: next_version))
|
|
297
370
|
payload = yield(payload, next_version) if block_given?
|
|
298
371
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
# Increment ONLY after successful store. On PersistenceVersionConflict
|
|
302
|
-
# (raised by store_versioned), @persistence_version stays at the
|
|
303
|
-
# previous value so callers can rescue + restore + retry cleanly.
|
|
304
|
-
@persistence_version = next_version
|
|
372
|
+
store_payload!(store, resolved_key, payload, previous_version:)
|
|
305
373
|
self
|
|
306
374
|
end
|
|
307
375
|
|
|
@@ -311,6 +379,12 @@ module Smith
|
|
|
311
379
|
self
|
|
312
380
|
end
|
|
313
381
|
|
|
382
|
+
# A map, call or spawn launches through a launcher that is idempotent
|
|
383
|
+
# by contract, so strict mode lets its step run again after a crash.
|
|
384
|
+
def replay_safe_pending_step?
|
|
385
|
+
self.class.find_transition(pending_transition_name)&.child_runs? || false
|
|
386
|
+
end
|
|
387
|
+
|
|
314
388
|
def mark_step_in_progress!
|
|
315
389
|
@step_in_progress = true
|
|
316
390
|
end
|
|
@@ -321,6 +395,33 @@ module Smith
|
|
|
321
395
|
|
|
322
396
|
private
|
|
323
397
|
|
|
398
|
+
# Increment ONLY after successful store. On PersistenceVersionConflict
|
|
399
|
+
# (raised by store_versioned), @persistence_version stays at the
|
|
400
|
+
# previous value so callers can rescue + restore + retry cleanly. A
|
|
401
|
+
# streamed step's output reaches its sink once stored (StepStream); a
|
|
402
|
+
# store that fails tells the sink the step failed.
|
|
403
|
+
def store_payload!(store, key, payload, previous_version:)
|
|
404
|
+
begin
|
|
405
|
+
check_state_size!(key, payload)
|
|
406
|
+
dispatch_store!(store, key, payload, previous_version:)
|
|
407
|
+
rescue StandardError => e
|
|
408
|
+
StepStream.instance_method(:discard_streamed_output).bind_call(self, e.class)
|
|
409
|
+
raise
|
|
410
|
+
end
|
|
411
|
+
@persistence_version = previous_version + 1
|
|
412
|
+
StepStream.instance_method(:deliver_streamed_output).bind_call(self)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# The step, or the wait it entered, is stored, and the mark of a step
|
|
416
|
+
# under way is cleared.
|
|
417
|
+
def finish_persisted_advance!(step, key, adapter, on_step)
|
|
418
|
+
clear_step_in_progress!
|
|
419
|
+
mark_active_step_start!(false)
|
|
420
|
+
persist!(key, adapter:)
|
|
421
|
+
invoke_on_step_callback(step, on_step) if step
|
|
422
|
+
step
|
|
423
|
+
end
|
|
424
|
+
|
|
324
425
|
def strict_idempotency?
|
|
325
426
|
self.class.idempotency_mode == :strict
|
|
326
427
|
end
|
|
@@ -330,6 +431,7 @@ module Smith
|
|
|
330
431
|
return if step_work_started?
|
|
331
432
|
|
|
332
433
|
clear_step_in_progress!
|
|
434
|
+
mark_active_step_start!(false)
|
|
333
435
|
persist!(key, adapter:)
|
|
334
436
|
end
|
|
335
437
|
|
|
@@ -350,6 +452,13 @@ module Smith
|
|
|
350
452
|
store.record_heartbeat(key, **kwargs) if Smith::PersistenceAdapters.supports?(store, :record_heartbeat)
|
|
351
453
|
end
|
|
352
454
|
|
|
455
|
+
# A state past persistence_max_bytes is refused before anything is
|
|
456
|
+
# written, and before a split-step boundary counts a write as begun.
|
|
457
|
+
def check_state_size!(key, payload)
|
|
458
|
+
limit = Smith.config.persistence_max_bytes
|
|
459
|
+
raise PersistedStateTooLarge.new(key:, bytes: payload.bytesize, limit:) if limit && payload.bytesize > limit
|
|
460
|
+
end
|
|
461
|
+
|
|
353
462
|
# Resolves the effective TTL once per persist, with the class-level
|
|
354
463
|
# DSL override taking precedence over Smith.config.persistence_ttl.
|
|
355
464
|
# Returns nil when neither is set, which collapses ttl_kwarg to an
|
|
@@ -387,7 +496,7 @@ module Smith
|
|
|
387
496
|
end
|
|
388
497
|
|
|
389
498
|
def invoke_on_step_callback(step, callback)
|
|
390
|
-
callback&.call(step)
|
|
499
|
+
IdempotencyKey.__send__(:without) { callback&.call(step) }
|
|
391
500
|
rescue StandardError => e
|
|
392
501
|
Smith.config.logger&.error("Smith::Workflow on_step callback error: #{e.message} (#{e.class})")
|
|
393
502
|
end
|
|
@@ -33,7 +33,9 @@ module Smith
|
|
|
33
33
|
state.config[:max_rounds].times do |round|
|
|
34
34
|
# The overlay scopes every trace and usage fact from this round's
|
|
35
35
|
# generator and evaluator calls to the round that produced them.
|
|
36
|
-
result = Attribution.with(round: round)
|
|
36
|
+
result = Attribution.with(round: round) do
|
|
37
|
+
IdempotencyKey.__send__(:within, :round, round) { run_optimization_round(state, round) }
|
|
38
|
+
end
|
|
37
39
|
return result if result
|
|
38
40
|
end
|
|
39
41
|
|
|
@@ -41,10 +43,13 @@ module Smith
|
|
|
41
43
|
"optimization exhausted #{state.config[:max_rounds]} rounds without acceptance")
|
|
42
44
|
end
|
|
43
45
|
|
|
46
|
+
# Each role is a position of its own in the round, so the generator,
|
|
47
|
+
# the before_eval callback and the evaluator never share a key.
|
|
44
48
|
def run_optimization_round(state, round)
|
|
45
|
-
generate_candidate!(state, round)
|
|
46
|
-
rejection = before_eval_rejection(state)
|
|
47
|
-
evaluation = rejection ||
|
|
49
|
+
IdempotencyKey.__send__(:within, :generator) { generate_candidate!(state, round) }
|
|
50
|
+
rejection = IdempotencyKey.__send__(:within, :before_eval) { before_eval_rejection(state) }
|
|
51
|
+
evaluation = rejection ||
|
|
52
|
+
normalize_evaluation(IdempotencyKey.__send__(:within, :evaluator) { evaluate_candidate(state) })
|
|
48
53
|
validate_evaluation_structure!(evaluation)
|
|
49
54
|
validate_evaluation_fields!(evaluation, state.config)
|
|
50
55
|
record_evaluation(state, evaluation, round, rejection ? :before_eval : :evaluator)
|
|
@@ -219,8 +224,9 @@ module Smith
|
|
|
219
224
|
prior.push(role: :user, content:)
|
|
220
225
|
end
|
|
221
226
|
|
|
222
|
-
# A structured candidate (an output_schema agent's Hash or Array
|
|
223
|
-
# the JSON serialisation the
|
|
227
|
+
# A structured candidate (an output_schema agent's Hash, or an Array a
|
|
228
|
+
# schema-less generator returned) gets the JSON serialisation the
|
|
229
|
+
# provider boundary applies; text stays as is.
|
|
224
230
|
def candidate_content(candidate)
|
|
225
231
|
candidate.is_a?(Hash) || candidate.is_a?(Array) ? json_message_content(candidate) : candidate.to_s
|
|
226
232
|
end
|
|
@@ -11,6 +11,11 @@ module Smith
|
|
|
11
11
|
data: { transition: transition.name, from: transition.from, to: transition.to }
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
+
# Handlers are not a step's own work, so they get no idempotency key.
|
|
15
|
+
IdempotencyKey.__send__(:without) { emit_step_completed_event(transition) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def emit_step_completed_event(transition)
|
|
14
19
|
Smith::Events.emit(
|
|
15
20
|
Events::StepCompleted.new(
|
|
16
21
|
transition: transition.name.to_sym,
|
|
@@ -54,6 +59,10 @@ module Smith
|
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
def emit_step_failed_event(failure)
|
|
62
|
+
IdempotencyKey.__send__(:without) { emit_step_failed_bus_event(failure) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def emit_step_failed_bus_event(failure)
|
|
57
66
|
Smith::Events.emit(
|
|
58
67
|
Events::StepFailed.new(
|
|
59
68
|
transition: DiagnosticText.capture(failure[:transition].to_s, max_bytes: 256).to_sym,
|
|
@@ -6,6 +6,7 @@ require_relative "guarded_step_execution"
|
|
|
6
6
|
require_relative "prepared_branch_execution"
|
|
7
7
|
require_relative "step_completion"
|
|
8
8
|
require_relative "step_context"
|
|
9
|
+
require_relative "step_stream"
|
|
9
10
|
|
|
10
11
|
module Smith
|
|
11
12
|
class Workflow
|
|
@@ -15,6 +16,7 @@ module Smith
|
|
|
15
16
|
include PreparedBranchExecution
|
|
16
17
|
include StepCompletion
|
|
17
18
|
include StepContext
|
|
19
|
+
include StepStream
|
|
18
20
|
include Agent::Lifecycle
|
|
19
21
|
include NestedExecution
|
|
20
22
|
include EvaluatorOptimizer
|
|
@@ -37,25 +39,47 @@ module Smith
|
|
|
37
39
|
# The optimizer's verdict carrier resets for the same reason.
|
|
38
40
|
@pending_agent_execution = nil
|
|
39
41
|
@pending_evaluations = nil
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
writable_output_key!(transition) if transition.output_to
|
|
43
|
+
with_step_stream(transition) do
|
|
44
|
+
output = with_scoped_artifacts { run_with_retry_policy(transition) }
|
|
45
|
+
output = queue_batch(transition, output) if output.is_a?(BatchExecution::Captured)
|
|
46
|
+
next output if wait_pending?(output)
|
|
47
|
+
|
|
48
|
+
StepCompletion.instance_method(:complete_step).bind_call(self, transition, output)
|
|
49
|
+
end
|
|
50
|
+
ensure
|
|
51
|
+
release_batch_holds
|
|
42
52
|
end
|
|
43
53
|
|
|
54
|
+
# Restored with the step's thread context (ThreadContextSnapshot): a
|
|
55
|
+
# step's depth counts the runs whose steps it runs inside.
|
|
44
56
|
def setup_step_context
|
|
57
|
+
Thread.current[:smith_step_depth] = (Thread.current[:smith_step_depth] || 0) + 1
|
|
45
58
|
Tool.current_deadline = wall_clock_deadline
|
|
46
59
|
Tool.current_ledger = @ledger
|
|
47
60
|
Tool.current_tool_result_collector = tool_result_collector
|
|
48
61
|
end
|
|
49
62
|
|
|
63
|
+
# An await, delay, map or call waits; a spawn launches and continues.
|
|
64
|
+
def execute_waiting_step(transition)
|
|
65
|
+
transition.spawns? ? execute_spawn_step(transition) : execute_await_step(transition)
|
|
66
|
+
end
|
|
67
|
+
|
|
50
68
|
def run_standard_guarded_step(transition)
|
|
69
|
+
return execute_waiting_step(transition) if transition.awaits? || transition.spawns?
|
|
51
70
|
return dispatch_step(transition) if transition.deterministic?
|
|
52
71
|
return run_guarded_fanout_step(transition) if transition.fanout?
|
|
53
72
|
|
|
54
73
|
agent_class = resolve_agent_class(transition)
|
|
55
74
|
run_input_guardrails(agent_class)
|
|
56
75
|
apply_tool_guardrails(agent_class)
|
|
57
|
-
prepared_input =
|
|
76
|
+
prepared_input = step_prepared_input(transition)
|
|
77
|
+
return send_agent_batch(transition, agent_class, prepared_input) if batch_send?(transition)
|
|
78
|
+
|
|
79
|
+
guarded_agent_output(transition, agent_class, prepared_input)
|
|
80
|
+
end
|
|
58
81
|
|
|
82
|
+
def guarded_agent_output(transition, agent_class, prepared_input)
|
|
59
83
|
output = with_agent_context(agent_class) do
|
|
60
84
|
dispatch_step(transition, prepared_input: prepared_input)
|
|
61
85
|
end
|
|
@@ -82,13 +106,9 @@ module Smith
|
|
|
82
106
|
@last_prepared_input = prepared_input
|
|
83
107
|
return nil unless transition.agent_name
|
|
84
108
|
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
# path resolves the actual id in build_model_chain at attempt time.
|
|
89
|
-
return nil unless agent_class.model_configured?
|
|
90
|
-
|
|
91
|
-
invoke_agent(agent_class, prepared_input)
|
|
109
|
+
# An agent with no model and no fallback models fails in
|
|
110
|
+
# complete_with_provider rather than skipping the step.
|
|
111
|
+
invoke_agent(resolve_agent_class(transition), prepared_input)
|
|
92
112
|
end
|
|
93
113
|
|
|
94
114
|
def execute_serial_step(transition, prepared_input: nil)
|
|
@@ -117,19 +137,6 @@ module Smith
|
|
|
117
137
|
agent_class = resolve_agent_class(transition)
|
|
118
138
|
reserve_serial_budget(ledger, agent_budget: agent_class&.budget)
|
|
119
139
|
end
|
|
120
|
-
|
|
121
|
-
def resolve_agent_class(transition)
|
|
122
|
-
parallel_binding = Thread.current[:smith_parallel_agent_binding]
|
|
123
|
-
parallel_agent_class = parallel_binding&.resolve(workflow: self, transition:)
|
|
124
|
-
return parallel_agent_class if parallel_agent_class
|
|
125
|
-
|
|
126
|
-
transition.agent_name && resolve_registered_agent!(
|
|
127
|
-
transition.agent_name,
|
|
128
|
-
workflow_class: self.class,
|
|
129
|
-
transition_name: transition.name,
|
|
130
|
-
role: :agent
|
|
131
|
-
)
|
|
132
|
-
end
|
|
133
140
|
end
|
|
134
141
|
end
|
|
135
142
|
end
|
|
@@ -11,6 +11,19 @@ module Smith
|
|
|
11
11
|
|
|
12
12
|
Agent::Registry.fetch!(name, workflow_class:, transition_name:, role:)
|
|
13
13
|
end
|
|
14
|
+
|
|
15
|
+
def resolve_agent_class(transition)
|
|
16
|
+
parallel_binding = Thread.current[:smith_parallel_agent_binding]
|
|
17
|
+
parallel_agent_class = parallel_binding&.resolve(workflow: self, transition:)
|
|
18
|
+
return parallel_agent_class if parallel_agent_class
|
|
19
|
+
|
|
20
|
+
transition.agent_name && resolve_registered_agent!(
|
|
21
|
+
transition.agent_name,
|
|
22
|
+
workflow_class: self.class,
|
|
23
|
+
transition_name: transition.name,
|
|
24
|
+
role: :agent
|
|
25
|
+
)
|
|
26
|
+
end
|
|
14
27
|
end
|
|
15
28
|
end
|
|
16
29
|
end
|
|
@@ -19,17 +19,32 @@ module Smith
|
|
|
19
19
|
"persisted_failure_invalid" => ->(record) { Smith::PersistedFailureInvalid.new(record[:error_message]) },
|
|
20
20
|
"deadline_exceeded" => ->(record) { Smith::DeadlineExceeded.new(record[:error_message]) },
|
|
21
21
|
"provider_permanent_failure" => ->(record) { provider_permanent_failure(record) },
|
|
22
|
-
"budget_exceeded" => ->(record) {
|
|
22
|
+
"budget_exceeded" => ->(record) { budget_exceeded(record) },
|
|
23
23
|
"guardrail_failed" => ->(record) { Smith::GuardrailFailed.new(record[:error_message]) },
|
|
24
24
|
"agent_error" => ->(record) { Smith::AgentError.new(record[:error_message]) },
|
|
25
25
|
"workflow_error" => ->(record) { Smith::WorkflowError.new(record[:error_message]) },
|
|
26
|
+
"output_failure" => lambda { |record|
|
|
27
|
+
Smith::OutputFailure.from_details(record.fetch(:error_details), message: record[:error_message])
|
|
28
|
+
},
|
|
29
|
+
"child_runs_failed" => lambda { |record|
|
|
30
|
+
Smith::ChildRunsFailed.from_details(record.fetch(:error_details), message: record[:error_message])
|
|
31
|
+
},
|
|
32
|
+
"wait_timed_out" => lambda { |record|
|
|
33
|
+
Smith::WaitTimedOut.from_details(record.fetch(:error_details), message: record[:error_message])
|
|
34
|
+
},
|
|
26
35
|
"other" => ->(record) { RuntimeError.new(record[:error_message]) }
|
|
27
36
|
}.freeze
|
|
28
37
|
SPECIAL_CLASS_BUILDERS = {
|
|
29
38
|
"Smith::Workflow::Composite::BranchFailure" => lambda { |record|
|
|
30
39
|
Smith::Workflow::Composite::BranchFailure.from_details(record[:error_details])
|
|
31
40
|
},
|
|
32
|
-
"Smith::BlankAgentOutputError" => ->(record) { blank_agent_output_failure(record) }
|
|
41
|
+
"Smith::BlankAgentOutputError" => ->(record) { blank_agent_output_failure(record) },
|
|
42
|
+
"Smith::PersistedStateTooLarge" => lambda { |record|
|
|
43
|
+
Smith::PersistedStateTooLarge.from_details(record.fetch(:error_details), message: record[:error_message])
|
|
44
|
+
},
|
|
45
|
+
**Smith::OutputFailure.restorers.transform_values do |restore|
|
|
46
|
+
->(record) { restore.call(record.fetch(:error_details), record[:error_message]) }
|
|
47
|
+
end
|
|
33
48
|
}.freeze
|
|
34
49
|
private_constant :FAMILY_BUILDERS, :SPECIAL_CLASS_BUILDERS
|
|
35
50
|
|
|
@@ -77,6 +92,13 @@ module Smith
|
|
|
77
92
|
Smith::ToolFailureNotificationFailed.from_details(record.fetch(:error_details))
|
|
78
93
|
end
|
|
79
94
|
|
|
95
|
+
# A host ledger's refusal stays one, so a host can tell waiting for
|
|
96
|
+
# budget from a run whose own budget ran out.
|
|
97
|
+
def self.budget_exceeded(record)
|
|
98
|
+
error_class = record[:error_class] == "Smith::BudgetUnavailable" ? Smith::BudgetUnavailable : Smith::BudgetExceeded
|
|
99
|
+
error_class.new(record[:error_message])
|
|
100
|
+
end
|
|
101
|
+
|
|
80
102
|
def self.provider_permanent_failure(record)
|
|
81
103
|
Smith::ProviderPermanentFailure.from_details(record.fetch(:error_details), message: record[:error_message])
|
|
82
104
|
end
|
|
@@ -88,8 +110,9 @@ module Smith
|
|
|
88
110
|
Smith::BlankAgentOutputError.from_details(details)
|
|
89
111
|
end
|
|
90
112
|
|
|
91
|
-
private_class_method :
|
|
92
|
-
:
|
|
113
|
+
private_class_method :budget_exceeded, :deterministic_step_failure, :tool_guardrail_failure,
|
|
114
|
+
:tool_failure_notification_failure, :provider_permanent_failure,
|
|
115
|
+
:blank_agent_output_failure
|
|
93
116
|
end
|
|
94
117
|
end
|
|
95
118
|
end
|
|
@@ -4,6 +4,8 @@ require "dry-initializer"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../diagnostic_text"
|
|
6
6
|
require_relative "../errors"
|
|
7
|
+
require_relative "../output_failure"
|
|
8
|
+
require_relative "../child_runs_failed"
|
|
7
9
|
require_relative "failure_detail_snapshot"
|
|
8
10
|
require_relative "failure_record_text"
|
|
9
11
|
|
|
@@ -23,7 +25,10 @@ module Smith
|
|
|
23
25
|
[Smith::ProviderPermanentFailure, "provider_permanent_failure"],
|
|
24
26
|
[Smith::BudgetExceeded, "budget_exceeded"],
|
|
25
27
|
[Smith::GuardrailFailed, "guardrail_failed"],
|
|
28
|
+
[Smith::OutputFailure, "output_failure"],
|
|
26
29
|
[Smith::AgentError, "agent_error"],
|
|
30
|
+
[Smith::WaitTimedOut, "wait_timed_out"],
|
|
31
|
+
[Smith::ChildRunsFailed, "child_runs_failed"],
|
|
27
32
|
[Smith::WorkflowError, "workflow_error"]
|
|
28
33
|
].freeze
|
|
29
34
|
BOOLEAN_VALUES = [true, false].freeze
|
|
@@ -126,7 +131,7 @@ module Smith
|
|
|
126
131
|
FailureDetailSnapshot.new(error.details).call
|
|
127
132
|
rescue StandardError => e
|
|
128
133
|
FailureDetailSnapshot.new(
|
|
129
|
-
smith_failure_details_omitted: DiagnosticText.capture(e
|
|
134
|
+
smith_failure_details_omitted: DiagnosticText.capture(error_message(e), max_bytes: 1_024)
|
|
130
135
|
).call
|
|
131
136
|
end
|
|
132
137
|
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require "dry-initializer"
|
|
4
4
|
|
|
5
5
|
require_relative "../errors"
|
|
6
|
+
require_relative "../output_failure"
|
|
7
|
+
require_relative "../child_runs_failed"
|
|
6
8
|
require_relative "composite/branch_failure"
|
|
7
9
|
|
|
8
10
|
module Smith
|
|
@@ -19,11 +21,14 @@ module Smith
|
|
|
19
21
|
"Smith::PersistedFailureInvalid" => "persisted_failure_invalid",
|
|
20
22
|
"Smith::AgentError" => "agent_error",
|
|
21
23
|
"Smith::BlankAgentOutputError" => "agent_error",
|
|
24
|
+
**Smith::OutputFailure.restorers.transform_values { "output_failure" },
|
|
22
25
|
"Smith::DeadlineExceeded" => "deadline_exceeded",
|
|
23
26
|
"Smith::ProviderPermanentFailure" => "provider_permanent_failure",
|
|
24
|
-
"Smith::BudgetExceeded" => "budget_exceeded",
|
|
27
|
+
"Smith::BudgetExceeded" => "budget_exceeded", "Smith::BudgetUnavailable" => "budget_exceeded",
|
|
25
28
|
"Smith::GuardrailFailed" => "guardrail_failed",
|
|
26
|
-
"Smith::
|
|
29
|
+
"Smith::WaitTimedOut" => "wait_timed_out",
|
|
30
|
+
"Smith::ChildRunsFailed" => "child_runs_failed",
|
|
31
|
+
"Smith::WorkflowError" => "workflow_error", "Smith::PersistedStateTooLarge" => "workflow_error",
|
|
27
32
|
"Smith::UnresolvedTransitionError" => "workflow_error",
|
|
28
33
|
"Smith::Workflow::Composite::BranchFailure" => "workflow_error"
|
|
29
34
|
}.freeze
|
|
@@ -38,7 +43,7 @@ module Smith
|
|
|
38
43
|
deterministic_step_failure tool_guardrail_failed tool_failure_notification_failed tool_capture_failed
|
|
39
44
|
tool_outcome_uncertain tool_execution_not_admitted bounded_completion_error persisted_failure_invalid
|
|
40
45
|
deadline_exceeded provider_permanent_failure budget_exceeded guardrail_failed agent_error workflow_error
|
|
41
|
-
other
|
|
46
|
+
wait_timed_out output_failure child_runs_failed other
|
|
42
47
|
].freeze
|
|
43
48
|
RETRY_FORBIDDEN_FAMILIES = %w[
|
|
44
49
|
tool_capture_failed tool_failure_notification_failed tool_outcome_uncertain tool_execution_not_admitted
|
|
@@ -53,9 +58,11 @@ module Smith
|
|
|
53
58
|
Smith::Workflow::Composite::BranchFailure.from_details(details)
|
|
54
59
|
},
|
|
55
60
|
"Smith::ProviderPermanentFailure" => ->(details) { Smith::ProviderPermanentFailure.from_details(details) },
|
|
61
|
+
**[WaitTimedOut, ChildRunsFailed, PersistedStateTooLarge].to_h { [_1.name, _1.method(:from_details)] },
|
|
56
62
|
"Smith::BlankAgentOutputError" => lambda { |details|
|
|
57
63
|
Smith::BlankAgentOutputError.from_details(details) unless details.nil?
|
|
58
|
-
}
|
|
64
|
+
},
|
|
65
|
+
**Smith::OutputFailure.restorers
|
|
59
66
|
}.freeze
|
|
60
67
|
BOOLEAN_VALUES = [true, false].freeze
|
|
61
68
|
private_constant :KNOWN_ERROR_FAMILIES, :LEGACY_ERROR_FAMILIES, :KNOWN_FAMILIES, :RETRY_FORBIDDEN_FAMILIES,
|
|
@@ -20,13 +20,10 @@ module Smith
|
|
|
20
20
|
branch_agent_classes = fanout_agent_classes(transition, branches)
|
|
21
21
|
run_workflow_input_guardrails
|
|
22
22
|
run_fanout_agent_input_guardrails(branch_agent_classes)
|
|
23
|
-
prepared_input =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
branch_agent_classes: branch_agent_classes,
|
|
28
|
-
prepared_input: prepared_input
|
|
29
|
-
)
|
|
23
|
+
prepared_input = step_prepared_input(transition)
|
|
24
|
+
return send_fanout_batch(transition, branches, branch_agent_classes, prepared_input) if batch_send?(transition)
|
|
25
|
+
|
|
26
|
+
output = execute_fanout_step(transition, branches:, branch_agent_classes:, prepared_input:)
|
|
30
27
|
run_workflow_output_guardrails(output)
|
|
31
28
|
output
|
|
32
29
|
end
|
|
@@ -68,7 +65,7 @@ module Smith
|
|
|
68
65
|
def guarded_fanout_branch_call(agent_class, env, signal)
|
|
69
66
|
check_cancellation!(signal)
|
|
70
67
|
check_deadline!
|
|
71
|
-
result =
|
|
68
|
+
result = invoke_agent(agent_class, env.prepared_input)
|
|
72
69
|
output = result.is_a?(AgentResult) ? result.content : result
|
|
73
70
|
validate_data_volume!(output, agent_class)
|
|
74
71
|
run_agent_output_guardrails(output, agent_class)
|