phronomy 0.27.0 → 0.29.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 (517) 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 +28 -7
  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/application-recipes.md +229 -0
  11. data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
  12. data/docs/architecture/agent-transition-ownership.md +13 -89
  13. data/docs/architecture/backend-groups.md +152 -0
  14. data/docs/architecture/before-llm-input.md +3 -3
  15. data/docs/architecture/context-management.md +1 -1
  16. data/docs/architecture/dx05-agent-scope.md +119 -0
  17. data/docs/architecture/dx05-workflow-task-state.md +134 -0
  18. data/docs/architecture/error-api-review.md +245 -0
  19. data/docs/architecture/execution-abstraction-review.md +88 -0
  20. data/docs/architecture/execution-boundaries.md +28 -0
  21. data/docs/architecture/execution-metadata-and-values.md +6 -0
  22. data/docs/architecture/multi-agent-handoff.md +6 -5
  23. data/docs/architecture/persistence.md +27 -9
  24. data/docs/architecture/r8-unit1.md +104 -0
  25. data/docs/architecture/r8-unit10.md +72 -0
  26. data/docs/architecture/r8-unit11.md +71 -0
  27. data/docs/architecture/r8-unit12.md +67 -0
  28. data/docs/architecture/r8-unit13.md +61 -0
  29. data/docs/architecture/r8-unit14.md +72 -0
  30. data/docs/architecture/r8-unit15.md +65 -0
  31. data/docs/architecture/r8-unit16.md +54 -0
  32. data/docs/architecture/r8-unit17.md +73 -0
  33. data/docs/architecture/r8-unit18.md +92 -0
  34. data/docs/architecture/r8-unit19.md +61 -0
  35. data/docs/architecture/r8-unit2.md +55 -0
  36. data/docs/architecture/r8-unit20.md +51 -0
  37. data/docs/architecture/r8-unit21.md +79 -0
  38. data/docs/architecture/r8-unit22.md +74 -0
  39. data/docs/architecture/r8-unit23.md +81 -0
  40. data/docs/architecture/r8-unit3.md +130 -0
  41. data/docs/architecture/r8-unit4.md +79 -0
  42. data/docs/architecture/r8-unit5.md +115 -0
  43. data/docs/architecture/r8-unit6.md +71 -0
  44. data/docs/architecture/r8-unit7.md +70 -0
  45. data/docs/architecture/r8-unit8.md +78 -0
  46. data/docs/architecture/r8-unit9.md +73 -0
  47. data/docs/architecture/refactoring-closure.md +10 -1
  48. data/docs/architecture/remaining-refactoring-plan.md +16 -0
  49. data/docs/architecture/storage-execution-boundaries.md +54 -0
  50. data/docs/architecture/tool-schema-recording-gap.md +6 -0
  51. data/docs/architecture/tracing.md +1 -1
  52. data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
  53. data/docs/architecture.md +24 -11
  54. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  55. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  56. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
  57. data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
  58. data/docs/decisions/032-storage-backend-composition.md +5 -0
  59. data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
  60. data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
  61. data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
  62. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
  63. data/docs/decisions/040-configuration-default-composition.md +12 -1
  64. data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
  65. data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
  66. data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
  67. data/docs/decisions/058-neutral-storage-primitives.md +5 -0
  68. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  69. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  70. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  71. data/docs/decisions/062-content-store-service.md +52 -0
  72. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  73. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  74. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  75. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  76. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  77. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  78. data/docs/decisions/README.md +18 -1
  79. data/docs/execution-boundaries-preview.md +45 -0
  80. data/docs/failure-reason-codes.md +45 -0
  81. data/docs/features.md +12 -4
  82. data/docs/getting-started.md +52 -48
  83. data/docs/migrations/0.22.md +2 -2
  84. data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
  85. data/docs/migrations/handoff-runner-multi-agent.md +2 -0
  86. data/docs/migrations/persistence-failure-contracts.md +67 -0
  87. data/docs/migrations/r8-unit10.md +36 -0
  88. data/docs/migrations/r8-unit11.md +33 -0
  89. data/docs/migrations/r8-unit12.md +31 -0
  90. data/docs/migrations/r8-unit13.md +36 -0
  91. data/docs/migrations/r8-unit14.md +31 -0
  92. data/docs/migrations/r8-unit15.md +31 -0
  93. data/docs/migrations/r8-unit16.md +26 -0
  94. data/docs/migrations/r8-unit17.md +28 -0
  95. data/docs/migrations/r8-unit18.md +29 -0
  96. data/docs/migrations/r8-unit19.md +28 -0
  97. data/docs/migrations/r8-unit20.md +24 -0
  98. data/docs/migrations/r8-unit21.md +28 -0
  99. data/docs/migrations/r8-unit22.md +29 -0
  100. data/docs/migrations/r8-unit23.md +20 -0
  101. data/docs/migrations/r8-unit4.md +67 -0
  102. data/docs/migrations/r8-unit5.md +75 -0
  103. data/docs/migrations/r8-unit6.md +109 -0
  104. data/docs/migrations/r8-unit7.md +31 -0
  105. data/docs/migrations/r8-unit8.md +33 -0
  106. data/docs/migrations/r8-unit9.md +28 -0
  107. data/docs/migrations/storage-async-client.md +103 -0
  108. data/docs/migrations/vector-async-clients.md +110 -0
  109. data/docs/ordered-event-delivery.md +54 -0
  110. data/docs/persistence-backends.md +38 -21
  111. data/docs/runtime-and-concurrency.md +26 -1
  112. data/docs/workflow-completion.md +92 -0
  113. data/gemfiles/http_minimum.gemfile +5 -0
  114. data/lib/phronomy/agent/admission.rb +78 -0
  115. data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
  116. data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
  117. data/lib/phronomy/agent/agent_not_found_error.rb +16 -0
  118. data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
  119. data/lib/phronomy/agent/agent_unloaded_error.rb +14 -0
  120. data/lib/phronomy/agent/api/agent.rb +13 -0
  121. data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
  122. data/lib/phronomy/agent/async_event_api.rb +132 -103
  123. data/lib/phronomy/agent/base.rb +146 -113
  124. data/lib/phronomy/agent/before_llm_input.rb +70 -0
  125. data/lib/phronomy/agent/composition/run_once.rb +1 -1
  126. data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
  127. data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
  128. data/lib/phronomy/agent/control_request.rb +21 -0
  129. data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
  130. data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
  131. data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
  132. data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
  133. data/lib/phronomy/agent/execution/exact_execution.rb +55 -32
  134. data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
  135. data/lib/phronomy/agent/execution/execution_coordinator.rb +229 -407
  136. data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
  137. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +71 -127
  138. data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
  139. data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
  140. data/lib/phronomy/agent/execution/initial_preparation.rb +19 -120
  141. data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
  142. data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
  143. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +8 -11
  144. data/lib/phronomy/agent/execution_change.rb +161 -0
  145. data/lib/phronomy/agent/execution_environment.rb +36 -0
  146. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  147. data/lib/phronomy/agent/execution_observation.rb +35 -0
  148. data/lib/phronomy/agent/filterable.rb +188 -0
  149. data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
  150. data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
  151. data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
  152. data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
  153. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  154. data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
  155. data/lib/phronomy/agent/lifecycle/lifetime_api.rb +76 -0
  156. data/lib/phronomy/agent/lifecycle/operation_lease.rb +33 -0
  157. data/lib/phronomy/agent/lifecycle/operation_result.rb +53 -0
  158. data/lib/phronomy/agent/lifecycle/ownership_registry.rb +117 -31
  159. data/lib/phronomy/agent/lifecycle/scope.rb +198 -0
  160. data/lib/phronomy/agent/lifecycle/scope_cleanup_error.rb +19 -0
  161. data/lib/phronomy/agent/lifecycle/unload_transition.rb +78 -0
  162. data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
  163. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  164. data/lib/phronomy/agent/persistence/codec.rb +1 -21
  165. data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
  166. data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
  167. data/lib/phronomy/agent/persistence/records.rb +33 -0
  168. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  169. data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
  170. data/lib/phronomy/agent/persistence/watermark.rb +13 -11
  171. data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
  172. data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +13 -12
  173. data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +34 -34
  174. data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +33 -39
  175. data/lib/phronomy/agent/recovery/recovery_coordinator.rb +28 -25
  176. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  177. data/lib/phronomy/agent/recovery/recovery_support.rb +10 -19
  178. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  179. data/lib/phronomy/agent/retention.rb +17 -0
  180. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  181. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  182. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  183. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  184. data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
  185. data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
  186. data/lib/phronomy/agent/settings.rb +47 -0
  187. data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
  188. data/lib/phronomy/agent/store.rb +253 -0
  189. data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +3 -3
  190. data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
  191. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
  192. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  193. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  194. data/lib/phronomy/common/error.rb +65 -1
  195. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  196. data/lib/phronomy/content_store/base.rb +7 -2
  197. data/lib/phronomy/content_store/storage_schema.rb +1 -0
  198. data/lib/phronomy/content_store/stored_contents.rb +2 -1
  199. data/lib/phronomy/context/assembly.rb +195 -0
  200. data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
  201. data/lib/phronomy/context/candidate.rb +51 -0
  202. data/lib/phronomy/context/constraint.rb +47 -0
  203. data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
  204. data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
  205. data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
  206. data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
  207. data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
  208. data/lib/phronomy/context/default_policy.rb +140 -0
  209. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  210. data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
  211. data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
  212. data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
  213. data/lib/phronomy/context/manifest_error.rb +10 -0
  214. data/lib/phronomy/context/prompt_template.rb +98 -0
  215. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  216. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  217. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  218. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  219. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  220. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  221. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  222. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  223. data/lib/phronomy/embeddings/authentication_error.rb +11 -0
  224. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +52 -0
  225. data/lib/phronomy/embeddings/base.rb +43 -0
  226. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  227. data/lib/phronomy/embeddings/rate_limit_error.rb +11 -0
  228. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  229. data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
  230. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  231. data/lib/phronomy/engine/diagnostics.rb +1 -1
  232. data/lib/phronomy/engine/event_loop.rb +7 -7
  233. data/lib/phronomy/engine/execution_binding.rb +23 -0
  234. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  235. data/lib/phronomy/engine/fsm_protocol.rb +1 -6
  236. data/lib/phronomy/engine/fsm_session.rb +3 -3
  237. data/lib/phronomy/engine/metrics.rb +16 -14
  238. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  239. data/lib/phronomy/engine/runtime.rb +25 -12
  240. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  241. data/lib/phronomy/execution/async_operation.rb +41 -0
  242. data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
  243. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
  244. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  245. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  246. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  247. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  248. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  249. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  250. data/lib/phronomy/{engine → execution}/execution.rb +61 -27
  251. data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +2 -2
  252. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  253. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  254. data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +2 -2
  255. data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
  256. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  257. data/lib/phronomy/execution/runnable.rb +24 -0
  258. data/lib/phronomy/{engine → execution}/task_result.rb +9 -4
  259. data/lib/phronomy/execution/wait_policy.rb +24 -0
  260. data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +3 -3
  261. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  262. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  263. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
  264. data/lib/phronomy/generation/generator_verifier.rb +7 -7
  265. data/lib/phronomy/generation/low_confidence_error.rb +2 -2
  266. data/lib/phronomy/integrations/ordered_event_delivery.rb +208 -0
  267. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  268. data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
  269. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  270. data/lib/phronomy/llm_adapter/base.rb +49 -68
  271. data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
  272. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  273. data/lib/phronomy/llm_adapter/message.rb +62 -0
  274. data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
  275. data/lib/phronomy/llm_adapter/request.rb +79 -0
  276. data/lib/phronomy/llm_adapter/response.rb +36 -0
  277. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  278. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  279. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  280. data/lib/phronomy/llm_adapter.rb +3 -16
  281. data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
  282. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  283. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  284. data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
  285. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
  286. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  287. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
  288. data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
  289. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
  290. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  291. data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
  292. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  293. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  294. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  295. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +60 -44
  296. data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
  297. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  298. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  299. data/lib/phronomy/multi_agent/shared_state.rb +2 -2
  300. data/lib/phronomy/multi_agent/store.rb +156 -0
  301. data/lib/phronomy/multi_agent/team_busy_error.rb +18 -0
  302. data/lib/phronomy/multi_agent/team_coordinator.rb +99 -77
  303. data/lib/phronomy/multi_agent/team_not_found_error.rb +18 -0
  304. data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
  305. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  306. data/lib/phronomy/persistence/content_repository.rb +26 -0
  307. data/lib/phronomy/persistence/errors.rb +16 -0
  308. data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
  309. data/lib/phronomy/persistence/persistence.rb +23 -0
  310. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  311. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  312. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  313. data/lib/phronomy/persistence/transaction.rb +86 -0
  314. data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
  315. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  316. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  317. data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
  318. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  319. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  320. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  321. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  322. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  323. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  324. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  325. data/lib/phronomy/storage/async/async_client.rb +60 -0
  326. data/lib/phronomy/storage/backend.rb +8 -2
  327. data/lib/phronomy/storage/condition_failed_error.rb +2 -2
  328. data/lib/phronomy/storage/unique_constraint_error.rb +2 -2
  329. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  330. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  331. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
  332. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
  333. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
  334. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
  335. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
  336. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
  337. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
  338. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
  339. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
  340. data/lib/phronomy/tool/authorization.rb +77 -0
  341. data/lib/phronomy/tool/base.rb +551 -9
  342. data/lib/phronomy/tool/call_request.rb +40 -0
  343. data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
  344. data/lib/phronomy/tool/operation.rb +88 -0
  345. data/lib/phronomy/tool/schema.rb +214 -0
  346. data/lib/phronomy/tool/settings.rb +31 -0
  347. data/lib/phronomy/tool/tool_error.rb +7 -0
  348. data/lib/phronomy/tool/tool_executor.rb +58 -0
  349. data/lib/phronomy/tools/agent.rb +22 -90
  350. data/lib/phronomy/tools/mcp.rb +21 -14
  351. data/lib/phronomy/tools/vector_search.rb +4 -4
  352. data/lib/phronomy/tracing/automatic.rb +11 -6
  353. data/lib/phronomy/tracing/base.rb +36 -7
  354. data/lib/phronomy/tracing/observation.rb +37 -0
  355. data/lib/phronomy/tracing/open_telemetry_tracer.rb +4 -29
  356. data/lib/phronomy/tracing/settings.rb +36 -0
  357. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  358. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  359. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  360. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  361. data/lib/phronomy/vector_store/base.rb +118 -49
  362. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  363. data/lib/phronomy/vector_store.rb +2 -17
  364. data/lib/phronomy/version.rb +1 -1
  365. data/lib/phronomy/workflow/completion.rb +11 -0
  366. data/lib/phronomy/workflow/execution/workflow.rb +37 -9
  367. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  368. data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
  369. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +28 -0
  370. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +77 -8
  371. data/lib/phronomy/workflow/execution/workflow_runner.rb +105 -92
  372. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  373. data/lib/phronomy/workflow/execution/workflow_task_execution.rb +153 -0
  374. data/lib/phronomy/workflow/execution/workflow_task_reception.rb +72 -0
  375. data/lib/phronomy/workflow/execution/workflow_task_resume_error.rb +14 -0
  376. data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
  377. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  378. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +61 -0
  379. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +158 -0
  380. data/lib/phronomy/workflow/runtime_binding/workflow_task_session.rb +76 -0
  381. data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
  382. data/lib/phronomy.rb +63 -16
  383. data/scripts/api_snapshot.rb +47 -15
  384. data/scripts/check_readme_ruby.rb +24 -29
  385. data/scripts/check_readme_runnable.rb +92 -81
  386. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  387. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  388. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  389. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  390. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  391. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  392. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  393. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  394. data/sig/phronomy/agent/admission.rbs +19 -0
  395. data/sig/phronomy/agent/agent_not_found_error.rbs +6 -0
  396. data/sig/phronomy/agent/agent_unloaded_error.rbs +6 -0
  397. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  398. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  399. data/sig/phronomy/agent/store.rbs +73 -0
  400. data/sig/phronomy/agent/stream_callback_error.rbs +8 -0
  401. data/sig/phronomy/agent.rbs +29 -135
  402. data/sig/phronomy/common.rbs +7 -0
  403. data/sig/phronomy/content_store/contracts.rbs +13 -0
  404. data/sig/phronomy/context.rbs +152 -0
  405. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  406. data/sig/phronomy/embeddings/base.rbs +23 -0
  407. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  408. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  409. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  410. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  411. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  412. data/sig/phronomy/engine/observability.rbs +13 -0
  413. data/sig/phronomy/execution/async_operation.rbs +7 -0
  414. data/sig/phronomy/execution/blocking.rbs +5 -0
  415. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  416. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  417. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  418. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  419. data/sig/phronomy/execution/errors.rbs +7 -0
  420. data/sig/phronomy/execution/execution.rbs +41 -0
  421. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  422. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  423. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  424. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  425. data/sig/phronomy/execution/task_result.rbs +29 -0
  426. data/sig/phronomy/extensions.rbs +1 -21
  427. data/sig/phronomy/filter/filter_block_error.rbs +6 -0
  428. data/sig/phronomy/generation/low_confidence_error.rbs +6 -0
  429. data/sig/phronomy/handoff.rbs +10 -4
  430. data/sig/phronomy/integrations.rbs +14 -0
  431. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  432. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  433. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  434. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  435. data/sig/phronomy/llm_context_window.rbs +10 -0
  436. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  437. data/sig/phronomy/multi_agent/store.rbs +54 -0
  438. data/sig/phronomy/multi_agent/team_busy_error.rbs +8 -0
  439. data/sig/phronomy/multi_agent/team_not_found_error.rbs +8 -0
  440. data/sig/phronomy/multi_agent.rbs +6 -3
  441. data/sig/phronomy/persistence/errors.rbs +18 -0
  442. data/sig/phronomy/persistence/migration.rbs +13 -0
  443. data/sig/phronomy/persistence/transaction.rbs +19 -0
  444. data/sig/phronomy/persistence.rbs +1 -99
  445. data/sig/phronomy/persistence_composition.rbs +19 -0
  446. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  447. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  448. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  449. data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +4 -18
  450. data/sig/phronomy/tool.rbs +48 -4
  451. data/sig/phronomy/tracing/observation.rbs +7 -0
  452. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  453. data/sig/phronomy/vector_store/base.rbs +23 -0
  454. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +15 -0
  455. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  456. data/sig/phronomy/workflow/store.rbs +8 -0
  457. data/sig/phronomy/workflow.rbs +5 -1
  458. data/sig/phronomy.rbs +4 -49
  459. metadata +365 -117
  460. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  461. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  462. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  463. data/lib/phronomy/agent/context/capability/base.rb +0 -548
  464. data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
  465. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  466. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
  467. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
  468. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  469. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  470. data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
  471. data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
  472. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
  473. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
  474. data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
  475. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
  476. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
  477. data/lib/phronomy/agent/persistence/queries.rb +0 -61
  478. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  479. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  480. data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
  481. data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
  482. data/lib/phronomy/configuration/configuration.rb +0 -68
  483. data/lib/phronomy/configuration/global_configuration.rb +0 -26
  484. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  485. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  486. data/lib/phronomy/engine/execution_receiver.rb +0 -65
  487. data/lib/phronomy/engine/runnable.rb +0 -54
  488. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  489. data/lib/phronomy/llm_contract/token_usage.rb +0 -85
  490. data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
  491. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
  492. data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
  493. data/lib/phronomy/persistence/api/persistence.rb +0 -140
  494. data/lib/phronomy/persistence_composition/repositories.rb +0 -77
  495. data/lib/phronomy/recovery/recovery.rb +0 -186
  496. data/lib/phronomy/tool/contract/tool_error.rb +0 -7
  497. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  498. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  499. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  500. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
  501. data/sig/phronomy/execution_receiver.rbs +0 -34
  502. data/sig/phronomy/llm_adapter.rbs +0 -11
  503. data/sig/phronomy/runtime.rbs +0 -76
  504. /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
  505. /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
  506. /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
  507. /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
  508. /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
  509. /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
  510. /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
  511. /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
  512. /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
  513. /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
  514. /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
  515. /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
  516. /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
  517. /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
@@ -11,12 +11,27 @@ module Phronomy
11
11
  # Scheduling and aggregation must be pure, replay-safe result calculations.
12
12
  # @api public
13
13
  class TeamCoordinator
14
- WorkerState = Data.define(:index, :agent_id, :transcript_size, :status) do
14
+ WorkerState = Data.define(:index, :agent_id, :assignment_count, :status) do
15
15
  def available? = status == :available
16
16
  end
17
17
  private_constant :WorkerState
18
18
 
19
+ # Raised before writing an operation; update still verifies readback.
20
+ OperationRejected = Class.new(Phronomy::ConfigurationError)
21
+ private_constant :OperationRejected
22
+
19
23
  class << self
24
+ # Boot composition supplies a fresh store without selecting it here.
25
+ # @api private
26
+ def install_persistence_factory(factory)
27
+ raise ArgumentError, "factory must respond to call" unless factory.respond_to?(:call)
28
+ raise Phronomy::ConfigurationError, "Team persistence factory already installed" if @persistence_factory
29
+ @persistence_factory = factory
30
+ end
31
+
32
+ # @api private
33
+ def build_default_persistence = @persistence_factory.call
34
+
20
35
  # @api public
21
36
  def team_definition(id: nil, version: nil)
22
37
  if id || version
@@ -69,7 +84,7 @@ module Phronomy
69
84
 
70
85
  # @api public
71
86
  def get(team_id)
72
- TeamOwnershipRegistry.existing_for(Phronomy::Runtime.instance)&.get(team_id.to_s, klass: self)
87
+ ExecutionEnvironment.current.existing_ownership&.get(team_id.to_s, klass: self)
73
88
  end
74
89
 
75
90
  # @api private
@@ -92,14 +107,14 @@ module Phronomy
92
107
  private
93
108
 
94
109
  def construct(id, persistence, metadata, listener, create:)
95
- raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::Runtime.in_event_loop_context?
110
+ raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::WaitPolicy.blocking_forbidden?
96
111
  key = id.to_s
97
112
  raise ArgumentError, "team_id must not be empty" if key.empty?
98
- store = persistence || Phronomy.configuration.persistence || Phronomy::Persistence.in_memory
99
- runtime = Phronomy::Runtime.instance
100
- TeamOwnershipRegistry.for(runtime).fetch(key, klass: self, create: create, persistence: store) do
113
+ store = persistence || Phronomy::MultiAgent::Store.configured || TeamCoordinator.build_default_persistence
114
+ environment = ExecutionEnvironment.current
115
+ environment.ownership.fetch(key, klass: self, create: create, persistence: store) do
101
116
  instance = allocate
102
- instance.send(:initialize, key.freeze, store, metadata, listener, runtime, create: create)
117
+ instance.send(:initialize, key.freeze, store, metadata, listener, environment, create: create)
103
118
  instance
104
119
  end
105
120
  end
@@ -134,12 +149,12 @@ module Phronomy
134
149
  # @api public
135
150
  def result(team_execution_id)
136
151
  execution = read_execution(team_execution_id)
137
- persistence.team_execution_result(execution.team_execution_id)
152
+ persistence.result(execution.team_execution_id)
138
153
  end
139
154
 
140
155
  # @api public
141
156
  def executions(after: nil, limit: 100)
142
- persistence.list_team_executions(team_id, after: after, limit: limit)
157
+ persistence.runs(team_id, after: after, limit: limit)
143
158
  end
144
159
 
145
160
  # Requests cancellation of this run only. Existing Agent cancellation and
@@ -159,9 +174,9 @@ module Phronomy
159
174
 
160
175
  private
161
176
 
162
- def initialize(id, store, metadata, listener, runtime, create:)
163
- @team_id, @persistence, @listener, @runtime = id, store, listener, runtime
164
- @admissions = AdmissionRegistry.for(@runtime)
177
+ def initialize(id, store, metadata, listener, environment, create:)
178
+ @team_id, @persistence, @listener, @environment = id, store, listener, environment
179
+ @admissions = @environment.admissions
165
180
  @tokens_mutex, @tokens = Mutex.new, {}
166
181
  @coordinator_classes = {}
167
182
  definition = self.class.team_definition
@@ -173,19 +188,30 @@ module Phronomy
173
188
  begin
174
189
  store.transaction { |tx| tx.teams.create(intended) }
175
190
  rescue => error
176
- confirmed = store.teams.load(id)
191
+ begin
192
+ confirmed = store.teams.load(id)
193
+ rescue
194
+ # A failed readback cannot turn an unknown create outcome into
195
+ # a claim that the Team is absent or safe to create again.
196
+ raise error, cause: error.cause
197
+ end
177
198
  raise error unless confirmed.to_h == intended.to_h
178
199
  end
179
200
  end
180
- root = store.teams.load(id)
201
+ begin
202
+ root = store.teams.load(id)
203
+ rescue Phronomy::Persistence::NotFoundError => error
204
+ raise if create || error.is_a?(TeamNotFoundError)
205
+ raise TeamNotFoundError.new(team_id: id), cause: error
206
+ end
181
207
  unless root.team_definition_id == definition.fetch(:id) && root.team_definition_version == definition.fetch(:version)
182
208
  raise Phronomy::ConfigurationError, "Team #{id} definition mismatch"
183
209
  end
184
210
  end
185
211
 
186
212
  def assert_caller!
187
- raise Phronomy::EventLoopReentrancyError, "Team operation cannot block EventLoop" if Phronomy::Runtime.in_event_loop_context?
188
- unless @runtime.equal?(Phronomy::Runtime.instance)
213
+ raise Phronomy::EventLoopReentrancyError, "Team operation cannot block EventLoop" if Phronomy::WaitPolicy.blocking_forbidden?
214
+ unless @environment.current?
189
215
  raise Phronomy::RuntimeShutdownError, "Team #{team_id} belongs to a previous Runtime"
190
216
  end
191
217
  end
@@ -202,7 +228,7 @@ module Phronomy
202
228
  def read_execution(id)
203
229
  execution = persistence.team_executions.load(id)
204
230
  unless execution.team_id == team_id
205
- raise Phronomy::Storage::ConflictError, "Team execution #{id} belongs to another Team"
231
+ raise Phronomy::Persistence::StateConflictError, "Team execution #{id} belongs to another Team"
206
232
  end
207
233
  execution
208
234
  end
@@ -221,13 +247,13 @@ module Phronomy
221
247
  now = Time.now.utc.iso8601(6)
222
248
  intended = nil
223
249
  begin
224
- persistence.transaction do |tx|
250
+ persistence.transaction do |tx, scope|
225
251
  root = tx.teams.load(team_id)
226
- raise Phronomy::AgentBusyError, "Team #{team_id} has an active run; use resume" unless tx.team_executions.list_active(team_id).empty?
252
+ raise TeamBusyError.new("Team #{team_id} has an active run; use resume", team_id: team_id) unless tx.team_executions.list_active(team_id).empty?
227
253
  intended = TeamExecution.new(team_execution_id: id, team_id: team_id,
228
254
  execution_revision: 0, status: "active", phase: "coordinator", input_ref: tx.contents.put_text(input),
229
255
  coordinator: {"agent_id" => SecureRandom.uuid, "execution_id" => SecureRandom.uuid, "state" => "reserved"},
230
- workers: Array.new(self.class._pool_size) { |i| {"index" => i, "agent_id" => SecureRandom.uuid, "transcript_size" => 0} },
256
+ workers: Array.new(self.class._pool_size) { |i| {"index" => i, "agent_id" => SecureRandom.uuid} },
231
257
  tasks: [], assignments: [], result_ref: nil, error_ref: nil, created_at: now, updated_at: now,
232
258
  metadata: {"definition" => definition, "operations" => {}, "cancel_requested" => false,
233
259
  "durable_context_ref" => config.key?(:durable_context) ? tx.contents.put_json(config.fetch(:durable_context)) : nil})
@@ -238,7 +264,7 @@ module Phronomy
238
264
  raise error unless intended
239
265
  begin
240
266
  confirmed = read_execution(id)
241
- rescue Phronomy::Storage::NotFoundError
267
+ rescue Phronomy::Persistence::NotFoundError
242
268
  raise error
243
269
  end
244
270
  raise error unless confirmed.to_h == intended.to_h
@@ -252,10 +278,10 @@ module Phronomy
252
278
  attempts = 0
253
279
  begin
254
280
  intended = nil
255
- persistence.transaction do |tx|
281
+ persistence.transaction do |tx, scope|
256
282
  current = tx.team_executions.load(id)
257
- raise Phronomy::Storage::ConflictError, "Team execution owner mismatch" unless current.team_id == team_id
258
- intended = yield(current, tx)
283
+ raise Phronomy::Persistence::StateConflictError, "Team execution owner mismatch" unless current.team_id == team_id
284
+ intended = yield(current, tx, scope)
259
285
  next if intended.equal?(current)
260
286
  tx.team_executions.save(id, expected_revision: current.execution_revision, execution: intended)
261
287
  if intended.terminal? && current.active?
@@ -267,7 +293,7 @@ module Phronomy
267
293
  rescue => error
268
294
  confirmed = read_execution(id)
269
295
  return confirmed if intended && confirmed.to_h == intended.to_h
270
- if error.is_a?(Phronomy::Storage::ConflictError) && (attempts += 1) < 8
296
+ if error.is_a?(Phronomy::Persistence::ConflictError) && (attempts += 1) < 8
271
297
  retry
272
298
  end
273
299
  raise error
@@ -282,9 +308,9 @@ module Phronomy
282
308
  end
283
309
  token = Phronomy::Concurrency::CancellationToken.new
284
310
  @tokens_mutex.synchronize { @tokens[id] = token }
285
- external = config[:cancellation_token]
286
- callback = proc { @runtime.offload.submit(on_full: :raise) { cancel(id) } }
287
- external&.on_cancel(&callback)
311
+ cancellation_subscriptions = Phronomy::Concurrency::ResultSubscriptions.new
312
+ callback = proc { @environment.submit(on_full: :raise) { cancel(id) } }
313
+ cancellation_subscriptions.explicit_cancellation(config[:cancellation_token], &callback)
288
314
  token.cancel! if current.metadata["cancel_requested"]
289
315
  begin
290
316
  loop do
@@ -323,13 +349,13 @@ module Phronomy
323
349
  values = assignment_values(current)
324
350
  aggregate = self.class._aggregator
325
351
  begin
326
- output = Phronomy::Agent::RecoverySupport.canonical_copy(aggregate ? aggregate.call(values) : values)
352
+ output = Phronomy::Values::Serializable.convert(aggregate ? aggregate.call(values) : values, unsupported_message: "Team result is not canonically serializable")
327
353
  Phronomy::CanonicalJSON.dump(output)
328
354
  rescue => error
329
- return terminal_value(finish_error(id, Phronomy::Agent::RecoverySupport.resolution_failure(error)))
355
+ return terminal_value(finish_error(id, Phronomy::Error.diagnostic(error)))
330
356
  end
331
357
  # Aggregate may be replayed only until the canonical outcome commits.
332
- completed = update(id) do |fresh, tx|
358
+ completed = update(id) do |fresh, tx, scope|
333
359
  next fresh if fresh.terminal? || fresh.metadata["cancel_requested"]
334
360
  fresh.with(status: "completed", phase: "completed", result_ref: tx.contents.put_json(output))
335
361
  end
@@ -345,7 +371,7 @@ module Phronomy
345
371
  terminal_value(finish_error(id, {"class" => "Phronomy::CancellationError", "message" => "Team run cancelled"}, status: "cancelled"))
346
372
  end
347
373
  ensure
348
- external&.send(:unregister_cancel_callback, callback)
374
+ cancellation_subscriptions.close
349
375
  @tokens_mutex.synchronize { @tokens.delete(id) }
350
376
  end
351
377
  end
@@ -383,30 +409,31 @@ module Phronomy
383
409
  id = slot.fetch("agent_id")
384
410
  child = klass.get(id)
385
411
  unless child
386
- begin
387
- persistence.agents.load(id)
388
- present = true
389
- rescue Phronomy::Storage::NotFoundError
390
- present = false
391
- end
412
+ present = persistence.agent_store.exist?(id)
392
413
  token.raise_if_cancelled! unless present
393
- child = present ? klass.load(id, persistence: persistence, on_event: @listener) : klass.create(agent_id: id, persistence: persistence, on_event: @listener)
414
+ child = present ? klass.load(id, persistence: persistence.agent_store, on_event: @listener) : klass.create(agent_id: id, persistence: persistence.agent_store, on_event: @listener)
394
415
  end
395
- unless child.persistence.equal?(persistence)
416
+ unless child.persistence.equal?(persistence.agent_store)
396
417
  raise Phronomy::ConfigurationError, "Team child #{id} Persistence mismatch"
397
418
  end
398
419
  child_config = config.merge(cancellation_token: token,
399
- phronomy_coordination: {"kind" => "team", "team_id" => team_id,
400
- "team_execution_id" => current.team_execution_id, "slot" => purpose})
420
+ phronomy_reservation: {"kind" => "team", "team_id" => team_id,
421
+ "team_execution_id" => current.team_execution_id, "slot" => purpose})
422
+ child_config = child_config.merge(phronomy_admission: ReservedChildAdmission.new(
423
+ persistence: persistence, owner: child_config.fetch(:phronomy_reservation)
424
+ ))
401
425
  if (context_ref = current.metadata["durable_context_ref"])
402
426
  child_config = child_config.merge(durable_context: persistence.contents.fetch_json(context_ref))
403
427
  end
404
- Phronomy::Agent::ExactExecution.start(agent: child, execution_id: slot.fetch("execution_id"), input: input, config: child_config).wait_result
428
+ child.start_reserved_async(reservation: Phronomy::Agent::ReservedExecution.new(agent_id: id, execution_id: slot.fetch("execution_id"), correlation: child_config.fetch(:phronomy_reservation)), input: input, config: child_config).wait_result
405
429
  end
406
430
 
407
431
  def reserve_assignment(current, task)
408
- available = current.workers.map { |w| WorkerState.new(index: w.fetch("index"), agent_id: w.fetch("agent_id"), transcript_size: w.fetch("transcript_size"), status: :available) }.freeze
409
- worker = self.class._scheduler ? self.class._scheduler.call(available) : available.min_by(&:transcript_size)
432
+ # Team owns this count. Deriving it from retained assignments also
433
+ # makes replay idempotent and avoids consulting the child's journal.
434
+ counts = current.assignments.each_with_object(Hash.new(0)) { |entry, totals| totals[entry.fetch("worker")] += 1 }
435
+ available = current.workers.map { |w| WorkerState.new(index: w.fetch("index"), agent_id: w.fetch("agent_id"), assignment_count: counts[w.fetch("index")], status: :available) }.freeze
436
+ worker = self.class._scheduler ? self.class._scheduler.call(available) : available.min_by(&:assignment_count)
410
437
  raise Phronomy::ConfigurationError, "Scheduler must select an available worker slot" unless available.include?(worker)
411
438
  reserved = {"task_id" => task.fetch("id"), "worker" => worker.index,
412
439
  "execution_id" => SecureRandom.uuid, "state" => "reserved", "result_ref" => nil, "error_ref" => nil}
@@ -417,20 +444,15 @@ module Phronomy
417
444
  end
418
445
 
419
446
  def record_assignment(id, assigned, outcome)
420
- update(id) do |fresh, tx|
447
+ update(id) do |fresh, _tx|
421
448
  entries = fresh.assignments.map do |entry|
422
449
  next entry unless entry.fetch("task_id") == assigned.fetch("task_id")
423
450
  next entry unless entry.fetch("state") == "reserved"
424
- raise Phronomy::Storage::ConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
451
+ raise Phronomy::Persistence::StateConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
425
452
  entry.merge("state" => outcome.fetch(:status).to_s,
426
453
  "result_ref" => outcome[:result_ref], "error_ref" => outcome[:error_ref])
427
454
  end
428
- workers = fresh.workers.map do |worker|
429
- next worker unless worker.fetch("index") == assigned.fetch("worker")
430
- root = tx.agents.load(worker.fetch("agent_id"))
431
- worker.merge("transcript_size" => root.journal_position)
432
- end
433
- fresh.with(assignments: entries, workers: workers)
455
+ fresh.with(assignments: entries)
434
456
  end
435
457
  end
436
458
 
@@ -444,7 +466,7 @@ module Phronomy
444
466
  end
445
467
 
446
468
  def finish_error(id, error, status: "failed")
447
- update(id) do |fresh, tx|
469
+ update(id) do |fresh, tx, scope|
448
470
  next fresh if fresh.terminal?
449
471
  fresh.with(status: status, phase: status, error_ref: tx.contents.put_json(error))
450
472
  end
@@ -452,7 +474,10 @@ module Phronomy
452
474
 
453
475
  def terminal_value(execution)
454
476
  raise Phronomy::CancellationError, "Team run #{execution.team_execution_id} cancelled" if execution.status == "cancelled"
455
- raise Phronomy::Agent::RecoverySupport.error_from_failure(persistence.contents.fetch_json(execution.error_ref)) if execution.error_ref
477
+ if execution.error_ref
478
+ failure = persistence.contents.fetch_json(execution.error_ref)
479
+ raise Phronomy::Error.from_diagnostic(failure)
480
+ end
456
481
  persistence.contents.fetch_json(execution.result_ref)
457
482
  end
458
483
 
@@ -477,7 +502,7 @@ module Phronomy
477
502
 
478
503
  def build_operation_tool(run_id, operation)
479
504
  team = self
480
- Class.new(Phronomy::Agent::Context::Capability::Base) do
505
+ Class.new(Phronomy::Tool::Base) do
481
506
  def self.__framework_owned_operation? = true
482
507
  tool_name operation.to_s
483
508
  description((operation == :enqueue_task) ? "Add a task to the worker queue." : "Finish task generation.")
@@ -489,7 +514,7 @@ module Phronomy
489
514
  param :summary, type: :string, desc: "Task generation summary", required: false
490
515
  end
491
516
  define_method(:call_async) do |args, cancellation_token: nil, config: {}|
492
- validated, schema_error = send(:validate_and_coerce, args)
517
+ validated, schema_error = validate_arguments(args)
493
518
  raise Phronomy::ToolError, schema_error if schema_error
494
519
  execute_async(**validated, cancellation_token: cancellation_token, config: config)
495
520
  rescue => error
@@ -497,8 +522,10 @@ module Phronomy
497
522
  end
498
523
  define_method(:execute_async) do |config: {}, cancellation_token: nil, **arguments|
499
524
  key = config.fetch(:phronomy_tool_invocation_id)
500
- Phronomy::Runtime.instance.offload.submit(on_full: :raise) do
525
+ Phronomy::Execution.submit(on_full: :raise) do
501
526
  team.send(:apply_operation, run_id, key, operation, arguments)
527
+ rescue OperationRejected => error
528
+ raise Phronomy::ConfigurationError.new(error.message, code: error.code)
502
529
  rescue Phronomy::CancellationError
503
530
  raise
504
531
  rescue => error
@@ -510,39 +537,34 @@ module Phronomy
510
537
  end
511
538
 
512
539
  def apply_operation(run_id, key, operation, arguments)
513
- argument_values = Phronomy::Agent::RecoverySupport.canonical_copy(arguments)
514
- current = update(run_id) do |fresh, tx|
540
+ argument_values = Phronomy::Values::Serializable.convert(arguments, unsupported_message: "Team arguments are not canonically serializable")
541
+ current = update(run_id) do |fresh, tx, scope|
515
542
  operations = fresh.metadata.fetch("operations")
516
543
  if (prior = operations[key])
517
544
  unless prior.fetch("operation") == operation.to_s && prior.fetch("arguments") == argument_values
518
- raise Phronomy::Storage::ConflictError, "Team operation #{key} identity mismatch"
545
+ raise Phronomy::Persistence::StateConflictError, "Team operation #{key} identity mismatch"
519
546
  end
520
547
  next fresh
521
548
  end
522
549
  raise Phronomy::CancellationError, "Team run cancelled" if fresh.metadata["cancel_requested"]
523
- raise Phronomy::Storage::ConflictError, "Team task generation is closed" unless fresh.active? && fresh.phase == "coordinator"
524
- coordinator = tx.executions.load(fresh.coordinator.fetch("execution_id"))
525
- unless coordinator.agent_id == fresh.coordinator.fetch("agent_id")
526
- raise Phronomy::Storage::ConflictError, "Team coordinator owner mismatch"
527
- end
528
- batch = Array(coordinator.metadata[Phronomy::Agent::ExecutionMetadata::TOOL_BATCH_METADATA_KEY])
529
- requested = batch.find { |entry| entry.fetch("tool_invocation_id") == key }
530
- unless requested && requested.fetch("status") == "authorized" && requested.fetch("tool_name") == operation.to_s && requested.fetch("arguments").compact == argument_values
531
- raise Phronomy::Storage::ConflictError, "Team operation #{key} is not the authorized call"
532
- end
550
+ raise Phronomy::Persistence::StateConflictError, "Team task generation is closed" unless fresh.active? && fresh.phase == "coordinator"
551
+ batch = persistence.agent_store.authorized_operations(scope,
552
+ agent_id: fresh.coordinator.fetch("agent_id"), execution_id: fresh.coordinator.fetch("execution_id"),
553
+ invocation_id: key, name: operation.to_s, arguments: argument_values, names: %w[enqueue_task finalize])
533
554
  # All calls passed the Agent authorization barrier. Commit this finite
534
555
  # batch in Provider order, so finalize cannot overtake queued tasks.
535
556
  tasks = fresh.tasks.dup
536
557
  operations = operations.dup
537
558
  metadata = fresh.metadata.dup
538
559
  batch.each do |entry|
539
- entry_id = entry.fetch("tool_invocation_id")
540
- next if operations.key?(entry_id) || entry.fetch("status") != "authorized"
541
- name = entry.fetch("tool_name")
542
- next unless %w[enqueue_task finalize].include?(name)
543
- values = entry.fetch("arguments").compact
560
+ entry_id = entry.invocation_id
561
+ next if operations.key?(entry_id)
562
+ name = entry.name
563
+ values = entry.arguments
544
564
  if name == "enqueue_task"
545
- raise Phronomy::ConfigurationError, "Cannot enqueue after finalize" if metadata["finalized"]
565
+ if metadata["finalized"]
566
+ raise OperationRejected.new("Cannot enqueue after finalize", code: "team.enqueue_after_finalize")
567
+ end
546
568
  task = {"id" => Digest::SHA256.hexdigest(entry_id)[0, 32], "description" => values.fetch("description"), "metadata" => values["metadata"]}
547
569
  tasks << task
548
570
  output = "Task ##{tasks.length} enqueued: #{task.fetch("description")}"
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../persistence/errors"
4
+
5
+ module Phronomy
6
+ module MultiAgent
7
+ # Describes the Team itself, not a child Agent.
8
+ # @api public
9
+ class TeamNotFoundError < Phronomy::Persistence::NotFoundError
10
+ attr_reader :team_id
11
+
12
+ def initialize(message = nil, team_id:)
13
+ @team_id = team_id.to_s.dup.freeze
14
+ super(message || "Team #{@team_id}: not_found", code: "team.not_found")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -2,19 +2,9 @@
2
2
 
3
3
  module Phronomy
4
4
  module MultiAgent
5
- # Runtime-local Team identity and construction exclusion; no execution state.
5
+ # Environment-local Team identity and construction exclusion; no execution state.
6
6
  # @api private
7
7
  class TeamOwnershipRegistry
8
- def self.for(runtime)
9
- existing_for(runtime) || runtime.__register_shutdown_participant(
10
- key: self, participant: new
11
- )
12
- end
13
-
14
- def self.existing_for(runtime)
15
- runtime.__shutdown_participant(key: self)
16
- end
17
-
18
8
  def initialize
19
9
  @mutex = Mutex.new
20
10
  @condition = ConditionVariable.new
@@ -28,7 +18,7 @@ module Phronomy
28
18
  loop do
29
19
  raise Phronomy::RuntimeShutdownError, "Runtime is draining" if @draining
30
20
  if (owner = @owners[id])
31
- raise Phronomy::Storage::ConflictError, "Team #{id} already exists" if create
21
+ raise Phronomy::Persistence::StateConflictError, "Team #{id} already exists" if create
32
22
  unless owner.is_a?(klass) && owner.persistence.equal?(persistence)
33
23
  raise Phronomy::ConfigurationError, "Team #{id} owner or Persistence mismatch"
34
24
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ class ParseError < Error; end
7
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class Persistence
5
+ # Domain-facing content access uses the Persistence failure vocabulary.
6
+ # ContentStore implementations retain their independent backend contract.
7
+ # @api private
8
+ class ContentRepository < Phronomy::ContentStore::Base
9
+ def initialize(store)
10
+ @store = store
11
+ end
12
+
13
+ def put(bytes, canonicalization_version:)
14
+ StorageBoundary.call { @store.put(bytes, canonicalization_version: canonicalization_version) }
15
+ end
16
+
17
+ def fetch(content_id)
18
+ StorageBoundary.call { @store.fetch(content_id) }
19
+ end
20
+
21
+ def exist?(content_id)
22
+ StorageBoundary.call { @store.exist?(content_id) }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ class Persistence
7
+ # Failures describe a category, not a universal claim about commit outcome.
8
+ class Error < Phronomy::Error; end
9
+ class ConflictError < Error; end
10
+ class StateConflictError < ConflictError; end
11
+ class NotFoundError < Error; end
12
+ class SerializationError < Error; end
13
+ class UnsupportedBackendError < Error; end
14
+ class TransactionError < Error; end
15
+ end
16
+ end
@@ -108,8 +108,8 @@ module Phronomy
108
108
  raise Phronomy::Storage::SerializationError,
109
109
  "unsupported pre-S3 LLMInputManifest version: #{old_version.inspect}"
110
110
  end
111
- source["version"] = Phronomy::Agent::LLMInputManifest::VERSION
112
- Phronomy::Agent::LLMInputManifest.from_h(source).to_h
111
+ source["version"] = Phronomy::Context::LLMInputManifest::VERSION
112
+ Phronomy::Context::LLMInputManifest.from_h(source).to_h
113
113
  rescue Phronomy::Storage::SerializationError
114
114
  raise
115
115
  rescue => error
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Domain-neutral transaction ownership and participation. Domain operations
5
+ # choose their atomic boundary; composition supplies their storage adapters.
6
+ # @api public
7
+ class Persistence
8
+ attr_reader :backend
9
+
10
+ def initialize(backend:)
11
+ StorageBoundary.call { Storage::Backend.validate_capabilities!(backend) }
12
+ @backend = backend
13
+ end
14
+
15
+ # Bind an injected adapter for ordinary synchronous reads/standalone writes.
16
+ # Only that adapter receives Storage's raw view. Multi-operation changes use
17
+ # atomic/Transaction#participate so all participants use the exact same view.
18
+ # @api private
19
+ def bind(adapter)
20
+ StorageBoundary.call { adapter.new(backend.view) }
21
+ end
22
+ end
23
+ end
@@ -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