smith-agents 0.4.5 → 0.6.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 +264 -0
- data/README.md +191 -4
- data/docs/CONFIGURATION.md +4 -1
- data/docs/PATTERNS.md +7 -0
- data/docs/PERSISTENCE.md +347 -25
- data/lib/smith/agent/lifecycle.rb +19 -10
- data/lib/smith/agent/registry/execution_binding_capture.rb +26 -0
- data/lib/smith/agent/registry/mutation_boundary.rb +29 -0
- data/lib/smith/agent/registry.rb +8 -0
- data/lib/smith/agent.rb +37 -2
- data/lib/smith/budget/amount_contract.rb +67 -0
- data/lib/smith/budget/amount_representation.rb +43 -0
- data/lib/smith/budget/decimal_context.rb +18 -0
- data/lib/smith/budget/ledger.rb +110 -18
- data/lib/smith/budget/ledger_publication.rb +60 -0
- data/lib/smith/budget/ledger_state.rb +21 -0
- data/lib/smith/budget/ledger_state_transition.rb +72 -0
- data/lib/smith/budget/public_state_validator.rb +25 -0
- data/lib/smith/budget/reservation.rb +20 -0
- data/lib/smith/budget/reservation_contract.rb +34 -0
- data/lib/smith/context/observation_masking.rb +3 -1
- data/lib/smith/context/state_injection.rb +1 -1
- data/lib/smith/doctor/checks/persistence_capabilities.rb +18 -4
- data/lib/smith/error.rb +5 -0
- data/lib/smith/errors.rb +10 -0
- data/lib/smith/exponential_backoff.rb +128 -0
- data/lib/smith/persistence_adapters/active_record_exact_predicate.rb +43 -0
- data/lib/smith/persistence_adapters/active_record_exact_store.rb +62 -0
- data/lib/smith/persistence_adapters/active_record_exact_write.rb +40 -0
- data/lib/smith/persistence_adapters/active_record_store.rb +7 -4
- data/lib/smith/persistence_adapters/cache_store.rb +1 -0
- data/lib/smith/persistence_adapters/memory.rb +16 -1
- data/lib/smith/persistence_adapters/redis_client_access.rb +38 -0
- data/lib/smith/persistence_adapters/redis_exact_write.rb +44 -0
- data/lib/smith/persistence_adapters/redis_store.rb +28 -11
- data/lib/smith/persistence_adapters/retry.rb +22 -10
- data/lib/smith/persistence_adapters.rb +21 -18
- data/lib/smith/persistence_payload_conflict.rb +12 -0
- data/lib/smith/tool/budget_enforcement.rb +2 -2
- data/lib/smith/types.rb +1 -0
- data/lib/smith/version.rb +2 -1
- data/lib/smith/workflow/branch_env.rb +1 -1
- data/lib/smith/workflow/budget_integration.rb +21 -11
- data/lib/smith/workflow/composite/branch.rb +93 -0
- data/lib/smith/workflow/composite/branch_budget_contract.rb +41 -0
- data/lib/smith/workflow/composite/branch_contract.rb +102 -0
- data/lib/smith/workflow/composite/branch_execution.rb +124 -0
- data/lib/smith/workflow/composite/branch_failure.rb +91 -0
- data/lib/smith/workflow/composite/branch_outcome.rb +108 -0
- data/lib/smith/workflow/composite/budget_allocator.rb +78 -0
- data/lib/smith/workflow/composite/budget_math.rb +51 -0
- data/lib/smith/workflow/composite/contract.rb +100 -0
- data/lib/smith/workflow/composite/effects.rb +140 -0
- data/lib/smith/workflow/composite/effects_application.rb +36 -0
- data/lib/smith/workflow/composite/effects_baseline.rb +36 -0
- data/lib/smith/workflow/composite/effects_preflight.rb +102 -0
- data/lib/smith/workflow/composite/encoded_value_budget.rb +36 -0
- data/lib/smith/workflow/composite/enums.rb +27 -0
- data/lib/smith/workflow/composite/error.rb +54 -0
- data/lib/smith/workflow/composite/error_evidence.rb +61 -0
- data/lib/smith/workflow/composite/execution_contract.rb +73 -0
- data/lib/smith/workflow/composite/fanout_branch_contract.rb +61 -0
- data/lib/smith/workflow/composite/input.rb +53 -0
- data/lib/smith/workflow/composite/outcome_accumulator.rb +131 -0
- data/lib/smith/workflow/composite/outcome_set.rb +23 -0
- data/lib/smith/workflow/composite/payload.rb +128 -0
- data/lib/smith/workflow/composite/payload_digest.rb +28 -0
- data/lib/smith/workflow/composite/plan.rb +130 -0
- data/lib/smith/workflow/composite/plan_integrity.rb +52 -0
- data/lib/smith/workflow/composite/planner.rb +53 -0
- data/lib/smith/workflow/composite/preparation.rb +27 -0
- data/lib/smith/workflow/composite/reducer.rb +133 -0
- data/lib/smith/workflow/composite/reduction.rb +31 -0
- data/lib/smith/workflow/composite/value_budget.rb +90 -0
- data/lib/smith/workflow/composite_branch_execution_authorization.rb +52 -0
- data/lib/smith/workflow/deadline_enforcement.rb +22 -5
- data/lib/smith/workflow/definition_identity/class_methods.rb +48 -0
- data/lib/smith/workflow/definition_identity.rb +25 -0
- data/lib/smith/workflow/deterministic_step.rb +2 -2
- data/lib/smith/workflow/dsl.rb +73 -5
- data/lib/smith/workflow/durability.rb +1 -1
- data/lib/smith/workflow/evaluator_optimizer.rb +7 -11
- data/lib/smith/workflow/execution.rb +28 -30
- data/lib/smith/workflow/execution_binding_resolution.rb +16 -0
- data/lib/smith/workflow/execution_result_snapshot.rb +118 -0
- data/lib/smith/workflow/fanout_execution.rb +20 -19
- data/lib/smith/workflow/graph/diagnostic.rb +27 -1
- data/lib/smith/workflow/graph/diagnostic_path.rb +24 -0
- data/lib/smith/workflow/graph/execution_successors.rb +65 -0
- data/lib/smith/workflow/graph/identifier_projection.rb +13 -0
- data/lib/smith/workflow/graph/metrics.rb +1 -3
- data/lib/smith/workflow/graph/nested_readiness_diagnostics.rb +15 -16
- data/lib/smith/workflow/graph/optimization_contract.rb +2 -2
- data/lib/smith/workflow/graph/orchestration_contract.rb +3 -3
- data/lib/smith/workflow/graph/reachability.rb +19 -31
- data/lib/smith/workflow/graph/reachability_diagnostics.rb +17 -1
- data/lib/smith/workflow/graph/retry_policy_diagnostic.rb +36 -0
- data/lib/smith/workflow/graph/runtime_binding_diagnostics.rb +2 -2
- data/lib/smith/workflow/graph/runtime_readiness.rb +3 -44
- data/lib/smith/workflow/graph/runtime_readiness_metrics.rb +2 -2
- data/lib/smith/workflow/graph/runtime_readiness_report_builder.rb +47 -0
- data/lib/smith/workflow/graph/runtime_readiness_traversal.rb +108 -0
- data/lib/smith/workflow/graph/state_diagnostics.rb +2 -2
- data/lib/smith/workflow/graph/transition_contract.rb +47 -0
- data/lib/smith/workflow/graph/transition_contract_attributes.rb +73 -0
- data/lib/smith/workflow/graph/transition_contract_configurations.rb +100 -0
- data/lib/smith/workflow/graph/transition_diagnostics.rb +54 -12
- data/lib/smith/workflow/graph/transition_optimization_configuration.rb +62 -0
- data/lib/smith/workflow/graph/transition_snapshot.rb +4 -1
- data/lib/smith/workflow/graph/validator.rb +1 -1
- data/lib/smith/workflow/graph.rb +62 -4
- data/lib/smith/workflow/guardrail_integration.rb +23 -9
- data/lib/smith/workflow/identifier.rb +24 -0
- data/lib/smith/workflow/message_admission.rb +40 -0
- data/lib/smith/workflow/message_admission_boundary.rb +22 -0
- data/lib/smith/workflow/message_batch.rb +60 -0
- data/lib/smith/workflow/message_value_normalizer.rb +134 -0
- data/lib/smith/workflow/nested_execution.rb +10 -0
- data/lib/smith/workflow/orchestrator_worker.rb +3 -7
- data/lib/smith/workflow/parallel/cancellation_signal.rb +14 -5
- data/lib/smith/workflow/parallel/execution_context.rb +113 -0
- data/lib/smith/workflow/parallel/nested_execution.rb +124 -0
- data/lib/smith/workflow/parallel/root_execution.rb +91 -0
- data/lib/smith/workflow/parallel.rb +19 -22
- data/lib/smith/workflow/parallel_agent_binding.rb +24 -0
- data/lib/smith/workflow/parallel_execution.rb +46 -14
- data/lib/smith/workflow/persistence.rb +37 -8
- data/lib/smith/workflow/prepared_branch_execution.rb +31 -0
- data/lib/smith/workflow/prepared_step.rb +122 -0
- data/lib/smith/workflow/prepared_step_dispatch.rb +88 -0
- data/lib/smith/workflow/prepared_step_execution_authorization.rb +101 -0
- data/lib/smith/workflow/prepared_step_execution_result.rb +49 -0
- data/lib/smith/workflow/prepared_step_execution_scope.rb +111 -0
- data/lib/smith/workflow/prepared_step_recovery.rb +38 -0
- data/lib/smith/workflow/process_local.rb +33 -0
- data/lib/smith/workflow/retry_execution.rb +20 -11
- data/lib/smith/workflow/split_step_persistence/boundary.rb +35 -22
- data/lib/smith/workflow/split_step_persistence/boundary_reset.rb +50 -0
- data/lib/smith/workflow/split_step_persistence/canonical_payload_digest.rb +55 -0
- data/lib/smith/workflow/split_step_persistence/checkpoint.rb +3 -5
- data/lib/smith/workflow/split_step_persistence/composite_branch_authorization.rb +99 -0
- data/lib/smith/workflow/split_step_persistence/composite_branch_effects.rb +41 -0
- data/lib/smith/workflow/split_step_persistence/composite_branch_execution.rb +95 -0
- data/lib/smith/workflow/split_step_persistence/composite_branch_outcome.rb +36 -0
- data/lib/smith/workflow/split_step_persistence/composite_execution.rb +39 -0
- data/lib/smith/workflow/split_step_persistence/composite_preparation.rb +57 -0
- data/lib/smith/workflow/split_step_persistence/composite_reduction_execution.rb +122 -0
- data/lib/smith/workflow/split_step_persistence/definition_boundary.rb +19 -0
- data/lib/smith/workflow/split_step_persistence/dispatch_boundary.rb +16 -0
- data/lib/smith/workflow/split_step_persistence/dispatch_claim.rb +118 -0
- data/lib/smith/workflow/split_step_persistence/dispatch_confirmation.rb +70 -0
- data/lib/smith/workflow/split_step_persistence/dispatch_verification.rb +35 -0
- data/lib/smith/workflow/split_step_persistence/execution.rb +20 -64
- data/lib/smith/workflow/split_step_persistence/execution_authorization.rb +109 -0
- data/lib/smith/workflow/split_step_persistence/execution_authorization_issuance.rb +57 -0
- data/lib/smith/workflow/split_step_persistence/execution_binding_collector.rb +95 -0
- data/lib/smith/workflow/split_step_persistence/execution_binding_snapshot.rb +109 -0
- data/lib/smith/workflow/split_step_persistence/execution_lifecycle.rb +96 -0
- data/lib/smith/workflow/split_step_persistence/execution_result_capture.rb +48 -0
- data/lib/smith/workflow/split_step_persistence/execution_verification.rb +77 -0
- data/lib/smith/workflow/split_step_persistence/execution_workflow_snapshot.rb +77 -0
- data/lib/smith/workflow/split_step_persistence/inheritance.rb +3 -1
- data/lib/smith/workflow/split_step_persistence/payloads.rb +7 -3
- data/lib/smith/workflow/split_step_persistence/preparation.rb +30 -2
- data/lib/smith/workflow/split_step_persistence/preparation_claim.rb +38 -12
- data/lib/smith/workflow/split_step_persistence/preparation_payload.rb +25 -0
- data/lib/smith/workflow/split_step_persistence/preparation_recovery.rb +10 -2
- data/lib/smith/workflow/split_step_persistence/recovery.rb +125 -0
- data/lib/smith/workflow/split_step_persistence/recovery_boundary.rb +56 -0
- data/lib/smith/workflow/split_step_persistence/recovery_class_methods.rb +14 -0
- data/lib/smith/workflow/split_step_persistence/replace_exact_signature.rb +46 -0
- data/lib/smith/workflow/split_step_persistence/restart_safe_adapter.rb +42 -0
- data/lib/smith/workflow/split_step_persistence/subclass_boundary.rb +66 -0
- data/lib/smith/workflow/split_step_persistence/transaction_identity.rb +45 -0
- data/lib/smith/workflow/split_step_persistence.rb +50 -0
- data/lib/smith/workflow/step_completion.rb +31 -0
- data/lib/smith/workflow/step_context.rb +46 -0
- data/lib/smith/workflow/string_snapshot.rb +25 -0
- data/lib/smith/workflow/thread_context_snapshot.rb +103 -0
- data/lib/smith/workflow/transition.rb +137 -133
- data/lib/smith/workflow/transition_actionability.rb +22 -0
- data/lib/smith/workflow.rb +64 -18
- data/lib/smith.rb +44 -2
- data/sig/smith.rbs +1 -0
- metadata +155 -19
data/lib/smith/workflow/dsl.rb
CHANGED
|
@@ -12,6 +12,12 @@ module Smith
|
|
|
12
12
|
super
|
|
13
13
|
subclass.instance_variable_set(:@states, (@states || []).dup)
|
|
14
14
|
subclass.instance_variable_set(:@transitions, (@transitions || {}).dup)
|
|
15
|
+
subclass.instance_variable_set(
|
|
16
|
+
:@transitions_by_state,
|
|
17
|
+
duplicate_transition_index(@transitions_by_state)
|
|
18
|
+
)
|
|
19
|
+
subclass.instance_variable_set(:@transition_order, (@transition_order || {}).dup)
|
|
20
|
+
subclass.instance_variable_set(:@transition_sequence, @transition_sequence)
|
|
15
21
|
subclass.instance_variable_set(:@initial_state_name, @initial_state_name)
|
|
16
22
|
subclass.instance_variable_set(:@budget_config, @budget_config&.dup)
|
|
17
23
|
subclass.instance_variable_set(:@max_transitions_count, @max_transitions_count)
|
|
@@ -29,19 +35,24 @@ module Smith
|
|
|
29
35
|
def initial_state(name = nil)
|
|
30
36
|
return @initial_state_name if name.nil?
|
|
31
37
|
|
|
32
|
-
@initial_state_name = name
|
|
33
|
-
state(
|
|
38
|
+
@initial_state_name = own_identifier(name)
|
|
39
|
+
state(@initial_state_name)
|
|
34
40
|
end
|
|
35
41
|
|
|
36
42
|
def state(name)
|
|
43
|
+
name = own_identifier(name)
|
|
37
44
|
@states ||= []
|
|
38
45
|
@states << name unless @states.include?(name)
|
|
39
46
|
generate_fail_transition if name == :failed
|
|
40
47
|
end
|
|
41
48
|
|
|
42
49
|
def transition(name, from:, to:, &)
|
|
50
|
+
declared = Transition.new(name, from: from, to: to, &)
|
|
51
|
+
name = declared.name
|
|
43
52
|
@transitions ||= {}
|
|
44
|
-
@transitions[name]
|
|
53
|
+
remove_from_transition_index(@transitions[name]) if @transitions.key?(name)
|
|
54
|
+
@transitions[name] = declared
|
|
55
|
+
insert_into_transition_index(declared)
|
|
45
56
|
end
|
|
46
57
|
|
|
47
58
|
def budget(**opts)
|
|
@@ -196,13 +207,25 @@ module Smith
|
|
|
196
207
|
end
|
|
197
208
|
|
|
198
209
|
def transitions_from(state)
|
|
199
|
-
(
|
|
210
|
+
transitions_by_state.fetch(state, []).dup
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def first_transition_from(state)
|
|
214
|
+
transitions_by_state.fetch(state, []).first
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def transition_from?(state)
|
|
218
|
+
transitions_by_state.fetch(state, []).any?
|
|
200
219
|
end
|
|
201
220
|
|
|
202
221
|
def find_transition(name)
|
|
203
222
|
(@transitions || {})[name]
|
|
204
223
|
end
|
|
205
224
|
|
|
225
|
+
def transition_at(index)
|
|
226
|
+
(@transitions || {}).values.fetch(index)
|
|
227
|
+
end
|
|
228
|
+
|
|
206
229
|
def from_state(hash)
|
|
207
230
|
workflow = allocate
|
|
208
231
|
workflow.send(:restore_state, hash)
|
|
@@ -211,11 +234,56 @@ module Smith
|
|
|
211
234
|
|
|
212
235
|
private
|
|
213
236
|
|
|
237
|
+
def own_identifier(identifier)
|
|
238
|
+
Identifier.normalize(identifier, label: "workflow identifier")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def transitions_by_state
|
|
242
|
+
@transitions_by_state ||= Hash.new { |index, state| index[state] = [] }
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def transition_order
|
|
246
|
+
@transition_order ||= {}
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def transition_sequence
|
|
250
|
+
@transition_sequence ||= 0
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def insert_into_transition_index(transition)
|
|
254
|
+
order = transition_order_for(transition.name)
|
|
255
|
+
indexed = transitions_by_state[transition.from]
|
|
256
|
+
insertion = indexed.bsearch_index { |candidate| transition_order.fetch(candidate.name) > order }
|
|
257
|
+
indexed.insert(insertion || indexed.length, transition)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def transition_order_for(name)
|
|
261
|
+
return transition_order.fetch(name) if transition_order.key?(name)
|
|
262
|
+
|
|
263
|
+
transition_order[name] = transition_sequence
|
|
264
|
+
@transition_sequence = transition_sequence + 1
|
|
265
|
+
transition_order.fetch(name)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def duplicate_transition_index(source)
|
|
269
|
+
source.to_h.each_with_object(Hash.new { |index, state| index[state] = [] }) do |(state, transitions), copy|
|
|
270
|
+
copy[state] = transitions.dup
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def remove_from_transition_index(transition)
|
|
275
|
+
return unless transition
|
|
276
|
+
|
|
277
|
+
indexed = transitions_by_state[transition.from]
|
|
278
|
+
indexed.delete(transition)
|
|
279
|
+
transitions_by_state.delete(transition.from) if indexed.empty?
|
|
280
|
+
end
|
|
281
|
+
|
|
214
282
|
def generate_fail_transition
|
|
215
283
|
@transitions ||= {}
|
|
216
284
|
return if @transitions.key?(:fail)
|
|
217
285
|
|
|
218
|
-
|
|
286
|
+
transition(:fail, from: nil, to: :failed)
|
|
219
287
|
end
|
|
220
288
|
end
|
|
221
289
|
end
|
|
@@ -164,7 +164,7 @@ module Smith
|
|
|
164
164
|
|
|
165
165
|
klass = Object.const_get(class_name)
|
|
166
166
|
state = state_name_for_payload(klass, state_name)
|
|
167
|
-
klass.
|
|
167
|
+
!klass.transition_from?(state) && next_transition.nil?
|
|
168
168
|
rescue JSON::ParserError, NameError, NoMethodError
|
|
169
169
|
false
|
|
170
170
|
end
|
|
@@ -12,13 +12,13 @@ module Smith
|
|
|
12
12
|
|
|
13
13
|
def execute_optimization_step(transition, prepared_input: nil)
|
|
14
14
|
state = OptimizationState.new(transition.optimization_config, prepared_input)
|
|
15
|
-
state.generator_class =
|
|
15
|
+
state.generator_class = resolve_registered_agent!(
|
|
16
16
|
state.config[:generator],
|
|
17
17
|
workflow_class: self.class,
|
|
18
18
|
transition_name: transition.name,
|
|
19
19
|
role: :generator
|
|
20
20
|
)
|
|
21
|
-
state.evaluator_class =
|
|
21
|
+
state.evaluator_class = resolve_registered_agent!(
|
|
22
22
|
state.config[:evaluator],
|
|
23
23
|
workflow_class: self.class,
|
|
24
24
|
transition_name: transition.name,
|
|
@@ -143,28 +143,24 @@ module Smith
|
|
|
143
143
|
end
|
|
144
144
|
|
|
145
145
|
def invoke_with_evaluator_schema(evaluator_class, schema, input)
|
|
146
|
-
|
|
147
|
-
evaluator_class.output_schema(schema)
|
|
148
|
-
invoke_agent_with_budget(evaluator_class, input)
|
|
149
|
-
ensure
|
|
150
|
-
evaluator_class.output_schema(original_schema)
|
|
146
|
+
invoke_agent_with_budget(evaluator_class, input, output_schema: schema)
|
|
151
147
|
end
|
|
152
148
|
|
|
153
|
-
def invoke_agent_with_budget(agent_class, prepared_input)
|
|
149
|
+
def invoke_agent_with_budget(agent_class, prepared_input, output_schema: agent_class.output_schema)
|
|
154
150
|
Thread.current[:smith_last_agent_result] = nil
|
|
155
151
|
clear_failed_billable_attempts
|
|
156
152
|
with_agent_context(agent_class) do
|
|
157
|
-
invoke_with_call_ledger(agent_class, prepared_input)
|
|
153
|
+
invoke_with_call_ledger(agent_class, prepared_input, output_schema:)
|
|
158
154
|
end
|
|
159
155
|
ensure
|
|
160
156
|
clear_failed_billable_attempts
|
|
161
157
|
end
|
|
162
158
|
|
|
163
|
-
def invoke_with_call_ledger(agent_class, prepared_input)
|
|
159
|
+
def invoke_with_call_ledger(agent_class, prepared_input, output_schema:)
|
|
164
160
|
ledger = effective_call_ledger
|
|
165
161
|
reserved = reserve_serial_budget(ledger, agent_budget: agent_class&.budget)
|
|
166
162
|
begin
|
|
167
|
-
result = invoke_agent(agent_class, prepared_input)
|
|
163
|
+
result = invoke_agent(agent_class, prepared_input, output_schema:)
|
|
168
164
|
agent_result = result.is_a?(AgentResult) ? result : nil
|
|
169
165
|
reconcile_branch_budget(ledger, reserved, agent_result: agent_result)
|
|
170
166
|
reserved = nil
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "agent_result"
|
|
4
|
+
require_relative "execution_binding_resolution"
|
|
5
|
+
require_relative "prepared_branch_execution"
|
|
6
|
+
require_relative "step_completion"
|
|
7
|
+
require_relative "step_context"
|
|
4
8
|
|
|
5
9
|
module Smith
|
|
6
10
|
class Workflow
|
|
7
11
|
module Execution
|
|
12
|
+
include ExecutionBindingResolution
|
|
13
|
+
include PreparedBranchExecution
|
|
14
|
+
include StepCompletion
|
|
15
|
+
include StepContext
|
|
8
16
|
include Agent::Lifecycle
|
|
9
17
|
include NestedExecution
|
|
10
18
|
include EvaluatorOptimizer
|
|
@@ -17,15 +25,12 @@ module Smith
|
|
|
17
25
|
private
|
|
18
26
|
|
|
19
27
|
def execute_step(transition)
|
|
20
|
-
|
|
28
|
+
with_step_context(transition) { execute_step_body(transition) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def execute_step_body(transition)
|
|
21
32
|
output = with_scoped_artifacts { run_with_retry_policy(transition) }
|
|
22
|
-
complete_step(transition, output)
|
|
23
|
-
rescue StandardError => e
|
|
24
|
-
@outcome = nil
|
|
25
|
-
handle_step_failure(transition, e)
|
|
26
|
-
{ transition: transition.name, from: transition.from, to: transition.to, error: e }
|
|
27
|
-
ensure
|
|
28
|
-
teardown_step_context
|
|
33
|
+
StepCompletion.instance_method(:complete_step).bind_call(self, transition, output)
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def setup_step_context
|
|
@@ -34,15 +39,16 @@ module Smith
|
|
|
34
39
|
Tool.current_tool_result_collector = tool_result_collector
|
|
35
40
|
end
|
|
36
41
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
def run_guarded_step(transition)
|
|
43
|
+
return apply_composite_reduction!(transition) if @composite_reduction
|
|
44
|
+
|
|
45
|
+
@resolved_parallel_branch_count = preflight_branch_count(transition)
|
|
46
|
+
run_standard_guarded_step(transition)
|
|
47
|
+
ensure
|
|
48
|
+
@resolved_parallel_branch_count = nil
|
|
43
49
|
end
|
|
44
50
|
|
|
45
|
-
def
|
|
51
|
+
def run_standard_guarded_step(transition)
|
|
46
52
|
return dispatch_step(transition) if transition.deterministic?
|
|
47
53
|
return run_guarded_fanout_step(transition) if transition.fanout?
|
|
48
54
|
|
|
@@ -60,20 +66,10 @@ module Smith
|
|
|
60
66
|
resolve_router_output(transition, output)
|
|
61
67
|
end
|
|
62
68
|
|
|
63
|
-
def
|
|
64
|
-
|
|
65
|
-
@next_transition_name = @router_next_transition || transition.success_transition
|
|
66
|
-
@router_next_transition = nil
|
|
67
|
-
append_accepted_output(output)
|
|
68
|
-
emit_step_completed(transition, output)
|
|
69
|
-
{ transition: transition.name, from: transition.from, to: transition.to, output: output }
|
|
70
|
-
end
|
|
69
|
+
def preflight_branch_count(transition)
|
|
70
|
+
return Parallel.validate_branch_count!(transition.fanout_config.fetch(:branches).length) if transition.fanout?
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
return unless @session_messages
|
|
74
|
-
return if output.nil?
|
|
75
|
-
|
|
76
|
-
@session_messages << { role: :assistant, content: output }
|
|
72
|
+
Parallel.resolve_branch_count(transition, @context) if transition.parallel?
|
|
77
73
|
end
|
|
78
74
|
|
|
79
75
|
def resolve_router_output(transition, output)
|
|
@@ -120,9 +116,11 @@ module Smith
|
|
|
120
116
|
end
|
|
121
117
|
|
|
122
118
|
def resolve_agent_class(transition)
|
|
123
|
-
|
|
119
|
+
parallel_binding = Thread.current[:smith_parallel_agent_binding]
|
|
120
|
+
parallel_agent_class = parallel_binding&.resolve(workflow: self, transition:)
|
|
121
|
+
return parallel_agent_class if parallel_agent_class
|
|
124
122
|
|
|
125
|
-
|
|
123
|
+
transition.agent_name && resolve_registered_agent!(
|
|
126
124
|
transition.agent_name,
|
|
127
125
|
workflow_class: self.class,
|
|
128
126
|
transition_name: transition.name,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
module ExecutionBindingResolution
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def resolve_registered_agent!(name, workflow_class:, transition_name:, role:)
|
|
9
|
+
authorization = @split_step_active_execution_authorization
|
|
10
|
+
return authorization.fetch_agent!(name, workflow_class:, transition_name:, role:) if authorization
|
|
11
|
+
|
|
12
|
+
Agent::Registry.fetch!(name, workflow_class:, transition_name:, role:)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-initializer"
|
|
4
|
+
|
|
5
|
+
require_relative "../errors"
|
|
6
|
+
require_relative "string_snapshot"
|
|
7
|
+
|
|
8
|
+
module Smith
|
|
9
|
+
class Workflow
|
|
10
|
+
class ExecutionResultSnapshot
|
|
11
|
+
MAX_DEPTH = 128
|
|
12
|
+
MAX_NODES = 100_000
|
|
13
|
+
MAX_BYTES = 4 * 1024 * 1024
|
|
14
|
+
|
|
15
|
+
extend Dry::Initializer
|
|
16
|
+
|
|
17
|
+
param :value
|
|
18
|
+
option :freeze_copy, default: proc { true }
|
|
19
|
+
option :preserved_error, default: proc {}
|
|
20
|
+
|
|
21
|
+
def call
|
|
22
|
+
reset
|
|
23
|
+
root = copy_value(value, depth: 0, preserve_error: true)
|
|
24
|
+
drain
|
|
25
|
+
@containers.each(&:freeze) if freeze_copy
|
|
26
|
+
root
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def reset
|
|
32
|
+
@bytes = 0
|
|
33
|
+
@nodes = 0
|
|
34
|
+
@copies = {}.compare_by_identity
|
|
35
|
+
@containers = []
|
|
36
|
+
@pending = []
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def drain
|
|
40
|
+
until @pending.empty?
|
|
41
|
+
source, copy, depth, preserve_error = @pending.pop
|
|
42
|
+
source.is_a?(Hash) ? copy_hash(source, copy, depth, preserve_error) : copy_array(source, copy, depth)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def copy_value(item, depth:, preserve_error: false)
|
|
47
|
+
visit!(depth)
|
|
48
|
+
return @copies.fetch(item) if @copies.key?(item)
|
|
49
|
+
|
|
50
|
+
case item
|
|
51
|
+
when Hash then prepare_container(item, {}, depth, preserve_error)
|
|
52
|
+
when Array then prepare_container(item, [], depth, false)
|
|
53
|
+
when String then copy_string(item)
|
|
54
|
+
when Float then copy_float(item)
|
|
55
|
+
when Symbol, Integer, true, false, nil then item
|
|
56
|
+
else
|
|
57
|
+
raise WorkflowError,
|
|
58
|
+
"prepared-step execution result contains unsupported mutable value #{item.class}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def prepare_container(source, copy, depth, preserve_error)
|
|
63
|
+
@copies[source] = copy
|
|
64
|
+
@containers << copy
|
|
65
|
+
@pending << [source, copy, depth, preserve_error]
|
|
66
|
+
copy
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def copy_hash(source, copy, depth, preserve_error)
|
|
70
|
+
source.each do |key, item|
|
|
71
|
+
copied_key = copy_hash_key(key, depth + 1)
|
|
72
|
+
copied_item = preserved_error?(copied_key, item, preserve_error) ? item : copy_value(item, depth: depth + 1)
|
|
73
|
+
copy[copied_key] = copied_item
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def copy_hash_key(key, depth)
|
|
78
|
+
unless key.is_a?(String) || key.is_a?(Symbol)
|
|
79
|
+
raise WorkflowError,
|
|
80
|
+
"prepared-step execution result contains unsupported Hash key #{key.class}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
copy_value(key, depth: depth)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def copy_array(source, copy, depth)
|
|
87
|
+
source.each { |item| copy << copy_value(item, depth: depth + 1) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def preserved_error?(key, item, permitted)
|
|
91
|
+
permitted && key == :error && item.equal?(preserved_error)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def copy_string(string)
|
|
95
|
+
@bytes += StringSnapshot.bytesize(string)
|
|
96
|
+
raise WorkflowError, "prepared-step execution result exceeds maximum bytes #{MAX_BYTES}" if
|
|
97
|
+
@bytes > MAX_BYTES
|
|
98
|
+
|
|
99
|
+
StringSnapshot.copy(string, freeze: freeze_copy)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def copy_float(float)
|
|
103
|
+
return float if float.finite?
|
|
104
|
+
|
|
105
|
+
raise WorkflowError, "prepared-step execution result contains non-finite Float"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def visit!(depth)
|
|
109
|
+
raise WorkflowError, "prepared-step execution result exceeds maximum depth #{MAX_DEPTH}" if depth > MAX_DEPTH
|
|
110
|
+
|
|
111
|
+
@nodes += 1
|
|
112
|
+
return if @nodes <= MAX_NODES
|
|
113
|
+
|
|
114
|
+
raise WorkflowError, "prepared-step execution result exceeds maximum size #{MAX_NODES}"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -35,29 +35,32 @@ module Smith
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
branch_calls = branches.map do |branch_key, agent_name|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
PreparedBranchExecution.instance_method(:prepared_branch).bind_call(
|
|
39
|
+
self,
|
|
40
|
+
FanoutExecution.instance_method(:run_fanout_branch),
|
|
41
|
+
branch_key,
|
|
42
|
+
agent_name,
|
|
43
|
+
branch_agent_classes.fetch(branch_key),
|
|
44
|
+
env
|
|
45
|
+
)
|
|
41
46
|
end
|
|
42
47
|
|
|
43
48
|
Parallel.execute(branches: branch_calls)
|
|
44
49
|
end
|
|
45
50
|
|
|
46
51
|
def run_fanout_branch(branch_key, agent_name, agent_class, env, signal)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
with_branch_context(env, @ledger, agent_class:) do
|
|
53
|
+
with_agent_context(agent_class) do
|
|
54
|
+
branch_ledger = effective_call_ledger
|
|
55
|
+
reserved = reserve_fanout_branch_call(branch_ledger, env.branch_estimates[branch_key], agent_class)
|
|
56
|
+
begin
|
|
57
|
+
result = guarded_fanout_branch_call(agent_class, env, signal)
|
|
58
|
+
finalize_named_branch(branch_key, agent_name, result, branch_ledger, reserved).tap { reserved = nil }
|
|
59
|
+
ensure
|
|
60
|
+
settle_budget_on_failure(branch_ledger, reserved, Thread.current[:smith_last_agent_result]) if reserved
|
|
61
|
+
end
|
|
57
62
|
end
|
|
58
63
|
end
|
|
59
|
-
ensure
|
|
60
|
-
teardown_branch_context(env)
|
|
61
64
|
end
|
|
62
65
|
|
|
63
66
|
def guarded_fanout_branch_call(agent_class, env, signal)
|
|
@@ -89,9 +92,7 @@ module Smith
|
|
|
89
92
|
end
|
|
90
93
|
|
|
91
94
|
def run_fanout_agent_input_guardrails(branch_agent_classes)
|
|
92
|
-
branch_agent_classes.each_value
|
|
93
|
-
run_agent_input_guardrails(agent_class)
|
|
94
|
-
end
|
|
95
|
+
branch_agent_classes.each_value { |agent_class| run_agent_input_guardrails(agent_class) }
|
|
95
96
|
end
|
|
96
97
|
|
|
97
98
|
def fanout_branch_estimates(branches, branch_agent_classes)
|
|
@@ -109,7 +110,7 @@ module Smith
|
|
|
109
110
|
end
|
|
110
111
|
|
|
111
112
|
def resolve_fanout_agent_class(transition, agent_name)
|
|
112
|
-
|
|
113
|
+
resolve_registered_agent!(
|
|
113
114
|
agent_name,
|
|
114
115
|
workflow_class: self.class,
|
|
115
116
|
transition_name: transition&.name,
|
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "diagnostic_path"
|
|
4
|
+
|
|
3
5
|
module Smith
|
|
4
6
|
class Workflow
|
|
5
7
|
class Graph
|
|
6
8
|
class Diagnostic
|
|
7
|
-
attr_reader :severity, :code, :
|
|
9
|
+
attr_reader :severity, :code, :state, :transition, :target, :suggestion
|
|
8
10
|
|
|
9
11
|
def initialize(**attributes)
|
|
10
12
|
@severity = attributes.fetch(:severity)
|
|
11
13
|
@code = attributes.fetch(:code)
|
|
12
14
|
@message = attributes.fetch(:message)
|
|
15
|
+
@path = attributes[:path]
|
|
13
16
|
@state = attributes[:state]
|
|
14
17
|
@transition = attributes[:transition]
|
|
15
18
|
@target = attributes[:target]
|
|
16
19
|
@suggestion = attributes[:suggestion]
|
|
17
20
|
end
|
|
18
21
|
|
|
22
|
+
def message
|
|
23
|
+
return @message unless @path
|
|
24
|
+
return @rendered_message if defined?(@rendered_message)
|
|
25
|
+
|
|
26
|
+
@rendered_message = String.new.tap do |rendered|
|
|
27
|
+
@path.each_label { |label| rendered << "Nested workflow #{label}: " }
|
|
28
|
+
rendered << @message
|
|
29
|
+
end.freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def nested(label:, code:, transition:, target:)
|
|
33
|
+
self.class.new(
|
|
34
|
+
severity: severity,
|
|
35
|
+
code: code,
|
|
36
|
+
message: @message,
|
|
37
|
+
path: DiagnosticPath.new(label:, tail: @path),
|
|
38
|
+
state: state,
|
|
39
|
+
transition: transition,
|
|
40
|
+
target: target,
|
|
41
|
+
suggestion: suggestion
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
19
45
|
def to_h
|
|
20
46
|
{
|
|
21
47
|
severity: severity,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-initializer"
|
|
4
|
+
|
|
5
|
+
module Smith
|
|
6
|
+
class Workflow
|
|
7
|
+
class Graph
|
|
8
|
+
class DiagnosticPath
|
|
9
|
+
extend Dry::Initializer
|
|
10
|
+
|
|
11
|
+
option :label
|
|
12
|
+
option :tail, default: proc {}
|
|
13
|
+
|
|
14
|
+
def each_label
|
|
15
|
+
current = self
|
|
16
|
+
while current
|
|
17
|
+
yield current.label
|
|
18
|
+
current = current.tail
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../transition_actionability"
|
|
4
|
+
|
|
5
|
+
module Smith
|
|
6
|
+
class Workflow
|
|
7
|
+
class Graph
|
|
8
|
+
class ExecutionSuccessors
|
|
9
|
+
attr_reader :graph
|
|
10
|
+
|
|
11
|
+
def initialize(graph)
|
|
12
|
+
@graph = graph
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initial_transition
|
|
16
|
+
graph.first_transition_from(graph.initial_state)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def for(transition)
|
|
20
|
+
successful_successors(transition) + failure_successors(transition)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def failure_control_marker(transition)
|
|
24
|
+
target = executable_target(transition.failure_transition, expected_state: transition.from)
|
|
25
|
+
target if target && !TransitionActionability.call(target)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def successful_successors(transition)
|
|
31
|
+
successful_target_names(transition).filter_map do |name|
|
|
32
|
+
executable_target(name, expected_state: transition.to)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def failure_successors(transition)
|
|
37
|
+
target = executable_target(transition.failure_transition, expected_state: transition.from)
|
|
38
|
+
return [] unless target
|
|
39
|
+
return [target] if TransitionActionability.call(target)
|
|
40
|
+
|
|
41
|
+
successor = graph.first_transition_from(target.to)
|
|
42
|
+
successor ? [successor] : []
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def successful_target_names(transition)
|
|
46
|
+
return Targets.router_for(transition) if transition.routed?
|
|
47
|
+
|
|
48
|
+
names = Array(transition.deterministic_routes).dup
|
|
49
|
+
name = transition.success_transition || graph.first_transition_from(transition.to)&.name
|
|
50
|
+
names << name if name
|
|
51
|
+
names.uniq
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def executable_target(name, expected_state:)
|
|
55
|
+
return if name.nil?
|
|
56
|
+
|
|
57
|
+
target = graph.transitions[name]
|
|
58
|
+
return unless target
|
|
59
|
+
|
|
60
|
+
target if target.from.nil? || target.from == expected_state
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -23,9 +23,7 @@ module Smith
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
25
|
def terminal_states
|
|
26
|
-
graph.states.select
|
|
27
|
-
graph.transitions.values.none? { |transition| transition.from == state }
|
|
28
|
-
end
|
|
26
|
+
graph.states.select { |state| graph.transitions_from(state).empty? }
|
|
29
27
|
end
|
|
30
28
|
end
|
|
31
29
|
end
|