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
@@ -23,37 +23,38 @@ are defined by
23
23
 
24
24
  ## Runtime model
25
25
 
26
- ```text
27
- Runtime
28
- ├─ Agent ownership registry
29
- │ └─ agent_id -> one mutable live Agent instance
30
- ├─ EventLoop (one control-plane operating-system Thread)
31
- │ ├─ FSMSession
32
- │ │ ├─ Agent
33
- │ │ ├─ Workflow
34
- │ │ ├─ ToolInvocation
35
- │ │ └─ MultiAgent fan-out
36
- │ ├─ Agent top-level admission
37
- │ │ └─ agent_id -> one nonterminal logical Execution admission
38
- │ └─ Agent execution directory
39
- │ └─ execution_id -> immutable live-state record
40
- ├─ OffloadPool (bounded operating-system Threads)
41
- │ ├─ private Operation records
42
- │ ├─ blocking input/output (I/O)
43
- │ ├─ central-processing-unit (CPU)-bound synchronous work
44
- │ └─ operation-specific durable Agent/Workflow work
45
- ├─ named OffloadPools
46
- └─ EventLoop-driven timers
47
-
48
- EventLoop / FSMSession ─┐
49
- ├─> TaskResult = completion handle
50
- OffloadPool ────────────┘
51
- ```
26
+ | Runtime-retained component | Implementation owner and responsibility |
27
+ |---|---|
28
+ | Shutdown participants | Feature-owned registries: Agent identity, Team identity, and MultiAgent synchronous-call admission |
29
+ | EventLoop | Engine dispatcher thread, FSMSessions and registered feature receivers |
30
+ | Execution receivers | Agent/Workflow own execution admission, state and idle policies; mutations run on EventLoop |
31
+ | OffloadPool and named pools | Engine bounded workers and private physical-operation records |
32
+ | Timers | Engine EventLoop-driven scheduling |
33
+
34
+ Runtime retains participants through a generic lifecycle protocol; it does not
35
+ construct Agent or Team identity registries. Each feature registers its own
36
+ instance. See [ADR-041](decisions/041-feature-owned-identity-registries.md).
52
37
 
53
38
  The framework does not allocate one operating-system Thread per logical
54
39
  Agent/Workflow/Tool lifecycle. Logical waits remain explicit states plus later
55
40
  EventLoop events.
56
41
 
42
+ ## Feature execution receivers
43
+
44
+ [ADR-042](decisions/042-feature-owned-execution-state.md) separates implementation
45
+ ownership from the single-writer execution context. Agent and Workflow create
46
+ `Agent::ExecutionRegistry` and `WorkflowExecutionRegistry` respectively; Engine
47
+ retains registered receivers without selecting concrete feature types.
48
+
49
+ Receiver registration and new request admission close before idle checks.
50
+ Already queued requests, pending durable operations and supervised physical
51
+ work are counted until their feature continuation settles. Worker callbacks
52
+ only enqueue results. Workflow instance routing and generic FSM enqueue share
53
+ one lock. After a clean join, receiver cleanup invalidates retained state;
54
+ a failing receiver makes Runtime cleanup incomplete. Dispatcher failure instead
55
+ notifies receivers on the failing loop thread with the original error.
56
+
57
+
57
58
  ## Live state and durable state
58
59
 
59
60
  A live Agent or Workflow owns its current logical state. `Persistence` is the
@@ -61,7 +62,7 @@ last committed durable representation and recovery source; it is not reloaded at
61
62
  every semantic boundary.
62
63
 
63
64
  For active Agents, **EventLoop is the single writer of Phronomy-managed live
64
- execution state**. EventLoop owns a process-local execution directory keyed by
65
+ execution state**. `Agent::ExecutionRegistry` owns the process-local directory keyed by
65
66
  canonical `execution_id`. Each directory value is immutable and is replaced on
66
67
  EventLoop when the current AgentExecution, RuntimeProjection, AgentInvocation, or
67
68
  owning FSMSession changes. The former mutex-protected
@@ -75,7 +76,7 @@ handling; workers do not receive AgentInvocation as a mutable state authority.
75
76
  Mutable Agent/Execution/Journal state is not automatically reloaded before every
76
77
  LLM or Tool step. Durable writes use optimistic revision/position guardrails; an
77
78
  external writer that advances the durable base causes
78
- `Persistence::ConflictError` rather than automatic reload or merge.
79
+ `Storage::ConflictError` rather than automatic reload or merge.
79
80
 
80
81
  For Workflows, the current `WorkflowContext` and FSMSession own the active
81
82
  logical state. A durable Workflow hydrates once at invocation/resume and saves at
@@ -88,8 +89,9 @@ content reference is value materialization rather than mutable state refresh.
88
89
 
89
90
  `agent_id` identifies one logical Agent, not a reusable lookup key for independent
90
91
  mutable objects. One Runtime therefore publishes at most one mutable live Agent
91
- instance for a given `agent_id`. The Runtime-owned registry is an authority, not a
92
- cache, and reserves the identity before create/load materialization.
92
+ instance for a given `agent_id`. The Agent-owned registry, strongly retained by
93
+ Runtime, is an authority, not a cache, and reserves the identity before
94
+ create/load materialization.
93
95
 
94
96
  The application-facing identity operations are distinct:
95
97
 
@@ -100,7 +102,7 @@ new / create
100
102
  load(agent_id, persistence:)
101
103
  live -> exact same Ruby object, with no Persistence reload
102
104
  durable-only -> hydrate and publish once
103
- missing -> Persistence::NotFoundError
105
+ missing -> Storage::NotFoundError
104
106
 
105
107
  get(agent_id)
106
108
  live Runtime lookup only; missing -> nil
@@ -114,7 +116,8 @@ invalidates the old object, deletes durable state, releases the process-local
114
116
  identity, and allows a later new Agent to reuse the textual ID.
115
117
 
116
118
  Live Agent ownership and top-level Execution admission are separate lifetimes.
117
- For one live Agent, EventLoop admits at most one nonterminal top-level Execution.
119
+ For one live Agent, `Agent::ExecutionRegistry` admits at most one nonterminal
120
+ top-level Execution on EventLoop.
118
121
  Admission is acquired **before** the initial Offload/Persistence operation:
119
122
 
120
123
  ```text
@@ -195,6 +198,15 @@ Phronomy-managed live domain objects are rejected from that value data. A comple
195
198
  value-type/serialization contract for arbitrary Application-owned opaque objects is
196
199
  deferred; such objects remain Application-owned and must be worker-safe.
197
200
 
201
+ The existing restricted types declare the internal methodless
202
+ `Concurrency::WorkerInputRestricted` marker at their own definitions.
203
+ ToolInvocation checks that execution-boundary contract instead of enumerating
204
+ Agent/Workflow/Runtime classes. Frozen marked values and subclasses remain
205
+ restricted; Hash keys and values, nested Arrays and behavior handles are all
206
+ checked. The marker does not inspect opaque application fields or closure
207
+ captures and is not a general restriction on every OffloadPool command.
208
+ See [ADR-045](decisions/045-worker-input-restriction-ownership.md).
209
+
198
210
  Worker authorization/execution outcomes return as values carrying
199
211
  `tool_invocation_id`; the Tool FSMSession consumes a mismatched semantic result
200
212
  without advancing its current state.
@@ -279,6 +291,32 @@ remain durable conflict defense rather than distributed ownership.
279
291
 
280
292
  ## Tool execution modes
281
293
 
294
+ The default dispatch helper is the private
295
+ `Phronomy::Tool::ToolExecutor`, colocated with Capability
296
+ Base. Agent ToolInvocation supplies Runtime and admission policy for the
297
+ standard path, and owns authorization and logical result handling. Custom
298
+ `call_async` implementations keep the public Tool protocol. This ownership is
299
+ defined by [ADR-035](decisions/035-tool-executor-capability-ownership.md); the
300
+ public `Phronomy::Tool::Base` facade and its Class identity remain unchanged.
301
+
302
+ Agent uses ordinary `RubyLLM::Chat` for both complete and streaming Provider
303
+ calls. The `before_tool_call` callback takes the complete assistant Tool-call
304
+ batch before RubyLLM executes the first Tool body. `AgentInvocation` then owns
305
+ authorization, dispatch, approval suspension and result collection. Offloaded
306
+ Tools can overlap within Runtime capacity; short cooperative Tools run according
307
+ to their existing execution contract.
308
+
309
+ On successful batch completion, Agent records one Tool result per call ID in
310
+ request order and includes the whole exchange in the next Provider request.
311
+ Worker completion order does not split the conversation or trigger a partial
312
+ Provider continuation. Failure, cancellation and approval keep their existing
313
+ Agent lifecycle semantics.
314
+
315
+ There is no separate Chat execution path or `parallel_tool_execution` switch.
316
+ The removed internal `MultiAgent::ParallelToolChat` class is not replaced by an
317
+ alias. See the [Chat migration guide](migrations/parallel-tool-chat-removal.md)
318
+ for application configuration and existing stored model-config records.
319
+
282
320
  Phronomy exposes two execution modes for capabilities:
283
321
 
284
322
  - `:cooperative` — short EventLoop-safe work, or specialized asynchronous work
@@ -539,14 +577,86 @@ TaskResult reports only caller-facing settlement.
539
577
  ## EventLoop metrics
540
578
 
541
579
  `Phronomy::Metrics.snapshot` also reports EventLoop queue depth and lag values.
580
+
581
+ Metrics and Diagnostics only observe resources already owned by the default
582
+ Runtime. Calling `Metrics.snapshot`, `Diagnostics.snapshot` or `Diagnostics.dump`
583
+ does not initialize a Runtime, pool, EventLoop or timer. Missing resources report
584
+ zero values, including `offload_pool_size: 0` when no default pool exists. Once a
585
+ pool exists, this field remains its configured capacity, not a live-worker count.
586
+ Named pools are not aggregated into the default-pool metrics.
587
+
588
+ Retained resources remain observable during and after shutdown, including
589
+ incomplete cleanup. Observation does not reopen admission or change a shutdown
590
+ result. After the default Runtime is reset, missing resources again report zero;
591
+ the next snapshot uses any newly initialized default Runtime. Counters are sampled
592
+ independently and do not constitute an atomic cross-resource snapshot.
542
593
  Use these to distinguish worker saturation from EventLoop backlog/latency.
543
594
 
544
595
  ## Shutdown
545
596
 
597
+ Pool creation and registration close atomically before the shutdown resource set
598
+ is captured. Accepted continuations can still acquire resources during Runtime's
599
+ draining phase. Once resource shutdown begins, all pools close admission before
600
+ any pool waits for its workers.
601
+
602
+ The stop-phase monotonic deadline is shared by EventLoop and every pool join.
603
+ A join timeout does not stop a synchronous worker. Runtime reports incomplete
604
+ cleanup while any worker remains, even if that worker's TaskResult has already
605
+ been cancelled or timed out. It does not forcefully interrupt worker threads.
606
+ See [R8 unit19](architecture/r8-unit19.md) and its
607
+ [migration notes](migrations/r8-unit19.md) for the shutdown budget and result rules.
608
+
546
609
  `Runtime#shutdown` is terminal for that Runtime. It drains/terminates the
547
610
  Runtime-owned EventLoop, then closes pools and timers according to the Runtime
548
611
  shutdown contract.
549
612
 
613
+ Internal subsystems can register one shutdown participant per Runtime-local key
614
+ through `Runtime#__register_shutdown_participant`. This is an internal lifecycle
615
+ contract, not a public extension API or an execution registry. Runtime does not
616
+ construct participants or interpret their admission rules.
617
+
618
+ Each participant implements two required operations:
619
+
620
+ - `begin_draining` closes admission under the participant's admission lock. It
621
+ must be idempotent, short and nonblocking, and must not call back into Runtime.
622
+ Runtime invokes it under its lifecycle lock when shutdown starts or EventLoop
623
+ fails, so no participant can be registered after closure begins.
624
+ - `wait_until_idle(deadline)` returns whether admitted calls have finished before
625
+ the shared absolute monotonic deadline. Runtime closes every participant before
626
+ waiting, and waits without holding its lifecycle lock. A timeout or failed hook
627
+ makes cleanup incomplete; other participants, pools and timers still receive
628
+ their shutdown calls. Incomplete cleanup prevents default Runtime replacement.
629
+
630
+ Participants may also implement `after_runtime_shutdown`. Runtime calls it
631
+ outside its lifecycle lock after every wait succeeds, EventLoop has stopped,
632
+ and pools/timers have shut down successfully. The hook must be idempotent,
633
+ short, and perform no I/O; its return value is ignored. A hook exception marks
634
+ cleanup incomplete, retains the first failure, and does not skip later hooks.
635
+ Completed releases are not rolled back; repeated shutdown returns its cached
636
+ result without retrying hooks. A prior closure/wait failure, timeout, or worker
637
+ shutdown failure skips finalization entirely.
638
+
639
+ `Runtime#__shutdown_participant(key:)` only retrieves an existing participant,
640
+ including during/after shutdown. Registration still requires a running Runtime,
641
+ even for an existing key. Agent/Team `get` uses lookup without registration;
642
+ feature gates reject new ownership changes through retained registries after
643
+ closure. Already admitted transitions may finish. Identity registries wait only
644
+ for active construction/purge, then detach Agent references or clear Team
645
+ owners at finalization; live idle objects do not themselves prolong shutdown.
646
+
647
+ `MultiAgent::AdmissionRegistry.for(runtime)` constructs and registers the shared
648
+ coordination admission registry. MultiAgent::HandoffRunner and TeamCoordinator retain that
649
+ registry, admit each synchronous call directly and release it in `ensure` after
650
+ successful admission. The registry closes admission atomically with respect to
651
+ `admit!`; a call admitted before closure is included in the wait, and a call after
652
+ closure raises `RuntimeShutdownError`, including after EventLoop failure. The
653
+ existing `HandoffError` for duplicate admission remains unchanged.
654
+
655
+ This tracks the current synchronous Handoff/Team call, not the lifetime or
656
+ completion of a durable AgentExecution or TeamExecution. Multi-Agent policy stays
657
+ in `multi_agent`; Runtime depends only on the shutdown operations. Application
658
+ authors do not register these participants themselves.
659
+
550
660
  Workflow durable admission participates in EventLoop idleness: a Workflow whose
551
661
  FSMSession has ended but whose durable save is still in flight remains owned until
552
662
  that save completes and owner-aware admission is released.
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Phronomy
6
+ module Agent
7
+ # The short durable phase of an Agent's already acquired runtime admission.
8
+ # A coordinator may join it to its reservation check; it cannot start an FSM,
9
+ # publish a provisional result or make the Agent interpret a parent's schema.
10
+ # @api public
11
+ class Admission
12
+ attr_reader :agent_id, :execution_id, :correlation
13
+
14
+ # Constructed by Agent execution, never restored from a durable record.
15
+ # @api private
16
+ def initialize(persistence:, root:, input:, config:, preparation_metadata:)
17
+ @persistence, @root, @input, @config = persistence, root, input, config
18
+ @preparation_metadata = preparation_metadata
19
+ @agent_id = root.agent_id
20
+ @execution_id = (config[:phronomy_reserved_execution_id] || SecureRandom.uuid).to_s.freeze
21
+ @correlation = Phronomy::Values::Immutable.copy(config[:phronomy_reservation])
22
+ @thread = Thread.current
23
+ end
24
+
25
+ # Own a standalone admission transaction.
26
+ # @api public
27
+ def accept
28
+ @persistence.coordinator.atomic { |scope| accept_in(scope) }
29
+ nil
30
+ end
31
+
32
+ # Participate without committing or publishing live state.
33
+ # @api public
34
+ def accept_in(scope)
35
+ check_thread!
36
+ raise Phronomy::ConfigurationError, "Agent admission is single-use" if @scope
37
+ @scope = scope
38
+ @persistence.participate(scope) do |records|
39
+ raise Phronomy::Error, "agent is closed: #{agent_id}" if @root.lifecycle_status == :closed
40
+ input_ref = records.contents.put_text(@input)
41
+ context_ref = records.contents.put_json(@config.fetch(:durable_context)) if @config.key?(:durable_context)
42
+ input_record = JournalRecord.new(agent_id: agent_id, kind: :input_received,
43
+ channel: :external, role: :user, content_ref: input_ref,
44
+ context_generation: @root.transcript_generation, context_candidate: false)
45
+ execution = AgentExecution.start(agent_root: @root, input_record: input_record,
46
+ execution_id: execution_id, metadata: {
47
+ "reservation" => correlation, "current_input_ref" => input_ref,
48
+ "durable_context_ref" => context_ref,
49
+ "execution_extension" => @config[:phronomy_execution_participant]&.binding&.to_h
50
+ }.merge(@preparation_metadata).compact)
51
+ input_record = JournalRecord.from_h(input_record.to_h.merge("execution_id" => execution_id))
52
+ execution = execution.with(execution_revision: 0, working_records: [input_record])
53
+ records.executions.create_active(execution)
54
+ next_root = @root.with(agent_revision: @root.agent_revision + 1, lifecycle_status: :active)
55
+ records.agents.save(agent_id, expected_revision: @root.agent_revision, root: next_root)
56
+ @result = [execution, next_root].freeze
57
+ end
58
+ nil
59
+ end
60
+
61
+ # Available to Agent execution only after the outermost successful response.
62
+ # @api private
63
+ def result
64
+ check_thread!
65
+ unless @result && @scope&.committed?
66
+ raise Phronomy::Persistence::TransactionError, "Agent admission has not committed"
67
+ end
68
+ @result
69
+ end
70
+
71
+ private
72
+
73
+ def check_thread!
74
+ raise Phronomy::Persistence::TransactionError, "Agent admission belongs to another execution context" unless @thread.equal?(Thread.current)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../common/error"
4
+
3
5
  module Phronomy
4
6
  class AgentAlreadyExistsError < Phronomy::Error; end
5
7
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../common/error"
4
+
3
5
  module Phronomy
4
6
  class AgentBusyError < Phronomy::Error; end
5
7
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../common/error"
4
+
3
5
  module Phronomy
4
6
  class AgentPurgedError < Phronomy::Error; end
5
7
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ StreamEvent = Data.define(:type, :payload)
6
+ end
7
+ end
8
+
9
+ require_relative "../async_event_api"
10
+
11
+ unless Phronomy::Agent::Base < Phronomy::Agent::AsyncEventApi
12
+ Phronomy::Agent::Base.prepend(Phronomy::Agent::AsyncEventApi)
13
+ end
14
+
15
+ # Durable Agent Recovery and Agent-incarnation event binding.
16
+ require_relative "../recovery/recovery_support"
17
+ require_relative "../recovery/recovery_coordinator"
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../common/error"
4
+
3
5
  module Phronomy
4
6
  class ExecutionRehydrationRequiredError < Phronomy::Error; end
5
7
  end
@@ -12,6 +12,35 @@ module Phronomy
12
12
  # - approval notifications share the Agent listener as :approval_required;
13
13
  # - Recovery resolution uses resolve/resolve_async.
14
14
  module AsyncEventApi
15
+ def start_reserved_async(reservation:, input:, config: {})
16
+ unless reservation.is_a?(ReservedExecution) && reservation.agent_id == agent_id
17
+ raise ArgumentError, "reservation must belong to this Agent"
18
+ end
19
+ config = __invocation_config(_prepare_invocation_config(config, nil))
20
+ ExactExecution.start(agent: self, execution_id: reservation.execution_id, input: input,
21
+ config: config.merge(phronomy_reservation: reservation.correlation))
22
+ end
23
+
24
+ def resume_async(execution_id, config: {})
25
+ config = __invocation_config(_prepare_invocation_config(config, nil))
26
+ ExactExecution.start(agent: self, execution_id: execution_id, input: nil, config: config, resume_only: true)
27
+ end
28
+
29
+ def resume(execution_id, config: {})
30
+ _check_event_loop_reentrancy(:resume, :resume_async)
31
+ resume_async(execution_id, config: config).wait_result
32
+ end
33
+
34
+ def cancel_async(execution_id)
35
+ __assert_live_agent!
36
+ environment = __execution_environment
37
+ environment.submit(on_full: :raise) do
38
+ requested = persistence.request_cancellation(agent_id: agent_id, execution_id: execution_id)
39
+ ExecutionCancellation.signal(execution_id, agent_id, environment: environment) if requested
40
+ persistence.observe_execution(agent_id: agent_id, execution_id: execution_id)
41
+ end
42
+ end
43
+
15
44
  def invoke(
16
45
  input,
17
46
  config: {},
@@ -96,7 +125,7 @@ module Phronomy
96
125
 
97
126
  def approve_async(execution_id, approval_request_id:, approved: true, config: {})
98
127
  _reject_removed_generic_identity_keys!(config)
99
- owner = Phronomy::Runtime.instance.__agent_execution_owner(execution_id)
128
+ owner = Phronomy::Agent::ExecutionRegistry.existing_current&.agent_execution_owner(execution_id)
100
129
  coordinator = if owner&.agent&.equal?(self)
101
130
  owner.coordinator
102
131
  else
@@ -115,8 +144,8 @@ module Phronomy
115
144
  expected_execution_revision:,
116
145
  subject:,
117
146
  outcome:,
118
- result: Phronomy::Recovery::MISSING,
119
- error: Phronomy::Recovery::MISSING
147
+ result: Phronomy::Agent::RecoveryRules::MISSING,
148
+ error: Phronomy::Agent::RecoveryRules::MISSING
120
149
  )
121
150
  _check_event_loop_reentrancy(:resolve, :resolve_async)
122
151
  resolve_async(
@@ -134,8 +163,8 @@ module Phronomy
134
163
  expected_execution_revision:,
135
164
  subject:,
136
165
  outcome:,
137
- result: Phronomy::Recovery::MISSING,
138
- error: Phronomy::Recovery::MISSING
166
+ result: Phronomy::Agent::RecoveryRules::MISSING,
167
+ error: Phronomy::Agent::RecoveryRules::MISSING
139
168
  )
140
169
  Phronomy::Agent::RecoveryCoordinator.new(self).resolve(
141
170
  execution_id,
@@ -149,26 +178,9 @@ module Phronomy
149
178
 
150
179
  private
151
180
 
152
- # Framework-private execution-local event routing. This is intentionally
153
- # not a public compatibility path for invoke_async(..., on_event:).
154
- def __invoke_async_with_event_sink(
155
- input,
156
- on_event:, config: {},
157
- invocation_context: nil
158
- )
159
- raise ArgumentError, "on_event is required" unless on_event
160
-
161
- config = _prepare_invocation_config(config, invocation_context)
162
- _start_agent_operation(
163
- input,
164
- config: config,
165
- mode: :invoke,
166
- listener: on_event
167
- )
168
- end
169
-
170
181
  def _start_agent_operation(input, config:, mode:, listener:)
171
- config = _snapshot_durable_context(config)
182
+ config = __invocation_config(_snapshot_durable_context(config))
183
+ @_phronomy_execution_wiring = config.dup.freeze
172
184
  approval = _approval_configuration_snapshot(nil)
173
185
  execution_coordinator_for(config).start(
174
186
  input,
@@ -190,7 +202,7 @@ module Phronomy
190
202
  end
191
203
 
192
204
  bytes = Phronomy::CanonicalJSON.dump(value)
193
- snapshot = Phronomy::Agent::Immutable.copy(
205
+ snapshot = Phronomy::Values::Immutable.copy(
194
206
  Phronomy::CanonicalJSON.load(bytes)
195
207
  )
196
208
  config.merge(durable_context: snapshot)
@@ -220,14 +232,7 @@ module Phronomy
220
232
  @execution_coordinator ||= Agent::ExecutionCoordinator.new(self)
221
233
  end
222
234
 
223
- def execution_coordinator_for(config)
224
- multi_agent = config.key?(:phronomy_handoff_bindings) ||
225
- config.key?(:phronomy_handoff_context)
226
- return execution_coordinator unless multi_agent
227
-
228
- @multi_agent_execution_coordinator ||=
229
- Phronomy::Agent::HandoffExecutionCoordinator.new(self)
230
- end
235
+ def execution_coordinator_for(_config) = execution_coordinator
231
236
  end
232
237
  end
233
238
  end