smith-agents 0.4.0 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6e0cbabc0bd3db4447206b0326a96996b5b3a6b46ec82e0533298f4074b0659
4
- data.tar.gz: 1e4f9346f46675db971627676f5d85621120d3387e95fab5258ef5de88f22939
3
+ metadata.gz: fce22e9b87caf5c01417a5e7d8fe7a6f4ee179abebc61644074400452a078791
4
+ data.tar.gz: e7d2ae59746ff545f5d45215bcec20dea95ee82a37d7fa765c4ded95484d4029
5
5
  SHA512:
6
- metadata.gz: c91ea9dfb9d21c284278a46a12380b94aba48091370a9db358ea2a5ddb96a21b1a18a453d6d0b431f2839a153a20e5d93bd9a611573f93c99efb59b9e48a21dd
7
- data.tar.gz: f75d0d058c722e640c1299cb62beaafb7a3051f13d786460b289187a7c4a15c5e32aa2f2f5d8b68369d5b5d1b8d6e479147bbf2beb668a2273e740ef7a21f882
6
+ metadata.gz: 8d8515c459487f57c20301581e6f1d5cc786f8bfe7089b4e628548145ac56e0c9c93e9560ac37f1fa7818f95a71a1c8753cb275267b341fb39ca4bf63d5e5f3e
7
+ data.tar.gz: b3e05ca16fc7fec62e8172d9d6ccb84b0bc3f7546af22b9f47c8a36ce4b7e6e5bfb35b0fb47eb51c0b86cf4ab4fbe47e651406c1666fcc63d4b2c16b0ccb0bf9
data/CHANGELOG.md CHANGED
@@ -8,6 +8,22 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version
8
8
 
9
9
  No unreleased changes.
10
10
 
11
+ ## [0.4.1] - 2026-06-28
12
+
13
+ Patch release for static workflow graph inspection. This is additive and diagnostic-only: Smith exposes declared workflow topology for hosts to render, lint, or cache without executing agents, advancing state, owning progress projection, or changing durability/recovery boundaries.
14
+
15
+ ### Added
16
+
17
+ - `Smith::Workflow.graph` — returns a read-only inspection object for a workflow class.
18
+ - `Smith::Workflow.validate_graph` — returns a structured report with validity status, diagnostics, suggestions, transition snapshots, and graph metrics.
19
+ - Pre-runtime graph diagnostics for missing initial states, undefined transition states, unresolved `on_success` / `on_failure` targets, unresolved router route/fallback targets, target-state mismatch warnings, and unreachable-transition warnings.
20
+ - Transition snapshots that preserve declared names exactly and expose `name`, `from`, `to`, `kind`, success/failure targets, router routes, and router fallback.
21
+
22
+ ### Test coverage
23
+
24
+ - Default suite: 862 examples, 0 failures.
25
+ - Touched Ruby files: 17 files inspected by RuboCop, 0 offenses.
26
+
11
27
  ## [0.4.0] - 2026-06-24
12
28
 
13
29
  Two more host-ergonomic primitives that close the deferred-from-0.3.0 backlog: `Workflow.stuck_for?` for liveness probing and `Context.persist :auto` for write-tracked context persistence. Both are purely additive.
data/README.md CHANGED
@@ -94,6 +94,21 @@ end
94
94
 
95
95
  The full pattern guide with working examples for each lives in [`docs/PATTERNS.md`](docs/PATTERNS.md).
96
96
 
97
+ ## Workflow Graph Inspection
98
+
99
+ Smith can inspect a workflow's declared graph without running agents or advancing state. This is useful for host apps that want to render, lint, or cache a workflow shape before execution.
100
+
101
+ ```ruby
102
+ report = ReplyWorkflow.validate_graph
103
+
104
+ report.valid? # => true
105
+ report.transitions # => read-only transition snapshots
106
+ report.diagnostics # => errors and warnings for missing states or routes
107
+ report.metrics # => state, transition, reachability, and terminal-state counts
108
+ ```
109
+
110
+ Graph inspection is static and diagnostic-only. Runtime execution, persistence, progress projection, retries, and recovery remain host-owned concerns.
111
+
97
112
  ## Configuration
98
113
 
99
114
  ```ruby
data/lib/smith/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Smith
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.1"
5
5
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ class Diagnostic
7
+ attr_reader :severity, :code, :message, :state, :transition, :target, :suggestion
8
+
9
+ def initialize(**attributes)
10
+ @severity = attributes.fetch(:severity)
11
+ @code = attributes.fetch(:code)
12
+ @message = attributes.fetch(:message)
13
+ @state = attributes[:state]
14
+ @transition = attributes[:transition]
15
+ @target = attributes[:target]
16
+ @suggestion = attributes[:suggestion]
17
+ end
18
+
19
+ def to_h
20
+ {
21
+ severity: severity,
22
+ code: code,
23
+ message: message,
24
+ state: state,
25
+ transition: transition,
26
+ target: target,
27
+ suggestion: suggestion
28
+ }.compact
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ class Metrics
7
+ attr_reader :graph, :reachable_transition_names
8
+
9
+ def initialize(graph, reachable_transition_names)
10
+ @graph = graph
11
+ @reachable_transition_names = reachable_transition_names
12
+ end
13
+
14
+ def to_h
15
+ {
16
+ states_count: graph.states.length,
17
+ transitions_count: graph.transitions.length,
18
+ reachable_transitions_count: reachable_transition_names.length,
19
+ terminal_states: terminal_states
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def terminal_states
26
+ graph.states.select do |state|
27
+ graph.transitions.values.none? { |transition| transition.from == state }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ class Reachability
7
+ attr_reader :graph
8
+
9
+ def initialize(graph)
10
+ @graph = graph
11
+ end
12
+
13
+ def transition_names
14
+ return [] unless graph.initial_state
15
+
16
+ reset_walk
17
+ drain_queue
18
+ @seen_transitions.keys
19
+ end
20
+
21
+ private
22
+
23
+ def reset_walk
24
+ @seen_states = { graph.initial_state => true }
25
+ @seen_transitions = {}
26
+ @queue = [graph.initial_state]
27
+ end
28
+
29
+ def drain_queue
30
+ transitions_from(@queue.shift).each { |transition| visit_transition(transition) } until @queue.empty?
31
+ end
32
+
33
+ def visit_transition(transition)
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
40
+
41
+ def visit_named_transition(target_name)
42
+ target = graph.transitions[target_name]
43
+ visit_transition(target) if target
44
+ end
45
+
46
+ def enqueue_state(state)
47
+ return if state.nil? || @seen_states.key?(state)
48
+
49
+ @seen_states[state] = true
50
+ @queue << state
51
+ end
52
+
53
+ def transitions_from(state)
54
+ graph.transitions.values.select { |transition| transition.from == state }
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ class ReachabilityDiagnostics
7
+ attr_reader :graph, :reachable_transition_names
8
+
9
+ def initialize(graph, reachable_transition_names)
10
+ @graph = graph
11
+ @reachable_transition_names = reachable_transition_names
12
+ end
13
+
14
+ def to_a
15
+ (graph.transitions.keys - reachable_transition_names).filter_map do |transition_name|
16
+ next if auto_fail_transition?(transition_name)
17
+
18
+ diagnostic_for(transition_name)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def auto_fail_transition?(transition_name)
25
+ transition_name == :fail && graph.transitions[transition_name]&.from.nil?
26
+ end
27
+
28
+ def diagnostic_for(transition_name)
29
+ transition = graph.transitions.fetch(transition_name)
30
+ Diagnostic.new(
31
+ severity: :warning,
32
+ code: :unreachable_transition,
33
+ transition: transition_name,
34
+ state: transition.from,
35
+ message: "Transition #{ref(transition_name)} is not reachable from " \
36
+ "initial_state #{ref(graph.initial_state)}.",
37
+ suggestion: "Connect transition #{ref(transition_name)} from a reachable state or remove it."
38
+ )
39
+ end
40
+
41
+ def ref(value)
42
+ Reference.format(value)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ module Reference
7
+ def self.format(value)
8
+ return ":#{value}" if value.is_a?(Symbol)
9
+
10
+ value.inspect
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ class Report
7
+ attr_reader :status, :workflow_class, :initial_state, :states, :transitions, :diagnostics, :metrics
8
+
9
+ def initialize(**attributes)
10
+ @status = attributes.fetch(:status)
11
+ @workflow_class = attributes.fetch(:workflow_class)
12
+ @initial_state = attributes.fetch(:initial_state)
13
+ @states = attributes.fetch(:states)
14
+ @transitions = attributes.fetch(:transitions)
15
+ @diagnostics = attributes.fetch(:diagnostics)
16
+ @metrics = attributes.fetch(:metrics)
17
+ end
18
+
19
+ def valid?
20
+ errors.empty?
21
+ end
22
+
23
+ def errors
24
+ diagnostics.select { |diagnostic| diagnostic.severity == :error }
25
+ end
26
+
27
+ def warnings
28
+ diagnostics.select { |diagnostic| diagnostic.severity == :warning }
29
+ end
30
+
31
+ def suggestions
32
+ diagnostics.map(&:suggestion).compact.uniq
33
+ end
34
+
35
+ def to_h
36
+ {
37
+ status: status,
38
+ workflow_class: workflow_class,
39
+ initial_state: initial_state,
40
+ states: states,
41
+ transitions: transitions.map(&:to_h),
42
+ diagnostics: diagnostics.map(&:to_h),
43
+ suggestions: suggestions,
44
+ metrics: metrics
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ class StateDiagnostics
7
+ attr_reader :graph
8
+
9
+ def initialize(graph)
10
+ @graph = graph
11
+ end
12
+
13
+ def to_a
14
+ [
15
+ *initial_state_diagnostics,
16
+ *state_reference_diagnostics
17
+ ]
18
+ end
19
+
20
+ private
21
+
22
+ def initial_state_diagnostics
23
+ return [] if graph.initial_state && graph.states.include?(graph.initial_state)
24
+
25
+ [
26
+ Diagnostic.new(
27
+ severity: :error,
28
+ code: :missing_initial_state,
29
+ state: graph.initial_state,
30
+ message: "Workflow initial_state is not declared as a state.",
31
+ suggestion: "Declare an initial_state and ensure it is included in the workflow states."
32
+ )
33
+ ]
34
+ end
35
+
36
+ def state_reference_diagnostics
37
+ graph.transitions.values.flat_map do |transition|
38
+ [
39
+ undefined_state_diagnostic(transition, :from, transition.from),
40
+ undefined_state_diagnostic(transition, :to, transition.to)
41
+ ].compact
42
+ end
43
+ end
44
+
45
+ def undefined_state_diagnostic(transition, edge, state)
46
+ return if edge == :from && state.nil?
47
+ return if graph.states.include?(state)
48
+
49
+ Diagnostic.new(
50
+ severity: :error,
51
+ code: :"undefined_#{edge}_state",
52
+ state: state,
53
+ transition: transition.name,
54
+ message: "Transition #{ref(transition.name)} references undefined #{edge} state #{ref(state)}.",
55
+ suggestion: "Declare state #{ref(state)} or update transition #{ref(transition.name)}."
56
+ )
57
+ end
58
+
59
+ def ref(value)
60
+ Reference.format(value)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ class Targets
7
+ attr_reader :transition
8
+
9
+ def self.for(transition)
10
+ new(transition).names
11
+ end
12
+
13
+ def self.router_for(transition)
14
+ new(transition).router_names
15
+ end
16
+
17
+ def initialize(transition)
18
+ @transition = transition
19
+ end
20
+
21
+ def names
22
+ names = [transition.success_transition, transition.failure_transition]
23
+ names.concat(router_names)
24
+ names.compact.uniq
25
+ end
26
+
27
+ def router_names
28
+ return [] unless transition.router_config
29
+
30
+ [
31
+ *transition.router_config.fetch(:routes).values,
32
+ transition.router_config.fetch(:fallback)
33
+ ]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ class TransitionDiagnostics
7
+ attr_reader :graph
8
+
9
+ def initialize(graph)
10
+ @graph = graph
11
+ end
12
+
13
+ def to_a
14
+ graph.transitions.values.flat_map do |transition|
15
+ [
16
+ transition_target_diagnostic(transition, :success_transition, transition.success_transition),
17
+ transition_target_diagnostic(transition, :failure_transition, transition.failure_transition),
18
+ *router_target_diagnostics(transition),
19
+ *target_state_mismatch_diagnostics(transition)
20
+ ].compact
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def transition_target_diagnostic(transition, code, target)
27
+ return if target.nil?
28
+ return if graph.transitions.key?(target)
29
+
30
+ Diagnostic.new(
31
+ severity: :error,
32
+ code: :"unresolved_#{code}",
33
+ transition: transition.name,
34
+ target: target,
35
+ message: "Transition #{ref(transition.name)} references missing transition #{ref(target)}.",
36
+ suggestion: "Declare transition #{ref(target)} or update transition #{ref(transition.name)}."
37
+ )
38
+ end
39
+
40
+ def router_target_diagnostics(transition)
41
+ return [] unless transition.router_config
42
+
43
+ Targets.router_for(transition).filter_map do |target|
44
+ transition_target_diagnostic(transition, :router_target, target)
45
+ end
46
+ end
47
+
48
+ def target_state_mismatch_diagnostics(transition)
49
+ Targets.for(transition).filter_map do |target_name|
50
+ target = graph.transitions[target_name]
51
+ next unless target
52
+ next if target.from.nil? || target.from == transition.to
53
+
54
+ mismatch_diagnostic(transition, target_name, target)
55
+ end
56
+ end
57
+
58
+ def mismatch_diagnostic(transition, target_name, target)
59
+ Diagnostic.new(
60
+ severity: :warning,
61
+ code: :target_from_state_mismatch,
62
+ transition: transition.name,
63
+ target: target_name,
64
+ state: transition.to,
65
+ message: "Transition #{ref(transition.name)} can route to #{ref(target_name)}, " \
66
+ "but #{ref(target_name)} starts from #{ref(target.from)} instead of #{ref(transition.to)}.",
67
+ suggestion: "Align #{ref(target_name)}'s from state with #{ref(transition.to)}, " \
68
+ "or remove the named route."
69
+ )
70
+ end
71
+
72
+ def ref(value)
73
+ Reference.format(value)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ class TransitionSnapshot
7
+ KINDS = [
8
+ %i[deterministic deterministic?],
9
+ %i[router routed?],
10
+ %i[nested_workflow nested?],
11
+ %i[optimizer optimized?],
12
+ %i[orchestrator orchestrated?],
13
+ %i[parallel parallel?]
14
+ ].freeze
15
+
16
+ attr_reader :name, :from, :to, :kind, :success_transition, :failure_transition, :routes, :fallback
17
+
18
+ def self.from_transition(transition)
19
+ new(
20
+ name: transition.name,
21
+ from: transition.from,
22
+ to: transition.to,
23
+ kind: kind_for(transition),
24
+ success_transition: transition.success_transition,
25
+ failure_transition: transition.failure_transition,
26
+ routes: transition.router_config&.fetch(:routes, nil),
27
+ fallback: transition.router_config&.fetch(:fallback, nil)
28
+ )
29
+ end
30
+
31
+ def self.kind_for(transition)
32
+ kind = KINDS.find { |_name, predicate| transition.public_send(predicate) }
33
+ return kind.first if kind
34
+ return :agent if transition.agent_name
35
+
36
+ :noop
37
+ end
38
+
39
+ def initialize(**attributes)
40
+ @name = attributes.fetch(:name)
41
+ @from = attributes.fetch(:from)
42
+ @to = attributes.fetch(:to)
43
+ @kind = attributes.fetch(:kind)
44
+ @success_transition = attributes[:success_transition]
45
+ @failure_transition = attributes[:failure_transition]
46
+ @routes = attributes[:routes]
47
+ @fallback = attributes[:fallback]
48
+ end
49
+
50
+ def to_h
51
+ {
52
+ name: name,
53
+ from: from,
54
+ to: to,
55
+ kind: kind,
56
+ success_transition: success_transition,
57
+ failure_transition: failure_transition,
58
+ routes: routes,
59
+ fallback: fallback
60
+ }.compact
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ class Validator
7
+ attr_reader :graph
8
+
9
+ def initialize(graph)
10
+ @graph = graph
11
+ end
12
+
13
+ def report
14
+ diagnostics = all_diagnostics
15
+
16
+ Report.new(
17
+ status: status_for(diagnostics),
18
+ workflow_class: graph.workflow_class.name,
19
+ initial_state: graph.initial_state,
20
+ states: graph.states,
21
+ transitions: graph.transition_snapshots,
22
+ diagnostics: diagnostics,
23
+ metrics: Metrics.new(graph, reachable_transition_names).to_h
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def all_diagnostics
30
+ [
31
+ *StateDiagnostics.new(graph).to_a,
32
+ *TransitionDiagnostics.new(graph).to_a,
33
+ *ReachabilityDiagnostics.new(graph, reachable_transition_names).to_a
34
+ ]
35
+ end
36
+
37
+ def reachable_transition_names
38
+ @reachable_transition_names ||= Reachability.new(graph).transition_names
39
+ end
40
+
41
+ def status_for(diagnostics)
42
+ return :invalid if diagnostics.any? { |diagnostic| diagnostic.severity == :error }
43
+ return :warning if diagnostics.any?
44
+
45
+ :valid
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ class Graph
6
+ attr_reader :workflow_class, :initial_state, :states, :transitions
7
+
8
+ def initialize(workflow_class:, initial_state:, states:, transitions:)
9
+ @workflow_class = workflow_class
10
+ @initial_state = initial_state
11
+ @states = Array(states).uniq.freeze
12
+ @transitions = transitions.dup.freeze
13
+ end
14
+
15
+ def validate
16
+ Validator.new(self).report
17
+ end
18
+
19
+ def transition_snapshots
20
+ transitions.values.map { |transition| TransitionSnapshot.from_transition(transition) }
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ require_relative "graph/reference"
27
+ require_relative "graph/diagnostic"
28
+ require_relative "graph/state_diagnostics"
29
+ require_relative "graph/reachability"
30
+ require_relative "graph/reachability_diagnostics"
31
+ require_relative "graph/metrics"
32
+ require_relative "graph/report"
33
+ require_relative "graph/targets"
34
+ require_relative "graph/transition_snapshot"
35
+ require_relative "graph/transition_diagnostics"
36
+ require_relative "graph/validator"
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smith
4
+ class Workflow
5
+ def self.graph
6
+ Graph.new(
7
+ workflow_class: self,
8
+ initial_state: initial_state,
9
+ states: @states || [],
10
+ transitions: @transitions || {}
11
+ )
12
+ end
13
+
14
+ def self.validate_graph
15
+ graph.validate
16
+ end
17
+ end
18
+ end
data/lib/smith.rb CHANGED
@@ -70,6 +70,7 @@ module Smith
70
70
  unless %i[off auto].include?(value)
71
71
  raise ArgumentError, "Smith.config.openai_api_mode must be :off or :auto, got #{value.inspect}"
72
72
  end
73
+
73
74
  value
74
75
  }
75
76
 
@@ -227,6 +228,8 @@ require_relative "smith/agent/registry"
227
228
 
228
229
  # Workflow (Transition, DSL, Persistence, and Execution must load before Workflow)
229
230
  require_relative "smith/workflow/transition"
231
+ require_relative "smith/workflow/graph"
232
+ require_relative "smith/workflow/graph_dsl"
230
233
  require_relative "smith/workflow/dsl"
231
234
  require_relative "smith/workflow/persistence"
232
235
  require_relative "smith/workflow/durability"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smith-agents
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Ralak
@@ -219,6 +219,19 @@ files:
219
219
  - lib/smith/workflow/event_integration.rb
220
220
  - lib/smith/workflow/execution.rb
221
221
  - lib/smith/workflow/execution_frame.rb
222
+ - lib/smith/workflow/graph.rb
223
+ - lib/smith/workflow/graph/diagnostic.rb
224
+ - lib/smith/workflow/graph/metrics.rb
225
+ - lib/smith/workflow/graph/reachability.rb
226
+ - lib/smith/workflow/graph/reachability_diagnostics.rb
227
+ - lib/smith/workflow/graph/reference.rb
228
+ - lib/smith/workflow/graph/report.rb
229
+ - lib/smith/workflow/graph/state_diagnostics.rb
230
+ - lib/smith/workflow/graph/targets.rb
231
+ - lib/smith/workflow/graph/transition_diagnostics.rb
232
+ - lib/smith/workflow/graph/transition_snapshot.rb
233
+ - lib/smith/workflow/graph/validator.rb
234
+ - lib/smith/workflow/graph_dsl.rb
222
235
  - lib/smith/workflow/guardrail_integration.rb
223
236
  - lib/smith/workflow/nested_execution.rb
224
237
  - lib/smith/workflow/orchestrator_worker.rb