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
@@ -11,12 +11,27 @@ module Phronomy
11
11
  # Scheduling and aggregation must be pure, replay-safe result calculations.
12
12
  # @api public
13
13
  class TeamCoordinator
14
- WorkerState = Data.define(:index, :agent_id, :transcript_size, :status) do
14
+ WorkerState = Data.define(:index, :agent_id, :assignment_count, :status) do
15
15
  def available? = status == :available
16
16
  end
17
17
  private_constant :WorkerState
18
18
 
19
+ # Raised before writing an operation; update still verifies readback.
20
+ OperationRejected = Class.new(Phronomy::ConfigurationError)
21
+ private_constant :OperationRejected
22
+
19
23
  class << self
24
+ # Boot composition supplies a fresh store without selecting it here.
25
+ # @api private
26
+ def install_persistence_factory(factory)
27
+ raise ArgumentError, "factory must respond to call" unless factory.respond_to?(:call)
28
+ raise Phronomy::ConfigurationError, "Team persistence factory already installed" if @persistence_factory
29
+ @persistence_factory = factory
30
+ end
31
+
32
+ # @api private
33
+ def build_default_persistence = @persistence_factory.call
34
+
20
35
  # @api public
21
36
  def team_definition(id: nil, version: nil)
22
37
  if id || version
@@ -68,7 +83,9 @@ module Phronomy
68
83
  end
69
84
 
70
85
  # @api public
71
- def get(team_id) = Phronomy::Runtime.instance.__get_team(team_id, klass: self)
86
+ def get(team_id)
87
+ ExecutionEnvironment.current.existing_ownership&.get(team_id.to_s, klass: self)
88
+ end
72
89
 
73
90
  # @api private
74
91
  def _coordinator_model = @coordinator_model
@@ -90,14 +107,14 @@ module Phronomy
90
107
  private
91
108
 
92
109
  def construct(id, persistence, metadata, listener, create:)
93
- raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::Runtime.in_event_loop_context?
110
+ raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::WaitPolicy.blocking_forbidden?
94
111
  key = id.to_s
95
112
  raise ArgumentError, "team_id must not be empty" if key.empty?
96
- store = persistence || Phronomy.configuration.persistence || Phronomy::Persistence::InMemory.new
97
- runtime = Phronomy::Runtime.instance
98
- runtime.__team_owner(key, klass: self, create: create, persistence: store) do
113
+ store = persistence || Phronomy::MultiAgent::Store.configured || TeamCoordinator.build_default_persistence
114
+ environment = ExecutionEnvironment.current
115
+ environment.ownership.fetch(key, klass: self, create: create, persistence: store) do
99
116
  instance = allocate
100
- instance.send(:initialize, key.freeze, store, metadata, listener, runtime, create: create)
117
+ instance.send(:initialize, key.freeze, store, metadata, listener, environment, create: create)
101
118
  instance
102
119
  end
103
120
  end
@@ -132,12 +149,12 @@ module Phronomy
132
149
  # @api public
133
150
  def result(team_execution_id)
134
151
  execution = read_execution(team_execution_id)
135
- persistence.team_execution_result(execution.team_execution_id)
152
+ persistence.result(execution.team_execution_id)
136
153
  end
137
154
 
138
155
  # @api public
139
156
  def executions(after: nil, limit: 100)
140
- persistence.list_team_executions(team_id, after: after, limit: limit)
157
+ persistence.runs(team_id, after: after, limit: limit)
141
158
  end
142
159
 
143
160
  # Requests cancellation of this run only. Existing Agent cancellation and
@@ -157,8 +174,9 @@ module Phronomy
157
174
 
158
175
  private
159
176
 
160
- def initialize(id, store, metadata, listener, runtime, create:)
161
- @team_id, @persistence, @listener, @runtime = id, store, listener, runtime
177
+ def initialize(id, store, metadata, listener, environment, create:)
178
+ @team_id, @persistence, @listener, @environment = id, store, listener, environment
179
+ @admissions = @environment.admissions
162
180
  @tokens_mutex, @tokens = Mutex.new, {}
163
181
  @coordinator_classes = {}
164
182
  definition = self.class.team_definition
@@ -181,25 +199,25 @@ module Phronomy
181
199
  end
182
200
 
183
201
  def assert_caller!
184
- raise Phronomy::EventLoopReentrancyError, "Team operation cannot block EventLoop" if Phronomy::Runtime.in_event_loop_context?
185
- unless @runtime.equal?(Phronomy::Runtime.instance)
202
+ raise Phronomy::EventLoopReentrancyError, "Team operation cannot block EventLoop" if Phronomy::WaitPolicy.blocking_forbidden?
203
+ unless @environment.current?
186
204
  raise Phronomy::RuntimeShutdownError, "Team #{team_id} belongs to a previous Runtime"
187
205
  end
188
206
  end
189
207
 
190
208
  def with_admission
191
209
  assert_caller!
192
- @runtime.__admit_multi_agent(self)
210
+ @admissions.admit!(self)
193
211
  admitted = true
194
212
  yield
195
213
  ensure
196
- @runtime.__release_multi_agent(self) if admitted
214
+ @admissions.release!(self) if admitted
197
215
  end
198
216
 
199
217
  def read_execution(id)
200
218
  execution = persistence.team_executions.load(id)
201
219
  unless execution.team_id == team_id
202
- raise Phronomy::Persistence::ConflictError, "Team execution #{id} belongs to another Team"
220
+ raise Phronomy::Persistence::StateConflictError, "Team execution #{id} belongs to another Team"
203
221
  end
204
222
  execution
205
223
  end
@@ -218,13 +236,13 @@ module Phronomy
218
236
  now = Time.now.utc.iso8601(6)
219
237
  intended = nil
220
238
  begin
221
- persistence.transaction do |tx|
239
+ persistence.transaction do |tx, scope|
222
240
  root = tx.teams.load(team_id)
223
241
  raise Phronomy::AgentBusyError, "Team #{team_id} has an active run; use resume" unless tx.team_executions.list_active(team_id).empty?
224
242
  intended = TeamExecution.new(team_execution_id: id, team_id: team_id,
225
243
  execution_revision: 0, status: "active", phase: "coordinator", input_ref: tx.contents.put_text(input),
226
244
  coordinator: {"agent_id" => SecureRandom.uuid, "execution_id" => SecureRandom.uuid, "state" => "reserved"},
227
- workers: Array.new(self.class._pool_size) { |i| {"index" => i, "agent_id" => SecureRandom.uuid, "transcript_size" => 0} },
245
+ workers: Array.new(self.class._pool_size) { |i| {"index" => i, "agent_id" => SecureRandom.uuid} },
228
246
  tasks: [], assignments: [], result_ref: nil, error_ref: nil, created_at: now, updated_at: now,
229
247
  metadata: {"definition" => definition, "operations" => {}, "cancel_requested" => false,
230
248
  "durable_context_ref" => config.key?(:durable_context) ? tx.contents.put_json(config.fetch(:durable_context)) : nil})
@@ -249,10 +267,10 @@ module Phronomy
249
267
  attempts = 0
250
268
  begin
251
269
  intended = nil
252
- persistence.transaction do |tx|
270
+ persistence.transaction do |tx, scope|
253
271
  current = tx.team_executions.load(id)
254
- raise Phronomy::Persistence::ConflictError, "Team execution owner mismatch" unless current.team_id == team_id
255
- intended = yield(current, tx)
272
+ raise Phronomy::Persistence::StateConflictError, "Team execution owner mismatch" unless current.team_id == team_id
273
+ intended = yield(current, tx, scope)
256
274
  next if intended.equal?(current)
257
275
  tx.team_executions.save(id, expected_revision: current.execution_revision, execution: intended)
258
276
  if intended.terminal? && current.active?
@@ -279,9 +297,9 @@ module Phronomy
279
297
  end
280
298
  token = Phronomy::Concurrency::CancellationToken.new
281
299
  @tokens_mutex.synchronize { @tokens[id] = token }
282
- external = config[:cancellation_token]
283
- callback = proc { @runtime.offload.submit(on_full: :raise) { cancel(id) } }
284
- external&.on_cancel(&callback)
300
+ cancellation_subscriptions = Phronomy::Concurrency::ResultSubscriptions.new
301
+ callback = proc { @environment.submit(on_full: :raise) { cancel(id) } }
302
+ cancellation_subscriptions.explicit_cancellation(config[:cancellation_token], &callback)
285
303
  token.cancel! if current.metadata["cancel_requested"]
286
304
  begin
287
305
  loop do
@@ -320,13 +338,13 @@ module Phronomy
320
338
  values = assignment_values(current)
321
339
  aggregate = self.class._aggregator
322
340
  begin
323
- output = Phronomy::Agent::RecoverySupport.canonical_copy(aggregate ? aggregate.call(values) : values)
341
+ output = Phronomy::Values::Serializable.convert(aggregate ? aggregate.call(values) : values, unsupported_message: "Team result is not canonically serializable")
324
342
  Phronomy::CanonicalJSON.dump(output)
325
343
  rescue => error
326
- return terminal_value(finish_error(id, Phronomy::Agent::RecoverySupport.resolution_failure(error)))
344
+ return terminal_value(finish_error(id, {"class" => error.class.name.to_s, "message" => error.message.to_s}))
327
345
  end
328
346
  # Aggregate may be replayed only until the canonical outcome commits.
329
- completed = update(id) do |fresh, tx|
347
+ completed = update(id) do |fresh, tx, scope|
330
348
  next fresh if fresh.terminal? || fresh.metadata["cancel_requested"]
331
349
  fresh.with(status: "completed", phase: "completed", result_ref: tx.contents.put_json(output))
332
350
  end
@@ -342,7 +360,7 @@ module Phronomy
342
360
  terminal_value(finish_error(id, {"class" => "Phronomy::CancellationError", "message" => "Team run cancelled"}, status: "cancelled"))
343
361
  end
344
362
  ensure
345
- external&.send(:unregister_cancel_callback, callback)
363
+ cancellation_subscriptions.close
346
364
  @tokens_mutex.synchronize { @tokens.delete(id) }
347
365
  end
348
366
  end
@@ -380,30 +398,31 @@ module Phronomy
380
398
  id = slot.fetch("agent_id")
381
399
  child = klass.get(id)
382
400
  unless child
383
- begin
384
- persistence.agents.load(id)
385
- present = true
386
- rescue Phronomy::Persistence::NotFoundError
387
- present = false
388
- end
401
+ present = persistence.agent_store.exist?(id)
389
402
  token.raise_if_cancelled! unless present
390
- child = present ? klass.load(id, persistence: persistence, on_event: @listener) : klass.create(agent_id: id, persistence: persistence, on_event: @listener)
403
+ child = present ? klass.load(id, persistence: persistence.agent_store, on_event: @listener) : klass.create(agent_id: id, persistence: persistence.agent_store, on_event: @listener)
391
404
  end
392
- unless child.persistence.equal?(persistence)
405
+ unless child.persistence.equal?(persistence.agent_store)
393
406
  raise Phronomy::ConfigurationError, "Team child #{id} Persistence mismatch"
394
407
  end
395
408
  child_config = config.merge(cancellation_token: token,
396
- phronomy_coordination: {"kind" => "team", "team_id" => team_id,
397
- "team_execution_id" => current.team_execution_id, "slot" => purpose})
409
+ phronomy_reservation: {"kind" => "team", "team_id" => team_id,
410
+ "team_execution_id" => current.team_execution_id, "slot" => purpose})
411
+ child_config = child_config.merge(phronomy_admission: ReservedChildAdmission.new(
412
+ persistence: persistence, owner: child_config.fetch(:phronomy_reservation)
413
+ ))
398
414
  if (context_ref = current.metadata["durable_context_ref"])
399
415
  child_config = child_config.merge(durable_context: persistence.contents.fetch_json(context_ref))
400
416
  end
401
- Phronomy::Agent::ExactExecution.start(agent: child, execution_id: slot.fetch("execution_id"), input: input, config: child_config).wait_result
417
+ child.start_reserved_async(reservation: Phronomy::Agent::ReservedExecution.new(agent_id: id, execution_id: slot.fetch("execution_id"), correlation: child_config.fetch(:phronomy_reservation)), input: input, config: child_config).wait_result
402
418
  end
403
419
 
404
420
  def reserve_assignment(current, task)
405
- available = current.workers.map { |w| WorkerState.new(index: w.fetch("index"), agent_id: w.fetch("agent_id"), transcript_size: w.fetch("transcript_size"), status: :available) }.freeze
406
- worker = self.class._scheduler ? self.class._scheduler.call(available) : available.min_by(&:transcript_size)
421
+ # Team owns this count. Deriving it from retained assignments also
422
+ # makes replay idempotent and avoids consulting the child's journal.
423
+ counts = current.assignments.each_with_object(Hash.new(0)) { |entry, totals| totals[entry.fetch("worker")] += 1 }
424
+ available = current.workers.map { |w| WorkerState.new(index: w.fetch("index"), agent_id: w.fetch("agent_id"), assignment_count: counts[w.fetch("index")], status: :available) }.freeze
425
+ worker = self.class._scheduler ? self.class._scheduler.call(available) : available.min_by(&:assignment_count)
407
426
  raise Phronomy::ConfigurationError, "Scheduler must select an available worker slot" unless available.include?(worker)
408
427
  reserved = {"task_id" => task.fetch("id"), "worker" => worker.index,
409
428
  "execution_id" => SecureRandom.uuid, "state" => "reserved", "result_ref" => nil, "error_ref" => nil}
@@ -414,20 +433,15 @@ module Phronomy
414
433
  end
415
434
 
416
435
  def record_assignment(id, assigned, outcome)
417
- update(id) do |fresh, tx|
436
+ update(id) do |fresh, _tx|
418
437
  entries = fresh.assignments.map do |entry|
419
438
  next entry unless entry.fetch("task_id") == assigned.fetch("task_id")
420
439
  next entry unless entry.fetch("state") == "reserved"
421
- raise Phronomy::Persistence::ConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
440
+ raise Phronomy::Persistence::StateConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
422
441
  entry.merge("state" => outcome.fetch(:status).to_s,
423
442
  "result_ref" => outcome[:result_ref], "error_ref" => outcome[:error_ref])
424
443
  end
425
- workers = fresh.workers.map do |worker|
426
- next worker unless worker.fetch("index") == assigned.fetch("worker")
427
- root = tx.agents.load(worker.fetch("agent_id"))
428
- worker.merge("transcript_size" => root.journal_position)
429
- end
430
- fresh.with(assignments: entries, workers: workers)
444
+ fresh.with(assignments: entries)
431
445
  end
432
446
  end
433
447
 
@@ -441,7 +455,7 @@ module Phronomy
441
455
  end
442
456
 
443
457
  def finish_error(id, error, status: "failed")
444
- update(id) do |fresh, tx|
458
+ update(id) do |fresh, tx, scope|
445
459
  next fresh if fresh.terminal?
446
460
  fresh.with(status: status, phase: status, error_ref: tx.contents.put_json(error))
447
461
  end
@@ -449,7 +463,10 @@ module Phronomy
449
463
 
450
464
  def terminal_value(execution)
451
465
  raise Phronomy::CancellationError, "Team run #{execution.team_execution_id} cancelled" if execution.status == "cancelled"
452
- raise Phronomy::Agent::RecoverySupport.error_from_failure(persistence.contents.fetch_json(execution.error_ref)) if execution.error_ref
466
+ if execution.error_ref
467
+ failure = persistence.contents.fetch_json(execution.error_ref)
468
+ raise Phronomy::Error, "#{failure.fetch("class")}: #{failure.fetch("message")}"
469
+ end
453
470
  persistence.contents.fetch_json(execution.result_ref)
454
471
  end
455
472
 
@@ -474,7 +491,7 @@ module Phronomy
474
491
 
475
492
  def build_operation_tool(run_id, operation)
476
493
  team = self
477
- Class.new(Phronomy::Agent::Context::Capability::Base) do
494
+ Class.new(Phronomy::Tool::Base) do
478
495
  def self.__framework_owned_operation? = true
479
496
  tool_name operation.to_s
480
497
  description((operation == :enqueue_task) ? "Add a task to the worker queue." : "Finish task generation.")
@@ -483,10 +500,10 @@ module Phronomy
483
500
  param :description, type: :string, desc: "Worker task"
484
501
  param :metadata, type: :string, desc: "Optional metadata", required: false
485
502
  else
486
- param :summary, type: :string, desc: "TaskResult summary", required: false
503
+ param :summary, type: :string, desc: "Task generation summary", required: false
487
504
  end
488
505
  define_method(:call_async) do |args, cancellation_token: nil, config: {}|
489
- validated, schema_error = send(:validate_and_coerce, args)
506
+ validated, schema_error = validate_arguments(args)
490
507
  raise Phronomy::ToolError, schema_error if schema_error
491
508
  execute_async(**validated, cancellation_token: cancellation_token, config: config)
492
509
  rescue => error
@@ -494,8 +511,10 @@ module Phronomy
494
511
  end
495
512
  define_method(:execute_async) do |config: {}, cancellation_token: nil, **arguments|
496
513
  key = config.fetch(:phronomy_tool_invocation_id)
497
- Phronomy::Runtime.instance.offload.submit(on_full: :raise) do
514
+ Phronomy::Execution.submit(on_full: :raise) do
498
515
  team.send(:apply_operation, run_id, key, operation, arguments)
516
+ rescue OperationRejected => error
517
+ raise Phronomy::ConfigurationError, error.message
499
518
  rescue Phronomy::CancellationError
500
519
  raise
501
520
  rescue => error
@@ -507,42 +526,35 @@ module Phronomy
507
526
  end
508
527
 
509
528
  def apply_operation(run_id, key, operation, arguments)
510
- argument_values = Phronomy::Agent::RecoverySupport.canonical_copy(arguments)
511
- current = update(run_id) do |fresh, tx|
529
+ argument_values = Phronomy::Values::Serializable.convert(arguments, unsupported_message: "Team arguments are not canonically serializable")
530
+ current = update(run_id) do |fresh, tx, scope|
512
531
  operations = fresh.metadata.fetch("operations")
513
532
  if (prior = operations[key])
514
533
  unless prior.fetch("operation") == operation.to_s && prior.fetch("arguments") == argument_values
515
- raise Phronomy::Persistence::ConflictError, "Team operation #{key} identity mismatch"
534
+ raise Phronomy::Persistence::StateConflictError, "Team operation #{key} identity mismatch"
516
535
  end
517
536
  next fresh
518
537
  end
519
538
  raise Phronomy::CancellationError, "Team run cancelled" if fresh.metadata["cancel_requested"]
520
- raise Phronomy::Persistence::ConflictError, "Team task generation is closed" unless fresh.active? && fresh.phase == "coordinator"
521
- coordinator = tx.executions.load(fresh.coordinator.fetch("execution_id"))
522
- unless coordinator.agent_id == fresh.coordinator.fetch("agent_id")
523
- raise Phronomy::Persistence::ConflictError, "Team coordinator owner mismatch"
524
- end
525
- batch = Array(coordinator.metadata[Phronomy::Agent::RecoverySupport::TOOL_BATCH_METADATA_KEY])
526
- requested = batch.find { |entry| entry.fetch("tool_invocation_id") == key }
527
- unless requested && requested.fetch("status") == "authorized" && requested.fetch("tool_name") == operation.to_s && requested.fetch("arguments").compact == argument_values
528
- raise Phronomy::Persistence::ConflictError, "Team operation #{key} is not the authorized call"
529
- end
539
+ raise Phronomy::Persistence::StateConflictError, "Team task generation is closed" unless fresh.active? && fresh.phase == "coordinator"
540
+ batch = persistence.agent_store.authorized_operations(scope,
541
+ agent_id: fresh.coordinator.fetch("agent_id"), execution_id: fresh.coordinator.fetch("execution_id"),
542
+ invocation_id: key, name: operation.to_s, arguments: argument_values, names: %w[enqueue_task finalize])
530
543
  # All calls passed the Agent authorization barrier. Commit this finite
531
544
  # batch in Provider order, so finalize cannot overtake queued tasks.
532
545
  tasks = fresh.tasks.dup
533
546
  operations = operations.dup
534
547
  metadata = fresh.metadata.dup
535
548
  batch.each do |entry|
536
- entry_id = entry.fetch("tool_invocation_id")
537
- next if operations.key?(entry_id) || entry.fetch("status") != "authorized"
538
- name = entry.fetch("tool_name")
539
- next unless %w[enqueue_task finalize].include?(name)
540
- values = entry.fetch("arguments").compact
549
+ entry_id = entry.invocation_id
550
+ next if operations.key?(entry_id)
551
+ name = entry.name
552
+ values = entry.arguments
541
553
  if name == "enqueue_task"
542
- raise Phronomy::ConfigurationError, "Cannot enqueue after finalize" if metadata["finalized"]
554
+ raise OperationRejected, "Cannot enqueue after finalize" if metadata["finalized"]
543
555
  task = {"id" => Digest::SHA256.hexdigest(entry_id)[0, 32], "description" => values.fetch("description"), "metadata" => values["metadata"]}
544
556
  tasks << task
545
- output = "TaskResult ##{tasks.length} enqueued: #{task.fetch("description")}"
557
+ output = "Task ##{tasks.length} enqueued: #{task.fetch("description")}"
546
558
  else
547
559
  output = "Finalized. #{tasks.size} task(s) enqueued. #{values["summary"]}".strip
548
560
  metadata["finalized"] = true
@@ -14,7 +14,7 @@ module Phronomy
14
14
  source = values.transform_keys(&:to_s)
15
15
  raise ArgumentError, "TeamExecution schema mismatch" unless source.keys.sort == ATTRIBUTES.sort
16
16
  canonical = Phronomy::CanonicalJSON.load(Phronomy::CanonicalJSON.dump(source))
17
- ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::Agent::Immutable.copy(canonical.fetch(key))) }
17
+ ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::Values::Immutable.copy(canonical.fetch(key))) }
18
18
  raise ArgumentError, "missing team_execution_id" if team_execution_id.to_s.empty?
19
19
  raise ArgumentError, "invalid execution_revision" unless execution_revision.is_a?(Integer) && execution_revision >= 0
20
20
  raise ArgumentError, "invalid metadata" unless metadata.is_a?(Hash)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- class Runtime
5
- # Runtime-local Team identity and construction exclusion; no execution state.
4
+ module MultiAgent
5
+ # Environment-local Team identity and construction exclusion; no execution state.
6
6
  # @api private
7
7
  class TeamOwnershipRegistry
8
8
  def initialize
@@ -18,7 +18,7 @@ module Phronomy
18
18
  loop do
19
19
  raise Phronomy::RuntimeShutdownError, "Runtime is draining" if @draining
20
20
  if (owner = @owners[id])
21
- raise Phronomy::Persistence::ConflictError, "Team #{id} already exists" if create
21
+ raise Phronomy::Persistence::StateConflictError, "Team #{id} already exists" if create
22
22
  unless owner.is_a?(klass) && owner.persistence.equal?(persistence)
23
23
  raise Phronomy::ConfigurationError, "Team #{id} owner or Persistence mismatch"
24
24
  end
@@ -58,7 +58,7 @@ module Phronomy
58
58
  end
59
59
  end
60
60
 
61
- def wait_until_stable(deadline)
61
+ def wait_until_idle(deadline)
62
62
  @mutex.synchronize do
63
63
  until @constructing.empty?
64
64
  remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -69,7 +69,7 @@ module Phronomy
69
69
  end
70
70
  end
71
71
 
72
- def shutdown!
72
+ def after_runtime_shutdown
73
73
  @mutex.synchronize { @owners.clear }
74
74
  end
75
75
  end
@@ -14,7 +14,7 @@ module Phronomy
14
14
  source = values.transform_keys(&:to_s)
15
15
  raise ArgumentError, "TeamRoot schema mismatch" unless source.keys.sort == ATTRIBUTES.sort
16
16
  canonical = Phronomy::CanonicalJSON.load(Phronomy::CanonicalJSON.dump(source))
17
- ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::Agent::Immutable.copy(canonical.fetch(key))) }
17
+ ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::Values::Immutable.copy(canonical.fetch(key))) }
18
18
  raise ArgumentError, "missing team_id" if team_id.to_s.empty?
19
19
  raise ArgumentError, "invalid team_revision" unless team_revision.is_a?(Integer) && team_revision >= 0
20
20
  raise ArgumentError, "invalid metadata" unless metadata.is_a?(Hash)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ class ParseError < Error; end
7
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class Persistence
5
+ # Domain-facing content access uses the Persistence failure vocabulary.
6
+ # ContentStore implementations retain their independent backend contract.
7
+ # @api private
8
+ class ContentRepository < Phronomy::ContentStore::Base
9
+ def initialize(store)
10
+ @store = store
11
+ end
12
+
13
+ def put(bytes, canonicalization_version:)
14
+ StorageBoundary.call { @store.put(bytes, canonicalization_version: canonicalization_version) }
15
+ end
16
+
17
+ def fetch(content_id)
18
+ StorageBoundary.call { @store.fetch(content_id) }
19
+ end
20
+
21
+ def exist?(content_id)
22
+ StorageBoundary.call { @store.exist?(content_id) }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ class Persistence
7
+ # Failures describe a category, not a universal claim about commit outcome.
8
+ class Error < Phronomy::Error; end
9
+ class ConflictError < Error; end
10
+ class StateConflictError < ConflictError; end
11
+ class NotFoundError < Error; end
12
+ class SerializationError < Error; end
13
+ class UnsupportedBackendError < Error; end
14
+ class TransactionError < Error; end
15
+ end
16
+ end
@@ -32,10 +32,10 @@ module Phronomy
32
32
  PRE_S3_AGENT_ROOT_KEYS,
33
33
  label: "pre-S3 AgentRoot"
34
34
  )
35
- DurableCodec.encode_agent_root(
35
+ Phronomy::Agent::Persistence::Codec.encode_agent_root(
36
36
  Phronomy::Agent::AgentRoot.from_h(source)
37
37
  )
38
- rescue Phronomy::Persistence::SerializationError
38
+ rescue Phronomy::Storage::SerializationError
39
39
  raise
40
40
  rescue => error
41
41
  migration_error("AgentRoot", error)
@@ -68,8 +68,8 @@ module Phronomy
68
68
  execution_id: source.fetch("execution_id")
69
69
  )
70
70
  execution = Phronomy::Agent::AgentExecution.from_h(source)
71
- DurableCodec.encode_agent_execution(execution)
72
- rescue Phronomy::Persistence::SerializationError
71
+ Phronomy::Agent::Persistence::Codec.encode_agent_execution(execution)
72
+ rescue Phronomy::Storage::SerializationError
73
73
  raise
74
74
  rescue => error
75
75
  migration_error("AgentExecution", error)
@@ -77,40 +77,40 @@ module Phronomy
77
77
 
78
78
  def journal_record(hash)
79
79
  source = normalize_legacy_journal_record(hash, label: "pre-S3 JournalRecord")
80
- DurableCodec.encode_journal_record(
80
+ Phronomy::Agent::Persistence::Codec.encode_journal_record(
81
81
  Phronomy::Agent::JournalRecord.from_h(source)
82
82
  )
83
- rescue Phronomy::Persistence::SerializationError
83
+ rescue Phronomy::Storage::SerializationError
84
84
  raise
85
85
  rescue => error
86
86
  migration_error("JournalRecord", error)
87
87
  end
88
88
 
89
89
  def workflow_state(workflow_instance_id:, revision:, snapshot:)
90
- DurableCodec.encode_workflow_state(
90
+ Phronomy::Workflow::Persistence::Codec.encode_workflow_state(
91
91
  workflow_instance_id: workflow_instance_id,
92
92
  workflow_revision: Integer(revision),
93
93
  snapshot: snapshot
94
94
  )
95
- rescue Phronomy::Persistence::SerializationError
95
+ rescue Phronomy::Storage::SerializationError
96
96
  raise
97
97
  rescue => error
98
98
  migration_error("Workflow state", error)
99
99
  end
100
100
 
101
101
  # LLMInputManifest is a ContentStore codec boundary, not a
102
- # Persistence::DurableRecord. Its pre-CG-07 integer version 1 is migrated
102
+ # Storage::DurableRecord. Its pre-CG-07 integer version 1 is migrated
103
103
  # explicitly to the pre-1.0 string version "0.1".
104
104
  def llm_input_manifest(hash)
105
105
  source = stringify_keys(hash)
106
106
  old_version = source.fetch("version")
107
107
  unless old_version == 1 || old_version == "1"
108
- raise Phronomy::Persistence::SerializationError,
108
+ raise Phronomy::Storage::SerializationError,
109
109
  "unsupported pre-S3 LLMInputManifest version: #{old_version.inspect}"
110
110
  end
111
- source["version"] = Phronomy::Agent::LLMInputManifest::VERSION
112
- Phronomy::Agent::LLMInputManifest.from_h(source).to_h
113
- rescue Phronomy::Persistence::SerializationError
111
+ source["version"] = Phronomy::Context::LLMInputManifest::VERSION
112
+ Phronomy::Context::LLMInputManifest.from_h(source).to_h
113
+ rescue Phronomy::Storage::SerializationError
114
114
  raise
115
115
  rescue => error
116
116
  migration_error("LLMInputManifest", error)
@@ -141,13 +141,13 @@ module Phronomy
141
141
  )
142
142
  parents = PRE_S3_APPROVAL_PARENT_KEYS.select { |key| source.key?(key) }
143
143
  unless parents.length == 1
144
- raise Phronomy::Persistence::SerializationError,
144
+ raise Phronomy::Storage::SerializationError,
145
145
  "pre-S3 approval_request must contain exactly one parent identity, got #{parents.inspect}"
146
146
  end
147
147
 
148
148
  items = source.fetch("items")
149
149
  unless items.is_a?(Array) && !items.empty?
150
- raise Phronomy::Persistence::SerializationError,
150
+ raise Phronomy::Storage::SerializationError,
151
151
  "pre-S3 approval_request items must be a non-empty Array"
152
152
  end
153
153
  source["items"] = items.map.with_index do |item, index|
@@ -164,7 +164,7 @@ module Phronomy
164
164
  source.delete("agent_invocation_id")
165
165
  source["execution_id"] = execution_id.to_s
166
166
  elsif source.fetch("execution_id").to_s != execution_id.to_s
167
- raise Phronomy::Persistence::SerializationError,
167
+ raise Phronomy::Storage::SerializationError,
168
168
  "pre-S3 approval_request execution_id does not match AgentExecution"
169
169
  end
170
170
  source
@@ -180,7 +180,7 @@ module Phronomy
180
180
  details = []
181
181
  details << "missing=#{missing.inspect}" unless missing.empty?
182
182
  details << "unknown=#{unknown.inspect}" unless unknown.empty?
183
- raise Phronomy::Persistence::SerializationError,
183
+ raise Phronomy::Storage::SerializationError,
184
184
  "#{label} schema mismatch (#{details.join(", ")})"
185
185
  end
186
186
  private_class_method :validate_allowed_keys!
@@ -197,17 +197,17 @@ module Phronomy
197
197
 
198
198
  def stringify_keys(hash)
199
199
  unless hash.is_a?(Hash)
200
- raise Phronomy::Persistence::SerializationError,
200
+ raise Phronomy::Storage::SerializationError,
201
201
  "migration input must be a Hash"
202
202
  end
203
203
  hash.each_with_object({}) do |(key, value), result|
204
204
  unless key.is_a?(String) || key.is_a?(Symbol)
205
- raise Phronomy::Persistence::SerializationError,
205
+ raise Phronomy::Storage::SerializationError,
206
206
  "migration key must be String or Symbol, got #{key.class}"
207
207
  end
208
208
  string_key = key.to_s
209
209
  if result.key?(string_key)
210
- raise Phronomy::Persistence::SerializationError,
210
+ raise Phronomy::Storage::SerializationError,
211
211
  "duplicate migration key after normalization: #{string_key.inspect}"
212
212
  end
213
213
  result[string_key] = value
@@ -216,7 +216,7 @@ module Phronomy
216
216
  private_class_method :stringify_keys
217
217
 
218
218
  def migration_error(label, error)
219
- raise Phronomy::Persistence::SerializationError,
219
+ raise Phronomy::Storage::SerializationError,
220
220
  "cannot migrate pre-S3 #{label}: #{error.class}: #{error.message}"
221
221
  end
222
222
  private_class_method :migration_error
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Domain-neutral transaction ownership and participation. Domain operations
5
+ # choose their atomic boundary; composition supplies their storage adapters.
6
+ # @api public
7
+ class Persistence
8
+ attr_reader :backend
9
+
10
+ def initialize(backend:)
11
+ StorageBoundary.call { Storage::Backend.validate_capabilities!(backend) }
12
+ @backend = backend
13
+ end
14
+
15
+ # Bind an injected adapter for ordinary synchronous reads/standalone writes.
16
+ # Only that adapter receives Storage's raw view. Multi-operation changes use
17
+ # atomic/Transaction#participate so all participants use the exact same view.
18
+ # @api private
19
+ def bind(adapter)
20
+ StorageBoundary.call { adapter.new(backend.view) }
21
+ end
22
+ end
23
+ end