phronomy 0.26.0 → 0.28.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 (557) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +92 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +13 -3
  6. data/VERIFY.sh +27 -27
  7. data/benchmark/bench_agent_invoke.rb +26 -47
  8. data/benchmark/bench_context_assembler.rb +17 -15
  9. data/benchmark/bench_regression.rb +1 -1
  10. data/benchmark/bench_tool_schema.rb +1 -1
  11. data/docs/architecture/agent-chat-and-state-ownership.md +153 -0
  12. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  13. data/docs/architecture/agent-context.md +4 -2
  14. data/docs/architecture/agent-transition-ownership.md +13 -0
  15. data/docs/architecture/backend-groups.md +152 -0
  16. data/docs/architecture/before-llm-input.md +9 -3
  17. data/docs/architecture/context-management.md +31 -1
  18. data/docs/architecture/context-preparation-steps.md +90 -0
  19. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  20. data/docs/architecture/execution-abstraction-review.md +88 -0
  21. data/docs/architecture/execution-boundaries.md +28 -0
  22. data/docs/architecture/execution-metadata-and-values.md +86 -0
  23. data/docs/architecture/generator-verifier-ownership.md +111 -0
  24. data/docs/architecture/multi-agent-handoff.md +11 -4
  25. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  26. data/docs/architecture/persistence.md +45 -4
  27. data/docs/architecture/r8-unit1.md +104 -0
  28. data/docs/architecture/r8-unit10.md +72 -0
  29. data/docs/architecture/r8-unit11.md +71 -0
  30. data/docs/architecture/r8-unit12.md +67 -0
  31. data/docs/architecture/r8-unit13.md +61 -0
  32. data/docs/architecture/r8-unit14.md +72 -0
  33. data/docs/architecture/r8-unit15.md +65 -0
  34. data/docs/architecture/r8-unit16.md +54 -0
  35. data/docs/architecture/r8-unit17.md +73 -0
  36. data/docs/architecture/r8-unit18.md +92 -0
  37. data/docs/architecture/r8-unit19.md +61 -0
  38. data/docs/architecture/r8-unit2.md +55 -0
  39. data/docs/architecture/r8-unit20.md +51 -0
  40. data/docs/architecture/r8-unit21.md +79 -0
  41. data/docs/architecture/r8-unit22.md +74 -0
  42. data/docs/architecture/r8-unit23.md +81 -0
  43. data/docs/architecture/r8-unit3.md +130 -0
  44. data/docs/architecture/r8-unit4.md +79 -0
  45. data/docs/architecture/r8-unit5.md +115 -0
  46. data/docs/architecture/r8-unit6.md +71 -0
  47. data/docs/architecture/r8-unit7.md +70 -0
  48. data/docs/architecture/r8-unit8.md +78 -0
  49. data/docs/architecture/r8-unit9.md +73 -0
  50. data/docs/architecture/refactoring-closure.md +343 -0
  51. data/docs/architecture/remaining-refactoring-plan.md +390 -0
  52. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  53. data/docs/architecture/storage-execution-boundaries.md +54 -0
  54. data/docs/architecture/tool-schema-recording-gap.md +56 -0
  55. data/docs/architecture/tracing.md +2 -2
  56. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  57. data/docs/architecture.md +222 -0
  58. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  59. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  60. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  61. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  62. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  63. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  64. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +7 -0
  65. data/docs/decisions/031-durable-multi-agent-coordination.md +7 -0
  66. data/docs/decisions/032-storage-backend-composition.md +85 -0
  67. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  68. data/docs/decisions/034-handoff-runner-coordination-ownership.md +78 -0
  69. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  70. data/docs/decisions/036-context-contract-ownership.md +86 -0
  71. data/docs/decisions/037-common-definition-ownership.md +62 -0
  72. data/docs/decisions/038-responsibility-based-source-layout.md +145 -0
  73. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +82 -0
  74. data/docs/decisions/040-configuration-default-composition.md +105 -0
  75. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  76. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  77. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  78. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  79. data/docs/decisions/045-worker-input-restriction-ownership.md +104 -0
  80. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  81. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  82. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  83. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  84. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  85. data/docs/decisions/051-execution-outcome-worker-ownership.md +153 -0
  86. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  87. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  88. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  89. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  90. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  91. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  92. data/docs/decisions/058-neutral-storage-primitives.md +83 -0
  93. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  94. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  95. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  96. data/docs/decisions/062-content-store-service.md +52 -0
  97. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  98. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  99. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  100. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  101. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  102. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  103. data/docs/decisions/README.md +52 -6
  104. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  105. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  106. data/docs/execution-boundaries-preview.md +45 -0
  107. data/docs/features.md +16 -5
  108. data/docs/getting-started.md +11 -8
  109. data/docs/migrations/0.15.md +5 -0
  110. data/docs/migrations/0.22.md +2 -2
  111. data/docs/migrations/durable-semantic-coordination-v2.md +8 -0
  112. data/docs/migrations/handoff-runner-multi-agent.md +37 -0
  113. data/docs/migrations/neutral-storage-spi.md +43 -0
  114. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  115. data/docs/migrations/persistence-failure-contracts.md +67 -0
  116. data/docs/migrations/r8-unit10.md +36 -0
  117. data/docs/migrations/r8-unit11.md +33 -0
  118. data/docs/migrations/r8-unit12.md +31 -0
  119. data/docs/migrations/r8-unit13.md +36 -0
  120. data/docs/migrations/r8-unit14.md +31 -0
  121. data/docs/migrations/r8-unit15.md +31 -0
  122. data/docs/migrations/r8-unit16.md +26 -0
  123. data/docs/migrations/r8-unit17.md +28 -0
  124. data/docs/migrations/r8-unit18.md +29 -0
  125. data/docs/migrations/r8-unit19.md +28 -0
  126. data/docs/migrations/r8-unit20.md +24 -0
  127. data/docs/migrations/r8-unit21.md +28 -0
  128. data/docs/migrations/r8-unit22.md +29 -0
  129. data/docs/migrations/r8-unit23.md +20 -0
  130. data/docs/migrations/r8-unit4.md +67 -0
  131. data/docs/migrations/r8-unit5.md +75 -0
  132. data/docs/migrations/r8-unit6.md +109 -0
  133. data/docs/migrations/r8-unit7.md +31 -0
  134. data/docs/migrations/r8-unit8.md +33 -0
  135. data/docs/migrations/r8-unit9.md +28 -0
  136. data/docs/migrations/shared-state-multi-agent.md +44 -0
  137. data/docs/migrations/storage-async-client.md +103 -0
  138. data/docs/migrations/storage-backend-composition.md +117 -0
  139. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  140. data/docs/migrations/vector-async-clients.md +110 -0
  141. data/docs/persistence-backends.md +190 -215
  142. data/docs/runtime-and-concurrency.md +142 -32
  143. data/lib/phronomy/agent/admission.rb +78 -0
  144. data/lib/phronomy/{agent_already_exists_error.rb → agent/agent_already_exists_error.rb} +2 -0
  145. data/lib/phronomy/{agent_busy_error.rb → agent/agent_busy_error.rb} +2 -0
  146. data/lib/phronomy/{agent_purged_error.rb → agent/agent_purged_error.rb} +2 -0
  147. data/lib/phronomy/agent/api/agent.rb +17 -0
  148. data/lib/phronomy/{execution_rehydration_required_error.rb → agent/api/execution_rehydration_required_error.rb} +2 -0
  149. data/lib/phronomy/agent/async_event_api.rb +38 -33
  150. data/lib/phronomy/agent/base.rb +106 -294
  151. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  152. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  153. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  154. data/lib/phronomy/agent/context_importer.rb +2 -2
  155. data/lib/phronomy/agent/{context_assembler.rb → context_preparation.rb} +157 -288
  156. data/lib/phronomy/agent/control_request.rb +21 -0
  157. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  158. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +32 -63
  159. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  160. data/lib/phronomy/agent/execution/dispatch_preparation.rb +195 -0
  161. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +43 -23
  162. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +8 -5
  163. data/lib/phronomy/agent/execution/execution_coordinator.rb +1744 -0
  164. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  165. data/lib/phronomy/agent/execution/execution_metadata.rb +54 -0
  166. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +288 -0
  167. data/lib/phronomy/agent/execution/execution_registry.rb +463 -0
  168. data/lib/phronomy/agent/execution/execution_session_runner.rb +113 -0
  169. data/lib/phronomy/agent/execution/initial_preparation.rb +323 -0
  170. data/lib/phronomy/agent/execution/invocation_actions.rb +368 -0
  171. data/lib/phronomy/agent/execution/invocation_transitions.rb +124 -0
  172. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  173. data/lib/phronomy/agent/execution_change.rb +161 -0
  174. data/lib/phronomy/agent/execution_environment.rb +36 -0
  175. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  176. data/lib/phronomy/agent/execution_observation.rb +35 -0
  177. data/lib/phronomy/agent/filterable.rb +182 -0
  178. data/lib/phronomy/agent/{handoff_context.rb → handoff/transfer_context.rb} +6 -6
  179. data/lib/phronomy/agent/{handoff_projection.rb → handoff/transfer_projection.rb} +14 -33
  180. data/lib/phronomy/agent/handoff_error.rb +7 -0
  181. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  182. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  183. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  184. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  185. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  186. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  187. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -15
  188. data/lib/phronomy/agent/persistence/agent_repository.rb +69 -0
  189. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  190. data/lib/phronomy/agent/persistence/codec.rb +338 -0
  191. data/lib/phronomy/agent/persistence/execution_repository.rb +124 -0
  192. data/lib/phronomy/agent/persistence/journal_repository.rb +63 -0
  193. data/lib/phronomy/agent/persistence/records.rb +33 -0
  194. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  195. data/lib/phronomy/agent/persistence/storage_schema.rb +29 -0
  196. data/lib/phronomy/agent/persistence/watermark.rb +29 -0
  197. data/lib/phronomy/agent/recovery/invocation_restorer.rb +124 -0
  198. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +28 -51
  199. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +50 -55
  200. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +38 -43
  201. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +26 -23
  202. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  203. data/lib/phronomy/agent/recovery/recovery_support.rb +224 -0
  204. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  205. data/lib/phronomy/agent/retention.rb +17 -0
  206. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  207. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  208. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  209. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  210. data/lib/phronomy/agent/{ruby_llm_materializer.rb → runtime_input.rb} +19 -39
  211. data/lib/phronomy/agent/saved_context_reader.rb +55 -0
  212. data/lib/phronomy/agent/settings.rb +47 -0
  213. data/lib/phronomy/agent/state_writer.rb +166 -0
  214. data/lib/phronomy/agent/store.rb +253 -0
  215. data/lib/phronomy/{stream_callback_error.rb → agent/stream_callback_error.rb} +2 -0
  216. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  217. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  218. data/lib/phronomy/agent/tool_execution/tool_binding.rb +30 -0
  219. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +114 -248
  220. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  221. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  222. data/lib/phronomy/common/configuration_error.rb +7 -0
  223. data/lib/phronomy/common/error.rb +5 -0
  224. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  225. data/lib/phronomy/common/values/serializable.rb +32 -0
  226. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  227. data/lib/phronomy/content_store/storage_schema.rb +12 -0
  228. data/lib/phronomy/content_store/stored_contents.rb +44 -0
  229. data/lib/phronomy/context/assembly.rb +202 -0
  230. data/lib/phronomy/{context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +5 -1
  231. data/lib/phronomy/context/candidate.rb +51 -0
  232. data/lib/phronomy/context/constraint.rb +47 -0
  233. data/lib/phronomy/{agent → context}/context_plan.rb +2 -2
  234. data/lib/phronomy/{agent → context}/context_plan_validator.rb +3 -3
  235. data/lib/phronomy/{agent → context}/context_policy.rb +1 -1
  236. data/lib/phronomy/{agent → context}/context_policy_input.rb +7 -7
  237. data/lib/phronomy/{agent → context}/context_policy_input_builder.rb +1 -1
  238. data/lib/phronomy/context/default_policy.rb +140 -0
  239. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  240. data/lib/phronomy/{agent → context}/llm_input_build_context.rb +2 -2
  241. data/lib/phronomy/{agent → context}/llm_input_manifest.rb +21 -21
  242. data/lib/phronomy/{agent → context}/llm_input_patch.rb +3 -3
  243. data/lib/phronomy/context/manifest_error.rb +10 -0
  244. data/lib/phronomy/context/prompt_template.rb +98 -0
  245. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  246. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  247. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  248. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  249. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  250. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  251. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  252. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  253. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  254. data/lib/phronomy/embeddings/base.rb +43 -0
  255. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  256. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  257. data/lib/phronomy/engine/concurrency/offload_pool.rb +39 -20
  258. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  259. data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +1 -1
  260. data/lib/phronomy/engine/event_loop.rb +164 -630
  261. data/lib/phronomy/engine/execution_binding.rb +23 -0
  262. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  263. data/lib/phronomy/engine/fsm_protocol.rb +9 -0
  264. data/lib/phronomy/engine/fsm_session.rb +37 -30
  265. data/lib/phronomy/engine/metrics.rb +28 -0
  266. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  267. data/lib/phronomy/engine/runtime.rb +104 -95
  268. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  269. data/lib/phronomy/execution/async_operation.rb +41 -0
  270. data/lib/phronomy/execution/backpressure_error.rb +7 -0
  271. data/lib/phronomy/{blocking.rb → execution/blocking.rb} +2 -2
  272. data/lib/phronomy/execution/cancellation_error.rb +7 -0
  273. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +5 -1
  274. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  275. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  276. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  277. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  278. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  279. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  280. data/lib/phronomy/execution/concurrency/worker_input_restricted.rb +14 -0
  281. data/lib/phronomy/execution/event_loop_reentrancy_error.rb +8 -0
  282. data/lib/phronomy/{execution.rb → execution/execution.rb} +57 -30
  283. data/lib/phronomy/{execution_cancellation_error.rb → execution/execution_cancellation_error.rb} +2 -0
  284. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  285. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  286. data/lib/phronomy/{execution_timeout_error.rb → execution/execution_timeout_error.rb} +2 -0
  287. data/lib/phronomy/{invalid_async_entry_action_error.rb → execution/invalid_async_entry_action_error.rb} +2 -0
  288. data/lib/phronomy/{invalid_async_transition_action_error.rb → execution/invalid_async_transition_action_error.rb} +2 -0
  289. data/lib/phronomy/{invalid_async_workflow_action_error.rb → execution/invalid_async_workflow_action_error.rb} +2 -0
  290. data/lib/phronomy/{invocation_context.rb → execution/invocation_context.rb} +0 -15
  291. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  292. data/lib/phronomy/execution/pool_shutdown_error.rb +7 -0
  293. data/lib/phronomy/execution/recursion_limit_error.rb +7 -0
  294. data/lib/phronomy/execution/runnable.rb +24 -0
  295. data/lib/phronomy/execution/runtime_shutdown_error.rb +7 -0
  296. data/lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb +7 -0
  297. data/lib/phronomy/execution/scheduler_reentrancy_error.rb +9 -0
  298. data/lib/phronomy/{engine → execution}/task_result.rb +7 -3
  299. data/lib/phronomy/execution/timeout_error.rb +7 -0
  300. data/lib/phronomy/execution/wait_policy.rb +24 -0
  301. data/lib/phronomy/filter/filter_block_error.rb +14 -0
  302. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  303. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  304. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  305. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  306. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  307. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  308. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  309. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  310. data/lib/phronomy/llm_adapter/authentication_error.rb +9 -0
  311. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  312. data/lib/phronomy/llm_adapter/base.rb +49 -68
  313. data/lib/phronomy/llm_adapter/context_length_error.rb +9 -0
  314. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  315. data/lib/phronomy/llm_adapter/message.rb +62 -0
  316. data/lib/phronomy/llm_adapter/rate_limit_error.rb +9 -0
  317. data/lib/phronomy/llm_adapter/request.rb +79 -0
  318. data/lib/phronomy/llm_adapter/response.rb +36 -0
  319. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  320. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  321. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  322. data/lib/phronomy/llm_adapter.rb +3 -16
  323. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  324. data/lib/phronomy/multi_agent/admission_registry.rb +15 -2
  325. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  326. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  327. data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
  328. data/lib/phronomy/{agent → multi_agent}/handoff_capability_factory.rb +21 -2
  329. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  330. data/lib/phronomy/{agent → multi_agent}/handoff_policy.rb +1 -1
  331. data/lib/phronomy/multi_agent/handoff_runner.rb +156 -0
  332. data/lib/phronomy/{agent → multi_agent}/handoff_state.rb +2 -2
  333. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  334. data/lib/phronomy/multi_agent/persistence/codec.rb +75 -0
  335. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  336. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  337. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  338. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +124 -0
  339. data/lib/phronomy/multi_agent/persistence/team_repository.rb +69 -0
  340. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  341. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  342. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  343. data/lib/phronomy/multi_agent/store.rb +156 -0
  344. data/lib/phronomy/multi_agent/team_coordinator.rb +87 -75
  345. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  346. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +5 -5
  347. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  348. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  349. data/lib/phronomy/persistence/content_repository.rb +26 -0
  350. data/lib/phronomy/persistence/errors.rb +16 -0
  351. data/lib/phronomy/persistence/migration/initial_format_migration.rb +21 -21
  352. data/lib/phronomy/persistence/persistence.rb +23 -0
  353. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  354. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  355. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  356. data/lib/phronomy/persistence/transaction.rb +86 -0
  357. data/lib/phronomy/persistence_composition/storage_schema.rb +29 -0
  358. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  359. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  360. data/lib/phronomy/runtime_composition/agent_defaults.rb +11 -0
  361. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  362. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  363. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  364. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  365. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  366. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  367. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  368. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  369. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  370. data/lib/phronomy/storage/async/async_client.rb +60 -0
  371. data/lib/phronomy/storage/backend.rb +107 -0
  372. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  373. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  374. data/lib/phronomy/storage/blobs.rb +31 -0
  375. data/lib/phronomy/storage/condition.rb +25 -0
  376. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  377. data/lib/phronomy/storage/conflict_error.rb +9 -0
  378. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  379. data/lib/phronomy/storage/entry.rb +33 -0
  380. data/lib/phronomy/storage/guard_ref.rb +13 -0
  381. data/lib/phronomy/storage/not_found_error.rb +9 -0
  382. data/lib/phronomy/storage/record_codec.rb +177 -0
  383. data/lib/phronomy/storage/records.rb +61 -0
  384. data/lib/phronomy/storage/resource.rb +126 -0
  385. data/lib/phronomy/storage/scope.rb +25 -0
  386. data/lib/phronomy/storage/serialization_error.rb +9 -0
  387. data/lib/phronomy/storage/streams.rb +44 -0
  388. data/lib/phronomy/storage/transaction_error.rb +10 -0
  389. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  390. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  391. data/lib/phronomy/storage/validation.rb +53 -0
  392. data/lib/phronomy/storage/view.rb +131 -0
  393. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  394. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  395. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  396. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  397. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +54 -48
  398. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +1 -1
  399. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +1 -1
  400. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  401. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +52 -46
  402. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  403. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +124 -0
  404. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  405. data/lib/phronomy/tool/authorization.rb +77 -0
  406. data/lib/phronomy/tool/base.rb +549 -9
  407. data/lib/phronomy/tool/call_request.rb +40 -0
  408. data/lib/phronomy/{agent/tool_definition_set.rb → tool/definition_set.rb} +16 -12
  409. data/lib/phronomy/tool/operation.rb +88 -0
  410. data/lib/phronomy/tool/schema.rb +214 -0
  411. data/lib/phronomy/tool/settings.rb +31 -0
  412. data/lib/phronomy/tool/tool_error.rb +7 -0
  413. data/lib/phronomy/{agent → tool}/tool_executor.rb +10 -7
  414. data/lib/phronomy/tools/agent.rb +22 -90
  415. data/lib/phronomy/tools/mcp.rb +21 -14
  416. data/lib/phronomy/tools/vector_search.rb +4 -4
  417. data/lib/phronomy/tracing/automatic.rb +11 -6
  418. data/lib/phronomy/tracing/base.rb +1 -1
  419. data/lib/phronomy/tracing/observation.rb +37 -0
  420. data/lib/phronomy/tracing/settings.rb +36 -0
  421. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  422. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  423. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  424. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  425. data/lib/phronomy/vector_store/base.rb +118 -49
  426. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  427. data/lib/phronomy/vector_store.rb +2 -17
  428. data/lib/phronomy/version.rb +1 -1
  429. data/lib/phronomy/workflow/completion.rb +11 -0
  430. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
  431. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  432. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +5 -1
  433. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  434. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  435. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +186 -0
  436. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +145 -115
  437. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  438. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  439. data/lib/phronomy/workflow/persistence/state_repository.rb +63 -0
  440. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  441. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  442. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  443. data/lib/phronomy/workflow/runtime_binding/workflow_terminal_policy.rb +40 -0
  444. data/lib/phronomy.rb +97 -98
  445. data/scripts/api_snapshot.rb +36 -16
  446. data/scripts/storage_spi_snapshot.rb +36 -0
  447. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  448. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  449. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  450. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  451. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  452. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  453. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  454. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  455. data/sig/phronomy/agent/admission.rbs +19 -0
  456. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  457. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  458. data/sig/phronomy/agent/store.rbs +73 -0
  459. data/sig/phronomy/agent.rbs +14 -139
  460. data/sig/phronomy/common.rbs +7 -0
  461. data/sig/phronomy/content_store/contracts.rbs +13 -0
  462. data/sig/phronomy/context.rbs +152 -0
  463. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  464. data/sig/phronomy/embeddings/base.rbs +17 -0
  465. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  466. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  467. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  468. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  469. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  470. data/sig/phronomy/engine/observability.rbs +13 -0
  471. data/sig/phronomy/execution/async_operation.rbs +7 -0
  472. data/sig/phronomy/execution/blocking.rbs +5 -0
  473. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  474. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  475. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  476. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  477. data/sig/phronomy/execution/errors.rbs +7 -0
  478. data/sig/phronomy/execution/execution.rbs +41 -0
  479. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  480. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  481. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  482. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  483. data/sig/phronomy/execution/task_result.rbs +29 -0
  484. data/sig/phronomy/extensions.rbs +0 -21
  485. data/sig/phronomy/handoff.rbs +12 -4
  486. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  487. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  488. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  489. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  490. data/sig/phronomy/llm_context_window.rbs +10 -0
  491. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  492. data/sig/phronomy/multi_agent/store.rbs +54 -0
  493. data/sig/phronomy/multi_agent.rbs +24 -3
  494. data/sig/phronomy/persistence/errors.rbs +18 -0
  495. data/sig/phronomy/persistence/migration.rbs +13 -0
  496. data/sig/phronomy/persistence/transaction.rbs +19 -0
  497. data/sig/phronomy/persistence.rbs +3 -185
  498. data/sig/phronomy/persistence_composition.rbs +19 -0
  499. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  500. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  501. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  502. data/sig/phronomy/storage/contracts.rbs +160 -0
  503. data/sig/phronomy/tool.rbs +58 -5
  504. data/sig/phronomy/tracing/observation.rbs +7 -0
  505. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  506. data/sig/phronomy/vector_store/base.rbs +23 -0
  507. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  508. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  509. data/sig/phronomy/workflow/store.rbs +8 -0
  510. data/sig/phronomy/workflow.rbs +1 -1
  511. data/sig/phronomy.rbs +0 -49
  512. metadata +462 -130
  513. data/lib/phronomy/agent/agent_invocation_session_builder.rb +0 -584
  514. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  515. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  516. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  517. data/lib/phronomy/agent/context/capability/base.rb +0 -507
  518. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  519. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  520. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  521. data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
  522. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  523. data/lib/phronomy/agent/handoff_request.rb +0 -45
  524. data/lib/phronomy/agent/handoff_runner.rb +0 -178
  525. data/lib/phronomy/agent/phase_machine_builder.rb +0 -152
  526. data/lib/phronomy/agent/provider_call_outcome.rb +0 -106
  527. data/lib/phronomy/agent/recovery_support.rb +0 -504
  528. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  529. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  530. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  531. data/lib/phronomy/agent/tool_call_intercepted.rb +0 -29
  532. data/lib/phronomy/agent/tool_invocation_session_builder.rb +0 -276
  533. data/lib/phronomy/configuration.rb +0 -59
  534. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  535. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  536. data/lib/phronomy/generator_verifier.rb +0 -369
  537. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  538. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  539. data/lib/phronomy/metrics.rb +0 -26
  540. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  541. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  542. data/lib/phronomy/persistence/in_memory.rb +0 -690
  543. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  544. data/lib/phronomy/persistence.rb +0 -276
  545. data/lib/phronomy/recovery.rb +0 -186
  546. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  547. data/lib/phronomy/runnable.rb +0 -54
  548. data/lib/phronomy/token_usage.rb +0 -85
  549. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  550. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  551. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  552. data/lib/phronomy/workflow_recovery.rb +0 -123
  553. data/sig/phronomy/llm_adapter.rbs +0 -11
  554. data/sig/phronomy/runtime.rbs +0 -76
  555. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  556. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  557. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Engine implementation of the Execution mechanism protocol. Installing this
5
+ # binding does not acquire any resources; default Runtime is resolved on use.
6
+ # @api private
7
+ module ExecutionBinding
8
+ def self.submit(pool: nil, runtime: nil, pool_name: nil, size: 10,
9
+ queue_size: 100, **options, &operation)
10
+ unless pool
11
+ runtime ||= Runtime.instance
12
+ pool = pool_name ? runtime.pool(pool_name, size: size, queue_size: queue_size) : runtime.offload
13
+ end
14
+ pool.submit(**options, &operation)
15
+ end
16
+
17
+ def self.after(seconds, &callback)
18
+ timer = Runtime.instance.timer_queue
19
+ timer.schedule(seconds: seconds, &callback)
20
+ -> { timer.cancel(callback) }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Concrete EventLoop transport and lifecycle registration for execution state.
5
+ # @api private
6
+ class ExecutionReceiverBinding
7
+ def self.receiver(receiver_class, event_loop)
8
+ event_loop.__execution_receiver(key: receiver_class) ||
9
+ event_loop.__register_execution_receiver(key: receiver_class,
10
+ receiver: receiver_class.new(channel: new(event_loop)))
11
+ end
12
+
13
+ def self.existing_receiver(receiver_class, runtime)
14
+ runtime.__event_loop_if_initialized&.__execution_receiver(key: receiver_class)
15
+ end
16
+
17
+ def initialize(event_loop)
18
+ @event_loop = event_loop
19
+ end
20
+
21
+ def bound_to?(owner) = @event_loop.equal?(owner)
22
+ def current? = @event_loop.current?
23
+
24
+ def synchronize(&block)
25
+ @event_loop.__synchronize_execution_state(&block)
26
+ end
27
+
28
+ def admit(receiver, &block)
29
+ @event_loop.__admit_execution(receiver, &block)
30
+ end
31
+
32
+ def post(receiver, message, admission:, completion:)
33
+ @event_loop.__post_execution(receiver, message, admission: admission, completion: completion)
34
+ end
35
+
36
+ def route(receiver, type:, payload:)
37
+ @event_loop.__route_execution_event(receiver) do
38
+ target = yield
39
+ target && Phronomy::Event.new(type: type, target_id: target, payload: payload)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Internal terminal vocabulary shared only by FSM adapters and sessions.
5
+ # @api private
6
+ module FSMProtocol
7
+ FINISH = :__end__
8
+ end
9
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../execution/concurrency/worker_input_restricted"
4
+
3
5
  require "securerandom"
4
6
 
5
7
  module Phronomy
@@ -9,6 +11,8 @@ module Phronomy
9
11
  # FSMSession owns FSM execution only; it does not own external TaskResult handles,
10
12
  # activity tokens, callback correlation, or domain-specific stale-event policy.
11
13
  class FSMSession
14
+ include Phronomy::Concurrency::WorkerInputRestricted
15
+
12
16
  class IdentityReservation
13
17
  attr_reader :fsm_session_id
14
18
 
@@ -30,6 +34,8 @@ module Phronomy
30
34
  private_constant :IdentityReservation
31
35
 
32
36
  class EventSink
37
+ include Phronomy::Concurrency::WorkerInputRestricted
38
+
33
39
  attr_reader :fsm_session_id
34
40
 
35
41
  def initialize(event_loop:)
@@ -60,8 +66,6 @@ module Phronomy
60
66
  end
61
67
  end
62
68
 
63
- FINISH = WorkflowRunner::FINISH
64
-
65
69
  attr_reader :id, :context, :event_sink
66
70
 
67
71
  # Returns the live current Workflow phase. During an active FSM transition
@@ -95,7 +99,7 @@ module Phronomy
95
99
  context_metadata: {},
96
100
  event_sink: nil,
97
101
  identity_reservation: nil,
98
- terminal_barrier: nil
102
+ terminal_policy: nil
99
103
  )
100
104
  @id = if identity_reservation
101
105
  unless identity_reservation.is_a?(IdentityReservation)
@@ -123,7 +127,7 @@ module Phronomy
123
127
  @resume_event = resume_event
124
128
  @resume_phase = resume_phase
125
129
  @stable_observer = stable_observer
126
- @terminal_barrier = terminal_barrier
130
+ @terminal_policy = terminal_policy
127
131
  @terminal_lifecycle_state = :running
128
132
  @pending_terminal_type = nil
129
133
  @pending_terminal_notify_stable = false
@@ -160,14 +164,13 @@ module Phronomy
160
164
  def handle(event)
161
165
  return if @done
162
166
 
163
- if event.type == :workflow_terminal_persistence_result
164
- handle_terminal_persistence_result(event.payload)
167
+ if @terminal_policy&.handles?(event)
168
+ handle_terminal_event(event)
165
169
  return
166
170
  end
167
171
 
168
- # Once terminal persistence begins, ordinary Workflow events no longer
169
- # have result authority. Only the persistence completion for this concrete
170
- # FSMSession can advance its terminal lifecycle.
172
+ # Once a terminal decision is pending, ordinary events cannot advance
173
+ # the state machine. Only the injected policy can permit terminalization.
171
174
  return unless @terminal_lifecycle_state == :running
172
175
 
173
176
  context_disposition = apply_context_event(event)
@@ -261,7 +264,7 @@ module Phronomy
261
264
  end
262
265
 
263
266
  def advance_or_halt
264
- return finish! if @current_state == FINISH
267
+ return finish! if @current_state == FSMProtocol::FINISH
265
268
 
266
269
  # A wait state is the logical end of this Workflow execution segment. Its
267
270
  # public stable-state notification is therefore part of terminalization
@@ -324,7 +327,7 @@ module Phronomy
324
327
  def finish!(notify_stable: false)
325
328
  request_terminal!(
326
329
  :finished,
327
- phase: :__end__,
330
+ phase: FSMProtocol::FINISH,
328
331
  notify_stable: notify_stable
329
332
  )
330
333
  end
@@ -339,13 +342,13 @@ module Phronomy
339
342
  apply_context_metadata!(phase: phase)
340
343
  @pending_terminal_type = terminal_type
341
344
  @pending_terminal_notify_stable = notify_stable
342
- unless @terminal_barrier
345
+ unless @terminal_policy
343
346
  complete_terminal!(terminal_type)
344
347
  return
345
348
  end
346
349
 
347
- @terminal_lifecycle_state = :persisting_terminal
348
- @terminal_barrier.call(
350
+ @terminal_lifecycle_state = :awaiting_terminal
351
+ @terminal_policy.start(
349
352
  terminal_type: terminal_type,
350
353
  context: @ctx,
351
354
  event_sink: @event_sink
@@ -354,31 +357,35 @@ module Phronomy
354
357
  finish_with_error(error)
355
358
  end
356
359
 
357
- def handle_terminal_persistence_result(result)
358
- return unless @terminal_lifecycle_state == :persisting_terminal
360
+ def handle_terminal_event(event)
361
+ return unless @terminal_lifecycle_state == :awaiting_terminal
359
362
 
360
- case result.outcome
361
- when :success
363
+ decision = @terminal_policy.decision_for(event)
364
+ case decision.action
365
+ when :complete
362
366
  complete_terminal!(@pending_terminal_type)
363
- when :known_failure
364
- finish_with_error(
365
- result.error || Phronomy::Error.new("Workflow terminal persistence failed")
366
- )
367
- when :outcome_unknown
368
- @done = true
369
- @terminal_lifecycle_state = :recovery_required
370
- post_recovery_required_event(result.error)
367
+ when :fail
368
+ finish_with_error(decision.error)
369
+ when :retire
370
+ retire_without_result!(decision.error)
371
371
  else
372
372
  raise Phronomy::Error,
373
- "unknown Workflow terminal persistence outcome: #{result.outcome.inspect}"
373
+ "unknown FSM terminal decision: #{decision.action.inspect}"
374
374
  end
375
375
  end
376
376
 
377
- def complete_terminal!(terminal_type)
377
+ def retire_without_result!(error)
378
378
  @done = true
379
+ @terminal_lifecycle_state = :retired
380
+ post_recovery_required_event(error)
381
+ end
382
+
383
+ def complete_terminal!(terminal_type)
379
384
  @terminal_lifecycle_state =
380
385
  (terminal_type == :halted) ? :halted : :completed
386
+ # Keep the error path open until the observer returns successfully.
381
387
  notify_stable_state! if @pending_terminal_notify_stable
388
+ @done = true
382
389
  post_terminal_event(terminal_type, @ctx)
383
390
  end
384
391
 
@@ -400,7 +407,7 @@ module Phronomy
400
407
  )
401
408
  return if accepted
402
409
 
403
- Phronomy.configuration.logger&.warn(
410
+ Phronomy::RuntimeSettings.current.logger&.warn(
404
411
  "[Phronomy::FSMSession] EventLoop rejected recovery-required event for #{@id}"
405
412
  )
406
413
  end
@@ -415,7 +422,7 @@ module Phronomy
415
422
  )
416
423
  return if accepted
417
424
 
418
- Phronomy.configuration.logger&.warn(
425
+ Phronomy::RuntimeSettings.current.logger&.warn(
419
426
  "[Phronomy::FSMSession] EventLoop rejected terminal event " \
420
427
  "#{type.inspect} for #{@id}"
421
428
  )
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Runtime observability snapshot for the two concurrency boundaries:
5
+ # EventLoop and the default OffloadPool. Observation never creates resources;
6
+ # missing resources contribute zero values. Individual counters are sampled
7
+ # independently, not as an atomic view of concurrent execution.
8
+ module Metrics
9
+ def self.snapshot
10
+ runtime = Runtime.__default_if_initialized
11
+ pool = runtime&.__offload_if_initialized
12
+ event_loop = runtime&.__event_loop_if_initialized
13
+
14
+ {
15
+ offload_pool_active: pool&.active_count || 0,
16
+ offload_pool_queue_length: pool&.queue_depth || 0,
17
+ offload_pool_abandoned_active: pool&.abandoned_active_count || 0,
18
+ offload_pool_abandoned_total: pool&.abandoned_count || 0,
19
+ offload_pool_size: pool&.pool_size || 0,
20
+ event_loop_queue_depth: event_loop&.queue_depth || 0,
21
+ event_loop_queue_max_depth: event_loop&.max_queue_depth || 0,
22
+ event_loop_lag_last_ms: ((event_loop&.last_lag_seconds || 0.0) * 1000).round(3),
23
+ event_loop_lag_max_ms: ((event_loop&.max_lag_seconds || 0.0) * 1000).round(3),
24
+ event_loop_lag_average_ms: ((event_loop&.average_lag_seconds || 0.0) * 1000).round(3)
25
+ }
26
+ end
27
+ end
28
+ end
@@ -68,7 +68,7 @@ module Phronomy
68
68
  callbacks.each do |callback|
69
69
  callback.call
70
70
  rescue => error
71
- Phronomy.configuration.logger&.error do
71
+ Phronomy::RuntimeSettings.current.logger&.error do
72
72
  "[TimerQueue] callback raised #{error.class}: #{error.message}"
73
73
  end
74
74
  end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../execution/concurrency/worker_input_restricted"
4
+
3
5
  require_relative "runtime/timer_queue"
4
6
  require_relative "runtime/shutdown_result"
5
7
  require_relative "runtime/timer_service"
6
- require_relative "runtime/agent_ownership_registry"
7
- require_relative "runtime/team_ownership_registry"
8
8
 
9
9
  module Phronomy
10
10
  class Runtime
11
+ include Phronomy::Concurrency::WorkerInputRestricted
12
+
11
13
  @instance_mutex = Mutex.new
12
14
 
13
15
  class << self
@@ -15,10 +17,16 @@ module Phronomy
15
17
  instance_mutex.synchronize { @instance ||= new }
16
18
  end
17
19
 
18
- def default_if_initialized_for_test
20
+ # Lookup only: diagnostics must not initialize the default Runtime.
21
+ # @api private
22
+ def __default_if_initialized
19
23
  instance_mutex.synchronize { @instance }
20
24
  end
21
25
 
26
+ def default_if_initialized_for_test
27
+ __default_if_initialized
28
+ end
29
+
22
30
  def replace_default_for_test(runtime)
23
31
  instance_mutex.synchronize do
24
32
  previous = @instance
@@ -31,7 +39,7 @@ module Phronomy
31
39
  instance_mutex.synchronize { @instance = runtime }
32
40
  end
33
41
 
34
- def reset_default!(timeout: Phronomy.configuration.event_loop_stop_grace_seconds)
42
+ def reset_default!(timeout: Phronomy::RuntimeSettings.current.event_loop_stop_grace_seconds)
35
43
  runtime = instance_mutex.synchronize { @instance }
36
44
  return ShutdownResult.not_started unless runtime
37
45
 
@@ -48,8 +56,7 @@ module Phronomy
48
56
  end
49
57
 
50
58
  def in_event_loop_context?
51
- runtime = instance_mutex.synchronize { @instance }
52
- runtime&.event_loop_current? || false
59
+ Phronomy::WaitPolicy.blocking_forbidden?
53
60
  end
54
61
 
55
62
  private
@@ -64,9 +71,8 @@ module Phronomy
64
71
  @pool_registry = Phronomy::Concurrency::PoolRegistry.new(
65
72
  timer_queue_provider: -> { timer_queue }
66
73
  )
67
- @multi_agent_admissions = Phronomy::MultiAgent::AdmissionRegistry.new
68
- @agent_ownership_registry = AgentOwnershipRegistry.new(runtime: self)
69
- @team_ownership_registry = TeamOwnershipRegistry.new
74
+ @shutdown_participants = {}
75
+ @shutdown_participant_error = nil
70
76
  @lifecycle_mutex = Mutex.new
71
77
  @shutdown_mutex = Mutex.new
72
78
  @state = :running
@@ -80,8 +86,8 @@ module Phronomy
80
86
  end
81
87
 
82
88
  def offload(
83
- pool_size: Phronomy.configuration.offload_pool_size,
84
- queue_size: Phronomy.configuration.offload_queue_size
89
+ pool_size: Phronomy::RuntimeSettings.current.offload_pool_size,
90
+ queue_size: Phronomy::RuntimeSettings.current.offload_queue_size
85
91
  )
86
92
  ensure_accepting_work!
87
93
  @pool_registry.default_pool(pool_size: pool_size, queue_size: queue_size)
@@ -92,6 +98,12 @@ module Phronomy
92
98
  @pool_registry.named_pool(name, size: size, queue_size: queue_size)
93
99
  end
94
100
 
101
+ # Lookup only; retained pools remain observable during and after shutdown.
102
+ # @api private
103
+ def __offload_if_initialized
104
+ @pool_registry.default_pool_if_initialized
105
+ end
106
+
95
107
  def timer_queue
96
108
  ensure_accepting_work!
97
109
  timer = @timer_service.timer_queue
@@ -103,83 +115,40 @@ module Phronomy
103
115
  @timer_service.timer_queue
104
116
  end
105
117
 
106
- # Returns only an immutable process-local routing/ownership view. Mutable
107
- # execution state remains inside EventLoop and is never exposed to callers.
108
- # @api private
109
- def __agent_execution_owner(execution_id)
110
- loop_instance = @lifecycle_mutex.synchronize { @event_loop }
111
- loop_instance&.agent_execution_owner(execution_id)
112
- end
113
-
114
- # @api private
115
- def __team_owner(id, klass:, create:, persistence:, &block)
116
- @team_ownership_registry.fetch(id, klass: klass, create: create, persistence: persistence, &block)
117
- end
118
-
119
- # @api private
120
- def __get_team(id, klass:)
121
- @team_ownership_registry.get(id.to_s, klass: klass)
122
- end
123
-
124
- # @api private
125
- def __create_agent(agent_id, expected_class:, &block)
126
- @agent_ownership_registry.create(agent_id, expected_class: expected_class, &block)
127
- end
128
-
129
- # @api private
130
- def __load_agent(agent_id, expected_class:, &block)
131
- @agent_ownership_registry.load(agent_id, expected_class: expected_class, &block)
132
- end
133
-
134
- # @api private
135
- def __get_agent(agent_id, expected_class:)
136
- @agent_ownership_registry.get(agent_id, expected_class: expected_class)
137
- end
138
-
139
- # @api private
140
- def __agent_owned?(agent)
141
- @agent_ownership_registry.owned?(agent)
142
- end
143
-
144
- # @api private
145
- def __begin_agent_purge(agent)
146
- @agent_ownership_registry.begin_purge(agent)
147
- end
148
-
149
- # @api private
150
- def __complete_agent_purge(agent, token)
151
- @agent_ownership_registry.complete_purge(agent, token)
152
- end
153
-
118
+ # Lookup only; inspection must not create an EventLoop during shutdown.
154
119
  # @api private
155
- def __abort_agent_purge(agent, token)
156
- @agent_ownership_registry.abort_purge(agent, token)
120
+ def __event_loop_if_initialized
121
+ @lifecycle_mutex.synchronize { @event_loop }
157
122
  end
158
123
 
124
+ # Shares one internal participant per key for this Runtime's lifetime.
125
+ # begin_draining must be idempotent, nonblocking and must not call Runtime:
126
+ # admission closes under the same lifecycle lock as registration.
127
+ # wait_until_idle receives an absolute monotonic deadline and returns a Boolean.
128
+ # Optional after_runtime_shutdown runs outside the lifecycle lock only after
129
+ # all waits succeed, EventLoop stops, and pools/timer finish shutdown. It must
130
+ # be idempotent, short, and perform no I/O. Exceptions mark cleanup incomplete.
131
+ # This is a shutdown contract, not an application extension point.
159
132
  # @api private
160
- def __leave_agent_purge_uncertain(agent, token)
161
- @agent_ownership_registry.leave_purge_uncertain(agent, token)
162
- end
163
-
164
- # @api private
165
- def __agent_execution_admitted?(agent_id)
166
- loop_instance = @lifecycle_mutex.synchronize { @event_loop }
167
- loop_instance&.agent_execution_admitted?(agent_id) || false
168
- end
133
+ def __register_shutdown_participant(key:, participant:)
134
+ unless participant.respond_to?(:begin_draining) && participant.respond_to?(:wait_until_idle)
135
+ raise ArgumentError, "shutdown participant must implement begin_draining and wait_until_idle"
136
+ end
169
137
 
170
- # @api private
171
- def __admit_multi_agent(coordinator)
172
- current_state = @lifecycle_mutex.synchronize { @state }
173
- unless current_state == :running
174
- raise Phronomy::RuntimeShutdownError,
175
- "Runtime is #{current_state}; new Multi-Agent turns are not accepted"
138
+ @lifecycle_mutex.synchronize do
139
+ unless @state == :running
140
+ raise Phronomy::RuntimeShutdownError,
141
+ "Runtime is #{@state}; shutdown participants cannot be registered"
142
+ end
143
+ @shutdown_participants[key] ||= participant
176
144
  end
177
- @multi_agent_admissions.admit!(coordinator)
178
145
  end
179
146
 
147
+ # Lookup only: never registers a participant or reopens its admission gate.
148
+ # Retained participants remain available during and after shutdown.
180
149
  # @api private
181
- def __release_multi_agent(coordinator)
182
- @multi_agent_admissions.release!(coordinator)
150
+ def __shutdown_participant(key:)
151
+ @lifecycle_mutex.synchronize { @shutdown_participants[key] }
183
152
  end
184
153
 
185
154
  def event_loop
@@ -214,11 +183,12 @@ module Phronomy
214
183
 
215
184
  @failure ||= error
216
185
  @state = :failed
186
+ begin_draining_participants
217
187
  end
218
188
  end
219
189
 
220
190
  def shutdown(
221
- timeout: Phronomy.configuration.event_loop_stop_grace_seconds,
191
+ timeout: Phronomy::RuntimeSettings.current.event_loop_stop_grace_seconds,
222
192
  cancel_grace: timeout
223
193
  )
224
194
  if event_loop_current?
@@ -232,17 +202,14 @@ module Phronomy
232
202
  return @shutdown_result if @shutdown_result
233
203
 
234
204
  drain_deadline = monotonic_now + timeout
235
- loop_instance = @lifecycle_mutex.synchronize do
205
+ loop_instance, participants = @lifecycle_mutex.synchronize do
236
206
  @state = :draining unless @state == :failed
237
- @event_loop
207
+ begin_draining_participants
208
+ [@event_loop, @shutdown_participants.values]
238
209
  end
239
210
  loop_instance&.begin_draining
240
- @agent_ownership_registry.begin_draining
241
- @team_ownership_registry.begin_draining
242
211
 
243
- admission_idle = @multi_agent_admissions.wait_until_idle(drain_deadline)
244
- agent_ownership_stable = @agent_ownership_registry.wait_until_stable(drain_deadline)
245
- team_ownership_stable = @team_ownership_registry.wait_until_stable(drain_deadline)
212
+ participants_idle = wait_for_participants(participants, drain_deadline)
246
213
  loop_idle = !loop_instance || loop_instance.wait_until_idle(drain_deadline)
247
214
 
248
215
  @lifecycle_mutex.synchronize do
@@ -256,12 +223,17 @@ module Phronomy
256
223
  :not_started
257
224
  end
258
225
 
259
- subsystem_error = shutdown_pools_and_timer
260
- cleanup_complete = admission_idle && agent_ownership_stable && team_ownership_stable && loop_idle &&
226
+ pools_stopped, subsystem_error = shutdown_pools_and_timer(deadline: stop_deadline)
227
+ participant_error = @lifecycle_mutex.synchronize { @shutdown_participant_error }
228
+ cleanup_complete = participants_idle && participant_error.nil? &&
229
+ loop_idle &&
261
230
  (!loop_instance || !loop_instance.thread_alive?) &&
262
231
  event_loop_status != :cancel_timeout &&
232
+ (!loop_instance || loop_instance.__receiver_cleanup_complete?) &&
233
+ pools_stopped &&
263
234
  subsystem_error.nil?
264
235
 
236
+ cleanup_complete = finalize_participants(participants) if cleanup_complete
265
237
  failure = @lifecycle_mutex.synchronize { @failure } || subsystem_error
266
238
  runtime_outcome = if failure || event_loop_status == :failed
267
239
  :failed
@@ -269,9 +241,6 @@ module Phronomy
269
241
  :terminated
270
242
  end
271
243
 
272
- @agent_ownership_registry.shutdown! if cleanup_complete
273
- @team_ownership_registry.shutdown! if cleanup_complete
274
-
275
244
  result = ShutdownResult.new(
276
245
  runtime_outcome: runtime_outcome,
277
246
  cleanup_status: cleanup_complete ? :complete : :incomplete,
@@ -290,6 +259,45 @@ module Phronomy
290
259
 
291
260
  private
292
261
 
262
+ # Called with the lifecycle lock held; participants only close their gates.
263
+ def begin_draining_participants
264
+ @shutdown_participants.each_value do |participant|
265
+ participant.begin_draining
266
+ rescue => error
267
+ record_participant_failure(error)
268
+ end
269
+ end
270
+
271
+ # All gates are already closed. Wait without holding the lifecycle lock and
272
+ # give every participant the same deadline, even if an earlier wait fails.
273
+ def wait_for_participants(participants, deadline)
274
+ participants.map do |participant|
275
+ participant.wait_until_idle(deadline) == true
276
+ rescue => error
277
+ @lifecycle_mutex.synchronize { record_participant_failure(error) }
278
+ false
279
+ end.all?
280
+ end
281
+
282
+ # Finalize every participant even if one fails. Earlier releases are not
283
+ # rolled back, and a failure prevents replacement of the default Runtime.
284
+ def finalize_participants(participants)
285
+ participants.map do |participant|
286
+ participant.after_runtime_shutdown if participant.respond_to?(:after_runtime_shutdown)
287
+ true
288
+ rescue => error
289
+ @lifecycle_mutex.synchronize { record_participant_failure(error) }
290
+ false
291
+ end.all?
292
+ end
293
+
294
+ # Called with the lifecycle lock held. Failed hooks make cleanup uncertain.
295
+ def record_participant_failure(error)
296
+ @shutdown_participant_error ||= error
297
+ @failure ||= error
298
+ @state = :failed
299
+ end
300
+
293
301
  def ensure_accepting_work!
294
302
  current_state = @lifecycle_mutex.synchronize { @state }
295
303
  return if %i[running draining].include?(current_state)
@@ -298,10 +306,11 @@ module Phronomy
298
306
  "Runtime is #{current_state}; new work is not accepted"
299
307
  end
300
308
 
301
- def shutdown_pools_and_timer
309
+ def shutdown_pools_and_timer(deadline:)
302
310
  error = nil
311
+ pools_stopped = false
303
312
  begin
304
- @pool_registry.shutdown
313
+ pools_stopped = @pool_registry.shutdown(deadline: deadline)
305
314
  rescue => caught
306
315
  error ||= caught
307
316
  ensure
@@ -311,7 +320,7 @@ module Phronomy
311
320
  error ||= caught
312
321
  end
313
322
  end
314
- error
323
+ [pools_stopped, error]
315
324
  end
316
325
 
317
326
  def validate_timeout!(value, name)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Mechanism-neutral completion/failure/retirement decision.
5
+ # @api private
6
+ TerminalDecision = Data.define(:action, :error)
7
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Completion adaptation for framework operations. Owns start-error capture,
5
+ # once-only propagation, error-to-value policy and physical completion tracking.
6
+ # Callers supply domain transformations, never completion setters or callbacks.
7
+ # @api private
8
+ module AsyncOperation
9
+ def self.map(source, name:, on_error: nil, &transform)
10
+ raise ArgumentError, "async mapping requires a block" unless transform
11
+ unless source.respond_to?(:on_complete)
12
+ raise TypeError, "asynchronous operation must return a completion handle"
13
+ end
14
+ Concurrency::ResultComposition.new(source, flatten: false, name: name,
15
+ completion_only: true, recover: on_error, &transform).start
16
+ end
17
+
18
+ # Capture synchronous preparation/start errors under the same domain policy.
19
+ def self.call(name:, on_error: nil, transform: ->(value) { value })
20
+ source = begin
21
+ yield
22
+ rescue => error
23
+ TaskResult.failed(error, name: name)
24
+ end
25
+ map(source, name: name, on_error: on_error, &transform)
26
+ end
27
+
28
+ # Short synchronous fallback/validation only; never offloads or awaits work.
29
+ def self.capture(name:, on_error: nil)
30
+ value = yield
31
+ TaskResult.completed(value, name: name)
32
+ rescue => error
33
+ begin
34
+ raise error unless on_error
35
+ TaskResult.completed(on_error.call(error), name: name)
36
+ rescue => failure
37
+ TaskResult.failed(failure, name: name)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ class BackpressureError < Error; end
7
+ end