phronomy 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (557) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +92 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +13 -3
  6. data/VERIFY.sh +27 -27
  7. data/benchmark/bench_agent_invoke.rb +26 -47
  8. data/benchmark/bench_context_assembler.rb +17 -15
  9. data/benchmark/bench_regression.rb +1 -1
  10. data/benchmark/bench_tool_schema.rb +1 -1
  11. data/docs/architecture/agent-chat-and-state-ownership.md +153 -0
  12. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  13. data/docs/architecture/agent-context.md +4 -2
  14. data/docs/architecture/agent-transition-ownership.md +13 -0
  15. data/docs/architecture/backend-groups.md +152 -0
  16. data/docs/architecture/before-llm-input.md +9 -3
  17. data/docs/architecture/context-management.md +31 -1
  18. data/docs/architecture/context-preparation-steps.md +90 -0
  19. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  20. data/docs/architecture/execution-abstraction-review.md +88 -0
  21. data/docs/architecture/execution-boundaries.md +28 -0
  22. data/docs/architecture/execution-metadata-and-values.md +86 -0
  23. data/docs/architecture/generator-verifier-ownership.md +111 -0
  24. data/docs/architecture/multi-agent-handoff.md +11 -4
  25. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  26. data/docs/architecture/persistence.md +45 -4
  27. data/docs/architecture/r8-unit1.md +104 -0
  28. data/docs/architecture/r8-unit10.md +72 -0
  29. data/docs/architecture/r8-unit11.md +71 -0
  30. data/docs/architecture/r8-unit12.md +67 -0
  31. data/docs/architecture/r8-unit13.md +61 -0
  32. data/docs/architecture/r8-unit14.md +72 -0
  33. data/docs/architecture/r8-unit15.md +65 -0
  34. data/docs/architecture/r8-unit16.md +54 -0
  35. data/docs/architecture/r8-unit17.md +73 -0
  36. data/docs/architecture/r8-unit18.md +92 -0
  37. data/docs/architecture/r8-unit19.md +61 -0
  38. data/docs/architecture/r8-unit2.md +55 -0
  39. data/docs/architecture/r8-unit20.md +51 -0
  40. data/docs/architecture/r8-unit21.md +79 -0
  41. data/docs/architecture/r8-unit22.md +74 -0
  42. data/docs/architecture/r8-unit23.md +81 -0
  43. data/docs/architecture/r8-unit3.md +130 -0
  44. data/docs/architecture/r8-unit4.md +79 -0
  45. data/docs/architecture/r8-unit5.md +115 -0
  46. data/docs/architecture/r8-unit6.md +71 -0
  47. data/docs/architecture/r8-unit7.md +70 -0
  48. data/docs/architecture/r8-unit8.md +78 -0
  49. data/docs/architecture/r8-unit9.md +73 -0
  50. data/docs/architecture/refactoring-closure.md +343 -0
  51. data/docs/architecture/remaining-refactoring-plan.md +390 -0
  52. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  53. data/docs/architecture/storage-execution-boundaries.md +54 -0
  54. data/docs/architecture/tool-schema-recording-gap.md +56 -0
  55. data/docs/architecture/tracing.md +2 -2
  56. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  57. data/docs/architecture.md +222 -0
  58. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  59. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  60. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  61. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  62. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  63. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  64. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +7 -0
  65. data/docs/decisions/031-durable-multi-agent-coordination.md +7 -0
  66. data/docs/decisions/032-storage-backend-composition.md +85 -0
  67. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  68. data/docs/decisions/034-handoff-runner-coordination-ownership.md +78 -0
  69. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  70. data/docs/decisions/036-context-contract-ownership.md +86 -0
  71. data/docs/decisions/037-common-definition-ownership.md +62 -0
  72. data/docs/decisions/038-responsibility-based-source-layout.md +145 -0
  73. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +82 -0
  74. data/docs/decisions/040-configuration-default-composition.md +105 -0
  75. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  76. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  77. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  78. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  79. data/docs/decisions/045-worker-input-restriction-ownership.md +104 -0
  80. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  81. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  82. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  83. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  84. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  85. data/docs/decisions/051-execution-outcome-worker-ownership.md +153 -0
  86. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  87. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  88. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  89. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  90. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  91. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  92. data/docs/decisions/058-neutral-storage-primitives.md +83 -0
  93. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  94. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  95. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  96. data/docs/decisions/062-content-store-service.md +52 -0
  97. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  98. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  99. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  100. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  101. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  102. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  103. data/docs/decisions/README.md +52 -6
  104. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  105. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  106. data/docs/execution-boundaries-preview.md +45 -0
  107. data/docs/features.md +16 -5
  108. data/docs/getting-started.md +11 -8
  109. data/docs/migrations/0.15.md +5 -0
  110. data/docs/migrations/0.22.md +2 -2
  111. data/docs/migrations/durable-semantic-coordination-v2.md +8 -0
  112. data/docs/migrations/handoff-runner-multi-agent.md +37 -0
  113. data/docs/migrations/neutral-storage-spi.md +43 -0
  114. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  115. data/docs/migrations/persistence-failure-contracts.md +67 -0
  116. data/docs/migrations/r8-unit10.md +36 -0
  117. data/docs/migrations/r8-unit11.md +33 -0
  118. data/docs/migrations/r8-unit12.md +31 -0
  119. data/docs/migrations/r8-unit13.md +36 -0
  120. data/docs/migrations/r8-unit14.md +31 -0
  121. data/docs/migrations/r8-unit15.md +31 -0
  122. data/docs/migrations/r8-unit16.md +26 -0
  123. data/docs/migrations/r8-unit17.md +28 -0
  124. data/docs/migrations/r8-unit18.md +29 -0
  125. data/docs/migrations/r8-unit19.md +28 -0
  126. data/docs/migrations/r8-unit20.md +24 -0
  127. data/docs/migrations/r8-unit21.md +28 -0
  128. data/docs/migrations/r8-unit22.md +29 -0
  129. data/docs/migrations/r8-unit23.md +20 -0
  130. data/docs/migrations/r8-unit4.md +67 -0
  131. data/docs/migrations/r8-unit5.md +75 -0
  132. data/docs/migrations/r8-unit6.md +109 -0
  133. data/docs/migrations/r8-unit7.md +31 -0
  134. data/docs/migrations/r8-unit8.md +33 -0
  135. data/docs/migrations/r8-unit9.md +28 -0
  136. data/docs/migrations/shared-state-multi-agent.md +44 -0
  137. data/docs/migrations/storage-async-client.md +103 -0
  138. data/docs/migrations/storage-backend-composition.md +117 -0
  139. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  140. data/docs/migrations/vector-async-clients.md +110 -0
  141. data/docs/persistence-backends.md +190 -215
  142. data/docs/runtime-and-concurrency.md +142 -32
  143. data/lib/phronomy/agent/admission.rb +78 -0
  144. data/lib/phronomy/{agent_already_exists_error.rb → agent/agent_already_exists_error.rb} +2 -0
  145. data/lib/phronomy/{agent_busy_error.rb → agent/agent_busy_error.rb} +2 -0
  146. data/lib/phronomy/{agent_purged_error.rb → agent/agent_purged_error.rb} +2 -0
  147. data/lib/phronomy/agent/api/agent.rb +17 -0
  148. data/lib/phronomy/{execution_rehydration_required_error.rb → agent/api/execution_rehydration_required_error.rb} +2 -0
  149. data/lib/phronomy/agent/async_event_api.rb +38 -33
  150. data/lib/phronomy/agent/base.rb +106 -294
  151. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  152. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  153. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  154. data/lib/phronomy/agent/context_importer.rb +2 -2
  155. data/lib/phronomy/agent/{context_assembler.rb → context_preparation.rb} +157 -288
  156. data/lib/phronomy/agent/control_request.rb +21 -0
  157. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  158. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +32 -63
  159. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  160. data/lib/phronomy/agent/execution/dispatch_preparation.rb +195 -0
  161. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +43 -23
  162. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +8 -5
  163. data/lib/phronomy/agent/execution/execution_coordinator.rb +1744 -0
  164. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  165. data/lib/phronomy/agent/execution/execution_metadata.rb +54 -0
  166. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +288 -0
  167. data/lib/phronomy/agent/execution/execution_registry.rb +463 -0
  168. data/lib/phronomy/agent/execution/execution_session_runner.rb +113 -0
  169. data/lib/phronomy/agent/execution/initial_preparation.rb +323 -0
  170. data/lib/phronomy/agent/execution/invocation_actions.rb +368 -0
  171. data/lib/phronomy/agent/execution/invocation_transitions.rb +124 -0
  172. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  173. data/lib/phronomy/agent/execution_change.rb +161 -0
  174. data/lib/phronomy/agent/execution_environment.rb +36 -0
  175. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  176. data/lib/phronomy/agent/execution_observation.rb +35 -0
  177. data/lib/phronomy/agent/filterable.rb +182 -0
  178. data/lib/phronomy/agent/{handoff_context.rb → handoff/transfer_context.rb} +6 -6
  179. data/lib/phronomy/agent/{handoff_projection.rb → handoff/transfer_projection.rb} +14 -33
  180. data/lib/phronomy/agent/handoff_error.rb +7 -0
  181. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  182. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  183. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  184. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  185. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  186. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  187. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -15
  188. data/lib/phronomy/agent/persistence/agent_repository.rb +69 -0
  189. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  190. data/lib/phronomy/agent/persistence/codec.rb +338 -0
  191. data/lib/phronomy/agent/persistence/execution_repository.rb +124 -0
  192. data/lib/phronomy/agent/persistence/journal_repository.rb +63 -0
  193. data/lib/phronomy/agent/persistence/records.rb +33 -0
  194. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  195. data/lib/phronomy/agent/persistence/storage_schema.rb +29 -0
  196. data/lib/phronomy/agent/persistence/watermark.rb +29 -0
  197. data/lib/phronomy/agent/recovery/invocation_restorer.rb +124 -0
  198. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +28 -51
  199. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +50 -55
  200. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +38 -43
  201. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +26 -23
  202. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  203. data/lib/phronomy/agent/recovery/recovery_support.rb +224 -0
  204. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  205. data/lib/phronomy/agent/retention.rb +17 -0
  206. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  207. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  208. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  209. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  210. data/lib/phronomy/agent/{ruby_llm_materializer.rb → runtime_input.rb} +19 -39
  211. data/lib/phronomy/agent/saved_context_reader.rb +55 -0
  212. data/lib/phronomy/agent/settings.rb +47 -0
  213. data/lib/phronomy/agent/state_writer.rb +166 -0
  214. data/lib/phronomy/agent/store.rb +253 -0
  215. data/lib/phronomy/{stream_callback_error.rb → agent/stream_callback_error.rb} +2 -0
  216. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  217. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  218. data/lib/phronomy/agent/tool_execution/tool_binding.rb +30 -0
  219. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +114 -248
  220. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  221. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  222. data/lib/phronomy/common/configuration_error.rb +7 -0
  223. data/lib/phronomy/common/error.rb +5 -0
  224. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  225. data/lib/phronomy/common/values/serializable.rb +32 -0
  226. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  227. data/lib/phronomy/content_store/storage_schema.rb +12 -0
  228. data/lib/phronomy/content_store/stored_contents.rb +44 -0
  229. data/lib/phronomy/context/assembly.rb +202 -0
  230. data/lib/phronomy/{context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +5 -1
  231. data/lib/phronomy/context/candidate.rb +51 -0
  232. data/lib/phronomy/context/constraint.rb +47 -0
  233. data/lib/phronomy/{agent → context}/context_plan.rb +2 -2
  234. data/lib/phronomy/{agent → context}/context_plan_validator.rb +3 -3
  235. data/lib/phronomy/{agent → context}/context_policy.rb +1 -1
  236. data/lib/phronomy/{agent → context}/context_policy_input.rb +7 -7
  237. data/lib/phronomy/{agent → context}/context_policy_input_builder.rb +1 -1
  238. data/lib/phronomy/context/default_policy.rb +140 -0
  239. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  240. data/lib/phronomy/{agent → context}/llm_input_build_context.rb +2 -2
  241. data/lib/phronomy/{agent → context}/llm_input_manifest.rb +21 -21
  242. data/lib/phronomy/{agent → context}/llm_input_patch.rb +3 -3
  243. data/lib/phronomy/context/manifest_error.rb +10 -0
  244. data/lib/phronomy/context/prompt_template.rb +98 -0
  245. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  246. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  247. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  248. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  249. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  250. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  251. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  252. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  253. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  254. data/lib/phronomy/embeddings/base.rb +43 -0
  255. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  256. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  257. data/lib/phronomy/engine/concurrency/offload_pool.rb +39 -20
  258. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  259. data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +1 -1
  260. data/lib/phronomy/engine/event_loop.rb +164 -630
  261. data/lib/phronomy/engine/execution_binding.rb +23 -0
  262. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  263. data/lib/phronomy/engine/fsm_protocol.rb +9 -0
  264. data/lib/phronomy/engine/fsm_session.rb +37 -30
  265. data/lib/phronomy/engine/metrics.rb +28 -0
  266. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  267. data/lib/phronomy/engine/runtime.rb +104 -95
  268. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  269. data/lib/phronomy/execution/async_operation.rb +41 -0
  270. data/lib/phronomy/execution/backpressure_error.rb +7 -0
  271. data/lib/phronomy/{blocking.rb → execution/blocking.rb} +2 -2
  272. data/lib/phronomy/execution/cancellation_error.rb +7 -0
  273. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +5 -1
  274. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  275. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  276. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  277. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  278. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  279. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  280. data/lib/phronomy/execution/concurrency/worker_input_restricted.rb +14 -0
  281. data/lib/phronomy/execution/event_loop_reentrancy_error.rb +8 -0
  282. data/lib/phronomy/{execution.rb → execution/execution.rb} +57 -30
  283. data/lib/phronomy/{execution_cancellation_error.rb → execution/execution_cancellation_error.rb} +2 -0
  284. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  285. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  286. data/lib/phronomy/{execution_timeout_error.rb → execution/execution_timeout_error.rb} +2 -0
  287. data/lib/phronomy/{invalid_async_entry_action_error.rb → execution/invalid_async_entry_action_error.rb} +2 -0
  288. data/lib/phronomy/{invalid_async_transition_action_error.rb → execution/invalid_async_transition_action_error.rb} +2 -0
  289. data/lib/phronomy/{invalid_async_workflow_action_error.rb → execution/invalid_async_workflow_action_error.rb} +2 -0
  290. data/lib/phronomy/{invocation_context.rb → execution/invocation_context.rb} +0 -15
  291. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  292. data/lib/phronomy/execution/pool_shutdown_error.rb +7 -0
  293. data/lib/phronomy/execution/recursion_limit_error.rb +7 -0
  294. data/lib/phronomy/execution/runnable.rb +24 -0
  295. data/lib/phronomy/execution/runtime_shutdown_error.rb +7 -0
  296. data/lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb +7 -0
  297. data/lib/phronomy/execution/scheduler_reentrancy_error.rb +9 -0
  298. data/lib/phronomy/{engine → execution}/task_result.rb +7 -3
  299. data/lib/phronomy/execution/timeout_error.rb +7 -0
  300. data/lib/phronomy/execution/wait_policy.rb +24 -0
  301. data/lib/phronomy/filter/filter_block_error.rb +14 -0
  302. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  303. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  304. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  305. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  306. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  307. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  308. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  309. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  310. data/lib/phronomy/llm_adapter/authentication_error.rb +9 -0
  311. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  312. data/lib/phronomy/llm_adapter/base.rb +49 -68
  313. data/lib/phronomy/llm_adapter/context_length_error.rb +9 -0
  314. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  315. data/lib/phronomy/llm_adapter/message.rb +62 -0
  316. data/lib/phronomy/llm_adapter/rate_limit_error.rb +9 -0
  317. data/lib/phronomy/llm_adapter/request.rb +79 -0
  318. data/lib/phronomy/llm_adapter/response.rb +36 -0
  319. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  320. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  321. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  322. data/lib/phronomy/llm_adapter.rb +3 -16
  323. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  324. data/lib/phronomy/multi_agent/admission_registry.rb +15 -2
  325. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  326. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  327. data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
  328. data/lib/phronomy/{agent → multi_agent}/handoff_capability_factory.rb +21 -2
  329. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  330. data/lib/phronomy/{agent → multi_agent}/handoff_policy.rb +1 -1
  331. data/lib/phronomy/multi_agent/handoff_runner.rb +156 -0
  332. data/lib/phronomy/{agent → multi_agent}/handoff_state.rb +2 -2
  333. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  334. data/lib/phronomy/multi_agent/persistence/codec.rb +75 -0
  335. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  336. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  337. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  338. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +124 -0
  339. data/lib/phronomy/multi_agent/persistence/team_repository.rb +69 -0
  340. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  341. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  342. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  343. data/lib/phronomy/multi_agent/store.rb +156 -0
  344. data/lib/phronomy/multi_agent/team_coordinator.rb +87 -75
  345. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  346. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +5 -5
  347. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  348. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  349. data/lib/phronomy/persistence/content_repository.rb +26 -0
  350. data/lib/phronomy/persistence/errors.rb +16 -0
  351. data/lib/phronomy/persistence/migration/initial_format_migration.rb +21 -21
  352. data/lib/phronomy/persistence/persistence.rb +23 -0
  353. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  354. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  355. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  356. data/lib/phronomy/persistence/transaction.rb +86 -0
  357. data/lib/phronomy/persistence_composition/storage_schema.rb +29 -0
  358. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  359. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  360. data/lib/phronomy/runtime_composition/agent_defaults.rb +11 -0
  361. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  362. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  363. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  364. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  365. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  366. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  367. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  368. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  369. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  370. data/lib/phronomy/storage/async/async_client.rb +60 -0
  371. data/lib/phronomy/storage/backend.rb +107 -0
  372. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  373. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  374. data/lib/phronomy/storage/blobs.rb +31 -0
  375. data/lib/phronomy/storage/condition.rb +25 -0
  376. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  377. data/lib/phronomy/storage/conflict_error.rb +9 -0
  378. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  379. data/lib/phronomy/storage/entry.rb +33 -0
  380. data/lib/phronomy/storage/guard_ref.rb +13 -0
  381. data/lib/phronomy/storage/not_found_error.rb +9 -0
  382. data/lib/phronomy/storage/record_codec.rb +177 -0
  383. data/lib/phronomy/storage/records.rb +61 -0
  384. data/lib/phronomy/storage/resource.rb +126 -0
  385. data/lib/phronomy/storage/scope.rb +25 -0
  386. data/lib/phronomy/storage/serialization_error.rb +9 -0
  387. data/lib/phronomy/storage/streams.rb +44 -0
  388. data/lib/phronomy/storage/transaction_error.rb +10 -0
  389. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  390. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  391. data/lib/phronomy/storage/validation.rb +53 -0
  392. data/lib/phronomy/storage/view.rb +131 -0
  393. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  394. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  395. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  396. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  397. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +54 -48
  398. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +1 -1
  399. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +1 -1
  400. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  401. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +52 -46
  402. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  403. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +124 -0
  404. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  405. data/lib/phronomy/tool/authorization.rb +77 -0
  406. data/lib/phronomy/tool/base.rb +549 -9
  407. data/lib/phronomy/tool/call_request.rb +40 -0
  408. data/lib/phronomy/{agent/tool_definition_set.rb → tool/definition_set.rb} +16 -12
  409. data/lib/phronomy/tool/operation.rb +88 -0
  410. data/lib/phronomy/tool/schema.rb +214 -0
  411. data/lib/phronomy/tool/settings.rb +31 -0
  412. data/lib/phronomy/tool/tool_error.rb +7 -0
  413. data/lib/phronomy/{agent → tool}/tool_executor.rb +10 -7
  414. data/lib/phronomy/tools/agent.rb +22 -90
  415. data/lib/phronomy/tools/mcp.rb +21 -14
  416. data/lib/phronomy/tools/vector_search.rb +4 -4
  417. data/lib/phronomy/tracing/automatic.rb +11 -6
  418. data/lib/phronomy/tracing/base.rb +1 -1
  419. data/lib/phronomy/tracing/observation.rb +37 -0
  420. data/lib/phronomy/tracing/settings.rb +36 -0
  421. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  422. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  423. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  424. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  425. data/lib/phronomy/vector_store/base.rb +118 -49
  426. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  427. data/lib/phronomy/vector_store.rb +2 -17
  428. data/lib/phronomy/version.rb +1 -1
  429. data/lib/phronomy/workflow/completion.rb +11 -0
  430. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
  431. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  432. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +5 -1
  433. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  434. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  435. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +186 -0
  436. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +145 -115
  437. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  438. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  439. data/lib/phronomy/workflow/persistence/state_repository.rb +63 -0
  440. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  441. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  442. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  443. data/lib/phronomy/workflow/runtime_binding/workflow_terminal_policy.rb +40 -0
  444. data/lib/phronomy.rb +97 -98
  445. data/scripts/api_snapshot.rb +36 -16
  446. data/scripts/storage_spi_snapshot.rb +36 -0
  447. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  448. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  449. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  450. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  451. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  452. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  453. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  454. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  455. data/sig/phronomy/agent/admission.rbs +19 -0
  456. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  457. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  458. data/sig/phronomy/agent/store.rbs +73 -0
  459. data/sig/phronomy/agent.rbs +14 -139
  460. data/sig/phronomy/common.rbs +7 -0
  461. data/sig/phronomy/content_store/contracts.rbs +13 -0
  462. data/sig/phronomy/context.rbs +152 -0
  463. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  464. data/sig/phronomy/embeddings/base.rbs +17 -0
  465. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  466. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  467. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  468. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  469. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  470. data/sig/phronomy/engine/observability.rbs +13 -0
  471. data/sig/phronomy/execution/async_operation.rbs +7 -0
  472. data/sig/phronomy/execution/blocking.rbs +5 -0
  473. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  474. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  475. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  476. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  477. data/sig/phronomy/execution/errors.rbs +7 -0
  478. data/sig/phronomy/execution/execution.rbs +41 -0
  479. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  480. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  481. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  482. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  483. data/sig/phronomy/execution/task_result.rbs +29 -0
  484. data/sig/phronomy/extensions.rbs +0 -21
  485. data/sig/phronomy/handoff.rbs +12 -4
  486. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  487. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  488. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  489. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  490. data/sig/phronomy/llm_context_window.rbs +10 -0
  491. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  492. data/sig/phronomy/multi_agent/store.rbs +54 -0
  493. data/sig/phronomy/multi_agent.rbs +24 -3
  494. data/sig/phronomy/persistence/errors.rbs +18 -0
  495. data/sig/phronomy/persistence/migration.rbs +13 -0
  496. data/sig/phronomy/persistence/transaction.rbs +19 -0
  497. data/sig/phronomy/persistence.rbs +3 -185
  498. data/sig/phronomy/persistence_composition.rbs +19 -0
  499. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  500. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  501. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  502. data/sig/phronomy/storage/contracts.rbs +160 -0
  503. data/sig/phronomy/tool.rbs +58 -5
  504. data/sig/phronomy/tracing/observation.rbs +7 -0
  505. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  506. data/sig/phronomy/vector_store/base.rbs +23 -0
  507. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  508. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  509. data/sig/phronomy/workflow/store.rbs +8 -0
  510. data/sig/phronomy/workflow.rbs +1 -1
  511. data/sig/phronomy.rbs +0 -49
  512. metadata +462 -130
  513. data/lib/phronomy/agent/agent_invocation_session_builder.rb +0 -584
  514. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  515. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  516. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  517. data/lib/phronomy/agent/context/capability/base.rb +0 -507
  518. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  519. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  520. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  521. data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
  522. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  523. data/lib/phronomy/agent/handoff_request.rb +0 -45
  524. data/lib/phronomy/agent/handoff_runner.rb +0 -178
  525. data/lib/phronomy/agent/phase_machine_builder.rb +0 -152
  526. data/lib/phronomy/agent/provider_call_outcome.rb +0 -106
  527. data/lib/phronomy/agent/recovery_support.rb +0 -504
  528. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  529. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  530. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  531. data/lib/phronomy/agent/tool_call_intercepted.rb +0 -29
  532. data/lib/phronomy/agent/tool_invocation_session_builder.rb +0 -276
  533. data/lib/phronomy/configuration.rb +0 -59
  534. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  535. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  536. data/lib/phronomy/generator_verifier.rb +0 -369
  537. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  538. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  539. data/lib/phronomy/metrics.rb +0 -26
  540. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  541. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  542. data/lib/phronomy/persistence/in_memory.rb +0 -690
  543. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  544. data/lib/phronomy/persistence.rb +0 -276
  545. data/lib/phronomy/recovery.rb +0 -186
  546. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  547. data/lib/phronomy/runnable.rb +0 -54
  548. data/lib/phronomy/token_usage.rb +0 -85
  549. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  550. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  551. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  552. data/lib/phronomy/workflow_recovery.rb +0 -123
  553. data/sig/phronomy/llm_adapter.rbs +0 -11
  554. data/sig/phronomy/runtime.rbs +0 -76
  555. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  556. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  557. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
@@ -1,179 +1,165 @@
1
- # Persistence backend contract
2
-
3
- `Phronomy::Persistence` is the single durable-state backend abstraction used by
4
- stateful Agents and durable Workflows. This document is the normative contract
5
- for authors of custom Persistence backends.
6
-
7
- The Backend SPI is **Beta**. It may evolve in a minor pre-1.0 release, but a
8
- backend should not depend on Phronomy private APIs or Runtime internals.
9
-
10
- ## Architecture boundary
11
-
12
- A backend implements durable storage only:
13
-
14
- ```text
15
- Application
16
- ↓
17
- Agent / Workflow
18
- ↓
19
- Runtime / EventLoop / ExecutionCoordinator
20
- ↓
21
- Phronomy::Persistence synchronous Backend SPI
22
- ↓
23
- Database / durable storage
24
- ```
25
-
26
- Persistence does not own live Agent identity, top-level Runtime admission, or
27
- live execution state. In particular, a backend must not persist or reconstruct
28
- the following as part of this SPI:
29
-
30
- - Runtime Agent ownership-registry entries;
31
- - EventLoop Agent top-level admission entries;
32
- - EventLoop Agent execution-directory entries;
33
- - `AgentInvocation`;
34
- - `FSMSession`;
35
- - `TaskResult` or callbacks;
36
- - EventLoop queue contents;
37
- - Runtime Workflow admission entries;
38
- - in-flight provider operations.
39
-
40
- Persistence operations are synchronous. Framework-owned blocking Persistence I/O
41
- is submitted to the Runtime OffloadPool by Phronomy; a backend must not post
42
- EventLoop events or introduce `load_async` / `save_async` variants into this
43
- contract.
44
-
45
- ## Required root surface
46
-
47
- A Persistence backend exposes eight durable repositories:
48
-
49
- ```text
50
- contents
51
- agents
52
- journals
53
- executions
54
- workflow_states
55
- handoff_states
56
- teams
57
- team_executions
58
- ```
59
-
60
- and two root operations:
1
+ # Persistence and neutral Storage SPI 2
2
+
3
+ [ADR-058](decisions/058-neutral-storage-primitives.md) defines the current
4
+ extension contract. This is an intentional breaking replacement of the old
5
+ fixed-repository Backend SPI. Application construction now uses
6
+ `PersistenceComposition.build(backend:)` or `.in_memory`; pass its `agent`, `team`,
7
+ or `workflow` component to that domain. `Persistence.new(backend:)` provides only
8
+ the neutral coordinator. The old eight-repository facade and its queries were
9
+ removed; see [r8 unit 3](architecture/r8-unit3.md).
10
+
11
+ ## Ownership and composition
12
+
13
+ Storage owns `Resource`, `Backend`, `View`, `Records`, `Streams`, `Blobs`, immutable
14
+ `Entry` values, guards, conditions and neutral exceptions. It has no Agent/Team
15
+ record types, active-execution policy, content digest algorithm or watermark API.
16
+
17
+ Agent, Team, Workflow and ContentStore declare their own resource schemas.
18
+ `PersistenceComposition::StorageSchema` gathers those declarations and
19
+ `PersistenceComposition` binds domain record adapters to one coordinator.
20
+ Team and Workflow schemas live in their existing `persistence/` implementation
21
+ directories. An isolated Agent uses `PersistenceComposition.agent` and does not
22
+ construct Team or Workflow stores.
23
+ `ContentStore::StoredContents` owns SHA-256 identity and digest verification.
24
+ The Agent-owned `Watermark` composes guarded revision and stream-head conditions.
25
+
26
+ InMemory receives `resources:` and supplies one Monitor/snapshot transaction
27
+ across the catalog. SQL reference composition supplies the same catalog and a
28
+ separate physical table/column mapping to the neutral driver in examples
29
+ `shared/storage`. Table names, columns, indexes, DurableRecord envelopes, payloads,
30
+ format versions and content identities remain unchanged.
31
+
32
+ The [S3 closure review](architecture/refactoring-closure.md) records why these
33
+ names and placements remain and separates applied evidence from the candidate.
34
+
35
+ ## Backend and View
61
36
 
62
37
  ```ruby
63
- persistence.transaction { |tx| ... }
64
- persistence.assert_agent_watermark!(
65
- agent_id:,
66
- agent_revision:,
67
- journal_position:
68
- )
38
+ backend = Phronomy::Storage::Backends::InMemory.new(resources: resources)
39
+ backend.view.records(resource)
40
+ backend.view.streams(resource)
41
+ backend.view.blobs(resource)
42
+ backend.transaction { |view| ... }
43
+ view.check!(guards: guards, conditions: conditions)
69
44
  ```
70
45
 
71
- The object yielded by `transaction` is a transaction-scoped Persistence view. It
72
- must respond to all eight repository accessors and
73
- `assert_agent_watermark!`. It may be the Persistence instance itself, but SQL
74
- backends may instead yield an object bound to a checked-out connection or
75
- transaction session.
76
-
77
- ## Required capabilities
78
-
79
- Every backend must advertise:
80
-
81
- ```ruby
82
- {
83
- atomic_all: true,
84
- atomic_admission: true,
85
- optimistic_revision: true
86
- }
87
- ```
88
-
89
- `Phronomy::Persistence::REQUIRED_CAPABILITIES` is the executable definition of
90
- this requirement.
91
-
92
- ### `atomic_all`
93
-
94
- All durable repositories must be able to participate in one atomic transaction
95
- domain. A transaction may change `contents`, `agents`, `journals`, `executions`,
96
- `workflow_states`, `handoff_states`, `teams`, and `team_executions`, and then either commit all changes or roll them all back.
97
-
98
- This requirement deliberately does not claim exactly-once semantics after an
99
- indeterminate database/network failure. If the underlying database cannot tell
100
- the caller whether a commit happened, the backend should surface the storage
101
- failure rather than pretending the outcome is known.
102
-
103
- ### `atomic_admission`
104
-
105
- This capability is a **durable Agent execution integrity defense**. It is not the
106
- primary same-process Agent ownership/admission mechanism and it is not Workflow
107
- distributed locking.
108
-
109
- For one Agent, `executions.create_active` must atomically guarantee both:
110
-
111
- ```text
112
- execution_id is unique
113
- AND
114
- no active/suspended execution already exists for agent_id
115
- ```
116
-
117
- A conflict with an existing active/suspended execution raises
118
- `Phronomy::AgentBusyError`.
119
-
120
- Within one process, Runtime/EventLoop admission is acquired before the initial
121
- Persistence operation and is the primary competing-execution exclusion
122
- mechanism. `atomic_admission` remains required as the durable second line of
123
- defense against stale paths, durable conflicts, and unsupported cross-process
124
- races. It must not be removed merely because Runtime admission exists.
125
-
126
- Workflow admission remains Runtime/process-local. Cross-process Agent or
127
- Workflow ownership/lease/fencing is a separate distributed-coordination concern
128
- and is not part of this Backend SPI.
129
-
130
- ### `optimistic_revision`
131
-
132
- The backend must implement compare-and-swap semantics used by Agent roots,
133
- Agent executions, Journals, Workflow snapshots, and the durable Agent watermark.
134
- Stale writers must receive `Phronomy::Persistence::ConflictError`; they must not
135
- silently overwrite newer durable state.
136
-
137
- ## Error contract
138
-
139
- Backends should translate backend-specific constraint errors into the following
140
- portable Phronomy errors when the meaning matches.
141
-
142
- ### `Phronomy::Persistence::NotFoundError`
143
-
144
- A requested durable record does not exist.
145
-
146
- ### `Phronomy::Persistence::ConflictError`
147
-
148
- A persistence precondition failed, including revision, Journal position,
149
- identity, duplicate-ID, or compare-and-swap conflicts.
150
-
151
- ### `Phronomy::AgentBusyError`
152
-
153
- A durable nonterminal Agent execution already exists and another durable
154
- execution record cannot be established. Phronomy also uses the same public error
155
- for a competing process-local top-level request rejected by Runtime/EventLoop
156
- before the backend is called.
157
-
158
- ### `Phronomy::Persistence::SerializationError`
159
-
160
- The backend cannot encode a value into its supported durable representation.
161
- This is intended primarily for durable backends whose Workflow state domain is
162
- narrower than the InMemory backend's Ruby-object domain.
163
-
164
- ### `Phronomy::Persistence::UnsupportedBackendError`
165
-
166
- The backend does not provide a required structural or capability contract.
167
-
168
- Database availability, connection loss, and other transport/storage failures
169
- must not be misreported as ordinary optimistic conflicts merely to fit this
170
- error taxonomy.
46
+ The backend declares `spi_version: 2` and true values for `atomic_resources`,
47
+ `record_cas`, `stream_cas`, `conditional_unique`, `guarded_checks` and
48
+ `nested_savepoints`. Persistence validates backend capabilities; composition
49
+ validates the required domain resource declarations before binding adapters.
50
+ Unsupported backends raise `Persistence::UnsupportedBackendError`. There is no
51
+ eight-slot compatibility adapter or duplicated facade capability API.
52
+
53
+ A root handle routes to the current transaction on the same backend/thread.
54
+ A bound view and all its handles expire on commit or rollback and reject use from
55
+ another thread. Explicit nested transactions use savepoints on the same SQL
56
+ connection, or nested InMemory snapshots. Inner success depends on outer commit;
57
+ an inner exception is re-raised after rollback and can be caught by the outer
58
+ scope. `ActiveRecord::Rollback` also propagates.
59
+
60
+ A failure during physical work marks the scope failed. Catching it inside that
61
+ same scope does not permit further operations or successful commit. Establish an
62
+ explicit inner transaction before a recoverable operation and catch outside it.
63
+ Input validation occurs before physical work. A successful optional read returning
64
+ nil is not a failed physical operation; domain required-load errors may be raised
65
+ after that read. Domain decode/returned-metadata failures stay inside the atomic
66
+ boundary so a corrupt response after a write causes rollback.
67
+
68
+ Transaction blocks must finish normally. `return`, `break` and `throw` escaping
69
+ them cause rollback and `TransactionError`. These exits are not commit controls.
70
+ Commit/rollback transport failures remain database failures; this contract does
71
+ not promise exactly-once external effects or infer commit certainty.
72
+
73
+ ## Resource declarations
74
+
75
+ A `Resource` is an immutable value with `id`, `kind`, `attributes`,
76
+ `immutable_attributes`, `indexes`, `unique` and optional `guard`. Attributes use
77
+ `:string`, `:integer`, `:boolean` and their explicit `:nullable_*` forms. Keys and
78
+ text attributes use valid UTF-8 without NUL; keys are nonempty. Binary data belongs
79
+ to Blobs. No Proc, SQL expression or payload predicate is accepted.
80
+
81
+ Named equality indexes specify exact fields. Records may declare conditional
82
+ unique constraints with a symbol name, fields and equality `where` values. Null
83
+ unique-key fields are distinct, matching the default SQL unique-index semantics.
84
+ Streams have no indexed attributes; Blobs have attributes but no indexes/guards.
85
+ A record guard uses its key or an immutable non-null string attribute. A stream
86
+ guard uses its stream ID. Its anchor must be a registered Records resource.
87
+ Required anchors must exist; missing parents raise `NotFoundError` in all drivers.
88
+
89
+ ## Records
90
+
91
+ | Operation | Contract |
92
+ |---|---|
93
+ | `insert(key:, revision:, attributes:, record:)` | Insert only when absent; return an independent immutable `Entry::Record`. Primary duplicates and named unique failures differ. |
94
+ | `read(key)` / `fetch(key)` | Optional nil / required `NotFoundError`. |
95
+ | `replace(key:, expected_revision:, next_revision:, attributes:, record:, expected_attributes: {})` | Check existence, revision, expected attributes, immutable fields and uniqueness atomically; next revision must equal expected + 1. Replace all attributes. |
96
+ | `delete(key:, expected_revision: Records::UNCHECKED)` | Unchecked deletion is idempotent. A supplied revision requires existence and equality. Return nil. |
97
+ | `scan(index:, equals:, after: nil, limit: nil)` | Exact named-index fields, UTF-8 byte order, exclusive key cursor, positive limit or nil for all. |
98
+ | `delete_matching(index:, equals:)` | Delete equality matches atomically; a parent-guarded resource must constrain the guard attribute. Return nil. |
99
+
100
+ An Entry carries key, revision, attributes and an opaque `DurableRecord`.
101
+ The driver does not reconstruct metadata from payload. Domain wrappers verify
102
+ that returned identity, revision and attributes agree with decoded domain data.
103
+ Workflow/Handoff choose initial revision 1 and route nil expected revision to
104
+ insert; nil is never an unchecked update. Team terminal-to-active rejection is a
105
+ domain `expected_attributes: {active: true}` precondition when saving active state.
106
+
107
+ ## Streams and Blobs
108
+
109
+ Streams expose `append(stream:, expected_head:, entries:)`,
110
+ `read(stream:, after: 0, limit: nil)`, `head(stream:)`, and `delete(stream:)`.
111
+ Append takes `Entry::Append(id:, record:)`, validates the entire batch before any
112
+ write, enforces unique entry IDs within a stream, assigns contiguous positions
113
+ and updates the head atomically. Empty append still checks the expected head.
114
+ Reads return immutable `Entry::Stream(position:, id:, record:)` values in position
115
+ order. Delete removes head and entries together. The domain Journal wrapper
116
+ preserves its existing `limit: 0` empty-result behavior without a raw zero-limit
117
+ operation.
118
+
119
+ Blobs expose `put_if_absent(key:, bytes:, attributes:)`, `fetch(key)` and
120
+ `exist?(key)`. Same bytes retain the first attributes; different bytes for an
121
+ existing key raise `BlobConflictError`. Entry bytes are independent immutable
122
+ binary strings. Blob keys are arbitrary storage keys; ContentStore adds the
123
+ `sha256:<digest>` contract and maps integrity failures to its own `IntegrityError`.
124
+
125
+ ## Guards, conditions and errors
126
+
127
+ `GuardRef(resource:, key:)` names a stable existing parent record. `View#check!`
128
+ acquires guards in resource/key byte order before evaluating the closed condition
129
+ set: `RevisionIs`, `StreamHeadIs`, `NoRows`. A condition must include the guard
130
+ required by its resource and scope. PostgreSQL locks parents before child heads
131
+ or records; SQLite relies on the transaction/CAS/unique constraints and must not
132
+ claim a SELECT alone reserves a writer. Transactions spanning multiple owners
133
+ must establish a consistent owner lock order; database deadlocks remain database
134
+ errors, never optimistic conflicts.
135
+
136
+ `UniqueConstraintError < ConflictError` carries the resource ID and constraint
137
+ name. Agent/Team translate only their exact `one_active_owner` constraint to
138
+ `AgentBusyError`. Their idle checks translate their own `NoRows` condition failure.
139
+ `ConditionFailedError` carries the failed condition. Duplicate identities,
140
+ stale revisions and ordinary constraint conflicts remain `ConflictError`.
141
+ `NotFoundError`, `SerializationError`, `UnsupportedBackendError` retain their
142
+ meanings; `TransactionError` identifies invalid scope use. The removed
143
+ `ActiveExecutionConflictError` and `Storage::Repositories` have no aliases.
144
+
145
+ The following sections describe the **domain-owned record protocols**, used by
146
+ domain implementations and backend conformance tests. They are not a shared
147
+ application facade. Unqualified
148
+ ConflictError, NotFoundError, SerializationError and UnsupportedBackendError in
149
+ these domain sections refer to `Phronomy::Persistence` errors. Repository exits
150
+ translate the four raw Storage categories, preserving `cause`, message and trace.
151
+ Backend implementation code continues to raise `Phronomy::Storage` errors as
152
+ specified above. Unknown/transport errors and Storage::TransactionError are not
153
+ translated. See [ADR-061](decisions/061-persistence-failure-contracts.md) and the
154
+ [application migration](migrations/persistence-failure-contracts.md).
171
155
 
172
156
  ## Contents repository
173
157
 
174
- The content repository should normally inherit from
175
- `Phronomy::ContentStore::Base`, which supplies text/JSON helpers and the canonical
176
- content-ID calculation.
158
+ `ContentStore::StoredContents < ContentStore::Base` supplies text/JSON helpers
159
+ and canonical content-ID calculation over neutral Blobs. The private
160
+ Persistence::ContentRepository adapts its primitive methods to the upper failure
161
+ contract when accessed through `persistence.contents`. Direct StoredContents
162
+ callers retain the raw Storage error surface.
177
163
 
178
164
  Required primitive surface:
179
165
 
@@ -188,7 +174,7 @@ Required semantics:
188
174
  - content is immutable and content-addressed;
189
175
  - writing identical bytes is idempotent and returns the same content ID;
190
176
  - `fetch` returns a binary `String` isolated from caller mutation;
191
- - a missing content ID raises `Persistence::NotFoundError`;
177
+ - a missing content ID through `persistence.contents` raises `Persistence::NotFoundError`;
192
178
  - one content ID must never resolve to different bytes; a digest-integrity
193
179
  violation raises `ContentStore::IntegrityError`.
194
180
 
@@ -374,10 +360,9 @@ honestly rather than converting them into `ConflictError`.
374
360
 
375
361
  ### Workflow value serialization
376
362
 
377
- `WorkflowContext#to_h` may contain ordinary Ruby application values. The
378
- InMemory backend can preserve a broader set of Ruby values than a JSON database.
379
- A durable backend is not required to serialize arbitrary Ruby objects such as
380
- `Proc`, IO objects, sockets, or runtime callbacks.
363
+ The Workflow domain codec accepts canonical JSON-compatible snapshot values in
364
+ all drivers, including InMemory. Proc, IO, sockets and runtime callbacks are not
365
+ durable snapshot values.
381
366
 
382
367
  A JSON/JSONB backend should document its supported value domain. A recommended
383
368
  domain is:
@@ -391,8 +376,9 @@ Array of supported values
391
376
  Hash with String/Symbol keys and supported values
392
377
  ```
393
378
 
394
- If a value cannot be represented, raise `Persistence::SerializationError` rather
395
- than silently converting it into a lossy form. JSON backends may return String
379
+ If a value cannot be represented, the backend/codec raises
380
+ `Storage::SerializationError`; the domain repository exposes
381
+ `Persistence::SerializationError`. Do not silently convert it into a lossy form. JSON backends may return String
396
382
  keys after decoding; `WorkflowRunner` deliberately accepts String and Symbol keys
397
383
  and normalizes them when comparing durable snapshots.
398
384
 
@@ -401,8 +387,8 @@ particular database backend accept arbitrary Workflow values.
401
387
 
402
388
  ## Durable Agent watermark
403
389
 
404
- `assert_agent_watermark!` is a public **Backend SPI** operation. It is not an
405
- ordinary application API.
390
+ `assert_agent_watermark!` is a domain Persistence operation. Agent-owned
391
+ `Watermark` composes a parent guard, root revision and Journal head conditions.
406
392
 
407
393
  Phronomy uses it at durable barriers because a hydrated live Agent owns the
408
394
  current logical state and Phronomy deliberately does not reload mutable Agent
@@ -421,16 +407,17 @@ differs, raise `ConflictError`. On success return `true`.
421
407
  The operation must not return a replacement AgentRoot or Journal. A mismatch is a
422
408
  conflict, not a request to reload/merge mutable state.
423
409
 
424
- When used inside `Persistence#transaction`, a SQL backend should perform the
410
+ When used inside `Agent::Store#transaction`, a SQL backend should perform the
425
411
  watermark check in the same database transaction as the subsequent durable
426
412
  write.
427
413
 
428
414
  ## Transaction contract
429
415
 
430
- A transaction block may combine operations across all repositories:
416
+ A domain transaction combines its own record operations. Multiple domains join
417
+ one coordinator scope explicitly; each receives only its own record protocol:
431
418
 
432
419
  ```ruby
433
- persistence.transaction do |tx|
420
+ stores.agent.transaction do |tx|
434
421
  tx.assert_agent_watermark!(...)
435
422
  content_ref = tx.contents.put_text("...")
436
423
  tx.journals.append(...)
@@ -482,9 +469,9 @@ This is a targeted migration rule for the removed generic identity field. It doe
482
469
  not establish a general unknown-field or long-term codec/schema-versioning
483
470
  policy.
484
471
 
485
- The canonical Hash representation is the Phronomy/domain boundary. A backend is
486
- free to map that representation to normalized SQL columns, JSON, or another
487
- storage format internally.
472
+ Domain codecs own canonical Hash representation. The raw driver stores the
473
+ DurableRecord envelope and separately supplied metadata without interpreting
474
+ domain payload fields.
488
475
 
489
476
  ## Conformance tests
490
477
 
@@ -508,6 +495,11 @@ an Agent repository
508
495
  a Journal repository
509
496
  an Execution repository
510
497
  a workflow state repository
498
+ a Handoff state repository
499
+ a Team repository
500
+ a Team execution repository
501
+ neutral storage primitives
502
+ storage transaction boundaries
511
503
  a Persistence backend
512
504
  ```
513
505
 
@@ -518,7 +510,7 @@ require "phronomy"
518
510
  require "phronomy/testing/persistence_contract"
519
511
 
520
512
  RSpec.describe MyPersistenceBackend do
521
- let(:persistence) { described_class.new(...) }
513
+ let(:stores) { Phronomy::PersistenceComposition.build(backend: described_class.new(...)) }
522
514
 
523
515
  it_behaves_like "a persistence content store"
524
516
  it_behaves_like "an Agent repository"
@@ -532,7 +524,7 @@ RSpec.describe MyPersistenceBackend do
532
524
  end
533
525
  ```
534
526
 
535
- `Persistence::InMemory` is run through the same shipped contract source in
527
+ `PersistenceComposition.in_memory` is run through the same shipped contract source in
536
528
  Phronomy CI. The files under `spec/support/shared_examples/` are compatibility
537
529
  require wrappers only; the authoritative shared-example implementations live
538
530
  under `lib/phronomy/testing/persistence_contract/` so the core suite and external
@@ -560,34 +552,17 @@ may use combinations of:
560
552
  Backend-specific database exceptions should be translated to the Phronomy error
561
553
  contract where their meaning is known.
562
554
 
563
- ## V2 coordination Backend SPI (clean break)
555
+ ## Coordination domain repositories
564
556
 
565
- Both `Persistence.new` and `build_transaction_view` require all eight raw
566
- repositories. Five-repository fallback is removed. Existing record formats remain
567
- `0.1`; three additional record types are `phronomy.handoff_state`,
568
- `phronomy.team_root`, and `phronomy.team_execution`. Backend index metadata is
569
- explicit and MUST NOT be reconstructed by parsing DurableRecord payloads.
557
+ Handoff starts at revision 1 with expected revision nil; later saves advance one
558
+ revision. Team roots and executions begin at revision 0. Team admission retains
559
+ `AgentBusyError`; stale CAS and duplicate identities use `ConflictError`.
560
+ Missing Team/execution loads raise `NotFoundError`; absent Handoff state returns
561
+ nil. The unchanged record types are `phronomy.handoff_state`, `phronomy.team_root`
562
+ and `phronomy.team_execution`, all version `0.1`.
570
563
 
571
- | Raw repository | Required operations and explicit metadata |
572
- |---|---|
573
- | `handoff_states` | `load(main_agent_id)`; `save(main_agent_id, expected_revision:, next_revision:, active_agent_id:, record:)`; `delete(main_agent_id, expected_revision:)` |
574
- | `teams` | `create(team_id:, team_revision:, record:)`; `load(team_id)`; `save(team_id, expected_revision:, next_revision:, record:)`; `delete(team_id)` |
575
- | `team_executions` | `create_active(team_execution_id:, team_id:, execution_revision:, record:)`; `load(id)`; `save(id, expected_revision:, next_revision:, team_id:, active:, record:)`; `list_active(team_id)`; `list(team_id, after: nil, limit: 100)`; `delete(id)`; `delete_for_team(team_id)`; `assert_idle!(team_id)` |
576
- | `executions` extension | `list(agent_id, after: nil, limit: 100)` for retained active and terminal records |
577
-
578
- Handoff starts at revision 1 with expected revision nil; later saves advance
579
- exactly one revision. Team roots and executions begin at revision 0. Team
580
- admission has the existing `AgentBusyError` contract for an already active owner;
581
- stale CAS/duplicate identity uses `ConflictError`. Missing Team/execution loads
582
- raise `NotFoundError`; absent Handoff state returns nil. Unavailable reads and
583
- codec errors must propagate separately.
584
-
585
- Lists sort IDs lexically, use an exclusive ID cursor, return at most the positive
586
- integer limit, include only the requested owner, and return immutable copies.
587
- Backends define retention; enumeration does not implement input correlation or
588
- request deduplication. Terminal-to-active rewrites are invalid.
589
-
590
- For F1, atomic commit does not imply a known response. Phronomy reads back the
591
- same intended ID/fact before advancing. A backend must provide authoritative
592
- reads/CAS; failure of readback is returned without new semantic work. No callback
593
- ACK/index is part of the SPI. Existing cross-process exclusion limitations remain.
564
+ Backend authors should also read the [SPI 2 migration guide](migrations/neutral-storage-spi.md).
565
+ The Stable/Beta product API snapshot and the explicit Storage SPI 2 signature
566
+ snapshot are separate gates. Live PostgreSQL locking and failure tests must run
567
+ against the candidate core and examples revisions; earlier SPI results do not
568
+ satisfy this gate.