phronomy 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (517) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +52 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +28 -7
  6. data/benchmark/bench_agent_invoke.rb +25 -50
  7. data/benchmark/bench_context_assembler.rb +14 -11
  8. data/benchmark/bench_regression.rb +1 -1
  9. data/benchmark/bench_tool_schema.rb +1 -1
  10. data/docs/application-recipes.md +229 -0
  11. data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
  12. data/docs/architecture/agent-transition-ownership.md +13 -89
  13. data/docs/architecture/backend-groups.md +152 -0
  14. data/docs/architecture/before-llm-input.md +3 -3
  15. data/docs/architecture/context-management.md +1 -1
  16. data/docs/architecture/dx05-agent-scope.md +119 -0
  17. data/docs/architecture/dx05-workflow-task-state.md +134 -0
  18. data/docs/architecture/error-api-review.md +245 -0
  19. data/docs/architecture/execution-abstraction-review.md +88 -0
  20. data/docs/architecture/execution-boundaries.md +28 -0
  21. data/docs/architecture/execution-metadata-and-values.md +6 -0
  22. data/docs/architecture/multi-agent-handoff.md +6 -5
  23. data/docs/architecture/persistence.md +27 -9
  24. data/docs/architecture/r8-unit1.md +104 -0
  25. data/docs/architecture/r8-unit10.md +72 -0
  26. data/docs/architecture/r8-unit11.md +71 -0
  27. data/docs/architecture/r8-unit12.md +67 -0
  28. data/docs/architecture/r8-unit13.md +61 -0
  29. data/docs/architecture/r8-unit14.md +72 -0
  30. data/docs/architecture/r8-unit15.md +65 -0
  31. data/docs/architecture/r8-unit16.md +54 -0
  32. data/docs/architecture/r8-unit17.md +73 -0
  33. data/docs/architecture/r8-unit18.md +92 -0
  34. data/docs/architecture/r8-unit19.md +61 -0
  35. data/docs/architecture/r8-unit2.md +55 -0
  36. data/docs/architecture/r8-unit20.md +51 -0
  37. data/docs/architecture/r8-unit21.md +79 -0
  38. data/docs/architecture/r8-unit22.md +74 -0
  39. data/docs/architecture/r8-unit23.md +81 -0
  40. data/docs/architecture/r8-unit3.md +130 -0
  41. data/docs/architecture/r8-unit4.md +79 -0
  42. data/docs/architecture/r8-unit5.md +115 -0
  43. data/docs/architecture/r8-unit6.md +71 -0
  44. data/docs/architecture/r8-unit7.md +70 -0
  45. data/docs/architecture/r8-unit8.md +78 -0
  46. data/docs/architecture/r8-unit9.md +73 -0
  47. data/docs/architecture/refactoring-closure.md +10 -1
  48. data/docs/architecture/remaining-refactoring-plan.md +16 -0
  49. data/docs/architecture/storage-execution-boundaries.md +54 -0
  50. data/docs/architecture/tool-schema-recording-gap.md +6 -0
  51. data/docs/architecture/tracing.md +1 -1
  52. data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
  53. data/docs/architecture.md +24 -11
  54. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  55. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  56. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
  57. data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
  58. data/docs/decisions/032-storage-backend-composition.md +5 -0
  59. data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
  60. data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
  61. data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
  62. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
  63. data/docs/decisions/040-configuration-default-composition.md +12 -1
  64. data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
  65. data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
  66. data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
  67. data/docs/decisions/058-neutral-storage-primitives.md +5 -0
  68. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  69. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  70. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  71. data/docs/decisions/062-content-store-service.md +52 -0
  72. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  73. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  74. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  75. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  76. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  77. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  78. data/docs/decisions/README.md +18 -1
  79. data/docs/execution-boundaries-preview.md +45 -0
  80. data/docs/failure-reason-codes.md +45 -0
  81. data/docs/features.md +12 -4
  82. data/docs/getting-started.md +52 -48
  83. data/docs/migrations/0.22.md +2 -2
  84. data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
  85. data/docs/migrations/handoff-runner-multi-agent.md +2 -0
  86. data/docs/migrations/persistence-failure-contracts.md +67 -0
  87. data/docs/migrations/r8-unit10.md +36 -0
  88. data/docs/migrations/r8-unit11.md +33 -0
  89. data/docs/migrations/r8-unit12.md +31 -0
  90. data/docs/migrations/r8-unit13.md +36 -0
  91. data/docs/migrations/r8-unit14.md +31 -0
  92. data/docs/migrations/r8-unit15.md +31 -0
  93. data/docs/migrations/r8-unit16.md +26 -0
  94. data/docs/migrations/r8-unit17.md +28 -0
  95. data/docs/migrations/r8-unit18.md +29 -0
  96. data/docs/migrations/r8-unit19.md +28 -0
  97. data/docs/migrations/r8-unit20.md +24 -0
  98. data/docs/migrations/r8-unit21.md +28 -0
  99. data/docs/migrations/r8-unit22.md +29 -0
  100. data/docs/migrations/r8-unit23.md +20 -0
  101. data/docs/migrations/r8-unit4.md +67 -0
  102. data/docs/migrations/r8-unit5.md +75 -0
  103. data/docs/migrations/r8-unit6.md +109 -0
  104. data/docs/migrations/r8-unit7.md +31 -0
  105. data/docs/migrations/r8-unit8.md +33 -0
  106. data/docs/migrations/r8-unit9.md +28 -0
  107. data/docs/migrations/storage-async-client.md +103 -0
  108. data/docs/migrations/vector-async-clients.md +110 -0
  109. data/docs/ordered-event-delivery.md +54 -0
  110. data/docs/persistence-backends.md +38 -21
  111. data/docs/runtime-and-concurrency.md +26 -1
  112. data/docs/workflow-completion.md +92 -0
  113. data/gemfiles/http_minimum.gemfile +5 -0
  114. data/lib/phronomy/agent/admission.rb +78 -0
  115. data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
  116. data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
  117. data/lib/phronomy/agent/agent_not_found_error.rb +16 -0
  118. data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
  119. data/lib/phronomy/agent/agent_unloaded_error.rb +14 -0
  120. data/lib/phronomy/agent/api/agent.rb +13 -0
  121. data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
  122. data/lib/phronomy/agent/async_event_api.rb +132 -103
  123. data/lib/phronomy/agent/base.rb +146 -113
  124. data/lib/phronomy/agent/before_llm_input.rb +70 -0
  125. data/lib/phronomy/agent/composition/run_once.rb +1 -1
  126. data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
  127. data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
  128. data/lib/phronomy/agent/control_request.rb +21 -0
  129. data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
  130. data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
  131. data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
  132. data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
  133. data/lib/phronomy/agent/execution/exact_execution.rb +55 -32
  134. data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
  135. data/lib/phronomy/agent/execution/execution_coordinator.rb +229 -407
  136. data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
  137. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +71 -127
  138. data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
  139. data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
  140. data/lib/phronomy/agent/execution/initial_preparation.rb +19 -120
  141. data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
  142. data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
  143. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +8 -11
  144. data/lib/phronomy/agent/execution_change.rb +161 -0
  145. data/lib/phronomy/agent/execution_environment.rb +36 -0
  146. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  147. data/lib/phronomy/agent/execution_observation.rb +35 -0
  148. data/lib/phronomy/agent/filterable.rb +188 -0
  149. data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
  150. data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
  151. data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
  152. data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
  153. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  154. data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
  155. data/lib/phronomy/agent/lifecycle/lifetime_api.rb +76 -0
  156. data/lib/phronomy/agent/lifecycle/operation_lease.rb +33 -0
  157. data/lib/phronomy/agent/lifecycle/operation_result.rb +53 -0
  158. data/lib/phronomy/agent/lifecycle/ownership_registry.rb +117 -31
  159. data/lib/phronomy/agent/lifecycle/scope.rb +198 -0
  160. data/lib/phronomy/agent/lifecycle/scope_cleanup_error.rb +19 -0
  161. data/lib/phronomy/agent/lifecycle/unload_transition.rb +78 -0
  162. data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
  163. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  164. data/lib/phronomy/agent/persistence/codec.rb +1 -21
  165. data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
  166. data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
  167. data/lib/phronomy/agent/persistence/records.rb +33 -0
  168. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  169. data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
  170. data/lib/phronomy/agent/persistence/watermark.rb +13 -11
  171. data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
  172. data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +13 -12
  173. data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +34 -34
  174. data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +33 -39
  175. data/lib/phronomy/agent/recovery/recovery_coordinator.rb +28 -25
  176. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  177. data/lib/phronomy/agent/recovery/recovery_support.rb +10 -19
  178. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  179. data/lib/phronomy/agent/retention.rb +17 -0
  180. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  181. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  182. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  183. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  184. data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
  185. data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
  186. data/lib/phronomy/agent/settings.rb +47 -0
  187. data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
  188. data/lib/phronomy/agent/store.rb +253 -0
  189. data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +3 -3
  190. data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
  191. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
  192. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  193. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  194. data/lib/phronomy/common/error.rb +65 -1
  195. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  196. data/lib/phronomy/content_store/base.rb +7 -2
  197. data/lib/phronomy/content_store/storage_schema.rb +1 -0
  198. data/lib/phronomy/content_store/stored_contents.rb +2 -1
  199. data/lib/phronomy/context/assembly.rb +195 -0
  200. data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
  201. data/lib/phronomy/context/candidate.rb +51 -0
  202. data/lib/phronomy/context/constraint.rb +47 -0
  203. data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
  204. data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
  205. data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
  206. data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
  207. data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
  208. data/lib/phronomy/context/default_policy.rb +140 -0
  209. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  210. data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
  211. data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
  212. data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
  213. data/lib/phronomy/context/manifest_error.rb +10 -0
  214. data/lib/phronomy/context/prompt_template.rb +98 -0
  215. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  216. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  217. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  218. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  219. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  220. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  221. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  222. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  223. data/lib/phronomy/embeddings/authentication_error.rb +11 -0
  224. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +52 -0
  225. data/lib/phronomy/embeddings/base.rb +43 -0
  226. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  227. data/lib/phronomy/embeddings/rate_limit_error.rb +11 -0
  228. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  229. data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
  230. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  231. data/lib/phronomy/engine/diagnostics.rb +1 -1
  232. data/lib/phronomy/engine/event_loop.rb +7 -7
  233. data/lib/phronomy/engine/execution_binding.rb +23 -0
  234. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  235. data/lib/phronomy/engine/fsm_protocol.rb +1 -6
  236. data/lib/phronomy/engine/fsm_session.rb +3 -3
  237. data/lib/phronomy/engine/metrics.rb +16 -14
  238. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  239. data/lib/phronomy/engine/runtime.rb +25 -12
  240. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  241. data/lib/phronomy/execution/async_operation.rb +41 -0
  242. data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
  243. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
  244. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  245. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  246. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  247. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  248. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  249. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  250. data/lib/phronomy/{engine → execution}/execution.rb +61 -27
  251. data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +2 -2
  252. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  253. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  254. data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +2 -2
  255. data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
  256. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  257. data/lib/phronomy/execution/runnable.rb +24 -0
  258. data/lib/phronomy/{engine → execution}/task_result.rb +9 -4
  259. data/lib/phronomy/execution/wait_policy.rb +24 -0
  260. data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +3 -3
  261. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  262. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  263. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
  264. data/lib/phronomy/generation/generator_verifier.rb +7 -7
  265. data/lib/phronomy/generation/low_confidence_error.rb +2 -2
  266. data/lib/phronomy/integrations/ordered_event_delivery.rb +208 -0
  267. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  268. data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
  269. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  270. data/lib/phronomy/llm_adapter/base.rb +49 -68
  271. data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
  272. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  273. data/lib/phronomy/llm_adapter/message.rb +62 -0
  274. data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
  275. data/lib/phronomy/llm_adapter/request.rb +79 -0
  276. data/lib/phronomy/llm_adapter/response.rb +36 -0
  277. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  278. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  279. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  280. data/lib/phronomy/llm_adapter.rb +3 -16
  281. data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
  282. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  283. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  284. data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
  285. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
  286. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  287. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
  288. data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
  289. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
  290. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  291. data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
  292. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  293. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  294. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  295. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +60 -44
  296. data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
  297. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  298. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  299. data/lib/phronomy/multi_agent/shared_state.rb +2 -2
  300. data/lib/phronomy/multi_agent/store.rb +156 -0
  301. data/lib/phronomy/multi_agent/team_busy_error.rb +18 -0
  302. data/lib/phronomy/multi_agent/team_coordinator.rb +99 -77
  303. data/lib/phronomy/multi_agent/team_not_found_error.rb +18 -0
  304. data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
  305. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  306. data/lib/phronomy/persistence/content_repository.rb +26 -0
  307. data/lib/phronomy/persistence/errors.rb +16 -0
  308. data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
  309. data/lib/phronomy/persistence/persistence.rb +23 -0
  310. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  311. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  312. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  313. data/lib/phronomy/persistence/transaction.rb +86 -0
  314. data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
  315. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  316. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  317. data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
  318. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  319. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  320. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  321. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  322. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  323. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  324. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  325. data/lib/phronomy/storage/async/async_client.rb +60 -0
  326. data/lib/phronomy/storage/backend.rb +8 -2
  327. data/lib/phronomy/storage/condition_failed_error.rb +2 -2
  328. data/lib/phronomy/storage/unique_constraint_error.rb +2 -2
  329. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  330. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  331. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
  332. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
  333. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
  334. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
  335. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
  336. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
  337. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
  338. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
  339. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
  340. data/lib/phronomy/tool/authorization.rb +77 -0
  341. data/lib/phronomy/tool/base.rb +551 -9
  342. data/lib/phronomy/tool/call_request.rb +40 -0
  343. data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
  344. data/lib/phronomy/tool/operation.rb +88 -0
  345. data/lib/phronomy/tool/schema.rb +214 -0
  346. data/lib/phronomy/tool/settings.rb +31 -0
  347. data/lib/phronomy/tool/tool_error.rb +7 -0
  348. data/lib/phronomy/tool/tool_executor.rb +58 -0
  349. data/lib/phronomy/tools/agent.rb +22 -90
  350. data/lib/phronomy/tools/mcp.rb +21 -14
  351. data/lib/phronomy/tools/vector_search.rb +4 -4
  352. data/lib/phronomy/tracing/automatic.rb +11 -6
  353. data/lib/phronomy/tracing/base.rb +36 -7
  354. data/lib/phronomy/tracing/observation.rb +37 -0
  355. data/lib/phronomy/tracing/open_telemetry_tracer.rb +4 -29
  356. data/lib/phronomy/tracing/settings.rb +36 -0
  357. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  358. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  359. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  360. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  361. data/lib/phronomy/vector_store/base.rb +118 -49
  362. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  363. data/lib/phronomy/vector_store.rb +2 -17
  364. data/lib/phronomy/version.rb +1 -1
  365. data/lib/phronomy/workflow/completion.rb +11 -0
  366. data/lib/phronomy/workflow/execution/workflow.rb +37 -9
  367. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  368. data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
  369. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +28 -0
  370. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +77 -8
  371. data/lib/phronomy/workflow/execution/workflow_runner.rb +105 -92
  372. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  373. data/lib/phronomy/workflow/execution/workflow_task_execution.rb +153 -0
  374. data/lib/phronomy/workflow/execution/workflow_task_reception.rb +72 -0
  375. data/lib/phronomy/workflow/execution/workflow_task_resume_error.rb +14 -0
  376. data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
  377. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  378. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +61 -0
  379. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +158 -0
  380. data/lib/phronomy/workflow/runtime_binding/workflow_task_session.rb +76 -0
  381. data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
  382. data/lib/phronomy.rb +63 -16
  383. data/scripts/api_snapshot.rb +47 -15
  384. data/scripts/check_readme_ruby.rb +24 -29
  385. data/scripts/check_readme_runnable.rb +92 -81
  386. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  387. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  388. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  389. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  390. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  391. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  392. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  393. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  394. data/sig/phronomy/agent/admission.rbs +19 -0
  395. data/sig/phronomy/agent/agent_not_found_error.rbs +6 -0
  396. data/sig/phronomy/agent/agent_unloaded_error.rbs +6 -0
  397. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  398. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  399. data/sig/phronomy/agent/store.rbs +73 -0
  400. data/sig/phronomy/agent/stream_callback_error.rbs +8 -0
  401. data/sig/phronomy/agent.rbs +29 -135
  402. data/sig/phronomy/common.rbs +7 -0
  403. data/sig/phronomy/content_store/contracts.rbs +13 -0
  404. data/sig/phronomy/context.rbs +152 -0
  405. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  406. data/sig/phronomy/embeddings/base.rbs +23 -0
  407. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  408. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  409. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  410. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  411. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  412. data/sig/phronomy/engine/observability.rbs +13 -0
  413. data/sig/phronomy/execution/async_operation.rbs +7 -0
  414. data/sig/phronomy/execution/blocking.rbs +5 -0
  415. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  416. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  417. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  418. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  419. data/sig/phronomy/execution/errors.rbs +7 -0
  420. data/sig/phronomy/execution/execution.rbs +41 -0
  421. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  422. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  423. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  424. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  425. data/sig/phronomy/execution/task_result.rbs +29 -0
  426. data/sig/phronomy/extensions.rbs +1 -21
  427. data/sig/phronomy/filter/filter_block_error.rbs +6 -0
  428. data/sig/phronomy/generation/low_confidence_error.rbs +6 -0
  429. data/sig/phronomy/handoff.rbs +10 -4
  430. data/sig/phronomy/integrations.rbs +14 -0
  431. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  432. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  433. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  434. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  435. data/sig/phronomy/llm_context_window.rbs +10 -0
  436. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  437. data/sig/phronomy/multi_agent/store.rbs +54 -0
  438. data/sig/phronomy/multi_agent/team_busy_error.rbs +8 -0
  439. data/sig/phronomy/multi_agent/team_not_found_error.rbs +8 -0
  440. data/sig/phronomy/multi_agent.rbs +6 -3
  441. data/sig/phronomy/persistence/errors.rbs +18 -0
  442. data/sig/phronomy/persistence/migration.rbs +13 -0
  443. data/sig/phronomy/persistence/transaction.rbs +19 -0
  444. data/sig/phronomy/persistence.rbs +1 -99
  445. data/sig/phronomy/persistence_composition.rbs +19 -0
  446. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  447. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  448. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  449. data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +4 -18
  450. data/sig/phronomy/tool.rbs +48 -4
  451. data/sig/phronomy/tracing/observation.rbs +7 -0
  452. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  453. data/sig/phronomy/vector_store/base.rbs +23 -0
  454. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +15 -0
  455. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  456. data/sig/phronomy/workflow/store.rbs +8 -0
  457. data/sig/phronomy/workflow.rbs +5 -1
  458. data/sig/phronomy.rbs +4 -49
  459. metadata +365 -117
  460. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  461. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  462. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  463. data/lib/phronomy/agent/context/capability/base.rb +0 -548
  464. data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
  465. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  466. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
  467. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
  468. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  469. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  470. data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
  471. data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
  472. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
  473. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
  474. data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
  475. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
  476. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
  477. data/lib/phronomy/agent/persistence/queries.rb +0 -61
  478. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  479. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  480. data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
  481. data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
  482. data/lib/phronomy/configuration/configuration.rb +0 -68
  483. data/lib/phronomy/configuration/global_configuration.rb +0 -26
  484. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  485. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  486. data/lib/phronomy/engine/execution_receiver.rb +0 -65
  487. data/lib/phronomy/engine/runnable.rb +0 -54
  488. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  489. data/lib/phronomy/llm_contract/token_usage.rb +0 -85
  490. data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
  491. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
  492. data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
  493. data/lib/phronomy/persistence/api/persistence.rb +0 -140
  494. data/lib/phronomy/persistence_composition/repositories.rb +0 -77
  495. data/lib/phronomy/recovery/recovery.rb +0 -186
  496. data/lib/phronomy/tool/contract/tool_error.rb +0 -7
  497. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  498. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  499. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  500. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
  501. data/sig/phronomy/execution_receiver.rbs +0 -34
  502. data/sig/phronomy/llm_adapter.rbs +0 -11
  503. data/sig/phronomy/runtime.rbs +0 -76
  504. /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
  505. /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
  506. /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
  507. /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
  508. /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
  509. /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
  510. /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
  511. /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
  512. /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
  513. /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
  514. /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
  515. /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
  516. /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
  517. /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
@@ -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
@@ -2,10 +2,9 @@
2
2
 
3
3
  module Phronomy
4
4
  module Concurrency
5
- # Disposable registrations. add also handles synchronous notification during
6
- # registration: cleanup added after close is performed immediately.
5
+ # Completion and explicit-cancellation subscriptions. No timer acquisition.
7
6
  # @api private
8
- class Subscriptions
7
+ class ResultSubscriptions
9
8
  def initialize
10
9
  @mutex = Mutex.new
11
10
  @closed = false
@@ -29,27 +28,24 @@ module Phronomy
29
28
  add { result.__unsubscribe(callback) }
30
29
  end
31
30
 
32
- def cancellation(token, &callback)
31
+ # Own the lifetime of explicit-cancellation notifications only. An elapsed
32
+ # deadline is not promoted to a notification here. close can race delivery:
33
+ # a callback already taken by cancel! may still run, as with the token API.
34
+ # Registration may deliver inline and close this collection before add;
35
+ # add then immediately disposes the completed registration.
36
+ # @api private
37
+ def explicit_cancellation(token, &callback)
33
38
  return unless token
34
39
 
35
40
  token.on_cancel(&callback)
36
41
  add { token.send(:unregister_cancel_callback, callback) }
37
- remaining = token.remaining_monotonic_seconds
38
- if token.cancelled?
39
- callback.call
40
- elsif remaining
41
- after(remaining, &callback)
42
- end
43
42
  end
44
43
 
45
- def after(seconds, &callback)
46
- if seconds <= 0
47
- callback.call
48
- return
49
- end
50
- timer = Phronomy::Runtime.instance.timer_queue
51
- timer.schedule(seconds: seconds, &callback)
52
- add { timer.cancel(callback) }
44
+ def cancellation(token, &callback)
45
+ return unless token
46
+
47
+ explicit_cancellation(token, &callback)
48
+ callback.call if token.cancelled?
53
49
  end
54
50
 
55
51
  def close
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Concurrency
5
+ # Services promotes deadlines into control notifications.
6
+ # @api private
7
+ class Subscriptions < ResultSubscriptions
8
+ def cancellation(token, &callback)
9
+ return unless token
10
+
11
+ super
12
+ remaining = token.remaining_monotonic_seconds
13
+ after(remaining, &callback) if !token.cancelled? && remaining
14
+ end
15
+
16
+ def after(seconds, &callback)
17
+ if seconds <= 0
18
+ callback.call
19
+ return
20
+ end
21
+ add(&Phronomy::Execution.after(seconds, &callback))
22
+ end
23
+ end
24
+ end
25
+ end
@@ -5,6 +5,28 @@ module Phronomy
5
5
  # that JOB; its application-defined intermediate work is not discovered here.
6
6
  # @api public
7
7
  class Execution
8
+ # Composition installs the mechanism. Public values and settled results do
9
+ # not access it; only scheduling/submission requires a configured backend.
10
+ class << self
11
+ attr_writer :backend_provider
12
+
13
+ def submit(**options, &operation)
14
+ raise ArgumentError, "Execution.submit requires a block" unless operation
15
+ backend.submit(**options, &operation)
16
+ end
17
+
18
+ def after(seconds, &callback)
19
+ raise ArgumentError, "Execution.after requires a block" unless callback
20
+ backend.after(seconds, &callback)
21
+ end
22
+
23
+ private
24
+
25
+ def backend
26
+ @backend_provider&.call || raise(ConfigurationError, "Execution backend has not been configured")
27
+ end
28
+ end
29
+
8
30
  # @return [InvocationContext] this execution's explicit operation context
9
31
  # @api public
10
32
  attr_reader :invocation_context
@@ -14,32 +36,25 @@ module Phronomy
14
36
  # @param cancellation_token [Concurrency::CancellationToken, nil]
15
37
  # @param invocation_context [InvocationContext, nil] existing information/controls
16
38
  # @yield [input, execution] starts one JOB and returns its final TaskResult
17
- # @return [TaskResult<Array<TaskResult::Outcome>>]
18
- # @api public
19
- def self.run_async(inputs, timeout: nil, cancellation_token: nil,
20
- invocation_context: nil, &block)
21
- __run_async(inputs, timeout: timeout, cancellation_token: cancellation_token,
22
- invocation_context: invocation_context, &block)
23
- end
24
-
25
39
  # Starts the same execution once and waits outside EventLoop.
26
40
  # @return [Array<TaskResult::Outcome>]
27
41
  # @see run_async
28
42
  # @api public
29
43
  def self.run(inputs, timeout: nil, cancellation_token: nil,
30
- invocation_context: nil, &block)
31
- if Runtime.in_event_loop_context?
44
+ invocation_context: nil, max_concurrency: nil, &block)
45
+ if WaitPolicy.blocking_forbidden?
32
46
  raise EventLoopReentrancyError, "Execution.run cannot run on EventLoop; use run_async"
33
47
  end
34
48
  run_async(inputs, timeout: timeout, cancellation_token: cancellation_token,
35
- invocation_context: invocation_context, &block).wait_result
49
+ invocation_context: invocation_context, max_concurrency: max_concurrency, &block).wait_result
36
50
  end
37
51
 
38
- # Existing Orchestrator concurrency policy uses the same execution engine.
39
- # This is not an additional public execution entrance or scheduling mode.
40
- # @api private
41
- def self.__run_async(inputs, timeout: nil, cancellation_token: nil,
42
- invocation_context: nil, concurrency_limit: nil, &block)
52
+ # Starts bounded fan-out without exposing collector or child bindings.
53
+ # @param max_concurrency [Integer, nil] maximum concurrently admitted JOBs
54
+ # @return [TaskResult<Array<TaskResult::Outcome>>]
55
+ # @api public
56
+ def self.run_async(inputs, timeout: nil, cancellation_token: nil,
57
+ invocation_context: nil, max_concurrency: nil, &block)
43
58
  raise TypeError, "inputs must be an Array" unless inputs.is_a?(Array)
44
59
  raise ArgumentError, "Execution.run_async requires a block" unless block
45
60
  if !cancellation_token.nil? && !cancellation_token.is_a?(Concurrency::CancellationToken)
@@ -54,11 +69,30 @@ module Phronomy
54
69
  raise ArgumentError, "timeout must be a finite real number"
55
70
  end
56
71
  end
72
+ if !max_concurrency.nil? && !(max_concurrency.is_a?(Integer) && max_concurrency.positive?)
73
+ raise ArgumentError, "max_concurrency must be a positive Integer"
74
+ end
57
75
  new(inputs.dup, timeout: timeout, cancellation_token: cancellation_token,
58
- invocation_context: invocation_context, concurrency_limit: concurrency_limit,
76
+ invocation_context: invocation_context, concurrency_limit: max_concurrency,
59
77
  &block).send(:start)
60
78
  end
61
- private_class_method :__run_async
79
+
80
+ # Starts one child with this execution's controls and the child's explicit
81
+ # context. Subscriptions live until the child settles, including start errors.
82
+ # A child-specific context keeps its own controls; the supplied token adds
83
+ # this execution's cancellation without mutating either original token.
84
+ # @api private
85
+ def start_child(invocation_context: nil, cancellation_token: nil, parent_task_id: nil)
86
+ controls = InvocationControls.new(invocation_context: @invocation_context,
87
+ cancellation_token: cancellation_token)
88
+ controls.check_start!
89
+ child_context = invocation_context || @invocation_context.merge(parent_task_id: parent_task_id)
90
+ result = yield child_context, controls.token
91
+ controls.track(result)
92
+ rescue
93
+ controls&.close
94
+ raise
95
+ end
62
96
 
63
97
  # A scoped observation copy. The source, its controls and its physical work
64
98
  # keep their original ownership. Bind before adding this run's map/flat_map.
@@ -70,7 +104,7 @@ module Phronomy
70
104
  unless source_result.is_a?(TaskResult)
71
105
  raise TypeError, "source_result must be a Phronomy::TaskResult"
72
106
  end
73
- observed = TaskResult.deferred(name: "execution-observation").__bind_execution(self)
107
+ observed = TaskResult.deferred(name: "execution-observation").__bind_execution(@scope)
74
108
  subscriptions = Concurrency::Subscriptions.new
75
109
  subscriptions.result(observed) { subscriptions.close }
76
110
  unless __open?
@@ -96,12 +130,12 @@ module Phronomy
96
130
  # Scope/admission hooks shared with framework-owned operations.
97
131
  # @api private
98
132
  def __while_open(&block)
99
- @collector.while_open(&block)
133
+ @scope.__while_open(&block)
100
134
  end
101
135
 
102
136
  # @api private
103
137
  def __open?
104
- @collector.open?
138
+ @scope.__open?
105
139
  end
106
140
 
107
141
  # @api private
@@ -111,7 +145,7 @@ module Phronomy
111
145
 
112
146
  # @api private
113
147
  def __cancellation_error
114
- @cancellation_error
148
+ @scope.__cancellation_error
115
149
  end
116
150
 
117
151
  private
@@ -126,9 +160,6 @@ module Phronomy
126
160
  @parent_token = cancellation_token
127
161
  @token = Concurrency::CancellationToken.new
128
162
  @subscriptions = Concurrency::Subscriptions.new
129
- @cancellation_error = CancellationError.new("Execution scope is closed")
130
- @invocation_context = (invocation_context || InvocationContext.new)
131
- .merge(cancellation_token: @token).__bind_execution(self)
132
163
  @result = TaskResult.deferred(name: "execution")
133
164
  @pump_mutex = Mutex.new
134
165
  @next_index = 0
@@ -138,6 +169,9 @@ module Phronomy
138
169
  @collector = Concurrency::ResultCollector.new(inputs.length) do |kind, outcomes|
139
170
  finish(kind, outcomes)
140
171
  end
172
+ @scope = ExecutionScopeState.new(collector: @collector, token: @token)
173
+ @invocation_context = (invocation_context || InvocationContext.new)
174
+ .merge(cancellation_token: @token).__bind_execution(@scope)
141
175
  end
142
176
 
143
177
  def start
@@ -214,12 +248,12 @@ module Phronomy
214
248
  when :completed
215
249
  @result.complete(outcomes)
216
250
  when :timeout
217
- @cancellation_error = CancellationError.new("Execution deadline exceeded")
251
+ @scope.__set_cancellation_error(CancellationError.new("Execution deadline exceeded"))
218
252
  error = ExecutionTimeoutError.new(outcomes: outcomes)
219
253
  @token.cancel!
220
254
  @result.fail(error)
221
255
  when :cancelled
222
- @cancellation_error = CancellationError.new("Execution cancelled")
256
+ @scope.__set_cancellation_error(CancellationError.new("Execution cancelled"))
223
257
  error = ExecutionCancellationError.new(outcomes: outcomes)
224
258
  @token.cancel!
225
259
  @result.cancel!(error)
@@ -6,9 +6,9 @@ module Phronomy
6
6
  class ExecutionCancellationError < CancellationError
7
7
  attr_reader :outcomes
8
8
 
9
- def initialize(message = "Execution cancelled", outcomes:)
9
+ def initialize(message = "Execution cancelled", outcomes:, code: nil)
10
10
  @outcomes = outcomes
11
- super(message)
11
+ super(message, code: code)
12
12
  end
13
13
  end
14
14
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Internal lifecycle/delivery contract for feature-owned execution state.
5
+ # A receiver has one serial owner supplied by composition, not an application
6
+ # callback SPI. Normal mutation and delivery run only on that owner. idle?
7
+ # runs under its lifecycle lock and must not acquire that lock again.
8
+ # shutdown runs on the failing owner or exclusively after it joins.
9
+ # @api private
10
+ class ExecutionReceiver
11
+ def self.install_binding(provider)
12
+ raise ArgumentError, "Execution receiver provider must be callable" unless provider.respond_to?(:call)
13
+ @binding_provider = provider
14
+ end
15
+
16
+ def self.for(owner)
17
+ ExecutionReceiver.binding.receiver(self, owner)
18
+ end
19
+
20
+ def self.existing_for(owner)
21
+ ExecutionReceiver.binding.existing_receiver(self, owner)
22
+ end
23
+
24
+ def self.binding
25
+ @binding_provider&.call || raise(ConfigurationError, "Execution receiver binding has not been configured")
26
+ end
27
+
28
+ def initialize(channel:)
29
+ @channel = channel
30
+ end
31
+
32
+ def __bound_to?(owner)
33
+ @channel.bound_to?(owner)
34
+ end
35
+
36
+ def idle?
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def deliver(message)
41
+ raise NotImplementedError
42
+ end
43
+
44
+ def shutdown(error:)
45
+ raise NotImplementedError
46
+ end
47
+
48
+ def session_retired(fsm_session_id, reason:)
49
+ end
50
+
51
+ private
52
+
53
+ def synchronize(&block)
54
+ @channel.synchronize(&block)
55
+ end
56
+
57
+ def admit(&block)
58
+ @channel.admit(self, &block)
59
+ end
60
+
61
+ def post_message(message, admission: false, completion: nil)
62
+ @channel.post(self, message, admission: admission, completion: completion)
63
+ end
64
+
65
+ # Resolve a live routing target atomically with delivery. The channel owns
66
+ # the transport envelope; domain receivers return only a target identity.
67
+ def route_event(type:, payload: nil, &block)
68
+ @channel.route(self, type: type, payload: payload, &block)
69
+ end
70
+
71
+ def assert_event_loop_thread!
72
+ return if @channel.current?
73
+
74
+ raise Phronomy::Error,
75
+ "Phronomy-managed live execution state may only be mutated on EventLoop"
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # A scope gate shared by observations and framework operations. The same
5
+ # collector lock arbitrates admission and completion; no Execution is held.
6
+ # @api private
7
+ class ExecutionScopeState
8
+ def initialize(collector:, token:)
9
+ @collector = collector
10
+ @token = token
11
+ @error_mutex = Mutex.new
12
+ @error = CancellationError.new("Execution scope is closed")
13
+ end
14
+
15
+ def __while_open(&block)
16
+ @collector.while_open(&block)
17
+ end
18
+
19
+ def __open?
20
+ @collector.open?
21
+ end
22
+
23
+ def __cancellation_token
24
+ @token
25
+ end
26
+
27
+ def __cancellation_error
28
+ @error_mutex.synchronize { @error }
29
+ end
30
+
31
+ def __set_cancellation_error(error)
32
+ @error_mutex.synchronize { @error = error }
33
+ end
34
+ end
35
+ end
@@ -6,9 +6,9 @@ module Phronomy
6
6
  class ExecutionTimeoutError < TimeoutError
7
7
  attr_reader :outcomes
8
8
 
9
- def initialize(message = "Execution timed out", outcomes:)
9
+ def initialize(message = "Execution timed out", outcomes:, code: nil)
10
10
  @outcomes = outcomes
11
- super(message)
11
+ super(message, code: code)
12
12
  end
13
13
  end
14
14
  end
@@ -70,20 +70,5 @@ module Phronomy
70
70
  def __execution_scope
71
71
  @execution_scope
72
72
  end
73
-
74
- # @api private
75
- def effective_cancellation_token
76
- @cancellation_token || Phronomy::Concurrency::CancellationToken.new
77
- end
78
-
79
- # @api private
80
- def effective_timeout_token
81
- return @cancellation_token if @cancellation_token
82
- return nil if @deadline.nil?
83
-
84
- token = Phronomy::Concurrency::CancellationToken.new
85
- @deadline.attach_to(token)
86
- token
87
- end
88
73
  end
89
74
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Owns invocation control attachment through the observed result's lifetime.
5
+ # Domain runners carry this operation control, not execution scope internals.
6
+ # @api private
7
+ class InvocationControls
8
+ attr_reader :token
9
+
10
+ def self.attach(result, invocation_context:, cancellation_token:)
11
+ controls = new(invocation_context: invocation_context, cancellation_token: cancellation_token)
12
+ controls.bind(result)
13
+ controls
14
+ rescue
15
+ controls&.close
16
+ raise
17
+ end
18
+
19
+ def initialize(invocation_context:, cancellation_token:)
20
+ if !invocation_context.nil? && !invocation_context.is_a?(InvocationContext)
21
+ raise TypeError, "invocation_context must be a Phronomy::InvocationContext or nil"
22
+ end
23
+ @scope = invocation_context&.__execution_scope
24
+ @subscriptions = Concurrency::Subscriptions.new
25
+ @token = invocation_context ? Concurrency::CancellationToken.new : cancellation_token
26
+ return unless invocation_context
27
+
28
+ [invocation_context.cancellation_token, cancellation_token].compact.uniq.each do |source|
29
+ @subscriptions.cancellation(source) { @token.cancel! }
30
+ end
31
+ if !@scope && invocation_context.deadline
32
+ @subscriptions.after(invocation_context.deadline.remaining_seconds) { @token.cancel! }
33
+ end
34
+ @token.cancel! if @scope && !@scope.__open?
35
+ rescue
36
+ close
37
+ raise
38
+ end
39
+
40
+ # Check at the receiving runner, after asynchronous delivery. An already
41
+ # closed scope must not acquire the domain's admission slot or start I/O.
42
+ def check_start!
43
+ if @scope && (!@scope.__open? || @token&.cancelled?)
44
+ raise @scope.__cancellation_error
45
+ end
46
+ end
47
+
48
+ def bind(result)
49
+ result.__bind_execution(@scope)
50
+ track(result)
51
+ end
52
+
53
+ def track(result)
54
+ @subscriptions.result(result) { close }
55
+ result
56
+ end
57
+
58
+ def close
59
+ @subscriptions&.close
60
+ end
61
+
62
+ def self.effective_timeout_token(context)
63
+ return context.cancellation_token if context.cancellation_token
64
+ return nil unless context.deadline
65
+
66
+ token = Concurrency::CancellationToken.new
67
+ subscriptions = Concurrency::Subscriptions.new
68
+ subscriptions.cancellation(token) { subscriptions.close }
69
+ subscriptions.after(context.deadline.remaining_seconds) { token.cancel! }
70
+ token
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Base interface for executable graph components.
5
+ # Provides invoke / stream / batch.
6
+ module Runnable
7
+ # Synchronous execution. Must be overridden in subclasses.
8
+ def invoke(input, config: {})
9
+ raise NotImplementedError, "#{self.class}#invoke is not implemented"
10
+ end
11
+
12
+ # Streaming execution. Default yields the invoke result as a single chunk.
13
+ def stream(input, config: {}, &block)
14
+ result = invoke(input, config: config)
15
+ yield result if block
16
+ result
17
+ end
18
+
19
+ # Batch execution. Default calls invoke sequentially.
20
+ def batch(inputs, config: {})
21
+ inputs.map { |input| invoke(input, config: config) }
22
+ end
23
+ end
24
+ end
@@ -130,7 +130,7 @@ module Phronomy
130
130
  # @raise [Exception] the error that settled the TaskResult
131
131
  # @api public
132
132
  def wait_result(timeout: nil)
133
- if Phronomy::Runtime.in_event_loop_context? && !done?
133
+ if Phronomy::WaitPolicy.blocking_forbidden? && !done?
134
134
  raise Phronomy::EventLoopReentrancyError,
135
135
  "TaskResult#wait_result cannot block the EventLoop thread; continue via an event"
136
136
  end
@@ -160,7 +160,7 @@ module Phronomy
160
160
  # Returns self when settled, nil on timeout.
161
161
  # @api private
162
162
  def join(limit = nil)
163
- if Phronomy::Runtime.in_event_loop_context? && !done?
163
+ if Phronomy::WaitPolicy.blocking_forbidden? && !done?
164
164
  raise Phronomy::EventLoopReentrancyError,
165
165
  "TaskResult#join cannot block the EventLoop thread; continue via an event"
166
166
  end
@@ -308,8 +308,13 @@ module Phronomy
308
308
  def deliver_completion_callback(callback, value, error)
309
309
  callback.call(value, error)
310
310
  rescue => callback_error
311
- Phronomy.configuration.logger&.error do
312
- "[TaskResult] on_complete callback raised #{callback_error.class}: #{callback_error.message}"
311
+ begin
312
+ Phronomy::RuntimeSettings.current.logger&.error do
313
+ "[TaskResult] on_complete callback raised #{callback_error.class}: #{callback_error.message}"
314
+ end
315
+ rescue
316
+ # Reporting must not interrupt delivery to the remaining observers.
317
+ nil
313
318
  end
314
319
  end
315
320
 
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Neutral thread-local blocking policy. It owns no Runtime or scheduler.
5
+ # No invocation context is inferred from this marker.
6
+ # @api private
7
+ module WaitPolicy
8
+ KEY = :phronomy_control_thread_depth
9
+ private_constant :KEY
10
+
11
+ def self.blocking_forbidden?
12
+ Thread.current.thread_variable_get(KEY).to_i > 0
13
+ end
14
+
15
+ def self.without_blocking
16
+ thread = Thread.current
17
+ previous = thread.thread_variable_get(KEY)
18
+ thread.thread_variable_set(KEY, previous.to_i + 1)
19
+ yield
20
+ ensure
21
+ thread.thread_variable_set(KEY, previous)
22
+ end
23
+ end
24
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../common/error"
3
+ require_relative "../common/error"
4
4
 
5
5
  module Phronomy
6
6
  class FilterBlockError < Error
7
7
  attr_reader :filter
8
8
 
9
- def initialize(message, filter: nil)
10
- super(message)
9
+ def initialize(message, filter: nil, code: nil)
10
+ super(message, code: code)
11
11
  @filter = filter
12
12
  end
13
13
  end