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.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +262 -0
  3. data/README.md +116 -6
  4. data/docs/CONFIGURATION.md +4 -1
  5. data/docs/PATTERNS.md +7 -0
  6. data/docs/PERSISTENCE.md +502 -1
  7. data/docs/workflow_claim.md +4 -1
  8. data/lib/smith/agent/lifecycle.rb +19 -10
  9. data/lib/smith/agent/registry/execution_binding_capture.rb +26 -0
  10. data/lib/smith/agent/registry/mutation_boundary.rb +29 -0
  11. data/lib/smith/agent/registry.rb +8 -0
  12. data/lib/smith/agent.rb +24 -2
  13. data/lib/smith/budget/amount_contract.rb +67 -0
  14. data/lib/smith/budget/amount_representation.rb +43 -0
  15. data/lib/smith/budget/decimal_context.rb +18 -0
  16. data/lib/smith/budget/ledger.rb +110 -18
  17. data/lib/smith/budget/ledger_publication.rb +60 -0
  18. data/lib/smith/budget/ledger_state.rb +21 -0
  19. data/lib/smith/budget/ledger_state_transition.rb +72 -0
  20. data/lib/smith/budget/public_state_validator.rb +25 -0
  21. data/lib/smith/budget/reservation.rb +20 -0
  22. data/lib/smith/budget/reservation_contract.rb +34 -0
  23. data/lib/smith/context/observation_masking.rb +3 -1
  24. data/lib/smith/context/state_injection.rb +1 -1
  25. data/lib/smith/doctor/checks/persistence_capabilities.rb +18 -3
  26. data/lib/smith/error.rb +5 -0
  27. data/lib/smith/errors.rb +6 -0
  28. data/lib/smith/exponential_backoff.rb +128 -0
  29. data/lib/smith/persistence_adapters/active_record_connection_errors.rb +23 -0
  30. data/lib/smith/persistence_adapters/active_record_exact_predicate.rb +43 -0
  31. data/lib/smith/persistence_adapters/active_record_exact_store.rb +62 -0
  32. data/lib/smith/persistence_adapters/active_record_exact_write.rb +40 -0
  33. data/lib/smith/persistence_adapters/active_record_initial_write.rb +42 -0
  34. data/lib/smith/persistence_adapters/active_record_store.rb +95 -52
  35. data/lib/smith/persistence_adapters/cache_store.rb +4 -1
  36. data/lib/smith/persistence_adapters/memory.rb +45 -14
  37. data/lib/smith/persistence_adapters/payload_version.rb +23 -0
  38. data/lib/smith/persistence_adapters/redis_client_access.rb +38 -0
  39. data/lib/smith/persistence_adapters/redis_exact_write.rb +44 -0
  40. data/lib/smith/persistence_adapters/redis_store.rb +38 -43
  41. data/lib/smith/persistence_adapters/redis_versioned_write.rb +56 -0
  42. data/lib/smith/persistence_adapters/retry.rb +22 -10
  43. data/lib/smith/persistence_adapters/version_expectation.rb +19 -0
  44. data/lib/smith/persistence_adapters.rb +26 -18
  45. data/lib/smith/persistence_payload_conflict.rb +12 -0
  46. data/lib/smith/tool/budget_enforcement.rb +2 -2
  47. data/lib/smith/types.rb +1 -0
  48. data/lib/smith/version.rb +2 -1
  49. data/lib/smith/workflow/branch_env.rb +1 -1
  50. data/lib/smith/workflow/budget_integration.rb +21 -11
  51. data/lib/smith/workflow/definition_identity/class_methods.rb +48 -0
  52. data/lib/smith/workflow/definition_identity.rb +25 -0
  53. data/lib/smith/workflow/deterministic_step.rb +2 -2
  54. data/lib/smith/workflow/dsl.rb +73 -5
  55. data/lib/smith/workflow/durability.rb +2 -1
  56. data/lib/smith/workflow/evaluator_optimizer.rb +7 -11
  57. data/lib/smith/workflow/execution.rb +16 -18
  58. data/lib/smith/workflow/execution_binding_resolution.rb +16 -0
  59. data/lib/smith/workflow/execution_result_snapshot.rb +118 -0
  60. data/lib/smith/workflow/fanout_execution.rb +1 -1
  61. data/lib/smith/workflow/graph/diagnostic.rb +27 -1
  62. data/lib/smith/workflow/graph/diagnostic_path.rb +24 -0
  63. data/lib/smith/workflow/graph/execution_successors.rb +65 -0
  64. data/lib/smith/workflow/graph/identifier_projection.rb +13 -0
  65. data/lib/smith/workflow/graph/metrics.rb +1 -3
  66. data/lib/smith/workflow/graph/nested_readiness_diagnostics.rb +15 -16
  67. data/lib/smith/workflow/graph/optimization_contract.rb +2 -2
  68. data/lib/smith/workflow/graph/orchestration_contract.rb +3 -3
  69. data/lib/smith/workflow/graph/reachability.rb +19 -31
  70. data/lib/smith/workflow/graph/reachability_diagnostics.rb +17 -1
  71. data/lib/smith/workflow/graph/retry_policy_diagnostic.rb +36 -0
  72. data/lib/smith/workflow/graph/runtime_binding_diagnostics.rb +2 -2
  73. data/lib/smith/workflow/graph/runtime_readiness.rb +3 -44
  74. data/lib/smith/workflow/graph/runtime_readiness_metrics.rb +2 -2
  75. data/lib/smith/workflow/graph/runtime_readiness_report_builder.rb +47 -0
  76. data/lib/smith/workflow/graph/runtime_readiness_traversal.rb +108 -0
  77. data/lib/smith/workflow/graph/state_diagnostics.rb +2 -2
  78. data/lib/smith/workflow/graph/transition_contract.rb +47 -0
  79. data/lib/smith/workflow/graph/transition_contract_attributes.rb +73 -0
  80. data/lib/smith/workflow/graph/transition_contract_configurations.rb +100 -0
  81. data/lib/smith/workflow/graph/transition_diagnostics.rb +54 -12
  82. data/lib/smith/workflow/graph/transition_optimization_configuration.rb +62 -0
  83. data/lib/smith/workflow/graph/transition_snapshot.rb +4 -1
  84. data/lib/smith/workflow/graph/validator.rb +1 -1
  85. data/lib/smith/workflow/graph.rb +62 -4
  86. data/lib/smith/workflow/guardrail_integration.rb +23 -9
  87. data/lib/smith/workflow/identifier.rb +24 -0
  88. data/lib/smith/workflow/message_admission.rb +40 -0
  89. data/lib/smith/workflow/message_admission_boundary.rb +22 -0
  90. data/lib/smith/workflow/message_batch.rb +60 -0
  91. data/lib/smith/workflow/message_value_normalizer.rb +133 -0
  92. data/lib/smith/workflow/nested_execution.rb +10 -0
  93. data/lib/smith/workflow/orchestrator_worker.rb +3 -7
  94. data/lib/smith/workflow/parallel/cancellation_signal.rb +14 -5
  95. data/lib/smith/workflow/parallel/execution_context.rb +113 -0
  96. data/lib/smith/workflow/parallel/nested_execution.rb +124 -0
  97. data/lib/smith/workflow/parallel/root_execution.rb +91 -0
  98. data/lib/smith/workflow/parallel.rb +19 -22
  99. data/lib/smith/workflow/parallel_agent_binding.rb +24 -0
  100. data/lib/smith/workflow/parallel_execution.rb +7 -3
  101. data/lib/smith/workflow/persistence.rb +49 -10
  102. data/lib/smith/workflow/prepared_step.rb +122 -0
  103. data/lib/smith/workflow/prepared_step_dispatch.rb +88 -0
  104. data/lib/smith/workflow/prepared_step_execution_authorization.rb +116 -0
  105. data/lib/smith/workflow/prepared_step_execution_result.rb +49 -0
  106. data/lib/smith/workflow/prepared_step_execution_scope.rb +45 -0
  107. data/lib/smith/workflow/prepared_step_recovery.rb +38 -0
  108. data/lib/smith/workflow/retry_execution.rb +20 -11
  109. data/lib/smith/workflow/split_step_persistence/boundary.rb +128 -0
  110. data/lib/smith/workflow/split_step_persistence/boundary_reset.rb +50 -0
  111. data/lib/smith/workflow/split_step_persistence/canonical_payload_digest.rb +55 -0
  112. data/lib/smith/workflow/split_step_persistence/checkpoint.rb +119 -0
  113. data/lib/smith/workflow/split_step_persistence/checkpoint_state.rb +51 -0
  114. data/lib/smith/workflow/split_step_persistence/definition_boundary.rb +19 -0
  115. data/lib/smith/workflow/split_step_persistence/dispatch_boundary.rb +16 -0
  116. data/lib/smith/workflow/split_step_persistence/dispatch_claim.rb +118 -0
  117. data/lib/smith/workflow/split_step_persistence/dispatch_confirmation.rb +70 -0
  118. data/lib/smith/workflow/split_step_persistence/dispatch_verification.rb +35 -0
  119. data/lib/smith/workflow/split_step_persistence/execution.rb +128 -0
  120. data/lib/smith/workflow/split_step_persistence/execution_authorization.rb +89 -0
  121. data/lib/smith/workflow/split_step_persistence/execution_binding_collector.rb +90 -0
  122. data/lib/smith/workflow/split_step_persistence/execution_binding_snapshot.rb +95 -0
  123. data/lib/smith/workflow/split_step_persistence/execution_result_capture.rb +48 -0
  124. data/lib/smith/workflow/split_step_persistence/execution_verification.rb +65 -0
  125. data/lib/smith/workflow/split_step_persistence/execution_workflow_snapshot.rb +77 -0
  126. data/lib/smith/workflow/split_step_persistence/inheritance.rb +22 -0
  127. data/lib/smith/workflow/split_step_persistence/payloads.rb +54 -0
  128. data/lib/smith/workflow/split_step_persistence/preparation.rb +125 -0
  129. data/lib/smith/workflow/split_step_persistence/preparation_claim.rb +117 -0
  130. data/lib/smith/workflow/split_step_persistence/preparation_payload.rb +25 -0
  131. data/lib/smith/workflow/split_step_persistence/preparation_recovery.rb +54 -0
  132. data/lib/smith/workflow/split_step_persistence/recovery.rb +125 -0
  133. data/lib/smith/workflow/split_step_persistence/recovery_boundary.rb +56 -0
  134. data/lib/smith/workflow/split_step_persistence/recovery_class_methods.rb +14 -0
  135. data/lib/smith/workflow/split_step_persistence/replace_exact_signature.rb +46 -0
  136. data/lib/smith/workflow/split_step_persistence/restart_safe_adapter.rb +42 -0
  137. data/lib/smith/workflow/split_step_persistence/state_snapshot.rb +84 -0
  138. data/lib/smith/workflow/split_step_persistence/subclass_boundary.rb +102 -0
  139. data/lib/smith/workflow/split_step_persistence/transaction_identity.rb +45 -0
  140. data/lib/smith/workflow/split_step_persistence/transition_contract.rb +48 -0
  141. data/lib/smith/workflow/split_step_persistence/transition_contract_freezer.rb +83 -0
  142. data/lib/smith/workflow/split_step_persistence/transition_contract_signature.rb +107 -0
  143. data/lib/smith/workflow/split_step_persistence/transition_contract_structured_values.rb +51 -0
  144. data/lib/smith/workflow/split_step_persistence.rb +79 -0
  145. data/lib/smith/workflow/step_completion.rb +31 -0
  146. data/lib/smith/workflow/string_snapshot.rb +25 -0
  147. data/lib/smith/workflow/transition.rb +122 -133
  148. data/lib/smith/workflow/transition_actionability.rb +22 -0
  149. data/lib/smith/workflow.rb +57 -19
  150. data/lib/smith.rb +42 -2
  151. data/sig/smith.rbs +1 -0
  152. 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