smith-agents 0.4.4 → 0.5.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 +262 -0
- data/README.md +116 -6
- data/docs/CONFIGURATION.md +4 -1
- data/docs/PATTERNS.md +7 -0
- data/docs/PERSISTENCE.md +502 -1
- data/docs/workflow_claim.md +4 -1
- 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 +24 -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 -3
- data/lib/smith/error.rb +5 -0
- data/lib/smith/errors.rb +6 -0
- data/lib/smith/exponential_backoff.rb +128 -0
- data/lib/smith/persistence_adapters/active_record_connection_errors.rb +23 -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_initial_write.rb +42 -0
- data/lib/smith/persistence_adapters/active_record_store.rb +95 -52
- data/lib/smith/persistence_adapters/cache_store.rb +4 -1
- data/lib/smith/persistence_adapters/memory.rb +45 -14
- data/lib/smith/persistence_adapters/payload_version.rb +23 -0
- 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 +38 -43
- data/lib/smith/persistence_adapters/redis_versioned_write.rb +56 -0
- data/lib/smith/persistence_adapters/retry.rb +22 -10
- data/lib/smith/persistence_adapters/version_expectation.rb +19 -0
- data/lib/smith/persistence_adapters.rb +26 -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/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 +2 -1
- data/lib/smith/workflow/evaluator_optimizer.rb +7 -11
- data/lib/smith/workflow/execution.rb +16 -18
- 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 +1 -1
- 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 +133 -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 +7 -3
- data/lib/smith/workflow/persistence.rb +49 -10
- 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 +116 -0
- data/lib/smith/workflow/prepared_step_execution_result.rb +49 -0
- data/lib/smith/workflow/prepared_step_execution_scope.rb +45 -0
- data/lib/smith/workflow/prepared_step_recovery.rb +38 -0
- data/lib/smith/workflow/retry_execution.rb +20 -11
- data/lib/smith/workflow/split_step_persistence/boundary.rb +128 -0
- 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 +119 -0
- data/lib/smith/workflow/split_step_persistence/checkpoint_state.rb +51 -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 +128 -0
- data/lib/smith/workflow/split_step_persistence/execution_authorization.rb +89 -0
- data/lib/smith/workflow/split_step_persistence/execution_binding_collector.rb +90 -0
- data/lib/smith/workflow/split_step_persistence/execution_binding_snapshot.rb +95 -0
- data/lib/smith/workflow/split_step_persistence/execution_result_capture.rb +48 -0
- data/lib/smith/workflow/split_step_persistence/execution_verification.rb +65 -0
- data/lib/smith/workflow/split_step_persistence/execution_workflow_snapshot.rb +77 -0
- data/lib/smith/workflow/split_step_persistence/inheritance.rb +22 -0
- data/lib/smith/workflow/split_step_persistence/payloads.rb +54 -0
- data/lib/smith/workflow/split_step_persistence/preparation.rb +125 -0
- data/lib/smith/workflow/split_step_persistence/preparation_claim.rb +117 -0
- data/lib/smith/workflow/split_step_persistence/preparation_payload.rb +25 -0
- data/lib/smith/workflow/split_step_persistence/preparation_recovery.rb +54 -0
- 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/state_snapshot.rb +84 -0
- data/lib/smith/workflow/split_step_persistence/subclass_boundary.rb +102 -0
- data/lib/smith/workflow/split_step_persistence/transaction_identity.rb +45 -0
- data/lib/smith/workflow/split_step_persistence/transition_contract.rb +48 -0
- data/lib/smith/workflow/split_step_persistence/transition_contract_freezer.rb +83 -0
- data/lib/smith/workflow/split_step_persistence/transition_contract_signature.rb +107 -0
- data/lib/smith/workflow/split_step_persistence/transition_contract_structured_values.rb +51 -0
- data/lib/smith/workflow/split_step_persistence.rb +79 -0
- data/lib/smith/workflow/step_completion.rb +31 -0
- data/lib/smith/workflow/string_snapshot.rb +25 -0
- data/lib/smith/workflow/transition.rb +122 -133
- data/lib/smith/workflow/transition_actionability.rb +22 -0
- data/lib/smith/workflow.rb +57 -19
- data/lib/smith.rb +42 -2
- data/sig/smith.rbs +1 -0
- metadata +131 -19
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
module SplitStepPersistence
|
|
6
|
+
module StateSnapshot
|
|
7
|
+
def to_state
|
|
8
|
+
state = super
|
|
9
|
+
return state unless split_step_boundary_active?
|
|
10
|
+
|
|
11
|
+
snapshot_value(state)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def session_messages
|
|
15
|
+
return super unless split_step_boundary_active?
|
|
16
|
+
|
|
17
|
+
snapshot_session_messages
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def ledger
|
|
21
|
+
return super unless split_step_boundary_active?
|
|
22
|
+
return unless @ledger
|
|
23
|
+
|
|
24
|
+
snapshot_split_step_ledger
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def effective_persistence_ttl
|
|
30
|
+
return super unless split_step_boundary_active?
|
|
31
|
+
return super unless instance_variable_defined?(:@split_step_persistence_ttl)
|
|
32
|
+
|
|
33
|
+
@split_step_persistence_ttl
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ttl_kwarg(ttl)
|
|
37
|
+
return super unless split_step_boundary_active?
|
|
38
|
+
return super unless instance_variable_defined?(:@split_step_persistence_ttl)
|
|
39
|
+
|
|
40
|
+
{ ttl: ttl }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def detach_split_step_execution_state!
|
|
44
|
+
detach_split_step_collections!
|
|
45
|
+
detach_split_step_results!
|
|
46
|
+
detach_split_step_metadata!
|
|
47
|
+
@ledger = snapshot_split_step_ledger if @ledger
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def detach_split_step_collections!
|
|
51
|
+
@context = snapshot_context
|
|
52
|
+
@session_messages = snapshot_session_messages
|
|
53
|
+
@tool_results = snapshot_tool_results
|
|
54
|
+
@outcome = snapshot_outcome
|
|
55
|
+
@usage_entries = snapshot_usage_entries
|
|
56
|
+
@persisted_keys = Set.new(snapshot_value((@persisted_keys || Set.new).to_a))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def detach_split_step_results!
|
|
60
|
+
@last_output = snapshot_value(@last_output)
|
|
61
|
+
@last_failed_step = snapshot_value(@last_failed_step)
|
|
62
|
+
@last_prepared_input = snapshot_value(@last_prepared_input)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def detach_split_step_metadata!
|
|
66
|
+
@created_at = snapshot_value(@created_at)
|
|
67
|
+
@updated_at = snapshot_value(@updated_at)
|
|
68
|
+
@execution_namespace = snapshot_value(@execution_namespace)
|
|
69
|
+
@seed_digest = snapshot_value(@seed_digest)
|
|
70
|
+
@router_next_transition = snapshot_value(@router_next_transition)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def snapshot_split_step_ledger
|
|
74
|
+
Budget::Ledger.new(
|
|
75
|
+
limits: snapshot_value(@ledger.limits),
|
|
76
|
+
consumed: snapshot_value(@ledger.consumed)
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def split_step_boundary_active? = !@split_step_phase.nil?
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
module SplitStepPersistence
|
|
6
|
+
module SubclassBoundary
|
|
7
|
+
class << self
|
|
8
|
+
def protect_execution_path!(workflow_class, *owners)
|
|
9
|
+
protected_method_names(owners).each do |method_name|
|
|
10
|
+
protect_method!(workflow_class, method_name)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def protected_method_names(owners)
|
|
17
|
+
owners.flat_map do |owner|
|
|
18
|
+
owner.private_instance_methods(false) + owner.protected_instance_methods(false)
|
|
19
|
+
end.uniq
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def protect_method!(workflow_class, method_name)
|
|
23
|
+
return if boundary_method_names.include?(method_name)
|
|
24
|
+
|
|
25
|
+
implementation = workflow_class.instance_method(method_name)
|
|
26
|
+
define_method(method_name) do |*arguments, **keywords, &block|
|
|
27
|
+
active = SubclassBoundary
|
|
28
|
+
.instance_method(:smith_prepared_execution_active?)
|
|
29
|
+
.bind_call(self)
|
|
30
|
+
if active
|
|
31
|
+
implementation.bind_call(self, *arguments, **keywords, &block)
|
|
32
|
+
else
|
|
33
|
+
super(*arguments, **keywords, &block)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
private method_name
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def boundary_method_names
|
|
40
|
+
instance_methods(false) + private_instance_methods(false) + protected_instance_methods(false)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.guard
|
|
45
|
+
Module.new.tap do |guard|
|
|
46
|
+
instance_methods(false).each do |method_name|
|
|
47
|
+
guard.define_method(method_name, instance_method(method_name))
|
|
48
|
+
end
|
|
49
|
+
private_instance_methods(false).each do |method_name|
|
|
50
|
+
guard.define_method(method_name, instance_method(method_name))
|
|
51
|
+
guard.send(:private, method_name)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def to_state
|
|
57
|
+
state = super
|
|
58
|
+
split_step_boundary_active? ? snapshot_value(state) : state
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def advance!
|
|
62
|
+
SplitStepPersistence.instance_method(:guard_split_step_subclass_execution!).bind_call(self)
|
|
63
|
+
super
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def run!
|
|
67
|
+
SplitStepPersistence.instance_method(:guard_split_step_subclass_execution!).bind_call(self)
|
|
68
|
+
super
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def smith_prepared_execution_active?
|
|
74
|
+
root_execution = @split_step_phase == :executing &&
|
|
75
|
+
@split_step_execution_thread.equal?(Thread.current)
|
|
76
|
+
return true if root_execution
|
|
77
|
+
|
|
78
|
+
authorization = @split_step_active_execution_authorization
|
|
79
|
+
authorization&.active_in_current_execution? == true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def effective_persistence_ttl
|
|
83
|
+
return super unless split_step_boundary_active?
|
|
84
|
+
return super unless instance_variable_defined?(:@split_step_persistence_ttl)
|
|
85
|
+
|
|
86
|
+
@split_step_persistence_ttl
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def ttl_kwarg(ttl)
|
|
90
|
+
return super unless split_step_boundary_active?
|
|
91
|
+
return super unless instance_variable_defined?(:@split_step_persistence_ttl)
|
|
92
|
+
|
|
93
|
+
{ ttl: ttl }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def dispatch_store!(...)
|
|
97
|
+
DispatchBoundary.instance_method(:dispatch_store!).bind_call(self, ...)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-initializer"
|
|
4
|
+
|
|
5
|
+
module Smith
|
|
6
|
+
class Workflow
|
|
7
|
+
module SplitStepPersistence
|
|
8
|
+
class TransactionIdentity
|
|
9
|
+
extend Dry::Initializer
|
|
10
|
+
|
|
11
|
+
param :adapter
|
|
12
|
+
|
|
13
|
+
def self.capture(adapter) = new(adapter).capture
|
|
14
|
+
def self.matches?(adapter, expected) = new(adapter).matches?(expected)
|
|
15
|
+
|
|
16
|
+
def capture
|
|
17
|
+
return unless PersistenceAdapters.supports?(adapter, :transaction_open?)
|
|
18
|
+
return unless adapter.transaction_open?
|
|
19
|
+
|
|
20
|
+
ensure_identity_capability!
|
|
21
|
+
identity = adapter.transaction_identity
|
|
22
|
+
if !identity || identity.to_s.empty?
|
|
23
|
+
raise WorkflowError, "transactional split-step persistence requires an active transaction identity"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
identity.to_s.dup.freeze
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def matches?(expected)
|
|
30
|
+
capture == expected
|
|
31
|
+
rescue StandardError
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def ensure_identity_capability!
|
|
38
|
+
return if PersistenceAdapters.supports?(adapter, :transaction_identity)
|
|
39
|
+
|
|
40
|
+
raise WorkflowError, "transactional split-step persistence requires transaction_identity"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-initializer"
|
|
4
|
+
|
|
5
|
+
module Smith
|
|
6
|
+
class Workflow
|
|
7
|
+
module SplitStepPersistence
|
|
8
|
+
class TransitionContract
|
|
9
|
+
extend Dry::Initializer
|
|
10
|
+
|
|
11
|
+
MAX_DEPTH = 128
|
|
12
|
+
MAX_NODES = 10_000
|
|
13
|
+
MAX_BYTES = 4 * 1024 * 1024
|
|
14
|
+
|
|
15
|
+
param :transition
|
|
16
|
+
|
|
17
|
+
def self.capture(transition) = new(transition).capture
|
|
18
|
+
def self.signature(transition) = new(transition).signature
|
|
19
|
+
|
|
20
|
+
def capture
|
|
21
|
+
captured = signature
|
|
22
|
+
freeze_value(transition)
|
|
23
|
+
freeze_value(captured)
|
|
24
|
+
captured
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def signature
|
|
28
|
+
TransitionContractSignature.new(
|
|
29
|
+
value: transition,
|
|
30
|
+
max_depth: MAX_DEPTH,
|
|
31
|
+
max_nodes: MAX_NODES,
|
|
32
|
+
max_bytes: MAX_BYTES
|
|
33
|
+
).call
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def freeze_value(value)
|
|
39
|
+
TransitionContractFreezer.new(
|
|
40
|
+
value: value,
|
|
41
|
+
max_depth: MAX_DEPTH,
|
|
42
|
+
max_nodes: MAX_NODES
|
|
43
|
+
).call
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-initializer"
|
|
4
|
+
|
|
5
|
+
module Smith
|
|
6
|
+
class Workflow
|
|
7
|
+
module SplitStepPersistence
|
|
8
|
+
class TransitionContractFreezer
|
|
9
|
+
extend Dry::Initializer
|
|
10
|
+
|
|
11
|
+
CORE_FREEZE = Object.instance_method(:freeze)
|
|
12
|
+
|
|
13
|
+
option :value
|
|
14
|
+
option :max_depth
|
|
15
|
+
option :max_nodes
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
stack = [[value, 0]]
|
|
19
|
+
seen = {}.compare_by_identity
|
|
20
|
+
nodes = 0
|
|
21
|
+
until stack.empty?
|
|
22
|
+
item, depth = stack.pop
|
|
23
|
+
nodes = visit!(nodes, depth)
|
|
24
|
+
freeze_item(item, depth, stack, seen)
|
|
25
|
+
end
|
|
26
|
+
value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def visit!(nodes, depth)
|
|
32
|
+
raise WorkflowError, "split-step transition contract exceeds maximum depth #{max_depth}" if depth > max_depth
|
|
33
|
+
return nodes + 1 if nodes < max_nodes
|
|
34
|
+
|
|
35
|
+
raise WorkflowError, "split-step transition contract exceeds maximum size #{max_nodes}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def freeze_item(item, depth, stack, seen)
|
|
39
|
+
return if immutable?(item) || item.is_a?(Module) || seen.key?(item)
|
|
40
|
+
|
|
41
|
+
seen[item] = true
|
|
42
|
+
children(item).reverse_each { |child| stack << [child, depth + 1] }
|
|
43
|
+
CORE_FREEZE.bind_call(item)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def children(item)
|
|
47
|
+
return [] if leaf?(item)
|
|
48
|
+
return item.flat_map { |key, value| [key, value] } if item.is_a?(Hash)
|
|
49
|
+
return [item.begin, item.end] if item.is_a?(Range)
|
|
50
|
+
|
|
51
|
+
object_children(item)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def object_children(item)
|
|
55
|
+
return item.to_a if item.is_a?(Array) || item.is_a?(Set)
|
|
56
|
+
return member_children(item) if item.is_a?(Struct) || item.is_a?(Data)
|
|
57
|
+
|
|
58
|
+
transition_children(item)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def member_children(item)
|
|
62
|
+
item.members.map { |name| item.public_send(name) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def transition_children(item)
|
|
66
|
+
unless item.is_a?(Workflow::Transition)
|
|
67
|
+
raise WorkflowError, "split-step transition contract contains unsupported value #{item.class}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
item.instance_variables.map { |name| item.instance_variable_get(name) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def leaf?(item)
|
|
74
|
+
item.is_a?(String) || item.is_a?(Proc)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def immutable?(item)
|
|
78
|
+
item.nil? || item.is_a?(Symbol) || item.is_a?(Numeric) || item == true || item == false
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-initializer"
|
|
4
|
+
require "digest"
|
|
5
|
+
|
|
6
|
+
module Smith
|
|
7
|
+
class Workflow
|
|
8
|
+
module SplitStepPersistence
|
|
9
|
+
class TransitionContractSignature
|
|
10
|
+
extend Dry::Initializer
|
|
11
|
+
include TransitionContractStructuredValues
|
|
12
|
+
|
|
13
|
+
option :value
|
|
14
|
+
option :max_depth
|
|
15
|
+
option :max_nodes
|
|
16
|
+
option :max_bytes
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
@nodes = 0
|
|
20
|
+
@bytes = 0
|
|
21
|
+
@seen = {}.compare_by_identity
|
|
22
|
+
signature_value(value, 0)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def signature_value(item, depth)
|
|
28
|
+
visit!(depth)
|
|
29
|
+
reference = reference_for(item)
|
|
30
|
+
return reference if reference
|
|
31
|
+
return terminal_signature(item) if terminal?(item)
|
|
32
|
+
|
|
33
|
+
composite_signature(item, depth)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def composite_signature(item, depth)
|
|
37
|
+
case item
|
|
38
|
+
when Hash then hash_signature(item, depth)
|
|
39
|
+
when Array then collection_signature(:array, item, depth)
|
|
40
|
+
when Set then collection_signature(:set, item, depth)
|
|
41
|
+
else structured_signature(item, depth)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def terminal_signature(item)
|
|
46
|
+
case item
|
|
47
|
+
when String then string_signature(item)
|
|
48
|
+
when Module then [:module, item.name, item.object_id]
|
|
49
|
+
when Proc then proc_signature(item)
|
|
50
|
+
else item
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def hash_signature(hash, depth)
|
|
55
|
+
[:hash, reference_id(hash), hash.map do |key, item|
|
|
56
|
+
[signature_value(key, depth + 1), signature_value(item, depth + 1)]
|
|
57
|
+
end]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def collection_signature(kind, collection, depth)
|
|
61
|
+
[kind, reference_id(collection), collection.map { |item| signature_value(item, depth + 1) }]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def proc_signature(callable)
|
|
65
|
+
[:proc, callable.object_id, callable.source_location, callable.parameters, callable.lambda?]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def string_signature(string)
|
|
69
|
+
@bytes += string.bytesize
|
|
70
|
+
raise WorkflowError, "split-step transition contract exceeds maximum bytes #{max_bytes}" if @bytes > max_bytes
|
|
71
|
+
|
|
72
|
+
[:string, string.bytesize, Digest::SHA256.hexdigest(string)]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def reference_for(item)
|
|
76
|
+
return unless referenceable?(item)
|
|
77
|
+
return [:reference, reference_id(item)] if @seen.key?(item)
|
|
78
|
+
|
|
79
|
+
@seen[item] = @seen.size
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def reference_id(item) = @seen.fetch(item)
|
|
84
|
+
|
|
85
|
+
def referenceable?(item)
|
|
86
|
+
!terminal?(item)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def terminal?(item)
|
|
90
|
+
case item
|
|
91
|
+
when String, Symbol, Numeric, Module, Proc, true, false, nil then true
|
|
92
|
+
else false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def visit!(depth)
|
|
97
|
+
raise WorkflowError, "split-step transition contract exceeds maximum depth #{max_depth}" if depth > max_depth
|
|
98
|
+
|
|
99
|
+
@nodes += 1
|
|
100
|
+
return if @nodes <= max_nodes
|
|
101
|
+
|
|
102
|
+
raise WorkflowError, "split-step transition contract exceeds maximum size #{max_nodes}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
module SplitStepPersistence
|
|
6
|
+
module TransitionContractStructuredValues
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def structured_signature(item, depth)
|
|
10
|
+
case item
|
|
11
|
+
when Struct then member_signature(:struct, item, depth)
|
|
12
|
+
when Data then member_signature(:data, item, depth)
|
|
13
|
+
when Range then range_signature(item, depth)
|
|
14
|
+
when Workflow::Transition then object_signature(item, depth)
|
|
15
|
+
else unsupported!(item)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def member_signature(kind, object, depth)
|
|
20
|
+
members = object.members.map do |name|
|
|
21
|
+
[name, signature_value(object.public_send(name), depth + 1)]
|
|
22
|
+
end
|
|
23
|
+
[kind, object.class.name, reference_id(object), members]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def object_signature(item, depth)
|
|
27
|
+
variables = item.instance_variables.sort.map do |name|
|
|
28
|
+
[name, signature_value(item.instance_variable_get(name), depth + 1)]
|
|
29
|
+
end
|
|
30
|
+
[:object, item.class.name, item.object_id, variables]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def range_signature(range, depth)
|
|
34
|
+
[
|
|
35
|
+
:range,
|
|
36
|
+
reference_id(range),
|
|
37
|
+
signature_value(range.begin, depth + 1),
|
|
38
|
+
signature_value(range.end, depth + 1),
|
|
39
|
+
range.exclude_end?
|
|
40
|
+
]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def unsupported!(item)
|
|
44
|
+
raise WorkflowError,
|
|
45
|
+
"split-step transition contract contains unsupported value #{item.class}; " \
|
|
46
|
+
"use primitives, Hash, Array, Set, Struct, Data, Range, Module, or Proc"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "json"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
|
|
7
|
+
require_relative "prepared_step"
|
|
8
|
+
require_relative "prepared_step_dispatch"
|
|
9
|
+
require_relative "prepared_step_execution_authorization"
|
|
10
|
+
require_relative "prepared_step_execution_result"
|
|
11
|
+
require_relative "prepared_step_recovery"
|
|
12
|
+
require_relative "split_step_persistence/subclass_boundary"
|
|
13
|
+
require_relative "split_step_persistence/inheritance"
|
|
14
|
+
require_relative "split_step_persistence/recovery_class_methods"
|
|
15
|
+
require_relative "split_step_persistence/transition_contract_structured_values"
|
|
16
|
+
require_relative "split_step_persistence/transition_contract_signature"
|
|
17
|
+
require_relative "split_step_persistence/transition_contract_freezer"
|
|
18
|
+
require_relative "split_step_persistence/transition_contract"
|
|
19
|
+
require_relative "split_step_persistence/transaction_identity"
|
|
20
|
+
require_relative "split_step_persistence/canonical_payload_digest"
|
|
21
|
+
require_relative "split_step_persistence/definition_boundary"
|
|
22
|
+
require_relative "split_step_persistence/boundary"
|
|
23
|
+
require_relative "split_step_persistence/boundary_reset"
|
|
24
|
+
require_relative "split_step_persistence/state_snapshot"
|
|
25
|
+
require_relative "split_step_persistence/preparation_claim"
|
|
26
|
+
require_relative "split_step_persistence/preparation_recovery"
|
|
27
|
+
require_relative "split_step_persistence/preparation"
|
|
28
|
+
require_relative "split_step_persistence/restart_safe_adapter"
|
|
29
|
+
require_relative "split_step_persistence/recovery"
|
|
30
|
+
require_relative "split_step_persistence/recovery_boundary"
|
|
31
|
+
require_relative "split_step_persistence/preparation_payload"
|
|
32
|
+
require_relative "split_step_persistence/dispatch_boundary"
|
|
33
|
+
require_relative "split_step_persistence/dispatch_claim"
|
|
34
|
+
require_relative "split_step_persistence/dispatch_verification"
|
|
35
|
+
require_relative "split_step_persistence/dispatch_confirmation"
|
|
36
|
+
require_relative "split_step_persistence/execution_verification"
|
|
37
|
+
require_relative "split_step_persistence/execution_binding_snapshot"
|
|
38
|
+
require_relative "split_step_persistence/execution_authorization"
|
|
39
|
+
require_relative "split_step_persistence/execution_result_capture"
|
|
40
|
+
require_relative "split_step_persistence/execution"
|
|
41
|
+
require_relative "split_step_persistence/checkpoint_state"
|
|
42
|
+
require_relative "split_step_persistence/checkpoint"
|
|
43
|
+
require_relative "split_step_persistence/payloads"
|
|
44
|
+
|
|
45
|
+
module Smith
|
|
46
|
+
class Workflow
|
|
47
|
+
module SplitStepPersistence
|
|
48
|
+
NO_SPLIT_TRANSITION = Object.new.freeze
|
|
49
|
+
|
|
50
|
+
def self.prepended(base)
|
|
51
|
+
base.singleton_class.prepend(Inheritance)
|
|
52
|
+
base.extend(RecoveryClassMethods)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
include Boundary
|
|
56
|
+
include BoundaryReset
|
|
57
|
+
include DefinitionBoundary
|
|
58
|
+
include StateSnapshot
|
|
59
|
+
include PreparationClaim
|
|
60
|
+
include PreparationRecovery
|
|
61
|
+
include Preparation
|
|
62
|
+
include RestartSafeAdapter
|
|
63
|
+
include Recovery
|
|
64
|
+
include RecoveryBoundary
|
|
65
|
+
include PreparationPayload
|
|
66
|
+
include DispatchBoundary
|
|
67
|
+
include DispatchClaim
|
|
68
|
+
include DispatchVerification
|
|
69
|
+
include DispatchConfirmation
|
|
70
|
+
include ExecutionVerification
|
|
71
|
+
include ExecutionAuthorization
|
|
72
|
+
include ExecutionResultCapture
|
|
73
|
+
include Execution
|
|
74
|
+
include CheckpointState
|
|
75
|
+
include Checkpoint
|
|
76
|
+
include Payloads
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
module StepCompletion
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def complete_step(transition, output)
|
|
9
|
+
step = { transition: transition.name, from: transition.from, to: transition.to, output: output }
|
|
10
|
+
result = SplitStepPersistence
|
|
11
|
+
.instance_method(:prepare_split_step_execution_result)
|
|
12
|
+
.bind_call(self, step)
|
|
13
|
+
StepCompletion.instance_method(:append_accepted_output).bind_call(self, output)
|
|
14
|
+
SplitStepPersistence
|
|
15
|
+
.instance_method(:commit_split_step_execution_result!)
|
|
16
|
+
.bind_call(self, result)
|
|
17
|
+
@state = transition.to
|
|
18
|
+
@next_transition_name = @router_next_transition || transition.success_transition
|
|
19
|
+
@router_next_transition = nil
|
|
20
|
+
emit_step_completed(transition, output)
|
|
21
|
+
step
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def append_accepted_output(output)
|
|
25
|
+
return unless @session_messages && !output.nil?
|
|
26
|
+
|
|
27
|
+
@session_messages << { role: :assistant, content: output }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
class StringSnapshot
|
|
6
|
+
BYTESIZE = String.instance_method(:bytesize)
|
|
7
|
+
FREEZE = String.instance_method(:freeze)
|
|
8
|
+
INITIALIZE_COPY = String.instance_method(:initialize_copy)
|
|
9
|
+
private_constant :BYTESIZE, :FREEZE, :INITIALIZE_COPY
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def bytesize(value)
|
|
13
|
+
BYTESIZE.bind_call(value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def copy(value, freeze: false)
|
|
17
|
+
String.allocate.tap do |copy|
|
|
18
|
+
INITIALIZE_COPY.bind_call(copy, value)
|
|
19
|
+
FREEZE.bind_call(copy) if freeze
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|