smith-agents 0.4.5 → 0.5.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.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +201 -0
  3. data/README.md +108 -4
  4. data/docs/CONFIGURATION.md +4 -1
  5. data/docs/PATTERNS.md +7 -0
  6. data/docs/PERSISTENCE.md +347 -25
  7. data/lib/smith/agent/lifecycle.rb +19 -10
  8. data/lib/smith/agent/registry/execution_binding_capture.rb +26 -0
  9. data/lib/smith/agent/registry/mutation_boundary.rb +29 -0
  10. data/lib/smith/agent/registry.rb +8 -0
  11. data/lib/smith/agent.rb +24 -2
  12. data/lib/smith/budget/amount_contract.rb +67 -0
  13. data/lib/smith/budget/amount_representation.rb +43 -0
  14. data/lib/smith/budget/decimal_context.rb +18 -0
  15. data/lib/smith/budget/ledger.rb +110 -18
  16. data/lib/smith/budget/ledger_publication.rb +60 -0
  17. data/lib/smith/budget/ledger_state.rb +21 -0
  18. data/lib/smith/budget/ledger_state_transition.rb +72 -0
  19. data/lib/smith/budget/public_state_validator.rb +25 -0
  20. data/lib/smith/budget/reservation.rb +20 -0
  21. data/lib/smith/budget/reservation_contract.rb +34 -0
  22. data/lib/smith/context/observation_masking.rb +3 -1
  23. data/lib/smith/context/state_injection.rb +1 -1
  24. data/lib/smith/doctor/checks/persistence_capabilities.rb +18 -4
  25. data/lib/smith/error.rb +5 -0
  26. data/lib/smith/errors.rb +6 -0
  27. data/lib/smith/exponential_backoff.rb +128 -0
  28. data/lib/smith/persistence_adapters/active_record_exact_predicate.rb +43 -0
  29. data/lib/smith/persistence_adapters/active_record_exact_store.rb +62 -0
  30. data/lib/smith/persistence_adapters/active_record_exact_write.rb +40 -0
  31. data/lib/smith/persistence_adapters/active_record_store.rb +7 -4
  32. data/lib/smith/persistence_adapters/cache_store.rb +1 -0
  33. data/lib/smith/persistence_adapters/memory.rb +16 -1
  34. data/lib/smith/persistence_adapters/redis_client_access.rb +38 -0
  35. data/lib/smith/persistence_adapters/redis_exact_write.rb +44 -0
  36. data/lib/smith/persistence_adapters/redis_store.rb +28 -11
  37. data/lib/smith/persistence_adapters/retry.rb +22 -10
  38. data/lib/smith/persistence_adapters.rb +21 -18
  39. data/lib/smith/persistence_payload_conflict.rb +12 -0
  40. data/lib/smith/tool/budget_enforcement.rb +2 -2
  41. data/lib/smith/types.rb +1 -0
  42. data/lib/smith/version.rb +2 -1
  43. data/lib/smith/workflow/branch_env.rb +1 -1
  44. data/lib/smith/workflow/budget_integration.rb +21 -11
  45. data/lib/smith/workflow/definition_identity/class_methods.rb +48 -0
  46. data/lib/smith/workflow/definition_identity.rb +25 -0
  47. data/lib/smith/workflow/deterministic_step.rb +2 -2
  48. data/lib/smith/workflow/dsl.rb +73 -5
  49. data/lib/smith/workflow/durability.rb +1 -1
  50. data/lib/smith/workflow/evaluator_optimizer.rb +7 -11
  51. data/lib/smith/workflow/execution.rb +16 -18
  52. data/lib/smith/workflow/execution_binding_resolution.rb +16 -0
  53. data/lib/smith/workflow/execution_result_snapshot.rb +118 -0
  54. data/lib/smith/workflow/fanout_execution.rb +1 -1
  55. data/lib/smith/workflow/graph/diagnostic.rb +27 -1
  56. data/lib/smith/workflow/graph/diagnostic_path.rb +24 -0
  57. data/lib/smith/workflow/graph/execution_successors.rb +65 -0
  58. data/lib/smith/workflow/graph/identifier_projection.rb +13 -0
  59. data/lib/smith/workflow/graph/metrics.rb +1 -3
  60. data/lib/smith/workflow/graph/nested_readiness_diagnostics.rb +15 -16
  61. data/lib/smith/workflow/graph/optimization_contract.rb +2 -2
  62. data/lib/smith/workflow/graph/orchestration_contract.rb +3 -3
  63. data/lib/smith/workflow/graph/reachability.rb +19 -31
  64. data/lib/smith/workflow/graph/reachability_diagnostics.rb +17 -1
  65. data/lib/smith/workflow/graph/retry_policy_diagnostic.rb +36 -0
  66. data/lib/smith/workflow/graph/runtime_binding_diagnostics.rb +2 -2
  67. data/lib/smith/workflow/graph/runtime_readiness.rb +3 -44
  68. data/lib/smith/workflow/graph/runtime_readiness_metrics.rb +2 -2
  69. data/lib/smith/workflow/graph/runtime_readiness_report_builder.rb +47 -0
  70. data/lib/smith/workflow/graph/runtime_readiness_traversal.rb +108 -0
  71. data/lib/smith/workflow/graph/state_diagnostics.rb +2 -2
  72. data/lib/smith/workflow/graph/transition_contract.rb +47 -0
  73. data/lib/smith/workflow/graph/transition_contract_attributes.rb +73 -0
  74. data/lib/smith/workflow/graph/transition_contract_configurations.rb +100 -0
  75. data/lib/smith/workflow/graph/transition_diagnostics.rb +54 -12
  76. data/lib/smith/workflow/graph/transition_optimization_configuration.rb +62 -0
  77. data/lib/smith/workflow/graph/transition_snapshot.rb +4 -1
  78. data/lib/smith/workflow/graph/validator.rb +1 -1
  79. data/lib/smith/workflow/graph.rb +62 -4
  80. data/lib/smith/workflow/guardrail_integration.rb +23 -9
  81. data/lib/smith/workflow/identifier.rb +24 -0
  82. data/lib/smith/workflow/message_admission.rb +40 -0
  83. data/lib/smith/workflow/message_admission_boundary.rb +22 -0
  84. data/lib/smith/workflow/message_batch.rb +60 -0
  85. data/lib/smith/workflow/message_value_normalizer.rb +133 -0
  86. data/lib/smith/workflow/nested_execution.rb +10 -0
  87. data/lib/smith/workflow/orchestrator_worker.rb +3 -7
  88. data/lib/smith/workflow/parallel/cancellation_signal.rb +14 -5
  89. data/lib/smith/workflow/parallel/execution_context.rb +113 -0
  90. data/lib/smith/workflow/parallel/nested_execution.rb +124 -0
  91. data/lib/smith/workflow/parallel/root_execution.rb +91 -0
  92. data/lib/smith/workflow/parallel.rb +19 -22
  93. data/lib/smith/workflow/parallel_agent_binding.rb +24 -0
  94. data/lib/smith/workflow/parallel_execution.rb +7 -3
  95. data/lib/smith/workflow/persistence.rb +37 -8
  96. data/lib/smith/workflow/prepared_step.rb +122 -0
  97. data/lib/smith/workflow/prepared_step_dispatch.rb +88 -0
  98. data/lib/smith/workflow/prepared_step_execution_authorization.rb +116 -0
  99. data/lib/smith/workflow/prepared_step_execution_result.rb +49 -0
  100. data/lib/smith/workflow/prepared_step_execution_scope.rb +45 -0
  101. data/lib/smith/workflow/prepared_step_recovery.rb +38 -0
  102. data/lib/smith/workflow/retry_execution.rb +20 -11
  103. data/lib/smith/workflow/split_step_persistence/boundary.rb +35 -22
  104. data/lib/smith/workflow/split_step_persistence/boundary_reset.rb +50 -0
  105. data/lib/smith/workflow/split_step_persistence/canonical_payload_digest.rb +55 -0
  106. data/lib/smith/workflow/split_step_persistence/checkpoint.rb +3 -5
  107. data/lib/smith/workflow/split_step_persistence/definition_boundary.rb +19 -0
  108. data/lib/smith/workflow/split_step_persistence/dispatch_boundary.rb +16 -0
  109. data/lib/smith/workflow/split_step_persistence/dispatch_claim.rb +118 -0
  110. data/lib/smith/workflow/split_step_persistence/dispatch_confirmation.rb +70 -0
  111. data/lib/smith/workflow/split_step_persistence/dispatch_verification.rb +35 -0
  112. data/lib/smith/workflow/split_step_persistence/execution.rb +49 -47
  113. data/lib/smith/workflow/split_step_persistence/execution_authorization.rb +89 -0
  114. data/lib/smith/workflow/split_step_persistence/execution_binding_collector.rb +90 -0
  115. data/lib/smith/workflow/split_step_persistence/execution_binding_snapshot.rb +95 -0
  116. data/lib/smith/workflow/split_step_persistence/execution_result_capture.rb +48 -0
  117. data/lib/smith/workflow/split_step_persistence/execution_verification.rb +65 -0
  118. data/lib/smith/workflow/split_step_persistence/execution_workflow_snapshot.rb +77 -0
  119. data/lib/smith/workflow/split_step_persistence/inheritance.rb +3 -1
  120. data/lib/smith/workflow/split_step_persistence/payloads.rb +7 -3
  121. data/lib/smith/workflow/split_step_persistence/preparation.rb +30 -2
  122. data/lib/smith/workflow/split_step_persistence/preparation_claim.rb +38 -12
  123. data/lib/smith/workflow/split_step_persistence/preparation_payload.rb +25 -0
  124. data/lib/smith/workflow/split_step_persistence/preparation_recovery.rb +10 -2
  125. data/lib/smith/workflow/split_step_persistence/recovery.rb +125 -0
  126. data/lib/smith/workflow/split_step_persistence/recovery_boundary.rb +56 -0
  127. data/lib/smith/workflow/split_step_persistence/recovery_class_methods.rb +14 -0
  128. data/lib/smith/workflow/split_step_persistence/replace_exact_signature.rb +46 -0
  129. data/lib/smith/workflow/split_step_persistence/restart_safe_adapter.rb +42 -0
  130. data/lib/smith/workflow/split_step_persistence/subclass_boundary.rb +50 -0
  131. data/lib/smith/workflow/split_step_persistence/transaction_identity.rb +45 -0
  132. data/lib/smith/workflow/split_step_persistence.rb +36 -0
  133. data/lib/smith/workflow/step_completion.rb +31 -0
  134. data/lib/smith/workflow/string_snapshot.rb +25 -0
  135. data/lib/smith/workflow/transition.rb +122 -133
  136. data/lib/smith/workflow/transition_actionability.rb +22 -0
  137. data/lib/smith/workflow.rb +44 -18
  138. data/lib/smith.rb +41 -2
  139. data/sig/smith.rbs +1 -0
  140. metadata +110 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a4cd092162200b5cb861d9b9cd48069eab2e00beef71abf5fbfb91705c235ee
4
- data.tar.gz: 483180aec2d6b6d63138324edfb10f564ca2c4ff800603eeb3a43807f4b20f95
3
+ metadata.gz: 5f0194d534f9aa31c42dc14a0bd92a092f0088b3ac138ffe65060b00c6c564a0
4
+ data.tar.gz: 314f07b2e7c51f2372fec28cf7d495672ce38f4ba1f09a9af4cccba6f9cefd17
5
5
  SHA512:
6
- metadata.gz: 240e30a347d30d06063e57bece46ef646ce68c31463192a123cffd47858311ee1a92d6f7c664c519347775ff5df861e63ae8103005806cf39e400826b3e04c75
7
- data.tar.gz: e478845f5665afeea75fb2b905dd4a7c48aa4ff56ca748661a1dc8302f44dacb029fa60117d900acfeca5558a52a9fde5bc8c7552a7a8149808b92b4898d1c82
6
+ metadata.gz: f75713ef01aa104f3add7f6804dc8f77061ad12ea787598a16dce09bb0b08c23e256f35b346d4f3e53c99b86a0bdb5603ca6f7236dc66a1c60130a16ab9180a5
7
+ data.tar.gz: 28953ec750b620efff05055af313031eb24e97f6591dea4ea04b029d8c71d0a72de37d2aecb6ec78c532f73d3301d854b6f853bd26515abd98f93a5fe4668edb
data/CHANGELOG.md CHANGED
@@ -6,6 +6,207 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.0] - 2026-07-19
10
+
11
+ This release contains intentional pre-1.0 public contract tightening and is a
12
+ minor release rather than a `0.4.x` patch.
13
+
14
+ ### Added
15
+
16
+ - Add configurable per-execution parallel branch/concurrency limits and a
17
+ shared finite exponential-backoff contract for workflow and persistence
18
+ retries.
19
+ - Keep nested fan-out inside one bounded execution context across Fiber
20
+ boundaries, inherit outer cancellation, use only idle top-level workers, and
21
+ drain already-running branch work rather than detaching work or forcibly
22
+ killing threads with live resources. Add a configurable nesting limit,
23
+ hard-capped at 256, so recursive host composition fails before Ruby stack
24
+ exhaustion.
25
+
26
+ - Add bounded host-coordinated session-message admission through
27
+ `Workflow#append_session_messages!`. Smith owns a canonical immutable copy,
28
+ serializes append against workflow execution, and returns an immutable
29
+ `MessageAdmission` digest witness while leaving persistence, idempotency,
30
+ session identity, and resume policy with the host.
31
+ - Add opt-in restart-safe prepared-step recovery. Workflow classes bind a
32
+ host-supplied executable `definition_digest`; hosts submit an immutable typed
33
+ `PreparedStepRecovery.not_started` decision; Smith verifies the exact
34
+ committed preparation before reconstructing a guarded boundary.
35
+ - Add exact-payload `replace_exact` and bounded `persistence_identity` adapter
36
+ capabilities. Restart-safe execution atomically advances the durable payload
37
+ from `prepared` to `dispatching` before transition work, preventing original
38
+ and recovered processes from both dispatching the same operation.
39
+ - Separate restart-safe dispatch claim, commit confirmation, and transition
40
+ execution so transactional hosts can atomically correlate their attempt
41
+ ledger before any provider, tool, or deterministic step work begins.
42
+ - Add immutable, bounded `PreparedStepDispatch` receipts. An exclusive host that
43
+ durably proves execution never started can reconstruct an exact committed
44
+ `dispatching` boundary without replaying its claim.
45
+ - Add a generic process-local prepared-step execution authorization. Smith
46
+ verifies an exact preparation or dispatch without performing transition work;
47
+ a host may then commit its own executing-attempt record before consuming the
48
+ non-copyable, process-bound capability exactly once. Standard Marshal,
49
+ Psych/YAML, and JSON serialization hooks reject the capability.
50
+ - Add `PreparedStepExecutionResult` so hosts can distinguish successful and
51
+ failure-routed transition attempts without inferring success from workflow
52
+ state.
53
+ - Add strict bounded `PreparedStep.deserialize` transport decoding and the
54
+ typed `Sha256Hex` scalar. Prepared step counters are constrained to positive
55
+ signed 64-bit values for both Hash and JSON transports.
56
+
57
+ - Expose `Workflow#pending_transition_name` so hosts can inspect the exact next
58
+ transition without serializing workflow state or traversing the graph.
59
+ - Expose an immutable `Smith::Workflow::PreparedStep` descriptor for an active
60
+ strict split-step boundary. Hosts can persist opaque token, transition,
61
+ persistence-version, step-number, and preparation-digest identity without
62
+ reaching into workflow internals or copying Smith state.
63
+ `prepare_persisted_step!` retains its existing transition-name return value.
64
+ - Compute descriptor identity before persistence dispatch with bounded,
65
+ canonical JSON hashing, preventing post-write validation failures and
66
+ unbounded preparation work.
67
+ - Fence transactional split-step descriptors to an adapter-provided exact
68
+ transaction identity. `ActiveRecordStore` uses Rails' public
69
+ `current_transaction.uuid`; custom transactional adapters fail before writing
70
+ unless they expose an equivalent identity.
71
+
72
+ ### Changed
73
+
74
+ - Make budget settlement receipt-based and one-shot. Reservation, reconciliation,
75
+ and release now fail closed on cross-ledger, replayed, unknown-dimension, or
76
+ amount-only settlement and publish aggregate state atomically. This tightens
77
+ the pre-1.0 ledger API and requires the next minor release.
78
+ - Define budget scalar inputs as finite, non-negative `Integer` or `Float`
79
+ values and use exact internal decimal arithmetic so ordinary Float budgets do
80
+ not strand receipts or reject mathematically valid capacity. Ledger snapshots
81
+ remain JSON-safe numerics. This intentionally rejects opaque custom numeric
82
+ objects and non-JSON-safe numeric types at the boundary. Budget arithmetic is
83
+ isolated from and restores any host-configured `BigDecimal` precision limit;
84
+ `bigdecimal` is now an explicit runtime dependency.
85
+ - Reject repeated or mixed primary transition declarations instead of silently
86
+ replacing an earlier execution primitive.
87
+ - Treat named transition target-origin mismatches as graph errors rather than
88
+ warnings because runtime cannot execute the declared route from that state.
89
+ - Constrain workflow topology identifiers to non-blank String or Symbol values,
90
+ matching runtime lookup, event, persistence, and graph-inspection semantics.
91
+ - Reject oversized static fan-out declarations, non-finite or unschedulable
92
+ retry delays, and retry attempt counts above the configurable default limit
93
+ of 100 before executable work begins. This intentionally tightens previously
94
+ unbounded pre-1.0 declarations; hosts that require a higher finite attempt
95
+ bound must configure it before loading workflow classes.
96
+
97
+ - Restore persisted workflow state without mutating caller-owned nested outcome
98
+ data while preserving Smith's symbolized runtime outcome contract.
99
+ - Reject strict in-progress payloads before schema migration so a migration
100
+ cannot clear the uncertainty marker and replay a transition.
101
+ - Keep an ambiguously acknowledged exact dispatch claim fail-closed in durable
102
+ `dispatching` state. Smith does not infer or replay an uncertain external
103
+ outcome.
104
+ - Run Redis versioned and exact-payload CAS inside redis-rb's native
105
+ no-reconnect scope and do not replay either write after a transient
106
+ connection failure; a lost acknowledgement is outcome-unknown and must be
107
+ reconciled.
108
+ - Pin the executable definition digest for the complete split-step boundary and
109
+ seal it on the workflow class when preparation or recovery authority is
110
+ acquired. Concurrent digest setters linearize before sealing or fail before
111
+ claim and execution.
112
+ - Make Active Record exact replacement one conditional SQL update over key and
113
+ byte-exact payload while incrementing its optimistic-lock column. Validate
114
+ the database primary key or an unconditional single-column unique key index
115
+ through Active Record's table-keyed schema cache before each exact write.
116
+ - Resolve callable Redis command clients as clients rather than factories, and
117
+ require a native reconnect-disabling scope before any Redis CAS begins.
118
+ - Treat a persistence identity as available in doctor diagnostics only when its
119
+ value satisfies the same bounded non-empty contract used at runtime.
120
+ - Revalidate workflow definition and transition identity when an execution
121
+ authorization is consumed. Capability release and execution are linearized by
122
+ exact identity so concurrent or stale holders cannot revoke or replay work.
123
+ - Route an active prepared execution through a Smith-owned private-method
124
+ membrane, including reachable nested workflows. Subclass overrides retain
125
+ normal Ruby dispatch outside the authorized boundary but cannot substitute
126
+ retry, dispatch, guardrail, budget, or completion behavior during it. Nested
127
+ authority is limited to the root execution thread and revoked when that
128
+ execution closes, even when a host retains a child workflow instance.
129
+ - Own String workflow identifiers at declaration time and keep graph snapshots
130
+ isolated from mutable caller aliases. Reject other topology object types at
131
+ the DSL boundary so graph lookup, event, and persistence semantics remain
132
+ consistent. The authorized
133
+ completion and failure boundaries bind Smith's implementation directly so
134
+ subclass method-name collisions cannot bypass typed result capture.
135
+ - Capture exact direct, fan-out, optimizer, orchestrator, and reachable nested
136
+ agent bindings during authorization with bounded `O(V + E)` traversal. Later
137
+ mutable registry replacement cannot alter the authorized execution.
138
+ Same-agent parallel execution resolves its captured binding on the authorized
139
+ root thread and passes an identity-scoped class reference into worker
140
+ branches without leaking it into re-entrant workflows on the same thread.
141
+ - Replace recursive graph reachability with an iterative walk over a graph-local
142
+ outgoing index. Existing graph snapshots remain isolated from later workflow
143
+ class mutation. State-reference validation and terminal-state metrics use
144
+ constant-time graph indexes. Nested runtime-readiness inspection uses an
145
+ iterative postorder traversal with workflow-class identity memoization and
146
+ cycle-edge diagnostics. Nested workflow contracts are captured and
147
+ revalidated before and immediately after child construction without repeated
148
+ full-transition scans.
149
+ - Bound durable preparation comparison by canonical payload byte and node
150
+ limits, and preserve the legacy mutable step Hash while typed execution
151
+ results own an immutable, cycle-aware, bounded JSON-like snapshot. Result
152
+ validation now completes before successful workflow state mutation; invalid
153
+ provider output enters normal failure routing and returns a typed failure.
154
+ Hash keys are limited to owned String or Symbol values, and non-finite Floats
155
+ are rejected before success mutation.
156
+ - Normalize top-level string keys in restored message Hashes before rebuilding
157
+ RubyLLM messages. Nested message content remains untouched so transport
158
+ decoding does not rewrite host payloads.
159
+
160
+ - Maintain a declaration-time outgoing-transition index. First-transition and
161
+ terminal checks are constant-time; enumerating transitions from one state is
162
+ proportional to that state's outdegree. Stable declaration precedence,
163
+ subclass isolation, and transition redefinition semantics are preserved.
164
+ - Custom adapters that report an open transaction now require
165
+ `transaction_identity` for strict split-step preparation. This intentionally
166
+ tightens the 0.4.5 boolean transaction contract so later unrelated
167
+ transactions cannot re-authorize a rolled-back descriptor.
168
+
169
+ ### Verification
170
+
171
+ - Default suite: 1,304 examples, 0 failures on the final tree.
172
+ - Runtime-hardening practical suite: 551 scenarios and 33,722 assertions across
173
+ graph reachability, retry/backoff, nested parallel execution, cancellation,
174
+ receipt-based budgets, hostile decimal precision, and asynchronous thread
175
+ interruption.
176
+ - The built gem and its declared dependency set loaded under Ruby 3.2.2 and
177
+ Ruby 3.2.8; each runtime passed the same 551-scenario practical suite.
178
+ - Downstream verification against the local checkout: Smith Runtime passed
179
+ 1,188 runs and 6,287 assertions with 35 skips, plus 20 practical scenarios;
180
+ Smith Studio passed 1,848 runs and 13,851 assertions with one skip, plus 20
181
+ practical scenarios.
182
+ - Message-admission and restore-input suite: 30 examples, 0 failures. A
183
+ 44-scenario public-API matrix covered canonicalization, alias isolation,
184
+ bounded rejection, lifecycle contention, persistence round trips, and a
185
+ 90,000-value near-limit message.
186
+ - Focused graph, execution-authorization, binding-snapshot, typed-result, and
187
+ restored-message suite: 71 examples, 0 failures. All newly added files pass RuboCop;
188
+ the changed surface passes after excluding the repository's pre-existing
189
+ metrics baseline, and `git diff --check` passes.
190
+ - Practical gem execution: 30 restart-safe scenarios covering serialized
191
+ preparation and dispatch recovery, exact-claim contention, corruption,
192
+ definition drift, ambiguous acknowledgements, and Active Record
193
+ commit/rollback coordination; a real redis-rb 5.4.1 run additionally proved
194
+ direct-client and factory resolution plus bounded multi-client exact-claim
195
+ contention.
196
+ - Practical execution-authorization matrix: 30 public-API scenarios covering
197
+ success and handled failure variants, immutable graph snapshots up to 5,000
198
+ transitions, cyclic and shared result values, bounded rejection cases,
199
+ release, single-use authorization, copying, serialization, and concurrent
200
+ claims.
201
+ - Additional practical hardening matrix: 20 public-API checks covering mutable
202
+ topology projection, 1,000/2,000/4,000-transition validation, 5,000-transition
203
+ reachability, a 1,200-workflow nested chain, shared-child memoization, cycle
204
+ diagnostics, root and nested execution membranes, and authorization
205
+ contention. Measured validation times were 5.80 ms, 12.80 ms, and 16.39 ms
206
+ respectively on the local Ruby 4.0.1 verification host.
207
+ - A downstream host passed its serial and parallel suites against the
208
+ local Smith checkout and completed its practical direct-model boundary.
209
+
9
210
  ## [0.4.5] - 2026-07-11
10
211
 
11
212
  Patch release for generic host-coordinated workflow step boundaries and
data/README.md CHANGED
@@ -17,7 +17,7 @@ environment before calling the slice complete.
17
17
 
18
18
  ```ruby
19
19
  # Gemfile
20
- gem "smith-agents", "0.4.5", require: "smith"
20
+ gem "smith-agents", "0.5.0", require: "smith"
21
21
  ```
22
22
 
23
23
  ```bash
@@ -175,6 +175,12 @@ Smith.configure do |config|
175
175
  config.persistence_ttl = 1.day.to_i
176
176
  config.persistence_retry_policy = { attempts: 3, base_delay: 0.1, max_delay: 1.0 }
177
177
 
178
+ # Generic runtime resource bounds
179
+ config.parallel_branch_limit = 1_000
180
+ config.parallel_concurrency = 8
181
+ config.parallel_nesting_limit = 64
182
+ config.retry_attempt_limit = 100
183
+
178
184
  # OpenAI /v1/responses routing for gpt-5 + tools + thinking. :auto (default) or :off.
179
185
  config.openai_api_mode = :auto
180
186
 
@@ -200,10 +206,21 @@ result = ReplyWorkflow.run_persisted!(
200
206
  key: "ticket:T-1042",
201
207
  adapter: Smith.persistence_adapter
202
208
  )
209
+
210
+ # Admit host-supplied conversation input at a stable workflow boundary.
211
+ workflow = ReplyWorkflow.restore("ticket:T-1042", adapter: Smith.persistence_adapter)
212
+ admission = workflow.append_session_messages!(role: :user, content: "Continue")
203
213
  ```
204
214
 
215
+ `append_session_messages!` validates and owns a bounded canonical copy of the
216
+ message batch, appends it under Smith's workflow lifecycle lock, and returns an
217
+ immutable digest witness. The host still owns session identity, transaction
218
+ coordination, persistence, idempotency, and resume policy.
219
+
205
220
  Built-in adapters (all support TTL where the backend allows; `Redis`,
206
- `ActiveRecord`, and `Memory` support optimistic locking via `store_versioned`;
221
+ `ActiveRecord`, and `Memory` support optimistic locking via `store_versioned`
222
+ and exact dispatch claims via `replace_exact`; `ActiveRecord` participates in
223
+ the caller's transaction for host-record coordination;
207
224
  `Redis` and `Memory` also support heartbeat probes via `record_heartbeat` /
208
225
  `last_heartbeat`):
209
226
 
@@ -214,7 +231,48 @@ Built-in adapters (all support TTL where the backend allows; `Redis`,
214
231
  - `:active_record` — keyed ActiveRecord model with an enabled optimistic-locking
215
232
  column for row-level CAS; Smith's logical version remains in the JSON payload
216
233
 
217
- See [`docs/PERSISTENCE.md`](docs/PERSISTENCE.md) for schema versioning, seed-drift validation, and the `idempotency_mode :strict` step-in-progress contract.
234
+ See [`docs/PERSISTENCE.md`](docs/PERSISTENCE.md) for schema versioning,
235
+ seed-drift validation, the `idempotency_mode :strict` step-in-progress contract,
236
+ host-coordinated message admission, and opt-in restart-safe prepared recovery.
237
+ Hosts that need to commit a durable attempt before provider or tool work can
238
+ separate verification from execution with
239
+ `authorize_prepared_step_execution!`, commit their own attempt ledger, and then
240
+ consume the exact process-local capability through
241
+ `execute_authorized_prepared_step!`. Smith does not own the host lease, attempt
242
+ record, scheduler, or unknown-outcome policy. Authorization discovers the exact
243
+ agent bindings reachable by that transition with a bounded `O(V + E)` graph
244
+ walk, then resolves the bounded binding set in one registry epoch. A concurrent
245
+ registry writer therefore cannot produce a mixed authorization containing
246
+ bindings from two registry states or replace a captured binding between
247
+ authorization and work. Every public Dry::Container mutation path participates
248
+ in that epoch. Execution authorization accepts only concrete registered
249
+ `Smith::Agent` classes; lazy container callbacks are not executed while
250
+ authority is captured.
251
+ While the capability is active, a host may call `each_agent_binding` with a
252
+ block to inspect the exact captured `(registry_name, agent_class)` pairs before
253
+ external work. Enumeration is process-local, performs no dispatch, and is
254
+ rejected after release or consumption; it deliberately does not return a lazy
255
+ enumerator that could escape the authorization scope.
256
+ Reachable nested workflow transition contracts are captured and revalidated
257
+ before child execution. The typed result owns a bounded snapshot of JSON-like
258
+ Ruby values; unsupported mutable output values fail closed.
259
+ During authorized execution, Smith also seals its private execution path against
260
+ workflow subclass method-name collisions, including nested child workflows.
261
+ Ordinary non-authorized runs retain normal Ruby override behavior.
262
+
263
+ Hosts that generate agent classes dynamically may call
264
+ `register_as :name, publish: false` while constructing the class. This records
265
+ the class identity without exposing it through `Smith::Agent::Registry`. After
266
+ the host has validated and sealed the class, it may publish the exact class with
267
+ `agent_class.publish_registration!`. Publication always uses the staged,
268
+ immutable identity; callers cannot provide a second key. Registry collision and
269
+ mutation epoch guarantees remain unchanged.
270
+
271
+ Hosts that persist or cache generated executable definitions should include
272
+ `Smith::EXECUTION_SEMANTICS_VERSION` in their definition identity. The gem
273
+ version describes the package release; the execution-semantics version changes
274
+ when Smith's executable workflow contract changes and must participate in host
275
+ cache invalidation.
218
276
 
219
277
  ## Tools and Guardrails
220
278
 
@@ -240,6 +298,30 @@ end
240
298
 
241
299
  Budgets reserve serially at each step and reconcile after the agent call. Parallel branches reserve scoped envelopes that release back to the parent ledger. The `Workflow::RunResult` carries `total_tokens`, `total_cost`, and per-call `usage_entries`.
242
300
 
301
+ Direct ledger integrations settle the exact receipt returned by reservation:
302
+
303
+ ```ruby
304
+ reservation = ledger.reserve_many!(total_tokens: 1_000, total_cost: 0.10)
305
+ ledger.reconcile_many!(reservation, actual: { total_tokens: 640, total_cost: 0.06 })
306
+ ```
307
+
308
+ `release!` / `release_many!` accept the same receipt when work never starts.
309
+ Receipts are ledger-owned and one-shot: another ledger, a replay, or an
310
+ amount-only settlement is rejected.
311
+
312
+ Budget amounts accept finite, non-negative `Integer` and `Float` values. Smith
313
+ uses an exact `BigDecimal` representation derived from each value's canonical
314
+ decimal text and converts ledger snapshots back to ordinary JSON-safe numerics,
315
+ avoiding cumulative binary Float drift without changing persisted workflow
316
+ state shapes. Ledger arithmetic temporarily uses unlimited decimal precision and
317
+ restores the host's existing `BigDecimal` precision limit before returning.
318
+
319
+ Budget operations are atomic for normal concurrent callers and synchronous
320
+ exceptions. Smith uses cooperative cancellation and never terminates branch
321
+ threads with `Thread#raise` or `Thread#kill`. Arbitrary asynchronous thread
322
+ termination can make any in-process return value outcome-ambiguous; hosts must
323
+ use cancellation signals, bounded I/O, and process isolation for hard stops.
324
+
243
325
  ## Doctor
244
326
 
245
327
  After adding Smith, verify the integration:
@@ -296,6 +378,28 @@ When no classes are passed, `retry_on` uses `Smith::Errors.retryable?`.
296
378
  This is a bounded local transition retry policy. Durable scheduling, long waits,
297
379
  and external idempotency guarantees remain host-owned.
298
380
 
381
+ ## Runtime Complexity
382
+
383
+ - Executable graph reachability is iterative `O(V + E)` time and `O(V)` space,
384
+ using indexed outgoing transitions and runtime-equivalent successor rules.
385
+ - Parallel execution is `O(B)` scheduling and result space for `B` branches,
386
+ with at most `C = parallel_concurrency` active worker threads per top-level
387
+ execution. Re-entrant fan-out inherits the same cancellation signal and uses
388
+ only idle workers from the top-level execution context, falling back to the
389
+ current worker when no capacity remains. Nested calls fail closed before the
390
+ configurable nesting bound can exhaust Ruby's stack. Cancellation prevents
391
+ queued branch bodies from starting;
392
+ already-running branches are drained before control returns so work is never
393
+ detached. Branch adapters must use bounded I/O and honor the cancellation
394
+ signal. Hosts continue to own process-level concurrency and hard deadlines.
395
+ - Reservation and settlement across `L` declared budget dimensions and `D`
396
+ operation dimensions is `O(L + D)` time and space. Receipt lookup is expected
397
+ `O(1)`; no operation scans or copies the active receipt registry. Integer and
398
+ Float amounts use exact decimal arithmetic internally.
399
+ - Retry delay calculation is `O(1)` time and space per attempt. A policy with
400
+ `A` attempts performs at most `A` guarded operations and never materializes an
401
+ exponential schedule.
402
+
299
403
  ## Development
300
404
 
301
405
  Smith changes must not use brute-force graph search, validation, scheduling, or
@@ -309,5 +413,5 @@ bundle exec rspec
309
413
  bundle exec rubocop
310
414
  ```
311
415
 
312
- 880 examples, MIT licensed. See [`CHANGELOG.md`](CHANGELOG.md) for the current
416
+ 1,175 examples, MIT licensed. See [`CHANGELOG.md`](CHANGELOG.md) for the current
313
417
  release surface.
@@ -105,6 +105,10 @@ end
105
105
  | `persistence_options` | Per-adapter options (client, namespace, model, columns) | See "Built-In Persistence Adapters" |
106
106
  | `persistence_ttl` | Global TTL for persisted state (Integer/Float seconds; nil = no expiry) | Set when long-tail abandoned workflows accumulate in storage |
107
107
  | `persistence_retry_policy` | Exponential-backoff policy for transient adapter I/O failures | Defaults to `{ attempts: 3, base_delay: 0.1, max_delay: 1.0 }` |
108
+ | `retry_attempt_limit` | Maximum attempts accepted by workflow and persistence retry policies | Defaults to `100` |
109
+ | `parallel_branch_limit` | Maximum branches accepted by one homogeneous or heterogeneous fan-out | Defaults to `1_000` |
110
+ | `parallel_concurrency` | Maximum active branch threads within one parallel execution | Defaults to `8` |
111
+ | `parallel_nesting_limit` | Maximum re-entrant fan-out depth; hard-capped at `256` | Defaults to `64` |
108
112
  | `test_mode` | Auto-select `:memory` adapter when `persistence_adapter` is nil | Enable in `spec_helper.rb` to skip Redis/cache wiring in tests |
109
113
  | `openai_api_mode` | `:auto` routes (gpt-5 family + tools + thinking) via `/v1/responses` using Smith's vendored Responses adapter (sync only; streaming over `/v1/responses` is not yet supported); `:off` drops incompatible tools instead | Leave `:auto` (default) unless you need streaming with the (gpt-5 + tools + thinking) combo, in which case set `:off` for graceful tool-dropping |
110
114
  | `trace_normalizer` | Emit `:normalizer_decision` trace events from `Smith::Models::Normalizer` | Useful when debugging cross-provider request shaping |
@@ -120,4 +124,3 @@ Add settings in this order:
120
124
  4. `config.pricing`
121
125
 
122
126
  Do not start by configuring every advanced switch at once.
123
-
data/docs/PATTERNS.md CHANGED
@@ -234,10 +234,17 @@ end
234
234
  Why this is valuable:
235
235
 
236
236
  - Smith treats each branch as a real invocation
237
+ - results preserve branch declaration order while active branch work is bounded by `parallel_concurrency`
237
238
  - workflow budgets remain cumulative outer limits
238
239
  - agent budgets still narrow each branch call
239
240
  - branch failures discard step output and route through normal failure handling
240
241
  - prepared input is reused consistently across branches
242
+ - cancellation prevents queued branch bodies from starting and drains any
243
+ already-running branch before returning; branch adapters should use bounded
244
+ I/O and honor the cancellation signal
245
+ - nested fan-out shares its top-level execution context and cancellation signal;
246
+ it uses only idle workers and falls back to its current worker when no capacity
247
+ remains, preserving the configured concurrency ceiling without pool deadlocks
241
248
 
242
249
  ## Example 6: Heterogeneous Fan-Out
243
250