smith-agents 0.5.0 → 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 +63 -0
- data/README.md +83 -0
- data/lib/smith/agent.rb +13 -0
- data/lib/smith/errors.rb +4 -0
- data/lib/smith/version.rb +2 -2
- 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/execution.rb +16 -16
- data/lib/smith/workflow/fanout_execution.rb +19 -18
- data/lib/smith/workflow/message_value_normalizer.rb +11 -10
- data/lib/smith/workflow/parallel_execution.rb +41 -13
- data/lib/smith/workflow/prepared_branch_execution.rb +31 -0
- data/lib/smith/workflow/prepared_step_execution_authorization.rb +14 -29
- data/lib/smith/workflow/prepared_step_execution_scope.rb +81 -15
- data/lib/smith/workflow/process_local.rb +33 -0
- 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/execution.rb +7 -53
- data/lib/smith/workflow/split_step_persistence/execution_authorization.rb +41 -21
- data/lib/smith/workflow/split_step_persistence/execution_authorization_issuance.rb +57 -0
- data/lib/smith/workflow/split_step_persistence/execution_binding_collector.rb +5 -0
- data/lib/smith/workflow/split_step_persistence/execution_binding_snapshot.rb +17 -3
- data/lib/smith/workflow/split_step_persistence/execution_lifecycle.rb +96 -0
- data/lib/smith/workflow/split_step_persistence/execution_verification.rb +17 -5
- data/lib/smith/workflow/split_step_persistence/subclass_boundary.rb +21 -5
- data/lib/smith/workflow/split_step_persistence.rb +14 -0
- data/lib/smith/workflow/step_context.rb +46 -0
- data/lib/smith/workflow/thread_context_snapshot.rb +103 -0
- data/lib/smith/workflow/transition.rb +16 -1
- data/lib/smith/workflow.rb +21 -1
- data/lib/smith.rb +3 -0
- metadata +46 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../composite/contract"
|
|
4
|
+
require_relative "composite_branch_effects"
|
|
5
|
+
require_relative "composite_branch_outcome"
|
|
6
|
+
|
|
7
|
+
module Smith
|
|
8
|
+
class Workflow
|
|
9
|
+
module SplitStepPersistence
|
|
10
|
+
module CompositeBranchExecution
|
|
11
|
+
include Composite::Contract
|
|
12
|
+
include CompositeBranchEffects
|
|
13
|
+
include CompositeBranchOutcome
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def execute_authorized_composite_branch!(authorization, execution:, input:)
|
|
18
|
+
authorization = validate_composite_authorization!(authorization)
|
|
19
|
+
perform_authorized_prepared_step_execution!(authorization) do
|
|
20
|
+
validate_composite_branch_execution!(authorization, execution, input)
|
|
21
|
+
[nil, execute_composite_branch(authorization, execution, input)]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def execute_composite_branch(authorization, execution, input)
|
|
26
|
+
branch = execution.branch
|
|
27
|
+
previous_ledger = @ledger
|
|
28
|
+
previous_execution_namespace = @execution_namespace
|
|
29
|
+
@ledger = composite_branch_ledger(branch)
|
|
30
|
+
@execution_namespace = execution.execution_namespace
|
|
31
|
+
offsets = composite_effect_offsets
|
|
32
|
+
output, error = capture_composite_branch do
|
|
33
|
+
dispatch_composite_branch(authorization, execution, input)
|
|
34
|
+
end
|
|
35
|
+
effects, effect_error = capture_composite_branch_effects(*offsets, branch)
|
|
36
|
+
composite_branch_outcome(execution, output, error || effect_error, effects)
|
|
37
|
+
ensure
|
|
38
|
+
@ledger = previous_ledger
|
|
39
|
+
@execution_namespace = previous_execution_namespace
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def capture_composite_branch(&block)
|
|
43
|
+
output = within_raw_step_context { with_scoped_artifacts(&block) }
|
|
44
|
+
[output, nil]
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
[nil, e]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def dispatch_composite_branch(authorization, execution, input)
|
|
50
|
+
branch = execution.branch
|
|
51
|
+
transition = @split_step_transition
|
|
52
|
+
agent_role = execution.kind == :parallel ? :agent : :fanout_agent
|
|
53
|
+
agent_class = captured_agent(authorization, transition, branch.agent, agent_role)
|
|
54
|
+
apply_tool_guardrails(agent_class) if execution.kind == :parallel
|
|
55
|
+
environment = composite_branch_environment(execution, input, agent_class, transition)
|
|
56
|
+
execute_composite_branch_kind(execution, transition, agent_class, environment)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def execute_composite_branch_kind(execution, transition, agent_class, environment)
|
|
60
|
+
branch = execution.branch
|
|
61
|
+
signal = Parallel::CancellationSignal.new
|
|
62
|
+
return run_branch(transition, branch.ordinal, environment, @ledger, signal) if execution.kind == :parallel
|
|
63
|
+
|
|
64
|
+
branch_key, agent = fetch_composite_fanout_branch(transition, branch.key)
|
|
65
|
+
run_fanout_branch(branch_key, agent, agent_class, environment, signal)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def composite_branch_environment(execution, input, agent_class, transition)
|
|
69
|
+
branch = execution.branch
|
|
70
|
+
budget = branch.budget.transform_keys(&:to_sym)
|
|
71
|
+
branch_key = fetch_composite_fanout_branch(transition, branch.key).first unless execution.kind == :parallel
|
|
72
|
+
estimates = execution.kind == :parallel ? budget : { branch_key => budget }
|
|
73
|
+
BranchEnv.new(
|
|
74
|
+
prepared_input: input.agent_messages,
|
|
75
|
+
guardrail_sources: Tool.current_guardrails,
|
|
76
|
+
scoped_store: propagate_scoped_artifacts,
|
|
77
|
+
branch_estimates: estimates,
|
|
78
|
+
deadline: wall_clock_deadline,
|
|
79
|
+
agent_class: execution.kind == :parallel ? agent_class : nil
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def composite_branch_ledger(branch)
|
|
84
|
+
return if branch.budget.empty?
|
|
85
|
+
|
|
86
|
+
Budget::Ledger.new(limits: branch.budget.transform_keys(&:to_sym))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def fetch_composite_fanout_branch(transition, branch_key)
|
|
90
|
+
Transition.instance_method(:fetch_fanout_branch!).bind_call(transition, branch_key)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../composite/branch_outcome"
|
|
4
|
+
require_relative "../composite/error_evidence"
|
|
5
|
+
|
|
6
|
+
module Smith
|
|
7
|
+
class Workflow
|
|
8
|
+
module SplitStepPersistence
|
|
9
|
+
module CompositeBranchOutcome
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def composite_branch_outcome(execution, output, error, effects)
|
|
13
|
+
return failed_composite_branch_outcome(execution, error, effects) if error
|
|
14
|
+
|
|
15
|
+
Composite::BranchOutcome.succeeded(
|
|
16
|
+
plan_digest: execution.plan_digest,
|
|
17
|
+
branch: execution.branch,
|
|
18
|
+
output: output.fetch(:output),
|
|
19
|
+
effects:
|
|
20
|
+
)
|
|
21
|
+
rescue WorkflowError, ArgumentError => e
|
|
22
|
+
failed_composite_branch_outcome(execution, e, effects)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def failed_composite_branch_outcome(execution, error, effects)
|
|
26
|
+
Composite::BranchOutcome.failed(
|
|
27
|
+
plan_digest: execution.plan_digest,
|
|
28
|
+
branch: execution.branch,
|
|
29
|
+
error: Composite::ErrorEvidence.call(error),
|
|
30
|
+
effects:
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
module SplitStepPersistence
|
|
6
|
+
module CompositeExecution
|
|
7
|
+
def prepare_composite_step!
|
|
8
|
+
ExecutionAuthorizationIssuance
|
|
9
|
+
.instance_method(:with_prepared_step_execution_authorization)
|
|
10
|
+
.bind_call(self) do |authorization|
|
|
11
|
+
CompositePreparation
|
|
12
|
+
.instance_method(:prepare_authorized_composite_step!)
|
|
13
|
+
.bind_call(self, authorization)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def execute_prepared_composite_branch!(execution:, input:)
|
|
18
|
+
CompositeBranchAuthorization
|
|
19
|
+
.instance_method(:with_composite_branch_authorization)
|
|
20
|
+
.bind_call(self, execution, input) do |authorization|
|
|
21
|
+
CompositeBranchExecution
|
|
22
|
+
.instance_method(:execute_authorized_composite_branch!)
|
|
23
|
+
.bind_call(self, authorization, execution:, input:)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reduce_prepared_composite_step!(plan:, input:, outcomes:, primary_failure: nil)
|
|
28
|
+
ExecutionAuthorizationIssuance
|
|
29
|
+
.instance_method(:with_prepared_step_execution_authorization)
|
|
30
|
+
.bind_call(self) do |authorization|
|
|
31
|
+
CompositeReductionExecution
|
|
32
|
+
.instance_method(:reduce_authorized_composite_step!)
|
|
33
|
+
.bind_call(self, authorization, plan:, input:, outcomes:, primary_failure:)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../composite/contract"
|
|
4
|
+
require_relative "../composite/input"
|
|
5
|
+
require_relative "../composite/planner"
|
|
6
|
+
require_relative "../composite/preparation"
|
|
7
|
+
|
|
8
|
+
module Smith
|
|
9
|
+
class Workflow
|
|
10
|
+
module SplitStepPersistence
|
|
11
|
+
module CompositePreparation
|
|
12
|
+
include Composite::Contract
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def prepare_authorized_composite_step!(authorization)
|
|
17
|
+
authorization = validate_composite_authorization!(authorization)
|
|
18
|
+
transition = @split_step_transition
|
|
19
|
+
specs = composite_branch_specs(authorization, transition)
|
|
20
|
+
input = prepare_composite_input(authorization, transition, specs)
|
|
21
|
+
plan = Composite::Planner.new(
|
|
22
|
+
dispatch: authorization.dispatch_claim,
|
|
23
|
+
kind: composite_kind(transition),
|
|
24
|
+
transition: transition.name,
|
|
25
|
+
from: transition.from,
|
|
26
|
+
execution_namespace: execution_namespace,
|
|
27
|
+
branch_specs: specs,
|
|
28
|
+
input_digest: input.digest,
|
|
29
|
+
budget_state_digest: composite_budget_state_digest
|
|
30
|
+
).call
|
|
31
|
+
Composite::Preparation.new(plan:, input:)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def prepare_composite_input(authorization, transition, specs)
|
|
35
|
+
ThreadContextSnapshot.new.around do
|
|
36
|
+
run_composite_input_guardrails(authorization, transition, specs)
|
|
37
|
+
agent_messages = build_session&.prepare!
|
|
38
|
+
Composite::Input.build(agent_messages:, session_messages: snapshot_session_messages)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def run_composite_input_guardrails(authorization, transition, specs)
|
|
43
|
+
if transition.parallel?
|
|
44
|
+
agent = specs.first.fetch(:agent)
|
|
45
|
+
run_input_guardrails(captured_agent(authorization, transition, agent, :agent))
|
|
46
|
+
else
|
|
47
|
+
run_workflow_input_guardrails
|
|
48
|
+
specs.each do |spec|
|
|
49
|
+
agent = spec.fetch(:agent)
|
|
50
|
+
run_agent_input_guardrails(captured_agent(authorization, transition, agent, :fanout_agent))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../composite/branch_failure"
|
|
4
|
+
require_relative "../composite/contract"
|
|
5
|
+
require_relative "../composite/effects_baseline"
|
|
6
|
+
require_relative "../composite/effects_preflight"
|
|
7
|
+
require_relative "../composite/reducer"
|
|
8
|
+
|
|
9
|
+
module Smith
|
|
10
|
+
class Workflow
|
|
11
|
+
module SplitStepPersistence
|
|
12
|
+
module CompositeReductionExecution
|
|
13
|
+
include Composite::Contract
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def reduce_authorized_composite_step!(authorization, plan:, input:, outcomes:, primary_failure: nil)
|
|
18
|
+
authorization = validate_composite_authorization!(authorization)
|
|
19
|
+
validate_composite_reduction_plan!(authorization, plan, input)
|
|
20
|
+
reduction = Composite::Reducer.new(plan:, outcomes:, primary_failure:).call
|
|
21
|
+
effects_application = prepare_composite_effects(reduction.effects)
|
|
22
|
+
@composite_plan = plan
|
|
23
|
+
@composite_input = input
|
|
24
|
+
@composite_reduction = reduction
|
|
25
|
+
@composite_effects_application = effects_application
|
|
26
|
+
@execution_namespace = plan.execution_namespace
|
|
27
|
+
Execution.instance_method(:execute_authorized_prepared_step!).bind_call(self, authorization)
|
|
28
|
+
ensure
|
|
29
|
+
@composite_plan = nil
|
|
30
|
+
@composite_input = nil
|
|
31
|
+
@composite_reduction = nil
|
|
32
|
+
@composite_effects_application = nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def apply_composite_reduction!(transition)
|
|
36
|
+
reduction = @composite_reduction
|
|
37
|
+
apply_composite_effects!(@composite_effects_application)
|
|
38
|
+
restore_composite_session!
|
|
39
|
+
if reduction.failed?
|
|
40
|
+
raise Composite::BranchFailure.new(
|
|
41
|
+
branch_key: reduction.failed_branch_key,
|
|
42
|
+
error: reduction.error
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
validate_composite_output!(transition, reduction.output)
|
|
47
|
+
reduction.output
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def restore_composite_session!
|
|
51
|
+
@session_messages = snapshot_value(@composite_input.session_messages)
|
|
52
|
+
@last_prepared_input = snapshot_value(@composite_input.agent_messages)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def prepare_composite_effects(effects)
|
|
56
|
+
Composite::EffectsPreflight.new(
|
|
57
|
+
effects:,
|
|
58
|
+
baseline: composite_effect_baseline,
|
|
59
|
+
snapshotter: method(:snapshot_value)
|
|
60
|
+
).call
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def apply_composite_effects!(application)
|
|
64
|
+
Thread.handle_interrupt(Object => :never) do
|
|
65
|
+
@usage_mutex.synchronize do
|
|
66
|
+
@tool_results_mutex.synchronize do
|
|
67
|
+
validate_composite_effect_baseline!(application.baseline)
|
|
68
|
+
@usage_entries = application.usage_entries.dup
|
|
69
|
+
@total_tokens = application.total_tokens
|
|
70
|
+
@total_cost = application.total_cost
|
|
71
|
+
@tool_results = application.tool_results.dup
|
|
72
|
+
@ledger = application.ledger
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def composite_effect_baseline
|
|
79
|
+
@usage_mutex.synchronize do
|
|
80
|
+
@tool_results_mutex.synchronize do
|
|
81
|
+
build_composite_effect_baseline
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def build_composite_effect_baseline
|
|
87
|
+
ledger = @ledger
|
|
88
|
+
Composite::EffectsBaseline.new(
|
|
89
|
+
usage_entries: @usage_entries.dup,
|
|
90
|
+
tool_results: snapshot_value(@tool_results),
|
|
91
|
+
total_tokens: @total_tokens,
|
|
92
|
+
total_cost: @total_cost,
|
|
93
|
+
ledger:,
|
|
94
|
+
budget_consumed: ledger ? ledger.consumed : {}
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def validate_composite_effect_baseline!(expected)
|
|
99
|
+
current = build_composite_effect_baseline
|
|
100
|
+
return if current == expected
|
|
101
|
+
|
|
102
|
+
raise WorkflowError, "composite effect baseline changed before application"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def validate_composite_output!(transition, output)
|
|
106
|
+
if @composite_plan.kind == :parallel
|
|
107
|
+
agent = captured_agent(
|
|
108
|
+
@split_step_active_execution_authorization,
|
|
109
|
+
transition,
|
|
110
|
+
@composite_plan.branches.first.agent,
|
|
111
|
+
:agent
|
|
112
|
+
)
|
|
113
|
+
validate_data_volume!(output, agent)
|
|
114
|
+
run_output_guardrails(output, agent)
|
|
115
|
+
else
|
|
116
|
+
run_workflow_output_guardrails(output)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -5,28 +5,19 @@ module Smith
|
|
|
5
5
|
module SplitStepPersistence
|
|
6
6
|
module Execution
|
|
7
7
|
def execute_prepared_step!
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
result = ExecutionAuthorizationIssuance
|
|
9
|
+
.instance_method(:with_prepared_step_execution_authorization)
|
|
10
|
+
.bind_call(self) do |authorization|
|
|
11
|
+
Execution.instance_method(:execute_authorized_prepared_step!).bind_call(self, authorization)
|
|
12
|
+
end
|
|
12
13
|
result.step_snapshot
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def execute_authorized_prepared_step!(authorization)
|
|
16
|
-
|
|
17
|
-
step = nil
|
|
18
|
-
result = nil
|
|
19
|
-
execution_thread = Thread.current
|
|
20
|
-
execution_active = false
|
|
21
|
-
begin
|
|
22
|
-
activate_split_step_execution!(authorization, execution_thread)
|
|
23
|
-
execution_active = true
|
|
17
|
+
perform_authorized_prepared_step_execution!(authorization) do |execution_thread|
|
|
24
18
|
step = execute_claimed_split_step_transition!
|
|
25
|
-
|
|
26
|
-
ensure
|
|
27
|
-
finish_split_step_execution!(step, authorization, execution_thread) if execution_active
|
|
19
|
+
[step, consume_split_step_execution_result!(execution_thread)]
|
|
28
20
|
end
|
|
29
|
-
result
|
|
30
21
|
end
|
|
31
22
|
|
|
32
23
|
def prepared_persisted_step?
|
|
@@ -38,26 +29,6 @@ module Smith
|
|
|
38
29
|
|
|
39
30
|
private
|
|
40
31
|
|
|
41
|
-
def activate_split_step_execution!(authorization, execution_thread)
|
|
42
|
-
@split_step_mutex.synchronize do
|
|
43
|
-
unless active_split_step_execution_authorization?(authorization)
|
|
44
|
-
raise WorkflowError, "the prepared-step execution authorization is no longer active"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
ensure_split_step_definition_current!
|
|
48
|
-
ensure_prepared_split_step_transition_matches!
|
|
49
|
-
PreparedStepExecutionAuthorization.instance_method(:activate_execution!)
|
|
50
|
-
.bind_call(authorization, execution_thread)
|
|
51
|
-
|
|
52
|
-
@split_step_active_execution_authorization = authorization
|
|
53
|
-
@split_step_execution_result = nil
|
|
54
|
-
@split_step_phase = :executing
|
|
55
|
-
clear_split_step_execution_authorization!
|
|
56
|
-
@split_step_execution_thread = execution_thread
|
|
57
|
-
@split_step_advance_permit = true
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
32
|
def restore_unverified_execution!(verification_token)
|
|
62
33
|
@split_step_mutex.synchronize do
|
|
63
34
|
next unless active_split_step_execution_verification?(verification_token)
|
|
@@ -75,23 +46,6 @@ module Smith
|
|
|
75
46
|
raise WorkflowError, "prepared execution did not return the claimed transition"
|
|
76
47
|
end
|
|
77
48
|
|
|
78
|
-
def finish_split_step_execution!(step, authorization, execution_thread)
|
|
79
|
-
@split_step_mutex.synchronize do
|
|
80
|
-
next unless @split_step_phase == :executing &&
|
|
81
|
-
@split_step_execution_thread.equal?(execution_thread)
|
|
82
|
-
|
|
83
|
-
@split_step_execution_thread = nil
|
|
84
|
-
@split_step_advance_permit = false
|
|
85
|
-
@split_step_execution_previous_phase = nil
|
|
86
|
-
@split_step_active_execution_authorization = nil
|
|
87
|
-
@split_step_execution_result = nil
|
|
88
|
-
@split_step_phase = step ? :executed : :attempted
|
|
89
|
-
end
|
|
90
|
-
ensure
|
|
91
|
-
PreparedStepExecutionAuthorization.instance_method(:close_execution!)
|
|
92
|
-
.bind_call(authorization, execution_thread)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
49
|
def resolve_split_step_advance_transition
|
|
96
50
|
@split_step_mutex.synchronize do
|
|
97
51
|
unless @split_step_phase == :executing && @split_step_execution_thread.equal?(Thread.current)
|
|
@@ -4,36 +4,56 @@ module Smith
|
|
|
4
4
|
class Workflow
|
|
5
5
|
module SplitStepPersistence
|
|
6
6
|
module ExecutionAuthorization
|
|
7
|
-
def authorize_prepared_step_execution!
|
|
8
|
-
verification_token = claim_split_step_execution_verification!
|
|
9
|
-
authorized = false
|
|
10
|
-
verify_claimed_split_step_execution!(verification_token)
|
|
11
|
-
authorization = build_split_step_execution_authorization
|
|
12
|
-
activate_split_step_execution_authorization!(authorization, verification_token)
|
|
13
|
-
authorized = true
|
|
14
|
-
authorization
|
|
15
|
-
ensure
|
|
16
|
-
restore_unverified_execution!(verification_token) if verification_token && !authorized
|
|
17
|
-
end
|
|
18
|
-
|
|
19
7
|
def release_prepared_step_execution!(authorization)
|
|
20
8
|
authorization = validate_split_step_execution_authorization!(authorization)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
Thread.handle_interrupt(Object => :never) do
|
|
10
|
+
@split_step_mutex.synchronize do
|
|
11
|
+
unless active_split_step_execution_authorization?(authorization)
|
|
12
|
+
raise WorkflowError, "the prepared-step execution authorization is no longer active"
|
|
13
|
+
end
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
@split_step_phase = @split_step_execution_previous_phase
|
|
16
|
+
clear_split_step_execution_authorization!
|
|
17
|
+
end
|
|
18
|
+
PreparedStepExecutionAuthorization
|
|
19
|
+
.instance_method(:close_execution!)
|
|
20
|
+
.bind_call(authorization)
|
|
28
21
|
end
|
|
29
|
-
PreparedStepExecutionAuthorization
|
|
30
|
-
.instance_method(:close_execution!)
|
|
31
|
-
.bind_call(authorization)
|
|
32
22
|
self
|
|
33
23
|
end
|
|
34
24
|
|
|
35
25
|
private
|
|
36
26
|
|
|
27
|
+
def authorize_claimed_prepared_step_execution!(verification_token)
|
|
28
|
+
ExecutionVerification
|
|
29
|
+
.instance_method(:verify_claimed_split_step_execution!)
|
|
30
|
+
.bind_call(self, verification_token)
|
|
31
|
+
authorization = ExecutionAuthorization
|
|
32
|
+
.instance_method(:build_split_step_execution_authorization)
|
|
33
|
+
.bind_call(self)
|
|
34
|
+
ExecutionAuthorization
|
|
35
|
+
.instance_method(:activate_split_step_execution_authorization!)
|
|
36
|
+
.bind_call(self, authorization, verification_token)
|
|
37
|
+
authorization
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def claim_framework_execution_verification!
|
|
41
|
+
ExecutionVerification
|
|
42
|
+
.instance_method(:claim_split_step_execution_verification!)
|
|
43
|
+
.bind_call(self)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def restore_framework_execution_verification!(verification_token)
|
|
47
|
+
return unless verification_token
|
|
48
|
+
|
|
49
|
+
active = ExecutionAuthorization
|
|
50
|
+
.instance_method(:active_split_step_execution_verification?)
|
|
51
|
+
.bind_call(self, verification_token)
|
|
52
|
+
return unless active
|
|
53
|
+
|
|
54
|
+
Execution.instance_method(:restore_unverified_execution!).bind_call(self, verification_token)
|
|
55
|
+
end
|
|
56
|
+
|
|
37
57
|
def build_split_step_execution_authorization
|
|
38
58
|
PreparedStepExecutionAuthorization.new(
|
|
39
59
|
prepared_step: @split_step_prepared_descriptor,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
module SplitStepPersistence
|
|
6
|
+
module ExecutionAuthorizationIssuance
|
|
7
|
+
def authorize_prepared_step_execution!
|
|
8
|
+
authorization = nil
|
|
9
|
+
handed_off = false
|
|
10
|
+
authorization = ExecutionAuthorizationIssuance
|
|
11
|
+
.instance_method(:interrupt_safe_prepared_step_authorization)
|
|
12
|
+
.bind_call(self)
|
|
13
|
+
handed_off = true
|
|
14
|
+
authorization
|
|
15
|
+
ensure
|
|
16
|
+
unless handed_off
|
|
17
|
+
ExecutionLifecycle
|
|
18
|
+
.instance_method(:release_failed_execution_authorization!)
|
|
19
|
+
.bind_call(self, authorization)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def with_prepared_step_execution_authorization
|
|
26
|
+
Thread.handle_interrupt(Object => :never) do
|
|
27
|
+
authorization = interrupt_safe_prepared_step_authorization
|
|
28
|
+
begin
|
|
29
|
+
Thread.handle_interrupt(Object => :immediate) { yield(authorization) }
|
|
30
|
+
ensure
|
|
31
|
+
ExecutionLifecycle
|
|
32
|
+
.instance_method(:release_failed_execution_authorization!)
|
|
33
|
+
.bind_call(self, authorization)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def interrupt_safe_prepared_step_authorization
|
|
39
|
+
Thread.handle_interrupt(Object => :never) do
|
|
40
|
+
verification_token = ExecutionAuthorization
|
|
41
|
+
.instance_method(:claim_framework_execution_verification!)
|
|
42
|
+
.bind_call(self)
|
|
43
|
+
begin
|
|
44
|
+
ExecutionAuthorization
|
|
45
|
+
.instance_method(:authorize_claimed_prepared_step_execution!)
|
|
46
|
+
.bind_call(self, verification_token)
|
|
47
|
+
ensure
|
|
48
|
+
ExecutionAuthorization
|
|
49
|
+
.instance_method(:restore_framework_execution_verification!)
|
|
50
|
+
.bind_call(self, verification_token)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -29,6 +29,11 @@ module Smith
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
def capture_agent(name, workflow_class:, transition:, role:)
|
|
33
|
+
capture_binding(name, workflow_class:, transition:, role:)
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
32
37
|
def resolve!
|
|
33
38
|
@bindings = Agent::Registry.capture_bindings!(@requests.values)
|
|
34
39
|
|
|
@@ -14,9 +14,14 @@ module Smith
|
|
|
14
14
|
new(transition, workflow_class:).capture
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def
|
|
17
|
+
def self.capture_agent(transition, workflow_class:, name:, role:)
|
|
18
|
+
new(transition, workflow_class:, selected_agent: { name:, role: }).capture
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(transition, workflow_class:, selected_agent: nil)
|
|
18
22
|
@transition = transition
|
|
19
23
|
@workflow_class = workflow_class
|
|
24
|
+
@selected_agent = selected_agent
|
|
20
25
|
@bindings = ExecutionBindingCollector.new
|
|
21
26
|
@visited_workflows = {}.compare_by_identity
|
|
22
27
|
@visited_workflows[workflow_class] = true
|
|
@@ -27,8 +32,17 @@ module Smith
|
|
|
27
32
|
end
|
|
28
33
|
|
|
29
34
|
def capture
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
if @selected_agent
|
|
36
|
+
@bindings.capture_agent(
|
|
37
|
+
@selected_agent.fetch(:name),
|
|
38
|
+
workflow_class: @workflow_class,
|
|
39
|
+
transition: @transition,
|
|
40
|
+
role: @selected_agent.fetch(:role)
|
|
41
|
+
)
|
|
42
|
+
else
|
|
43
|
+
capture_transition(@transition, workflow_class: @workflow_class)
|
|
44
|
+
drain_workflow_queue
|
|
45
|
+
end
|
|
32
46
|
@bindings.resolve!
|
|
33
47
|
@bindings.freeze
|
|
34
48
|
@workflow_snapshots.freeze
|