phronomy 0.25.0 → 0.27.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 (323) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -0
  3. data/CONTRIBUTING.md +4 -4
  4. data/README.md +14 -7
  5. data/VERIFY.sh +27 -27
  6. data/benchmark/bench_agent_invoke.rb +26 -22
  7. data/benchmark/bench_context_assembler.rb +4 -5
  8. data/benchmark/bench_regression.rb +1 -1
  9. data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
  10. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  11. data/docs/architecture/agent-context.md +5 -3
  12. data/docs/architecture/agent-transition-ownership.md +89 -0
  13. data/docs/architecture/before-llm-input.md +6 -0
  14. data/docs/architecture/context-management.md +30 -0
  15. data/docs/architecture/context-preparation-steps.md +90 -0
  16. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  17. data/docs/architecture/execution-metadata-and-values.md +80 -0
  18. data/docs/architecture/generator-verifier-ownership.md +111 -0
  19. data/docs/architecture/multi-agent-handoff.md +8 -2
  20. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  21. data/docs/architecture/persistence.md +29 -6
  22. data/docs/architecture/refactoring-closure.md +334 -0
  23. data/docs/architecture/remaining-refactoring-plan.md +374 -0
  24. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  25. data/docs/architecture/tool-schema-recording-gap.md +50 -0
  26. data/docs/architecture/tracing.md +4 -4
  27. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  28. data/docs/architecture.md +209 -0
  29. data/docs/async-composition.md +205 -0
  30. data/docs/decisions/010-cooperative-first-concurrency.md +23 -23
  31. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  32. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  33. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  34. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  35. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
  36. data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
  37. data/docs/decisions/032-storage-backend-composition.md +80 -0
  38. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  39. data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -0
  40. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  41. data/docs/decisions/036-context-contract-ownership.md +86 -0
  42. data/docs/decisions/037-common-definition-ownership.md +62 -0
  43. data/docs/decisions/038-responsibility-based-source-layout.md +131 -0
  44. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
  45. data/docs/decisions/040-configuration-default-composition.md +94 -0
  46. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  47. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  48. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  49. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  50. data/docs/decisions/045-worker-input-restriction-ownership.md +100 -0
  51. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  52. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  53. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  54. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  55. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  56. data/docs/decisions/051-execution-outcome-worker-ownership.md +151 -0
  57. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  58. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  59. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  60. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  61. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  62. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  63. data/docs/decisions/058-neutral-storage-primitives.md +78 -0
  64. data/docs/decisions/README.md +35 -6
  65. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  66. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  67. data/docs/features.md +21 -12
  68. data/docs/getting-started.md +10 -10
  69. data/docs/migrations/0.15.md +5 -0
  70. data/docs/migrations/durable-semantic-coordination-v2.md +7 -1
  71. data/docs/migrations/handoff-runner-multi-agent.md +35 -0
  72. data/docs/migrations/neutral-storage-spi.md +43 -0
  73. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  74. data/docs/migrations/shared-state-multi-agent.md +44 -0
  75. data/docs/migrations/storage-backend-composition.md +117 -0
  76. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  77. data/docs/persistence-backends.md +162 -204
  78. data/docs/runtime-and-concurrency.md +205 -60
  79. data/lib/phronomy/agent/api/agent.rb +17 -0
  80. data/lib/phronomy/agent/async_event_api.rb +2 -2
  81. data/lib/phronomy/agent/base.rb +55 -251
  82. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  83. data/lib/phronomy/agent/context/capability/base.rb +67 -26
  84. data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
  85. data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
  86. data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
  87. data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
  88. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
  89. data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
  90. data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
  91. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
  92. data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
  93. data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
  94. data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
  95. data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
  96. data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
  97. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  98. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
  99. data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +20 -96
  100. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  101. data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
  102. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +12 -13
  103. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
  104. data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
  105. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  106. data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
  107. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
  108. data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
  109. data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
  110. data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
  111. data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
  112. data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +22 -73
  113. data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
  114. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  115. data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
  116. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
  117. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
  118. data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
  119. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  120. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  121. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  122. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  123. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  124. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -10
  125. data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
  126. data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
  127. data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
  128. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
  129. data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
  130. data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
  131. data/lib/phronomy/agent/persistence/codec.rb +358 -0
  132. data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
  133. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
  134. data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
  135. data/lib/phronomy/agent/persistence/queries.rb +61 -0
  136. data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
  137. data/lib/phronomy/agent/persistence/watermark.rb +27 -0
  138. data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
  139. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
  140. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +36 -41
  141. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
  142. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +9 -13
  143. data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
  144. data/lib/phronomy/agent/selection/candidate.rb +1 -1
  145. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  146. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  147. data/lib/phronomy/agent/tool_execution/tool_binding.rb +90 -0
  148. data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
  149. data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
  150. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
  151. data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +2 -2
  152. data/lib/phronomy/common/configuration_error.rb +7 -0
  153. data/lib/phronomy/common/error.rb +5 -0
  154. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  155. data/lib/phronomy/common/values/serializable.rb +32 -0
  156. data/lib/phronomy/{configuration.rb → configuration/configuration.rb} +14 -5
  157. data/lib/phronomy/configuration/global_configuration.rb +26 -0
  158. data/lib/phronomy/content_store/storage_schema.rb +11 -0
  159. data/lib/phronomy/content_store/stored_contents.rb +43 -0
  160. data/lib/phronomy/engine/backpressure_error.rb +7 -0
  161. data/lib/phronomy/{blocking.rb → engine/blocking.rb} +15 -8
  162. data/lib/phronomy/engine/cancellation_error.rb +7 -0
  163. data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
  164. data/lib/phronomy/engine/concurrency/offload_pool.rb +29 -16
  165. data/lib/phronomy/engine/concurrency/operation_binding.rb +43 -0
  166. data/lib/phronomy/engine/concurrency/physical_completion_task.rb +4 -62
  167. data/lib/phronomy/engine/concurrency/result_collector.rb +99 -0
  168. data/lib/phronomy/engine/concurrency/result_composition.rb +145 -0
  169. data/lib/phronomy/engine/concurrency/subscriptions.rb +68 -0
  170. data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
  171. data/lib/phronomy/engine/event_loop.rb +160 -626
  172. data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
  173. data/lib/phronomy/engine/execution.rb +229 -0
  174. data/lib/phronomy/engine/execution_cancellation_error.rb +14 -0
  175. data/lib/phronomy/engine/execution_receiver.rb +65 -0
  176. data/lib/phronomy/engine/execution_timeout_error.rb +14 -0
  177. data/lib/phronomy/engine/fsm_protocol.rb +14 -0
  178. data/lib/phronomy/engine/fsm_session.rb +38 -31
  179. data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +3 -1
  180. data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +3 -1
  181. data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +3 -1
  182. data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +13 -1
  183. data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
  184. data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
  185. data/lib/phronomy/engine/runtime/timer_queue.rb +11 -0
  186. data/lib/phronomy/engine/runtime.rb +80 -84
  187. data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
  188. data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
  189. data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
  190. data/lib/phronomy/engine/{task.rb → task_result.rb} +101 -42
  191. data/lib/phronomy/engine/timeout_error.rb +7 -0
  192. data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
  193. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  194. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  195. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  196. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  197. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  198. data/lib/phronomy/llm_adapter/base.rb +2 -2
  199. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  200. data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
  201. data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
  202. data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
  203. data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
  204. data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
  205. data/lib/phronomy/llm_contract/transport_error.rb +7 -0
  206. data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
  207. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +8 -8
  208. data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
  209. data/lib/phronomy/multi_agent/orchestrator.rb +36 -68
  210. data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
  211. data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
  212. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
  213. data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
  214. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  215. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
  216. data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
  217. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  218. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
  219. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  220. data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
  221. data/lib/phronomy/persistence/api/persistence.rb +140 -0
  222. data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
  223. data/lib/phronomy/persistence_composition/repositories.rb +77 -0
  224. data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
  225. data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
  226. data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
  227. data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
  228. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  229. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  230. data/lib/phronomy/storage/backend.rb +101 -0
  231. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  232. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  233. data/lib/phronomy/storage/blobs.rb +31 -0
  234. data/lib/phronomy/storage/condition.rb +25 -0
  235. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  236. data/lib/phronomy/storage/conflict_error.rb +9 -0
  237. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  238. data/lib/phronomy/storage/entry.rb +33 -0
  239. data/lib/phronomy/storage/guard_ref.rb +13 -0
  240. data/lib/phronomy/storage/not_found_error.rb +9 -0
  241. data/lib/phronomy/storage/record_codec.rb +177 -0
  242. data/lib/phronomy/storage/records.rb +61 -0
  243. data/lib/phronomy/storage/resource.rb +126 -0
  244. data/lib/phronomy/storage/scope.rb +25 -0
  245. data/lib/phronomy/storage/serialization_error.rb +9 -0
  246. data/lib/phronomy/storage/streams.rb +44 -0
  247. data/lib/phronomy/storage/transaction_error.rb +10 -0
  248. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  249. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  250. data/lib/phronomy/storage/validation.rb +53 -0
  251. data/lib/phronomy/storage/view.rb +131 -0
  252. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +5 -3
  253. data/lib/phronomy/testing/fake_clock.rb +13 -9
  254. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  255. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
  256. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
  257. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
  258. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
  259. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
  260. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
  261. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  262. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
  263. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  264. data/lib/phronomy/tool/contract/tool_error.rb +7 -0
  265. data/lib/phronomy/tools/agent.rb +6 -6
  266. data/lib/phronomy/vector_store/async_backend.rb +5 -5
  267. data/lib/phronomy/vector_store/embeddings/base.rb +2 -2
  268. data/lib/phronomy/version.rb +1 -1
  269. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
  270. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
  271. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  272. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
  273. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +126 -75
  274. data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
  275. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  276. data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
  277. data/lib/phronomy/workflow/phase_machine_builder.rb +8 -8
  278. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
  279. data/lib/phronomy.rb +52 -94
  280. data/scripts/api_snapshot.rb +4 -2
  281. data/scripts/storage_spi_snapshot.rb +36 -0
  282. data/sig/phronomy/agent.rbs +4 -5
  283. data/sig/phronomy/execution_receiver.rbs +34 -0
  284. data/sig/phronomy/extensions.rbs +5 -5
  285. data/sig/phronomy/handoff.rbs +4 -2
  286. data/sig/phronomy/multi_agent.rbs +19 -3
  287. data/sig/phronomy/persistence.rbs +7 -91
  288. data/sig/phronomy/runtime.rbs +34 -7
  289. data/sig/phronomy/storage.rbs +174 -0
  290. data/sig/phronomy/tool.rbs +11 -2
  291. data/sig/phronomy/workflow.rbs +1 -1
  292. metadata +236 -99
  293. data/lib/phronomy/agent/execution_coordinator.rb +0 -3138
  294. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  295. data/lib/phronomy/agent/recovery_support.rb +0 -504
  296. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  297. data/lib/phronomy/agent/tool_executor.rb +0 -55
  298. data/lib/phronomy/generator_verifier.rb +0 -369
  299. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  300. data/lib/phronomy/multi_agent/fan_out_invocation.rb +0 -137
  301. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +0 -118
  302. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  303. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  304. data/lib/phronomy/persistence/in_memory.rb +0 -690
  305. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  306. data/lib/phronomy/persistence.rb +0 -276
  307. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  308. data/lib/phronomy/workflow_recovery.rb +0 -123
  309. /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
  310. /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
  311. /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
  312. /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
  313. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  314. /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
  315. /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
  316. /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
  317. /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
  318. /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
  319. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  320. /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
  321. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
  322. /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
  323. /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
data/docs/architecture.md CHANGED
@@ -41,6 +41,215 @@ explicit compatibility decision.
41
41
  dependency minimization must not weaken accepted durability,
42
42
  ownership, security-boundary, or extension contracts.
43
43
 
44
+ ## Responsibility groups and dependency graphs
45
+
46
+ Dependency views describe a directed graph of responsibility groups, not a
47
+ tree or a single pipeline. Several distinct groups may share one horizontal
48
+ band. A band's height expresses a proposed abstraction level; it does not imply
49
+ that its groups are independent or that every valid dependency points down.
50
+
51
+ Assess each dependency by the referenced contract and its owner. Shared value
52
+ types, extension contracts, and collaboration within a component can justify
53
+ dependencies between groups. A directory containing both shared definitions and
54
+ concrete orchestration must be examined by responsibility before assigning it a
55
+ single position. Cycles and upward arrows are review evidence, not defect counts.
56
+
57
+ The FSM terminal marker illustrates this distinction. `FSMProtocol::FINISH`
58
+ owns the internal `:__end__` marker used by the session and Workflow compilers.
59
+ The public Workflow DSL continues to use `:__finish__`. The execution session
60
+ and phase compiler do not depend on `WorkflowRunner` for this shared vocabulary;
61
+ the Workflow builder still legitimately creates its runner.
62
+
63
+ Context Policy and hook contracts provide another example. The shared values,
64
+ Manifest representation, and Plan validation live in `agent/context_contract/`,
65
+ separately from concrete policies and Agent execution. Zeitwerk collapses that
66
+ directory to preserve the existing `Phronomy::Agent` constants. A Policy's
67
+ dependency on those contracts does not make it depend on Agent execution. See
68
+ [ADR-036](decisions/036-context-contract-ownership.md).
69
+
70
+ ### Common definitions
71
+
72
+ `common/` owns general definitions shared across the framework that do not
73
+ belong to a particular feature. They must not depend on concrete Agent,
74
+ Workflow, Runtime, or other feature implementations. Being used in several
75
+ places, or inheriting a common base class, does not by itself make a definition
76
+ common; feature-owned contracts remain with their owners.
77
+
78
+ This group contains `Phronomy::Error`, `Phronomy::ConfigurationError`,
79
+ `Phronomy::CanonicalJSON`, and `Phronomy::Values::Immutable`. Zeitwerk collapses
80
+ `common/`, preserving these canonical names without introducing a
81
+ `Phronomy::Common` namespace. Other exceptions belong to their feature
82
+ contracts. `configuration/` owns settings and scalar defaults. The concrete
83
+ adapter and tracer defaults are selected in `runtime_composition/`, which binds
84
+ fresh-instance factories consumed by `Configuration.new`. This keeps concrete
85
+ feature selection outside settings while preserving application behavior.
86
+ Application Runtime reset and configuration replacement are also coordinated in
87
+ `runtime_composition/`, separately from configuration access and Engine mechanics.
88
+ See [ADR-039](decisions/039-runtime-configuration-lifecycle-ownership.md) and
89
+ [ADR-040](decisions/040-configuration-default-composition.md).
90
+ See [ADR-037](decisions/037-common-definition-ownership.md) and
91
+ [ADR-038](decisions/038-responsibility-based-source-layout.md).
92
+
93
+ ### Source placement and loading
94
+
95
+ `lib/phronomy.rb` is the application loading entry point. Internal production
96
+ files must not require it. Feature implementations and contracts live in their
97
+ responsibility directories; the direct root contains only `version.rb` and the
98
+ small namespace/loading files enumerated in ADR-038.
99
+ The separately documented external backend-test entry
100
+ `phronomy/testing/persistence_contract` retains its existing opt-in loading
101
+ contract and is excluded from production automatic loading.
102
+
103
+ Engine owns Event, Execution composition and its outcome exceptions, and the
104
+ synchronous FSM callback exceptions. Recovery owns shared rehydration
105
+ requirements. Workflow implementation lives under `workflow/execution/`;
106
+ Agent namespace/event loading lives under `agent/api/`, separately from the shared
107
+ Agent lifecycle exceptions in `agent/lifecycle_contract/`. LLM values and
108
+ call-boundary exceptions live under `llm_contract/`.
109
+
110
+ Agent consumes its private fresh-Persistence factory only when neither an
111
+ explicit instance nor a configured instance is available. Concrete selection
112
+ and binding live in `runtime_composition/agent_defaults.rb`. The one-shot
113
+ `Agent.run_once` method is defined in `agent/composition/run_once.rb`, because it
114
+ explicitly composes Agent and fresh ephemeral Persistence on every call.
115
+ The application entry loads both composition files; Agent execution and
116
+ namespace loading do not delegate upward to them. See
117
+ [ADR-044](decisions/044-agent-default-and-one-shot-composition.md).
118
+
119
+ Types excluded from authorization worker inputs declare the internal,
120
+ methodless `Concurrency::WorkerInputRestricted` contract at their own
121
+ definitions. ToolInvocation checks that execution-boundary contract rather
122
+ than concrete Workflow types. The original restriction set and opaque
123
+ application-value behavior are preserved; see
124
+ [ADR-045](decisions/045-worker-input-restriction-ownership.md).
125
+
126
+ Agent implementation files are grouped into lifecycle, execution, Tool execution,
127
+ context assembly, journal, Handoff and recovery directories. These directories
128
+ are collapsed, so existing Agent constant names remain unchanged. Journal encoding,
129
+ saved context reads and live invocation restoration have separate internal owners;
130
+ transaction and EventLoop state decisions remain with their callers. See
131
+ [ADR-046](decisions/046-agent-responsibility-layout-and-shared-records.md).
132
+
133
+ Recovery hands semantic continuation commands to the execution owner through
134
+ its EventLoop delivery boundary. The owner checks current identity, revision
135
+ and session state before applying them. `Agent::ExecutionSessionRunner` shares
136
+ ordinary and recovered Agent/Tool session registration and reports completion
137
+ back to the same coordinator; operation workers now own terminal persistence
138
+ under its EventLoop result authority (ADR-051 below).
139
+ See [ADR-047](decisions/047-recovered-execution-continuation-contract.md).
140
+
141
+ InvocationRestorer decodes saved Tool snapshots, resolves the Tool Call and Tool,
142
+ and matches approval evidence. The newly constructed ToolInvocation applies its
143
+ own saved state through the internal `restore_state!` operation; Recovery no
144
+ longer writes its instance variables. State application neither evaluates approval
145
+ nor dispatches Tool execution. Existing recovery installation notifications and
146
+ continuation rules are unchanged. See
147
+ [ADR-052](decisions/052-tool-invocation-restoration-ownership.md).
148
+
149
+ `Agent::DispatchPreparation` owns Provider/Tool dispatch prerequisites and their
150
+ operation-specific readback. ExecutionCoordinator captures and submits inputs,
151
+ then validates/applies results on EventLoop before dispatch. The worker's Provider
152
+ entry separates record encoding, application ContextPolicy, prerequisite commit
153
+ and post-commit materialization without changing transaction/rescue boundaries.
154
+ Its input/result types are worker-owned; existing Coordinator constant paths are
155
+ internal aliases, with changed canonical Ruby names. See
156
+ [ADR-048](decisions/048-dispatch-preparation-worker-ownership.md).
157
+
158
+ `Agent::InitialPreparation` owns initial durable admission, Context preparation,
159
+ preparation failure persistence and replay from saved preparing inputs. Ordinary
160
+ start and recovery share the admitted-preparation steps; Runtime admission,
161
+ result validation, live-state apply and session delivery remain on EventLoop.
162
+ The known failure base advances only after a successful commit response.
163
+ `Agent::ExecutionFailure` shares the existing pure failure classification with
164
+ terminal persistence; it does not own transactions or delivery. See
165
+ [ADR-049](decisions/049-initial-preparation-worker-ownership.md).
166
+
167
+ `Agent::ApprovalResumeCommit` persists approval decisions with operation-owned
168
+ Tool recovery snapshots. Coordinator validates the suspended owner and approval
169
+ request before copying canonical snapshot values into the immutable Command;
170
+ there is no shared snapshot lookup. The worker validates the target, stages
171
+ recovery facts and commits decision/Execution/Root together. EventLoop retains
172
+ admission, stale-result checks, live-state application and session resumption.
173
+ An uncertain commit still requires recovery; it is not retried or treated as a
174
+ confirmed resume. Internal Coordinator type aliases remain, with changed
175
+ canonical names and an added Command snapshot field. See
176
+ [ADR-050](decisions/050-approval-resume-snapshot-and-commit-ownership.md).
177
+
178
+ `Agent::ExecutionOutcomeCommitter` owns ordinary completion, failure, suspension
179
+ and child waiting; `Agent::HandoffOutcomeCommitter` adds atomic Source transfer.
180
+ The Handoff Coordinator now only selects its worker. Transaction boundaries,
181
+ operation-specific readback and Handoff selection precedence remain unchanged.
182
+ Coordinator retains quiescence, submission, stale-result validation, live-state
183
+ application, admission and Task/listener delivery. Command/view/outcome types are
184
+ worker-owned with internal Coordinator aliases and changed canonical Ruby names.
185
+ See [ADR-051](decisions/051-execution-outcome-worker-ownership.md).
186
+
187
+ The remaining execution owner expresses result handling as validation, committed
188
+ state installation, and continuation or delivery. Private methods keep these
189
+ steps in Coordinator; they introduce neither another owner nor shared per-operation
190
+ fields. The operation-specific authority checks and outer rescue boundaries stay
191
+ at the result entry points. Start/resume admission and submission flags stay in
192
+ the same methods as their cleanup decisions.
193
+
194
+ | Owner entry | Purpose-level steps |
195
+ | --- | --- |
196
+ | Initial preparation recovery result | Validate preparing owner; restart the prepared session or settle its saved failure; complete the load observer |
197
+ | Approval resume result | Validate suspended revision; install committed state and waiter; observe the task; resume the FSM |
198
+ | Terminal result | Validate revision/session; handle commit uncertainty; apply state and acknowledge the snapshot; deliver the selected outcome |
199
+
200
+ Terminal delivery releases ownership before notifying completed/failed/Handoff
201
+ observers, then settles Tasks. Suspension keeps ordinary Tasks pending. Ordinary
202
+ commit uncertainty keeps recovery admission and pending waiters; coordination
203
+ errors retain their separate release-and-fail behavior. Session registration
204
+ failure during preparation recovery still terminalizes without a live session;
205
+ trace/resume failure still uses the newly installed execution revision.
206
+ This is an internal readability refinement of ADR-024/047/051, not a change to
207
+ persistence, recovery guarantees or public interfaces.
208
+
209
+ Selected nested Zeitwerk roots retain existing top-level Phronomy constants
210
+ without changing the enclosing feature's existing nested constants. For
211
+ example, `Phronomy::WorkflowContext` and `Phronomy::WorkflowRunner` coexist with
212
+ `Phronomy::Workflow::Persistence`. Workflow remains a class and its source
213
+ file lives beside its implementation. These moves do not introduce aliases or
214
+ a new public API for requiring arbitrary internal paths.
215
+
216
+ WorkflowRunner directly owns terminal save submission and F1 snapshot readback.
217
+ The former WorkflowRecovery prepend and shadowed save implementation are removed;
218
+ the active path is independent of an installation hook. Private methods separate
219
+ submission, save/reconciliation and result delivery. FSMSession still interprets
220
+ the terminal persistence event; that ownership is a separate next step. See
221
+ [ADR-054](decisions/054-workflow-terminal-save-single-owner.md) and the
222
+ [remaining refactoring plan](architecture/remaining-refactoring-plan.md).
223
+ The Agent entry explicitly preserves Agent lifecycle extension installation.
224
+ Configuration accessors now live beside Configuration, rather than inside the
225
+ loader. Configuration constructs fresh components through internally bound
226
+ factories; composition selects their concrete types. Static source-reference
227
+ graphs do not follow these injected calls. LLMAdapter's async bridge still uses
228
+ Runtime, and other dependency cycles remain.
229
+
230
+ Agent and Team implement their identity registries in `agent/` and
231
+ `multi_agent/`. Runtime strongly retains one of each when registered, using only
232
+ its generic shutdown participant contract. Feature code reserves identities,
233
+ handles feature exceptions, and detaches owners after completed cleanup.
234
+ See [ADR-041](decisions/041-feature-owned-identity-registries.md).
235
+
236
+ Agent and Workflow also own their distinct execution registries. EventLoop
237
+ retains them through Engine's internal `ExecutionReceiver` contract, dispatches
238
+ queued messages and combines generic session/delivery counts with their idle
239
+ predicates. Normal mutation remains on the EventLoop thread; only final
240
+ invalidation after join runs on the management thread. EventLoop has no Agent
241
+ or Workflow dispatch branch. See
242
+ [ADR-042](decisions/042-feature-owned-execution-state.md).
243
+
244
+ `MultiAgent::SharedState` owns sequential peer coordination through a findings
245
+ store created for each invocation. It equips member Agents with shared-store
246
+ Tools, runs complete cycles, checks stopping conditions and aggregates findings.
247
+ It is not an individual Agent's durable state or execution coordinator. Its
248
+ Experimental public namespace moves from Agent to MultiAgent without an alias;
249
+ generated semantic definition IDs retain their previous values. See
250
+ [ADR-053](decisions/053-shared-state-coordination-ownership.md) and the
251
+ [migration guide](migrations/shared-state-multi-agent.md).
252
+
44
253
  ## Current architecture
45
254
 
46
255
  | Area | Current document |
@@ -0,0 +1,205 @@
1
+ # Result composition and Execution
2
+
3
+ `Phronomy::TaskResult` represents a pending or settled result. It does not start
4
+ a thread. `Phronomy::Execution` starts the input JOBs and waits for the final
5
+ TaskResult returned by each JOB. The application defines those completion
6
+ conditions and what to do with the collected values.
7
+
8
+ ## Invocation, individual results and fan-in
9
+
10
+ ```ruby
11
+ require "phronomy"
12
+
13
+ def collect_answers_async(agents, question:, invocation_context: nil)
14
+ Phronomy::Execution.run_async(
15
+ agents, timeout: 30, invocation_context: invocation_context
16
+ ) do |agent, execution|
17
+ agent.invoke_async(question, invocation_context: execution.invocation_context)
18
+ .map { |response| response.fetch(:output).strip }
19
+ end
20
+ end
21
+
22
+ # Each outcome identifies the original input position, including failed JOBs.
23
+ outcomes = collect_answers_async(agents, question: "Explain the tradeoff").wait_result
24
+ outcomes.each do |outcome|
25
+ puts [outcome.index, outcome.status, outcome.value, outcome.error&.message].inspect
26
+ end
27
+ ```
28
+
29
+ `agents` contains separately generated or loaded Agent instances. Use each
30
+ Agent's materialization listener for lifecycle/progress events. Result handling
31
+ uses the TaskResult returned by the invocation.
32
+
33
+ The input must be an Array. Its positions are shallowly copied before any JOB
34
+ starts. Start blocks run in input order without waiting for previous returned
35
+ results. They can run on the calling thread. Execution adds no per-JOB thread
36
+ or worker: keep start and transformation blocks short and use
37
+ `Blocking.call_async` for application work that can block. A start block itself
38
+ can delay the return of `run_async` if the application performs synchronous work.
39
+
40
+ Zero, one and many inputs all produce an Array of Outcome records. Empty input
41
+ still validates parameters and rejects an already-expired deadline or cancelled
42
+ token before completing. `Execution.run` starts the same execution once and
43
+ waits for its Outcome Array. The synchronous entrance is rejected on EventLoop
44
+ even for empty or already-completed inputs.
45
+
46
+ ## map and flat_map
47
+
48
+ | Operation | Success callback | Derived result |
49
+ | --- | --- | --- |
50
+ | `result.map { ... }` | Returns any value | That value, even when the value itself is a TaskResult |
51
+ | `result.flat_map { ... }` | Returns a TaskResult | Waits for and adopts that inner result's terminal value/state |
52
+ | `result.on_complete { \|value, error\| ... }` | Independent completion notification | Returns the original result; the callback does not transform it |
53
+
54
+ A callback can run immediately during registration or on the completing thread.
55
+ There is no callback thread guarantee. `map`/`flat_map` block StandardError
56
+ failures become failures of the derived result, preserving the original error.
57
+ A wrong `flat_map` return becomes a TypeError failure. Missing blocks raise
58
+ ArgumentError at registration. A source or inner **cancelled state** propagates
59
+ as cancelled with the original error. A failed CancellationError, or one raised
60
+ by an application transformation, stays failed.
61
+
62
+ For example, extend each JOB through a second Agent before returning its result:
63
+
64
+ ```ruby
65
+ evaluations = Phronomy::Execution.run_async(pairs, timeout: 30) do |pair, execution|
66
+ context = execution.invocation_context
67
+ pair.fetch(:author).invoke_async(question, invocation_context: context)
68
+ .flat_map do |answer|
69
+ pair.fetch(:reviewer).invoke_async(answer.fetch(:output), invocation_context: context)
70
+ .map { |review| review.fetch(:output) }
71
+ end
72
+ end
73
+ ```
74
+
75
+ If a JOB raises StandardError or returns something other than a TaskResult,
76
+ that JOB becomes failed. Other JOBs continue. Once all final results settle,
77
+ the whole execution succeeds with the Outcome Array even if it contains
78
+ individual failures or cancellations.
79
+
80
+ ## Outcome records and whole-execution deadlines
81
+
82
+ `TaskResult::Outcome` has read-only `index`, `status`, `value` and `error` fields.
83
+ The returned Array and its records are frozen. Values and original exceptions
84
+ are retained by reference, without deep copying or freezing application objects.
85
+
86
+ | Whole execution | Whole TaskResult | Available records |
87
+ | --- | --- | --- |
88
+ | All JOBs settled | completed | Successful value is the Outcome Array |
89
+ | `timeout:` won | failed, ExecutionTimeoutError | `error.outcomes` |
90
+ | Explicit or inherited token/deadline cancellation won | cancelled, ExecutionCancellationError | `error.outcomes` |
91
+
92
+ Record statuses are `:completed`, `:failed`, `:cancelled`, and, in an interrupted
93
+ snapshot, `:unfinished`. Unstarted JOBs also have unfinished records. This is
94
+ not another terminal state of TaskResult. A JOB's record refers to its final
95
+ returned result, so completed transformed values are retained; an unfinished
96
+ inner `flat_map` result makes that JOB unfinished.
97
+
98
+ The execution serializes record updates and terminal claims. The first terminal
99
+ claim wins. At timeout/cancellation it fixes the records before requesting child
100
+ cancellation; later notifications cannot rewrite those records. It does not
101
+ retroactively decide using physical worker completion timestamps.
102
+
103
+ `timeout:` covers fan-out start through the final JOB results reaching fan-in.
104
+ `nil` adds no deadline. Zero or negative values fail before starting JOBs. It
105
+ accepts finite real Numeric values usable as elapsed seconds; other types raise
106
+ TypeError, and non-finite/complex values raise ArgumentError. These checks occur
107
+ before JOB starts or cancellation subscriptions. The existing Blocking numeric
108
+ conversion rules are unchanged.
109
+
110
+ `cancellation_token:` accepts nil or a CancellationToken (including subclasses).
111
+ `invocation_context:` accepts nil or an existing InvocationContext (including
112
+ subclasses). Other types raise TypeError at the Execution entrance.
113
+
114
+ `TaskResult#wait_result(timeout:)` sets only that caller's wait limit. It does not
115
+ cancel or alter the underlying result. Pending waits are forbidden on EventLoop.
116
+ An individual Blocking timeout keeps its TimeoutError failure; an individual
117
+ deadline token keeps the cancellation state. These are distinct from the whole
118
+ execution's errors and snapshot.
119
+
120
+ ## Context, cancellation and ownership
121
+
122
+ Pass `execution.invocation_context` explicitly to an Agent or to
123
+ `Blocking.call_async(invocation_context: ..., cancellation_token: ...)` for work
124
+ belonging to that execution. The result is bound before it returns to the app;
125
+ its `map`/`flat_map` continuations inherit the scope. A continuation checks the
126
+ scope before running. Explicitly passing the context again when starting an
127
+ inner operation also covers cancellation after the continuation has begun.
128
+
129
+ An optional existing context contributes its user, policy, budget and tracing
130
+ information by reference. Execution derives a new context without modifying
131
+ the supplied one. Existing context cancellation/deadline and the explicit
132
+ Execution token/timeout all remain effective. Controls connect one way into a
133
+ private token. Cancelling one execution or an individual operation does not
134
+ cancel a parent's or another caller's shared token. Agent and Blocking admission
135
+ combine the individual token with the context's controls.
136
+
137
+ Scope cancellation stops unstarted scoped operations/transformations and requests
138
+ cooperative cancellation of running operations. It does not use Thread#raise,
139
+ roll back effects, or guarantee physical worker termination. Offload work and
140
+ owned composition steps retain their physical-completion tracking after logical
141
+ cancellation. `on_complete` notifications still run, and suppressed framework
142
+ continuations settle as cancelled rather than remaining pending.
143
+
144
+ Execution waits only for registered JOBs' returned final results. If a JOB
145
+ starts X and Y but returns Y, the app is responsible for X. Normal completion
146
+ does not discover, join, or sweep-cancel X. If the app explicitly supplied this
147
+ scope to X, its API's cancellation and closed-scope start restrictions still
148
+ apply. A normally closed context cannot be reused to start another operation.
149
+
150
+ ## Observing work started elsewhere
151
+
152
+ ```ruby
153
+ # Source started under its original owner and controls.
154
+ shared_result = existing_agent.invoke_async(question)
155
+
156
+ run = Phronomy::Execution.run_async([shared_result], timeout: 5) do |source, execution|
157
+ execution.observe(source).map { |response| response.fetch(:output).upcase }
158
+ end
159
+ ```
160
+
161
+ `observe` returns a distinct scoped result. It does not restart, rebind or cancel
162
+ the source, its existing continuations, or its physical work. A JOB may also
163
+ return an external result directly if it only needs to wait for it.
164
+
165
+ `TaskResult.all_settled(results)` is the public wait-only API. It accepts an Array
166
+ of TaskResult instances/subclasses, snapshots the positions, and preserves order
167
+ and duplicate positions. Invalid elements cause an immediate TypeError before
168
+ any source is subscribed. It accepts an empty list and has no timeout/cancel
169
+ parameters. It does not own or infer scopes from its sources. If subsequent
170
+ transformations should be scoped, explicitly use
171
+ `execution.observe(TaskResult.all_settled(results)).map { ... }`.
172
+
173
+ ## Whole-result processing and existing layers
174
+
175
+ The whole TaskResult is the scope exit. An outer `map`/`flat_map` does not inherit
176
+ or extend the completed inner scope, whether registered before or after fan-in.
177
+ For example, vote counting belongs in an outer `map`. A slow save belongs in an
178
+ outer `flat_map` returning `Blocking.call_async(timeout: 5) { ... }`. That new
179
+ operation's five seconds start when it is invoked, independently of the first
180
+ execution's deadline. There is no `compose` argument or separate fan-out entrance.
181
+
182
+ Execution owns common runtime coordination. Orchestrator remains above the Agent
183
+ layer: `dispatch_parallel[_async]` retains Agent construction, knowledge
184
+ inheritance, bounded active children and `on_error` policy, and delegates its
185
+ runtime coordination to Execution. AgentExecution remains the durable Agent
186
+ record; ExecutionCoordinator keeps admission, persistence, approval, recovery
187
+ and terminal barriers while reusing the common context/control binding.
188
+
189
+ ## Development-release migration
190
+
191
+ - Replace `Phronomy::Task` with `Phronomy::TaskResult`. The old constant and file
192
+ are removed; there is no compatibility alias.
193
+ - Replace `orchestrator.fan_out(agent: klass, inputs: inputs, ...)` with
194
+ `orchestrator.dispatch_parallel(*inputs.map { |input| {agent: klass, input: input} }, ...)`
195
+ when keeping Orchestrator's Agent construction/knowledge/concurrency policy.
196
+ Use Execution directly for application-defined JOB result composition.
197
+ - Replace the asynchronous equivalent with `dispatch_parallel_async` or
198
+ `Execution.run_async` as appropriate. `fan_out` and `fan_out_async` are removed.
199
+ - Keep per-incarnation listeners at Agent creation/load. This change introduces
200
+ no invocation listener blocks, Proc persistence, durable result transforms,
201
+ callback acknowledgements or new scheduler.
202
+
203
+ See examples `32_async_composition` for complete basic and asynchronously
204
+ evaluated majority-vote applications, and `23_bounded_parallel` for bounded
205
+ Agent dispatch. Examples must depend on a core commit containing these APIs.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Status
4
4
 
5
- Accepted — revised for a single Task completion contract and the OffloadPool
5
+ Accepted — revised for a single TaskResult completion contract and the OffloadPool
6
6
  execution boundary.
7
7
 
8
8
  ## Context
@@ -19,7 +19,7 @@ executes is separate from the object used by callers to observe completion.
19
19
  ## Decision
20
20
 
21
21
  Framework lifecycle coordination uses one Runtime-owned EventLoop and explicit
22
- FSMSession state/events. **`Phronomy::Task` is the single caller-facing completion
22
+ FSMSession state/events. **`Phronomy::TaskResult` is the single caller-facing completion
23
23
  handle**, not an execution backend.
24
24
 
25
25
  There are two framework execution mechanisms:
@@ -28,22 +28,22 @@ There are two framework execution mechanisms:
28
28
  - **OffloadPool** for synchronous work that must execute on bounded worker OS
29
29
  Threads away from EventLoop.
30
30
 
31
- Both mechanisms surface asynchronous completion as `Phronomy::Task`.
31
+ Both mechanisms surface asynchronous completion as `Phronomy::TaskResult`.
32
32
 
33
33
  Phronomy defines two Tool execution modes:
34
34
 
35
35
  - `:cooperative` — short, EventLoop-safe work, or a specialized asynchronous
36
36
  implementation that starts another Phronomy lifecycle and immediately returns
37
- a Task;
37
+ a TaskResult;
38
38
  - `:offloaded` — synchronous work that must not run on EventLoop. It executes in
39
- the bounded OffloadPool and returns a Task.
39
+ the bounded OffloadPool and returns a TaskResult.
40
40
 
41
41
  Workload classification such as I/O-bound versus CPU-bound is application-owned.
42
42
  Phronomy does not provide separate `:blocking_io`, `:cpu_bound`, or
43
43
  `:external_process` Tool execution modes.
44
44
 
45
45
  Logical waits are never offloaded merely to obtain concurrency. Waiting for an
46
- Agent, Workflow, ToolInvocation, approval, timer, or another Task is represented
46
+ Agent, Workflow, ToolInvocation, approval, timer, or another TaskResult is represented
47
47
  as FSMSession state plus a later EventLoop event.
48
48
 
49
49
  ## Runtime model
@@ -65,7 +65,7 @@ Runtime
65
65
  └─ EventLoop-driven timers
66
66
 
67
67
  EventLoop / FSMSession ─┐
68
- ├─> Task = completion handle
68
+ ├─> TaskResult = completion handle
69
69
  OffloadPool ────────────┘
70
70
  ```
71
71
 
@@ -91,11 +91,11 @@ resource domains.
91
91
  An EventLoop action must not block while waiting for a free OffloadPool queue
92
92
  slot. Framework-owned EventLoop-origin submissions therefore use non-blocking
93
93
  admission (`on_full: :raise`) and propagate `BackpressureError` through the normal
94
- FSM/Task completion path.
94
+ FSM/TaskResult completion path.
95
95
 
96
96
  ## Timeout and cancellation
97
97
 
98
- An OffloadPool submit-time timeout settles the caller-facing Task with
98
+ An OffloadPool submit-time timeout settles the caller-facing TaskResult with
99
99
  `TimeoutError`. It does not asynchronously interrupt a running worker Thread. If
100
100
  execution has already started, the private Operation becomes abandoned, the
101
101
  worker may continue until the submitted synchronous call returns, and that
@@ -104,8 +104,8 @@ eventual worker result is discarded.
104
104
  The cancellation token passed to `OffloadPool#submit` follows the same model:
105
105
 
106
106
  - cancellation before worker execution prevents the submitted block from
107
- starting and settles the Task as cancelled;
108
- - cancellation after worker execution starts settles the Task immediately, marks
107
+ starting and settles the TaskResult as cancelled;
108
+ - cancellation after worker execution starts settles the TaskResult immediately, marks
109
109
  the private Operation abandoned, and allows the worker to continue;
110
110
  - cancellation does not use `Thread#raise`;
111
111
  - application code may observe the same CancellationToken and terminate its own
@@ -114,18 +114,18 @@ The cancellation token passed to `OffloadPool#submit` follows the same model:
114
114
  A submit token with a monotonic deadline is connected to the Runtime timer queue,
115
115
  so deadline expiry becomes explicit cancellation without a polling Thread.
116
116
 
117
- `Task#wait_result(timeout:)` is a synchronous bridge for non-EventLoop callers.
117
+ `TaskResult#wait_result(timeout:)` is a synchronous bridge for non-EventLoop callers.
118
118
  Its timeout is waiter-local: it raises `TimeoutError` only to that caller and does
119
- not settle the Task, cancel the submitted operation, or mark an OffloadPool
119
+ not settle the TaskResult, cancel the submitted operation, or mark an OffloadPool
120
120
  Operation abandoned.
121
121
 
122
- Framework components own Task settlement. Application code should request
122
+ Framework components own TaskResult settlement. Application code should request
123
123
  operation-wide cancellation through the CancellationToken accepted by the API
124
- that created the Task rather than calling Task settlement methods directly. A
125
- Task cancellation must not implicitly cancel a shared CancellationToken in the
124
+ that created the TaskResult rather than calling TaskResult settlement methods directly. A
125
+ TaskResult cancellation must not implicitly cancel a shared CancellationToken in the
126
126
  reverse direction.
127
127
 
128
- Independent `Task#on_complete` notification callbacks are fault-isolated. Their
128
+ Independent `TaskResult#on_complete` notification callbacks are fault-isolated. Their
129
129
  execution thread is not guaranteed, so callbacks must be thread-safe and should
130
130
  return quickly. Framework lifecycle code normally converts completion into an
131
131
  explicit EventLoop event.
@@ -136,7 +136,7 @@ explicit EventLoop event.
136
136
  caller-facing timeout or cancellation won after worker execution had already
137
137
  started. `offload_pool_abandoned_active` is current-state: it counts only those
138
138
  abandoned operations whose synchronous workers are still occupying OffloadPool
139
- capacity. Task does not expose abandonment as caller-facing completion state;
139
+ capacity. TaskResult does not expose abandonment as caller-facing completion state;
140
140
  that distinction remains private OffloadPool execution/observability state.
141
141
 
142
142
  ## CPU-bound work
@@ -154,7 +154,7 @@ does not reintroduce a Tool-level workload class.
154
154
  A component that truly uses a native asynchronous driver and does not create a
155
155
  Phronomy-owned OS Thread does not need an OffloadPool worker. If such an
156
156
  extension point is formally introduced, it must still adapt completion into a
157
- `Phronomy::Task`; it must not expose provider-specific futures or private Runtime
157
+ `Phronomy::TaskResult`; it must not expose provider-specific futures or private Runtime
158
158
  operation records as Phronomy's completion contract.
159
159
 
160
160
  The current VectorStore and Embeddings extension contracts are synchronous; their
@@ -168,14 +168,14 @@ OffloadPool worker
168
168
  → wait_result
169
169
  ```
170
170
 
171
- and equivalently for Workflow/ToolInvocation/Task lifecycles.
171
+ and equivalently for Workflow/ToolInvocation/TaskResult lifecycles.
172
172
 
173
173
  The correct model is:
174
174
 
175
175
  ```text
176
176
  parent FSMSession
177
177
  → start child lifecycle
178
- → return Task immediately
178
+ → return TaskResult immediately
179
179
  → child settles
180
180
  → post parent EventLoop event
181
181
  ```
@@ -183,8 +183,8 @@ parent FSMSession
183
183
  ## Consequences
184
184
 
185
185
  - There is one explicit framework continuation model: FSMSession + EventLoop.
186
- - There is one caller-facing completion abstraction: Task.
187
- - Task stays thread-free and represents settlement only.
186
+ - There is one caller-facing completion abstraction: TaskResult.
187
+ - TaskResult stays thread-free and represents settlement only.
188
188
  - OffloadPool owns bounded OS-thread execution and its private Operation state.
189
189
  - Tool execution classification remains `:cooperative` / `:offloaded`.
190
190
  - CPU/I/O classification and resource sizing are application responsibilities.
@@ -278,3 +278,14 @@ ambiguous.
278
278
 
279
279
  Rejected. Cleanup from an invocation that failed to acquire admission could then
280
280
  release the real owner's reservation. Release is owner-aware by construction.
281
+
282
+ ## Storage composition refinement (2026-09-17)
283
+
284
+ [ADR-032](032-storage-backend-composition.md) refines the public storage boundary:
285
+ `Persistence` is the domain-facing composed service and `Storage::Backend` is
286
+ the raw storage extension contract. Backend inheritance from `Persistence`, the
287
+ old raw-repository constructor, and `build_transaction_view` are replaced.
288
+ Storage record/error constants move to `Storage`. The single atomic transaction
289
+ domain, durable-state/live-owner split, and failure guarantees in this decision
290
+ remain in force. Earlier API spellings in this decision are historical for this
291
+ amended scope; use the current Backend contract and migration guide.
@@ -1,6 +1,10 @@
1
1
  # ADR-024: EventLoop Single-Writer Agent Runtime State
2
2
 
3
- **Status**: Accepted
3
+ **Status**: Amended
4
+ **Implementation ownership amendment**: [042-feature-owned-execution-state](042-feature-owned-execution-state.md) moves feature execution state and decisions to Agent/Workflow receivers; the EventLoop thread remains the single writer. Its registration, delivery and drain rules refine the original descriptions below.
5
+
6
+ **Worker input classification amendment**: [045-worker-input-restriction-ownership](045-worker-input-restriction-ownership.md) replaces the concrete rejection list with an execution-boundary marker declared by each owning type. The existing value/behavior restrictions and application-owned opaque-value contract remain unchanged.
7
+
4
8
  **Date**: 2026-08-24
5
9
  **Partially supersedes**: [ADR-014](014-unified-persistence-durable-state.md) for live Agent Runtime ownership and `AgentExecutionActivation`
6
10
  **Complements**: [ADR-010](010-cooperative-first-concurrency.md), [ADR-012](012-canonical-execution-log-and-context-policy.md), [ADR-018](018-durability-guarantees-and-failure-model.md), [ADR-022](022-agent-execution-parent-identity-and-runtime-routing-boundary.md), [ADR-023](023-fsm-session-incarnation-identity-and-routing.md)
@@ -1,7 +1,10 @@
1
1
  # ADR-025: Process-Local Agent Ownership and Runtime Admission
2
2
 
3
- **Status**: Accepted
3
+ **Status**: Amended
4
+ **Implementation ownership amendment**: [042-feature-owned-execution-state](042-feature-owned-execution-state.md) moves feature execution state and decisions to Agent/Workflow receivers; the EventLoop thread remains the single writer. Its registration, delivery and drain rules refine the original descriptions below.
5
+
4
6
  **Date**: 2026-08-24
7
+ **Amended by**: [041-feature-owned-identity-registries](041-feature-owned-identity-registries.md) for registry implementation ownership and its generic Runtime shutdown contract
5
8
  **Partially supersedes**: [ADR-014](014-unified-persistence-durable-state.md) for same-process Agent live-instance ownership and top-level execution admission
6
9
  **Complements**: [ADR-018](018-durability-guarantees-and-failure-model.md), [ADR-022](022-agent-execution-parent-identity-and-runtime-routing-boundary.md), [ADR-023](023-fsm-session-incarnation-identity-and-routing.md), [ADR-024](024-event-loop-single-writer-agent-runtime.md)
7
10
 
@@ -43,8 +46,8 @@ live ownership service.
43
46
 
44
47
  ### One `agent_id` has one mutable live Agent owner per Runtime
45
48
 
46
- A Runtime owns a purpose-specific Agent ownership registry keyed only by
47
- `agent_id`:
49
+ Agent owns the purpose-specific registry implementation, keyed only by
50
+ `agent_id`; Runtime strongly retains one registered instance for its lifetime:
48
51
 
49
52
  ```text
50
53
  agent_id
@@ -63,6 +66,13 @@ The registry is separate from EventLoop's `execution_id -> AgentExecutionState`
63
66
  directory. Agent lifetime and Execution lifetime are different semantic
64
67
  lifetimes and must not be represented by one registry.
65
68
 
69
+ The original implementation placed this registry under Runtime. ADR-041 moves
70
+ it to `Agent::OwnershipRegistry` and removes the feature-specific Runtime
71
+ forwarding methods. Runtime uses only the generic shutdown participant contract
72
+ to close, wait for, and finally detach registered owners. The invariant and
73
+ Runtime-scoped lifetime are unchanged; EventLoop admission is not moved by that
74
+ amendment.
75
+
66
76
  ### Public Agent construction/resolution semantics
67
77
 
68
78
  The public operations have distinct meanings:
@@ -1,6 +1,10 @@
1
1
  # ADR-026: Workflow Runtime Admission and Durable Terminal Barrier
2
2
 
3
- **Status**: Accepted
3
+ **Status**: Amended
4
+ **Implementation ownership amendment**: [042-feature-owned-execution-state](042-feature-owned-execution-state.md) moves feature execution state and decisions to Agent/Workflow receivers; the EventLoop thread remains the single writer. Its registration, delivery and drain rules refine the original descriptions below.
5
+ **Observer failure clarification**: [055-terminal-observer-failure-settlement](055-terminal-observer-failure-settlement.md) keeps terminal observer errors on the ordinary failure path without undoing an already confirmed save.
6
+ **Terminal policy ownership amendment**: [056-workflow-terminal-policy-ownership](056-workflow-terminal-policy-ownership.md) moves Workflow event/outcome interpretation to WorkflowTerminalPolicy while preserving the shared session, durable barrier and uncertainty lifecycle.
7
+
4
8
  **Date**: 2026-08-24
5
9
  **Partially supersedes**: [ADR-014](014-unified-persistence-durable-state.md) for same-process Workflow admission ownership and terminal durable-barrier ordering
6
10
  **Refines**: [ADR-023](023-fsm-session-incarnation-identity-and-routing.md) by removing the transitional Workflow FSMSession-identity reservation bridge