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
@@ -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
@@ -184,8 +199,8 @@ module Phronomy
184
199
  end
185
200
 
186
201
  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)
202
+ raise Phronomy::EventLoopReentrancyError, "Team operation cannot block EventLoop" if Phronomy::WaitPolicy.blocking_forbidden?
203
+ unless @environment.current?
189
204
  raise Phronomy::RuntimeShutdownError, "Team #{team_id} belongs to a previous Runtime"
190
205
  end
191
206
  end
@@ -202,7 +217,7 @@ module Phronomy
202
217
  def read_execution(id)
203
218
  execution = persistence.team_executions.load(id)
204
219
  unless execution.team_id == team_id
205
- raise Phronomy::Storage::ConflictError, "Team execution #{id} belongs to another Team"
220
+ raise Phronomy::Persistence::StateConflictError, "Team execution #{id} belongs to another Team"
206
221
  end
207
222
  execution
208
223
  end
@@ -221,13 +236,13 @@ module Phronomy
221
236
  now = Time.now.utc.iso8601(6)
222
237
  intended = nil
223
238
  begin
224
- persistence.transaction do |tx|
239
+ persistence.transaction do |tx, scope|
225
240
  root = tx.teams.load(team_id)
226
241
  raise Phronomy::AgentBusyError, "Team #{team_id} has an active run; use resume" unless tx.team_executions.list_active(team_id).empty?
227
242
  intended = TeamExecution.new(team_execution_id: id, team_id: team_id,
228
243
  execution_revision: 0, status: "active", phase: "coordinator", input_ref: tx.contents.put_text(input),
229
244
  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} },
245
+ workers: Array.new(self.class._pool_size) { |i| {"index" => i, "agent_id" => SecureRandom.uuid} },
231
246
  tasks: [], assignments: [], result_ref: nil, error_ref: nil, created_at: now, updated_at: now,
232
247
  metadata: {"definition" => definition, "operations" => {}, "cancel_requested" => false,
233
248
  "durable_context_ref" => config.key?(:durable_context) ? tx.contents.put_json(config.fetch(:durable_context)) : nil})
@@ -238,7 +253,7 @@ module Phronomy
238
253
  raise error unless intended
239
254
  begin
240
255
  confirmed = read_execution(id)
241
- rescue Phronomy::Storage::NotFoundError
256
+ rescue Phronomy::Persistence::NotFoundError
242
257
  raise error
243
258
  end
244
259
  raise error unless confirmed.to_h == intended.to_h
@@ -252,10 +267,10 @@ module Phronomy
252
267
  attempts = 0
253
268
  begin
254
269
  intended = nil
255
- persistence.transaction do |tx|
270
+ persistence.transaction do |tx, scope|
256
271
  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)
272
+ raise Phronomy::Persistence::StateConflictError, "Team execution owner mismatch" unless current.team_id == team_id
273
+ intended = yield(current, tx, scope)
259
274
  next if intended.equal?(current)
260
275
  tx.team_executions.save(id, expected_revision: current.execution_revision, execution: intended)
261
276
  if intended.terminal? && current.active?
@@ -267,7 +282,7 @@ module Phronomy
267
282
  rescue => error
268
283
  confirmed = read_execution(id)
269
284
  return confirmed if intended && confirmed.to_h == intended.to_h
270
- if error.is_a?(Phronomy::Storage::ConflictError) && (attempts += 1) < 8
285
+ if error.is_a?(Phronomy::Persistence::ConflictError) && (attempts += 1) < 8
271
286
  retry
272
287
  end
273
288
  raise error
@@ -282,9 +297,9 @@ module Phronomy
282
297
  end
283
298
  token = Phronomy::Concurrency::CancellationToken.new
284
299
  @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)
300
+ cancellation_subscriptions = Phronomy::Concurrency::ResultSubscriptions.new
301
+ callback = proc { @environment.submit(on_full: :raise) { cancel(id) } }
302
+ cancellation_subscriptions.explicit_cancellation(config[:cancellation_token], &callback)
288
303
  token.cancel! if current.metadata["cancel_requested"]
289
304
  begin
290
305
  loop do
@@ -323,13 +338,13 @@ module Phronomy
323
338
  values = assignment_values(current)
324
339
  aggregate = self.class._aggregator
325
340
  begin
326
- output = Phronomy::Agent::RecoverySupport.canonical_copy(aggregate ? aggregate.call(values) : values)
341
+ output = Phronomy::Values::Serializable.convert(aggregate ? aggregate.call(values) : values, unsupported_message: "Team result is not canonically serializable")
327
342
  Phronomy::CanonicalJSON.dump(output)
328
343
  rescue => error
329
- return terminal_value(finish_error(id, Phronomy::Agent::RecoverySupport.resolution_failure(error)))
344
+ return terminal_value(finish_error(id, {"class" => error.class.name.to_s, "message" => error.message.to_s}))
330
345
  end
331
346
  # Aggregate may be replayed only until the canonical outcome commits.
332
- completed = update(id) do |fresh, tx|
347
+ completed = update(id) do |fresh, tx, scope|
333
348
  next fresh if fresh.terminal? || fresh.metadata["cancel_requested"]
334
349
  fresh.with(status: "completed", phase: "completed", result_ref: tx.contents.put_json(output))
335
350
  end
@@ -345,7 +360,7 @@ module Phronomy
345
360
  terminal_value(finish_error(id, {"class" => "Phronomy::CancellationError", "message" => "Team run cancelled"}, status: "cancelled"))
346
361
  end
347
362
  ensure
348
- external&.send(:unregister_cancel_callback, callback)
363
+ cancellation_subscriptions.close
349
364
  @tokens_mutex.synchronize { @tokens.delete(id) }
350
365
  end
351
366
  end
@@ -383,30 +398,31 @@ module Phronomy
383
398
  id = slot.fetch("agent_id")
384
399
  child = klass.get(id)
385
400
  unless child
386
- begin
387
- persistence.agents.load(id)
388
- present = true
389
- rescue Phronomy::Storage::NotFoundError
390
- present = false
391
- end
401
+ present = persistence.agent_store.exist?(id)
392
402
  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)
403
+ child = present ? klass.load(id, persistence: persistence.agent_store, on_event: @listener) : klass.create(agent_id: id, persistence: persistence.agent_store, on_event: @listener)
394
404
  end
395
- unless child.persistence.equal?(persistence)
405
+ unless child.persistence.equal?(persistence.agent_store)
396
406
  raise Phronomy::ConfigurationError, "Team child #{id} Persistence mismatch"
397
407
  end
398
408
  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})
409
+ phronomy_reservation: {"kind" => "team", "team_id" => team_id,
410
+ "team_execution_id" => current.team_execution_id, "slot" => purpose})
411
+ child_config = child_config.merge(phronomy_admission: ReservedChildAdmission.new(
412
+ persistence: persistence, owner: child_config.fetch(:phronomy_reservation)
413
+ ))
401
414
  if (context_ref = current.metadata["durable_context_ref"])
402
415
  child_config = child_config.merge(durable_context: persistence.contents.fetch_json(context_ref))
403
416
  end
404
- Phronomy::Agent::ExactExecution.start(agent: child, execution_id: slot.fetch("execution_id"), input: input, config: child_config).wait_result
417
+ child.start_reserved_async(reservation: Phronomy::Agent::ReservedExecution.new(agent_id: id, execution_id: slot.fetch("execution_id"), correlation: child_config.fetch(:phronomy_reservation)), input: input, config: child_config).wait_result
405
418
  end
406
419
 
407
420
  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)
421
+ # Team owns this count. Deriving it from retained assignments also
422
+ # makes replay idempotent and avoids consulting the child's journal.
423
+ counts = current.assignments.each_with_object(Hash.new(0)) { |entry, totals| totals[entry.fetch("worker")] += 1 }
424
+ available = current.workers.map { |w| WorkerState.new(index: w.fetch("index"), agent_id: w.fetch("agent_id"), assignment_count: counts[w.fetch("index")], status: :available) }.freeze
425
+ worker = self.class._scheduler ? self.class._scheduler.call(available) : available.min_by(&:assignment_count)
410
426
  raise Phronomy::ConfigurationError, "Scheduler must select an available worker slot" unless available.include?(worker)
411
427
  reserved = {"task_id" => task.fetch("id"), "worker" => worker.index,
412
428
  "execution_id" => SecureRandom.uuid, "state" => "reserved", "result_ref" => nil, "error_ref" => nil}
@@ -417,20 +433,15 @@ module Phronomy
417
433
  end
418
434
 
419
435
  def record_assignment(id, assigned, outcome)
420
- update(id) do |fresh, tx|
436
+ update(id) do |fresh, _tx|
421
437
  entries = fresh.assignments.map do |entry|
422
438
  next entry unless entry.fetch("task_id") == assigned.fetch("task_id")
423
439
  next entry unless entry.fetch("state") == "reserved"
424
- raise Phronomy::Storage::ConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
440
+ raise Phronomy::Persistence::StateConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
425
441
  entry.merge("state" => outcome.fetch(:status).to_s,
426
442
  "result_ref" => outcome[:result_ref], "error_ref" => outcome[:error_ref])
427
443
  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)
444
+ fresh.with(assignments: entries)
434
445
  end
435
446
  end
436
447
 
@@ -444,7 +455,7 @@ module Phronomy
444
455
  end
445
456
 
446
457
  def finish_error(id, error, status: "failed")
447
- update(id) do |fresh, tx|
458
+ update(id) do |fresh, tx, scope|
448
459
  next fresh if fresh.terminal?
449
460
  fresh.with(status: status, phase: status, error_ref: tx.contents.put_json(error))
450
461
  end
@@ -452,7 +463,10 @@ module Phronomy
452
463
 
453
464
  def terminal_value(execution)
454
465
  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
466
+ if execution.error_ref
467
+ failure = persistence.contents.fetch_json(execution.error_ref)
468
+ raise Phronomy::Error, "#{failure.fetch("class")}: #{failure.fetch("message")}"
469
+ end
456
470
  persistence.contents.fetch_json(execution.result_ref)
457
471
  end
458
472
 
@@ -477,7 +491,7 @@ module Phronomy
477
491
 
478
492
  def build_operation_tool(run_id, operation)
479
493
  team = self
480
- Class.new(Phronomy::Agent::Context::Capability::Base) do
494
+ Class.new(Phronomy::Tool::Base) do
481
495
  def self.__framework_owned_operation? = true
482
496
  tool_name operation.to_s
483
497
  description((operation == :enqueue_task) ? "Add a task to the worker queue." : "Finish task generation.")
@@ -489,7 +503,7 @@ module Phronomy
489
503
  param :summary, type: :string, desc: "Task generation summary", required: false
490
504
  end
491
505
  define_method(:call_async) do |args, cancellation_token: nil, config: {}|
492
- validated, schema_error = send(:validate_and_coerce, args)
506
+ validated, schema_error = validate_arguments(args)
493
507
  raise Phronomy::ToolError, schema_error if schema_error
494
508
  execute_async(**validated, cancellation_token: cancellation_token, config: config)
495
509
  rescue => error
@@ -497,8 +511,10 @@ module Phronomy
497
511
  end
498
512
  define_method(:execute_async) do |config: {}, cancellation_token: nil, **arguments|
499
513
  key = config.fetch(:phronomy_tool_invocation_id)
500
- Phronomy::Runtime.instance.offload.submit(on_full: :raise) do
514
+ Phronomy::Execution.submit(on_full: :raise) do
501
515
  team.send(:apply_operation, run_id, key, operation, arguments)
516
+ rescue OperationRejected => error
517
+ raise Phronomy::ConfigurationError, error.message
502
518
  rescue Phronomy::CancellationError
503
519
  raise
504
520
  rescue => error
@@ -510,39 +526,32 @@ module Phronomy
510
526
  end
511
527
 
512
528
  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|
529
+ argument_values = Phronomy::Values::Serializable.convert(arguments, unsupported_message: "Team arguments are not canonically serializable")
530
+ current = update(run_id) do |fresh, tx, scope|
515
531
  operations = fresh.metadata.fetch("operations")
516
532
  if (prior = operations[key])
517
533
  unless prior.fetch("operation") == operation.to_s && prior.fetch("arguments") == argument_values
518
- raise Phronomy::Storage::ConflictError, "Team operation #{key} identity mismatch"
534
+ raise Phronomy::Persistence::StateConflictError, "Team operation #{key} identity mismatch"
519
535
  end
520
536
  next fresh
521
537
  end
522
538
  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
539
+ raise Phronomy::Persistence::StateConflictError, "Team task generation is closed" unless fresh.active? && fresh.phase == "coordinator"
540
+ batch = persistence.agent_store.authorized_operations(scope,
541
+ agent_id: fresh.coordinator.fetch("agent_id"), execution_id: fresh.coordinator.fetch("execution_id"),
542
+ invocation_id: key, name: operation.to_s, arguments: argument_values, names: %w[enqueue_task finalize])
533
543
  # All calls passed the Agent authorization barrier. Commit this finite
534
544
  # batch in Provider order, so finalize cannot overtake queued tasks.
535
545
  tasks = fresh.tasks.dup
536
546
  operations = operations.dup
537
547
  metadata = fresh.metadata.dup
538
548
  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
549
+ entry_id = entry.invocation_id
550
+ next if operations.key?(entry_id)
551
+ name = entry.name
552
+ values = entry.arguments
544
553
  if name == "enqueue_task"
545
- raise Phronomy::ConfigurationError, "Cannot enqueue after finalize" if metadata["finalized"]
554
+ raise OperationRejected, "Cannot enqueue after finalize" if metadata["finalized"]
546
555
  task = {"id" => Digest::SHA256.hexdigest(entry_id)[0, 32], "description" => values.fetch("description"), "metadata" => values["metadata"]}
547
556
  tasks << task
548
557
  output = "Task ##{tasks.length} enqueued: #{task.fetch("description")}"
@@ -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
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class Persistence
5
+ # Translate only the backend's existing known failure categories. Unknown
6
+ # transport/commit outcomes and application exceptions pass
7
+ # through unchanged so the operation's existing reconciliation stays in charge.
8
+ # @api private
9
+ module StorageBoundary
10
+ def self.call
11
+ yield
12
+ rescue Phronomy::Storage::ConflictError => error
13
+ raise_domain_error(ConflictError, error)
14
+ rescue Phronomy::Storage::NotFoundError => error
15
+ raise_domain_error(NotFoundError, error)
16
+ rescue Phronomy::Storage::SerializationError => error
17
+ raise_domain_error(SerializationError, error)
18
+ rescue Phronomy::Storage::TransactionError => error
19
+ raise_domain_error(TransactionError, error)
20
+ rescue Phronomy::Storage::UnsupportedBackendError => error
21
+ raise_domain_error(UnsupportedBackendError, error)
22
+ end
23
+
24
+ def self.raise_domain_error(type, original)
25
+ mapped = type.new(original.message)
26
+ mapped.set_backtrace(original.backtrace)
27
+ raise mapped, cause: original
28
+ end
29
+ private_class_method :raise_domain_error
30
+ end
31
+ end
32
+ end