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
|
@@ -9,7 +9,8 @@ module Smith
|
|
|
9
9
|
extend Dry::Initializer
|
|
10
10
|
|
|
11
11
|
param :graph
|
|
12
|
-
option :
|
|
12
|
+
option :reports
|
|
13
|
+
option :cycle_transitions, default: proc { {}.compare_by_identity }
|
|
13
14
|
|
|
14
15
|
def to_a
|
|
15
16
|
cycle_diagnostics + report_entries.flat_map { |entry| diagnostics_for(entry) }
|
|
@@ -28,27 +29,30 @@ module Smith
|
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def cycle_diagnostics
|
|
31
|
-
|
|
32
|
+
cycle_transitions.keys.map do |transition|
|
|
32
33
|
workflow_class = transition.workflow_class
|
|
33
|
-
cycle_diagnostic(transition, workflow_class)
|
|
34
|
+
cycle_diagnostic(transition, workflow_class)
|
|
34
35
|
end
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def report_entries
|
|
38
39
|
@report_entries ||= nested_workflow_transitions.filter_map do |transition|
|
|
40
|
+
next if cycle_transitions.key?(transition)
|
|
41
|
+
|
|
39
42
|
workflow_class = transition.workflow_class
|
|
40
|
-
|
|
43
|
+
report = reports[workflow_class]
|
|
44
|
+
next unless report
|
|
41
45
|
|
|
42
46
|
{
|
|
43
47
|
transition: transition,
|
|
44
48
|
workflow_class: workflow_class,
|
|
45
|
-
report:
|
|
49
|
+
report: report
|
|
46
50
|
}.freeze
|
|
47
51
|
end.freeze
|
|
48
52
|
end
|
|
49
53
|
|
|
50
54
|
def nested_workflow_transitions
|
|
51
|
-
@nested_workflow_transitions ||= graph.
|
|
55
|
+
@nested_workflow_transitions ||= graph.reachable_transitions.select(&:nested?)
|
|
52
56
|
end
|
|
53
57
|
|
|
54
58
|
def cycle_diagnostic(transition, workflow_class)
|
|
@@ -67,21 +71,16 @@ module Smith
|
|
|
67
71
|
|
|
68
72
|
def nested_diagnostic(transition, workflow_class, diagnostic)
|
|
69
73
|
label = workflow_label(workflow_class)
|
|
74
|
+
code = diagnostic.code.to_s.start_with?("nested_") ? diagnostic.code : :"nested_#{diagnostic.code}"
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
code:
|
|
76
|
+
diagnostic.nested(
|
|
77
|
+
label: label,
|
|
78
|
+
code: code,
|
|
74
79
|
transition: transition.name,
|
|
75
|
-
target: label
|
|
76
|
-
message: "Nested workflow #{label}: #{diagnostic.message}",
|
|
77
|
-
suggestion: diagnostic.suggestion
|
|
80
|
+
target: label
|
|
78
81
|
)
|
|
79
82
|
end
|
|
80
83
|
|
|
81
|
-
def visited_workflows
|
|
82
|
-
@visited_workflows ||= Set.new(Array(visited)).add(graph.workflow_class)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
84
|
def ref(value)
|
|
86
85
|
Reference.format(value)
|
|
87
86
|
end
|
|
@@ -44,10 +44,10 @@ module Smith
|
|
|
44
44
|
|
|
45
45
|
def evaluation_contract
|
|
46
46
|
{
|
|
47
|
-
evaluator_schema:
|
|
47
|
+
evaluator_schema: config.fetch(:evaluator_schema_label),
|
|
48
48
|
evaluator_context: config[:evaluator_context],
|
|
49
49
|
improvement_threshold: config[:improvement_threshold],
|
|
50
|
-
before_eval:
|
|
50
|
+
before_eval: config[:before_eval]
|
|
51
51
|
}
|
|
52
52
|
end
|
|
53
53
|
|
|
@@ -46,9 +46,9 @@ module Smith
|
|
|
46
46
|
|
|
47
47
|
def schema_contract
|
|
48
48
|
{
|
|
49
|
-
task_schema:
|
|
50
|
-
worker_output_schema:
|
|
51
|
-
final_output_schema:
|
|
49
|
+
task_schema: config.fetch(:task_schema_label),
|
|
50
|
+
worker_output_schema: config.fetch(:worker_output_schema_label),
|
|
51
|
+
final_output_schema: config.fetch(:final_output_schema_label)
|
|
52
52
|
}
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -11,47 +11,35 @@ module Smith
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def transition_names
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
reset_walk
|
|
17
|
-
drain_queue
|
|
18
|
-
@seen_transitions.keys
|
|
14
|
+
each_transition.map(&:name)
|
|
19
15
|
end
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
def each_transition
|
|
18
|
+
return enum_for(__method__) unless block_given?
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@seen_transitions = {}
|
|
26
|
-
@queue = [graph.initial_state]
|
|
27
|
-
end
|
|
20
|
+
root = successors.initial_transition
|
|
21
|
+
return unless root
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
transitions_from(@queue.shift).each { |transition| visit_transition(transition) } until @queue.empty?
|
|
23
|
+
walk(root) { yield _1 }
|
|
31
24
|
end
|
|
32
25
|
|
|
33
|
-
|
|
34
|
-
return if @seen_transitions.key?(transition.name)
|
|
35
|
-
|
|
36
|
-
@seen_transitions[transition.name] = true
|
|
37
|
-
enqueue_state(transition.to)
|
|
38
|
-
Targets.for(transition).each { |target_name| visit_named_transition(target_name) }
|
|
39
|
-
end
|
|
26
|
+
private
|
|
40
27
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
visit_transition(target) if target
|
|
28
|
+
def successors
|
|
29
|
+
@successors ||= ExecutionSuccessors.new(graph)
|
|
44
30
|
end
|
|
45
31
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
def walk(root)
|
|
33
|
+
seen = {}
|
|
34
|
+
stack = [root]
|
|
35
|
+
until stack.empty?
|
|
36
|
+
transition = stack.pop
|
|
37
|
+
next if seen.key?(transition.name)
|
|
52
38
|
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
seen[transition.name] = true
|
|
40
|
+
yield transition
|
|
41
|
+
successors.for(transition).reverse_each { stack << _1 }
|
|
42
|
+
end
|
|
55
43
|
end
|
|
56
44
|
end
|
|
57
45
|
end
|
|
@@ -9,11 +9,13 @@ module Smith
|
|
|
9
9
|
def initialize(graph, reachable_transition_names)
|
|
10
10
|
@graph = graph
|
|
11
11
|
@reachable_transition_names = reachable_transition_names
|
|
12
|
+
@reachable_transition_index = reachable_transition_names.to_h { [_1, true] }.freeze
|
|
13
|
+
@control_marker_index = control_marker_index
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def to_a
|
|
15
17
|
(graph.transitions.keys - reachable_transition_names).filter_map do |transition_name|
|
|
16
|
-
next if auto_fail_transition?(transition_name)
|
|
18
|
+
next if auto_fail_transition?(transition_name) || control_marker?(transition_name)
|
|
17
19
|
|
|
18
20
|
diagnostic_for(transition_name)
|
|
19
21
|
end
|
|
@@ -25,6 +27,20 @@ module Smith
|
|
|
25
27
|
transition_name == :fail && graph.transitions[transition_name]&.from.nil?
|
|
26
28
|
end
|
|
27
29
|
|
|
30
|
+
def control_marker?(transition_name)
|
|
31
|
+
@control_marker_index.key?(transition_name)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def control_marker_index
|
|
35
|
+
successors = ExecutionSuccessors.new(graph)
|
|
36
|
+
graph.transitions.each_value.with_object({}) do |transition, index|
|
|
37
|
+
next unless @reachable_transition_index.key?(transition.name)
|
|
38
|
+
|
|
39
|
+
marker = successors.failure_control_marker(transition)
|
|
40
|
+
index[marker.name] = true if marker
|
|
41
|
+
end.freeze
|
|
42
|
+
end
|
|
43
|
+
|
|
28
44
|
def diagnostic_for(transition_name)
|
|
29
45
|
transition = graph.transitions.fetch(transition_name)
|
|
30
46
|
Diagnostic.new(
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-initializer"
|
|
4
|
+
|
|
5
|
+
module Smith
|
|
6
|
+
class Workflow
|
|
7
|
+
class Graph
|
|
8
|
+
class RetryPolicyDiagnostic
|
|
9
|
+
extend Dry::Initializer
|
|
10
|
+
|
|
11
|
+
option :transition
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
attempts = transition.retry_config&.fetch(:attempts, nil)
|
|
15
|
+
limit = Smith.config.retry_attempt_limit
|
|
16
|
+
return unless attempts && attempts > limit
|
|
17
|
+
|
|
18
|
+
Diagnostic.new(
|
|
19
|
+
severity: :error,
|
|
20
|
+
code: :retry_attempt_limit_exceeded,
|
|
21
|
+
transition: transition.name,
|
|
22
|
+
message: "Transition #{ref(transition.name)} declares #{attempts} retry attempts, " \
|
|
23
|
+
"exceeding the configured limit #{limit}.",
|
|
24
|
+
suggestion: "Reduce the retry attempts or explicitly raise Smith.config.retry_attempt_limit."
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def ref(value)
|
|
31
|
+
Reference.format(value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -17,9 +17,9 @@ module Smith
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def agent_bindings
|
|
20
|
-
@agent_bindings ||= graph.
|
|
20
|
+
@agent_bindings ||= graph.reachable_transitions.flat_map do |transition|
|
|
21
21
|
bindings_for_transition(transition)
|
|
22
|
-
end
|
|
22
|
+
end.freeze
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
private
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "dry-initializer"
|
|
4
4
|
|
|
5
|
+
require_relative "runtime_readiness_traversal"
|
|
6
|
+
|
|
5
7
|
module Smith
|
|
6
8
|
class Workflow
|
|
7
9
|
class Graph
|
|
@@ -12,50 +14,7 @@ module Smith
|
|
|
12
14
|
option :visited, default: proc {}
|
|
13
15
|
|
|
14
16
|
def report
|
|
15
|
-
|
|
16
|
-
workflow_class: workflow_label(graph.workflow_class),
|
|
17
|
-
topology_report: topology_report,
|
|
18
|
-
runtime_diagnostics: runtime_diagnostics,
|
|
19
|
-
metrics: runtime_metrics
|
|
20
|
-
)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
def topology_report
|
|
26
|
-
@topology_report ||= graph.validate
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def runtime_diagnostics
|
|
30
|
-
@runtime_diagnostics ||= [
|
|
31
|
-
*binding_diagnostics.to_a,
|
|
32
|
-
*nested_diagnostics.to_a
|
|
33
|
-
]
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def binding_diagnostics
|
|
37
|
-
@binding_diagnostics ||= RuntimeBindingDiagnostics.new(graph)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def nested_diagnostics
|
|
41
|
-
@nested_diagnostics ||= NestedReadinessDiagnostics.new(graph, visited: visited)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def runtime_metrics
|
|
45
|
-
RuntimeReadinessMetrics.new(
|
|
46
|
-
graph,
|
|
47
|
-
topology_report: topology_report,
|
|
48
|
-
runtime_diagnostics: runtime_diagnostics,
|
|
49
|
-
agent_bindings: binding_diagnostics.agent_bindings,
|
|
50
|
-
nested_reports: nested_diagnostics.child_reports
|
|
51
|
-
).to_h
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def workflow_label(workflow_class)
|
|
55
|
-
name = workflow_class.name
|
|
56
|
-
return name if name && !name.empty?
|
|
57
|
-
|
|
58
|
-
workflow_class.inspect
|
|
17
|
+
RuntimeReadinessTraversal.new(graph, visited:).report
|
|
59
18
|
end
|
|
60
19
|
end
|
|
61
20
|
end
|
|
@@ -75,11 +75,11 @@ module Smith
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def fanout_transitions
|
|
78
|
-
@fanout_transitions ||= graph.
|
|
78
|
+
@fanout_transitions ||= graph.reachable_transitions.select(&:fanout?)
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
def direct_nested_workflow_count
|
|
82
|
-
graph.
|
|
82
|
+
graph.reachable_transitions.count(&:nested?)
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-initializer"
|
|
4
|
+
|
|
5
|
+
module Smith
|
|
6
|
+
class Workflow
|
|
7
|
+
class Graph
|
|
8
|
+
class RuntimeReadinessReportBuilder
|
|
9
|
+
extend Dry::Initializer
|
|
10
|
+
|
|
11
|
+
param :graph
|
|
12
|
+
option :reports
|
|
13
|
+
option :cycle_transitions
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
topology_report = graph.validate
|
|
17
|
+
bindings = RuntimeBindingDiagnostics.new(graph)
|
|
18
|
+
nested = NestedReadinessDiagnostics.new(graph, reports:, cycle_transitions:)
|
|
19
|
+
diagnostics = [*bindings.to_a, *nested.to_a]
|
|
20
|
+
RuntimeReadinessReport.new(
|
|
21
|
+
workflow_class: workflow_label,
|
|
22
|
+
topology_report: topology_report,
|
|
23
|
+
runtime_diagnostics: diagnostics,
|
|
24
|
+
metrics: metrics(topology_report, diagnostics, bindings, nested)
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def metrics(topology_report, diagnostics, bindings, nested)
|
|
31
|
+
RuntimeReadinessMetrics.new(
|
|
32
|
+
graph,
|
|
33
|
+
topology_report: topology_report,
|
|
34
|
+
runtime_diagnostics: diagnostics,
|
|
35
|
+
agent_bindings: bindings.agent_bindings,
|
|
36
|
+
nested_reports: nested.child_reports
|
|
37
|
+
).to_h
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def workflow_label
|
|
41
|
+
name = graph.workflow_class.name
|
|
42
|
+
name && !name.empty? ? name : graph.workflow_class.inspect
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-initializer"
|
|
4
|
+
|
|
5
|
+
require_relative "runtime_readiness_report_builder"
|
|
6
|
+
|
|
7
|
+
module Smith
|
|
8
|
+
class Workflow
|
|
9
|
+
class Graph
|
|
10
|
+
class RuntimeReadinessTraversal
|
|
11
|
+
extend Dry::Initializer
|
|
12
|
+
|
|
13
|
+
param :graph
|
|
14
|
+
option :visited, default: proc {}
|
|
15
|
+
|
|
16
|
+
def report
|
|
17
|
+
initialize_walk
|
|
18
|
+
inspect_graphs
|
|
19
|
+
@reports.fetch(graph.workflow_class)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def initialize_walk
|
|
25
|
+
@states = {}.compare_by_identity
|
|
26
|
+
@reports = {}.compare_by_identity
|
|
27
|
+
@graphs = {}.compare_by_identity
|
|
28
|
+
@nested_transitions = {}.compare_by_identity
|
|
29
|
+
@cycle_transitions = {}.compare_by_identity
|
|
30
|
+
visited_workflows.each { @states[_1] = :visiting }
|
|
31
|
+
@graphs[graph.workflow_class] = graph
|
|
32
|
+
@states[graph.workflow_class] = :visiting
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def inspect_graphs
|
|
36
|
+
frames = [frame_for(graph)]
|
|
37
|
+
until frames.empty?
|
|
38
|
+
frame = frames.last
|
|
39
|
+
if frame.fetch(:index) >= frame.fetch(:transitions).length
|
|
40
|
+
complete_graph(frame.fetch(:graph))
|
|
41
|
+
frames.pop
|
|
42
|
+
next
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
transition = next_transition(frame)
|
|
46
|
+
visit_child(frame.fetch(:graph), transition, frames)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def frame_for(current_graph)
|
|
51
|
+
{
|
|
52
|
+
graph: current_graph,
|
|
53
|
+
transitions: nested_transitions_for(current_graph),
|
|
54
|
+
index: 0
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def next_transition(frame)
|
|
59
|
+
transition = frame.fetch(:transitions).fetch(frame.fetch(:index))
|
|
60
|
+
frame[:index] += 1
|
|
61
|
+
transition
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def visit_child(parent_graph, transition, frames)
|
|
65
|
+
workflow_class = transition.workflow_class
|
|
66
|
+
case @states[workflow_class]
|
|
67
|
+
when :visiting then mark_cycle(parent_graph, transition)
|
|
68
|
+
when :done then nil
|
|
69
|
+
else
|
|
70
|
+
child_graph = graph_for(workflow_class)
|
|
71
|
+
@states[workflow_class] = :visiting
|
|
72
|
+
frames << frame_for(child_graph)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def complete_graph(current_graph)
|
|
77
|
+
workflow_class = current_graph.workflow_class
|
|
78
|
+
@reports[workflow_class] = RuntimeReadinessReportBuilder.new(
|
|
79
|
+
current_graph,
|
|
80
|
+
reports: @reports,
|
|
81
|
+
cycle_transitions: cycles_for(current_graph)
|
|
82
|
+
).call
|
|
83
|
+
@states[workflow_class] = :done
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def graph_for(workflow_class)
|
|
87
|
+
@graphs[workflow_class] ||= workflow_class.graph
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def nested_transitions_for(current_graph)
|
|
91
|
+
@nested_transitions[current_graph] ||= current_graph.reachable_transitions.select(&:nested?).freeze
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def mark_cycle(current_graph, transition)
|
|
95
|
+
cycles_for(current_graph)[transition] = true
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def cycles_for(current_graph)
|
|
99
|
+
@cycle_transitions[current_graph] ||= {}.compare_by_identity
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def visited_workflows
|
|
103
|
+
visited.respond_to?(:keys) ? visited.keys : Array(visited)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -20,7 +20,7 @@ module Smith
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
22
|
def initial_state_diagnostics
|
|
23
|
-
return [] if graph.initial_state && graph.
|
|
23
|
+
return [] if graph.initial_state && graph.state?(graph.initial_state)
|
|
24
24
|
|
|
25
25
|
[
|
|
26
26
|
Diagnostic.new(
|
|
@@ -44,7 +44,7 @@ module Smith
|
|
|
44
44
|
|
|
45
45
|
def undefined_state_diagnostic(transition, edge, state)
|
|
46
46
|
return if edge == :from && state.nil?
|
|
47
|
-
return if graph.
|
|
47
|
+
return if graph.state?(state)
|
|
48
48
|
|
|
49
49
|
Diagnostic.new(
|
|
50
50
|
severity: :error,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-initializer"
|
|
4
|
+
|
|
5
|
+
require_relative "transition_contract_attributes"
|
|
6
|
+
|
|
7
|
+
module Smith
|
|
8
|
+
class Workflow
|
|
9
|
+
class Graph
|
|
10
|
+
class TransitionContract
|
|
11
|
+
extend Dry::Initializer
|
|
12
|
+
|
|
13
|
+
option :name
|
|
14
|
+
option :definition_index
|
|
15
|
+
option :from
|
|
16
|
+
option :to
|
|
17
|
+
option :agent_name, default: proc {}
|
|
18
|
+
option :success_transition, default: proc {}
|
|
19
|
+
option :failure_transition, default: proc {}
|
|
20
|
+
option :router_config, default: proc {}
|
|
21
|
+
option :workflow_class, default: proc {}
|
|
22
|
+
option :optimization_config, default: proc {}
|
|
23
|
+
option :orchestrator_config, default: proc {}
|
|
24
|
+
option :fanout_config, default: proc {}
|
|
25
|
+
option :retry_config, default: proc {}
|
|
26
|
+
option :deterministic_kind, default: proc {}
|
|
27
|
+
option :deterministic_routes, default: proc {}
|
|
28
|
+
option :deterministic, default: proc { false }
|
|
29
|
+
option :parallel, default: proc { false }
|
|
30
|
+
option :parallel_count, default: proc {}
|
|
31
|
+
|
|
32
|
+
def self.from_transition(transition, identifiers:, definition_index:)
|
|
33
|
+
attributes = TransitionContractAttributes.new(transition, identifiers:).to_h
|
|
34
|
+
new(**attributes, definition_index:).freeze
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def deterministic? = deterministic
|
|
38
|
+
def orchestrated? = !orchestrator_config.nil?
|
|
39
|
+
def fanout? = !fanout_config.nil?
|
|
40
|
+
def optimized? = !optimization_config.nil?
|
|
41
|
+
def nested? = !workflow_class.nil?
|
|
42
|
+
def routed? = !router_config.nil?
|
|
43
|
+
def parallel? = parallel
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-initializer"
|
|
4
|
+
|
|
5
|
+
require_relative "transition_contract_configurations"
|
|
6
|
+
|
|
7
|
+
module Smith
|
|
8
|
+
class Workflow
|
|
9
|
+
class Graph
|
|
10
|
+
class TransitionContractAttributes
|
|
11
|
+
extend Dry::Initializer
|
|
12
|
+
|
|
13
|
+
param :transition
|
|
14
|
+
option :identifiers
|
|
15
|
+
|
|
16
|
+
def to_h
|
|
17
|
+
identity_attributes
|
|
18
|
+
.merge(routing_attributes)
|
|
19
|
+
.merge(kind_attributes)
|
|
20
|
+
.merge(TransitionContractConfigurations.new(transition, identifiers:).to_h)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def identity_attributes
|
|
26
|
+
{
|
|
27
|
+
name: identifiers.call(transition.name),
|
|
28
|
+
from: identifiers.call(transition.from),
|
|
29
|
+
to: identifiers.call(transition.to),
|
|
30
|
+
agent_name: own(transition.agent_name),
|
|
31
|
+
workflow_class: transition.workflow_class
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def routing_attributes
|
|
36
|
+
{
|
|
37
|
+
success_transition: project(transition.success_transition),
|
|
38
|
+
failure_transition: project(transition.failure_transition),
|
|
39
|
+
deterministic_routes: project_array(transition.deterministic_routes)
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def kind_attributes
|
|
44
|
+
{
|
|
45
|
+
deterministic_kind: transition.deterministic_kind,
|
|
46
|
+
deterministic: transition.deterministic?,
|
|
47
|
+
parallel: transition.parallel?,
|
|
48
|
+
parallel_count: static_parallel_count
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def static_parallel_count
|
|
53
|
+
return unless transition.parallel?
|
|
54
|
+
|
|
55
|
+
count = transition.agent_opts[:count]
|
|
56
|
+
count.respond_to?(:call) ? nil : (count || 1)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def project_array(array)
|
|
60
|
+
array&.map { identifiers.call(_1) }&.freeze
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def project(value)
|
|
64
|
+
identifiers.call(value) unless value.nil?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def own(value)
|
|
68
|
+
value.is_a?(String) ? value.dup.freeze : value
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|