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



47
48
49
50
51
# File 'lib/smith/workflow/dsl.rb', line 47

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

  @budget_config = opts
end

#context_manager(klass = nil) ⇒ Object



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

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

  @context_manager_class = klass
end

#find_transition(name) ⇒ Object



202
203
204
# File 'lib/smith/workflow/dsl.rb', line 202

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

#from_state(hash) ⇒ Object



206
207
208
209
210
# File 'lib/smith/workflow/dsl.rb', line 206

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

#guardrails(klass = nil) ⇒ Object



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

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.


162
163
164
165
166
167
168
169
170
# File 'lib/smith/workflow/dsl.rb', line 162

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
# 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(:@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



29
30
31
32
33
34
# File 'lib/smith/workflow/dsl.rb', line 29

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

  @initial_state_name = name
  state(name)
end

#max_transitions(count = nil) ⇒ Object



53
54
55
56
57
# File 'lib/smith/workflow/dsl.rb', line 53

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)


107
108
109
110
111
112
113
114
115
116
# File 'lib/smith/workflow/dsl.rb', line 107

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



118
119
120
# File 'lib/smith/workflow/dsl.rb', line 118

def migrations
  @migrations || {}
end

#persistence_key(&block) ⇒ Object



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

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.



91
92
93
94
95
96
97
98
99
# File 'lib/smith/workflow/dsl.rb', line 91

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.



187
188
189
190
191
192
193
194
195
196
# File 'lib/smith/workflow/dsl.rb', line 187

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



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

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.


138
139
140
141
142
143
144
145
146
# File 'lib/smith/workflow/dsl.rb', line 138

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



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

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

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



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

def transition(name, from:, to:, &)
  @transitions ||= {}
  @transitions[name] = Transition.new(name, from: from, to: to, &)
end

#transitions_from(state) ⇒ Object



198
199
200
# File 'lib/smith/workflow/dsl.rb', line 198

def transitions_from(state)
  (@transitions || {}).values.select { |t| t.from == state }
end