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
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class Persistence
5
+ # Classifies evidence supplied by the owning domains. No retry, callback,
6
+ # ownership release or external operation is performed here.
7
+ # @api public
8
+ SaveOutcome = Data.define(:disposition, :original_error, :read_error) do
9
+ def self.compare(before:, after:, original_error: nil)
10
+ current = yield
11
+ disposition = if current == after
12
+ :committed
13
+ elsif current == before
14
+ :not_committed
15
+ else
16
+ :unknown
17
+ end
18
+ new(disposition: disposition, original_error: original_error, read_error: nil)
19
+ rescue => error
20
+ new(disposition: :unknown, original_error: original_error, read_error: error)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class Persistence
5
+ # Compares revision and snapshot evidence supplied by the owning domain.
6
+ # Does not load, save, retry, resolve external operations or decide resumption.
7
+ # @api private
8
+ module SnapshotComparison
9
+ module_function
10
+
11
+ # Compare authoritative current state with a known pre/post revision pair.
12
+ # This is deliberately small: callers still validate entity-specific content.
13
+ def compare_revisions(current_revision:, expected_pre_revision:, intended_post_revision:)
14
+ current = current_revision
15
+ return :post_state if current == intended_post_revision
16
+ return :pre_state if current == expected_pre_revision
17
+
18
+ :conflict
19
+ end
20
+
21
+ # Evidence comparison for revisioned snapshot repositories.
22
+ # Some repositories return a newly allocated revision only after save, so
23
+ # callers may not know the intended post revision in advance. In that case
24
+ # the intended snapshot plus a revision different from the expected pre
25
+ # revision identifies the post-state.
26
+ def compare_revisioned_snapshot(
27
+ record:,
28
+ expected_pre_revision:,
29
+ intended_snapshot:,
30
+ intended_post_revision: nil
31
+ )
32
+ return :pre_state if record.nil? && expected_pre_revision.nil?
33
+ return :conflict if record.nil?
34
+
35
+ revision = fetch_value(record, :revision)
36
+ snapshot = fetch_value(record, :snapshot)
37
+ normalized_snapshot = normalize_value(snapshot)
38
+ normalized_intended = normalize_value(intended_snapshot)
39
+
40
+ if intended_post_revision
41
+ if revision == intended_post_revision &&
42
+ normalized_snapshot == normalized_intended
43
+ return :post_state
44
+ end
45
+ elsif normalized_snapshot == normalized_intended &&
46
+ revision != expected_pre_revision
47
+ return :post_state
48
+ end
49
+
50
+ return :pre_state if revision == expected_pre_revision
51
+
52
+ :conflict
53
+ end
54
+
55
+ def normalize_value(value)
56
+ case value
57
+ when Hash
58
+ value.each_with_object({}) do |(key, child), result|
59
+ result[key.to_s] = normalize_value(child)
60
+ end
61
+ when Array
62
+ value.map { |child| normalize_value(child) }
63
+ when Symbol
64
+ value.to_s
65
+ else
66
+ value
67
+ end
68
+ end
69
+
70
+ def fetch_value(record, key)
71
+ return nil unless record
72
+ return record.public_send(key) if record.respond_to?(key)
73
+ return record[key] if record.respond_to?(:key?) && record.key?(key)
74
+ string_key = key.to_s
75
+ return record[string_key] if record.respond_to?(:key?) && record.key?(string_key)
76
+
77
+ nil
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class Persistence
5
+ # Translate only the backend's existing known failure categories. Unknown
6
+ # transport/commit outcomes and application exceptions pass
7
+ # through unchanged so the operation's existing reconciliation stays in charge.
8
+ # @api private
9
+ module StorageBoundary
10
+ def self.call
11
+ yield
12
+ rescue Phronomy::Storage::ConflictError => error
13
+ raise_domain_error(ConflictError, error)
14
+ rescue Phronomy::Storage::NotFoundError => error
15
+ raise_domain_error(NotFoundError, error)
16
+ rescue Phronomy::Storage::SerializationError => error
17
+ raise_domain_error(SerializationError, error)
18
+ rescue Phronomy::Storage::TransactionError => error
19
+ raise_domain_error(TransactionError, error)
20
+ rescue Phronomy::Storage::UnsupportedBackendError => error
21
+ raise_domain_error(UnsupportedBackendError, error)
22
+ end
23
+
24
+ def self.raise_domain_error(type, original)
25
+ mapped = type.new(original.message)
26
+ mapped.set_backtrace(original.backtrace)
27
+ raise mapped, cause: original
28
+ end
29
+ private_class_method :raise_domain_error
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class Persistence
5
+ # Execute short domain operations in one synchronous transaction. Participants
6
+ # bind their own storage adapters; this protocol does not select any domain.
7
+ # Explicit nesting remains a savepoint, never an independent commit.
8
+ # Storage failures whose commit outcome is fundamentally unknown remain
9
+ # backend/database failures; Phronomy does not claim exactly-once semantics
10
+ # for such failures. No automatic retry or rollback assumption is added.
11
+ # @api public
12
+ def atomic
13
+ scopes = Thread.current.thread_variable_get(:phronomy_persistence_scopes)
14
+ unless scopes
15
+ scopes = {}
16
+ Thread.current.thread_variable_set(:phronomy_persistence_scopes, scopes)
17
+ end
18
+ stack = (scopes[self] ||= [])
19
+ stack.last&.validate!
20
+ if backend.transaction_open? && stack.empty?
21
+ raise TransactionError, "cannot establish commit ownership inside an unrelated storage transaction"
22
+ end
23
+ scope = nil
24
+ result = StorageBoundary.call do
25
+ backend.transaction do |view|
26
+ scope = Transaction.new(self, view, parent: stack.last)
27
+ stack.push(scope)
28
+ begin
29
+ yield scope
30
+ ensure
31
+ scope.close!
32
+ stack.pop
33
+ end
34
+ end
35
+ end
36
+ scope.confirm!
37
+ result
38
+ ensure
39
+ scopes&.delete(self) if stack && stack.empty?
40
+ end
41
+
42
+ # A synchronous participation scope. Only storage adapters receive the raw
43
+ # view; domain operations exchange this scope and their own public values.
44
+ # @api public
45
+ class Transaction
46
+ def initialize(persistence, view, parent: nil)
47
+ @persistence, @view, @parent = persistence, view, parent
48
+ @open = true
49
+ @committed = false
50
+ @thread = Thread.current
51
+ end
52
+
53
+ # Bind a domain-owned storage adapter to this exact transaction. Exceptions
54
+ # poison this scope even if its caller rescues them inside the outer block.
55
+ # @api public
56
+ def participate(persistence:, adapter:)
57
+ validate!
58
+ unless @persistence.equal?(persistence)
59
+ raise TransactionError, "participation requires the same open Persistence scope"
60
+ end
61
+ StorageBoundary.call do
62
+ @view.atomic { |view| yield adapter.new(view) }
63
+ end
64
+ end
65
+
66
+ # A savepoint result is provisional until every enclosing scope commits.
67
+ # @api public
68
+ def outermost? = @parent.nil?
69
+
70
+ def committed? = @committed && (!@parent || @parent.committed?)
71
+
72
+ # @api private
73
+ def validate!
74
+ unless @open && @thread.equal?(Thread.current)
75
+ raise TransactionError, "participation requires its open synchronous context"
76
+ end
77
+ @view.validate!
78
+ end
79
+
80
+ # @api private
81
+ def close! = @open = false
82
+ # @api private
83
+ def confirm! = @committed = true
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module PersistenceComposition
5
+ # One composition catalog, supplied to a neutral storage driver.
6
+ # @api private
7
+ module StorageSchema
8
+ def self.validate!(view)
9
+ resources.each { |resource| view.public_send(resource.kind, resource) }
10
+ end
11
+
12
+ def self.resources
13
+ (agent_resources + [Phronomy::MultiAgent::Persistence::StorageSchema::HANDOFF_STATES,
14
+ Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS,
15
+ Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS,
16
+ Phronomy::Workflow::Persistence::StorageSchema::STATES]).freeze
17
+ end
18
+
19
+ def self.agent_resources
20
+ [Agent::Persistence::StorageSchema::ROOTS,
21
+ Agent::Persistence::StorageSchema::EXECUTIONS,
22
+ Agent::Persistence::StorageSchema::JOURNAL,
23
+ Agent::Persistence::StorageSchema::RETENTIONS,
24
+ Agent::Persistence::StorageSchema::CANCELLATIONS,
25
+ ContentStore::StorageSchema::CONTENTS].freeze
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module PersistenceComposition
5
+ # The application's construction result, not an execution or query facade.
6
+ # Pass only the corresponding domain store to each domain's API.
7
+ Stores = Data.define(:coordinator, :agent, :multi_agent, :workflow)
8
+
9
+ # An ordinary Agent needs only its own records and content. Do not load or
10
+ # instantiate Team/Workflow implementations merely to construct an Agent.
11
+ def self.agent(backend: nil)
12
+ resources = StorageSchema.agent_resources
13
+ backend ||= Phronomy::Storage::Backends::InMemory.new(resources: resources)
14
+ coordinator = Phronomy::Persistence.new(backend: backend)
15
+ Phronomy::Persistence::StorageBoundary.call do
16
+ resources.each { |resource| backend.view.public_send(resource.kind, resource) }
17
+ end
18
+ Phronomy::Agent::Store.new(coordinator: coordinator, records: Phronomy::Agent::Persistence::Records)
19
+ end
20
+
21
+ def self.build(backend:)
22
+ coordinator = Phronomy::Persistence.new(backend: backend)
23
+ Phronomy::Persistence::StorageBoundary.call { StorageSchema.validate!(backend.view) }
24
+ agent = Phronomy::Agent::Store.new(coordinator: coordinator, records: Phronomy::Agent::Persistence::Records)
25
+ multi_agent = Phronomy::MultiAgent::Store.new(coordinator: coordinator, records: Phronomy::MultiAgent::Persistence::Records, agent_store: agent)
26
+ workflow = coordinator.bind(Phronomy::Workflow::Persistence::StateRepository)
27
+ Stores.new(coordinator: coordinator, agent: agent, multi_agent: multi_agent, workflow: workflow)
28
+ end
29
+
30
+ def self.in_memory
31
+ build(backend: Phronomy::Storage::Backends::InMemory.new(resources: StorageSchema.resources))
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Phronomy
6
+ module PersistenceComposition
7
+ # Explicit offline conversion of a quiescent snapshot into an unpublished
8
+ # destination. Runtime codecs never call this converter.
9
+ # @api public
10
+ class Unit4Migration
11
+ def self.convert(snapshot, quiescent:)
12
+ raise ArgumentError, "stop every writer before migration" unless quiescent == true
13
+ new(snapshot).convert
14
+ end
15
+
16
+ def initialize(snapshot)
17
+ @snapshot = Phronomy::CanonicalJSON.load(Phronomy::CanonicalJSON.dump(snapshot))
18
+ unless @snapshot.fetch("format") == "phronomy.snapshot/1" && @snapshot.fetch("revision") == "r8-unit3"
19
+ raise ArgumentError, "expected an r8-unit3 phronomy.snapshot/1 export"
20
+ end
21
+ @resources = @snapshot.fetch("resources")
22
+ @roots = index("agent.roots")
23
+ @executions = index("agent.executions")
24
+ @routing = index("handoff.states")
25
+ @blobs = index("content.blobs")
26
+ @holds, @cancellations = {}, {}
27
+ end
28
+
29
+ def convert
30
+ validate_source!
31
+ @executions.each_value { |entry| migrate_execution(entry) }
32
+ @routing.each_value { |entry| migrate_routing(entry) }
33
+ @resources["agent.retentions"] = {"kind" => "records", "entries" => @holds.values.sort_by { |entry| entry.fetch("key") }}
34
+ @resources["agent.cancellations"] = {"kind" => "records", "entries" => @cancellations.values.sort_by { |entry| entry.fetch("key") }}
35
+ @resources.fetch("content.blobs")["entries"] = @blobs.values.sort_by { |entry| entry.fetch("key") }
36
+ @snapshot["revision"] = "r8-unit4"
37
+ @snapshot["migration"] = {"source" => "r8-unit3", "logical_revisions" => "preserved", "physical_revisions" => "preserved"}
38
+ @snapshot["migration"]["resources_sha256"] = Digest::SHA256.hexdigest(Phronomy::CanonicalJSON.dump(@resources))
39
+ @snapshot
40
+ rescue KeyError, ArgumentError, Phronomy::Storage::SerializationError => error
41
+ raise Phronomy::Persistence::SerializationError, "Unit 4 migration aborted: #{error.message}"
42
+ end
43
+
44
+ private
45
+
46
+ def index(id)
47
+ entries = @resources.fetch(id).fetch("entries")
48
+ result = entries.to_h { |entry| [entry.fetch("key"), entry] }
49
+ raise ArgumentError, "duplicate identity in #{id}" unless result.size == entries.size
50
+ result
51
+ end
52
+
53
+ def validate_source!
54
+ expected = StorageSchema.resources.map(&:id) - %w[agent.retentions agent.cancellations]
55
+ raise ArgumentError, "source resource set differs from unit 3" unless @resources.keys.sort == expected.sort
56
+ @blobs.each do |key, entry|
57
+ bytes = [entry.fetch("hex")].pack("H*")
58
+ unless entry.fetch("hex").match?(/\A(?:[0-9a-f]{2})*\z/) && "sha256:#{Digest::SHA256.hexdigest(bytes)}" == key
59
+ raise ArgumentError, "content digest mismatch: #{key}"
60
+ end
61
+ end
62
+ @blobs.each_value do |entry|
63
+ bytes = [entry.fetch("hex")].pack("H*")
64
+ next unless bytes.lstrip.start_with?("{", "[")
65
+ begin
66
+ value = Phronomy::CanonicalJSON.load(bytes)
67
+ rescue Phronomy::Persistence::SerializationError, Phronomy::Storage::SerializationError, JSON::ParserError
68
+ next # Arbitrary text blobs are allowed.
69
+ end
70
+ validate_references!(value)
71
+ end
72
+ @roots.each do |id, entry|
73
+ root = Phronomy::Agent::Persistence::Codec.decode_agent_root(record(entry))
74
+ unless root.agent_id == id && root.agent_revision == entry.fetch("revision")
75
+ raise ArgumentError, "Agent root identity/revision mismatch: #{id}"
76
+ end
77
+ end
78
+ validate_references!(@resources)
79
+ validate_journal!
80
+ @executions.each do |id, entry|
81
+ envelope = entry.fetch("record")
82
+ unless envelope.fetch("record_type") == "phronomy.agent_execution" && envelope.fetch("format_version") == "0.1"
83
+ raise ArgumentError, "unsupported source execution format: #{id}"
84
+ end
85
+ body = envelope.fetch("payload")
86
+ Phronomy::Agent::Persistence::Codec.validate_agent_execution_payload!(body)
87
+ unless body.fetch("execution_id") == id && body.fetch("execution_revision") == entry.fetch("revision") && @roots.key?(body.fetch("agent_id"))
88
+ raise ArgumentError, "execution identity/revision/owner mismatch: #{id}"
89
+ end
90
+ attributes = {"owner" => body.fetch("agent_id"), "active" => %w[preparing active suspended].include?(body.fetch("status"))}
91
+ raise ArgumentError, "execution attributes mismatch: #{id}" unless entry.fetch("attributes") == attributes
92
+ [body["result_ref"], body["error_ref"], *body.fetch("working_records").map { |item| item["content_ref"] }].compact.each { |ref| fetch_bytes(ref) }
93
+ end
94
+ end
95
+
96
+ def validate_references!(value)
97
+ case value
98
+ when Hash
99
+ value.each do |key, child|
100
+ if key.end_with?("_ref") && child.is_a?(String)
101
+ fetch_bytes(child)
102
+ elsif key.end_with?("_refs") && child.is_a?(Array)
103
+ child.each { |ref| fetch_bytes(ref) if ref.is_a?(String) }
104
+ end
105
+ validate_references!(child)
106
+ end
107
+ when Array then value.each { |child| validate_references!(child) }
108
+ end
109
+ end
110
+
111
+ def validate_journal!
112
+ source = @resources.fetch("agent.journal")
113
+ groups = source.fetch("entries").group_by { |entry| entry.fetch("stream") }
114
+ heads = source.fetch("heads")
115
+ raise ArgumentError, "journal has unknown owner" unless ((groups.keys + heads.keys).uniq - @roots.keys).empty?
116
+ @roots.each do |id, root|
117
+ position = root.fetch("record").fetch("payload").fetch("journal_position")
118
+ rows = groups.fetch(id, []).sort_by { |entry| entry.fetch("position") }
119
+ unless heads.fetch(id, 0) == position && rows.map { |entry| entry.fetch("position") } == (1..position).to_a
120
+ raise ArgumentError, "journal position mismatch: #{id}"
121
+ end
122
+ raise ArgumentError, "duplicate journal identity: #{id}" unless rows.map { |entry| entry.fetch("id") }.uniq.size == rows.size
123
+ rows.each do |entry|
124
+ value = Phronomy::Agent::Persistence::Codec.decode_journal_record(record(entry))
125
+ unless value.agent_id == id && value.sequence == entry.fetch("position") && value.record_id == entry.fetch("id")
126
+ raise ArgumentError, "journal entry identity mismatch: #{id}"
127
+ end
128
+ fetch_bytes(value.content_ref) if value.content_ref
129
+ end
130
+ end
131
+ end
132
+
133
+ def migrate_execution(entry)
134
+ body = entry.fetch("record").fetch("payload")
135
+ id, owner = body.values_at("execution_id", "agent_id")
136
+ metadata = body.fetch("metadata")
137
+ if (correlation = metadata.delete("coordination"))
138
+ correlation.delete("handoff_revision") if correlation.fetch("kind") == "handoff"
139
+ metadata["reservation"] = correlation
140
+ if correlation.fetch("kind") == "handoff"
141
+ main = correlation.fetch("main_agent_id")
142
+ raise ArgumentError, "missing Handoff anchor for #{id}" unless @routing.key?(main)
143
+ metadata["execution_extension"] = binding("phronomy.handoff")
144
+ retain(owner, id, "handoff:#{main}")
145
+ end
146
+ end
147
+ cancelled = metadata.delete("coordination_cancel_requested") == true
148
+ if cancelled
149
+ metadata["cancellation_requested"] = true
150
+ @cancellations[id] = record_entry(id, 0, {"owner" => owner}, "phronomy.agent_cancellation", "0.1",
151
+ {"agent_id" => owner, "execution_id" => id})
152
+ end
153
+ if (ref = metadata.delete("multi_agent_coordination_ref"))
154
+ migrate_children(body, fetch_json(ref), cancelled)
155
+ end
156
+ if (target = metadata.delete("handoff_target_agent_id"))
157
+ target_execution = metadata.delete("handoff_target_execution_id") || raise(ArgumentError, "missing transfer execution: #{id}")
158
+ ref = metadata.delete("handoff_context_ref") || raise(ArgumentError, "missing transfer context: #{id}")
159
+ raise ArgumentError, "transfer receipt on unfinished Source: #{id}" unless body.fetch("status") == "handed_off"
160
+ Phronomy::Agent::TransferContext.from_h(fetch_json(ref))
161
+ main = metadata.fetch("reservation").fetch("main_agent_id")
162
+ metadata["transfer_receipt"] = {"target_agent_id" => target, "target_execution_id" => target_execution,
163
+ "context_ref" => ref, "owner_key" => "handoff:#{main}"}
164
+ retain(target, target_execution, "handoff:#{main}")
165
+ end
166
+ entry.fetch("record")["format_version"] = "0.2"
167
+ Phronomy::Agent::Persistence::Codec.validate_agent_execution_payload!(body)
168
+ end
169
+
170
+ def migrate_children(body, snapshot, cancelled)
171
+ id = body.fetch("execution_id")
172
+ metadata = body.fetch("metadata")
173
+ children = snapshot.fetch("children").map do |child|
174
+ child_agent, child_execution = child.values_at("agent_id", "execution_id")
175
+ existing = @executions[child_execution]&.fetch("record")&.fetch("payload")
176
+ raise ArgumentError, "subagent owner mismatch: #{child_execution}" if existing && existing.fetch("agent_id") != child_agent
177
+ retain(child_agent, child_execution, "subagent:#{id}") if @roots.key?(child_agent)
178
+ unless existing || child.fetch("state") == "reserved"
179
+ raise ArgumentError, "accepted child execution is missing: #{child_execution}"
180
+ end
181
+ if @roots.key?(child_agent)
182
+ root = @roots.fetch(child_agent).fetch("record").fetch("payload")
183
+ unless child.fetch("definition") == {"id" => root.fetch("agent_definition_id"), "version" => root.fetch("agent_definition_version")}
184
+ raise ArgumentError, "child definition mismatch: #{child_agent}"
185
+ end
186
+ end
187
+ raise ArgumentError, "unknown child error policy" unless %w[raise skip].include?(child.fetch("on_error"))
188
+ knowledge = fetch_json(child.fetch("knowledge_ref"))
189
+ knowledge.each { |item| Phronomy::Agent::KnowledgeItem.new(content: item.fetch("content"), metadata: item.fetch("metadata")) }
190
+ calls = Array(metadata[Phronomy::Agent::ExecutionMetadata::TOOL_BATCH_METADATA_KEY])
191
+ call = calls.find { |item| item.fetch("tool_invocation_id") == child.fetch("slot") }
192
+ imported = call ? %w[completed failed cancelled rejected].include?(call.fetch("status")) : body.fetch("phase") != "dispatching_tools"
193
+ {"slot" => child.fetch("slot"), "name" => child.fetch("name"), "definition" => child.fetch("definition"),
194
+ "agent_id" => child_agent, "execution_id" => child_execution,
195
+ "input" => fetch_bytes(child.fetch("input_ref")).force_encoding(Encoding::UTF_8),
196
+ "durable_context" => child["durable_context_ref"] && fetch_json(child.fetch("durable_context_ref")),
197
+ "knowledge" => knowledge, "on_error" => child.fetch("on_error"), "imported" => imported}
198
+ end
199
+ raise ArgumentError, "duplicate child slot: #{id}" unless children.map { |child| child.fetch("slot") }.uniq.size == children.size
200
+ ref = put_json("children" => children, "cancel_requested" => cancelled)
201
+ metadata["execution_extension"] = binding("phronomy.subagent", ref)
202
+ retain(body.fetch("agent_id"), id, "subagent:#{id}") unless children.empty?
203
+ end
204
+
205
+ def migrate_routing(entry)
206
+ envelope = entry.fetch("record")
207
+ unless envelope.fetch("record_type") == "phronomy.handoff_state" && envelope.fetch("format_version") == "0.1"
208
+ raise ArgumentError, "unsupported source Handoff routing format"
209
+ end
210
+ envelope["format_version"] = "0.2"
211
+ state = Phronomy::MultiAgent::Persistence::Codec.decode_handoff_state(record(entry))
212
+ unless entry.fetch("key") == state.main_agent_id && entry.fetch("revision") == state.handoff_revision
213
+ raise ArgumentError, "Handoff routing identity/revision mismatch"
214
+ end
215
+ main = state.main_agent_id
216
+ ids = [main, state.active_agent_id]
217
+ @executions.each_value do |execution|
218
+ body = execution.fetch("record").fetch("payload")
219
+ next unless body.dig("metadata", "reservation", "main_agent_id") == main
220
+ ids << body.fetch("agent_id")
221
+ receipt = body.dig("metadata", "transfer_receipt")
222
+ ids << receipt.fetch("target_agent_id") if receipt
223
+ end
224
+ ids = ids.uniq.sort
225
+ ids.each { |id| retain(id, nil, "handoff:#{main}") }
226
+ Phronomy::Agent::TransferContext.from_h(fetch_json(state.active_handoff_context_ref)) if state.active_handoff_context_ref
227
+ body = entry.fetch("record").fetch("payload")
228
+ body.fetch("metadata")["retained_agent_ids"] = ids
229
+ Array(state.metadata["cancelled_execution_ids"]).each do |execution_id|
230
+ cancelled = @executions[execution_id]&.fetch("record")&.fetch("payload")
231
+ next unless cancelled # A cancelled reservation can precede acceptance.
232
+ unless cancelled.dig("metadata", "reservation", "main_agent_id") == main
233
+ raise ArgumentError, "Handoff cancellation points outside its conversation: #{execution_id}"
234
+ end
235
+ @cancellations[execution_id] = record_entry(execution_id, 0, {"owner" => cancelled.fetch("agent_id")},
236
+ "phronomy.agent_cancellation", "0.1", {"agent_id" => cancelled.fetch("agent_id"), "execution_id" => execution_id})
237
+ end
238
+ if state.pending_source_execution_id
239
+ source = @executions.fetch(state.pending_source_execution_id).fetch("record").fetch("payload")
240
+ receipt = source.fetch("metadata").fetch("transfer_receipt")
241
+ unless receipt.fetch("target_execution_id") == state.pending_target_execution_id && receipt.fetch("target_agent_id") == state.active_agent_id && receipt.fetch("context_ref") == state.active_handoff_context_ref
242
+ raise ArgumentError, "Handoff pending identities/context do not match the Source receipt"
243
+ end
244
+ body.fetch("metadata")["source_agent_id"] = source.fetch("agent_id")
245
+ end
246
+ end
247
+
248
+ def binding(key, ref = nil)
249
+ {"binding_key" => key, "binding_version" => 1, "state_ref" => ref}
250
+ end
251
+
252
+ def retain(agent_id, execution_id, owner_key)
253
+ raise ArgumentError, "retained Agent is missing: #{agent_id}" unless @roots.key?(agent_id)
254
+ value = Phronomy::Agent::Retention.new(agent_id: agent_id, execution_id: execution_id, owner_key: owner_key)
255
+ key = Digest::SHA256.hexdigest(Phronomy::CanonicalJSON.dump(value.to_h))
256
+ @holds[key] = record_entry(key, 0, {"owner" => agent_id, "holder" => owner_key}, "phronomy.agent_retention", "0.1", value.to_h)
257
+ end
258
+
259
+ def record_entry(key, revision, attributes, type, version, payload)
260
+ {"key" => key, "revision" => revision, "attributes" => attributes,
261
+ "record" => {"record_type" => type, "format_version" => version, "payload" => payload}}
262
+ end
263
+
264
+ def record(entry) = Phronomy::Storage::DurableRecord.new(**entry.fetch("record").transform_keys(&:to_sym))
265
+ def fetch_bytes(ref) = [@blobs.fetch(ref).fetch("hex")].pack("H*")
266
+ def fetch_json(ref) = Phronomy::CanonicalJSON.load(fetch_bytes(ref))
267
+
268
+ def put_json(value)
269
+ bytes = Phronomy::CanonicalJSON.dump(value)
270
+ key = "sha256:#{Digest::SHA256.hexdigest(bytes)}"
271
+ @blobs[key] ||= {"key" => key, "hex" => bytes.unpack1("H*"), "attributes" => {"canonicalization_version" => 1}}
272
+ key
273
+ end
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Select the Agent default at the application composition boundary. Binding
4
+ # does not create Persistence, global configuration, or a Runtime.
5
+ Phronomy::Agent::Base.context_policy(Phronomy::Context::DefaultPolicy.instance)
6
+
7
+ Phronomy::Agent::DefaultPersistence.install_factory(
8
+ -> { Phronomy::PersistenceComposition.agent }
9
+ )
10
+
11
+ Phronomy::Agent::ExecutionEnvironment.install_provider(-> { Phronomy::Agent::EngineEnvironment.new })