Module: Smith::Workflow::DSL::ClassMethods
- Defined in:
- lib/smith/workflow/dsl.rb,
lib/smith/workflow/pipeline.rb
Instance Method Summary collapse
- #budget(**opts) ⇒ Object
- #context_manager(klass = nil) ⇒ Object
- #find_transition(name) ⇒ Object
- #first_transition_from(state) ⇒ Object
- #from_state(hash) ⇒ Object
- #guardrails(klass = nil) ⇒ Object
-
#idempotency_mode(mode = nil) ⇒ Object
Controls whether run_persisted! / advance_persisted! stamp a step_in_progress marker before each advance and clear it afterward.
- #inherited(subclass) ⇒ Object
- #initial_state(name = nil) ⇒ Object
- #max_transitions(count = nil) ⇒ Object
-
#migrate_from(version, &block) ⇒ Object
Register a one-step migration from stored version N to N+1.
- #migrations ⇒ Object
- #persistence_key(&block) ⇒ Object
-
#persistence_schema_version(version = nil) ⇒ Object
Schema version stamped into every persisted payload's :schema_version key.
-
#persistence_ttl(seconds = nil) ⇒ Object
Per-workflow TTL override (in seconds).
- #pipeline(name, from:, to:) ⇒ Object
- #seed_messages(&block) ⇒ Object
-
#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.
- #state(name) ⇒ Object
- #transition(name, from:, to:) ⇒ Object
- #transition_at(index) ⇒ Object
- #transition_from?(state) ⇒ Boolean
- #transitions_from(state) ⇒ Object
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.
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 |
#migrations ⇒ Object
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 (&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
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 |