phronomy 0.26.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 (304) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +4 -0
  4. data/VERIFY.sh +27 -27
  5. data/benchmark/bench_agent_invoke.rb +26 -22
  6. data/benchmark/bench_context_assembler.rb +4 -5
  7. data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
  8. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  9. data/docs/architecture/agent-context.md +4 -2
  10. data/docs/architecture/agent-transition-ownership.md +89 -0
  11. data/docs/architecture/before-llm-input.md +6 -0
  12. data/docs/architecture/context-management.md +30 -0
  13. data/docs/architecture/context-preparation-steps.md +90 -0
  14. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  15. data/docs/architecture/execution-metadata-and-values.md +80 -0
  16. data/docs/architecture/generator-verifier-ownership.md +111 -0
  17. data/docs/architecture/multi-agent-handoff.md +8 -2
  18. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  19. data/docs/architecture/persistence.md +27 -4
  20. data/docs/architecture/refactoring-closure.md +334 -0
  21. data/docs/architecture/remaining-refactoring-plan.md +374 -0
  22. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  23. data/docs/architecture/tool-schema-recording-gap.md +50 -0
  24. data/docs/architecture/tracing.md +1 -1
  25. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  26. data/docs/architecture.md +209 -0
  27. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  28. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  29. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  30. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  31. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
  32. data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
  33. data/docs/decisions/032-storage-backend-composition.md +80 -0
  34. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  35. data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -0
  36. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  37. data/docs/decisions/036-context-contract-ownership.md +86 -0
  38. data/docs/decisions/037-common-definition-ownership.md +62 -0
  39. data/docs/decisions/038-responsibility-based-source-layout.md +131 -0
  40. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
  41. data/docs/decisions/040-configuration-default-composition.md +94 -0
  42. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  43. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  44. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  45. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  46. data/docs/decisions/045-worker-input-restriction-ownership.md +100 -0
  47. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  48. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  49. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  50. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  51. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  52. data/docs/decisions/051-execution-outcome-worker-ownership.md +151 -0
  53. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  54. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  55. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  56. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  57. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  58. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  59. data/docs/decisions/058-neutral-storage-primitives.md +78 -0
  60. data/docs/decisions/README.md +35 -6
  61. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  62. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  63. data/docs/features.md +9 -3
  64. data/docs/getting-started.md +4 -4
  65. data/docs/migrations/0.15.md +5 -0
  66. data/docs/migrations/durable-semantic-coordination-v2.md +6 -0
  67. data/docs/migrations/handoff-runner-multi-agent.md +35 -0
  68. data/docs/migrations/neutral-storage-spi.md +43 -0
  69. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  70. data/docs/migrations/shared-state-multi-agent.md +44 -0
  71. data/docs/migrations/storage-backend-composition.md +117 -0
  72. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  73. data/docs/persistence-backends.md +162 -204
  74. data/docs/runtime-and-concurrency.md +117 -32
  75. data/lib/phronomy/agent/api/agent.rb +17 -0
  76. data/lib/phronomy/agent/async_event_api.rb +2 -2
  77. data/lib/phronomy/agent/base.rb +51 -241
  78. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  79. data/lib/phronomy/agent/context/capability/base.rb +67 -26
  80. data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
  81. data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
  82. data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
  83. data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
  84. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
  85. data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
  86. data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
  87. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
  88. data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
  89. data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
  90. data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
  91. data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
  92. data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
  93. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  94. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
  95. data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +19 -95
  96. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  97. data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
  98. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +9 -10
  99. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
  100. data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
  101. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  102. data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
  103. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
  104. data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
  105. data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
  106. data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
  107. data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
  108. data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +20 -71
  109. data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
  110. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  111. data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
  112. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
  113. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
  114. data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
  115. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  116. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  117. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  118. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  119. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  120. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -10
  121. data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
  122. data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
  123. data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
  124. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
  125. data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
  126. data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
  127. data/lib/phronomy/agent/persistence/codec.rb +358 -0
  128. data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
  129. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
  130. data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
  131. data/lib/phronomy/agent/persistence/queries.rb +61 -0
  132. data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
  133. data/lib/phronomy/agent/persistence/watermark.rb +27 -0
  134. data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
  135. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
  136. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +32 -37
  137. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
  138. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +6 -10
  139. data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
  140. data/lib/phronomy/agent/selection/candidate.rb +1 -1
  141. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  142. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  143. data/lib/phronomy/agent/tool_execution/tool_binding.rb +90 -0
  144. data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
  145. data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
  146. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
  147. data/lib/phronomy/common/configuration_error.rb +7 -0
  148. data/lib/phronomy/common/error.rb +5 -0
  149. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  150. data/lib/phronomy/common/values/serializable.rb +32 -0
  151. data/lib/phronomy/{configuration.rb → configuration/configuration.rb} +14 -5
  152. data/lib/phronomy/configuration/global_configuration.rb +26 -0
  153. data/lib/phronomy/content_store/storage_schema.rb +11 -0
  154. data/lib/phronomy/content_store/stored_contents.rb +43 -0
  155. data/lib/phronomy/engine/backpressure_error.rb +7 -0
  156. data/lib/phronomy/{blocking.rb → engine/blocking.rb} +1 -1
  157. data/lib/phronomy/engine/cancellation_error.rb +7 -0
  158. data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
  159. data/lib/phronomy/engine/concurrency/offload_pool.rb +4 -0
  160. data/lib/phronomy/engine/concurrency/operation_binding.rb +1 -1
  161. data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
  162. data/lib/phronomy/engine/event_loop.rb +158 -624
  163. data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
  164. data/lib/phronomy/{execution.rb → engine/execution.rb} +0 -7
  165. data/lib/phronomy/{execution_cancellation_error.rb → engine/execution_cancellation_error.rb} +2 -0
  166. data/lib/phronomy/engine/execution_receiver.rb +65 -0
  167. data/lib/phronomy/{execution_timeout_error.rb → engine/execution_timeout_error.rb} +2 -0
  168. data/lib/phronomy/engine/fsm_protocol.rb +14 -0
  169. data/lib/phronomy/engine/fsm_session.rb +35 -28
  170. data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +2 -0
  171. data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +2 -0
  172. data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +2 -0
  173. data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
  174. data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
  175. data/lib/phronomy/engine/runtime.rb +80 -84
  176. data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
  177. data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
  178. data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
  179. data/lib/phronomy/engine/task_result.rb +4 -0
  180. data/lib/phronomy/engine/timeout_error.rb +7 -0
  181. data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
  182. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  183. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  184. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  185. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  186. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  187. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  188. data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
  189. data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
  190. data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
  191. data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
  192. data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
  193. data/lib/phronomy/llm_contract/transport_error.rb +7 -0
  194. data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
  195. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +5 -5
  196. data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
  197. data/lib/phronomy/multi_agent/orchestrator.rb +2 -2
  198. data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
  199. data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
  200. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
  201. data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
  202. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  203. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
  204. data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
  205. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  206. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
  207. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  208. data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
  209. data/lib/phronomy/persistence/api/persistence.rb +140 -0
  210. data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
  211. data/lib/phronomy/persistence_composition/repositories.rb +77 -0
  212. data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
  213. data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
  214. data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
  215. data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
  216. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  217. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  218. data/lib/phronomy/storage/backend.rb +101 -0
  219. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  220. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  221. data/lib/phronomy/storage/blobs.rb +31 -0
  222. data/lib/phronomy/storage/condition.rb +25 -0
  223. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  224. data/lib/phronomy/storage/conflict_error.rb +9 -0
  225. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  226. data/lib/phronomy/storage/entry.rb +33 -0
  227. data/lib/phronomy/storage/guard_ref.rb +13 -0
  228. data/lib/phronomy/storage/not_found_error.rb +9 -0
  229. data/lib/phronomy/storage/record_codec.rb +177 -0
  230. data/lib/phronomy/storage/records.rb +61 -0
  231. data/lib/phronomy/storage/resource.rb +126 -0
  232. data/lib/phronomy/storage/scope.rb +25 -0
  233. data/lib/phronomy/storage/serialization_error.rb +9 -0
  234. data/lib/phronomy/storage/streams.rb +44 -0
  235. data/lib/phronomy/storage/transaction_error.rb +10 -0
  236. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  237. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  238. data/lib/phronomy/storage/validation.rb +53 -0
  239. data/lib/phronomy/storage/view.rb +131 -0
  240. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  241. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
  242. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
  243. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
  244. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
  245. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
  246. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
  247. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  248. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
  249. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  250. data/lib/phronomy/tool/contract/tool_error.rb +7 -0
  251. data/lib/phronomy/tools/agent.rb +1 -1
  252. data/lib/phronomy/version.rb +1 -1
  253. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +8 -6
  254. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
  255. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  256. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
  257. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +122 -71
  258. data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
  259. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  260. data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
  261. data/lib/phronomy/workflow/phase_machine_builder.rb +2 -2
  262. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
  263. data/lib/phronomy.rb +52 -94
  264. data/scripts/api_snapshot.rb +1 -1
  265. data/scripts/storage_spi_snapshot.rb +36 -0
  266. data/sig/phronomy/agent.rbs +0 -1
  267. data/sig/phronomy/execution_receiver.rbs +34 -0
  268. data/sig/phronomy/handoff.rbs +4 -2
  269. data/sig/phronomy/multi_agent.rbs +18 -0
  270. data/sig/phronomy/persistence.rbs +7 -91
  271. data/sig/phronomy/storage.rbs +174 -0
  272. data/sig/phronomy/tool.rbs +10 -1
  273. metadata +230 -99
  274. data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
  275. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  276. data/lib/phronomy/agent/recovery_support.rb +0 -504
  277. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  278. data/lib/phronomy/agent/tool_executor.rb +0 -55
  279. data/lib/phronomy/generator_verifier.rb +0 -369
  280. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  281. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  282. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  283. data/lib/phronomy/persistence/in_memory.rb +0 -690
  284. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  285. data/lib/phronomy/persistence.rb +0 -276
  286. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  287. data/lib/phronomy/workflow_recovery.rb +0 -123
  288. /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
  289. /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
  290. /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
  291. /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
  292. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  293. /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
  294. /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
  295. /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
  296. /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
  297. /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
  298. /data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +0 -0
  299. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  300. /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
  301. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
  302. /data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +0 -0
  303. /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
  304. /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
@@ -0,0 +1,75 @@
1
+ # ADR-043: Storage-Owned Execution Constraint Notifications
2
+
3
+ The raw fixed-repository/error portions are amended by [ADR-058](058-neutral-storage-primitives.md). Domain ownership and applicable transaction/uncertainty decisions remain in force.
4
+
5
+
6
+ **Status**: Accepted
7
+ **Date**: 2026-09-21
8
+ **Refines**: [033-domain-persistence-ownership](033-domain-persistence-ownership.md) and the Beta Backend error contract in [persistence-backends](../persistence-backends.md)
9
+
10
+ ## Problem
11
+
12
+ InMemory and the SQLite/PostgreSQL reference backends raised `AgentBusyError`
13
+ for stored nonterminal-execution constraints. A storage implementation therefore
14
+ selected a feature lifecycle exception, even though domain repositories already
15
+ owned record conversion and public operation semantics.
16
+
17
+ ## Decision
18
+
19
+ `Storage::ActiveExecutionConflictError < Storage::ConflictError` identifies only
20
+ a stored nonterminal execution preventing another admission or an idle-only
21
+ operation for the same owner. It contains no Agent/Team class, domain object,
22
+ callback, or new persisted field. Generic duplicate IDs, stale revisions and
23
+ other precondition failures remain ordinary `Storage::ConflictError` values;
24
+ connection errors and uncertain outcomes retain their existing error types.
25
+
26
+ Raw execution repositories use this subtype from `create_active`, `assert_idle!`,
27
+ and any existing `save` branch that detects the same active-owner constraint.
28
+ Agent and MultiAgent execution repository facades translate only this subtype
29
+ to the existing public `AgentBusyError`, preserving its message and Ruby cause.
30
+ They do not catch every `ConflictError` or reinterpret database exceptions.
31
+ The existing Team public error remains unchanged; choosing a different Team
32
+ lifecycle exception would be a separate API decision.
33
+
34
+ Constraint detection and the write remain inside the same backend consistency
35
+ boundary. No preflight lookup is moved above the backend. Domain translation
36
+ occurs inside the existing transaction block so the mapped exception still
37
+ causes rollback before commit. SQL statements, lock order, connection binding,
38
+ indexes, persisted records and transaction boundaries are unchanged.
39
+
40
+ ## Compatibility and migration
41
+
42
+ This intentionally changes the Beta raw Backend error contract. Callers of
43
+ `backend.executions` and `backend.team_executions` must catch the new storage
44
+ subtype instead of `AgentBusyError`. Callers of the domain-facing Persistence,
45
+ Agent and Team APIs continue receiving `AgentBusyError` for that condition.
46
+
47
+ The core, InMemory and both reference SQL backends are migrated together. Apply
48
+ the core before the new SQL sources; the new backend sources require the new
49
+ storage constant. An old backend that still raises `AgentBusyError` passes
50
+ through an updated domain facade unchanged, but no longer conforms to the new
51
+ raw SPI. There is no fallback alias in Storage or Engine.
52
+
53
+ The shared backend suite now checks raw notifications separately from domain
54
+ exceptions, including duplicate-ID and stale-revision distinction and rollback.
55
+ The storage isolation guard removes its former Agent lifecycle file exemption.
56
+
57
+ ## Guarantees and limits
58
+
59
+ | Subject/property | Provider | Failure/boundary | Result |
60
+ |---|---|---|---|
61
+ | One nonterminal execution per stored owner | Existing atomic admission, backend lock/transaction/DB constraint | F2; no X0 | CONDITIONAL on the backend satisfying the existing atomic_admission contract; no new cross-process owner lease |
62
+ | Uncommitted writes roll back when a mapped constraint escapes | Existing storage transaction with domain conversion inside its block | F0/F2; no X0 | YES for an ordinary known pre-commit constraint failure; not a claim about uncertain commits |
63
+ | Caller-facing busy errors retain their meaning | Feature repository translation of the dedicated subtype | F0/F2; no X0 | YES for the documented domain repository paths; unrelated conflicts are not converted |
64
+
65
+ This change adds no F1 outcome reconciliation, F4 rehydration, external-effect
66
+ rollback or exactly-once guarantee. The eight repository names and Agent
67
+ watermark that ADR-033 leaves in Storage remain an explicit intermediate state.
68
+ Removing the exception dependency does not make the entire SPI domain-neutral.
69
+
70
+ ## Rejected alternatives
71
+
72
+ - Move `AgentBusyError` into common definitions or alias it from Storage.
73
+ - Translate every `ConflictError` into a busy error.
74
+ - Check active state in the domain facade before performing a separate write.
75
+ - Migrate only InMemory and silently leave the SQL raw contract inconsistent.
@@ -0,0 +1,106 @@
1
+ # ADR-044: Agent Default and One-Shot Composition
2
+
3
+ **Status**: Accepted on the architecture refactoring branch
4
+ **Date**: 2026-09-21
5
+ **Refines**: [038-responsibility-based-source-layout](038-responsibility-based-source-layout.md)
6
+ and [040-configuration-default-composition](040-configuration-default-composition.md)
7
+
8
+ ## Problem
9
+
10
+ `Agent::Base` selected the concrete default with `Persistence.in_memory`.
11
+ `Agent.run_once`, defined beside Agent lifecycle namespace loading, also selected
12
+ and constructed Persistence. Both files therefore depended on the higher-level
13
+ Persistence composition API, but for different reasons.
14
+
15
+ Default selection is an injected construction dependency of Agent execution.
16
+ One-shot execution is itself a composition API. Treating both as lower-level
17
+ Agent execution obscured those responsibilities. Replacing one-shot storage with
18
+ the configured shared Persistence would change isolation and existing behavior.
19
+
20
+ ## Decision
21
+
22
+ 1. Agent owns the private `Agent::DefaultPersistence` construction contract. A
23
+ zero-argument factory is bound once and frozen during application loading.
24
+ `Base` invokes it only after the explicit `persistence:` and configured
25
+ `configuration.persistence` values have both been excluded, preserving their
26
+ existing truthy fallback order. It neither caches the constructed instance
27
+ nor writes it to configuration. Subclasses consume the same binding.
28
+ 2. `runtime_composition/agent_defaults.rb` selects `Persistence.in_memory` in
29
+ that factory. Binding does not invoke it, create settings, or start Runtime.
30
+ The binding is not a public plugin registry, an alternative application
31
+ configuration mechanism, or a new Runtime service locator. Runtime and
32
+ configuration resets do not replace or reinstall it.
33
+ 3. `agent/composition/run_once.rb` owns the actual `Agent.run_once` definition.
34
+ It remains `Phronomy::Agent.run_once`, with the same parameters, forwarding,
35
+ result, and exception behavior. It constructs fresh ephemeral Persistence on
36
+ every call even when global Persistence is configured. It does not consume
37
+ Base's fallback factory. Rejecting simultaneous `on_event:` and a block still
38
+ occurs before storage or Agent construction.
39
+ 4. `agent/api/agent.rb` retains `StreamEvent` and lifecycle extension loading.
40
+ It has no delegate to the higher-level one-shot implementation. The
41
+ application entry explicitly requires the namespace extensions, factory
42
+ binding, and one-shot method definition after Zeitwerk setup and global
43
+ configuration access installation. The composition directory and binding
44
+ file are ignored by Zeitwerk because they wire existing constants rather
45
+ than introduce matching public namespaces. Agent execution never requires
46
+ the application entry or either composition file.
47
+ 5. Normal application loading, first constant access, repeated requires, and
48
+ eager loading retain constant identity and exactly one lifecycle extension.
49
+ Explicit namespace loading also handles the internal unbound factory
50
+ contract having been loaded first. Arbitrary partial loading of framework
51
+ implementation files remains outside the public application-loading API.
52
+
53
+ ## Compatibility and guarantees
54
+
55
+ The public constructor and one-shot API, event callback paths, stored records,
56
+ transaction boundaries, Agent ownership, and shutdown behavior are unchanged.
57
+ The existing API snapshot is not regenerated. It does not cover the `run_once`
58
+ singleton signature, which is checked explicitly along with real invocation,
59
+ creation-time context/Knowledge, and both forms of event listener.
60
+
61
+ The factory's exception propagates unchanged through the existing Agent
62
+ creation/ownership handling. Classified failures such as ConfigurationError
63
+ release the reservation; unclassified failures such as IOError retain the
64
+ existing fail-closed recovery-required reservation. Do not make every factory
65
+ failure retryable as a side effect of moving default selection. Existing
66
+ explicit Persistence injection works without invoking the factory.
67
+
68
+ This is source dependency inversion, not removal of the runtime construction
69
+ call: `Base` still invokes the bound factory when a default is needed. The
70
+ one-shot implementation still depends explicitly on Agent and Persistence.
71
+ Both selecting and invoking the one-shot composition remain above execution.
72
+ The private factory slot is immutable after boot; returned Persistence
73
+ instances remain separate, mutable storage instances.
74
+
75
+ No F1 uncertain-commit reconciliation, F4 recovery guarantee, external-effect
76
+ rollback, or exactly-once guarantee is added. The previous in-memory durability
77
+ limits remain. Runtime retains created Agents according to existing ownership
78
+ rules; moving `run_once` does not introduce new cleanup or lifetime behavior.
79
+
80
+ ## Dependency interpretation
81
+
82
+ Remove `agent -> persistence/api` and `agent/api -> persistence/api`.
83
+ Add explicit composition dependencies from `runtime_composition` to Agent and
84
+ Persistence and from `agent/composition` to Persistence. One-shot composition
85
+ also calls the injected Agent definition's `create` method; namespace declarations
86
+ and injected calls do not count as static constant-reference edges. The
87
+ `Tools::Agent` call to `Agent.run_once` now targets its actual definition in
88
+ `agent/composition`. Treat the new Agent composition directory as B1; it is
89
+ not a new Ruby namespace.
90
+ The internal Base-to-factory reference remains within Agent's directory.
91
+
92
+ The remaining reverse dependency `agent -> workflow/execution` is the separate
93
+ worker-input classification change (E). This change does not resolve all
94
+ directory/file cycles, Storage's domain-specific repository names, or the
95
+ Engine FSM terminal persistence responsibilities.
96
+
97
+ ## Verification
98
+
99
+ Cover fallback precedence, fresh instances and subclass construction, resets,
100
+ classified failure/retry and unclassified fail-closed behavior, supplied-factory
101
+ independence, normal/preloaded/eager
102
+ loading, one-shot isolation from global Persistence, argument/result/exception
103
+ identity, context/Knowledge forwarding, callback conflict ordering, and both
104
+ event listener forms. Run existing Agent ownership/event tests, default and
105
+ integration suites, style, API snapshot, RBS, and annotation gates. Verify
106
+ examples against the changed core without modifying their public call sites.
@@ -0,0 +1,100 @@
1
+ # ADR-045: Worker Input Restriction Ownership
2
+
3
+ **Status**: Accepted on the architecture refactoring branch
4
+ **Date**: 2026-09-21
5
+ **Refines**: [024-event-loop-single-writer-agent-runtime](024-event-loop-single-writer-agent-runtime.md)
6
+ and [038-responsibility-based-source-layout](038-responsibility-based-source-layout.md)
7
+
8
+ ## Problem
9
+
10
+ Tool authorization snapshots exclude framework-managed live objects from value
11
+ data and application behavior handles. `ToolInvocation` identified those objects
12
+ by enumerating 18 Agent, Workflow and execution types. The Agent-side boundary
13
+ therefore depended on concrete Workflow types solely to reject them as input.
14
+
15
+ Moving that list into a shared helper would retain the wrong ownership. Treating
16
+ all frozen objects or all callables as safe would weaken the existing boundary;
17
+ rejecting all opaque application objects would break ACS-11.
18
+
19
+ ## Decision
20
+
21
+ 1. The execution boundary owns the internal, methodless
22
+ `Concurrency::WorkerInputRestricted` marker in `engine/concurrency/`.
23
+ It declares that instances of an owning type are excluded from the existing
24
+ authorization worker value/behavior boundary. It contains no feature list,
25
+ registration mechanism, class-name matching, constant lookup or callbacks.
26
+ 2. Each previously restricted type directly includes the marker in its own
27
+ definition. WorkflowContext includes it as a module so implementations and
28
+ their subclasses retain the restriction. Other classes pass it through
29
+ normal Ruby inheritance. Nested types are marked only when they were in
30
+ the original rejection set; marking FSMSession does not mark every nested
31
+ class, so its EventSink opts in separately.
32
+ 3. `ToolInvocation` checks only `is_a?(Concurrency::WorkerInputRestricted)`.
33
+ Recursive Hash-key/value and Array traversal, String copying/freezing,
34
+ exception type/messages and behavior-handle checks remain unchanged.
35
+ Frozen marked instances remain restricted.
36
+ 4. Each owner explicitly requires the small marker file. The marker can load
37
+ alone without Agent, Workflow, Runtime or application bootstrap. Normal,
38
+ preloaded, repeated and eager application loading retain marker identity
39
+ and do not create settings or start Runtime. The marker adds no methods to
40
+ public APIs; the existing API snapshot is not regenerated.
41
+
42
+ ## Existing restriction inventory
43
+
44
+ | Owner | Types that include the marker |
45
+ |---|---|
46
+ | Agent execution/state | `Agent::Base`, `AgentRoot`, `AgentExecution`, `AgentInvocation`, `ToolInvocation`, `JournalProjection`, `ExecutionCoordinator` (all under Agent) |
47
+ | Agent capability | `Agent::Context::Capability::Base` |
48
+ | Workflow | `Workflow`, `WorkflowRunner`, `WorkflowContext` |
49
+ | Execution | `Runtime`, `TaskResult`, `EventLoop`, `FSMSession`, `FSMSession::EventSink` |
50
+ | Concurrency | `Concurrency::CancellationToken`, `Concurrency::OffloadPool` |
51
+
52
+ These are the same 18 root types as the previous predicate. Regression tests
53
+ retain this inventory to prevent an accidentally unmarked existing type from
54
+ becoming admissible. Future framework types subject to this boundary must opt
55
+ in at their owning definition and add an input-boundary test.
56
+
57
+ ## Scope and compatibility
58
+
59
+ This marker is internal classification, not a public safety certification or
60
+ serialization SPI. It does not reject all Runnable implementations, all
61
+ Phronomy objects, class/module objects, or every worker command. Other operation
62
+ snapshots and OffloadPool APIs are unchanged.
63
+
64
+ Application-owned opaque values and behavior handles remain permitted by
65
+ identity. Their instance variables, callback receivers and closure captures
66
+ are not traversed. Applications remain responsible for worker safety under
67
+ the existing ACS-11 contract. The marker is not a security sandbox.
68
+
69
+ The ancestry of the 18 types intentionally gains one methodless module.
70
+ Public methods, constructors, value copying, events, ownership, cancellation,
71
+ transaction boundaries, persistence formats and exception behavior are
72
+ unchanged. No F1 uncertain-commit reconciliation, F4 recovery, external-effect
73
+ rollback or exactly-once guarantee is added.
74
+
75
+ ## Dependency interpretation
76
+
77
+ The Agent-to-Workflow execution dependency disappears. Workflow types and
78
+ Agent types instead refer downward to an execution-boundary contract.
79
+ Existing Engine types refer within the execution foundation. Explicit
80
+ marker loading adds require edges; it does not move feature references into
81
+ the marker. A methodless include is still a real source dependency.
82
+
83
+ This completes the specific A-E reverse-dependency proposals. It does not
84
+ eliminate all directory/file cycles, Storage's domain-specific repository
85
+ names, or FSMSession's Workflow terminal persistence responsibilities.
86
+
87
+ ## Verification
88
+
89
+ Verify the 18-type inventory for direct instances and subclasses, frozen and
90
+ unfrozen, root values, Hash keys/values, nested Arrays and all three behavior
91
+ handle names. Exercise actual authorization command capture for Workflow,
92
+ WorkflowRunner and WorkflowContext via context/policy/facts/requirement paths,
93
+ and reject a marked value returned from a facts callable. Preserve opaque
94
+ application data, callables and unmarked Runnable/Outcome values.
95
+
96
+ Also verify a new marked type without changing the evaluator, standalone and
97
+ normal/preloaded/eager loading, no Workflow type references in Agent source,
98
+ and the marker's absence of feature knowledge. Run the existing snapshot
99
+ boundary suite, full and integration tests, style, unchanged API snapshot,
100
+ RBS, annotations and examples against the candidate core.
@@ -0,0 +1,110 @@
1
+ # ADR-046: Agent Responsibility Layout and Shared Records
2
+
3
+ **Status**: Amended on the architecture refactoring branch
4
+ **SharedState amendment**: [053-shared-state-coordination-ownership](053-shared-state-coordination-ownership.md) moves the deferred multi-Agent coordination responsibility to MultiAgent. The original placement below is retained as history.
5
+ **Continuation amendment**: [047-recovered-execution-continuation-contract](047-recovered-execution-continuation-contract.md) removes the remaining Recovery-to-private-control calls and shares session registration. The original extraction scope below is retained as history.
6
+ **Date**: 2026-09-21
7
+ **Refines**: [038-responsibility-based-source-layout](038-responsibility-based-source-layout.md)
8
+ and [024-event-loop-single-writer-agent-runtime](024-event-loop-single-writer-agent-runtime.md)
9
+
10
+ ## Problem
11
+
12
+ Agent's direct directory contained 41 files with unrelated responsibilities.
13
+ Recovery also obtained the ordinary execution coordinator solely to invoke its
14
+ private record encoder and content writer. RecoverySupport mixed recovery
15
+ vocabulary, durable reads, and construction of live invocations.
16
+
17
+ Moving files alone does not resolve that coupling. Moving transaction decisions
18
+ into a shared encoder would instead give it too many responsibilities.
19
+
20
+ ## Decision
21
+
22
+ Apply and verify the changes in two stages: source placement first, then shared
23
+ record generation and restoration responsibilities.
24
+
25
+ ### Source placement
26
+
27
+ Collapse the following directories with Zeitwerk, preserving existing Agent
28
+ constant names and the application entry point `require "phronomy"`:
29
+
30
+ | Directory | Responsibility |
31
+ | --- | --- |
32
+ | `agent/lifecycle/` | Agent identity, ownership and fallback construction contract |
33
+ | `agent/execution/` | Invocation, phase transitions, admission and execution coordination |
34
+ | `agent/tool_execution/` | Tool invocation, authorization, approval and interception |
35
+ | `agent/context_assembly/` | Candidate selection, budgeting and Provider context materialization |
36
+ | `agent/journal/` | Canonical execution records and their projection |
37
+ | `agent/handoff/` | Agent-owned Handoff contracts, state and execution coordination |
38
+ | `agent/recovery/` | Durable recovery classification, resolution and live restoration |
39
+
40
+ The three files below `recovery_coordinator/` move with their owning class into
41
+ `recovery/recovery_coordinator/`. That directory retains its real nested Ruby
42
+ namespace; it is not independently collapsed. Existing API, composition,
43
+ context contracts, capabilities, concerns and persistence directories retain
44
+ their responsibilities. `base.rb`, `async_event_api.rb` and `shared_state.rb`
45
+ remain directly under Agent. SharedState's multi-Agent orchestration is a
46
+ separate future ownership decision, not evidence of generic shared data.
47
+
48
+ Update relative requires and source-reading regression guards. Do not provide
49
+ old-path forwarding shims: arbitrary implementation-file requires are not a
50
+ public partial-loading API. Existing constant identities, method signatures,
51
+ ancestry, lifecycle extension installation and stored class names are preserved.
52
+
53
+ ### Shared record generation
54
+
55
+ `Agent::RuntimeRecordEncoder` under `execution/` receives execution facts, the
56
+ Agent identity, root generation, context eligibility and an existing transaction.
57
+ It writes content and returns JournalRecord and LLMCallRecord values. Ordinary
58
+ execution and recovery resolution both call it directly. It has no Agent live
59
+ object, coordinator, Runtime lookup, transaction opener, journal append, execution
60
+ save, commit/reconciliation decision or task settlement responsibility.
61
+ The encoder belongs to execution because it interprets Tool interception and
62
+ Provider settlement; the journal value/projection directory must not depend on
63
+ Tool execution solely to classify those facts.
64
+
65
+ Keep call sequence numbering, interception-as-success, abandoned call recording,
66
+ Tool message duplicate detection, context eligibility, content formats and
67
+ canonical-value failure messages. The caller still owns the transaction and
68
+ F1 outcome reconciliation. Encoding is not a pure function because it writes
69
+ content using that caller-owned transaction.
70
+
71
+ ### Saved context and live restoration
72
+
73
+ `Agent::SavedContextReader` under `context_assembly/` reads persisted manifests,
74
+ materializes projections, and obtains saved Provider output/usage. Execution
75
+ preparation reconciliation, Handoff and Recovery consume this common reader.
76
+ Reading occurs in the same worker/caller preparation phase as before, outside
77
+ EventLoop state application.
78
+
79
+ `Agent::InvocationRestorer` under `recovery/` constructs live Chat, AgentInvocation
80
+ and ToolInvocation state from already-materialized facts. Suspended and resolved
81
+ continuations share the same invocation/chat construction sequence. Tool status,
82
+ approval facts, listener, invocation mode, coordination configuration, cancellation
83
+ and message order are preserved. It does not perform persistence reads or decide
84
+ which continuation to execute. RecoverySupport retains recovery metadata and
85
+ subject vocabulary rather than forwarding to the extracted helpers.
86
+
87
+ ## Compatibility and limits
88
+
89
+ No public API or extension SPI is changed, and the existing API snapshot is not
90
+ regenerated. These three helpers are internal (`@api private`). Removed methods
91
+ on the internal RecoverySupport module and ExecutionCoordinator are not public
92
+ application contracts. No persistence schema, durability level, exactly-once
93
+ claim, cancellation rule, external effect replay rule or ownership rule changes.
94
+
95
+ ExecutionCoordinator still owns several operations and remains large. Recovery
96
+ still calls its private continuation/terminal entry points; this decision only
97
+ removes the record-generation and content-writing coupling. Moving directories
98
+ reveals previously internal cycles; counts across different grouping schemes are
99
+ not directly comparable. No claim of eliminating all Agent cycles is made.
100
+
101
+ ## Verification
102
+
103
+ Verify the placement-only checkpoint before extracting helpers. Run ordinary
104
+ and recovery tests, F1 response-loss and I/O-boundary checks, restart/output-filter
105
+ coverage, full and integration suites, API byte comparison, RBS, annotations,
106
+ style and examples. Compare the complete encoded records and captured content
107
+ against the baseline under fixed timestamps and IDs, including success, structured
108
+ output, failure, interception, abandoned calls, duplicate Tool messages and
109
+ context eligibility. Verify gem packaging includes every moved/new source and
110
+ loads without the old implementation paths.
@@ -0,0 +1,116 @@
1
+ # ADR-047: Recovered Execution Continuation Contract
2
+
3
+ **Status**: Accepted on the architecture refactoring branch
4
+ **Date**: 2026-09-21
5
+ **Refines**: [046-agent-responsibility-layout-and-shared-records](046-agent-responsibility-layout-and-shared-records.md),
6
+ [024-event-loop-single-writer-agent-runtime](024-event-loop-single-writer-agent-runtime.md)
7
+ and [028-preparing-recovery-replay-contract](028-preparing-recovery-replay-contract.md).
8
+
9
+ ## Problem
10
+
11
+ Recovery chose a continuation from saved facts but also registered its Agent FSM
12
+ and reached five private ExecutionCoordinator entry points through `send`.
13
+ Initial execution, approval resume, framework Tool recovery and resolved-result
14
+ recovery independently wired parent session completion. Recovery therefore had
15
+ to understand the execution owner's terminal and session implementation.
16
+
17
+ ## Decision
18
+
19
+ ### Recovery describes the continuation; execution validates and starts it
20
+
21
+ Use the execution owner's existing `deliver_on_event_loop` boundary with two
22
+ internal command types:
23
+
24
+ - `RecoverPreparationCommand` identifies the installed execution and expected
25
+ revision, with separate execution and load completion observers.
26
+ - `ContinueRecoveredCommand` supplies that identity/revision, a semantic
27
+ continuation, the restored invocation, its materialized projection, the
28
+ execution observer and a failure when applicable.
29
+
30
+ Recovery still classifies saved facts, requests factual resolution, materializes
31
+ saved inputs off EventLoop and restores live invocations on EventLoop. It no
32
+ longer registers an Agent FSM, supplies FSM state/event names, owns its terminal
33
+ callback, or names private execution-control methods.
34
+
35
+ The execution owner validates EventLoop affinity, coordinator/Agent ownership,
36
+ current revision, nonterminal execution and absence of an installed FSM. It
37
+ also checks the continuation's saved phase and invocation identity before any
38
+ replacement, admission change or session dispatch. A mismatched continuation
39
+ must not replace or release another lifecycle's state. Stale initial preparation
40
+ fails both observers without releasing that newer lifecycle.
41
+
42
+ The execution owner interprets the six continuation intents:
43
+
44
+ | Intent | Execution-owned action |
45
+ | --- | --- |
46
+ | Approval rejection | Resume the saved rejected approval and its Tool sessions |
47
+ | Framework Tool batch | Resume the authorized framework Tool sessions |
48
+ | Saved framework calls | Enter ordinary Provider-result handling |
49
+ | Saved Provider output | Enter ordinary Provider-result/output-filter handling |
50
+ | Saved Tool results | Enter ordinary Tool-result/follow-up handling |
51
+ | Resolved failure | Enter the ordinary terminal persistence barrier |
52
+
53
+ Delivery is synchronous within the existing EventLoop turn. This is an internal
54
+ handover, not an additional queued turn or an Offload operation. Commands are
55
+ process-local controls, not persisted records or an application extension SPI.
56
+ The existing private terminal/preparation methods remain private.
57
+
58
+ ### One owner wires Agent and Tool sessions
59
+
60
+ `Agent::ExecutionSessionRunner` owns parent session registration, resumed session
61
+ construction, resumed child registration and parent completion wiring. Ordinary
62
+ initial execution, approval resume and recovery use it. It reports completion
63
+ through an execution-owner-supplied callback; the owner delivers an internal
64
+ `SessionFinishedCommand` to itself, including the concrete FSM incarnation and
65
+ invocation. This preserves Handoff coordinator dispatch without making the
66
+ runner depend on the coordinator's concrete class or command definitions.
67
+
68
+ The coordinator retains admission, completion observers, stale-callback checks,
69
+ physical-quiescence waiting, terminal snapshot creation and persistence. The
70
+ runner performs no persistence I/O, saved-fact interpretation, retry decision or
71
+ caller-facing result settlement. Parent registration precedes child registration
72
+ as before. Its live Runtime/callback handles carry the existing worker-input
73
+ restriction marker.
74
+
75
+ ## Compatibility and failure model
76
+
77
+ Public API snapshots, RBS contracts, stored formats, recovery subjects, approval
78
+ decisions and external replay eligibility remain unchanged. A restored rejected
79
+ approval now registers its internal execution observer at the same pre-session
80
+ boundary used by the other continuation intents; terminal settlement still
81
+ occurs only through the coordinator's existing barrier.
82
+
83
+ Under [018-durability-guarantees-and-failure-model](018-durability-guarantees-and-failure-model.md):
84
+
85
+ | Subject / property | Provider and condition | Failure / boundary | Result |
86
+ | --- | --- | --- | --- |
87
+ | Current live execution: reject stale or foreign continuation before state replacement | Execution-owner identity/revision/FSM validation on EventLoop | F2/F3; no X0 dispatch by rejected command | YES |
88
+ | Caller completion follows the authoritative terminal outcome | Existing coordinator terminal barrier and quiescence checks; runner reports FSM completion only | F0/F1/F3; existing external effects remain outside transaction | YES under the existing Persistence contract |
89
+ | Confirmed recovery state: resume the same logical execution | Recovery classification/materialization plus the execution owner; replay-safe preparation or resolved external facts are required | F1/F4; X0 may have occurred before recovery | CONDITIONAL on the existing operation-specific recovery contract |
90
+ | Arbitrary external-effect exactly-once execution | No new external idempotency or transaction protocol | F1/F4 across X0 | NO new guarantee |
91
+
92
+ The command boundary is process-local validation, not cross-process exclusion.
93
+ No broader exactly-once or automatic replay claim follows from this change.
94
+
95
+ ## Verification and remaining work
96
+
97
+ Exercise rejected commands on a real EventLoop/ExecutionRegistry: wrong owner,
98
+ coordinator, revision, installed FSM, terminal state, invocation identity and
99
+ continuation phase. Verify both preparation observers and a late session
100
+ completion. Existing recovery, approval, F1/F4, output-filter, cancellation,
101
+ Handoff and full-suite tests cover valid dispatch and terminal behavior.
102
+
103
+ ExecutionCoordinator still owns preparation, causal barriers, admission and
104
+ terminal operations. This decision removes Recovery's private control coupling
105
+ and duplicated session wiring; it does not complete decomposition of that class.
106
+ Tool snapshot restoration's direct field coupling, SharedState ownership,
107
+ Storage domain responsibilities and Workflow terminal persistence remain
108
+ separate work.
109
+
110
+ ## Tool restoration amendment (2026-09-22)
111
+
112
+ [052-tool-invocation-restoration-ownership](052-tool-invocation-restoration-ownership.md)
113
+ completes the direct Tool field coupling item above. ToolInvocation owns saved
114
+ state application; Recovery retains decoding and identity matching. The original
115
+ scope and historical remaining-work statement above are preserved. SharedState,
116
+ Storage domain responsibilities and Workflow terminal persistence remain open.
@@ -0,0 +1,120 @@
1
+ # ADR-048: Dispatch Preparation Worker Ownership
2
+
3
+ **Status**: Accepted on the architecture refactoring branch
4
+ **Date**: 2026-09-22
5
+ **Refines**: [024-event-loop-single-writer-agent-runtime](024-event-loop-single-writer-agent-runtime.md),
6
+ [042-feature-owned-execution-state](042-feature-owned-execution-state.md)
7
+ and [047-recovered-execution-continuation-contract](047-recovered-execution-continuation-contract.md).
8
+
9
+ ## Problem
10
+
11
+ ExecutionCoordinator mixed EventLoop ownership and application result delivery
12
+ with the Persistence operations required before Provider/Tool dispatch. Moving
13
+ whole methods alone would leave the Provider operation mixing control steps,
14
+ metadata representation, transactions, application policy and materialization.
15
+ Its worker also used input/result types nested under the execution owner, which
16
+ would create a reverse dependency after extraction.
17
+
18
+ ## Decision
19
+
20
+ ### The execution owner captures, submits and applies
21
+
22
+ ExecutionCoordinator keeps current Agent/invocation/FSM validation, operation
23
+ capture, Offload submission, Ready delivery, semantic revision/phase checks,
24
+ snapshot acknowledgment and physical Provider/Tool dispatch. These responsibilities
25
+ remain on their existing threads and in their existing order. Ordinary and
26
+ recovered execution keep the same owner and continuation contract.
27
+
28
+ `Agent::DispatchPreparation` owns four internal worker operations:
29
+
30
+ - `prepare_provider`: stage the next call, encode runtime records, prepare the
31
+ Context, commit prerequisites and materialize the confirmed input.
32
+ - `prepare_tools`: encode runtime records and save pending Tool recovery facts.
33
+ - `reconcile_provider`: read back the execution, classify the exact saved state
34
+ and restore input only for the confirmed intended result.
35
+ - `reconcile_tools`: read back and classify the exact saved state.
36
+
37
+ The worker returns values and never schedules work, advances the live Registry,
38
+ acknowledges invocation snapshots, starts Provider/Tool calls, or settles Tasks.
39
+ It does not refer back to ExecutionCoordinator or its private methods. The
40
+ Coordinator creates the worker with the existing Agent service context and its
41
+ Persistence instance. Agent hooks, ContextAssembler and coordination preparation
42
+ retain their existing service contracts; this is not removal of all Agent
43
+ references. The worker is marked `WorkerInputRestricted` because it retains
44
+ framework service handles. Per-operation inputs/results remain local variables.
45
+
46
+ ### Preserve the operation-specific persistence boundaries
47
+
48
+ Provider preparation has two transactions with application hooks/ContextPolicy
49
+ between them. The first can encode content-addressed records but does not advance
50
+ Execution. The second rechecks the local Agent watermark, finalizes the Manifest
51
+ and saves the Execution. Cancellation is checked after Policy and before that
52
+ second transaction. Tool preparation retains its single transaction.
53
+
54
+ Only an exception in the guarded save transaction, after an intended result was
55
+ captured and outside the existing known-failure classes, becomes an unknown
56
+ outcome. Encoding failures and application Policy failures do not become an
57
+ uncertain execution save. Post-commit materialization failure returns the saved
58
+ Execution together with an error, so the owner still applies/acknowledges the
59
+ known committed snapshot before reporting setup failure.
60
+
61
+ Reconciliation compares both revision and complete execution contents. It returns
62
+ `committed` only for the intended state, `not_committed` only for the exact original
63
+ state, and `conflict` otherwise. Read failures propagate. It never retries the
64
+ write or authorizes dispatch by itself. Terminal and coordination-wait
65
+ reconciliation remain separate and retain their different acceptance criteria.
66
+
67
+ The watermark rule remains owned by Persistence's `assert_agent_watermark!`.
68
+ The worker and the remaining initial-preparation code only map captured Root
69
+ fields to that contract; neither implements a second version of the rule.
70
+
71
+ ### Names describe the purpose; bodies describe its immediate steps
72
+
73
+ The Provider entry reads as stage, encode, prepare Context, commit, materialize.
74
+ Private helpers own metadata keys, record fields and value construction. The
75
+ Coordinator's dispatch entry reads as validate, capture, submit. Transaction
76
+ and rescue scopes stay explicit in the operations that own them. We do not use
77
+ one-line delegation chains or a generic execution framework to reduce line counts.
78
+
79
+ ### Types belong to the operation boundary
80
+
81
+ DispatchPreparation owns Provider/Tool commands, reconciliation commands and
82
+ their results. ExecutionCoordinator retains its existing eight nested constant
83
+ paths as aliases to these types; Ready messages and live delivery remain in the
84
+ Coordinator. Its private uncertainty exception constant is also an alias.
85
+
86
+ The aliases preserve construction, members and shared type identity, but the
87
+ canonical Ruby class names now belong to DispatchPreparation. These are internal,
88
+ process-local types, not persisted records or public extension contracts. This
89
+ change makes no promise to preserve their former reflected names. Product API
90
+ snapshots, saved formats, public exception contracts and recovery eligibility
91
+ remain unchanged.
92
+
93
+ ## Failure model and verification
94
+
95
+ Using [018-durability-guarantees-and-failure-model](018-durability-guarantees-and-failure-model.md):
96
+
97
+ | Subject / property | Provider and condition | Failure / boundary | Result |
98
+ | --- | --- | --- | --- |
99
+ | Current execution: apply only a matching preparation result | Existing EventLoop owner, revision and FSM checks | F2/F3; no new X0 dispatch from stale results | YES, unchanged |
100
+ | Provider/Tool dispatch: confirmed durable prerequisites precede physical dispatch | Operation-specific commit/readback plus owner apply; conforming Persistence is required | F0/F1; this barrier precedes the next X0 call | CONDITIONAL on confirming the intended durable state and current live ownership |
101
+ | Application Policy: no preparation transaction held while it executes | Explicit separation of encoding, Policy and save | F0/F3; application-defined side effects remain its responsibility | YES for the framework-owned transaction boundary |
102
+ | Confirmed records: restart readability and continuation | Existing Persistence and Recovery contracts | F4; replay or resolution depends on the external operation | CONDITIONAL, no broader resumption guarantee |
103
+ | Arbitrary external-effect exactly-once execution | No new external protocol or distributed exclusion | F1/F4 across X0 | NO new guarantee |
104
+
105
+ Existing causal-durability tests now invoke the owning worker instead of private
106
+ Coordinator persistence methods. Architecture guards inspect the complete worker,
107
+ including helpers, while keeping checks on EventLoop apply. Behavioral probes
108
+ exercise Policy outside transactions, post-Policy watermark/cancellation checks,
109
+ encoding-response loss, post-commit materialization failure, reconciliation read
110
+ failure, materialization failure after confirmation, and same-revision content
111
+ conflicts. Existing valid Agent/Tool execution, Recovery and Handoff suites cover
112
+ the production wiring. Public API, loading and gem-content checks remain required.
113
+
114
+ ## Remaining work
115
+
116
+ Initial preparation, approval snapshot isolation, and normal/Handoff outcome
117
+ persistence are separate stages. The approval Hash/Mutex is not moved into this
118
+ worker. The existing Coordinator and Handoff terminal machinery are unchanged.
119
+ This stage improves responsibility and abstraction boundaries; it does not claim
120
+ to remove every Agent dependency cycle or finish Coordinator decomposition.