phronomy 0.27.0 → 0.29.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 (517) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +52 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +28 -7
  6. data/benchmark/bench_agent_invoke.rb +25 -50
  7. data/benchmark/bench_context_assembler.rb +14 -11
  8. data/benchmark/bench_regression.rb +1 -1
  9. data/benchmark/bench_tool_schema.rb +1 -1
  10. data/docs/application-recipes.md +229 -0
  11. data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
  12. data/docs/architecture/agent-transition-ownership.md +13 -89
  13. data/docs/architecture/backend-groups.md +152 -0
  14. data/docs/architecture/before-llm-input.md +3 -3
  15. data/docs/architecture/context-management.md +1 -1
  16. data/docs/architecture/dx05-agent-scope.md +119 -0
  17. data/docs/architecture/dx05-workflow-task-state.md +134 -0
  18. data/docs/architecture/error-api-review.md +245 -0
  19. data/docs/architecture/execution-abstraction-review.md +88 -0
  20. data/docs/architecture/execution-boundaries.md +28 -0
  21. data/docs/architecture/execution-metadata-and-values.md +6 -0
  22. data/docs/architecture/multi-agent-handoff.md +6 -5
  23. data/docs/architecture/persistence.md +27 -9
  24. data/docs/architecture/r8-unit1.md +104 -0
  25. data/docs/architecture/r8-unit10.md +72 -0
  26. data/docs/architecture/r8-unit11.md +71 -0
  27. data/docs/architecture/r8-unit12.md +67 -0
  28. data/docs/architecture/r8-unit13.md +61 -0
  29. data/docs/architecture/r8-unit14.md +72 -0
  30. data/docs/architecture/r8-unit15.md +65 -0
  31. data/docs/architecture/r8-unit16.md +54 -0
  32. data/docs/architecture/r8-unit17.md +73 -0
  33. data/docs/architecture/r8-unit18.md +92 -0
  34. data/docs/architecture/r8-unit19.md +61 -0
  35. data/docs/architecture/r8-unit2.md +55 -0
  36. data/docs/architecture/r8-unit20.md +51 -0
  37. data/docs/architecture/r8-unit21.md +79 -0
  38. data/docs/architecture/r8-unit22.md +74 -0
  39. data/docs/architecture/r8-unit23.md +81 -0
  40. data/docs/architecture/r8-unit3.md +130 -0
  41. data/docs/architecture/r8-unit4.md +79 -0
  42. data/docs/architecture/r8-unit5.md +115 -0
  43. data/docs/architecture/r8-unit6.md +71 -0
  44. data/docs/architecture/r8-unit7.md +70 -0
  45. data/docs/architecture/r8-unit8.md +78 -0
  46. data/docs/architecture/r8-unit9.md +73 -0
  47. data/docs/architecture/refactoring-closure.md +10 -1
  48. data/docs/architecture/remaining-refactoring-plan.md +16 -0
  49. data/docs/architecture/storage-execution-boundaries.md +54 -0
  50. data/docs/architecture/tool-schema-recording-gap.md +6 -0
  51. data/docs/architecture/tracing.md +1 -1
  52. data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
  53. data/docs/architecture.md +24 -11
  54. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  55. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  56. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
  57. data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
  58. data/docs/decisions/032-storage-backend-composition.md +5 -0
  59. data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
  60. data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
  61. data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
  62. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
  63. data/docs/decisions/040-configuration-default-composition.md +12 -1
  64. data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
  65. data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
  66. data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
  67. data/docs/decisions/058-neutral-storage-primitives.md +5 -0
  68. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  69. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  70. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  71. data/docs/decisions/062-content-store-service.md +52 -0
  72. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  73. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  74. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  75. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  76. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  77. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  78. data/docs/decisions/README.md +18 -1
  79. data/docs/execution-boundaries-preview.md +45 -0
  80. data/docs/failure-reason-codes.md +45 -0
  81. data/docs/features.md +12 -4
  82. data/docs/getting-started.md +52 -48
  83. data/docs/migrations/0.22.md +2 -2
  84. data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
  85. data/docs/migrations/handoff-runner-multi-agent.md +2 -0
  86. data/docs/migrations/persistence-failure-contracts.md +67 -0
  87. data/docs/migrations/r8-unit10.md +36 -0
  88. data/docs/migrations/r8-unit11.md +33 -0
  89. data/docs/migrations/r8-unit12.md +31 -0
  90. data/docs/migrations/r8-unit13.md +36 -0
  91. data/docs/migrations/r8-unit14.md +31 -0
  92. data/docs/migrations/r8-unit15.md +31 -0
  93. data/docs/migrations/r8-unit16.md +26 -0
  94. data/docs/migrations/r8-unit17.md +28 -0
  95. data/docs/migrations/r8-unit18.md +29 -0
  96. data/docs/migrations/r8-unit19.md +28 -0
  97. data/docs/migrations/r8-unit20.md +24 -0
  98. data/docs/migrations/r8-unit21.md +28 -0
  99. data/docs/migrations/r8-unit22.md +29 -0
  100. data/docs/migrations/r8-unit23.md +20 -0
  101. data/docs/migrations/r8-unit4.md +67 -0
  102. data/docs/migrations/r8-unit5.md +75 -0
  103. data/docs/migrations/r8-unit6.md +109 -0
  104. data/docs/migrations/r8-unit7.md +31 -0
  105. data/docs/migrations/r8-unit8.md +33 -0
  106. data/docs/migrations/r8-unit9.md +28 -0
  107. data/docs/migrations/storage-async-client.md +103 -0
  108. data/docs/migrations/vector-async-clients.md +110 -0
  109. data/docs/ordered-event-delivery.md +54 -0
  110. data/docs/persistence-backends.md +38 -21
  111. data/docs/runtime-and-concurrency.md +26 -1
  112. data/docs/workflow-completion.md +92 -0
  113. data/gemfiles/http_minimum.gemfile +5 -0
  114. data/lib/phronomy/agent/admission.rb +78 -0
  115. data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
  116. data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
  117. data/lib/phronomy/agent/agent_not_found_error.rb +16 -0
  118. data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
  119. data/lib/phronomy/agent/agent_unloaded_error.rb +14 -0
  120. data/lib/phronomy/agent/api/agent.rb +13 -0
  121. data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
  122. data/lib/phronomy/agent/async_event_api.rb +132 -103
  123. data/lib/phronomy/agent/base.rb +146 -113
  124. data/lib/phronomy/agent/before_llm_input.rb +70 -0
  125. data/lib/phronomy/agent/composition/run_once.rb +1 -1
  126. data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
  127. data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
  128. data/lib/phronomy/agent/control_request.rb +21 -0
  129. data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
  130. data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
  131. data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
  132. data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
  133. data/lib/phronomy/agent/execution/exact_execution.rb +55 -32
  134. data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
  135. data/lib/phronomy/agent/execution/execution_coordinator.rb +229 -407
  136. data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
  137. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +71 -127
  138. data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
  139. data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
  140. data/lib/phronomy/agent/execution/initial_preparation.rb +19 -120
  141. data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
  142. data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
  143. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +8 -11
  144. data/lib/phronomy/agent/execution_change.rb +161 -0
  145. data/lib/phronomy/agent/execution_environment.rb +36 -0
  146. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  147. data/lib/phronomy/agent/execution_observation.rb +35 -0
  148. data/lib/phronomy/agent/filterable.rb +188 -0
  149. data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
  150. data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
  151. data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
  152. data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
  153. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  154. data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
  155. data/lib/phronomy/agent/lifecycle/lifetime_api.rb +76 -0
  156. data/lib/phronomy/agent/lifecycle/operation_lease.rb +33 -0
  157. data/lib/phronomy/agent/lifecycle/operation_result.rb +53 -0
  158. data/lib/phronomy/agent/lifecycle/ownership_registry.rb +117 -31
  159. data/lib/phronomy/agent/lifecycle/scope.rb +198 -0
  160. data/lib/phronomy/agent/lifecycle/scope_cleanup_error.rb +19 -0
  161. data/lib/phronomy/agent/lifecycle/unload_transition.rb +78 -0
  162. data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
  163. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  164. data/lib/phronomy/agent/persistence/codec.rb +1 -21
  165. data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
  166. data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
  167. data/lib/phronomy/agent/persistence/records.rb +33 -0
  168. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  169. data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
  170. data/lib/phronomy/agent/persistence/watermark.rb +13 -11
  171. data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
  172. data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +13 -12
  173. data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +34 -34
  174. data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +33 -39
  175. data/lib/phronomy/agent/recovery/recovery_coordinator.rb +28 -25
  176. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  177. data/lib/phronomy/agent/recovery/recovery_support.rb +10 -19
  178. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  179. data/lib/phronomy/agent/retention.rb +17 -0
  180. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  181. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  182. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  183. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  184. data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
  185. data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
  186. data/lib/phronomy/agent/settings.rb +47 -0
  187. data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
  188. data/lib/phronomy/agent/store.rb +253 -0
  189. data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +3 -3
  190. data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
  191. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
  192. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  193. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  194. data/lib/phronomy/common/error.rb +65 -1
  195. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  196. data/lib/phronomy/content_store/base.rb +7 -2
  197. data/lib/phronomy/content_store/storage_schema.rb +1 -0
  198. data/lib/phronomy/content_store/stored_contents.rb +2 -1
  199. data/lib/phronomy/context/assembly.rb +195 -0
  200. data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
  201. data/lib/phronomy/context/candidate.rb +51 -0
  202. data/lib/phronomy/context/constraint.rb +47 -0
  203. data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
  204. data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
  205. data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
  206. data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
  207. data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
  208. data/lib/phronomy/context/default_policy.rb +140 -0
  209. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  210. data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
  211. data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
  212. data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
  213. data/lib/phronomy/context/manifest_error.rb +10 -0
  214. data/lib/phronomy/context/prompt_template.rb +98 -0
  215. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  216. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  217. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  218. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  219. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  220. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  221. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  222. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  223. data/lib/phronomy/embeddings/authentication_error.rb +11 -0
  224. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +52 -0
  225. data/lib/phronomy/embeddings/base.rb +43 -0
  226. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  227. data/lib/phronomy/embeddings/rate_limit_error.rb +11 -0
  228. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  229. data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
  230. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  231. data/lib/phronomy/engine/diagnostics.rb +1 -1
  232. data/lib/phronomy/engine/event_loop.rb +7 -7
  233. data/lib/phronomy/engine/execution_binding.rb +23 -0
  234. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  235. data/lib/phronomy/engine/fsm_protocol.rb +1 -6
  236. data/lib/phronomy/engine/fsm_session.rb +3 -3
  237. data/lib/phronomy/engine/metrics.rb +16 -14
  238. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  239. data/lib/phronomy/engine/runtime.rb +25 -12
  240. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  241. data/lib/phronomy/execution/async_operation.rb +41 -0
  242. data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
  243. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
  244. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  245. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  246. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  247. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  248. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  249. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  250. data/lib/phronomy/{engine → execution}/execution.rb +61 -27
  251. data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +2 -2
  252. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  253. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  254. data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +2 -2
  255. data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
  256. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  257. data/lib/phronomy/execution/runnable.rb +24 -0
  258. data/lib/phronomy/{engine → execution}/task_result.rb +9 -4
  259. data/lib/phronomy/execution/wait_policy.rb +24 -0
  260. data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +3 -3
  261. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  262. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  263. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
  264. data/lib/phronomy/generation/generator_verifier.rb +7 -7
  265. data/lib/phronomy/generation/low_confidence_error.rb +2 -2
  266. data/lib/phronomy/integrations/ordered_event_delivery.rb +208 -0
  267. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  268. data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
  269. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  270. data/lib/phronomy/llm_adapter/base.rb +49 -68
  271. data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
  272. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  273. data/lib/phronomy/llm_adapter/message.rb +62 -0
  274. data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
  275. data/lib/phronomy/llm_adapter/request.rb +79 -0
  276. data/lib/phronomy/llm_adapter/response.rb +36 -0
  277. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  278. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  279. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  280. data/lib/phronomy/llm_adapter.rb +3 -16
  281. data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
  282. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  283. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  284. data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
  285. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
  286. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  287. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
  288. data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
  289. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
  290. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  291. data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
  292. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  293. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  294. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  295. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +60 -44
  296. data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
  297. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  298. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  299. data/lib/phronomy/multi_agent/shared_state.rb +2 -2
  300. data/lib/phronomy/multi_agent/store.rb +156 -0
  301. data/lib/phronomy/multi_agent/team_busy_error.rb +18 -0
  302. data/lib/phronomy/multi_agent/team_coordinator.rb +99 -77
  303. data/lib/phronomy/multi_agent/team_not_found_error.rb +18 -0
  304. data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
  305. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  306. data/lib/phronomy/persistence/content_repository.rb +26 -0
  307. data/lib/phronomy/persistence/errors.rb +16 -0
  308. data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
  309. data/lib/phronomy/persistence/persistence.rb +23 -0
  310. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  311. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  312. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  313. data/lib/phronomy/persistence/transaction.rb +86 -0
  314. data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
  315. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  316. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  317. data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
  318. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  319. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  320. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  321. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  322. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  323. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  324. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  325. data/lib/phronomy/storage/async/async_client.rb +60 -0
  326. data/lib/phronomy/storage/backend.rb +8 -2
  327. data/lib/phronomy/storage/condition_failed_error.rb +2 -2
  328. data/lib/phronomy/storage/unique_constraint_error.rb +2 -2
  329. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  330. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  331. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
  332. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
  333. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
  334. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
  335. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
  336. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
  337. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
  338. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
  339. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
  340. data/lib/phronomy/tool/authorization.rb +77 -0
  341. data/lib/phronomy/tool/base.rb +551 -9
  342. data/lib/phronomy/tool/call_request.rb +40 -0
  343. data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
  344. data/lib/phronomy/tool/operation.rb +88 -0
  345. data/lib/phronomy/tool/schema.rb +214 -0
  346. data/lib/phronomy/tool/settings.rb +31 -0
  347. data/lib/phronomy/tool/tool_error.rb +7 -0
  348. data/lib/phronomy/tool/tool_executor.rb +58 -0
  349. data/lib/phronomy/tools/agent.rb +22 -90
  350. data/lib/phronomy/tools/mcp.rb +21 -14
  351. data/lib/phronomy/tools/vector_search.rb +4 -4
  352. data/lib/phronomy/tracing/automatic.rb +11 -6
  353. data/lib/phronomy/tracing/base.rb +36 -7
  354. data/lib/phronomy/tracing/observation.rb +37 -0
  355. data/lib/phronomy/tracing/open_telemetry_tracer.rb +4 -29
  356. data/lib/phronomy/tracing/settings.rb +36 -0
  357. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  358. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  359. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  360. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  361. data/lib/phronomy/vector_store/base.rb +118 -49
  362. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  363. data/lib/phronomy/vector_store.rb +2 -17
  364. data/lib/phronomy/version.rb +1 -1
  365. data/lib/phronomy/workflow/completion.rb +11 -0
  366. data/lib/phronomy/workflow/execution/workflow.rb +37 -9
  367. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  368. data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
  369. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +28 -0
  370. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +77 -8
  371. data/lib/phronomy/workflow/execution/workflow_runner.rb +105 -92
  372. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  373. data/lib/phronomy/workflow/execution/workflow_task_execution.rb +153 -0
  374. data/lib/phronomy/workflow/execution/workflow_task_reception.rb +72 -0
  375. data/lib/phronomy/workflow/execution/workflow_task_resume_error.rb +14 -0
  376. data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
  377. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  378. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +61 -0
  379. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +158 -0
  380. data/lib/phronomy/workflow/runtime_binding/workflow_task_session.rb +76 -0
  381. data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
  382. data/lib/phronomy.rb +63 -16
  383. data/scripts/api_snapshot.rb +47 -15
  384. data/scripts/check_readme_ruby.rb +24 -29
  385. data/scripts/check_readme_runnable.rb +92 -81
  386. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  387. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  388. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  389. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  390. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  391. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  392. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  393. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  394. data/sig/phronomy/agent/admission.rbs +19 -0
  395. data/sig/phronomy/agent/agent_not_found_error.rbs +6 -0
  396. data/sig/phronomy/agent/agent_unloaded_error.rbs +6 -0
  397. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  398. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  399. data/sig/phronomy/agent/store.rbs +73 -0
  400. data/sig/phronomy/agent/stream_callback_error.rbs +8 -0
  401. data/sig/phronomy/agent.rbs +29 -135
  402. data/sig/phronomy/common.rbs +7 -0
  403. data/sig/phronomy/content_store/contracts.rbs +13 -0
  404. data/sig/phronomy/context.rbs +152 -0
  405. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  406. data/sig/phronomy/embeddings/base.rbs +23 -0
  407. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  408. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  409. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  410. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  411. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  412. data/sig/phronomy/engine/observability.rbs +13 -0
  413. data/sig/phronomy/execution/async_operation.rbs +7 -0
  414. data/sig/phronomy/execution/blocking.rbs +5 -0
  415. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  416. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  417. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  418. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  419. data/sig/phronomy/execution/errors.rbs +7 -0
  420. data/sig/phronomy/execution/execution.rbs +41 -0
  421. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  422. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  423. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  424. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  425. data/sig/phronomy/execution/task_result.rbs +29 -0
  426. data/sig/phronomy/extensions.rbs +1 -21
  427. data/sig/phronomy/filter/filter_block_error.rbs +6 -0
  428. data/sig/phronomy/generation/low_confidence_error.rbs +6 -0
  429. data/sig/phronomy/handoff.rbs +10 -4
  430. data/sig/phronomy/integrations.rbs +14 -0
  431. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  432. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  433. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  434. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  435. data/sig/phronomy/llm_context_window.rbs +10 -0
  436. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  437. data/sig/phronomy/multi_agent/store.rbs +54 -0
  438. data/sig/phronomy/multi_agent/team_busy_error.rbs +8 -0
  439. data/sig/phronomy/multi_agent/team_not_found_error.rbs +8 -0
  440. data/sig/phronomy/multi_agent.rbs +6 -3
  441. data/sig/phronomy/persistence/errors.rbs +18 -0
  442. data/sig/phronomy/persistence/migration.rbs +13 -0
  443. data/sig/phronomy/persistence/transaction.rbs +19 -0
  444. data/sig/phronomy/persistence.rbs +1 -99
  445. data/sig/phronomy/persistence_composition.rbs +19 -0
  446. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  447. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  448. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  449. data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +4 -18
  450. data/sig/phronomy/tool.rbs +48 -4
  451. data/sig/phronomy/tracing/observation.rbs +7 -0
  452. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  453. data/sig/phronomy/vector_store/base.rbs +23 -0
  454. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +15 -0
  455. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  456. data/sig/phronomy/workflow/store.rbs +8 -0
  457. data/sig/phronomy/workflow.rbs +5 -1
  458. data/sig/phronomy.rbs +4 -49
  459. metadata +365 -117
  460. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  461. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  462. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  463. data/lib/phronomy/agent/context/capability/base.rb +0 -548
  464. data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
  465. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  466. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
  467. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
  468. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  469. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  470. data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
  471. data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
  472. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
  473. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
  474. data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
  475. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
  476. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
  477. data/lib/phronomy/agent/persistence/queries.rb +0 -61
  478. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  479. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  480. data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
  481. data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
  482. data/lib/phronomy/configuration/configuration.rb +0 -68
  483. data/lib/phronomy/configuration/global_configuration.rb +0 -26
  484. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  485. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  486. data/lib/phronomy/engine/execution_receiver.rb +0 -65
  487. data/lib/phronomy/engine/runnable.rb +0 -54
  488. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  489. data/lib/phronomy/llm_contract/token_usage.rb +0 -85
  490. data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
  491. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
  492. data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
  493. data/lib/phronomy/persistence/api/persistence.rb +0 -140
  494. data/lib/phronomy/persistence_composition/repositories.rb +0 -77
  495. data/lib/phronomy/recovery/recovery.rb +0 -186
  496. data/lib/phronomy/tool/contract/tool_error.rb +0 -7
  497. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  498. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  499. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  500. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
  501. data/sig/phronomy/execution_receiver.rbs +0 -34
  502. data/sig/phronomy/llm_adapter.rbs +0 -11
  503. data/sig/phronomy/runtime.rbs +0 -76
  504. /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
  505. /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
  506. /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
  507. /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
  508. /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
  509. /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
  510. /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
  511. /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
  512. /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
  513. /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
  514. /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
  515. /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
  516. /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
  517. /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # C02 owns which state admission may consume a TaskResult. The injected
5
+ # connection applies context/terminal decisions to the concrete state machine.
6
+ # @api private
7
+ class WorkflowTaskExecution
8
+ attr_reader :result_task, :owner_token, :cancelled_error
9
+
10
+ def initialize(definitions:, registry:, environment:, owner_token:, result_task:, cancellation_token: nil)
11
+ unless cancellation_token.nil? || cancellation_token.is_a?(Phronomy::Concurrency::CancellationToken)
12
+ raise TypeError, "cancellation_token must be a Phronomy::Concurrency::CancellationToken or nil"
13
+ end
14
+ @definitions = definitions
15
+ @registry = registry
16
+ @environment = environment
17
+ @owner_token = owner_token
18
+ @result_task = result_task
19
+ @cancellation_token = cancellation_token
20
+ @connection = nil
21
+ @reception = nil
22
+ @state_name = nil
23
+ @applied = false
24
+ @closed = false
25
+ end
26
+
27
+ def bind(connection)
28
+ @connection = connection
29
+ end
30
+
31
+ def activate
32
+ if @registry.draining?
33
+ stop(Phronomy::RuntimeShutdownError.new("Runtime is stopping Workflow task execution"))
34
+ elsif @cancellation_token&.cancelled?
35
+ cancel
36
+ elsif @cancellation_token
37
+ @cancel_reception = WorkflowTaskReception.new(registry: @registry, owner_token: @owner_token)
38
+ @cancel_subscription = @environment.subscribe_cancellation(
39
+ @cancellation_token, &@cancel_reception.method(:cancelled)
40
+ )
41
+ end
42
+ end
43
+
44
+ # Called after synchronous entry. true suppresses automatic completion.
45
+ def enter(state_name, context)
46
+ if @state_name != state_name
47
+ leave
48
+ @state_name = state_name
49
+ @entry_key = Object.new.freeze
50
+ end
51
+ return false unless @definitions.key?(state_name)
52
+ return !@applied if @reception || @applied
53
+
54
+ @reception = WorkflowTaskReception.new(registry: @registry, owner_token: @owner_token)
55
+ snapshot = context.merge({})
56
+ snapshot.set_graph_metadata(phase: state_name)
57
+ source = @definitions.fetch(state_name).fetch(:start).call(snapshot)
58
+ unless source.is_a?(Phronomy::TaskResult)
59
+ raise TypeError, "task_state #{state_name.inspect} start must return Phronomy::TaskResult"
60
+ end
61
+ @reception.watch(source)
62
+ true
63
+ end
64
+
65
+ # Queued automatic progression must belong to the same entry as the
66
+ # applied result, even if an external transition wins before dispatch.
67
+ def completion_key
68
+ return if @closed || (@definitions.key?(@state_name) && !@applied)
69
+
70
+ @entry_key
71
+ end
72
+
73
+ def permits_completion?(key)
74
+ key&.equal?(completion_key)
75
+ end
76
+
77
+ def receive(notification)
78
+ return unless current?(notification.reception)
79
+
80
+ definition = @definitions.fetch(@state_name)
81
+ @reception.close
82
+ @reception = nil
83
+ case notification.status
84
+ when :completed
85
+ context = @connection.context
86
+ updated = definition.fetch(:apply).call(context, notification.value)
87
+ unless updated.nil? || updated.is_a?(context.class)
88
+ raise TypeError, "task_state #{@state_name.inspect} apply must return its WorkflowContext or nil"
89
+ end
90
+ @applied = true
91
+ @connection.apply_task_context(updated || context)
92
+ when :cancelled
93
+ cancel(notification.error)
94
+ when :failed
95
+ stop(notification.error)
96
+ else
97
+ raise TypeError, "task_state received nonterminal TaskResult status #{notification.status.inspect}"
98
+ end
99
+ rescue => error
100
+ stop(error)
101
+ end
102
+
103
+ def delivery_failed(reception, error)
104
+ stop(error) if current?(reception) || (!@closed && reception.equal?(@cancel_reception))
105
+ end
106
+
107
+ def receive_cancellation(reception)
108
+ cancel if !@closed && reception.equal?(@cancel_reception)
109
+ end
110
+
111
+ def cancel(error = nil)
112
+ return if @closed
113
+
114
+ @cancelled_error = error || Phronomy::CancellationError.new("Workflow task execution cancelled")
115
+ stop(@cancelled_error)
116
+ end
117
+
118
+ def stop(error)
119
+ @connection&.fail_task_execution(error) unless @closed
120
+ end
121
+
122
+ def leave
123
+ @reception&.close
124
+ @reception = nil
125
+ @state_name = nil
126
+ @entry_key = nil
127
+ @applied = false
128
+ end
129
+
130
+ def close
131
+ return if @closed
132
+
133
+ @closed = true
134
+ leave
135
+ @cancel_reception&.close
136
+ @cancel_subscription&.close
137
+ @cancel_reception = @cancel_subscription = @cancellation_token = nil
138
+ @connection = nil
139
+ end
140
+
141
+ # Unknown durable outcomes retain admission, but are still observable.
142
+ def retire(error)
143
+ close
144
+ @result_task.fail(error)
145
+ end
146
+
147
+ private
148
+
149
+ def current?(reception)
150
+ !@closed && reception.equal?(@reception) && @connection.current_state == @state_name
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # A revocable, process-local completion address. The callback retains only
5
+ # this address; closing it drops its source and receiver, not the source work.
6
+ # @api private
7
+ class WorkflowTaskReception
8
+ Notification = Data.define(:reception, :status, :value, :error)
9
+ DeliveryFailure = Data.define(:reception, :error)
10
+ Cancellation = Data.define(:reception)
11
+
12
+ attr_reader :owner_token
13
+
14
+ def initialize(registry:, owner_token:)
15
+ @registry = registry
16
+ @owner_token = owner_token
17
+ @mutex = Mutex.new
18
+ @source = nil
19
+ @notified = false
20
+ end
21
+
22
+ def watch(source)
23
+ @mutex.synchronize { @source = source if @registry }
24
+ source.on_complete(&method(:completed))
25
+ end
26
+
27
+ def completed(value, error)
28
+ registry, source = claim
29
+ return unless registry
30
+
31
+ deliver(registry, Notification.new(reception: self,
32
+ status: source.status, value: value, error: error))
33
+ end
34
+
35
+ def cancelled
36
+ registry, = claim
37
+ deliver(registry, Cancellation.new(reception: self)) if registry
38
+ end
39
+
40
+ def close
41
+ @mutex.synchronize do
42
+ @registry = nil
43
+ @source = nil
44
+ end
45
+ nil
46
+ end
47
+
48
+ def active?
49
+ @mutex.synchronize { !@registry.nil? }
50
+ end
51
+
52
+ private
53
+
54
+ def claim
55
+ @mutex.synchronize do
56
+ return [nil, nil] if @notified || !@registry
57
+
58
+ @notified = true
59
+ [@registry, @source]
60
+ end
61
+ end
62
+
63
+ def deliver(registry, command)
64
+ return if registry.post(command)
65
+
66
+ registry.task_delivery_failed(self,
67
+ Phronomy::RuntimeShutdownError.new("Runtime rejected Workflow task completion"))
68
+ rescue => error
69
+ registry.task_delivery_failed(self, error)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../common/error"
4
+
5
+ module Phronomy
6
+ # A live task wait cannot be reconstructed from a Workflow state snapshot.
7
+ # @api public
8
+ class WorkflowTaskResumeError < Error
9
+ # @api public
10
+ def initialize(message = "Cannot resume a pending Workflow task_state; explicit reconciliation is required", code: "workflow.task_resume_unsupported")
11
+ super
12
+ end
13
+ end
14
+ end
@@ -9,37 +9,43 @@ module Phronomy
9
9
  def initialize(view) = @view = view
10
10
 
11
11
  def load(workflow_instance_id)
12
- @view.atomic do |bound|
13
- entry = bound.records(Phronomy::WorkflowStorageSchema::STATES).read(workflow_instance_id.to_s)
14
- entry && decode(entry, workflow_instance_id)
12
+ Phronomy::Persistence::StorageBoundary.call do
13
+ @view.atomic do |bound|
14
+ entry = bound.records(Phronomy::Workflow::Persistence::StorageSchema::STATES).read(workflow_instance_id.to_s)
15
+ entry && decode(entry, workflow_instance_id)
16
+ end
15
17
  end
16
18
  end
17
19
 
18
20
  def save(workflow_instance_id, expected_revision:, snapshot:)
19
- expected = expected_revision.nil? ? nil : Integer(expected_revision)
20
- revision = expected.nil? ? 1 : expected + 1
21
- record = Codec.encode_workflow_state(workflow_instance_id: workflow_instance_id,
22
- workflow_revision: revision, snapshot: snapshot)
23
- @view.atomic do |bound|
24
- records = bound.records(Phronomy::WorkflowStorageSchema::STATES)
25
- values = {key: workflow_instance_id.to_s, attributes: {}, record: record}
26
- entry = if expected.nil?
27
- records.insert(**values, revision: revision)
28
- else
29
- records.replace(**values, expected_revision: expected, next_revision: revision)
30
- end
31
- decoded = decode(entry, workflow_instance_id)
32
- unless decoded.fetch(:revision) == revision
33
- raise Phronomy::Storage::SerializationError, "backend returned another Workflow revision"
21
+ Phronomy::Persistence::StorageBoundary.call do
22
+ expected = expected_revision.nil? ? nil : Integer(expected_revision)
23
+ revision = expected.nil? ? 1 : expected + 1
24
+ record = Codec.encode_workflow_state(workflow_instance_id: workflow_instance_id,
25
+ workflow_revision: revision, snapshot: snapshot)
26
+ @view.atomic do |bound|
27
+ records = bound.records(Phronomy::Workflow::Persistence::StorageSchema::STATES)
28
+ values = {key: workflow_instance_id.to_s, attributes: {}, record: record}
29
+ entry = if expected.nil?
30
+ records.insert(**values, revision: revision)
31
+ else
32
+ records.replace(**values, expected_revision: expected, next_revision: revision)
33
+ end
34
+ decoded = decode(entry, workflow_instance_id)
35
+ unless decoded.fetch(:revision) == revision
36
+ raise Phronomy::Storage::SerializationError, "backend returned another Workflow revision"
37
+ end
38
+ revision
34
39
  end
35
- revision
40
+ rescue Phronomy::Storage::NotFoundError => error
41
+ raise Phronomy::Storage::ConflictError, error.message
36
42
  end
37
- rescue Phronomy::Storage::NotFoundError => error
38
- raise Phronomy::Storage::ConflictError, error.message
39
43
  end
40
44
 
41
45
  def delete(workflow_instance_id, expected_revision:)
42
- @view.records(Phronomy::WorkflowStorageSchema::STATES).delete(key: workflow_instance_id.to_s, expected_revision: Integer(expected_revision))
46
+ Phronomy::Persistence::StorageBoundary.call do
47
+ @view.records(Phronomy::Workflow::Persistence::StorageSchema::STATES).delete(key: workflow_instance_id.to_s, expected_revision: Integer(expected_revision))
48
+ end
43
49
  end
44
50
 
45
51
  private
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Domain-owned Workflow metadata, loadable without the Workflow runtime.
5
+ # @api private
6
+ class Workflow
7
+ module Persistence
8
+ module StorageSchema
9
+ STATES = Phronomy::Storage::Resource.new(id: "workflow.states", kind: :records)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Connects one compiled Workflow to its owning Runtime. Construction and
5
+ # transition compilation do not start workers or register execution state.
6
+ # @api private
7
+ class WorkflowEngineEnvironment < WorkflowExecutionEnvironment
8
+ def initialize(runtime: Phronomy::Runtime.instance)
9
+ @runtime = runtime
10
+ end
11
+
12
+ def registry
13
+ receiver = WorkflowExecutionRegistry.for(@runtime.event_loop)
14
+ @runtime.__shutdown_participant(key: WorkflowExecutionRegistry) ||
15
+ @runtime.__register_shutdown_participant(key: WorkflowExecutionRegistry, participant: receiver)
16
+ receiver
17
+ end
18
+
19
+ def existing_registry = WorkflowExecutionRegistry.existing_for(@runtime)
20
+ def executing? = @runtime.event_loop.current?
21
+
22
+ def submit(**options, &operation)
23
+ Phronomy::Execution.submit(runtime: @runtime, **options, &operation)
24
+ end
25
+
26
+ def compile_transitions(**definition)
27
+ WorkflowPhaseMachineBuilder.new(**definition).build
28
+ end
29
+
30
+ def build_session(entry_actions:, persist: nil, task_execution: nil, **options)
31
+ # Preserve nil/scalar results: initial entry must not re-adopt metadata
32
+ # unless the application actually returns a context.
33
+ initial_actions = entry_actions.to_h do |state_name, callbacks|
34
+ [state_name, callbacks.map { |callback| ->(context) { WorkflowActionRules.entry(callback, context, state_name) } }]
35
+ end
36
+ klass = task_execution ? Phronomy::WorkflowTaskSession : Phronomy::FSMSession
37
+ options = options.merge(task_execution: task_execution) if task_execution
38
+ klass.new(**options, entry_actions: initial_actions, event_loop: @runtime.event_loop,
39
+ terminal_policy: persist && WorkflowTerminalPolicy.new(persist: persist))
40
+ end
41
+
42
+ def register_session(session, completion:)
43
+ @runtime.event_loop.register(session, completion: completion, receiver: registry)
44
+ end
45
+
46
+ def subscribe_cancellation(token, &callback)
47
+ subscriptions = Phronomy::Concurrency::ResultSubscriptions.new
48
+ subscriptions.cancellation(token, &callback)
49
+ remaining = token.remaining_monotonic_seconds
50
+ if remaining && !token.cancelled?
51
+ timer = @runtime.timer_queue
52
+ timer.schedule(seconds: remaining, &callback)
53
+ subscriptions.add { timer.cancel(callback) }
54
+ end
55
+ subscriptions
56
+ rescue
57
+ subscriptions&.close
58
+ raise
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "state_machines"
4
+ require_relative "../execution/workflow_action_rules"
5
+
6
+ module Phronomy
7
+ # Builds the anonymous state-machine Class used by WorkflowRunner.
8
+ #
9
+ # This class compiles Workflow topology into state_machines declarations.
10
+ # It intentionally does not await Tasks, register completion callbacks,
11
+ # cancel external work, or interpret application event payloads.
12
+ #
13
+ # @api private
14
+ class WorkflowPhaseMachineBuilder
15
+ def initialize(
16
+ entry_point:,
17
+ declared_states:,
18
+ wait_state_names:,
19
+ external_events:,
20
+ entry_actions:,
21
+ auto_transitions:,
22
+ exit_actions:
23
+ )
24
+ @entry_point = entry_point
25
+ @declared_states = declared_states
26
+ @wait_state_names = wait_state_names
27
+ @external_events = external_events.transform_values { |items| items.map { |transition| convert_transition(transition) } }
28
+ @entry_actions = entry_actions
29
+ @auto_transitions = auto_transitions.map { |transition| convert_transition(transition) }
30
+ @exit_actions = exit_actions
31
+ end
32
+
33
+ def build
34
+ entry = @entry_point
35
+ all_states = (@declared_states + @wait_state_names + [FSMProtocol::FINISH]).uniq
36
+ auto_transitions = @auto_transitions
37
+ external_events = @external_events
38
+ entry_actions = @entry_actions
39
+ exit_actions = @exit_actions
40
+ condition_builder = method(:build_transition_condition)
41
+ entry_callback_builder = method(:build_entry_callback)
42
+ exit_callback_builder = method(:build_exit_callback)
43
+ transition_callback = build_transition_action_callback
44
+
45
+ Class.new do
46
+ attr_accessor(
47
+ :context,
48
+ :current_event,
49
+ :selected_transition_action,
50
+ :selected_transition_metadata,
51
+ :workflow_task_execution
52
+ )
53
+
54
+ state_machine :phase, initial: entry do
55
+ all_states.each { |state_name| state state_name }
56
+
57
+ event :state_completed do
58
+ auto_transitions.each do |transition_definition|
59
+ transition(
60
+ transition_definition[:from] => transition_definition[:to],
61
+ :if => condition_builder.call(transition_definition)
62
+ )
63
+ end
64
+ end
65
+
66
+ external_events.each do |event_name, transitions|
67
+ event event_name do
68
+ transitions.each do |transition_definition|
69
+ transition(
70
+ transition_definition[:from] => transition_definition[:to],
71
+ :if => condition_builder.call(transition_definition)
72
+ )
73
+ end
74
+ end
75
+ end
76
+
77
+ entry_actions.each do |state_name, callables|
78
+ callables.each do |callable|
79
+ after_transition(
80
+ to: state_name,
81
+ &entry_callback_builder.call(callable, state_name)
82
+ )
83
+ end
84
+ end
85
+
86
+ # Both source exit callbacks and the transition action are
87
+ # before_transition callbacks. Register exits first to preserve the
88
+ # required exit -> transition action -> entry ordering.
89
+ before_transition { |machine| machine.workflow_task_execution&.leave }
90
+ exit_actions.each do |state_name, callables|
91
+ callables.each do |callable|
92
+ before_transition(
93
+ from: state_name,
94
+ &exit_callback_builder.call(callable, state_name)
95
+ )
96
+ end
97
+ end
98
+
99
+ before_transition(&transition_callback)
100
+ end
101
+ end
102
+ rescue => error
103
+ raise ArgumentError, "Failed to build phase machine: #{error.message}"
104
+ end
105
+
106
+ private
107
+
108
+ def convert_transition(transition)
109
+ destination = transition[:to]
110
+ destination = FSMProtocol::FINISH if destination == Workflow::Completion::FINISH
111
+ transition.merge(to: destination)
112
+ end
113
+
114
+ def build_transition_condition(transition_definition)
115
+ guard = transition_definition[:guard]
116
+ action = transition_definition[:action]
117
+ metadata = {
118
+ from: transition_definition[:from],
119
+ event: transition_definition[:event],
120
+ to: public_destination(transition_definition[:to])
121
+ }.freeze
122
+
123
+ ->(machine) {
124
+ matched = WorkflowActionRules.allowed?(guard, machine.context, machine.current_event)
125
+ if matched
126
+ machine.selected_transition_action = action
127
+ machine.selected_transition_metadata = metadata
128
+ end
129
+ matched
130
+ }
131
+ end
132
+
133
+ def build_transition_action_callback
134
+ ->(machine) {
135
+ machine.context = WorkflowActionRules.transition(
136
+ machine.selected_transition_action, machine.context, machine.current_event,
137
+ metadata: machine.selected_transition_metadata || {}
138
+ )
139
+ }
140
+ end
141
+
142
+ def build_entry_callback(callable, state_name)
143
+ ->(machine) {
144
+ machine.context = WorkflowActionRules.apply_entry(callable, machine.context, state_name)
145
+ }
146
+ end
147
+
148
+ def build_exit_callback(callable, state_name)
149
+ ->(machine) { WorkflowActionRules.exit(callable, machine.context, state_name) }
150
+ end
151
+
152
+ def public_destination(destination)
153
+ return Workflow::Completion::FINISH if destination == FSMProtocol::FINISH
154
+
155
+ destination
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # I07 translates C02 task-state decisions to generic FSM progression. Engine
5
+ # does not learn TaskResult ownership, cancellation or stale-result policy.
6
+ # @api private
7
+ class WorkflowTaskSession < FSMSession
8
+ def initialize(task_execution:, **options)
9
+ super(**options)
10
+ @task_execution = task_execution
11
+ @task_execution.bind(self)
12
+ end
13
+
14
+ def start
15
+ @task_execution.activate
16
+ super unless @done
17
+ rescue => error
18
+ finish_with_error(error)
19
+ end
20
+
21
+ def apply_task_context(context)
22
+ return unless @terminal_lifecycle_state == :running
23
+
24
+ @ctx = @context = context
25
+ apply_context_metadata!
26
+ @tracker.context = context
27
+ advance_or_halt
28
+ rescue => error
29
+ finish_with_error(error)
30
+ end
31
+
32
+ def fail_task_execution(error)
33
+ finish_with_error(error) if @terminal_lifecycle_state == :running
34
+ end
35
+
36
+ def handle(event)
37
+ return if event.type == :state_completed && !@task_execution.permits_completion?(event.payload)
38
+
39
+ super
40
+ end
41
+
42
+ private
43
+
44
+ def post_session_event(type, payload = nil)
45
+ payload = @task_execution.completion_key if type == :state_completed
46
+ super
47
+ end
48
+
49
+ def advance_or_halt
50
+ if @task_execution.enter(@current_state, @ctx)
51
+ notify_stable_state!
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+ def build_tracker(from_state)
58
+ super.tap { |tracker| tracker.workflow_task_execution = @task_execution }
59
+ end
60
+
61
+ def request_terminal!(...)
62
+ @task_execution.close
63
+ super
64
+ end
65
+
66
+ def finish_with_error(error)
67
+ @task_execution.close
68
+ super
69
+ end
70
+
71
+ def retire_without_result!(error)
72
+ @task_execution.retire(error)
73
+ super
74
+ end
75
+ end
76
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- # Interprets durable Workflow results for the shared FSM terminal gate.
4
+ # Translates classified Workflow outcomes into shared FSM terminal decisions.
5
5
  # The session owns pending state; Runner owns persistence and reconciliation.
6
6
  # @api private
7
7
  class WorkflowTerminalPolicy
@@ -23,14 +23,14 @@ module Phronomy
23
23
  result = event.payload
24
24
  case result.outcome
25
25
  when :success
26
- FSMProtocol::TerminalDecision.new(action: :complete, error: nil)
26
+ Phronomy::TerminalDecision.new(action: :complete, error: nil)
27
27
  when :known_failure
28
- FSMProtocol::TerminalDecision.new(
28
+ Phronomy::TerminalDecision.new(
29
29
  action: :fail,
30
30
  error: result.error || Phronomy::Error.new("Workflow terminal persistence failed")
31
31
  )
32
32
  when :outcome_unknown
33
- FSMProtocol::TerminalDecision.new(action: :retire, error: result.error)
33
+ Phronomy::TerminalDecision.new(action: :retire, error: result.error)
34
34
  else
35
35
  raise Phronomy::Error,
36
36
  "unknown Workflow terminal persistence outcome: #{result.outcome.inspect}"