Module: Smith::Workflow::DSL::ClassMethods

Defined in:
lib/smith/workflow/dsl.rb,
lib/smith/workflow/pipeline.rb

Instance Method Summary collapse

Instance Method Details

#budget(**opts) ⇒ Object



58
59
60
61
62
# File 'lib/smith/workflow/dsl.rb', line 58

def budget(**opts)
  return @budget_config if opts.empty?

  @budget_config = opts
end

#context_manager(klass = nil) ⇒ Object



76
77
78
79
80
# File 'lib/smith/workflow/dsl.rb', line 76

def context_manager(klass = nil)
  return @context_manager_class if klass.nil?

  @context_manager_class = klass
end

#find_transition(name) ⇒ Object



221
222
223
# File 'lib/smith/workflow/dsl.rb', line 221

def find_transition(name)
  (@transitions || {})[name]
end

#first_transition_from(state) ⇒ Object



213
214
215
# File 'lib/smith/workflow/dsl.rb', line 213

def first_transition_from(state)
  transitions_by_state.fetch(state, []).first
end

#from_state(hash) ⇒ Object



229
230
231
232
233
# File 'lib/smith/workflow/dsl.rb', line 229

def from_state(hash)
  workflow = allocate
  workflow.send(:restore_state, hash)
  workflow
end

#guardrails(klass = nil) ⇒ Object



70
71
72
73
74
# File 'lib/smith/workflow/dsl.rb', line 70

def guardrails(klass = nil)
  return @guardrails_class if klass.nil?

  @guardrails_class = klass
end

#idempotency_mode(mode = nil) ⇒ Object

Controls whether run_persisted! / advance_persisted! stamp a step_in_progress marker before each advance and clear it afterward.

Modes:

:lax    (default) no marker stamping; restore never raises.
       Safe when agent calls and tools are idempotent, so
       re-running a step on restore is harmless.
:strict marker is persisted before each advance and cleared
       after. Restore raises
       Smith::StepInProgressOnRestore when the marker is
       set, indicating a previous worker crashed mid-step
       and re-running could double-execute non-idempotent
       agent calls or tools.


173
174
175
176
177
178
179
180
181
# File 'lib/smith/workflow/dsl.rb', line 173

def idempotency_mode(mode = nil)
  return @idempotency_mode || :lax if mode.nil?

  unless %i[strict lax].include?(mode)
    raise ArgumentError, "idempotency_mode must be :strict or :lax, got #{mode.inspect}"
  end

  @idempotency_mode = mode
end

#inherited(subclass) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/smith/workflow/dsl.rb', line 11

def inherited(subclass)
  super
  subclass.instance_variable_set(:@states, (@states || []).dup)
  subclass.instance_variable_set(:@transitions, (@transitions || {}).dup)
  subclass.instance_variable_set(
    :@transitions_by_state,
    duplicate_transition_index(@transitions_by_state)
  )
  subclass.instance_variable_set(:@transition_order, (@transition_order || {}).dup)
  subclass.instance_variable_set(:@transition_sequence, @transition_sequence)
  subclass.instance_variable_set(:@initial_state_name, @initial_state_name)
  subclass.instance_variable_set(:@budget_config, @budget_config&.dup)
  subclass.instance_variable_set(:@max_transitions_count, @max_transitions_count)
  subclass.instance_variable_set(:@guardrails_class, @guardrails_class)
  subclass.instance_variable_set(:@context_manager_class, @context_manager_class)
  subclass.instance_variable_set(:@seed_messages_builder, @seed_messages_builder)
  subclass.instance_variable_set(:@persistence_key_builder, @persistence_key_builder)
  subclass.instance_variable_set(:@persistence_schema_version, @persistence_schema_version)
  subclass.instance_variable_set(:@migrations, (@migrations || {}).dup)
  subclass.instance_variable_set(:@seed_validation_mode, @seed_validation_mode)
  subclass.instance_variable_set(:@idempotency_mode, @idempotency_mode)
  subclass.instance_variable_set(:@persistence_ttl, @persistence_ttl)
end

#initial_state(name = nil) ⇒ Object



35
36
37
38
39
40
# File 'lib/smith/workflow/dsl.rb', line 35

def initial_state(name = nil)
  return @initial_state_name if name.nil?

  @initial_state_name = own_identifier(name)
  state(@initial_state_name)
end

#max_transitions(count = nil) ⇒ Object



64
65
66
67
68
# File 'lib/smith/workflow/dsl.rb', line 64

def max_transitions(count = nil)
  return @max_transitions_count if count.nil?

  @max_transitions_count = count
end

#migrate_from(version, &block) ⇒ Object

Register a one-step migration from stored version N to N+1. The block receives the persisted payload Hash (top-level keys already symbolized) and must return the migrated payload. Bumping the :schema_version key is the migration's responsibility but Smith advances defensively if the block omits it, so migrations stay loop-free.

Raises:

  • (ArgumentError)


118
119
120
121
122
123
124
125
126
127
# File 'lib/smith/workflow/dsl.rb', line 118

def migrate_from(version, &block)
  raise ArgumentError, "migrate_from requires a block" unless block

  unless version.is_a?(Integer) && version >= 1
    raise ArgumentError, "migrate_from version must be a positive Integer, got #{version.inspect}"
  end

  @migrations ||= {}
  @migrations[version] = block
end

#migrationsObject



129
130
131
# File 'lib/smith/workflow/dsl.rb', line 129

def migrations
  @migrations || {}
end

#persistence_key(&block) ⇒ Object



88
89
90
91
92
# File 'lib/smith/workflow/dsl.rb', line 88

def persistence_key(&block)
  return @persistence_key_builder unless block_given?

  @persistence_key_builder = block
end

#persistence_schema_version(version = nil) ⇒ Object

Schema version stamped into every persisted payload's :schema_version key. Restore compares the stored version with this value and either passes through (equal), applies registered migrate_from blocks one step at a time (stored less than current), or raises Smith::PersistenceSchemaMismatch (stored greater than current, or unbridged gap). Pre-versioning payloads (no :schema_version key) are treated as v1 for backward compatibility.



102
103
104
105
106
107
108
109
110
# File 'lib/smith/workflow/dsl.rb', line 102

def persistence_schema_version(version = nil)
  return @persistence_schema_version || 1 if version.nil?

  unless version.is_a?(Integer) && version >= 1
    raise ArgumentError, "persistence_schema_version must be a positive Integer, got #{version.inspect}"
  end

  @persistence_schema_version = version
end

#persistence_ttl(seconds = nil) ⇒ Object

Per-workflow TTL override (in seconds). Takes precedence over Smith.config.persistence_ttl at persist! time. nil (default) means inherit the global config.

Hosts typically set this when different workflow classes have different durability horizons: e.g., short-lived UI sessions at 1.day.to_i, long-running research workflows at 30.days.to_i.

Wiring contract: when the resolved TTL is non-nil, Workflow#persist! forwards it to the adapter as a ttl: kwarg. Shipped adapters (Memory, RedisStore, CacheStore, ActiveRecordStore) accept this kwarg; external duck-typed adapters that implement only the bare REQUIRED_METHODS contract without a ttl: kwarg will only break when a host actually opts into TTL.



198
199
200
201
202
203
204
205
206
207
# File 'lib/smith/workflow/dsl.rb', line 198

def persistence_ttl(seconds = nil)
  return @persistence_ttl if seconds.nil?

  unless seconds.is_a?(Numeric) && seconds.positive?
    raise ArgumentError,
          "persistence_ttl must be a positive Numeric (seconds), got #{seconds.inspect}"
  end

  @persistence_ttl = seconds
end

#pipeline(name, from:, to:) ⇒ Object



111
112
113
# File 'lib/smith/workflow/pipeline.rb', line 111

def pipeline(name, from:, to:, &)
  Pipeline.new(name, from: from, to: to, &).compile!(self)
end

#seed_messages(&block) ⇒ Object



82
83
84
85
86
# File 'lib/smith/workflow/dsl.rb', line 82

def seed_messages(&block)
  return @seed_messages_builder unless block_given?

  @seed_messages_builder = block
end

#seed_validation(mode = nil) ⇒ Object

Controls whether restore validates that the seed_messages builder still produces the same digest as when this workflow was originally persisted.

Modes:

:off    (default) skip validation entirely. Recommended when
       the seed builder is non-deterministic (timestamps,
       UUIDs, request-scoped data) since drift would
       surface on every restore.
:warn   log a warning via Smith.config.logger on drift; do
       not raise. Suitable for soft monitoring.
:strict raise Smith::SeedMismatch on drift. Suitable when
       the seed builder is deterministic (system
       instructions, static templates) and divergence
       indicates a code change that would invalidate the
       persisted conversation context.


149
150
151
152
153
154
155
156
157
# File 'lib/smith/workflow/dsl.rb', line 149

def seed_validation(mode = nil)
  return @seed_validation_mode || :off if mode.nil?

  unless %i[strict warn off].include?(mode)
    raise ArgumentError, "seed_validation must be :strict, :warn, or :off, got #{mode.inspect}"
  end

  @seed_validation_mode = mode
end

#state(name) ⇒ Object



42
43
44
45
46
47
# File 'lib/smith/workflow/dsl.rb', line 42

def state(name)
  name = own_identifier(name)
  @states ||= []
  @states << name unless @states.include?(name)
  generate_fail_transition if name == :failed
end

#transition(name, from:, to:) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/smith/workflow/dsl.rb', line 49

def transition(name, from:, to:, &)
  declared = Transition.new(name, from: from, to: to, &)
  name = declared.name
  @transitions ||= {}
  remove_from_transition_index(@transitions[name]) if @transitions.key?(name)
  @transitions[name] = declared
  insert_into_transition_index(declared)
end

#transition_at(index) ⇒ Object



225
226
227
# File 'lib/smith/workflow/dsl.rb', line 225

def transition_at(index)
  (@transitions || {}).values.fetch(index)
end

#transition_from?(state) ⇒ Boolean

Returns:

  • (Boolean)


217
218
219
# File 'lib/smith/workflow/dsl.rb', line 217

def transition_from?(state)
  transitions_by_state.fetch(state, []).any?
end

#transitions_from(state) ⇒ Object



209
210
211
# File 'lib/smith/workflow/dsl.rb', line 209

def transitions_from(state)
  transitions_by_state.fetch(state, []).dup
end