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
@@ -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.
@@ -0,0 +1,111 @@
1
+ # ADR-049: Initial 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
+ [047-recovered-execution-continuation-contract](047-recovered-execution-continuation-contract.md)
8
+ and [048-dispatch-preparation-worker-ownership](048-dispatch-preparation-worker-ownership.md).
9
+
10
+ ## Problem
11
+
12
+ ExecutionCoordinator still owned initial durable admission, application filtering,
13
+ Context preparation, failure persistence and replay of saved preparation inputs.
14
+ Its ordinary start and preparing-recovery paths shared persistence work with
15
+ EventLoop admission, live state and completion delivery. Simply relocating that
16
+ work would leave long methods mixing representation details and control steps.
17
+
18
+ ## Decision
19
+
20
+ ### Preparation owns persistence; the execution owner owns live control
21
+
22
+ `Agent::InitialPreparation` owns `prepare(Command)` and `recover(RecoveryCommand)`.
23
+ Its Command and Result retain the fields of the former Coordinator-owned types.
24
+ Coordinator keeps their old constant paths as aliases. Their canonical Ruby names
25
+ change; these are internal process-local types, not saved or public API records.
26
+
27
+ Coordinator retains replay-eligibility capture, Runtime admission, live-owner
28
+ checks, Offload submission, completion messages, stale-result validation, live
29
+ Agent/Registry updates, session registration and Task/listener settlement.
30
+ Preparing recovery captures the execution, root and journal records in an explicit
31
+ RecoveryCommand. Result tasks and load completion handles stay on EventLoop.
32
+ The worker does not call back into Coordinator or initiate Provider/Tool dispatch.
33
+
34
+ The worker is marked `WorkerInputRestricted` and holds existing Agent services and
35
+ Persistence. It keeps operation state in local variables. This does not remove all
36
+ Agent service references or make arbitrary application input/config values deeply
37
+ immutable. Approval snapshot isolation remains a later stage.
38
+
39
+ ### Preserve admission outcomes and operation-specific failure boundaries
40
+
41
+ Input extraction precedes durable admission. An extraction failure returns
42
+ `not_established`. During admission, durable busy returns `recovery_required`;
43
+ the existing known-conflict/validation exception set returns `not_established`;
44
+ other exceptions return `outcome_unknown`. The execution owner preserves the
45
+ corresponding Runtime admission release or fail-closed behavior.
46
+
47
+ Durable admission still creates Execution and advances AgentRoot together, after
48
+ validating Team/Subagent/Handoff reservation ownership in the same transaction.
49
+ Splitting those ownership checks into named helpers changes no acceptance rule.
50
+
51
+ For admitted preparation the body reads as filter, stage input, prepare Context,
52
+ commit, and materialize. Hooks and ContextPolicy run outside the commit
53
+ transaction. Cancellation is checked before filtering, before assembly and after
54
+ Policy. The commit revalidates the Agent watermark after Policy returns.
55
+
56
+ The failure base advances from preparing Execution to active Execution only after
57
+ the active commit has returned successfully. A lost active-commit response leaves
58
+ the old revision as the failure base. If the active commit did occur, the existing
59
+ failure transaction conflicts and rolls back its Journal append; the worker does
60
+ not return an unconfirmed terminal result or add a new retry/readback policy.
61
+
62
+ A post-commit materialization error is terminalized from the known active
63
+ revision. Failure persistence atomically appends audit records, saves terminal
64
+ Execution and advances AgentRoot. A failure in that transaction, including lost
65
+ response, escapes to the execution owner; it is not converted into a confirmed
66
+ terminal Result. This initial-preparation contract intentionally differs from
67
+ DispatchPreparation's operation-specific uncertain-outcome reconciliation.
68
+
69
+ `Agent::ExecutionFailure` owns the existing pure error-to-status and status-to-audit
70
+ kind mappings. Both initial failure persistence and the remaining terminal worker
71
+ use it, avoiding duplicated classification rules. It owns no transaction, live
72
+ state or result delivery. Normal/Handoff terminal transaction behavior is unchanged.
73
+
74
+ ### Recovery consumes saved inputs without admitting another execution
75
+
76
+ The worker first checks the replayable marker, reads current input and rebuilds
77
+ config from the saved invocation mode, coordination services and optional canonical
78
+ Hash durable_context. The recovered config and durable context remain frozen.
79
+ Read failures or invalid durable context escape before admitted preparation;
80
+ they do not trigger a failure commit or a speculative retry.
81
+
82
+ Ordinary start and recovery use the same admitted-preparation operation and Result.
83
+ Recovery keeps the same execution identity and does not call durable admission.
84
+ Runtime-only approval/listener state is not reconstructed. Existing Coordinator
85
+ validation and recovery delivery remain authoritative.
86
+
87
+ ## Guarantees and verification
88
+
89
+ Using [018-durability-guarantees-and-failure-model](018-durability-guarantees-and-failure-model.md):
90
+
91
+ | Subject / property | Provider | Failure / boundary | Result |
92
+ | --- | --- | --- | --- |
93
+ | Same-process Agent starts: fail closed for uncertain/busy admission | Existing Runtime registry plus worker outcome classification | F0/F1/F4; before framework Provider/Tool X0 | YES, unchanged |
94
+ | Initial preparation: stale root/revision rejection | Persistence watermark/CAS and failure transaction rollback | F1/F2/F3; before framework Provider/Tool X0 | CONDITIONAL on conforming Persistence |
95
+ | Failure transition: Journal, Execution and Root commit together | Existing Persistence transaction | F0/F1; atomicity does not imply response certainty | CONDITIONAL on conforming backend; no new certainty guarantee |
96
+ | Preparing recovery: continue the same execution | Existing replay eligibility, saved inputs and owner validation | F4; application preparation may run again | CONDITIONAL on the existing replay-safe preparation contract |
97
+ | Arbitrary application or external effects: exactly once | No new exclusion/idempotency protocol | F1/F4 across application-defined X0 | NO new guarantee |
98
+
99
+ Behavioral tests cover transaction ordering, pre-admission extraction failure,
100
+ known admission failure, lost admission/active/failure commit responses,
101
+ post-commit materialization failure, cancellation and watermark changes after
102
+ Policy, blocked input and replay input restoration/validation/read failure.
103
+ Architecture guards include all new worker helpers and exclude control/delivery
104
+ dependencies. Existing normal execution, Team/Subagent/Handoff and recovery suites
105
+ cover the owner wiring and unchanged coordination checks.
106
+
107
+ ## Remaining work
108
+
109
+ Approval snapshot isolation and approval persistence, followed by normal/Handoff
110
+ outcome worker extraction, remain separate stages. This stage does not finish
111
+ Coordinator decomposition or remove all Agent dependency cycles.
@@ -0,0 +1,98 @@
1
+ # ADR-050: Approval Resume Snapshot and Commit 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
+ [047-recovered-execution-continuation-contract](047-recovered-execution-continuation-contract.md)
7
+ and [049-initial-preparation-worker-ownership](049-initial-preparation-worker-ownership.md).
8
+
9
+ ## Problem
10
+
11
+ Approval resume captured a Tool batch into a Coordinator Hash keyed by execution
12
+ ID before validating the current approval request. A worker later removed that
13
+ entry under a Mutex. Two queued requests could overwrite or consume each other's
14
+ snapshot even though the worker commands belonged to distinct operations.
15
+ The outer frozen Array also shared mutable String values with the invocation.
16
+ The command did not completely describe the recovery facts it would persist.
17
+
18
+ ## Decision
19
+
20
+ ### Validate and capture on EventLoop
21
+
22
+ ExecutionCoordinator validates the live Agent owner, suspended Execution and
23
+ approval request ID before capturing the invocation's canonical Tool snapshot.
24
+ It copies and recursively freezes that value tree with `Values::Immutable.copy`
25
+ and stores it directly in the operation's `tool_batch_snapshot` field. An absent
26
+ invocation gives nil; a present invocation with no children gives a frozen empty
27
+ Array. No shared approval snapshot Hash or Mutex remains in either owner.
28
+
29
+ This applies to canonical recovery facts only. Arbitrary application objects in
30
+ config, listener or callback state do not acquire a new deep-copy contract.
31
+ The general RecoverySupport snapshot builder keeps its existing contract.
32
+
33
+ Snapshot isolation was implemented and behaviorally verified before extracting
34
+ the persistence operation, so the concurrency fix can be reviewed independently
35
+ of the ownership change.
36
+
37
+ ### Persist the captured decision in an operation worker
38
+
39
+ `Agent::ApprovalResumeCommit#commit` reads as validate the approval target, stage
40
+ the captured recovery facts, then persist the decision. Its helpers own record
41
+ fields, metadata representation and repository calls. It holds only the Agent ID
42
+ and Persistence; all operation state is local. It is `WorkerInputRestricted`.
43
+
44
+ The existing transaction still writes approval decision Content, adds the
45
+ approval_decided record to Execution working records, saves active/resuming
46
+ Execution with its expected revision and advances AgentRoot with its expected
47
+ revision. Staging metadata preserves the original execution revision so the
48
+ durable transition advances it exactly once. A nil snapshot preserves existing
49
+ metadata; an empty snapshot replaces the previous Tool batch.
50
+
51
+ Coordinator retains Runtime admission, Offload submission, completion messages,
52
+ late-result validation, live-state replacement, completion waiters, tracing and
53
+ SessionRunner resumption. Rejection before Offload submission restores suspended
54
+ admission. A commit error keeps admission recovery_required. A late result for
55
+ an advanced or cancelled Execution fails only the approval observer and does not
56
+ start a session. Existing duplicate-approval acceptance and CAS rules are unchanged.
57
+
58
+ The worker does not read back uncertain results, retry a transaction, contact
59
+ Provider/Tool adapters or invoke application callbacks. A lost commit response
60
+ escapes as an error; the owner follows the existing recovery-required path.
61
+ Persistence atomicity must not be presented as certainty about that response.
62
+
63
+ ### Internal type compatibility
64
+
65
+ Command and Result are owned by ApprovalResumeCommit. Coordinator retains
66
+ `ResumeCommitCommand` and `ResumeCommitResult` as aliases to those exact classes.
67
+ Their canonical Ruby names change to `ApprovalResumeCommit::Command` and
68
+ `ApprovalResumeCommit::Result`. Command appends the required internal field
69
+ `tool_batch_snapshot`; Result retains its existing members. Internal positional
70
+ construction/reflection is consequently not unchanged. These types are neither
71
+ public API nor durable records. Saved schema and public approval API stay unchanged.
72
+
73
+ ## Guarantees and verification
74
+
75
+ Using [ADR-018](018-durability-guarantees-and-failure-model.md):
76
+
77
+ | Subject / property | Provider | Failure / boundary | Result |
78
+ | --- | --- | --- | --- |
79
+ | Queued approval operation retains its captured facts | EventLoop validation and independent immutable Command values | F0/F2/F3; before Provider/Tool X0 | YES for canonical Tool snapshot values |
80
+ | Approval transition is atomic (G5), stale writes are rejected (G8) | Existing Persistence transaction and Execution/Root CAS | F2; no framework X0 in this worker | CONDITIONAL on conforming backend |
81
+ | Unknown approval commit is not treated as confirmed resumption | Worker error propagation and owner recovery_required admission | F1; no new worker X0 | YES; no new commit certainty or readback guarantee |
82
+ | Restored approval waiting continues the same execution (G4) | Existing recovery reconstruction and validated resume path | F4; later Tool work may cross X0 | CONDITIONAL on existing recovery/integration contracts |
83
+ | Cross-process exclusion (G7), external duplicate prevention (G9), exactly once (G10) | No new protocol supplied by this change | F1/F2/F4 across X0 | NO new guarantee |
84
+
85
+ Tests cover mutation after capture, reversed worker order, stale and invalid
86
+ requests, rejected submission, empty/absent batches, successive approvals,
87
+ approval/denial persistence, Execution and Root conflicts, transaction response
88
+ loss and stale results after cancellation or a newer approval. Existing recovery
89
+ integration tests restore approval waiting and resume both approval and denial.
90
+ The unit fault injections are not a hard-process-loss or real external-service test.
91
+ Architecture guards inspect the worker helpers for live-control dependencies.
92
+
93
+ ## Remaining work
94
+
95
+ Normal terminal persistence and Handoff terminal persistence remain coupled in
96
+ ExecutionCoordinator and its subclass. They are the next extraction stage and
97
+ must be designed together. This change does not complete Coordinator decomposition
98
+ or eliminate Agent dependency cycles.