phronomy 0.27.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 (475) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +52 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +9 -3
  6. data/benchmark/bench_agent_invoke.rb +25 -50
  7. data/benchmark/bench_context_assembler.rb +14 -11
  8. data/benchmark/bench_regression.rb +1 -1
  9. data/benchmark/bench_tool_schema.rb +1 -1
  10. data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
  11. data/docs/architecture/agent-transition-ownership.md +13 -89
  12. data/docs/architecture/backend-groups.md +152 -0
  13. data/docs/architecture/before-llm-input.md +3 -3
  14. data/docs/architecture/context-management.md +1 -1
  15. data/docs/architecture/execution-abstraction-review.md +88 -0
  16. data/docs/architecture/execution-boundaries.md +28 -0
  17. data/docs/architecture/execution-metadata-and-values.md +6 -0
  18. data/docs/architecture/multi-agent-handoff.md +6 -5
  19. data/docs/architecture/persistence.md +27 -9
  20. data/docs/architecture/r8-unit1.md +104 -0
  21. data/docs/architecture/r8-unit10.md +72 -0
  22. data/docs/architecture/r8-unit11.md +71 -0
  23. data/docs/architecture/r8-unit12.md +67 -0
  24. data/docs/architecture/r8-unit13.md +61 -0
  25. data/docs/architecture/r8-unit14.md +72 -0
  26. data/docs/architecture/r8-unit15.md +65 -0
  27. data/docs/architecture/r8-unit16.md +54 -0
  28. data/docs/architecture/r8-unit17.md +73 -0
  29. data/docs/architecture/r8-unit18.md +92 -0
  30. data/docs/architecture/r8-unit19.md +61 -0
  31. data/docs/architecture/r8-unit2.md +55 -0
  32. data/docs/architecture/r8-unit20.md +51 -0
  33. data/docs/architecture/r8-unit21.md +79 -0
  34. data/docs/architecture/r8-unit22.md +74 -0
  35. data/docs/architecture/r8-unit23.md +81 -0
  36. data/docs/architecture/r8-unit3.md +130 -0
  37. data/docs/architecture/r8-unit4.md +79 -0
  38. data/docs/architecture/r8-unit5.md +115 -0
  39. data/docs/architecture/r8-unit6.md +71 -0
  40. data/docs/architecture/r8-unit7.md +70 -0
  41. data/docs/architecture/r8-unit8.md +78 -0
  42. data/docs/architecture/r8-unit9.md +73 -0
  43. data/docs/architecture/refactoring-closure.md +10 -1
  44. data/docs/architecture/remaining-refactoring-plan.md +16 -0
  45. data/docs/architecture/storage-execution-boundaries.md +54 -0
  46. data/docs/architecture/tool-schema-recording-gap.md +6 -0
  47. data/docs/architecture/tracing.md +1 -1
  48. data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
  49. data/docs/architecture.md +24 -11
  50. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  51. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  52. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
  53. data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
  54. data/docs/decisions/032-storage-backend-composition.md +5 -0
  55. data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
  56. data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
  57. data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
  58. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
  59. data/docs/decisions/040-configuration-default-composition.md +12 -1
  60. data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
  61. data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
  62. data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
  63. data/docs/decisions/058-neutral-storage-primitives.md +5 -0
  64. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  65. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  66. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  67. data/docs/decisions/062-content-store-service.md +52 -0
  68. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  69. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  70. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  71. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  72. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  73. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  74. data/docs/decisions/README.md +18 -1
  75. data/docs/execution-boundaries-preview.md +45 -0
  76. data/docs/features.md +8 -3
  77. data/docs/getting-started.md +9 -6
  78. data/docs/migrations/0.22.md +2 -2
  79. data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
  80. data/docs/migrations/handoff-runner-multi-agent.md +2 -0
  81. data/docs/migrations/persistence-failure-contracts.md +67 -0
  82. data/docs/migrations/r8-unit10.md +36 -0
  83. data/docs/migrations/r8-unit11.md +33 -0
  84. data/docs/migrations/r8-unit12.md +31 -0
  85. data/docs/migrations/r8-unit13.md +36 -0
  86. data/docs/migrations/r8-unit14.md +31 -0
  87. data/docs/migrations/r8-unit15.md +31 -0
  88. data/docs/migrations/r8-unit16.md +26 -0
  89. data/docs/migrations/r8-unit17.md +28 -0
  90. data/docs/migrations/r8-unit18.md +29 -0
  91. data/docs/migrations/r8-unit19.md +28 -0
  92. data/docs/migrations/r8-unit20.md +24 -0
  93. data/docs/migrations/r8-unit21.md +28 -0
  94. data/docs/migrations/r8-unit22.md +29 -0
  95. data/docs/migrations/r8-unit23.md +20 -0
  96. data/docs/migrations/r8-unit4.md +67 -0
  97. data/docs/migrations/r8-unit5.md +75 -0
  98. data/docs/migrations/r8-unit6.md +109 -0
  99. data/docs/migrations/r8-unit7.md +31 -0
  100. data/docs/migrations/r8-unit8.md +33 -0
  101. data/docs/migrations/r8-unit9.md +28 -0
  102. data/docs/migrations/storage-async-client.md +103 -0
  103. data/docs/migrations/vector-async-clients.md +110 -0
  104. data/docs/persistence-backends.md +38 -21
  105. data/docs/runtime-and-concurrency.md +26 -1
  106. data/lib/phronomy/agent/admission.rb +78 -0
  107. data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
  108. data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
  109. data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
  110. data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
  111. data/lib/phronomy/agent/async_event_api.rb +37 -32
  112. data/lib/phronomy/agent/base.rb +74 -72
  113. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  114. data/lib/phronomy/agent/composition/run_once.rb +1 -1
  115. data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
  116. data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
  117. data/lib/phronomy/agent/control_request.rb +21 -0
  118. data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
  119. data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
  120. data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
  121. data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
  122. data/lib/phronomy/agent/execution/exact_execution.rb +44 -23
  123. data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
  124. data/lib/phronomy/agent/execution/execution_coordinator.rb +214 -395
  125. data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
  126. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +70 -126
  127. data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
  128. data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
  129. data/lib/phronomy/agent/execution/initial_preparation.rb +18 -116
  130. data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
  131. data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
  132. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +7 -7
  133. data/lib/phronomy/agent/execution_change.rb +161 -0
  134. data/lib/phronomy/agent/execution_environment.rb +36 -0
  135. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  136. data/lib/phronomy/agent/execution_observation.rb +35 -0
  137. data/lib/phronomy/agent/filterable.rb +182 -0
  138. data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
  139. data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
  140. data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
  141. data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
  142. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  143. data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
  144. data/lib/phronomy/agent/lifecycle/ownership_registry.rb +18 -23
  145. data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
  146. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  147. data/lib/phronomy/agent/persistence/codec.rb +1 -21
  148. data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
  149. data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
  150. data/lib/phronomy/agent/persistence/records.rb +33 -0
  151. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  152. data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
  153. data/lib/phronomy/agent/persistence/watermark.rb +13 -11
  154. data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
  155. data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +10 -9
  156. data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +27 -27
  157. data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +19 -21
  158. data/lib/phronomy/agent/recovery/recovery_coordinator.rb +23 -16
  159. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  160. data/lib/phronomy/agent/recovery/recovery_support.rb +8 -11
  161. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  162. data/lib/phronomy/agent/retention.rb +17 -0
  163. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  164. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  165. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  166. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  167. data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
  168. data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
  169. data/lib/phronomy/agent/settings.rb +47 -0
  170. data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
  171. data/lib/phronomy/agent/store.rb +253 -0
  172. data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +1 -1
  173. data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
  174. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
  175. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  176. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  177. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  178. data/lib/phronomy/content_store/storage_schema.rb +1 -0
  179. data/lib/phronomy/content_store/stored_contents.rb +2 -1
  180. data/lib/phronomy/context/assembly.rb +202 -0
  181. data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
  182. data/lib/phronomy/context/candidate.rb +51 -0
  183. data/lib/phronomy/context/constraint.rb +47 -0
  184. data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
  185. data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
  186. data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
  187. data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
  188. data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
  189. data/lib/phronomy/context/default_policy.rb +140 -0
  190. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  191. data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
  192. data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
  193. data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
  194. data/lib/phronomy/context/manifest_error.rb +10 -0
  195. data/lib/phronomy/context/prompt_template.rb +98 -0
  196. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  197. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  198. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  199. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  200. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  201. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  202. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  203. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  204. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  205. data/lib/phronomy/embeddings/base.rb +43 -0
  206. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  207. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  208. data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
  209. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  210. data/lib/phronomy/engine/diagnostics.rb +1 -1
  211. data/lib/phronomy/engine/event_loop.rb +7 -7
  212. data/lib/phronomy/engine/execution_binding.rb +23 -0
  213. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  214. data/lib/phronomy/engine/fsm_protocol.rb +1 -6
  215. data/lib/phronomy/engine/fsm_session.rb +3 -3
  216. data/lib/phronomy/engine/metrics.rb +16 -14
  217. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  218. data/lib/phronomy/engine/runtime.rb +25 -12
  219. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  220. data/lib/phronomy/execution/async_operation.rb +41 -0
  221. data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
  222. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
  223. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  224. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  225. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  226. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  227. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  228. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  229. data/lib/phronomy/{engine → execution}/execution.rb +61 -27
  230. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  231. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  232. data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
  233. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  234. data/lib/phronomy/execution/runnable.rb +24 -0
  235. data/lib/phronomy/{engine → execution}/task_result.rb +3 -3
  236. data/lib/phronomy/execution/wait_policy.rb +24 -0
  237. data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +1 -1
  238. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  239. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  240. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
  241. data/lib/phronomy/generation/generator_verifier.rb +7 -7
  242. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  243. data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
  244. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  245. data/lib/phronomy/llm_adapter/base.rb +49 -68
  246. data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
  247. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  248. data/lib/phronomy/llm_adapter/message.rb +62 -0
  249. data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
  250. data/lib/phronomy/llm_adapter/request.rb +79 -0
  251. data/lib/phronomy/llm_adapter/response.rb +36 -0
  252. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  253. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  254. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  255. data/lib/phronomy/llm_adapter.rb +3 -16
  256. data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
  257. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  258. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  259. data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
  260. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
  261. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  262. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
  263. data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
  264. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
  265. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  266. data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
  267. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  268. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  269. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  270. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +58 -42
  271. data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
  272. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  273. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  274. data/lib/phronomy/multi_agent/shared_state.rb +2 -2
  275. data/lib/phronomy/multi_agent/store.rb +156 -0
  276. data/lib/phronomy/multi_agent/team_coordinator.rb +83 -74
  277. data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
  278. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  279. data/lib/phronomy/persistence/content_repository.rb +26 -0
  280. data/lib/phronomy/persistence/errors.rb +16 -0
  281. data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
  282. data/lib/phronomy/persistence/persistence.rb +23 -0
  283. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  284. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  285. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  286. data/lib/phronomy/persistence/transaction.rb +86 -0
  287. data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
  288. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  289. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  290. data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
  291. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  292. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  293. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  294. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  295. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  296. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  297. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  298. data/lib/phronomy/storage/async/async_client.rb +60 -0
  299. data/lib/phronomy/storage/backend.rb +8 -2
  300. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  301. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  302. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
  303. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
  304. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
  305. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
  306. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
  307. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
  308. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
  309. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
  310. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
  311. data/lib/phronomy/tool/authorization.rb +77 -0
  312. data/lib/phronomy/tool/base.rb +549 -9
  313. data/lib/phronomy/tool/call_request.rb +40 -0
  314. data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
  315. data/lib/phronomy/tool/operation.rb +88 -0
  316. data/lib/phronomy/tool/schema.rb +214 -0
  317. data/lib/phronomy/tool/settings.rb +31 -0
  318. data/lib/phronomy/tool/tool_error.rb +7 -0
  319. data/lib/phronomy/tool/tool_executor.rb +58 -0
  320. data/lib/phronomy/tools/agent.rb +22 -90
  321. data/lib/phronomy/tools/mcp.rb +21 -14
  322. data/lib/phronomy/tools/vector_search.rb +4 -4
  323. data/lib/phronomy/tracing/automatic.rb +11 -6
  324. data/lib/phronomy/tracing/base.rb +1 -1
  325. data/lib/phronomy/tracing/observation.rb +37 -0
  326. data/lib/phronomy/tracing/settings.rb +36 -0
  327. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  328. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  329. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  330. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  331. data/lib/phronomy/vector_store/base.rb +118 -49
  332. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  333. data/lib/phronomy/vector_store.rb +2 -17
  334. data/lib/phronomy/version.rb +1 -1
  335. data/lib/phronomy/workflow/completion.rb +11 -0
  336. data/lib/phronomy/workflow/execution/workflow.rb +7 -7
  337. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  338. data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
  339. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  340. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +3 -5
  341. data/lib/phronomy/workflow/execution/workflow_runner.rb +58 -79
  342. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  343. data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
  344. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  345. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  346. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  347. data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
  348. data/lib/phronomy.rb +57 -16
  349. data/scripts/api_snapshot.rb +35 -15
  350. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  351. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  352. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  353. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  354. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  355. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  356. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  357. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  358. data/sig/phronomy/agent/admission.rbs +19 -0
  359. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  360. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  361. data/sig/phronomy/agent/store.rbs +73 -0
  362. data/sig/phronomy/agent.rbs +14 -138
  363. data/sig/phronomy/common.rbs +7 -0
  364. data/sig/phronomy/content_store/contracts.rbs +13 -0
  365. data/sig/phronomy/context.rbs +152 -0
  366. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  367. data/sig/phronomy/embeddings/base.rbs +17 -0
  368. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  369. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  370. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  371. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  372. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  373. data/sig/phronomy/engine/observability.rbs +13 -0
  374. data/sig/phronomy/execution/async_operation.rbs +7 -0
  375. data/sig/phronomy/execution/blocking.rbs +5 -0
  376. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  377. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  378. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  379. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  380. data/sig/phronomy/execution/errors.rbs +7 -0
  381. data/sig/phronomy/execution/execution.rbs +41 -0
  382. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  383. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  384. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  385. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  386. data/sig/phronomy/execution/task_result.rbs +29 -0
  387. data/sig/phronomy/extensions.rbs +0 -21
  388. data/sig/phronomy/handoff.rbs +10 -4
  389. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  390. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  391. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  392. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  393. data/sig/phronomy/llm_context_window.rbs +10 -0
  394. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  395. data/sig/phronomy/multi_agent/store.rbs +54 -0
  396. data/sig/phronomy/multi_agent.rbs +6 -3
  397. data/sig/phronomy/persistence/errors.rbs +18 -0
  398. data/sig/phronomy/persistence/migration.rbs +13 -0
  399. data/sig/phronomy/persistence/transaction.rbs +19 -0
  400. data/sig/phronomy/persistence.rbs +1 -99
  401. data/sig/phronomy/persistence_composition.rbs +19 -0
  402. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  403. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  404. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  405. data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +2 -16
  406. data/sig/phronomy/tool.rbs +48 -4
  407. data/sig/phronomy/tracing/observation.rbs +7 -0
  408. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  409. data/sig/phronomy/vector_store/base.rbs +23 -0
  410. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  411. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  412. data/sig/phronomy/workflow/store.rbs +8 -0
  413. data/sig/phronomy/workflow.rbs +1 -1
  414. data/sig/phronomy.rbs +0 -49
  415. metadata +318 -117
  416. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  417. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  418. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  419. data/lib/phronomy/agent/context/capability/base.rb +0 -548
  420. data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
  421. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  422. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
  423. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
  424. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  425. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  426. data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
  427. data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
  428. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
  429. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
  430. data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
  431. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
  432. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
  433. data/lib/phronomy/agent/persistence/queries.rb +0 -61
  434. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  435. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  436. data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
  437. data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
  438. data/lib/phronomy/configuration/configuration.rb +0 -68
  439. data/lib/phronomy/configuration/global_configuration.rb +0 -26
  440. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  441. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  442. data/lib/phronomy/engine/execution_receiver.rb +0 -65
  443. data/lib/phronomy/engine/runnable.rb +0 -54
  444. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  445. data/lib/phronomy/llm_contract/token_usage.rb +0 -85
  446. data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
  447. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
  448. data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
  449. data/lib/phronomy/persistence/api/persistence.rb +0 -140
  450. data/lib/phronomy/persistence_composition/repositories.rb +0 -77
  451. data/lib/phronomy/recovery/recovery.rb +0 -186
  452. data/lib/phronomy/tool/contract/tool_error.rb +0 -7
  453. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  454. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  455. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  456. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
  457. data/sig/phronomy/execution_receiver.rbs +0 -34
  458. data/sig/phronomy/llm_adapter.rbs +0 -11
  459. data/sig/phronomy/runtime.rbs +0 -76
  460. /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
  461. /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
  462. /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
  463. /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
  464. /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
  465. /data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +0 -0
  466. /data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +0 -0
  467. /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
  468. /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
  469. /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
  470. /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
  471. /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
  472. /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
  473. /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
  474. /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
  475. /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
@@ -19,7 +19,7 @@ RSpec.shared_context "coordination repository values" do
19
19
  end
20
20
  let(:coordination_handoff) do
21
21
  now = Time.now.utc.iso8601(6)
22
- Phronomy::Agent::HandoffState.new(main_agent_id: SecureRandom.uuid, handoff_revision: 1,
22
+ Phronomy::MultiAgent::HandoffState.new(main_agent_id: SecureRandom.uuid, handoff_revision: 1,
23
23
  active_agent_id: "contract-target", active_handoff_context_ref: nil, phase: "target_pending",
24
24
  pending_source_execution_id: "source-execution", pending_target_execution_id: "target-execution",
25
25
  created_at: now, updated_at: now, metadata: {})
@@ -30,27 +30,27 @@ RSpec.shared_examples "a Handoff state repository" do
30
30
  include_context "coordination repository values"
31
31
 
32
32
  it "returns nil only for an authoritative absent Handoff anchor" do
33
- expect(persistence.handoff_states.load(SecureRandom.uuid)).to be_nil
33
+ expect(stores.multi_agent.handoff_states.load(SecureRandom.uuid)).to be_nil
34
34
  end
35
35
 
36
36
  it "round-trips the immutable Handoff snapshot and rejects stale routing CAS" do
37
37
  value = coordination_handoff
38
- persistence.handoff_states.save(value.main_agent_id, expected_revision: nil, state: value)
39
- loaded = persistence.handoff_states.load(value.main_agent_id)
38
+ stores.multi_agent.handoff_states.save(value.main_agent_id, expected_revision: nil, state: value)
39
+ loaded = stores.multi_agent.handoff_states.load(value.main_agent_id)
40
40
  expect(loaded.to_h).to eq(value.to_h)
41
41
  expect(loaded).to be_frozen
42
42
  updated = value.with(phase: "stable")
43
- persistence.handoff_states.save(value.main_agent_id, expected_revision: 1, state: updated)
43
+ stores.multi_agent.handoff_states.save(value.main_agent_id, expected_revision: 1, state: updated)
44
44
  expect do
45
- persistence.handoff_states.save(value.main_agent_id, expected_revision: 1, state: updated)
46
- end.to raise_error(Phronomy::Storage::ConflictError)
47
- expect(persistence.handoff_states.load(value.main_agent_id).handoff_revision).to eq(2)
45
+ stores.multi_agent.handoff_states.save(value.main_agent_id, expected_revision: 1, state: updated)
46
+ end.to raise_error(Phronomy::Persistence::ConflictError)
47
+ expect(stores.multi_agent.handoff_states.load(value.main_agent_id).handoff_revision).to eq(2)
48
48
  end
49
49
 
50
50
  it "rejects a snapshot for a different routing anchor" do
51
51
  expect do
52
- persistence.handoff_states.save("other", expected_revision: nil, state: coordination_handoff)
53
- end.to raise_error(Phronomy::Storage::SerializationError)
52
+ stores.multi_agent.handoff_states.save("other", expected_revision: nil, state: coordination_handoff)
53
+ end.to raise_error(Phronomy::Persistence::SerializationError)
54
54
  end
55
55
  end
56
56
 
@@ -58,53 +58,53 @@ RSpec.shared_examples "a Team repository" do
58
58
  include_context "coordination repository values"
59
59
 
60
60
  it "round-trips a Team lineage and rejects duplicate creation" do
61
- persistence.teams.create(coordination_team)
62
- expect(persistence.teams.load(coordination_team.team_id).to_h).to eq(coordination_team.to_h)
63
- expect { persistence.teams.create(coordination_team) }.to raise_error(Phronomy::Storage::ConflictError)
61
+ stores.multi_agent.teams.create(coordination_team)
62
+ expect(stores.multi_agent.teams.load(coordination_team.team_id).to_h).to eq(coordination_team.to_h)
63
+ expect { stores.multi_agent.teams.create(coordination_team) }.to raise_error(Phronomy::Persistence::ConflictError)
64
64
  end
65
65
 
66
66
  it "rejects a stale Team revision" do
67
- persistence.teams.create(coordination_team)
67
+ stores.multi_agent.teams.create(coordination_team)
68
68
  updated = coordination_team.with(lifecycle_status: "active")
69
- persistence.teams.save(coordination_team.team_id, expected_revision: 0, root: updated)
69
+ stores.multi_agent.teams.save(coordination_team.team_id, expected_revision: 0, root: updated)
70
70
  expect do
71
- persistence.teams.save(coordination_team.team_id, expected_revision: 0, root: updated)
72
- end.to raise_error(Phronomy::Storage::ConflictError)
71
+ stores.multi_agent.teams.save(coordination_team.team_id, expected_revision: 0, root: updated)
72
+ end.to raise_error(Phronomy::Persistence::ConflictError)
73
73
  end
74
74
 
75
75
  it "raises NotFoundError for an absent Team" do
76
- expect { persistence.teams.load(SecureRandom.uuid) }.to raise_error(Phronomy::Storage::NotFoundError)
76
+ expect { stores.multi_agent.teams.load(SecureRandom.uuid) }.to raise_error(Phronomy::Persistence::NotFoundError)
77
77
  end
78
78
  end
79
79
 
80
80
  RSpec.shared_examples "a Team execution repository" do
81
81
  include_context "coordination repository values"
82
- before { persistence.teams.create(coordination_team) }
82
+ before { stores.multi_agent.teams.create(coordination_team) }
83
83
 
84
84
  it "atomically admits at most one active run per Team" do
85
- persistence.team_executions.create_active(coordination_run)
85
+ stores.multi_agent.team_executions.create_active(coordination_run)
86
86
  other = coordination_run.with(team_execution_id: SecureRandom.uuid, execution_revision: 0)
87
- expect { persistence.team_executions.create_active(other) }.to raise_error(Phronomy::AgentBusyError)
88
- expect(persistence.team_executions.list_active(coordination_team.team_id).map(&:team_execution_id)).to eq([coordination_run.team_execution_id])
87
+ expect { stores.multi_agent.team_executions.create_active(other) }.to raise_error(Phronomy::AgentBusyError)
88
+ expect(stores.multi_agent.team_executions.list_active(coordination_team.team_id).map(&:team_execution_id)).to eq([coordination_run.team_execution_id])
89
89
  end
90
90
 
91
91
  it "releases admission after terminal CAS while retaining discoverable outcomes" do
92
- persistence.team_executions.create_active(coordination_run)
92
+ stores.multi_agent.team_executions.create_active(coordination_run)
93
93
  completed = coordination_run.with(status: "completed", phase: "completed")
94
- persistence.team_executions.save(coordination_run.team_execution_id, expected_revision: 0, execution: completed)
94
+ stores.multi_agent.team_executions.save(coordination_run.team_execution_id, expected_revision: 0, execution: completed)
95
95
  other = coordination_run.with(team_execution_id: SecureRandom.uuid, execution_revision: 0)
96
- persistence.team_executions.create_active(other)
96
+ stores.multi_agent.team_executions.create_active(other)
97
97
  ids = [completed.team_execution_id, other.team_execution_id].sort
98
- expect(persistence.team_executions.list(coordination_team.team_id, limit: 1).map(&:team_execution_id)).to eq(ids.first(1))
99
- expect(persistence.team_executions.list(coordination_team.team_id, after: ids.first).map(&:team_execution_id)).to eq(ids.last(1))
100
- expect(persistence.team_executions.list("other-team")).to be_empty
98
+ expect(stores.multi_agent.team_executions.list(coordination_team.team_id, limit: 1).map(&:team_execution_id)).to eq(ids.first(1))
99
+ expect(stores.multi_agent.team_executions.list(coordination_team.team_id, after: ids.first).map(&:team_execution_id)).to eq(ids.last(1))
100
+ expect(stores.multi_agent.team_executions.list("other-team")).to be_empty
101
101
  expect do
102
- persistence.team_executions.save(completed.team_execution_id, expected_revision: 0, execution: completed)
103
- end.to raise_error(Phronomy::Storage::ConflictError)
102
+ stores.multi_agent.team_executions.save(completed.team_execution_id, expected_revision: 0, execution: completed)
103
+ end.to raise_error(Phronomy::Persistence::ConflictError)
104
104
  expect do
105
- persistence.team_executions.save(completed.team_execution_id, expected_revision: 1,
105
+ stores.multi_agent.team_executions.save(completed.team_execution_id, expected_revision: 1,
106
106
  execution: completed.with(status: "active", phase: "coordinator"))
107
- end.to raise_error(Phronomy::Storage::ConflictError)
107
+ end.to raise_error(Phronomy::Persistence::ConflictError)
108
108
  end
109
109
 
110
110
  it "rolls the new three repositories back with the existing transaction domain" do
@@ -112,26 +112,32 @@ RSpec.shared_examples "a Team execution repository" do
112
112
  root_id = "coordination-rollback-agent-#{SecureRandom.uuid}"
113
113
  content_ref = nil
114
114
  expect do
115
- persistence.transaction do |tx|
116
- tx.team_executions.create_active(coordination_run)
117
- tx.teams.save(coordination_team.team_id, expected_revision: 0, root: coordination_team.with(lifecycle_status: "active"))
118
- tx.handoff_states.save(handoff.main_agent_id, expected_revision: nil, state: handoff)
119
- content_ref = tx.contents.put_text("coordination-rollback-#{SecureRandom.uuid}")
120
- root = Phronomy::Agent::AgentRoot.create(agent_id: root_id, agent_definition_id: "rollback", agent_definition_version: 1)
121
- tx.agents.create(root)
122
- tx.journals.append(root_id, expected_position: 0, records: [])
123
- record = Phronomy::Agent::JournalRecord.new(agent_id: root_id, kind: :input_received, channel: :external)
124
- tx.executions.create_active(Phronomy::Agent::AgentExecution.start(agent_root: root, input_record: record))
125
- tx.workflow_states.save(root_id, expected_revision: nil, snapshot: {fields: {value: "committed"}, phase: "pause"})
126
- raise "rollback all eight"
115
+ stores.coordinator.atomic do |scope|
116
+ stores.multi_agent.participate(scope) do |team|
117
+ stores.agent.participate(scope) do |agent|
118
+ scope.participate(persistence: stores.coordinator, adapter: Phronomy::Workflow::Persistence::StateRepository) do |workflow|
119
+ team.team_executions.create_active(coordination_run)
120
+ team.teams.save(coordination_team.team_id, expected_revision: 0, root: coordination_team.with(lifecycle_status: "active"))
121
+ team.handoff_states.save(handoff.main_agent_id, expected_revision: nil, state: handoff)
122
+ content_ref = agent.contents.put_text("coordination-rollback-#{SecureRandom.uuid}")
123
+ root = Phronomy::Agent::AgentRoot.create(agent_id: root_id, agent_definition_id: "rollback", agent_definition_version: 1)
124
+ agent.agents.create(root)
125
+ agent.journals.append(root_id, expected_position: 0, records: [])
126
+ record = Phronomy::Agent::JournalRecord.new(agent_id: root_id, kind: :input_received, channel: :external)
127
+ agent.executions.create_active(Phronomy::Agent::AgentExecution.start(agent_root: root, input_record: record))
128
+ workflow.save(root_id, expected_revision: nil, snapshot: {fields: {value: "committed"}, phase: "pause"})
129
+ raise "rollback all eight"
130
+ end
131
+ end
132
+ end
127
133
  end
128
134
  end.to raise_error(RuntimeError, "rollback all eight")
129
- expect(persistence.teams.load(coordination_team.team_id).team_revision).to eq(0)
130
- expect(persistence.handoff_states.load(handoff.main_agent_id)).to be_nil
131
- expect(persistence.team_executions.list(coordination_team.team_id)).to be_empty
132
- expect { persistence.agents.load(root_id) }.to raise_error(Phronomy::Storage::NotFoundError)
133
- expect(persistence.executions.list(root_id)).to be_empty
134
- expect(persistence.workflow_states.load(root_id)).to be_nil
135
- expect(persistence.contents.exist?(content_ref)).to be(false)
135
+ expect(stores.multi_agent.teams.load(coordination_team.team_id).team_revision).to eq(0)
136
+ expect(stores.multi_agent.handoff_states.load(handoff.main_agent_id)).to be_nil
137
+ expect(stores.multi_agent.team_executions.list(coordination_team.team_id)).to be_empty
138
+ expect { stores.agent.agents.load(root_id) }.to raise_error(Phronomy::Persistence::NotFoundError)
139
+ expect(stores.agent.executions.list(root_id)).to be_empty
140
+ expect(stores.workflow.load(root_id)).to be_nil
141
+ expect(stores.agent.contents.exist?(content_ref)).to be(false)
136
142
  end
137
143
  end
@@ -4,7 +4,7 @@ require "securerandom"
4
4
 
5
5
  # This same opaque-record suite runs against InMemory, SQLite and PostgreSQL.
6
6
  RSpec.shared_examples "neutral storage primitives" do
7
- let(:raw_backend) { persistence.backend }
7
+ let(:raw_backend) { stores.coordinator.backend }
8
8
  let(:raw_view) { raw_backend.view }
9
9
  let(:raw_schema) { Phronomy::Agent::Persistence::StorageSchema }
10
10
  let(:raw_record) { Phronomy::Storage::DurableRecord.new(record_type: "unrelated.envelope", format_version: "0.1", payload: {"status" => "ignored"}) }
@@ -9,7 +9,7 @@ RSpec.shared_examples "storage transaction boundaries" do
9
9
  view.is_a?(Phronomy::Storage::View) ? Phronomy::ContentStore::StoredContents.new(view) : view.contents
10
10
  end
11
11
 
12
- let(:transaction_service) { (entry_point == :backend) ? persistence.backend : persistence }
12
+ let(:transaction_service) { (entry_point == :backend) ? stores.coordinator.backend : stores.agent }
13
13
 
14
14
  it "rolls back a failed inner scope and re-raises the same error before the outer scope continues" do
15
15
  outer_id = inner_id = later_id = nil
@@ -19,39 +19,40 @@ RSpec.shared_examples "storage transaction boundaries" do
19
19
  result = transaction_service.transaction do |outer|
20
20
  outer_id = transaction_contents(outer).put_text("outer-#{workflow_id}")
21
21
  expect do
22
- transaction_service.transaction do |inner|
22
+ transaction_service.transaction do |inner, scope|
23
23
  inner_id = transaction_contents(inner).put_text("inner-#{workflow_id}")
24
24
  if entry_point == :backend
25
- inner.records(Phronomy::WorkflowStorageSchema::STATES).insert(key: workflow_id,
25
+ inner.records(Phronomy::Workflow::Persistence::StorageSchema::STATES).insert(key: workflow_id,
26
26
  revision: 1, attributes: {}, record: Phronomy::Storage::DurableRecord.new(
27
27
  record_type: "opaque.transaction", format_version: "0.1", payload: {"value" => "inner"}
28
28
  ))
29
29
  else
30
- inner.workflow_states.save(workflow_id,
31
- expected_revision: nil, snapshot: {fields: {value: "inner"}, phase: "pause"})
30
+ scope.participate(persistence: stores.coordinator, adapter: Phronomy::Workflow::Persistence::StateRepository) do |workflow|
31
+ workflow.save(workflow_id, expected_revision: nil, snapshot: {fields: {value: "inner"}, phase: "pause"})
32
+ end
32
33
  end
33
34
  raise failure
34
35
  end
35
36
  end.to raise_error { |error| expect(error).to equal(failure) }
36
37
  expect(transaction_contents(outer).exist?(outer_id)).to be(true)
37
38
  expect(transaction_contents(outer).exist?(inner_id)).to be(false)
38
- expect(persistence.backend.view.records(Phronomy::WorkflowStorageSchema::STATES).read(workflow_id)).to be_nil
39
+ expect(stores.coordinator.backend.view.records(Phronomy::Workflow::Persistence::StorageSchema::STATES).read(workflow_id)).to be_nil
39
40
  later_id = transaction_contents(outer).put_text("later-#{workflow_id}")
40
41
  :outer_result
41
42
  end
42
43
 
43
44
  expect(result).to eq(:outer_result)
44
- expect(persistence.contents.exist?(outer_id)).to be(true)
45
- expect(persistence.contents.exist?(later_id)).to be(true)
46
- expect(persistence.contents.exist?(inner_id)).to be(false)
47
- expect(persistence.backend.view.records(Phronomy::WorkflowStorageSchema::STATES).read(workflow_id)).to be_nil
45
+ expect(stores.agent.contents.exist?(outer_id)).to be(true)
46
+ expect(stores.agent.contents.exist?(later_id)).to be(true)
47
+ expect(stores.agent.contents.exist?(inner_id)).to be(false)
48
+ expect(stores.coordinator.backend.view.records(Phronomy::Workflow::Persistence::StorageSchema::STATES).read(workflow_id)).to be_nil
48
49
  end
49
50
 
50
51
  it "commits both scopes on normal completion and returns their block values" do
51
52
  outer_id = inner_id = nil
52
53
  result = transaction_service.transaction do |outer|
53
54
  outer_id = transaction_contents(outer).put_text("outer-#{SecureRandom.uuid}")
54
- inner_result = transaction_service.transaction do |inner|
55
+ inner_result = transaction_service.transaction do |inner, scope|
55
56
  inner_id = transaction_contents(inner).put_text("inner-#{SecureRandom.uuid}")
56
57
  :inner_result
57
58
  end
@@ -59,8 +60,8 @@ RSpec.shared_examples "storage transaction boundaries" do
59
60
  :outer_result
60
61
  end
61
62
  expect(result).to eq(:outer_result)
62
- expect(persistence.contents.exist?(outer_id)).to be(true)
63
- expect(persistence.contents.exist?(inner_id)).to be(true)
63
+ expect(stores.agent.contents.exist?(outer_id)).to be(true)
64
+ expect(stores.agent.contents.exist?(inner_id)).to be(true)
64
65
  end
65
66
 
66
67
  it "rolls back successful inner writes when the outer scope fails" do
@@ -68,7 +69,7 @@ RSpec.shared_examples "storage transaction boundaries" do
68
69
  expect do
69
70
  transaction_service.transaction do |outer|
70
71
  outer_id = transaction_contents(outer).put_text("outer-#{SecureRandom.uuid}")
71
- result = transaction_service.transaction do |inner|
72
+ result = transaction_service.transaction do |inner, scope|
72
73
  inner_id = transaction_contents(inner).put_text("inner-#{SecureRandom.uuid}")
73
74
  :inner_result
74
75
  end
@@ -77,8 +78,8 @@ RSpec.shared_examples "storage transaction boundaries" do
77
78
  raise "outer failure"
78
79
  end
79
80
  end.to raise_error("outer failure")
80
- expect(persistence.contents.exist?(outer_id)).to be(false)
81
- expect(persistence.contents.exist?(inner_id)).to be(false)
81
+ expect(stores.agent.contents.exist?(outer_id)).to be(false)
82
+ expect(stores.agent.contents.exist?(inner_id)).to be(false)
82
83
  end
83
84
 
84
85
  it "rolls back both scopes when an inner error escapes the outer scope" do
@@ -86,20 +87,20 @@ RSpec.shared_examples "storage transaction boundaries" do
86
87
  expect do
87
88
  transaction_service.transaction do |outer|
88
89
  outer_id = transaction_contents(outer).put_text("outer-#{SecureRandom.uuid}")
89
- transaction_service.transaction do |inner|
90
+ transaction_service.transaction do |inner, scope|
90
91
  inner_id = transaction_contents(inner).put_text("inner-#{SecureRandom.uuid}")
91
92
  raise "uncaught inner failure"
92
93
  end
93
94
  end
94
95
  end.to raise_error("uncaught inner failure")
95
- expect(persistence.contents.exist?(outer_id)).to be(false)
96
- expect(persistence.contents.exist?(inner_id)).to be(false)
96
+ expect(stores.agent.contents.exist?(outer_id)).to be(false)
97
+ expect(stores.agent.contents.exist?(inner_id)).to be(false)
97
98
  end
98
99
  end
99
100
  end
100
101
 
101
102
  it "validates every stream entry before writing any part of a batch" do
102
- backend = persistence.backend
103
+ backend = stores.coordinator.backend
103
104
  schema = Phronomy::Agent::Persistence::StorageSchema
104
105
  owner_id = "batch-#{SecureRandom.uuid}"
105
106
  record = Phronomy::Storage::DurableRecord.new(record_type: "opaque.transaction", format_version: "0.1", payload: {})
@@ -107,14 +108,14 @@ RSpec.shared_examples "storage transaction boundaries" do
107
108
  valid = Phronomy::Storage::Entry::Append.new(id: "first", record: record)
108
109
  content_id = nil
109
110
  backend.transaction do |view|
110
- content_id = persistence.contents.put_text("retained-#{owner_id}")
111
+ content_id = stores.agent.contents.put_text("retained-#{owner_id}")
111
112
  stream = view.streams(schema::JOURNAL)
112
113
  expect { stream.append(stream: owner_id, expected_head: 0, entries: [valid, Object.new]) }
113
114
  .to raise_error(Phronomy::Storage::SerializationError)
114
115
  expect(stream.head(stream: owner_id)).to eq(0)
115
116
  expect(stream.read(stream: owner_id)).to be_empty
116
117
  end
117
- expect(persistence.contents.exist?(content_id)).to be(true)
118
+ expect(stores.agent.contents.exist?(content_id)).to be(true)
118
119
  entries = backend.view.streams(schema::JOURNAL).append(stream: owner_id, expected_head: 0,
119
120
  entries: [valid, Phronomy::Storage::Entry::Append.new(id: "second", record: record)])
120
121
  expect(entries.map(&:position)).to eq([1, 2])
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Tool
5
+ # Common authorization framework. Requests implement the Tool request protocol;
6
+ # callers attach their own immutable identity context, never a live owner.
7
+ module Authorization
8
+ VALID_DECISIONS = %i[allow require_approval reject].freeze
9
+ Decision = Data.define(:decision, :facts, :reason)
10
+
11
+ def self.call(request:, arguments:, context:, facts: nil, requirement: false, policy: nil)
12
+ value = facts&.call(arguments, context)
13
+ unless value.nil? || value.is_a?(Hash)
14
+ raise Phronomy::ConfigurationError, "approval_facts must return a Hash or nil (got #{value.class})"
15
+ end
16
+ value = snapshot(value || {})
17
+ request = request.with(facts: value)
18
+ required = requirement.respond_to?(:call) ? requirement.call(request) : requirement
19
+ default = case required
20
+ when true then :require_approval
21
+ when false, nil then :allow
22
+ else
23
+ raise Phronomy::ConfigurationError,
24
+ "requires_approval callable must return true or false (got #{required.inspect})"
25
+ end
26
+ request = request.with(default_decision: default)
27
+ decision = policy ? policy.call(request) : default
28
+ decision = decision.to_sym if decision.respond_to?(:to_sym)
29
+ unless VALID_DECISIONS.include?(decision)
30
+ raise Phronomy::ConfigurationError,
31
+ "tool_approval_policy must return :allow, :require_approval, or :reject (got #{decision.inspect})"
32
+ end
33
+ reason = if decision == :require_approval
34
+ (request.origin == :mcp) ? "MCP Tool execution requires approval" : "Tool execution requires approval"
35
+ end
36
+ Decision.new(decision: decision, facts: value, reason: reason)
37
+ end
38
+
39
+ def self.snapshot(value)
40
+ if phronomy_managed_live_domain_object?(value)
41
+ raise Phronomy::ConfigurationError,
42
+ "authorization worker snapshot cannot contain Phronomy-managed live " \
43
+ "domain object #{value.class}"
44
+ end
45
+
46
+ case value
47
+ when Hash
48
+ value.each_with_object({}) do |(key, item), result|
49
+ result[snapshot(key)] = snapshot(item)
50
+ end.freeze
51
+ when Array
52
+ value.map { |item| snapshot(item) }.freeze
53
+ when String
54
+ value.dup.freeze
55
+ else
56
+ # Application-defined opaque objects are permitted by ACS-11 and remain
57
+ # Application-owned. A stricter general value-type protocol is deferred.
58
+ value
59
+ end
60
+ end
61
+
62
+ def self.phronomy_managed_live_domain_object?(value)
63
+ value.is_a?(Phronomy::Concurrency::WorkerInputRestricted)
64
+ end
65
+ private_class_method :phronomy_managed_live_domain_object?
66
+
67
+ def self.behavior(value, name)
68
+ return value if value.nil? || value == true || value == false
69
+ if phronomy_managed_live_domain_object?(value)
70
+ raise Phronomy::ConfigurationError,
71
+ "#{name} must not be a Phronomy-managed live domain object (got #{value.class})"
72
+ end
73
+ value
74
+ end
75
+ end
76
+ end
77
+ end