phronomy 0.27.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 (475) 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 +9 -3
  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/architecture/agent-chat-and-state-ownership.md +7 -1
  11. data/docs/architecture/agent-transition-ownership.md +13 -89
  12. data/docs/architecture/backend-groups.md +152 -0
  13. data/docs/architecture/before-llm-input.md +3 -3
  14. data/docs/architecture/context-management.md +1 -1
  15. data/docs/architecture/execution-abstraction-review.md +88 -0
  16. data/docs/architecture/execution-boundaries.md +28 -0
  17. data/docs/architecture/execution-metadata-and-values.md +6 -0
  18. data/docs/architecture/multi-agent-handoff.md +6 -5
  19. data/docs/architecture/persistence.md +27 -9
  20. data/docs/architecture/r8-unit1.md +104 -0
  21. data/docs/architecture/r8-unit10.md +72 -0
  22. data/docs/architecture/r8-unit11.md +71 -0
  23. data/docs/architecture/r8-unit12.md +67 -0
  24. data/docs/architecture/r8-unit13.md +61 -0
  25. data/docs/architecture/r8-unit14.md +72 -0
  26. data/docs/architecture/r8-unit15.md +65 -0
  27. data/docs/architecture/r8-unit16.md +54 -0
  28. data/docs/architecture/r8-unit17.md +73 -0
  29. data/docs/architecture/r8-unit18.md +92 -0
  30. data/docs/architecture/r8-unit19.md +61 -0
  31. data/docs/architecture/r8-unit2.md +55 -0
  32. data/docs/architecture/r8-unit20.md +51 -0
  33. data/docs/architecture/r8-unit21.md +79 -0
  34. data/docs/architecture/r8-unit22.md +74 -0
  35. data/docs/architecture/r8-unit23.md +81 -0
  36. data/docs/architecture/r8-unit3.md +130 -0
  37. data/docs/architecture/r8-unit4.md +79 -0
  38. data/docs/architecture/r8-unit5.md +115 -0
  39. data/docs/architecture/r8-unit6.md +71 -0
  40. data/docs/architecture/r8-unit7.md +70 -0
  41. data/docs/architecture/r8-unit8.md +78 -0
  42. data/docs/architecture/r8-unit9.md +73 -0
  43. data/docs/architecture/refactoring-closure.md +10 -1
  44. data/docs/architecture/remaining-refactoring-plan.md +16 -0
  45. data/docs/architecture/storage-execution-boundaries.md +54 -0
  46. data/docs/architecture/tool-schema-recording-gap.md +6 -0
  47. data/docs/architecture/tracing.md +1 -1
  48. data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
  49. data/docs/architecture.md +24 -11
  50. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  51. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  52. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
  53. data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
  54. data/docs/decisions/032-storage-backend-composition.md +5 -0
  55. data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
  56. data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
  57. data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
  58. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
  59. data/docs/decisions/040-configuration-default-composition.md +12 -1
  60. data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
  61. data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
  62. data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
  63. data/docs/decisions/058-neutral-storage-primitives.md +5 -0
  64. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  65. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  66. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  67. data/docs/decisions/062-content-store-service.md +52 -0
  68. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  69. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  70. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  71. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  72. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  73. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  74. data/docs/decisions/README.md +18 -1
  75. data/docs/execution-boundaries-preview.md +45 -0
  76. data/docs/features.md +8 -3
  77. data/docs/getting-started.md +9 -6
  78. data/docs/migrations/0.22.md +2 -2
  79. data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
  80. data/docs/migrations/handoff-runner-multi-agent.md +2 -0
  81. data/docs/migrations/persistence-failure-contracts.md +67 -0
  82. data/docs/migrations/r8-unit10.md +36 -0
  83. data/docs/migrations/r8-unit11.md +33 -0
  84. data/docs/migrations/r8-unit12.md +31 -0
  85. data/docs/migrations/r8-unit13.md +36 -0
  86. data/docs/migrations/r8-unit14.md +31 -0
  87. data/docs/migrations/r8-unit15.md +31 -0
  88. data/docs/migrations/r8-unit16.md +26 -0
  89. data/docs/migrations/r8-unit17.md +28 -0
  90. data/docs/migrations/r8-unit18.md +29 -0
  91. data/docs/migrations/r8-unit19.md +28 -0
  92. data/docs/migrations/r8-unit20.md +24 -0
  93. data/docs/migrations/r8-unit21.md +28 -0
  94. data/docs/migrations/r8-unit22.md +29 -0
  95. data/docs/migrations/r8-unit23.md +20 -0
  96. data/docs/migrations/r8-unit4.md +67 -0
  97. data/docs/migrations/r8-unit5.md +75 -0
  98. data/docs/migrations/r8-unit6.md +109 -0
  99. data/docs/migrations/r8-unit7.md +31 -0
  100. data/docs/migrations/r8-unit8.md +33 -0
  101. data/docs/migrations/r8-unit9.md +28 -0
  102. data/docs/migrations/storage-async-client.md +103 -0
  103. data/docs/migrations/vector-async-clients.md +110 -0
  104. data/docs/persistence-backends.md +38 -21
  105. data/docs/runtime-and-concurrency.md +26 -1
  106. data/lib/phronomy/agent/admission.rb +78 -0
  107. data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
  108. data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
  109. data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
  110. data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
  111. data/lib/phronomy/agent/async_event_api.rb +37 -32
  112. data/lib/phronomy/agent/base.rb +74 -72
  113. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  114. data/lib/phronomy/agent/composition/run_once.rb +1 -1
  115. data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
  116. data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
  117. data/lib/phronomy/agent/control_request.rb +21 -0
  118. data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
  119. data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
  120. data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
  121. data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
  122. data/lib/phronomy/agent/execution/exact_execution.rb +44 -23
  123. data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
  124. data/lib/phronomy/agent/execution/execution_coordinator.rb +214 -395
  125. data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
  126. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +70 -126
  127. data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
  128. data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
  129. data/lib/phronomy/agent/execution/initial_preparation.rb +18 -116
  130. data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
  131. data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
  132. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +7 -7
  133. data/lib/phronomy/agent/execution_change.rb +161 -0
  134. data/lib/phronomy/agent/execution_environment.rb +36 -0
  135. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  136. data/lib/phronomy/agent/execution_observation.rb +35 -0
  137. data/lib/phronomy/agent/filterable.rb +182 -0
  138. data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
  139. data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
  140. data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
  141. data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
  142. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  143. data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
  144. data/lib/phronomy/agent/lifecycle/ownership_registry.rb +18 -23
  145. data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
  146. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  147. data/lib/phronomy/agent/persistence/codec.rb +1 -21
  148. data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
  149. data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
  150. data/lib/phronomy/agent/persistence/records.rb +33 -0
  151. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  152. data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
  153. data/lib/phronomy/agent/persistence/watermark.rb +13 -11
  154. data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
  155. data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +10 -9
  156. data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +27 -27
  157. data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +19 -21
  158. data/lib/phronomy/agent/recovery/recovery_coordinator.rb +23 -16
  159. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  160. data/lib/phronomy/agent/recovery/recovery_support.rb +8 -11
  161. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  162. data/lib/phronomy/agent/retention.rb +17 -0
  163. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  164. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  165. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  166. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  167. data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
  168. data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
  169. data/lib/phronomy/agent/settings.rb +47 -0
  170. data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
  171. data/lib/phronomy/agent/store.rb +253 -0
  172. data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +1 -1
  173. data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
  174. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
  175. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  176. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  177. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  178. data/lib/phronomy/content_store/storage_schema.rb +1 -0
  179. data/lib/phronomy/content_store/stored_contents.rb +2 -1
  180. data/lib/phronomy/context/assembly.rb +202 -0
  181. data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
  182. data/lib/phronomy/context/candidate.rb +51 -0
  183. data/lib/phronomy/context/constraint.rb +47 -0
  184. data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
  185. data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
  186. data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
  187. data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
  188. data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
  189. data/lib/phronomy/context/default_policy.rb +140 -0
  190. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  191. data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
  192. data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
  193. data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
  194. data/lib/phronomy/context/manifest_error.rb +10 -0
  195. data/lib/phronomy/context/prompt_template.rb +98 -0
  196. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  197. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  198. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  199. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  200. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  201. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  202. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  203. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  204. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  205. data/lib/phronomy/embeddings/base.rb +43 -0
  206. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  207. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  208. data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
  209. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  210. data/lib/phronomy/engine/diagnostics.rb +1 -1
  211. data/lib/phronomy/engine/event_loop.rb +7 -7
  212. data/lib/phronomy/engine/execution_binding.rb +23 -0
  213. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  214. data/lib/phronomy/engine/fsm_protocol.rb +1 -6
  215. data/lib/phronomy/engine/fsm_session.rb +3 -3
  216. data/lib/phronomy/engine/metrics.rb +16 -14
  217. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  218. data/lib/phronomy/engine/runtime.rb +25 -12
  219. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  220. data/lib/phronomy/execution/async_operation.rb +41 -0
  221. data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
  222. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
  223. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  224. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  225. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  226. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  227. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  228. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  229. data/lib/phronomy/{engine → execution}/execution.rb +61 -27
  230. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  231. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  232. data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
  233. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  234. data/lib/phronomy/execution/runnable.rb +24 -0
  235. data/lib/phronomy/{engine → execution}/task_result.rb +3 -3
  236. data/lib/phronomy/execution/wait_policy.rb +24 -0
  237. data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +1 -1
  238. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  239. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  240. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
  241. data/lib/phronomy/generation/generator_verifier.rb +7 -7
  242. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  243. data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
  244. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  245. data/lib/phronomy/llm_adapter/base.rb +49 -68
  246. data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
  247. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  248. data/lib/phronomy/llm_adapter/message.rb +62 -0
  249. data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
  250. data/lib/phronomy/llm_adapter/request.rb +79 -0
  251. data/lib/phronomy/llm_adapter/response.rb +36 -0
  252. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  253. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  254. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  255. data/lib/phronomy/llm_adapter.rb +3 -16
  256. data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
  257. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  258. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  259. data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
  260. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
  261. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  262. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
  263. data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
  264. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
  265. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  266. data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
  267. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  268. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  269. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  270. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +58 -42
  271. data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
  272. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  273. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  274. data/lib/phronomy/multi_agent/shared_state.rb +2 -2
  275. data/lib/phronomy/multi_agent/store.rb +156 -0
  276. data/lib/phronomy/multi_agent/team_coordinator.rb +83 -74
  277. data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
  278. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  279. data/lib/phronomy/persistence/content_repository.rb +26 -0
  280. data/lib/phronomy/persistence/errors.rb +16 -0
  281. data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
  282. data/lib/phronomy/persistence/persistence.rb +23 -0
  283. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  284. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  285. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  286. data/lib/phronomy/persistence/transaction.rb +86 -0
  287. data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
  288. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  289. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  290. data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
  291. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  292. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  293. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  294. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  295. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  296. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  297. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  298. data/lib/phronomy/storage/async/async_client.rb +60 -0
  299. data/lib/phronomy/storage/backend.rb +8 -2
  300. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  301. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  302. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
  303. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
  304. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
  305. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
  306. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
  307. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
  308. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
  309. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
  310. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
  311. data/lib/phronomy/tool/authorization.rb +77 -0
  312. data/lib/phronomy/tool/base.rb +549 -9
  313. data/lib/phronomy/tool/call_request.rb +40 -0
  314. data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
  315. data/lib/phronomy/tool/operation.rb +88 -0
  316. data/lib/phronomy/tool/schema.rb +214 -0
  317. data/lib/phronomy/tool/settings.rb +31 -0
  318. data/lib/phronomy/tool/tool_error.rb +7 -0
  319. data/lib/phronomy/tool/tool_executor.rb +58 -0
  320. data/lib/phronomy/tools/agent.rb +22 -90
  321. data/lib/phronomy/tools/mcp.rb +21 -14
  322. data/lib/phronomy/tools/vector_search.rb +4 -4
  323. data/lib/phronomy/tracing/automatic.rb +11 -6
  324. data/lib/phronomy/tracing/base.rb +1 -1
  325. data/lib/phronomy/tracing/observation.rb +37 -0
  326. data/lib/phronomy/tracing/settings.rb +36 -0
  327. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  328. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  329. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  330. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  331. data/lib/phronomy/vector_store/base.rb +118 -49
  332. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  333. data/lib/phronomy/vector_store.rb +2 -17
  334. data/lib/phronomy/version.rb +1 -1
  335. data/lib/phronomy/workflow/completion.rb +11 -0
  336. data/lib/phronomy/workflow/execution/workflow.rb +7 -7
  337. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  338. data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
  339. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  340. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +3 -5
  341. data/lib/phronomy/workflow/execution/workflow_runner.rb +58 -79
  342. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  343. data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
  344. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  345. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  346. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  347. data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
  348. data/lib/phronomy.rb +57 -16
  349. data/scripts/api_snapshot.rb +35 -15
  350. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  351. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  352. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  353. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  354. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  355. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  356. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  357. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  358. data/sig/phronomy/agent/admission.rbs +19 -0
  359. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  360. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  361. data/sig/phronomy/agent/store.rbs +73 -0
  362. data/sig/phronomy/agent.rbs +14 -138
  363. data/sig/phronomy/common.rbs +7 -0
  364. data/sig/phronomy/content_store/contracts.rbs +13 -0
  365. data/sig/phronomy/context.rbs +152 -0
  366. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  367. data/sig/phronomy/embeddings/base.rbs +17 -0
  368. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  369. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  370. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  371. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  372. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  373. data/sig/phronomy/engine/observability.rbs +13 -0
  374. data/sig/phronomy/execution/async_operation.rbs +7 -0
  375. data/sig/phronomy/execution/blocking.rbs +5 -0
  376. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  377. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  378. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  379. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  380. data/sig/phronomy/execution/errors.rbs +7 -0
  381. data/sig/phronomy/execution/execution.rbs +41 -0
  382. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  383. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  384. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  385. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  386. data/sig/phronomy/execution/task_result.rbs +29 -0
  387. data/sig/phronomy/extensions.rbs +0 -21
  388. data/sig/phronomy/handoff.rbs +10 -4
  389. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  390. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  391. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  392. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  393. data/sig/phronomy/llm_context_window.rbs +10 -0
  394. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  395. data/sig/phronomy/multi_agent/store.rbs +54 -0
  396. data/sig/phronomy/multi_agent.rbs +6 -3
  397. data/sig/phronomy/persistence/errors.rbs +18 -0
  398. data/sig/phronomy/persistence/migration.rbs +13 -0
  399. data/sig/phronomy/persistence/transaction.rbs +19 -0
  400. data/sig/phronomy/persistence.rbs +1 -99
  401. data/sig/phronomy/persistence_composition.rbs +19 -0
  402. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  403. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  404. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  405. data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +2 -16
  406. data/sig/phronomy/tool.rbs +48 -4
  407. data/sig/phronomy/tracing/observation.rbs +7 -0
  408. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  409. data/sig/phronomy/vector_store/base.rbs +23 -0
  410. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  411. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  412. data/sig/phronomy/workflow/store.rbs +8 -0
  413. data/sig/phronomy/workflow.rbs +1 -1
  414. data/sig/phronomy.rbs +0 -49
  415. metadata +318 -117
  416. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  417. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  418. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  419. data/lib/phronomy/agent/context/capability/base.rb +0 -548
  420. data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
  421. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  422. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
  423. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
  424. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  425. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  426. data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
  427. data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
  428. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
  429. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
  430. data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
  431. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
  432. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
  433. data/lib/phronomy/agent/persistence/queries.rb +0 -61
  434. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  435. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  436. data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
  437. data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
  438. data/lib/phronomy/configuration/configuration.rb +0 -68
  439. data/lib/phronomy/configuration/global_configuration.rb +0 -26
  440. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  441. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  442. data/lib/phronomy/engine/execution_receiver.rb +0 -65
  443. data/lib/phronomy/engine/runnable.rb +0 -54
  444. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  445. data/lib/phronomy/llm_contract/token_usage.rb +0 -85
  446. data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
  447. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
  448. data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
  449. data/lib/phronomy/persistence/api/persistence.rb +0 -140
  450. data/lib/phronomy/persistence_composition/repositories.rb +0 -77
  451. data/lib/phronomy/recovery/recovery.rb +0 -186
  452. data/lib/phronomy/tool/contract/tool_error.rb +0 -7
  453. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  454. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  455. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  456. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
  457. data/sig/phronomy/execution_receiver.rbs +0 -34
  458. data/sig/phronomy/llm_adapter.rbs +0 -11
  459. data/sig/phronomy/runtime.rbs +0 -76
  460. /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
  461. /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
  462. /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
  463. /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
  464. /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
  465. /data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +0 -0
  466. /data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +0 -0
  467. /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
  468. /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
  469. /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
  470. /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
  471. /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
  472. /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
  473. /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
  474. /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
  475. /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
@@ -16,6 +16,7 @@ module Phronomy
16
16
  @mutex = Mutex.new
17
17
  @pools = {}
18
18
  @default = nil
19
+ @closed = false
19
20
  end
20
21
 
21
22
  # Returns (or lazily creates) the unnamed default pool.
@@ -24,12 +25,22 @@ module Phronomy
24
25
  # @return [OffloadPool]
25
26
  # @api private
26
27
  def default_pool(pool_size: 10, queue_size: 100)
27
- @default ||= OffloadPool.new(
28
- name: :default,
29
- pool_size: pool_size,
30
- queue_size: queue_size,
31
- timer_queue_provider: @timer_queue_provider
32
- )
28
+ @mutex.synchronize do
29
+ ensure_open!
30
+ @default ||= OffloadPool.new(
31
+ name: :default,
32
+ pool_size: pool_size,
33
+ queue_size: queue_size,
34
+ timer_queue_provider: @timer_queue_provider
35
+ )
36
+ end
37
+ end
38
+
39
+ # Lookup only, including after registration has closed.
40
+ # @return [OffloadPool, nil]
41
+ # @api private
42
+ def default_pool_if_initialized
43
+ @mutex.synchronize { @default }
33
44
  end
34
45
 
35
46
  # Returns (or lazily creates) a named pool.
@@ -40,6 +51,7 @@ module Phronomy
40
51
  # @api private
41
52
  def named_pool(name, size: 10, queue_size: 100)
42
53
  @mutex.synchronize do
54
+ ensure_open!
43
55
  @pools[name.to_sym] ||= OffloadPool.new(
44
56
  name: name,
45
57
  pool_size: size,
@@ -49,13 +61,36 @@ module Phronomy
49
61
  end
50
62
  end
51
63
 
52
- # Shuts down the default pool and all named pools.
53
- # @return [void]
64
+ # Closes registration and every pool before waiting, outside the registry
65
+ # lock. All pools share one absolute monotonic deadline. Attempt every
66
+ # cleanup even if one fails; report physical completion separately.
67
+ # @return [Boolean] whether every owned worker has stopped
54
68
  # @api private
55
- def shutdown
56
- @default&.shutdown
57
- pools = @mutex.synchronize { @pools.values.dup }
58
- pools.each(&:shutdown)
69
+ def shutdown(deadline: Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30)
70
+ pools = @mutex.synchronize do
71
+ @closed = true
72
+ [@default, *@pools.values].compact
73
+ end
74
+ error = nil
75
+ pools.each do |pool|
76
+ pool.begin_shutdown
77
+ rescue => caught
78
+ error ||= caught
79
+ end
80
+ pools.each do |pool|
81
+ pool.shutdown(deadline: deadline)
82
+ rescue => caught
83
+ error ||= caught
84
+ end
85
+ raise error if error
86
+
87
+ pools.all?(&:terminated?)
88
+ end
89
+
90
+ private
91
+
92
+ def ensure_open!
93
+ raise Phronomy::PoolShutdownError, "pool registry has been shut down" if @closed
59
94
  end
60
95
  end
61
96
  end
@@ -24,7 +24,7 @@ module Phronomy
24
24
  end
25
25
 
26
26
  def self.assert_not_in_event_loop!
27
- return unless Phronomy::Runtime.in_event_loop_context?
27
+ return unless Phronomy::WaitPolicy.blocking_forbidden?
28
28
 
29
29
  raise Phronomy::EventLoopReentrancyError,
30
30
  "Blocking invoke called from inside an EventLoop action. Use invoke_async instead."
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "concurrency/worker_input_restricted"
3
+ require_relative "../execution/concurrency/worker_input_restricted"
4
4
 
5
5
  module Phronomy
6
6
  # Runtime-owned FIFO event loop for FSMSession instances.
@@ -53,7 +53,7 @@ module Phronomy
53
53
  @dispatch_count = 0
54
54
  @total_lag_ns = 0
55
55
 
56
- @thread = Thread.new { run_loop }
56
+ @thread = Thread.new { Phronomy::WaitPolicy.without_blocking { run_loop } }
57
57
  @thread.name = "phronomy-event-loop"
58
58
  end
59
59
 
@@ -632,7 +632,7 @@ module Phronomy
632
632
  end
633
633
 
634
634
  def warn_queue_backlog(message)
635
- logger = Phronomy.configuration.logger
635
+ logger = Phronomy::RuntimeSettings.current.logger
636
636
  logger ? logger.warn(message) : Kernel.warn(message)
637
637
  rescue
638
638
  nil
@@ -648,11 +648,11 @@ module Phronomy
648
648
  end
649
649
 
650
650
  def check_starvation_lag(lag_ns, event)
651
- threshold = Phronomy.configuration.event_loop_starvation_threshold_seconds
651
+ threshold = Phronomy::RuntimeSettings.current.event_loop_starvation_threshold_seconds
652
652
  return unless threshold
653
653
  return unless lag_ns > (threshold * 1_000_000_000)
654
654
 
655
- Phronomy.configuration.logger&.warn do
655
+ Phronomy::RuntimeSettings.current.logger&.warn do
656
656
  "[Phronomy::EventLoop] Starvation detected: event #{event.type.inspect} " \
657
657
  "for target #{event.target_id.inspect} waited " \
658
658
  "#{format("%.3f", lag_ns / 1_000_000_000.0)}s in queue " \
@@ -661,13 +661,13 @@ module Phronomy
661
661
  end
662
662
 
663
663
  def check_dispatch_time(dispatch_start_ns, event)
664
- threshold = Phronomy.configuration.event_loop_dispatch_threshold_seconds
664
+ threshold = Phronomy::RuntimeSettings.current.event_loop_dispatch_threshold_seconds
665
665
  return unless threshold
666
666
 
667
667
  elapsed_ns = monotonic_nanoseconds - dispatch_start_ns
668
668
  return unless elapsed_ns > (threshold * 1_000_000_000)
669
669
 
670
- Phronomy.configuration.logger&.warn do
670
+ Phronomy::RuntimeSettings.current.logger&.warn do
671
671
  "[Phronomy::EventLoop] Long dispatch: event #{event.type.inspect} " \
672
672
  "for target #{event.target_id.inspect} took " \
673
673
  "#{format("%.3f", elapsed_ns / 1_000_000_000.0)}s on the EventLoop thread " \
@@ -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
@@ -1,14 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- # Shared terminal-state vocabulary and domain decisions for FSM execution.
5
- # This protocol does not depend on a particular runner or session instance.
4
+ # Internal terminal vocabulary shared only by FSM adapters and sessions.
6
5
  # @api private
7
6
  module FSMProtocol
8
7
  FINISH = :__end__
9
-
10
- # A domain policy permits completion, reports failure, or retires a session
11
- # without settling its caller. It never carries live session authority.
12
- TerminalDecision = Data.define(:action, :error)
13
8
  end
14
9
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "concurrency/worker_input_restricted"
3
+ require_relative "../execution/concurrency/worker_input_restricted"
4
4
 
5
5
  require "securerandom"
6
6
 
@@ -407,7 +407,7 @@ module Phronomy
407
407
  )
408
408
  return if accepted
409
409
 
410
- Phronomy.configuration.logger&.warn(
410
+ Phronomy::RuntimeSettings.current.logger&.warn(
411
411
  "[Phronomy::FSMSession] EventLoop rejected recovery-required event for #{@id}"
412
412
  )
413
413
  end
@@ -422,7 +422,7 @@ module Phronomy
422
422
  )
423
423
  return if accepted
424
424
 
425
- Phronomy.configuration.logger&.warn(
425
+ Phronomy::RuntimeSettings.current.logger&.warn(
426
426
  "[Phronomy::FSMSession] EventLoop rejected terminal event " \
427
427
  "#{type.inspect} for #{@id}"
428
428
  )
@@ -2,24 +2,26 @@
2
2
 
3
3
  module Phronomy
4
4
  # Runtime observability snapshot for the two concurrency boundaries:
5
- # EventLoop and OffloadPool.
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.
6
8
  module Metrics
7
9
  def self.snapshot
8
- runtime = Runtime.instance
9
- pool = runtime.offload
10
- event_loop = runtime.event_loop
10
+ runtime = Runtime.__default_if_initialized
11
+ pool = runtime&.__offload_if_initialized
12
+ event_loop = runtime&.__event_loop_if_initialized
11
13
 
12
14
  {
13
- offload_pool_active: pool.active_count,
14
- offload_pool_queue_length: pool.queue_depth,
15
- offload_pool_abandoned_active: pool.abandoned_active_count,
16
- offload_pool_abandoned_total: pool.abandoned_count,
17
- offload_pool_size: pool.pool_size,
18
- event_loop_queue_depth: event_loop.queue_depth,
19
- event_loop_queue_max_depth: event_loop.max_queue_depth,
20
- event_loop_lag_last_ms: (event_loop.last_lag_seconds * 1000).round(3),
21
- event_loop_lag_max_ms: (event_loop.max_lag_seconds * 1000).round(3),
22
- event_loop_lag_average_ms: (event_loop.average_lag_seconds * 1000).round(3)
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)
23
25
  }
24
26
  end
25
27
  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,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "concurrency/worker_input_restricted"
3
+ require_relative "../execution/concurrency/worker_input_restricted"
4
4
 
5
5
  require_relative "runtime/timer_queue"
6
6
  require_relative "runtime/shutdown_result"
@@ -17,10 +17,16 @@ module Phronomy
17
17
  instance_mutex.synchronize { @instance ||= new }
18
18
  end
19
19
 
20
- def default_if_initialized_for_test
20
+ # Lookup only: diagnostics must not initialize the default Runtime.
21
+ # @api private
22
+ def __default_if_initialized
21
23
  instance_mutex.synchronize { @instance }
22
24
  end
23
25
 
26
+ def default_if_initialized_for_test
27
+ __default_if_initialized
28
+ end
29
+
24
30
  def replace_default_for_test(runtime)
25
31
  instance_mutex.synchronize do
26
32
  previous = @instance
@@ -33,7 +39,7 @@ module Phronomy
33
39
  instance_mutex.synchronize { @instance = runtime }
34
40
  end
35
41
 
36
- def reset_default!(timeout: Phronomy.configuration.event_loop_stop_grace_seconds)
42
+ def reset_default!(timeout: Phronomy::RuntimeSettings.current.event_loop_stop_grace_seconds)
37
43
  runtime = instance_mutex.synchronize { @instance }
38
44
  return ShutdownResult.not_started unless runtime
39
45
 
@@ -50,8 +56,7 @@ module Phronomy
50
56
  end
51
57
 
52
58
  def in_event_loop_context?
53
- runtime = instance_mutex.synchronize { @instance }
54
- runtime&.event_loop_current? || false
59
+ Phronomy::WaitPolicy.blocking_forbidden?
55
60
  end
56
61
 
57
62
  private
@@ -81,8 +86,8 @@ module Phronomy
81
86
  end
82
87
 
83
88
  def offload(
84
- pool_size: Phronomy.configuration.offload_pool_size,
85
- 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
86
91
  )
87
92
  ensure_accepting_work!
88
93
  @pool_registry.default_pool(pool_size: pool_size, queue_size: queue_size)
@@ -93,6 +98,12 @@ module Phronomy
93
98
  @pool_registry.named_pool(name, size: size, queue_size: queue_size)
94
99
  end
95
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
+
96
107
  def timer_queue
97
108
  ensure_accepting_work!
98
109
  timer = @timer_service.timer_queue
@@ -177,7 +188,7 @@ module Phronomy
177
188
  end
178
189
 
179
190
  def shutdown(
180
- timeout: Phronomy.configuration.event_loop_stop_grace_seconds,
191
+ timeout: Phronomy::RuntimeSettings.current.event_loop_stop_grace_seconds,
181
192
  cancel_grace: timeout
182
193
  )
183
194
  if event_loop_current?
@@ -212,13 +223,14 @@ module Phronomy
212
223
  :not_started
213
224
  end
214
225
 
215
- subsystem_error = shutdown_pools_and_timer
226
+ pools_stopped, subsystem_error = shutdown_pools_and_timer(deadline: stop_deadline)
216
227
  participant_error = @lifecycle_mutex.synchronize { @shutdown_participant_error }
217
228
  cleanup_complete = participants_idle && participant_error.nil? &&
218
229
  loop_idle &&
219
230
  (!loop_instance || !loop_instance.thread_alive?) &&
220
231
  event_loop_status != :cancel_timeout &&
221
232
  (!loop_instance || loop_instance.__receiver_cleanup_complete?) &&
233
+ pools_stopped &&
222
234
  subsystem_error.nil?
223
235
 
224
236
  cleanup_complete = finalize_participants(participants) if cleanup_complete
@@ -294,10 +306,11 @@ module Phronomy
294
306
  "Runtime is #{current_state}; new work is not accepted"
295
307
  end
296
308
 
297
- def shutdown_pools_and_timer
309
+ def shutdown_pools_and_timer(deadline:)
298
310
  error = nil
311
+ pools_stopped = false
299
312
  begin
300
- @pool_registry.shutdown
313
+ pools_stopped = @pool_registry.shutdown(deadline: deadline)
301
314
  rescue => caught
302
315
  error ||= caught
303
316
  ensure
@@ -307,7 +320,7 @@ module Phronomy
307
320
  error ||= caught
308
321
  end
309
322
  end
310
- error
323
+ [pools_stopped, error]
311
324
  end
312
325
 
313
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
@@ -22,10 +22,10 @@ module Phronomy
22
22
 
23
23
  begin
24
24
  unless invocation_context.nil?
25
- binding = Concurrency::OperationBinding.new(invocation_context: invocation_context,
25
+ binding = InvocationControls.new(invocation_context: invocation_context,
26
26
  cancellation_token: cancellation_token)
27
27
  end
28
- result = Phronomy::Runtime.instance.offload.submit(
28
+ result = Phronomy::Execution.submit(
29
29
  on_full: :raise,
30
30
  timeout: timeout,
31
31
  cancellation_token: binding ? binding.token : cancellation_token,
@@ -98,7 +98,7 @@ module Phronomy
98
98
  def deliver_cancel_callback(callback)
99
99
  callback.call
100
100
  rescue => error
101
- Phronomy.configuration.logger&.error do
101
+ Phronomy::RuntimeSettings.current.logger&.error do
102
102
  "[CancellationToken] on_cancel callback raised #{error.class}: #{error.message}"
103
103
  end
104
104
  end
@@ -42,23 +42,6 @@ module Phronomy
42
42
  remaining = @monotonic_at - Process.clock_gettime(Process::CLOCK_MONOTONIC)
43
43
  [remaining, 0.0].max
44
44
  end
45
-
46
- # Attaches this deadline to a {CancellationToken} by cancelling the token
47
- # when the deadline expires. The Runtime timer queue is driven by EventLoop;
48
- # no timer-specific OS thread is created.
49
- #
50
- # @param token [CancellationToken]
51
- # @param timer_queue [Runtime::TimerQueue, nil] queue to register with;
52
- # defaults to +Phronomy::Runtime.instance.timer_queue+
53
- # @return [self]
54
- # @api private
55
- def attach_to(token, timer_queue: Phronomy::Runtime.instance.timer_queue)
56
- seconds = remaining_seconds
57
- return self if seconds <= 0
58
-
59
- timer_queue.schedule(seconds: seconds) { token.cancel! }
60
- self
61
- end
62
45
  end
63
46
  end
64
47
  end
@@ -68,7 +68,7 @@ module Phronomy
68
68
  def deliver_physical_callback(callback)
69
69
  callback.call
70
70
  rescue => error
71
- Phronomy.configuration.logger&.error do
71
+ Phronomy::RuntimeSettings.current.logger&.error do
72
72
  "[PhysicalCompletionTask] callback raised #{error.class}: #{error.message}"
73
73
  end
74
74
  end
@@ -12,7 +12,7 @@ module Phronomy
12
12
  @remaining = size
13
13
  @closed = false
14
14
  @sources = {}.compare_by_identity
15
- @subscriptions = Subscriptions.new
15
+ @subscriptions = ResultSubscriptions.new
16
16
  @finished = finished
17
17
  end
18
18
 
@@ -6,21 +6,26 @@ module Phronomy
6
6
  # boundary. Observing a result never mutates that result or its cancellation.
7
7
  # @api private
8
8
  class ResultComposition
9
- def initialize(source, flatten:, &block)
9
+ def initialize(source, flatten:, name: nil, completion_only: false, recover: nil, &block)
10
10
  @source = source
11
- @scope = source.__execution_scope
11
+ # Framework decorators also accept callback-only handles. They observe
12
+ # the source without inheriting application Execution controls or parents.
13
+ @completion_only = completion_only
14
+ @scope = completion_only ? nil : source.__execution_scope
12
15
  @flatten = flatten
13
16
  @block = block
14
- physical = flatten || source.respond_to?(:on_physical_complete)
17
+ @recover = recover
18
+ physical = completion_only || flatten || source.respond_to?(:on_physical_complete)
15
19
  klass = physical ? PhysicalCompletionTask : TaskResult
16
- @result = klass.deferred(name: "#{source.name}-#{flatten ? "flat-mapped" : "mapped"}",
17
- parent: source.parent).__bind_execution(@scope)
20
+ result_name = name || "#{source.name}-#{flatten ? "flat-mapped" : "mapped"}"
21
+ @result = klass.deferred(name: result_name,
22
+ parent: completion_only ? nil : source.parent).__bind_execution(@scope)
18
23
  @mutex = Mutex.new
19
24
  @phase = :waiting
20
25
  @source_physical = !source.respond_to?(:on_physical_complete)
21
26
  @work_done = false
22
27
  @inner_physical = true
23
- @subscriptions = Subscriptions.new
28
+ @subscriptions = ResultSubscriptions.new
24
29
  end
25
30
 
26
31
  def start
@@ -30,7 +35,18 @@ module Phronomy
30
35
  finish_physical
31
36
  end
32
37
  end
33
- @subscriptions.result(@source) { receive_source }
38
+ if @completion_only
39
+ @source.on_complete do |value, error|
40
+ status = if error
41
+ (@source.respond_to?(:status) && @source.status == :cancelled) ? :cancelled : :failed
42
+ else
43
+ :completed
44
+ end
45
+ receive_source(status, value, error)
46
+ end
47
+ else
48
+ @subscriptions.result(@source) { receive_source(*@source.__snapshot) }
49
+ end
34
50
  if @scope
35
51
  @subscriptions.cancellation(@scope.__cancellation_token) { suppress }
36
52
  suppress unless @scope.__open?
@@ -40,9 +56,9 @@ module Phronomy
40
56
 
41
57
  private
42
58
 
43
- def receive_source
44
- status, value, error = @source.__snapshot
45
- if status != :completed
59
+ def receive_source(status, value, error)
60
+ recovering = status != :completed
61
+ if recovering && !@recover
46
62
  started = @mutex.synchronize do
47
63
  next false unless @phase == :waiting
48
64
  @phase = :finished
@@ -66,12 +82,17 @@ module Phronomy
66
82
  end
67
83
  allowed = @scope ? @scope.__while_open(&claim) : claim.call
68
84
  unless allowed
69
- suppress
85
+ suppress if @scope
70
86
  return
71
87
  end
72
88
 
73
89
  begin
74
- transformed = @block.call(value)
90
+ transformed = begin
91
+ recovering ? @recover.call(error) : @block.call(value)
92
+ rescue => transformation_error
93
+ raise if recovering || !@recover
94
+ @recover.call(transformation_error)
95
+ end
75
96
  if @flatten && !transformed.is_a?(TaskResult)
76
97
  raise TypeError, "flat_map block must return a Phronomy::TaskResult"
77
98
  end