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,151 @@
1
+ # ADR-051: Execution Outcome 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
+ [030-agent-handoff-domain-and-durable-responsibility](030-agent-handoff-domain-and-durable-responsibility.md),
7
+ [047-recovered-execution-continuation-contract](047-recovered-execution-continuation-contract.md)
8
+ and [050-approval-resume-snapshot-and-commit-ownership](050-approval-resume-snapshot-and-commit-ownership.md).
9
+
10
+ ## Problem
11
+
12
+ ExecutionCoordinator owned both EventLoop execution control and Offload result
13
+ persistence. HandoffExecutionCoordinator overrode outcome selection and depended
14
+ on its parent's transaction helpers. Extracting only one side would preserve
15
+ that hidden dependency or accidentally change Handoff precedence. Moving the
16
+ long bodies unchanged would also leave persistence policy mixed with individual
17
+ record fields.
18
+
19
+ ## Decision
20
+
21
+ ### Select and persist outcomes in operation workers
22
+
23
+ `Agent::ExecutionOutcomeCommitter#commit_outcome` selects completion, failure,
24
+ approval suspension or owned-child waiting from a captured command. It handles
25
+ persistence exceptions through the existing operation-specific readback rules.
26
+ `Agent::HandoffOutcomeCommitter` specializes that selection and atomic Source
27
+ transfer, sharing ordinary completion, failure and suspension persistence.
28
+ Handoff selection retains its separate callback/error/suspension/transfer order;
29
+ it does not acquire the ordinary worker's child-coordination wait branch.
30
+
31
+ HandoffExecutionCoordinator retains its class identity and inheritance, but now
32
+ only selects the Handoff worker. This selection is internal, not a public SPI.
33
+ Coordinator captures input, submits the worker, validates the returned revision
34
+ and session, applies live state and delivers results. Worker code never calls
35
+ back into Coordinator. Command state and intermediate results remain local to
36
+ one operation; workers hold only Agent service context and Persistence.
37
+
38
+ Agent context is still required by the existing coordination hook, error
39
+ translation, saved Manifest reader and transcript materializer. This is not a
40
+ claim that all Agent references or dependency cycles have been removed.
41
+ `WorkerInputRestricted` marks both worker types; it is not a recursive validator
42
+ of arbitrary application-owned values.
43
+
44
+ ### Keep transactions visible in the purpose-level methods
45
+
46
+ `commit_completed` encodes completion records, appends Journal, saves Execution
47
+ and coordination, advances Root and materializes the caller-facing transcript
48
+ inside one transaction, then constructs the result. Record fields and repository
49
+ arguments belong to its helpers. Transcript materialization stays inside the
50
+ transaction so failure rolls back the transition.
51
+
52
+ `commit_failed_outcome` persists audit-only records and the translated failure.
53
+ The Context revision stays unchanged because none of those records is a Context
54
+ candidate. Completion and Handoff advance Context only if the appended records
55
+ contain a candidate. Suspension retains its records in Execution working state,
56
+ advances Root to suspended and does not append Journal or advance Context.
57
+
58
+ `commit_handed_off` validates Source routing and cancellation, persists projected
59
+ Context, transfers routing to a deterministic Target execution ID, and saves
60
+ Source Journal, Execution and Root in one transaction. Target definition lookup
61
+ uses the transaction view; the existing saved Manifest reader keeps its service
62
+ context. Target completion/failure stabilizes routing inside its own existing
63
+ terminal transaction. No Provider/Tool dispatch or application delivery moves
64
+ into these workers.
65
+
66
+ ### Preserve the distinct readback contracts
67
+
68
+ | Operation | Existing confirmation rule after an exception | Consequence |
69
+ | --- | --- | --- |
70
+ | Ordinary/Handoff terminal | Same Agent, terminal Execution, expected revision + 1; then load saved Root, Journal and result | Reuse the persisted outcome; do not write a second terminal transition |
71
+ | Child-coordination wait | Complete saved Execution payload equals the intended waiting record | Return that nonterminal wait; active status alone does not establish a match |
72
+ | Approval suspension | It is nonterminal, so terminal readback does not confirm it | Propagate uncertain completion to the owner |
73
+
74
+ Terminal confirmation is deliberately not changed to DispatchPreparation's
75
+ full intended-payload comparison. Missing, active, mismatched or unreadable
76
+ results do not authorize another write. Readback errors retain their existing
77
+ propagation behavior. Recovered success retains the existing smaller result
78
+ shape; recovered failures use `RecoverySupport.error_from_failure`, including
79
+ its generic Phronomy::Error representation. This extraction does not normalize
80
+ those results or introduce a stronger commit-certainty protocol.
81
+
82
+ ### Preserve EventLoop result authority and delivery
83
+
84
+ The owner still waits for physical work quiescence before terminal persistence.
85
+ A stale result changes no live state, admission or Task. Completed/failed/Handoff
86
+ results release ownership and deliver their existing events before settling
87
+ waiters. Suspension retains admission and leaves the original Task pending;
88
+ exact observers receive the existing approval-required failure. Child waiting
89
+ releases ownership and fails waiters with recovery required.
90
+
91
+ A worker error for ordinary execution retains recovery_required admission and
92
+ pending waiters. The existing coordination-metadata error path releases the
93
+ owner and fails waiters. Those branches are not merged into unconditional cleanup.
94
+ Live Handoff request/Manifest delivery handles remain outside the worker command.
95
+
96
+ ### Internal types
97
+
98
+ ExecutionOutcomeCommitter owns `Command`, `Outcome`, `TerminalView` and
99
+ `HandoffTerminalView`. Coordinator retains its previous constant paths as aliases
100
+ to the same classes. All four member lists stay unchanged; their canonical Ruby
101
+ names change. TerminalDelivery and TerminalCommitReady remain owner-local.
102
+ These types are internal and are not serialized by class name. Public API, saved
103
+ schemas, Agent/Handoff Coordinator identity and caller selection remain unchanged.
104
+
105
+ ## Guarantees and verification
106
+
107
+ Using [ADR-018](018-durability-guarantees-and-failure-model.md):
108
+
109
+ | Subject / property | Provider | Failure / boundary | Result |
110
+ | --- | --- | --- | --- |
111
+ | Terminal or Handoff transition atomicity (G5), stale write rejection (G8) | Existing transaction and repository CAS | F0/F2; no new framework X0 | CONDITIONAL on conforming Persistence backend |
112
+ | No second terminal write after uncertain response | Operation-specific worker readback and owner recovery path | F1; no new worker X0 | YES; no stronger outcome-certainty guarantee |
113
+ | Current execution alone may apply worker results | Existing EventLoop owner/revision/session guards | F2/F3 and delayed completion | YES within the same Runtime ownership contract |
114
+ | Logical continuation from saved Source/Target state (G4) | Existing Handoff recovery and exact Target identity | F4; later execution can cross X0 | CONDITIONAL on existing recovery contracts |
115
+ | Cross-process exclusion (G7), external duplicate prevention (G9), exactly once (G10) | No new protocol added | F1/F2/F4 across X0 | NO new guarantee |
116
+
117
+ Behavior tests exercise completion/rejection, failure categories, suspension,
118
+ transaction rollback, Root CAS, response loss and readback mismatches/failures,
119
+ child waiting, atomic Source transfer, routing conflict/cancellation and Target
120
+ completion/failure. Existing suites cover physical quiescence, stale results,
121
+ callback policy, approval recovery and durable Handoff recovery. Unit fault
122
+ injections do not represent real process loss or live external service failures.
123
+ Architecture guards now inspect the actual workers and disallow repository
124
+ reloads in Coordinator without the old terminal-helper exceptions.
125
+
126
+ ## Owner-control review (2026-09-22)
127
+
128
+ The fifth stage keeps one execution owner and makes its remaining result paths
129
+ read as validation, state application and continuation/delivery. Preparation
130
+ recovery separates failed-outcome settlement from session restart. Approval
131
+ resume separates committed-state installation and tracing from FSM entry.
132
+ Terminal results retain visible outcome selection while private methods handle
133
+ the distinct waiting, suspension, completion, Handoff and failure deliveries.
134
+ Only the identical execution/admission release is shared across terminal paths.
135
+ Initial preparation snapshot construction is named explicitly; start/resume
136
+ admission flags, submission flags and their rescue decisions remain together.
137
+
138
+ No transaction, authority condition, failure policy, public type or class owner
139
+ changes. Direct Ready delivery tests run on the real EventLoop/ExecutionRegistry
140
+ and check stale-result isolation, state-before-notification, notification-before-
141
+ settlement, fallback waiters, suspension, uncertainty and recovery/resume failure
142
+ boundaries. These tests also pass against the preceding implementation, documenting
143
+ preserved behavior rather than a new contract. F0/F1/F3 are injected locally;
144
+ no new X0 operation or stronger F4 guarantee is claimed.
145
+
146
+ ## Remaining work
147
+
148
+ The execution-owner decomposition and final readability review are implemented.
149
+ Tool restoration, SharedState, Storage domain responsibilities and Workflow
150
+ terminal ownership remain separate work items. Further owner splitting needs a
151
+ concrete responsibility or failure-boundary reason; file length alone is not one.
@@ -0,0 +1,92 @@
1
+ # ADR-052: Tool Invocation Restoration Ownership
2
+
3
+ **Status**: Accepted on the architecture refactoring branch
4
+ **Date**: 2026-09-22
5
+ **Refines**: [046-agent-responsibility-layout-and-shared-records](046-agent-responsibility-layout-and-shared-records.md)
6
+ and [047-recovered-execution-continuation-contract](047-recovered-execution-continuation-contract.md).
7
+
8
+ ## Problem
9
+
10
+ InvocationRestorer decoded saved Tool batches and directly assigned
11
+ ToolInvocation's status, result, authorization decision and approval evidence.
12
+ Moving restoration into its own module in ADR-046 left that knowledge of another
13
+ object's representation intact. ADR-047 explicitly retained it as separate work.
14
+ A change to ToolInvocation's state representation therefore required Recovery to
15
+ track its instance variables as well as the saved format.
16
+
17
+ ## Decision
18
+
19
+ Keep the existing classes and introduce the internal
20
+ `ToolInvocation#restore_state!(status:, result: nil, approval_item: nil)` operation.
21
+ It is Ruby-public for the Recovery caller and classified `@api private`; it is
22
+ not an Application extension SPI. Its input is materialized semantic state,
23
+ not an Execution record or a persistence repository.
24
+
25
+ InvocationRestorer retains saved snapshot key normalization, Tool Call and Tool
26
+ lookup, construction of ordinary or missing-Tool invocations, and matching the
27
+ approval item by invocation ID. It converts the saved status to a Symbol and
28
+ passes the saved result and matched approval item to the newly constructed
29
+ ToolInvocation. No ToolInvocation instance variable is assigned by Recovery.
30
+ The separate AgentInvocation batch-ID assignment is outside this change.
31
+
32
+ ToolInvocation owns the supported-state dispatch and approval evidence copy:
33
+
34
+ | Saved state | Existing behavior retained |
35
+ | --- | --- |
36
+ | awaiting_approval | Validate arguments unless already terminal, restore require_approval and the waiting state |
37
+ | authorized | Validate arguments unless already terminal, restore allow and the authorized state |
38
+ | completed | Restore the saved result, including nil or false, and completed state |
39
+ | rejected | Restore rejection and its decision |
40
+ | failed | Restore the existing generic Tool preflight failure |
41
+ | cancelled | Restore cancellation |
42
+ | Other state | Raise ExecutionRehydrationRequiredError before applying approval evidence |
43
+
44
+ The entry method reads as saved-state application followed by saved approval
45
+ evidence application. Private methods contain each operation's implementation.
46
+ Evidence copying preserves the existing immutable facts and reason semantics,
47
+ including an explicit nil facts value. A missing approval item leaves constructor
48
+ defaults intact. Saved display evidence is not a new policy evaluation input.
49
+
50
+ The caller supplies a newly constructed invocation before session installation.
51
+ This is not a general rollback or arbitrary live-state replacement interface.
52
+ The existing EventLoop ownership and continuation validation remain unchanged.
53
+ The old internal `InvocationRestorer.restore_tool_snapshot!` entry is removed;
54
+ no forwarding compatibility wrapper is introduced for this private helper.
55
+
56
+ ## Compatibility and limits
57
+
58
+ No public API, RBS contract, saved schema, approval decision, external replay
59
+ eligibility, missing-Tool rule, or callback sequence changes. The existing
60
+ validation behavior is retained, including the handling of current Tool
61
+ validation errors and missing definitions; this refactor does not add a new
62
+ schema-migration or saved-state validation policy. It does not restore arbitrary
63
+ Application Tool instance variables.
64
+
65
+ Restoration itself does not run authorization policy, execute a Tool, acquire a
66
+ Runtime, perform persistence I/O or notify listeners. This does not suppress the
67
+ existing recovery installation notification of an outstanding approval request.
68
+ Load-time notification and later approval/rejection dispatch keep their existing
69
+ owners. External outcome-unknown resolution and framework operation recovery
70
+ remain with RecoveryCoordinator and the execution owner.
71
+
72
+ Under [018-durability-guarantees-and-failure-model](018-durability-guarantees-and-failure-model.md),
73
+ this change concerns operation-local reconstruction from already materialized
74
+ facts. Unsupported input is an F0 rejection without X0 dispatch by this operation.
75
+ Same logical execution resumption after F4 remains CONDITIONAL on the existing
76
+ confirmed saved state and operation-specific recovery contract. F1 outcome
77
+ resolution is unchanged. No new external-effect exactly-once guarantee follows.
78
+
79
+ ## Verification
80
+
81
+ Replace tests that mock another object's instance-variable writes with real
82
+ ToolInvocation behavior tests. Run identical assertions against the old owner
83
+ boundary and the new operation. Cover all six states, unsupported states, saved
84
+ results, missing definitions, immutable approval evidence, and dispatch gating
85
+ before and after approval. Existing restart tests exercise approval and rejection
86
+ through actual recovery installation and session continuation. Run the ordinary
87
+ and integration suites, API snapshot, annotations, RBS, examples and SQLite
88
+ persistence checks. No test count implies a stronger F4 or X0 guarantee.
89
+
90
+ SharedState coordination ownership, Storage domain responsibilities and Workflow
91
+ terminal persistence remain separate work. This decision completes only the Tool
92
+ restoration ownership item carried forward by ADR-047.
@@ -0,0 +1,71 @@
1
+ # ADR-053: SharedState Coordination Ownership
2
+
3
+ ## Status
4
+
5
+ Accepted on the architecture refactoring branch, 2026-09-22.
6
+ This resolves the deferred SharedState ownership decision in
7
+ [046-agent-responsibility-layout-and-shared-records](046-agent-responsibility-layout-and-shared-records.md).
8
+
9
+ ## Context
10
+
11
+ SharedState creates an invocation-local findings store, equips several Agent
12
+ definitions with read/write Tools, invokes members in declaration order, repeats
13
+ cycles until a configured limit, and aggregates the findings. It is not an
14
+ individual Agent's state container or execution coordinator. These responsibilities
15
+ belong to MultiAgent, alongside the other multi-Agent coordination patterns.
16
+
17
+ Its implementation also mixes cycle orchestration with individual member calls,
18
+ and researcher instrumentation with the bodies of injected Tool definitions.
19
+ Moving a file alone would leave those abstraction levels mixed.
20
+
21
+ ## Decision
22
+
23
+ 1. Move the implementation to `multi_agent/shared_state.rb`, exposing the
24
+ Experimental `Phronomy::MultiAgent::SharedState` and its nested `KnowledgeStore`.
25
+ Remove `Phronomy::Agent::SharedState` without an alias on this branch, following
26
+ the coordination ownership precedent in
27
+ [034-handoff-runner-coordination-ownership](034-handoff-runner-coordination-ownership.md).
28
+ Update examples, RBS and the [migration guide](../migrations/shared-state-multi-agent.md)
29
+ together. Ordinary consumers continue to `require "phronomy"`.
30
+ 2. Express invocation as termination validation, store creation, member
31
+ coordination and result aggregation. Express each cycle as ordered member
32
+ invocation followed by stopping decisions. Build the two injected Tool
33
+ definitions in named private methods. Keep these responsibilities in the same
34
+ class; no new execution manager or general coordination abstraction is needed.
35
+ 3. Preserve the DSL, method parameters, prompt text, Tool descriptions/schema,
36
+ cooperative execution mode, original Tool aliases, result shape and exceptions.
37
+ Members remain sequential; findings from an earlier member are visible to the
38
+ next member in the same cycle. `terminate_when` still takes precedence over
39
+ timeout after each complete cycle, and timeout does not interrupt a member.
40
+ 4. Preserve the generated Agent definition ID prefix
41
+ `Phronomy::Agent::SharedState::Instrumented/` and instrumentation version 1.
42
+ This string is semantic identity, not a Ruby constant lookup or old-name alias.
43
+ Namespace placement alone must not change the wrapped definition revision.
44
+
45
+ ## Consequences and limits
46
+
47
+ This is a breaking rename of an Experimental public API, including the nested
48
+ store class name. The Stable/Beta API snapshot does not enumerate SharedState;
49
+ an unchanged snapshot does not establish complete public API compatibility.
50
+
51
+ The store is still newly created for each invocation and is not durable shared
52
+ Agent state. The coordinator adds no resume, cancellation, asynchronous execution,
53
+ transaction, F4 or X0 guarantee. `invoke` still accepts `config:` without
54
+ forwarding it to the member calls. Revising that behavior is a separate change.
55
+
56
+ No persistence schema or saved-record migration is introduced. Individual Agent
57
+ persistence keeps its existing rules; this move does not make the in-memory
58
+ coordination lifetime resumable. The method extraction slightly increases source
59
+ length and does not eliminate unrelated dependency cycles.
60
+
61
+ Storage domain responsibilities and Workflow terminal ownership remain separate
62
+ work items. This decision does not reopen the completed ExecutionCoordinator split.
63
+
64
+ ## Verification obligations
65
+
66
+ Verify new namespace loading, absence of the old alias, repeated eager-load
67
+ identity and no Runtime startup during loading. Exercise existing coordination,
68
+ stopping, aggregation, Tool alias and definition identity tests. Check the full
69
+ and integration suites, offline examples, RBS, annotations, style and built gem
70
+ contents, including removal of the old implementation path. Record executed
71
+ results and untested environments separately in the delivery review.
@@ -0,0 +1,76 @@
1
+ # ADR-054: One Workflow Terminal Save Implementation
2
+
3
+ ## Status and scope
4
+
5
+ Accepted on the architecture refactoring branch, 2026-09-22.
6
+ Amends only the Workflow recovery prepend requirement in
7
+ [038-responsibility-based-source-layout](038-responsibility-based-source-layout.md).
8
+ The durable barrier in
9
+ [026-workflow-runtime-admission-and-durable-terminal-barrier](026-workflow-runtime-admission-and-durable-terminal-barrier.md)
10
+ and the receiver ownership in
11
+ [042-feature-owned-execution-state](042-feature-owned-execution-state.md)
12
+ remain unchanged.
13
+
14
+ ## Problem
15
+
16
+ WorkflowRunner defines terminal save submission, but WorkflowRecovery defines
17
+ the same method and is prepended during application loading. It never calls
18
+ `super`. The active implementation reconciles uncertain save outcomes against
19
+ durable pre/post state; the shadowed implementation does not. Reading or editing
20
+ the Runner alone can therefore target code that is not actually used.
21
+
22
+ ## Decision
23
+
24
+ Move the currently active submission and F1 reconciliation into WorkflowRunner.
25
+ Delete the shadowed implementation, the private WorkflowRecovery module and its
26
+ explicit installation. Do not introduce a new class or compatibility alias for
27
+ the removed private override.
28
+
29
+ Express terminal save submission as admission marking, immutable command
30
+ construction, Offload submission and session-local result delivery. Named private
31
+ methods own saving/reconciliation and delivery separately. The existing Command
32
+ and Result Data classes retain their identity and members.
33
+
34
+ The worker saves once. Portable ConflictError, NotFoundError, SerializationError
35
+ and UnsupportedBackendError remain known failures without readback. Other save
36
+ errors use the existing authoritative snapshot comparison: exact post-state is
37
+ success, pre-state preserves the original failure, and conflict or failed
38
+ readback remains outcome-unknown. No automatic save retry is introduced.
39
+
40
+ The existing session sink receives `workflow_terminal_persistence_result`.
41
+ Offload completion errors still become outcome-unknown, and rejected sink
42
+ delivery retains its existing warning. Runner still marks admission and captures
43
+ the snapshot on EventLoop; workers only use that captured command for persistence.
44
+
45
+ ## Compatibility and guarantees
46
+
47
+ This is an internal ownership/readability change. Public Workflow/Persistence
48
+ APIs, Backend SPI, snapshots, revisions, error messages and the session event
49
+ protocol are unchanged. Removing the prepend can make WorkflowRunner load lazily
50
+ under ordinary `require "phronomy"`; first access and eager loading must retain
51
+ the same recovery behavior without starting Runtime.
52
+
53
+ F1 save certainty remains CONDITIONAL on authoritative readback matching the
54
+ existing expected pre-state or intended post-state rules. It is not established
55
+ merely by receiving an exception. F0 portable failures follow the existing known
56
+ failure path. Runtime release, success notification and Task settlement still
57
+ wait for EventLoop-owned session acceptance; uncertain outcomes keep the existing
58
+ recovery-required behavior. F4 readability depends on the backend retaining
59
+ confirmed data. X0 external effects are outside this save, and this change adds
60
+ no execution replay, distributed ownership or exactly-once guarantee.
61
+
62
+ FSMSession still owns interpretation of the Workflow terminal persistence
63
+ event. Moving that policy out of Engine is a separate next step; this decision
64
+ does not claim that the entire Workflow terminal ownership issue is resolved.
65
+ Storage's fixed repository slots and Agent watermark also remain separate work.
66
+
67
+ ## Verification
68
+
69
+ Characterize the effective submission path before and after the change, including
70
+ immutable snapshots, known errors, F1 post/pre/conflicting/unreadable outcomes,
71
+ single-save behavior and failed Offload completion delivery. Preserve existing
72
+ real-runtime tests for delayed save, stream barriers, admission release and
73
+ uncertain outcomes. Verify ordinary/eager loading without the override, full and
74
+ integration suites, API/RBS/annotations/style, offline examples and built gem
75
+ contents. Keep results from tests actually run distinct from unavailable live
76
+ Provider or database-server evidence.
@@ -0,0 +1,60 @@
1
+ # ADR-055: Settle Terminal Observer Failures
2
+
3
+ ## Status
4
+
5
+ Accepted on the architecture refactoring branch, 2026-09-23.
6
+ Clarifies terminal observer failure ordering in
7
+ [026-workflow-runtime-admission-and-durable-terminal-barrier](026-workflow-runtime-admission-and-durable-terminal-barrier.md).
8
+ It does not move Workflow terminal policy out of FSMSession; that remains W2b.
9
+
10
+ ## Problem
11
+
12
+ FSMSession marked itself done before delivering a pending stable-state observer
13
+ notification. If that observer raised at a wait state or a declared terminal
14
+ state, the existing error handler saw done and returned without an error event.
15
+ The synchronous stream caller remained blocked and Workflow admission stayed
16
+ owned. Durable executions could already have saved their terminal snapshot.
17
+
18
+ The existing observer-exception test covered an automatic transition's ordinary
19
+ stable notification, before terminalization. It did not cover the deferred
20
+ terminal notification. Six real-runtime observations on both pre-Refactor-31 and
21
+ applied Refactor-31 source reproduce four affected cases and two unaffected
22
+ automatic-transition controls. This is not a regression introduced by Refactor 31.
23
+
24
+ ## Decision
25
+
26
+ Keep the existing terminal lifecycle selection, invoke the pending stable
27
+ observer, and set done only after the observer returns successfully. If it
28
+ raises, the existing start/handle/request error boundary emits the ordinary
29
+ error event with the original exception. EventLoop retires the session and the
30
+ existing Runner completion path releases admission before failing the caller.
31
+
32
+ Preserve successful notification-before-terminal-event ordering. All transitions
33
+ remain on EventLoop. Do not introduce a new class, callback retry, extra save,
34
+ Task settlement path, or direct admission mutation from the observer.
35
+
36
+ ## Durable meaning and limits
37
+
38
+ A durable terminal observer runs only after the session accepts a known-success
39
+ save result. Observer failure therefore means notification failed; it does not
40
+ mean the save failed, and must not erase, roll back, or repeat the saved record.
41
+ The caller receives the original observer exception. Applications must not
42
+ assume that every raised stream exception proves non-commit, nor replay external
43
+ effects automatically. This adds no exactly-once or crash-atomic notification
44
+ guarantee; existing F1/F4/X0 limits remain.
45
+
46
+ Known save failure and unresolved save uncertainty are unchanged. In particular,
47
+ the outcome-unknown path still retires the session without falsely settling the
48
+ Workflow Task or releasing its recovery-required admission.
49
+
50
+ ## Verification
51
+
52
+ Add public stream regressions for wait/declared-terminal boundaries, each with
53
+ and without persistence. Require the same exception object, one notification
54
+ on EventLoop, caller completion, session retirement and admission release.
55
+ For durable cases, also require the existing snapshot and revision 1 to remain.
56
+ Keep test cleanup bounded so these tests can demonstrate failure on old source.
57
+
58
+ Run the shared FSMSession/Agent/Tool tests, Workflow admission and F1 tests, full
59
+ and integration suites, API/RBS/style, examples and real SQLite. Keep W2b policy
60
+ extraction in a later package after application verification of this fix.
@@ -0,0 +1,87 @@
1
+ # ADR-056: Workflow-Owned Terminal Policy
2
+
3
+ ## Status
4
+
5
+ Accepted on the architecture refactoring branch, 2026-09-23.
6
+ Amends the implementation ownership in
7
+ [ADR-026](026-workflow-runtime-admission-and-durable-terminal-barrier.md).
8
+ Preserves the ordering and failure semantics clarified by
9
+ [ADR-055](055-terminal-observer-failure-settlement.md).
10
+
11
+ ## Context
12
+
13
+ FSMSession is used by WorkflowRunner, AgentInvocationSessionBuilder and
14
+ ToolInvocationSessionBuilder. It belongs to Engine, but it directly recognized
15
+ Workflow persistence events and interpreted success, known failure and unknown
16
+ save outcomes. Moving the entire session would couple Agent and Tool to Workflow.
17
+ Runner already owns the one terminal save and F1 reconciliation (ADR-054), and
18
+ WorkflowExecutionRegistry already owns admission and owner tokens (ADR-042).
19
+
20
+ ## Decision
21
+
22
+ WorkflowRunner injects a WorkflowTerminalPolicy only for durable execution.
23
+ Ephemeral Workflow, Agent and Tool sessions keep their immediate terminal path.
24
+ The policy holds the persistence callback, not live session state or admission.
25
+
26
+ The private session protocol is:
27
+
28
+ - `start(terminal_type:, context:, event_sink:)` begins the barrier on EventLoop.
29
+ Its return value does not authorize completion. Results use the bound sink.
30
+ - `handles?(event)` recognizes a policy event, including an early event that
31
+ must be discarded by the session before any terminal request.
32
+ - `decision_for(event)` interprets an accepted event and returns the immutable
33
+ `FSMProtocol::TerminalDecision(action:, error:)` value.
34
+
35
+ WorkflowTerminalPolicy maps success to `complete`, known failure to `fail` with
36
+ its original error or the existing fallback error, and outcome unknown to
37
+ `retire` with its diagnostic error. Invalid Workflow outcomes remain errors.
38
+ FSMSession knows these generic actions, not the Workflow event or outcome values.
39
+ An unsupported action fails through the existing session error path.
40
+
41
+ FSMSession alone owns the pending terminal type, stable-notification flag and
42
+ acceptance state. It enters `awaiting_terminal` before starting the policy,
43
+ ignores ordinary events during that wait, ignores early policy events, and
44
+ accepts at most one terminal decision. The policy must not maintain a second
45
+ pending lifecycle or mutate the session from a worker.
46
+
47
+ `complete` delivers any deferred stable notification before marking done and
48
+ posting the terminal event. `fail` uses the existing failure path. `retire`
49
+ marks the session retired and uses EventLoop's existing recovery-required
50
+ management route without settling a result. Runner persistence/F1 code,
51
+ Registry ownership and EventLoop retirement/shutdown code remain unchanged.
52
+
53
+ ## Uncertainty and shutdown
54
+
55
+ Retirement for an unknown save result removes the concrete routing session and
56
+ retains recovery-required admission. It does not complete or fail the caller.
57
+ The old sink cannot target a subsequent incarnation. Normal Runtime shutdown
58
+ clears Registry ownership and terminates the dispatcher, but does not synthesize
59
+ a Workflow result: the unresolved caller remains pending. Unexpected dispatcher
60
+ failure has its separate existing waiter cleanup. These are preserved behaviors,
61
+ not a new guarantee of automatic recovery or a stronger F1/F4/X0 contract.
62
+
63
+ ## Compatibility and non-goals
64
+
65
+ `terminal_barrier:` is replaced by private `terminal_policy:` with no alias.
66
+ The internal lifecycle names change to `awaiting_terminal` and `retired`.
67
+ Neither constructor injection nor these states are a new public plugin API.
68
+ Without an injected policy, Engine no longer reserves a Workflow-only event
69
+ name. Workflow's producer emits that event only for its durable execution path.
70
+ Session identity, EventSink correlation, public Workflow/Agent/Tool APIs,
71
+ Workflow result types, durable records and Storage SPI are unchanged.
72
+
73
+ Do not move snapshot persistence into the policy, settle caller Tasks there,
74
+ rename outcome strings while leaving interpretation in Engine, introduce a
75
+ subclass/prepend override, or duplicate admission. A small new policy and value
76
+ type clarify ownership; reducing the total line count is not the objective.
77
+
78
+ ## Verification
79
+
80
+ The generic session contract uses a non-Workflow event and policy, exercising
81
+ early/ordinary/duplicate events, completion, failure, retirement, submission
82
+ error, malformed decisions and the immediate default path. Workflow tests cover
83
+ invalid outcomes and the existing missing-error fallback through Runner wiring.
84
+ Real Runtime tests cover F1 pre-state, conflicting/unreadable readback, retained
85
+ admission and normal shutdown, stale sinks across incarnations, and submission
86
+ rejection. Existing delayed-save, halted-stream, observer-failure, Agent/Tool and
87
+ F1 tests remain acceptance gates; rejected result delivery must not retry save.
@@ -0,0 +1,73 @@
1
+ # ADR-057: Storage update constraints and explicit transaction boundaries
2
+
3
+ ## Status
4
+
5
+ Accepted for implementation in Refactor 34 (Storage S2a).
6
+
7
+
8
+ 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.
9
+
10
+ ## Context
11
+
12
+ The existing eight-repository SPI had three concrete behavioral differences.
13
+ InMemory allowed an inactive Agent execution to become active while another
14
+ execution for that Agent was active. SQL rejected that update. SQL Journal
15
+ append serialized each record immediately before inserting it; catching an
16
+ invalid later record inside the outer transaction could commit earlier rows
17
+ without advancing the head. Explicit nested InMemory transactions restored an
18
+ inner snapshot, whereas ActiveRecord's default nesting joined the outer scope.
19
+
20
+ ## Decision
21
+
22
+ Keep the existing SPI and data formats while closing these differences.
23
+
24
+ 1. InMemory checks active-owner uniqueness on Agent execution updates under the
25
+ same Monitor as revision validation and writes. Exclude the updated identity.
26
+ Report `Storage::ActiveExecutionConflictError` and leave record and revision
27
+ unchanged on rejection. Team terminal execution reactivation remains forbidden.
28
+ 2. Both SQL Journal adapters serialize the complete batch and normalize the
29
+ expected position before any writes. Preserve ID checks, lock order, CAS,
30
+ return values and transaction-bound connection access.
31
+ 3. Explicit `Backend#transaction` / `Persistence#transaction` nesting on the same
32
+ backend and synchronous execution context uses savepoint semantics. Roll back
33
+ a failed inner scope and re-raise the same exception. The outer scope may catch
34
+ it and continue. Inner success is not an independent commit: outer failure
35
+ rolls back both. SQL uses `requires_new: true` on the same checked-out
36
+ connection. InMemory retains its reentrant Monitor and snapshots.
37
+
38
+ The SQL wrappers also re-raise `ActiveRecord::Rollback` after ActiveRecord rolls
39
+ back and consumes it. The Storage API does not use an exception as a successful
40
+ return value. Repository operations on a bound view still join that scope; this
41
+ change does not introduce per-operation savepoints or separate connections.
42
+
43
+ ## Compatibility and migration
44
+
45
+ This intentionally changes SQL behavior. If an application catches an inner
46
+ failure, writes from that failed inner block will no longer remain in the outer
47
+ transaction. Catch outside the explicit inner block and re-read any conditions
48
+ needed to continue. Catching a database failure inside the same transaction view
49
+ and continuing is not a portable recovery contract; propagate it or isolate the
50
+ operation in an explicit inner transaction before executing it.
51
+
52
+ `ActiveRecord::Rollback` now propagates from the Storage/Persistence boundary.
53
+ Callers relying on ActiveRecord's silent rollback must catch it outside that
54
+ boundary. Backend authors should run the public Persistence contract suite;
55
+ method signatures and required capability keys are unchanged.
56
+
57
+ No schema, record type/version, payload, content identity or public facade
58
+ changes are required. Unknown commit outcomes remain backend/database failures;
59
+ this decision does not add exactly-once behavior or retry external effects.
60
+
61
+ ## Verification and remaining work
62
+
63
+ Shared tests cover rejected update immutability, nonconflicting active updates,
64
+ invalid later Journal records, retrying the same record IDs, inner-only rollback,
65
+ exception identity, continued outer writes, normal results and outer rollback.
66
+ Both raw Backend and domain Persistence transaction entry points are exercised.
67
+ SQL-specific tests cover `ActiveRecord::Rollback` propagation.
68
+
69
+ The new neutral Records/Streams/Blobs SPI, failed-view lifecycle and explicit
70
+ non-local block-exit handling (`return` / `break` / `throw`) belong to S2b.
71
+ Do not use non-local exits as portable commit controls. PostgreSQL live-server
72
+ conformance and concurrency tests remain a required integration gate; source
73
+ parity or tests with a non-PostgreSQL connection do not satisfy that gate.