smith-agents 0.4.5 → 0.6.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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -0
  3. data/README.md +191 -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 +37 -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 +10 -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/composite/branch.rb +93 -0
  46. data/lib/smith/workflow/composite/branch_budget_contract.rb +41 -0
  47. data/lib/smith/workflow/composite/branch_contract.rb +102 -0
  48. data/lib/smith/workflow/composite/branch_execution.rb +124 -0
  49. data/lib/smith/workflow/composite/branch_failure.rb +91 -0
  50. data/lib/smith/workflow/composite/branch_outcome.rb +108 -0
  51. data/lib/smith/workflow/composite/budget_allocator.rb +78 -0
  52. data/lib/smith/workflow/composite/budget_math.rb +51 -0
  53. data/lib/smith/workflow/composite/contract.rb +100 -0
  54. data/lib/smith/workflow/composite/effects.rb +140 -0
  55. data/lib/smith/workflow/composite/effects_application.rb +36 -0
  56. data/lib/smith/workflow/composite/effects_baseline.rb +36 -0
  57. data/lib/smith/workflow/composite/effects_preflight.rb +102 -0
  58. data/lib/smith/workflow/composite/encoded_value_budget.rb +36 -0
  59. data/lib/smith/workflow/composite/enums.rb +27 -0
  60. data/lib/smith/workflow/composite/error.rb +54 -0
  61. data/lib/smith/workflow/composite/error_evidence.rb +61 -0
  62. data/lib/smith/workflow/composite/execution_contract.rb +73 -0
  63. data/lib/smith/workflow/composite/fanout_branch_contract.rb +61 -0
  64. data/lib/smith/workflow/composite/input.rb +53 -0
  65. data/lib/smith/workflow/composite/outcome_accumulator.rb +131 -0
  66. data/lib/smith/workflow/composite/outcome_set.rb +23 -0
  67. data/lib/smith/workflow/composite/payload.rb +128 -0
  68. data/lib/smith/workflow/composite/payload_digest.rb +28 -0
  69. data/lib/smith/workflow/composite/plan.rb +130 -0
  70. data/lib/smith/workflow/composite/plan_integrity.rb +52 -0
  71. data/lib/smith/workflow/composite/planner.rb +53 -0
  72. data/lib/smith/workflow/composite/preparation.rb +27 -0
  73. data/lib/smith/workflow/composite/reducer.rb +133 -0
  74. data/lib/smith/workflow/composite/reduction.rb +31 -0
  75. data/lib/smith/workflow/composite/value_budget.rb +90 -0
  76. data/lib/smith/workflow/composite_branch_execution_authorization.rb +52 -0
  77. data/lib/smith/workflow/deadline_enforcement.rb +22 -5
  78. data/lib/smith/workflow/definition_identity/class_methods.rb +48 -0
  79. data/lib/smith/workflow/definition_identity.rb +25 -0
  80. data/lib/smith/workflow/deterministic_step.rb +2 -2
  81. data/lib/smith/workflow/dsl.rb +73 -5
  82. data/lib/smith/workflow/durability.rb +1 -1
  83. data/lib/smith/workflow/evaluator_optimizer.rb +7 -11
  84. data/lib/smith/workflow/execution.rb +28 -30
  85. data/lib/smith/workflow/execution_binding_resolution.rb +16 -0
  86. data/lib/smith/workflow/execution_result_snapshot.rb +118 -0
  87. data/lib/smith/workflow/fanout_execution.rb +20 -19
  88. data/lib/smith/workflow/graph/diagnostic.rb +27 -1
  89. data/lib/smith/workflow/graph/diagnostic_path.rb +24 -0
  90. data/lib/smith/workflow/graph/execution_successors.rb +65 -0
  91. data/lib/smith/workflow/graph/identifier_projection.rb +13 -0
  92. data/lib/smith/workflow/graph/metrics.rb +1 -3
  93. data/lib/smith/workflow/graph/nested_readiness_diagnostics.rb +15 -16
  94. data/lib/smith/workflow/graph/optimization_contract.rb +2 -2
  95. data/lib/smith/workflow/graph/orchestration_contract.rb +3 -3
  96. data/lib/smith/workflow/graph/reachability.rb +19 -31
  97. data/lib/smith/workflow/graph/reachability_diagnostics.rb +17 -1
  98. data/lib/smith/workflow/graph/retry_policy_diagnostic.rb +36 -0
  99. data/lib/smith/workflow/graph/runtime_binding_diagnostics.rb +2 -2
  100. data/lib/smith/workflow/graph/runtime_readiness.rb +3 -44
  101. data/lib/smith/workflow/graph/runtime_readiness_metrics.rb +2 -2
  102. data/lib/smith/workflow/graph/runtime_readiness_report_builder.rb +47 -0
  103. data/lib/smith/workflow/graph/runtime_readiness_traversal.rb +108 -0
  104. data/lib/smith/workflow/graph/state_diagnostics.rb +2 -2
  105. data/lib/smith/workflow/graph/transition_contract.rb +47 -0
  106. data/lib/smith/workflow/graph/transition_contract_attributes.rb +73 -0
  107. data/lib/smith/workflow/graph/transition_contract_configurations.rb +100 -0
  108. data/lib/smith/workflow/graph/transition_diagnostics.rb +54 -12
  109. data/lib/smith/workflow/graph/transition_optimization_configuration.rb +62 -0
  110. data/lib/smith/workflow/graph/transition_snapshot.rb +4 -1
  111. data/lib/smith/workflow/graph/validator.rb +1 -1
  112. data/lib/smith/workflow/graph.rb +62 -4
  113. data/lib/smith/workflow/guardrail_integration.rb +23 -9
  114. data/lib/smith/workflow/identifier.rb +24 -0
  115. data/lib/smith/workflow/message_admission.rb +40 -0
  116. data/lib/smith/workflow/message_admission_boundary.rb +22 -0
  117. data/lib/smith/workflow/message_batch.rb +60 -0
  118. data/lib/smith/workflow/message_value_normalizer.rb +134 -0
  119. data/lib/smith/workflow/nested_execution.rb +10 -0
  120. data/lib/smith/workflow/orchestrator_worker.rb +3 -7
  121. data/lib/smith/workflow/parallel/cancellation_signal.rb +14 -5
  122. data/lib/smith/workflow/parallel/execution_context.rb +113 -0
  123. data/lib/smith/workflow/parallel/nested_execution.rb +124 -0
  124. data/lib/smith/workflow/parallel/root_execution.rb +91 -0
  125. data/lib/smith/workflow/parallel.rb +19 -22
  126. data/lib/smith/workflow/parallel_agent_binding.rb +24 -0
  127. data/lib/smith/workflow/parallel_execution.rb +46 -14
  128. data/lib/smith/workflow/persistence.rb +37 -8
  129. data/lib/smith/workflow/prepared_branch_execution.rb +31 -0
  130. data/lib/smith/workflow/prepared_step.rb +122 -0
  131. data/lib/smith/workflow/prepared_step_dispatch.rb +88 -0
  132. data/lib/smith/workflow/prepared_step_execution_authorization.rb +101 -0
  133. data/lib/smith/workflow/prepared_step_execution_result.rb +49 -0
  134. data/lib/smith/workflow/prepared_step_execution_scope.rb +111 -0
  135. data/lib/smith/workflow/prepared_step_recovery.rb +38 -0
  136. data/lib/smith/workflow/process_local.rb +33 -0
  137. data/lib/smith/workflow/retry_execution.rb +20 -11
  138. data/lib/smith/workflow/split_step_persistence/boundary.rb +35 -22
  139. data/lib/smith/workflow/split_step_persistence/boundary_reset.rb +50 -0
  140. data/lib/smith/workflow/split_step_persistence/canonical_payload_digest.rb +55 -0
  141. data/lib/smith/workflow/split_step_persistence/checkpoint.rb +3 -5
  142. data/lib/smith/workflow/split_step_persistence/composite_branch_authorization.rb +99 -0
  143. data/lib/smith/workflow/split_step_persistence/composite_branch_effects.rb +41 -0
  144. data/lib/smith/workflow/split_step_persistence/composite_branch_execution.rb +95 -0
  145. data/lib/smith/workflow/split_step_persistence/composite_branch_outcome.rb +36 -0
  146. data/lib/smith/workflow/split_step_persistence/composite_execution.rb +39 -0
  147. data/lib/smith/workflow/split_step_persistence/composite_preparation.rb +57 -0
  148. data/lib/smith/workflow/split_step_persistence/composite_reduction_execution.rb +122 -0
  149. data/lib/smith/workflow/split_step_persistence/definition_boundary.rb +19 -0
  150. data/lib/smith/workflow/split_step_persistence/dispatch_boundary.rb +16 -0
  151. data/lib/smith/workflow/split_step_persistence/dispatch_claim.rb +118 -0
  152. data/lib/smith/workflow/split_step_persistence/dispatch_confirmation.rb +70 -0
  153. data/lib/smith/workflow/split_step_persistence/dispatch_verification.rb +35 -0
  154. data/lib/smith/workflow/split_step_persistence/execution.rb +20 -64
  155. data/lib/smith/workflow/split_step_persistence/execution_authorization.rb +109 -0
  156. data/lib/smith/workflow/split_step_persistence/execution_authorization_issuance.rb +57 -0
  157. data/lib/smith/workflow/split_step_persistence/execution_binding_collector.rb +95 -0
  158. data/lib/smith/workflow/split_step_persistence/execution_binding_snapshot.rb +109 -0
  159. data/lib/smith/workflow/split_step_persistence/execution_lifecycle.rb +96 -0
  160. data/lib/smith/workflow/split_step_persistence/execution_result_capture.rb +48 -0
  161. data/lib/smith/workflow/split_step_persistence/execution_verification.rb +77 -0
  162. data/lib/smith/workflow/split_step_persistence/execution_workflow_snapshot.rb +77 -0
  163. data/lib/smith/workflow/split_step_persistence/inheritance.rb +3 -1
  164. data/lib/smith/workflow/split_step_persistence/payloads.rb +7 -3
  165. data/lib/smith/workflow/split_step_persistence/preparation.rb +30 -2
  166. data/lib/smith/workflow/split_step_persistence/preparation_claim.rb +38 -12
  167. data/lib/smith/workflow/split_step_persistence/preparation_payload.rb +25 -0
  168. data/lib/smith/workflow/split_step_persistence/preparation_recovery.rb +10 -2
  169. data/lib/smith/workflow/split_step_persistence/recovery.rb +125 -0
  170. data/lib/smith/workflow/split_step_persistence/recovery_boundary.rb +56 -0
  171. data/lib/smith/workflow/split_step_persistence/recovery_class_methods.rb +14 -0
  172. data/lib/smith/workflow/split_step_persistence/replace_exact_signature.rb +46 -0
  173. data/lib/smith/workflow/split_step_persistence/restart_safe_adapter.rb +42 -0
  174. data/lib/smith/workflow/split_step_persistence/subclass_boundary.rb +66 -0
  175. data/lib/smith/workflow/split_step_persistence/transaction_identity.rb +45 -0
  176. data/lib/smith/workflow/split_step_persistence.rb +50 -0
  177. data/lib/smith/workflow/step_completion.rb +31 -0
  178. data/lib/smith/workflow/step_context.rb +46 -0
  179. data/lib/smith/workflow/string_snapshot.rb +25 -0
  180. data/lib/smith/workflow/thread_context_snapshot.rb +103 -0
  181. data/lib/smith/workflow/transition.rb +137 -133
  182. data/lib/smith/workflow/transition_actionability.rb +22 -0
  183. data/lib/smith/workflow.rb +64 -18
  184. data/lib/smith.rb +44 -2
  185. data/sig/smith.rbs +1 -0
  186. metadata +155 -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: 986d706ecdf0a51035e20dde373106a6c6df18bc2150cae87e117993bd3fcd1e
4
+ data.tar.gz: e67ca0f8e6b4d6d35c5e1683876a9e191d0da92e29bee4c4379ca8b60b77c50e
5
5
  SHA512:
6
- metadata.gz: 240e30a347d30d06063e57bece46ef646ce68c31463192a123cffd47858311ee1a92d6f7c664c519347775ff5df861e63ae8103005806cf39e400826b3e04c75
7
- data.tar.gz: e478845f5665afeea75fb2b905dd4a7c48aa4ff56ca748661a1dc8302f44dacb029fa60117d900acfeca5558a52a9fde5bc8c7552a7a8149808b92b4898d1c82
6
+ metadata.gz: fa10aed2a7fc3874e8629af28beb07c230d5632b5a653630e70374931c8067775b8f17bee8c70a5c7e96c723e70dbad10c693491c37f8f4bb4a3c7f29b5aed1e
7
+ data.tar.gz: 13560c156073e79e1950d46e460a7f8a1a964a3af6d9b63b68394a0835b6b117f1fb60636c8cecb62d4fb6380e9064a535607cd2e57155e92f64915d2ab76228
data/CHANGELOG.md CHANGED
@@ -6,6 +6,270 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.6.0] - 2026-07-20
10
+
11
+ ### Added
12
+
13
+ - Add a host-neutral durable composite lifecycle for same-agent parallel and
14
+ heterogeneous fan-out transitions. Smith emits immutable bounded plans and
15
+ inputs, executes one authorized branch through captured agent bindings, and
16
+ deterministically reduces a complete ordered outcome set through the normal
17
+ transition completion and failure paths.
18
+ - Add exact transport contracts for ordered branch descriptors, redacted branch
19
+ failures, usage/tool/budget effects, and host-committed primary failure
20
+ selection. Composite branch retries remain explicitly unsupported; hosts own
21
+ scheduling, persistence, claims, fences, and incomplete-branch resumption.
22
+ - Add compact selected-branch execution envelopes and stable host-declared agent
23
+ execution identities so workers do not receive the full plan and registry
24
+ replacement after planning fails closed.
25
+ - Add scoped prepare, selected-branch execution, and reduction entry points so
26
+ process-local authority never crosses the supported public composite API.
27
+
28
+ ### Changed
29
+
30
+ - Bump `Smith::EXECUTION_SEMANTICS_VERSION` to `2` so hosts invalidate generated
31
+ executable definitions when adopting the composite lifecycle.
32
+ - Canonicalize aggregate branch output to the same JSON-compatible shape before
33
+ and after persistence.
34
+
35
+ ### Fixed
36
+
37
+ - Preserve complete workflow, agent, and branch execution context across
38
+ nested parallel and heterogeneous fan-out work without leaking thread- or
39
+ Fiber-local Smith state.
40
+ - Keep ordinary subclass setup and teardown extension points intact while
41
+ prepared execution seals framework-owned branch dispatch against subclass
42
+ replacement.
43
+ - Restrict prepared execution authority to the exact coordinator or registered
44
+ branch Fiber, reject copied or serialized process-local authority, and make
45
+ activation, cleanup, and context restoration safe against asynchronous Ruby
46
+ thread interruption.
47
+ - Keep composite planning and full-run validation linear in branch count, make
48
+ individual branch binding capture and validation constant-time in branch
49
+ count, reject the hard transport ceiling before descriptor allocation, and
50
+ incrementally enforce cumulative effect/output envelopes before consuming
51
+ execution authority.
52
+ - Isolate decimal aggregation from host `BigDecimal` precision, reject aggregate
53
+ usage overflow, and preserve composite branch identity and retry
54
+ classification across persistence.
55
+ - Bind one artifact execution namespace into every branch and the reducer,
56
+ reject aggregate budget overrun and known usage replay before consuming
57
+ authority, and validate cumulative persisted token/cost accounting.
58
+ - Enforce complete canonical payload fields and encoded JSON byte limits before
59
+ returning transport values.
60
+ - Allocate disjoint branch budget envelopes without exceeding the parent,
61
+ reconcile usage against branch-local token/cost consumption, bind branch
62
+ authority to one exact execution envelope, and prevent subclass collisions
63
+ from bypassing durable dispatch verification.
64
+ - Reject forged retryability, untrusted error metadata, and unknown transport
65
+ keys without symbol interning; validate scalar and replay failures before
66
+ snapshotting tool effects.
67
+ - Reject unknown transport enum values without symbol interning, reject corrupt
68
+ persisted branch-failure evidence instead of inventing defaults, enforce
69
+ cumulative encoded-effect bounds while streaming outcomes, and close active
70
+ branch authority before asynchronous interruption can escape a scoped call.
71
+
72
+ ## [0.5.0] - 2026-07-19
73
+
74
+ This release contains intentional pre-1.0 public contract tightening and is a
75
+ minor release rather than a `0.4.x` patch.
76
+
77
+ ### Added
78
+
79
+ - Add configurable per-execution parallel branch/concurrency limits and a
80
+ shared finite exponential-backoff contract for workflow and persistence
81
+ retries.
82
+ - Keep nested fan-out inside one bounded execution context across Fiber
83
+ boundaries, inherit outer cancellation, use only idle top-level workers, and
84
+ drain already-running branch work rather than detaching work or forcibly
85
+ killing threads with live resources. Add a configurable nesting limit,
86
+ hard-capped at 256, so recursive host composition fails before Ruby stack
87
+ exhaustion.
88
+
89
+ - Add bounded host-coordinated session-message admission through
90
+ `Workflow#append_session_messages!`. Smith owns a canonical immutable copy,
91
+ serializes append against workflow execution, and returns an immutable
92
+ `MessageAdmission` digest witness while leaving persistence, idempotency,
93
+ session identity, and resume policy with the host.
94
+ - Add opt-in restart-safe prepared-step recovery. Workflow classes bind a
95
+ host-supplied executable `definition_digest`; hosts submit an immutable typed
96
+ `PreparedStepRecovery.not_started` decision; Smith verifies the exact
97
+ committed preparation before reconstructing a guarded boundary.
98
+ - Add exact-payload `replace_exact` and bounded `persistence_identity` adapter
99
+ capabilities. Restart-safe execution atomically advances the durable payload
100
+ from `prepared` to `dispatching` before transition work, preventing original
101
+ and recovered processes from both dispatching the same operation.
102
+ - Separate restart-safe dispatch claim, commit confirmation, and transition
103
+ execution so transactional hosts can atomically correlate their attempt
104
+ ledger before any provider, tool, or deterministic step work begins.
105
+ - Add immutable, bounded `PreparedStepDispatch` receipts. An exclusive host that
106
+ durably proves execution never started can reconstruct an exact committed
107
+ `dispatching` boundary without replaying its claim.
108
+ - Add a generic process-local prepared-step execution authorization. Smith
109
+ verifies an exact preparation or dispatch without performing transition work;
110
+ a host may then commit its own executing-attempt record before consuming the
111
+ non-copyable, process-bound capability exactly once. Standard Marshal,
112
+ Psych/YAML, and JSON serialization hooks reject the capability.
113
+ - Add `PreparedStepExecutionResult` so hosts can distinguish successful and
114
+ failure-routed transition attempts without inferring success from workflow
115
+ state.
116
+ - Add strict bounded `PreparedStep.deserialize` transport decoding and the
117
+ typed `Sha256Hex` scalar. Prepared step counters are constrained to positive
118
+ signed 64-bit values for both Hash and JSON transports.
119
+
120
+ - Expose `Workflow#pending_transition_name` so hosts can inspect the exact next
121
+ transition without serializing workflow state or traversing the graph.
122
+ - Expose an immutable `Smith::Workflow::PreparedStep` descriptor for an active
123
+ strict split-step boundary. Hosts can persist opaque token, transition,
124
+ persistence-version, step-number, and preparation-digest identity without
125
+ reaching into workflow internals or copying Smith state.
126
+ `prepare_persisted_step!` retains its existing transition-name return value.
127
+ - Compute descriptor identity before persistence dispatch with bounded,
128
+ canonical JSON hashing, preventing post-write validation failures and
129
+ unbounded preparation work.
130
+ - Fence transactional split-step descriptors to an adapter-provided exact
131
+ transaction identity. `ActiveRecordStore` uses Rails' public
132
+ `current_transaction.uuid`; custom transactional adapters fail before writing
133
+ unless they expose an equivalent identity.
134
+
135
+ ### Changed
136
+
137
+ - Make budget settlement receipt-based and one-shot. Reservation, reconciliation,
138
+ and release now fail closed on cross-ledger, replayed, unknown-dimension, or
139
+ amount-only settlement and publish aggregate state atomically. This tightens
140
+ the pre-1.0 ledger API and requires the next minor release.
141
+ - Define budget scalar inputs as finite, non-negative `Integer` or `Float`
142
+ values and use exact internal decimal arithmetic so ordinary Float budgets do
143
+ not strand receipts or reject mathematically valid capacity. Ledger snapshots
144
+ remain JSON-safe numerics. This intentionally rejects opaque custom numeric
145
+ objects and non-JSON-safe numeric types at the boundary. Budget arithmetic is
146
+ isolated from and restores any host-configured `BigDecimal` precision limit;
147
+ `bigdecimal` is now an explicit runtime dependency.
148
+ - Reject repeated or mixed primary transition declarations instead of silently
149
+ replacing an earlier execution primitive.
150
+ - Treat named transition target-origin mismatches as graph errors rather than
151
+ warnings because runtime cannot execute the declared route from that state.
152
+ - Constrain workflow topology identifiers to non-blank String or Symbol values,
153
+ matching runtime lookup, event, persistence, and graph-inspection semantics.
154
+ - Reject oversized static fan-out declarations, non-finite or unschedulable
155
+ retry delays, and retry attempt counts above the configurable default limit
156
+ of 100 before executable work begins. This intentionally tightens previously
157
+ unbounded pre-1.0 declarations; hosts that require a higher finite attempt
158
+ bound must configure it before loading workflow classes.
159
+
160
+ - Restore persisted workflow state without mutating caller-owned nested outcome
161
+ data while preserving Smith's symbolized runtime outcome contract.
162
+ - Reject strict in-progress payloads before schema migration so a migration
163
+ cannot clear the uncertainty marker and replay a transition.
164
+ - Keep an ambiguously acknowledged exact dispatch claim fail-closed in durable
165
+ `dispatching` state. Smith does not infer or replay an uncertain external
166
+ outcome.
167
+ - Run Redis versioned and exact-payload CAS inside redis-rb's native
168
+ no-reconnect scope and do not replay either write after a transient
169
+ connection failure; a lost acknowledgement is outcome-unknown and must be
170
+ reconciled.
171
+ - Pin the executable definition digest for the complete split-step boundary and
172
+ seal it on the workflow class when preparation or recovery authority is
173
+ acquired. Concurrent digest setters linearize before sealing or fail before
174
+ claim and execution.
175
+ - Make Active Record exact replacement one conditional SQL update over key and
176
+ byte-exact payload while incrementing its optimistic-lock column. Validate
177
+ the database primary key or an unconditional single-column unique key index
178
+ through Active Record's table-keyed schema cache before each exact write.
179
+ - Resolve callable Redis command clients as clients rather than factories, and
180
+ require a native reconnect-disabling scope before any Redis CAS begins.
181
+ - Treat a persistence identity as available in doctor diagnostics only when its
182
+ value satisfies the same bounded non-empty contract used at runtime.
183
+ - Revalidate workflow definition and transition identity when an execution
184
+ authorization is consumed. Capability release and execution are linearized by
185
+ exact identity so concurrent or stale holders cannot revoke or replay work.
186
+ - Route an active prepared execution through a Smith-owned private-method
187
+ membrane, including reachable nested workflows. Subclass overrides retain
188
+ normal Ruby dispatch outside the authorized boundary but cannot substitute
189
+ retry, dispatch, guardrail, budget, or completion behavior during it. Nested
190
+ authority is limited to the root execution thread and revoked when that
191
+ execution closes, even when a host retains a child workflow instance.
192
+ - Own String workflow identifiers at declaration time and keep graph snapshots
193
+ isolated from mutable caller aliases. Reject other topology object types at
194
+ the DSL boundary so graph lookup, event, and persistence semantics remain
195
+ consistent. The authorized
196
+ completion and failure boundaries bind Smith's implementation directly so
197
+ subclass method-name collisions cannot bypass typed result capture.
198
+ - Capture exact direct, fan-out, optimizer, orchestrator, and reachable nested
199
+ agent bindings during authorization with bounded `O(V + E)` traversal. Later
200
+ mutable registry replacement cannot alter the authorized execution.
201
+ Same-agent parallel execution resolves its captured binding on the authorized
202
+ root thread and passes an identity-scoped class reference into worker
203
+ branches without leaking it into re-entrant workflows on the same thread.
204
+ - Replace recursive graph reachability with an iterative walk over a graph-local
205
+ outgoing index. Existing graph snapshots remain isolated from later workflow
206
+ class mutation. State-reference validation and terminal-state metrics use
207
+ constant-time graph indexes. Nested runtime-readiness inspection uses an
208
+ iterative postorder traversal with workflow-class identity memoization and
209
+ cycle-edge diagnostics. Nested workflow contracts are captured and
210
+ revalidated before and immediately after child construction without repeated
211
+ full-transition scans.
212
+ - Bound durable preparation comparison by canonical payload byte and node
213
+ limits, and preserve the legacy mutable step Hash while typed execution
214
+ results own an immutable, cycle-aware, bounded JSON-like snapshot. Result
215
+ validation now completes before successful workflow state mutation; invalid
216
+ provider output enters normal failure routing and returns a typed failure.
217
+ Hash keys are limited to owned String or Symbol values, and non-finite Floats
218
+ are rejected before success mutation.
219
+ - Normalize top-level string keys in restored message Hashes before rebuilding
220
+ RubyLLM messages. Nested message content remains untouched so transport
221
+ decoding does not rewrite host payloads.
222
+
223
+ - Maintain a declaration-time outgoing-transition index. First-transition and
224
+ terminal checks are constant-time; enumerating transitions from one state is
225
+ proportional to that state's outdegree. Stable declaration precedence,
226
+ subclass isolation, and transition redefinition semantics are preserved.
227
+ - Custom adapters that report an open transaction now require
228
+ `transaction_identity` for strict split-step preparation. This intentionally
229
+ tightens the 0.4.5 boolean transaction contract so later unrelated
230
+ transactions cannot re-authorize a rolled-back descriptor.
231
+
232
+ ### Verification
233
+
234
+ - Default suite: 1,304 examples, 0 failures on the final tree.
235
+ - Runtime-hardening practical suite: 551 scenarios and 33,722 assertions across
236
+ graph reachability, retry/backoff, nested parallel execution, cancellation,
237
+ receipt-based budgets, hostile decimal precision, and asynchronous thread
238
+ interruption.
239
+ - The built gem and its declared dependency set loaded under Ruby 3.2.2 and
240
+ Ruby 3.2.8; each runtime passed the same 551-scenario practical suite.
241
+ - Downstream verification against the local checkout: Smith Runtime passed
242
+ 1,188 runs and 6,287 assertions with 35 skips, plus 20 practical scenarios;
243
+ Smith Studio passed 1,848 runs and 13,851 assertions with one skip, plus 20
244
+ practical scenarios.
245
+ - Message-admission and restore-input suite: 30 examples, 0 failures. A
246
+ 44-scenario public-API matrix covered canonicalization, alias isolation,
247
+ bounded rejection, lifecycle contention, persistence round trips, and a
248
+ 90,000-value near-limit message.
249
+ - Focused graph, execution-authorization, binding-snapshot, typed-result, and
250
+ restored-message suite: 71 examples, 0 failures. All newly added files pass RuboCop;
251
+ the changed surface passes after excluding the repository's pre-existing
252
+ metrics baseline, and `git diff --check` passes.
253
+ - Practical gem execution: 30 restart-safe scenarios covering serialized
254
+ preparation and dispatch recovery, exact-claim contention, corruption,
255
+ definition drift, ambiguous acknowledgements, and Active Record
256
+ commit/rollback coordination; a real redis-rb 5.4.1 run additionally proved
257
+ direct-client and factory resolution plus bounded multi-client exact-claim
258
+ contention.
259
+ - Practical execution-authorization matrix: 30 public-API scenarios covering
260
+ success and handled failure variants, immutable graph snapshots up to 5,000
261
+ transitions, cyclic and shared result values, bounded rejection cases,
262
+ release, single-use authorization, copying, serialization, and concurrent
263
+ claims.
264
+ - Additional practical hardening matrix: 20 public-API checks covering mutable
265
+ topology projection, 1,000/2,000/4,000-transition validation, 5,000-transition
266
+ reachability, a 1,200-workflow nested chain, shared-child memoization, cycle
267
+ diagnostics, root and nested execution membranes, and authorization
268
+ contention. Measured validation times were 5.80 ms, 12.80 ms, and 16.39 ms
269
+ respectively on the local Ruby 4.0.1 verification host.
270
+ - A downstream host passed its serial and parallel suites against the
271
+ local Smith checkout and completed its practical direct-model boundary.
272
+
9
273
  ## [0.4.5] - 2026-07-11
10
274
 
11
275
  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,131 @@ 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
+ For `parallel: true` and `fan_out` transitions, a host can split one confirmed
264
+ dispatch into independently scheduled branch operations without invoking agent
265
+ classes outside Smith:
266
+
267
+ ```ruby
268
+ preparation = workflow.prepare_composite_step!
269
+
270
+ # Persist preparation.plan and preparation.input in host-owned records.
271
+ # Each worker recovers the same confirmed dispatch and receives one descriptor.
272
+ branch = preparation.plan.branches.fetch(0)
273
+ execution = preparation.plan.execution_for(branch)
274
+ outcome = branch_workflow.execute_prepared_composite_branch!(
275
+ execution:,
276
+ input: preparation.input
277
+ )
278
+
279
+ # After every branch has a terminal outcome, use another fresh recovery to
280
+ # complete the Smith transition exactly once.
281
+ result = reducer_workflow.reduce_prepared_composite_step!(
282
+ plan: preparation.plan,
283
+ input: preparation.input,
284
+ outcomes: ordered_or_unordered_complete_outcome_set,
285
+ primary_failure: host_committed_failed_branch_key
286
+ )
287
+ ```
288
+
289
+ Plans bind the exact prepared dispatch, host-supplied complete workflow
290
+ `definition_digest`, execution-semantics version, prepared input digest, budget
291
+ baseline, one shared artifact execution namespace, ordered branch identities,
292
+ and explicit policies. Each plan, input, outcome, or effect payload is
293
+ canonical, immutable, bounded to 1 MiB after JSON encoding, rejects missing or
294
+ unknown fields, and redacts exception messages and backtraces.
295
+ Reduction additionally bounds cumulative effects to 1 MiB and final aggregate
296
+ output to 4 MiB before consuming execution authority. Reduction accepts either
297
+ an `Array` or an `Enumerator`, validates each outcome incrementally, and stops
298
+ as soon as a cumulative bound is crossed. The transport hard limit is 10,000
299
+ branches; every recovered worker also enforces the current
300
+ `Smith.config.parallel_branch_limit` before work.
301
+
302
+ Planning is `O(B * D)`, where `D` is the bounded number of budget dimensions.
303
+ The coordinator derives a compact selected-branch execution envelope in
304
+ `O(D)` without copying the full branch collection. The scoped branch execution
305
+ entry point captures and validates one selected binding in `O(D)` and constant
306
+ time with respect to total branch count; the reducer validates the complete plan
307
+ once. Reduction is `O(B + P log P)` in
308
+ the worst case because canonical nested Hash ownership sorts keys, and uses
309
+ `O(B + P)` bounded space.
310
+ Smith does not persist plans or outcomes, select scheduling slots, claim work,
311
+ choose an uncertain-outcome policy, or infer the primary failure. The host
312
+ durably owns those decisions, gives each branch and reduction a separately
313
+ recovered workflow instance, and must pass a complete outcome set. Transport or
314
+ encoding faults become typed branch failures when Smith can preserve accounting
315
+ evidence; otherwise the host must treat the worker result as uncertain and must
316
+ not infer safe retry. Resume policy is incomplete branches only; per-branch
317
+ retry is intentionally unsupported in this contract version.
318
+ Composite payload digests prove canonical content integrity, not worker
319
+ authenticity. Before reduction, the host must authenticate every outcome against
320
+ its own claimed operation identity and current fencing token; those durable
321
+ receipts remain outside Smith.
322
+
323
+ The supported composite API is the three scoped workflow methods above plus the
324
+ immutable transport values they return or accept: `Preparation`, `Plan`,
325
+ `Input`, `Branch`, `BranchExecution`, `BranchOutcome`, `BranchFailure`, `Error`,
326
+ `Effects`, and `Reduction`. Other constants under `Workflow::Composite` are
327
+ implementation collaborators, not extension points or compatibility promises.
328
+
329
+ Every agent used by this durable composite boundary must declare a stable
330
+ host-computed SHA-256 execution identity. The identity represents the exact
331
+ sealed executable asset the host intends to dispatch; Smith records it in the
332
+ branch descriptor and rejects registry replacement after planning. Identities
333
+ are deliberately not inherited because a subclass is a distinct executable
334
+ implementation.
335
+
336
+ ```ruby
337
+ class Researcher < Smith::Agent
338
+ execution_identity Digest::SHA256.hexdigest(generated_asset_manifest)
339
+ register_as :researcher, publish: false
340
+ end
341
+
342
+ Researcher.freeze
343
+ Researcher.publish_registration!
344
+ ```
345
+
346
+ Hosts that generate agent classes dynamically may call
347
+ `register_as :name, publish: false` while constructing the class. This records
348
+ the class identity without exposing it through `Smith::Agent::Registry`. After
349
+ the host has validated and sealed the class, it may publish the exact class with
350
+ `agent_class.publish_registration!`. Publication always uses the staged,
351
+ immutable identity; callers cannot provide a second key. Registry collision and
352
+ mutation epoch guarantees remain unchanged.
353
+
354
+ Hosts that persist or cache generated executable definitions should include
355
+ `Smith::EXECUTION_SEMANTICS_VERSION` in their definition identity. The gem
356
+ version describes the package release; the execution-semantics version changes
357
+ when Smith's executable workflow contract changes and must participate in host
358
+ cache invalidation.
218
359
 
219
360
  ## Tools and Guardrails
220
361
 
@@ -240,6 +381,30 @@ end
240
381
 
241
382
  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
383
 
384
+ Direct ledger integrations settle the exact receipt returned by reservation:
385
+
386
+ ```ruby
387
+ reservation = ledger.reserve_many!(total_tokens: 1_000, total_cost: 0.10)
388
+ ledger.reconcile_many!(reservation, actual: { total_tokens: 640, total_cost: 0.06 })
389
+ ```
390
+
391
+ `release!` / `release_many!` accept the same receipt when work never starts.
392
+ Receipts are ledger-owned and one-shot: another ledger, a replay, or an
393
+ amount-only settlement is rejected.
394
+
395
+ Budget amounts accept finite, non-negative `Integer` and `Float` values. Smith
396
+ uses an exact `BigDecimal` representation derived from each value's canonical
397
+ decimal text and converts ledger snapshots back to ordinary JSON-safe numerics,
398
+ avoiding cumulative binary Float drift without changing persisted workflow
399
+ state shapes. Ledger arithmetic temporarily uses unlimited decimal precision and
400
+ restores the host's existing `BigDecimal` precision limit before returning.
401
+
402
+ Budget operations are atomic for normal concurrent callers and synchronous
403
+ exceptions. Smith uses cooperative cancellation and never terminates branch
404
+ threads with `Thread#raise` or `Thread#kill`. Arbitrary asynchronous thread
405
+ termination can make any in-process return value outcome-ambiguous; hosts must
406
+ use cancellation signals, bounded I/O, and process isolation for hard stops.
407
+
243
408
  ## Doctor
244
409
 
245
410
  After adding Smith, verify the integration:
@@ -296,6 +461,28 @@ When no classes are passed, `retry_on` uses `Smith::Errors.retryable?`.
296
461
  This is a bounded local transition retry policy. Durable scheduling, long waits,
297
462
  and external idempotency guarantees remain host-owned.
298
463
 
464
+ ## Runtime Complexity
465
+
466
+ - Executable graph reachability is iterative `O(V + E)` time and `O(V)` space,
467
+ using indexed outgoing transitions and runtime-equivalent successor rules.
468
+ - Parallel execution is `O(B)` scheduling and result space for `B` branches,
469
+ with at most `C = parallel_concurrency` active worker threads per top-level
470
+ execution. Re-entrant fan-out inherits the same cancellation signal and uses
471
+ only idle workers from the top-level execution context, falling back to the
472
+ current worker when no capacity remains. Nested calls fail closed before the
473
+ configurable nesting bound can exhaust Ruby's stack. Cancellation prevents
474
+ queued branch bodies from starting;
475
+ already-running branches are drained before control returns so work is never
476
+ detached. Branch adapters must use bounded I/O and honor the cancellation
477
+ signal. Hosts continue to own process-level concurrency and hard deadlines.
478
+ - Reservation and settlement across `L` declared budget dimensions and `D`
479
+ operation dimensions is `O(L + D)` time and space. Receipt lookup is expected
480
+ `O(1)`; no operation scans or copies the active receipt registry. Integer and
481
+ Float amounts use exact decimal arithmetic internally.
482
+ - Retry delay calculation is `O(1)` time and space per attempt. A policy with
483
+ `A` attempts performs at most `A` guarded operations and never materializes an
484
+ exponential schedule.
485
+
299
486
  ## Development
300
487
 
301
488
  Smith changes must not use brute-force graph search, validation, scheduling, or
@@ -309,5 +496,5 @@ bundle exec rspec
309
496
  bundle exec rubocop
310
497
  ```
311
498
 
312
- 880 examples, MIT licensed. See [`CHANGELOG.md`](CHANGELOG.md) for the current
499
+ 1,175 examples, MIT licensed. See [`CHANGELOG.md`](CHANGELOG.md) for the current
313
500
  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