phronomy 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (557) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +92 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +13 -3
  6. data/VERIFY.sh +27 -27
  7. data/benchmark/bench_agent_invoke.rb +26 -47
  8. data/benchmark/bench_context_assembler.rb +17 -15
  9. data/benchmark/bench_regression.rb +1 -1
  10. data/benchmark/bench_tool_schema.rb +1 -1
  11. data/docs/architecture/agent-chat-and-state-ownership.md +153 -0
  12. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  13. data/docs/architecture/agent-context.md +4 -2
  14. data/docs/architecture/agent-transition-ownership.md +13 -0
  15. data/docs/architecture/backend-groups.md +152 -0
  16. data/docs/architecture/before-llm-input.md +9 -3
  17. data/docs/architecture/context-management.md +31 -1
  18. data/docs/architecture/context-preparation-steps.md +90 -0
  19. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  20. data/docs/architecture/execution-abstraction-review.md +88 -0
  21. data/docs/architecture/execution-boundaries.md +28 -0
  22. data/docs/architecture/execution-metadata-and-values.md +86 -0
  23. data/docs/architecture/generator-verifier-ownership.md +111 -0
  24. data/docs/architecture/multi-agent-handoff.md +11 -4
  25. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  26. data/docs/architecture/persistence.md +45 -4
  27. data/docs/architecture/r8-unit1.md +104 -0
  28. data/docs/architecture/r8-unit10.md +72 -0
  29. data/docs/architecture/r8-unit11.md +71 -0
  30. data/docs/architecture/r8-unit12.md +67 -0
  31. data/docs/architecture/r8-unit13.md +61 -0
  32. data/docs/architecture/r8-unit14.md +72 -0
  33. data/docs/architecture/r8-unit15.md +65 -0
  34. data/docs/architecture/r8-unit16.md +54 -0
  35. data/docs/architecture/r8-unit17.md +73 -0
  36. data/docs/architecture/r8-unit18.md +92 -0
  37. data/docs/architecture/r8-unit19.md +61 -0
  38. data/docs/architecture/r8-unit2.md +55 -0
  39. data/docs/architecture/r8-unit20.md +51 -0
  40. data/docs/architecture/r8-unit21.md +79 -0
  41. data/docs/architecture/r8-unit22.md +74 -0
  42. data/docs/architecture/r8-unit23.md +81 -0
  43. data/docs/architecture/r8-unit3.md +130 -0
  44. data/docs/architecture/r8-unit4.md +79 -0
  45. data/docs/architecture/r8-unit5.md +115 -0
  46. data/docs/architecture/r8-unit6.md +71 -0
  47. data/docs/architecture/r8-unit7.md +70 -0
  48. data/docs/architecture/r8-unit8.md +78 -0
  49. data/docs/architecture/r8-unit9.md +73 -0
  50. data/docs/architecture/refactoring-closure.md +343 -0
  51. data/docs/architecture/remaining-refactoring-plan.md +390 -0
  52. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  53. data/docs/architecture/storage-execution-boundaries.md +54 -0
  54. data/docs/architecture/tool-schema-recording-gap.md +56 -0
  55. data/docs/architecture/tracing.md +2 -2
  56. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  57. data/docs/architecture.md +222 -0
  58. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  59. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  60. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  61. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  62. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  63. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  64. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +7 -0
  65. data/docs/decisions/031-durable-multi-agent-coordination.md +7 -0
  66. data/docs/decisions/032-storage-backend-composition.md +85 -0
  67. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  68. data/docs/decisions/034-handoff-runner-coordination-ownership.md +78 -0
  69. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  70. data/docs/decisions/036-context-contract-ownership.md +86 -0
  71. data/docs/decisions/037-common-definition-ownership.md +62 -0
  72. data/docs/decisions/038-responsibility-based-source-layout.md +145 -0
  73. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +82 -0
  74. data/docs/decisions/040-configuration-default-composition.md +105 -0
  75. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  76. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  77. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  78. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  79. data/docs/decisions/045-worker-input-restriction-ownership.md +104 -0
  80. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  81. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  82. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  83. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  84. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  85. data/docs/decisions/051-execution-outcome-worker-ownership.md +153 -0
  86. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  87. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  88. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  89. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  90. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  91. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  92. data/docs/decisions/058-neutral-storage-primitives.md +83 -0
  93. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  94. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  95. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  96. data/docs/decisions/062-content-store-service.md +52 -0
  97. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  98. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  99. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  100. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  101. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  102. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  103. data/docs/decisions/README.md +52 -6
  104. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  105. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  106. data/docs/execution-boundaries-preview.md +45 -0
  107. data/docs/features.md +16 -5
  108. data/docs/getting-started.md +11 -8
  109. data/docs/migrations/0.15.md +5 -0
  110. data/docs/migrations/0.22.md +2 -2
  111. data/docs/migrations/durable-semantic-coordination-v2.md +8 -0
  112. data/docs/migrations/handoff-runner-multi-agent.md +37 -0
  113. data/docs/migrations/neutral-storage-spi.md +43 -0
  114. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  115. data/docs/migrations/persistence-failure-contracts.md +67 -0
  116. data/docs/migrations/r8-unit10.md +36 -0
  117. data/docs/migrations/r8-unit11.md +33 -0
  118. data/docs/migrations/r8-unit12.md +31 -0
  119. data/docs/migrations/r8-unit13.md +36 -0
  120. data/docs/migrations/r8-unit14.md +31 -0
  121. data/docs/migrations/r8-unit15.md +31 -0
  122. data/docs/migrations/r8-unit16.md +26 -0
  123. data/docs/migrations/r8-unit17.md +28 -0
  124. data/docs/migrations/r8-unit18.md +29 -0
  125. data/docs/migrations/r8-unit19.md +28 -0
  126. data/docs/migrations/r8-unit20.md +24 -0
  127. data/docs/migrations/r8-unit21.md +28 -0
  128. data/docs/migrations/r8-unit22.md +29 -0
  129. data/docs/migrations/r8-unit23.md +20 -0
  130. data/docs/migrations/r8-unit4.md +67 -0
  131. data/docs/migrations/r8-unit5.md +75 -0
  132. data/docs/migrations/r8-unit6.md +109 -0
  133. data/docs/migrations/r8-unit7.md +31 -0
  134. data/docs/migrations/r8-unit8.md +33 -0
  135. data/docs/migrations/r8-unit9.md +28 -0
  136. data/docs/migrations/shared-state-multi-agent.md +44 -0
  137. data/docs/migrations/storage-async-client.md +103 -0
  138. data/docs/migrations/storage-backend-composition.md +117 -0
  139. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  140. data/docs/migrations/vector-async-clients.md +110 -0
  141. data/docs/persistence-backends.md +190 -215
  142. data/docs/runtime-and-concurrency.md +142 -32
  143. data/lib/phronomy/agent/admission.rb +78 -0
  144. data/lib/phronomy/{agent_already_exists_error.rb → agent/agent_already_exists_error.rb} +2 -0
  145. data/lib/phronomy/{agent_busy_error.rb → agent/agent_busy_error.rb} +2 -0
  146. data/lib/phronomy/{agent_purged_error.rb → agent/agent_purged_error.rb} +2 -0
  147. data/lib/phronomy/agent/api/agent.rb +17 -0
  148. data/lib/phronomy/{execution_rehydration_required_error.rb → agent/api/execution_rehydration_required_error.rb} +2 -0
  149. data/lib/phronomy/agent/async_event_api.rb +38 -33
  150. data/lib/phronomy/agent/base.rb +106 -294
  151. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  152. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  153. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  154. data/lib/phronomy/agent/context_importer.rb +2 -2
  155. data/lib/phronomy/agent/{context_assembler.rb → context_preparation.rb} +157 -288
  156. data/lib/phronomy/agent/control_request.rb +21 -0
  157. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  158. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +32 -63
  159. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  160. data/lib/phronomy/agent/execution/dispatch_preparation.rb +195 -0
  161. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +43 -23
  162. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +8 -5
  163. data/lib/phronomy/agent/execution/execution_coordinator.rb +1744 -0
  164. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  165. data/lib/phronomy/agent/execution/execution_metadata.rb +54 -0
  166. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +288 -0
  167. data/lib/phronomy/agent/execution/execution_registry.rb +463 -0
  168. data/lib/phronomy/agent/execution/execution_session_runner.rb +113 -0
  169. data/lib/phronomy/agent/execution/initial_preparation.rb +323 -0
  170. data/lib/phronomy/agent/execution/invocation_actions.rb +368 -0
  171. data/lib/phronomy/agent/execution/invocation_transitions.rb +124 -0
  172. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  173. data/lib/phronomy/agent/execution_change.rb +161 -0
  174. data/lib/phronomy/agent/execution_environment.rb +36 -0
  175. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  176. data/lib/phronomy/agent/execution_observation.rb +35 -0
  177. data/lib/phronomy/agent/filterable.rb +182 -0
  178. data/lib/phronomy/agent/{handoff_context.rb → handoff/transfer_context.rb} +6 -6
  179. data/lib/phronomy/agent/{handoff_projection.rb → handoff/transfer_projection.rb} +14 -33
  180. data/lib/phronomy/agent/handoff_error.rb +7 -0
  181. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  182. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  183. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  184. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  185. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  186. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  187. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -15
  188. data/lib/phronomy/agent/persistence/agent_repository.rb +69 -0
  189. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  190. data/lib/phronomy/agent/persistence/codec.rb +338 -0
  191. data/lib/phronomy/agent/persistence/execution_repository.rb +124 -0
  192. data/lib/phronomy/agent/persistence/journal_repository.rb +63 -0
  193. data/lib/phronomy/agent/persistence/records.rb +33 -0
  194. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  195. data/lib/phronomy/agent/persistence/storage_schema.rb +29 -0
  196. data/lib/phronomy/agent/persistence/watermark.rb +29 -0
  197. data/lib/phronomy/agent/recovery/invocation_restorer.rb +124 -0
  198. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +28 -51
  199. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +50 -55
  200. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +38 -43
  201. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +26 -23
  202. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  203. data/lib/phronomy/agent/recovery/recovery_support.rb +224 -0
  204. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  205. data/lib/phronomy/agent/retention.rb +17 -0
  206. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  207. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  208. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  209. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  210. data/lib/phronomy/agent/{ruby_llm_materializer.rb → runtime_input.rb} +19 -39
  211. data/lib/phronomy/agent/saved_context_reader.rb +55 -0
  212. data/lib/phronomy/agent/settings.rb +47 -0
  213. data/lib/phronomy/agent/state_writer.rb +166 -0
  214. data/lib/phronomy/agent/store.rb +253 -0
  215. data/lib/phronomy/{stream_callback_error.rb → agent/stream_callback_error.rb} +2 -0
  216. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  217. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  218. data/lib/phronomy/agent/tool_execution/tool_binding.rb +30 -0
  219. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +114 -248
  220. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  221. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  222. data/lib/phronomy/common/configuration_error.rb +7 -0
  223. data/lib/phronomy/common/error.rb +5 -0
  224. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  225. data/lib/phronomy/common/values/serializable.rb +32 -0
  226. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  227. data/lib/phronomy/content_store/storage_schema.rb +12 -0
  228. data/lib/phronomy/content_store/stored_contents.rb +44 -0
  229. data/lib/phronomy/context/assembly.rb +202 -0
  230. data/lib/phronomy/{context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +5 -1
  231. data/lib/phronomy/context/candidate.rb +51 -0
  232. data/lib/phronomy/context/constraint.rb +47 -0
  233. data/lib/phronomy/{agent → context}/context_plan.rb +2 -2
  234. data/lib/phronomy/{agent → context}/context_plan_validator.rb +3 -3
  235. data/lib/phronomy/{agent → context}/context_policy.rb +1 -1
  236. data/lib/phronomy/{agent → context}/context_policy_input.rb +7 -7
  237. data/lib/phronomy/{agent → context}/context_policy_input_builder.rb +1 -1
  238. data/lib/phronomy/context/default_policy.rb +140 -0
  239. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  240. data/lib/phronomy/{agent → context}/llm_input_build_context.rb +2 -2
  241. data/lib/phronomy/{agent → context}/llm_input_manifest.rb +21 -21
  242. data/lib/phronomy/{agent → context}/llm_input_patch.rb +3 -3
  243. data/lib/phronomy/context/manifest_error.rb +10 -0
  244. data/lib/phronomy/context/prompt_template.rb +98 -0
  245. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  246. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  247. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  248. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  249. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  250. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  251. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  252. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  253. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  254. data/lib/phronomy/embeddings/base.rb +43 -0
  255. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  256. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  257. data/lib/phronomy/engine/concurrency/offload_pool.rb +39 -20
  258. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  259. data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +1 -1
  260. data/lib/phronomy/engine/event_loop.rb +164 -630
  261. data/lib/phronomy/engine/execution_binding.rb +23 -0
  262. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  263. data/lib/phronomy/engine/fsm_protocol.rb +9 -0
  264. data/lib/phronomy/engine/fsm_session.rb +37 -30
  265. data/lib/phronomy/engine/metrics.rb +28 -0
  266. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  267. data/lib/phronomy/engine/runtime.rb +104 -95
  268. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  269. data/lib/phronomy/execution/async_operation.rb +41 -0
  270. data/lib/phronomy/execution/backpressure_error.rb +7 -0
  271. data/lib/phronomy/{blocking.rb → execution/blocking.rb} +2 -2
  272. data/lib/phronomy/execution/cancellation_error.rb +7 -0
  273. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +5 -1
  274. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  275. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  276. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  277. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  278. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  279. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  280. data/lib/phronomy/execution/concurrency/worker_input_restricted.rb +14 -0
  281. data/lib/phronomy/execution/event_loop_reentrancy_error.rb +8 -0
  282. data/lib/phronomy/{execution.rb → execution/execution.rb} +57 -30
  283. data/lib/phronomy/{execution_cancellation_error.rb → execution/execution_cancellation_error.rb} +2 -0
  284. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  285. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  286. data/lib/phronomy/{execution_timeout_error.rb → execution/execution_timeout_error.rb} +2 -0
  287. data/lib/phronomy/{invalid_async_entry_action_error.rb → execution/invalid_async_entry_action_error.rb} +2 -0
  288. data/lib/phronomy/{invalid_async_transition_action_error.rb → execution/invalid_async_transition_action_error.rb} +2 -0
  289. data/lib/phronomy/{invalid_async_workflow_action_error.rb → execution/invalid_async_workflow_action_error.rb} +2 -0
  290. data/lib/phronomy/{invocation_context.rb → execution/invocation_context.rb} +0 -15
  291. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  292. data/lib/phronomy/execution/pool_shutdown_error.rb +7 -0
  293. data/lib/phronomy/execution/recursion_limit_error.rb +7 -0
  294. data/lib/phronomy/execution/runnable.rb +24 -0
  295. data/lib/phronomy/execution/runtime_shutdown_error.rb +7 -0
  296. data/lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb +7 -0
  297. data/lib/phronomy/execution/scheduler_reentrancy_error.rb +9 -0
  298. data/lib/phronomy/{engine → execution}/task_result.rb +7 -3
  299. data/lib/phronomy/execution/timeout_error.rb +7 -0
  300. data/lib/phronomy/execution/wait_policy.rb +24 -0
  301. data/lib/phronomy/filter/filter_block_error.rb +14 -0
  302. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  303. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  304. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  305. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  306. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  307. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  308. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  309. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  310. data/lib/phronomy/llm_adapter/authentication_error.rb +9 -0
  311. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  312. data/lib/phronomy/llm_adapter/base.rb +49 -68
  313. data/lib/phronomy/llm_adapter/context_length_error.rb +9 -0
  314. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  315. data/lib/phronomy/llm_adapter/message.rb +62 -0
  316. data/lib/phronomy/llm_adapter/rate_limit_error.rb +9 -0
  317. data/lib/phronomy/llm_adapter/request.rb +79 -0
  318. data/lib/phronomy/llm_adapter/response.rb +36 -0
  319. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  320. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  321. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  322. data/lib/phronomy/llm_adapter.rb +3 -16
  323. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  324. data/lib/phronomy/multi_agent/admission_registry.rb +15 -2
  325. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  326. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  327. data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
  328. data/lib/phronomy/{agent → multi_agent}/handoff_capability_factory.rb +21 -2
  329. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  330. data/lib/phronomy/{agent → multi_agent}/handoff_policy.rb +1 -1
  331. data/lib/phronomy/multi_agent/handoff_runner.rb +156 -0
  332. data/lib/phronomy/{agent → multi_agent}/handoff_state.rb +2 -2
  333. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  334. data/lib/phronomy/multi_agent/persistence/codec.rb +75 -0
  335. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  336. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  337. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  338. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +124 -0
  339. data/lib/phronomy/multi_agent/persistence/team_repository.rb +69 -0
  340. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  341. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  342. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  343. data/lib/phronomy/multi_agent/store.rb +156 -0
  344. data/lib/phronomy/multi_agent/team_coordinator.rb +87 -75
  345. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  346. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +5 -5
  347. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  348. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  349. data/lib/phronomy/persistence/content_repository.rb +26 -0
  350. data/lib/phronomy/persistence/errors.rb +16 -0
  351. data/lib/phronomy/persistence/migration/initial_format_migration.rb +21 -21
  352. data/lib/phronomy/persistence/persistence.rb +23 -0
  353. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  354. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  355. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  356. data/lib/phronomy/persistence/transaction.rb +86 -0
  357. data/lib/phronomy/persistence_composition/storage_schema.rb +29 -0
  358. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  359. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  360. data/lib/phronomy/runtime_composition/agent_defaults.rb +11 -0
  361. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  362. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  363. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  364. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  365. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  366. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  367. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  368. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  369. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  370. data/lib/phronomy/storage/async/async_client.rb +60 -0
  371. data/lib/phronomy/storage/backend.rb +107 -0
  372. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  373. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  374. data/lib/phronomy/storage/blobs.rb +31 -0
  375. data/lib/phronomy/storage/condition.rb +25 -0
  376. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  377. data/lib/phronomy/storage/conflict_error.rb +9 -0
  378. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  379. data/lib/phronomy/storage/entry.rb +33 -0
  380. data/lib/phronomy/storage/guard_ref.rb +13 -0
  381. data/lib/phronomy/storage/not_found_error.rb +9 -0
  382. data/lib/phronomy/storage/record_codec.rb +177 -0
  383. data/lib/phronomy/storage/records.rb +61 -0
  384. data/lib/phronomy/storage/resource.rb +126 -0
  385. data/lib/phronomy/storage/scope.rb +25 -0
  386. data/lib/phronomy/storage/serialization_error.rb +9 -0
  387. data/lib/phronomy/storage/streams.rb +44 -0
  388. data/lib/phronomy/storage/transaction_error.rb +10 -0
  389. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  390. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  391. data/lib/phronomy/storage/validation.rb +53 -0
  392. data/lib/phronomy/storage/view.rb +131 -0
  393. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  394. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  395. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  396. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  397. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +54 -48
  398. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +1 -1
  399. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +1 -1
  400. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  401. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +52 -46
  402. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  403. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +124 -0
  404. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  405. data/lib/phronomy/tool/authorization.rb +77 -0
  406. data/lib/phronomy/tool/base.rb +549 -9
  407. data/lib/phronomy/tool/call_request.rb +40 -0
  408. data/lib/phronomy/{agent/tool_definition_set.rb → tool/definition_set.rb} +16 -12
  409. data/lib/phronomy/tool/operation.rb +88 -0
  410. data/lib/phronomy/tool/schema.rb +214 -0
  411. data/lib/phronomy/tool/settings.rb +31 -0
  412. data/lib/phronomy/tool/tool_error.rb +7 -0
  413. data/lib/phronomy/{agent → tool}/tool_executor.rb +10 -7
  414. data/lib/phronomy/tools/agent.rb +22 -90
  415. data/lib/phronomy/tools/mcp.rb +21 -14
  416. data/lib/phronomy/tools/vector_search.rb +4 -4
  417. data/lib/phronomy/tracing/automatic.rb +11 -6
  418. data/lib/phronomy/tracing/base.rb +1 -1
  419. data/lib/phronomy/tracing/observation.rb +37 -0
  420. data/lib/phronomy/tracing/settings.rb +36 -0
  421. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  422. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  423. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  424. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  425. data/lib/phronomy/vector_store/base.rb +118 -49
  426. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  427. data/lib/phronomy/vector_store.rb +2 -17
  428. data/lib/phronomy/version.rb +1 -1
  429. data/lib/phronomy/workflow/completion.rb +11 -0
  430. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
  431. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  432. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +5 -1
  433. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  434. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  435. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +186 -0
  436. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +145 -115
  437. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  438. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  439. data/lib/phronomy/workflow/persistence/state_repository.rb +63 -0
  440. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  441. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  442. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  443. data/lib/phronomy/workflow/runtime_binding/workflow_terminal_policy.rb +40 -0
  444. data/lib/phronomy.rb +97 -98
  445. data/scripts/api_snapshot.rb +36 -16
  446. data/scripts/storage_spi_snapshot.rb +36 -0
  447. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  448. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  449. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  450. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  451. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  452. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  453. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  454. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  455. data/sig/phronomy/agent/admission.rbs +19 -0
  456. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  457. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  458. data/sig/phronomy/agent/store.rbs +73 -0
  459. data/sig/phronomy/agent.rbs +14 -139
  460. data/sig/phronomy/common.rbs +7 -0
  461. data/sig/phronomy/content_store/contracts.rbs +13 -0
  462. data/sig/phronomy/context.rbs +152 -0
  463. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  464. data/sig/phronomy/embeddings/base.rbs +17 -0
  465. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  466. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  467. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  468. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  469. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  470. data/sig/phronomy/engine/observability.rbs +13 -0
  471. data/sig/phronomy/execution/async_operation.rbs +7 -0
  472. data/sig/phronomy/execution/blocking.rbs +5 -0
  473. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  474. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  475. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  476. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  477. data/sig/phronomy/execution/errors.rbs +7 -0
  478. data/sig/phronomy/execution/execution.rbs +41 -0
  479. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  480. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  481. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  482. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  483. data/sig/phronomy/execution/task_result.rbs +29 -0
  484. data/sig/phronomy/extensions.rbs +0 -21
  485. data/sig/phronomy/handoff.rbs +12 -4
  486. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  487. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  488. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  489. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  490. data/sig/phronomy/llm_context_window.rbs +10 -0
  491. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  492. data/sig/phronomy/multi_agent/store.rbs +54 -0
  493. data/sig/phronomy/multi_agent.rbs +24 -3
  494. data/sig/phronomy/persistence/errors.rbs +18 -0
  495. data/sig/phronomy/persistence/migration.rbs +13 -0
  496. data/sig/phronomy/persistence/transaction.rbs +19 -0
  497. data/sig/phronomy/persistence.rbs +3 -185
  498. data/sig/phronomy/persistence_composition.rbs +19 -0
  499. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  500. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  501. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  502. data/sig/phronomy/storage/contracts.rbs +160 -0
  503. data/sig/phronomy/tool.rbs +58 -5
  504. data/sig/phronomy/tracing/observation.rbs +7 -0
  505. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  506. data/sig/phronomy/vector_store/base.rbs +23 -0
  507. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  508. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  509. data/sig/phronomy/workflow/store.rbs +8 -0
  510. data/sig/phronomy/workflow.rbs +1 -1
  511. data/sig/phronomy.rbs +0 -49
  512. metadata +462 -130
  513. data/lib/phronomy/agent/agent_invocation_session_builder.rb +0 -584
  514. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  515. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  516. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  517. data/lib/phronomy/agent/context/capability/base.rb +0 -507
  518. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  519. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  520. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  521. data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
  522. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  523. data/lib/phronomy/agent/handoff_request.rb +0 -45
  524. data/lib/phronomy/agent/handoff_runner.rb +0 -178
  525. data/lib/phronomy/agent/phase_machine_builder.rb +0 -152
  526. data/lib/phronomy/agent/provider_call_outcome.rb +0 -106
  527. data/lib/phronomy/agent/recovery_support.rb +0 -504
  528. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  529. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  530. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  531. data/lib/phronomy/agent/tool_call_intercepted.rb +0 -29
  532. data/lib/phronomy/agent/tool_invocation_session_builder.rb +0 -276
  533. data/lib/phronomy/configuration.rb +0 -59
  534. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  535. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  536. data/lib/phronomy/generator_verifier.rb +0 -369
  537. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  538. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  539. data/lib/phronomy/metrics.rb +0 -26
  540. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  541. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  542. data/lib/phronomy/persistence/in_memory.rb +0 -690
  543. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  544. data/lib/phronomy/persistence.rb +0 -276
  545. data/lib/phronomy/recovery.rb +0 -186
  546. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  547. data/lib/phronomy/runnable.rb +0 -54
  548. data/lib/phronomy/token_usage.rb +0 -85
  549. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  550. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  551. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  552. data/lib/phronomy/workflow_recovery.rb +0 -123
  553. data/sig/phronomy/llm_adapter.rbs +0 -11
  554. data/sig/phronomy/runtime.rbs +0 -76
  555. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  556. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  557. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
@@ -22,10 +22,10 @@ module Phronomy
22
22
 
23
23
  begin
24
24
  unless invocation_context.nil?
25
- binding = Execution.__operation_binding(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,
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ class CancellationError < Error; end
7
+ end
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "worker_input_restricted"
4
+
3
5
  module Phronomy
4
6
  module Concurrency
5
7
  # Cooperative cancellation token for Agent/Tool work.
6
8
  class CancellationToken
9
+ include Phronomy::Concurrency::WorkerInputRestricted
10
+
7
11
  # Creates a token that expires after +seconds+ measured with the monotonic clock.
8
12
  # @api public
9
13
  def self.timeout_after(seconds)
@@ -94,7 +98,7 @@ module Phronomy
94
98
  def deliver_cancel_callback(callback)
95
99
  callback.call
96
100
  rescue => error
97
- Phronomy.configuration.logger&.error do
101
+ Phronomy::RuntimeSettings.current.logger&.error do
98
102
  "[CancellationToken] on_cancel callback raised #{error.class}: #{error.message}"
99
103
  end
100
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
@@ -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
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Concurrency
5
+ # Marks framework-managed objects excluded from authorization worker value
6
+ # snapshots and application behavior handles. Each owning type opts in.
7
+ # This methodless contract does not validate arbitrary object graphs or
8
+ # restrict every OffloadPool command. Application-owned opaque values retain
9
+ # their existing contract (ADR-024/045).
10
+ # @api private
11
+ module WorkerInputRestricted
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ # Raised when a synchronous API would block the EventLoop control thread.
7
+ class EventLoopReentrancyError < Error; end
8
+ 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,17 +69,29 @@ 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
62
79
 
63
- # Common admission binding for Agent and synchronous-work adapters.
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.
64
84
  # @api private
65
- def self.__operation_binding(invocation_context:, cancellation_token:)
66
- Concurrency::OperationBinding.new(invocation_context: invocation_context,
85
+ def start_child(invocation_context: nil, cancellation_token: nil, parent_task_id: nil)
86
+ controls = InvocationControls.new(invocation_context: @invocation_context,
67
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
68
95
  end
69
96
 
70
97
  # A scoped observation copy. The source, its controls and its physical work
@@ -77,7 +104,7 @@ module Phronomy
77
104
  unless source_result.is_a?(TaskResult)
78
105
  raise TypeError, "source_result must be a Phronomy::TaskResult"
79
106
  end
80
- observed = TaskResult.deferred(name: "execution-observation").__bind_execution(self)
107
+ observed = TaskResult.deferred(name: "execution-observation").__bind_execution(@scope)
81
108
  subscriptions = Concurrency::Subscriptions.new
82
109
  subscriptions.result(observed) { subscriptions.close }
83
110
  unless __open?
@@ -103,12 +130,12 @@ module Phronomy
103
130
  # Scope/admission hooks shared with framework-owned operations.
104
131
  # @api private
105
132
  def __while_open(&block)
106
- @collector.while_open(&block)
133
+ @scope.__while_open(&block)
107
134
  end
108
135
 
109
136
  # @api private
110
137
  def __open?
111
- @collector.open?
138
+ @scope.__open?
112
139
  end
113
140
 
114
141
  # @api private
@@ -118,7 +145,7 @@ module Phronomy
118
145
 
119
146
  # @api private
120
147
  def __cancellation_error
121
- @cancellation_error
148
+ @scope.__cancellation_error
122
149
  end
123
150
 
124
151
  private
@@ -133,9 +160,6 @@ module Phronomy
133
160
  @parent_token = cancellation_token
134
161
  @token = Concurrency::CancellationToken.new
135
162
  @subscriptions = Concurrency::Subscriptions.new
136
- @cancellation_error = CancellationError.new("Execution scope is closed")
137
- @invocation_context = (invocation_context || InvocationContext.new)
138
- .merge(cancellation_token: @token).__bind_execution(self)
139
163
  @result = TaskResult.deferred(name: "execution")
140
164
  @pump_mutex = Mutex.new
141
165
  @next_index = 0
@@ -145,6 +169,9 @@ module Phronomy
145
169
  @collector = Concurrency::ResultCollector.new(inputs.length) do |kind, outcomes|
146
170
  finish(kind, outcomes)
147
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)
148
175
  end
149
176
 
150
177
  def start
@@ -221,12 +248,12 @@ module Phronomy
221
248
  when :completed
222
249
  @result.complete(outcomes)
223
250
  when :timeout
224
- @cancellation_error = CancellationError.new("Execution deadline exceeded")
251
+ @scope.__set_cancellation_error(CancellationError.new("Execution deadline exceeded"))
225
252
  error = ExecutionTimeoutError.new(outcomes: outcomes)
226
253
  @token.cancel!
227
254
  @result.fail(error)
228
255
  when :cancelled
229
- @cancellation_error = CancellationError.new("Execution cancelled")
256
+ @scope.__set_cancellation_error(CancellationError.new("Execution cancelled"))
230
257
  error = ExecutionCancellationError.new(outcomes: outcomes)
231
258
  @token.cancel!
232
259
  @result.cancel!(error)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "cancellation_error"
4
+
3
5
  module Phronomy
4
6
  class ExecutionCancellationError < CancellationError
5
7
  attr_reader :outcomes
@@ -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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "timeout_error"
4
+
3
5
  module Phronomy
4
6
  class ExecutionTimeoutError < TimeoutError
5
7
  attr_reader :outcomes
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "invalid_async_workflow_action_error"
4
+
3
5
  module Phronomy
4
6
  # Raised when a synchronous FSM entry action returns Phronomy::TaskResult.
5
7
  #
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "invalid_async_workflow_action_error"
4
+
3
5
  module Phronomy
4
6
  # Raised when a synchronous Workflow transition action returns Phronomy::TaskResult.
5
7
  #
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../common/error"
4
+
3
5
  module Phronomy
4
6
  # Base error for synchronous Workflow callbacks that return Phronomy::TaskResult.
5
7
  #
@@ -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