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
@@ -9,86 +9,102 @@ module Phronomy
9
9
  def initialize(view) = @view = view
10
10
 
11
11
  def create_active(execution)
12
- raise Phronomy::Storage::SerializationError, "create_active requires an active TeamExecution" unless execution.active?
13
- record = Codec.encode_team_execution(execution)
14
- admission do
15
- @view.atomic do |bound|
16
- entry = bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).insert(key: execution.team_execution_id.to_s,
17
- revision: Integer(execution.execution_revision), attributes: attributes(execution), record: record)
18
- decode(entry, execution.team_execution_id, owner: execution.team_id,
19
- revision: execution.execution_revision, active: true)
12
+ Phronomy::Persistence::StorageBoundary.call do
13
+ raise Phronomy::Storage::SerializationError, "create_active requires an active TeamExecution" unless execution.active?
14
+ record = Codec.encode_team_execution(execution)
15
+ admission(execution.team_id) do
16
+ @view.atomic do |bound|
17
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).insert(key: execution.team_execution_id.to_s,
18
+ revision: Integer(execution.execution_revision), attributes: attributes(execution), record: record)
19
+ decode(entry, execution.team_execution_id, owner: execution.team_id,
20
+ revision: execution.execution_revision, active: true)
21
+ end
20
22
  end
21
23
  end
22
24
  end
23
25
 
24
26
  def load(team_execution_id)
25
- value = @view.atomic do |bound|
26
- entry = bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).read(team_execution_id.to_s)
27
- entry && decode(entry, team_execution_id)
27
+ Phronomy::Persistence::StorageBoundary.call do
28
+ value = @view.atomic do |bound|
29
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).read(team_execution_id.to_s)
30
+ entry && decode(entry, team_execution_id)
31
+ end
32
+ value || raise(Phronomy::Storage::NotFoundError, "TeamExecution not found: #{team_execution_id}")
28
33
  end
29
- value || raise(Phronomy::Storage::NotFoundError, "TeamExecution not found: #{team_execution_id}")
30
34
  end
31
35
 
32
36
  def save(team_execution_id, expected_revision:, execution:)
33
- expected = Integer(expected_revision)
34
- revision = Integer(execution.execution_revision)
35
- raise Phronomy::Storage::ConflictError, "execution save must advance revision exactly once" unless revision == expected + 1
36
- unless execution.team_execution_id.to_s == team_execution_id.to_s
37
- raise Phronomy::Storage::SerializationError, "Execution identity mismatch"
38
- end
39
- record = Codec.encode_team_execution(execution)
40
- admission do
41
- @view.atomic do |bound|
42
- entry = bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).replace(key: team_execution_id.to_s,
43
- expected_revision: expected, next_revision: revision, attributes: attributes(execution),
44
- expected_attributes: execution.active? ? {active: true} : {}, record: record)
45
- decode(entry, team_execution_id, owner: execution.team_id, revision: revision, active: execution.active?)
37
+ Phronomy::Persistence::StorageBoundary.call do
38
+ expected = Integer(expected_revision)
39
+ revision = Integer(execution.execution_revision)
40
+ raise Phronomy::Storage::ConflictError, "execution save must advance revision exactly once" unless revision == expected + 1
41
+ unless execution.team_execution_id.to_s == team_execution_id.to_s
42
+ raise Phronomy::Storage::SerializationError, "Execution identity mismatch"
43
+ end
44
+ record = Codec.encode_team_execution(execution)
45
+ admission(execution.team_id) do
46
+ @view.atomic do |bound|
47
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).replace(key: team_execution_id.to_s,
48
+ expected_revision: expected, next_revision: revision, attributes: attributes(execution),
49
+ expected_attributes: execution.active? ? {active: true} : {}, record: record)
50
+ decode(entry, team_execution_id, owner: execution.team_id, revision: revision, active: execution.active?)
51
+ end
46
52
  end
47
53
  end
48
54
  end
49
55
 
50
56
  def list_active(team_id)
51
- @view.atomic do |bound|
52
- bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).scan(index: :owner_active,
53
- equals: {owner: team_id.to_s, active: true}).map { |entry| decode(entry, entry.key, owner: team_id, active: true) }.freeze
57
+ Phronomy::Persistence::StorageBoundary.call do
58
+ @view.atomic do |bound|
59
+ bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).scan(index: :owner_active,
60
+ equals: {owner: team_id.to_s, active: true}).map { |entry| decode(entry, entry.key, owner: team_id, active: true) }.freeze
61
+ end
54
62
  end
55
63
  end
56
64
 
57
65
  def list(team_id, after: nil, limit: 100)
58
- raise ArgumentError, "limit must be a positive Integer" unless limit.is_a?(Integer) && limit.positive?
59
- @view.atomic do |bound|
60
- bound.records(Phronomy::TeamStorageSchema::EXECUTIONS).scan(index: :owner, equals: {owner: team_id.to_s},
61
- after: after&.to_s, limit: limit).map { |entry| decode(entry, entry.key, owner: team_id) }.freeze
66
+ Phronomy::Persistence::StorageBoundary.call do
67
+ raise ArgumentError, "limit must be a positive Integer" unless limit.is_a?(Integer) && limit.positive?
68
+ @view.atomic do |bound|
69
+ bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).scan(index: :owner, equals: {owner: team_id.to_s},
70
+ after: after&.to_s, limit: limit).map { |entry| decode(entry, entry.key, owner: team_id) }.freeze
71
+ end
62
72
  end
63
73
  end
64
74
 
65
75
  def delete(team_execution_id)
66
- @view.records(Phronomy::TeamStorageSchema::EXECUTIONS).delete(key: team_execution_id.to_s)
76
+ Phronomy::Persistence::StorageBoundary.call do
77
+ @view.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).delete(key: team_execution_id.to_s)
78
+ end
67
79
  end
68
80
 
69
81
  def delete_for_team(team_id)
70
- @view.records(Phronomy::TeamStorageSchema::EXECUTIONS).delete_matching(index: :owner, equals: {owner: team_id.to_s})
82
+ Phronomy::Persistence::StorageBoundary.call do
83
+ @view.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).delete_matching(index: :owner, equals: {owner: team_id.to_s})
84
+ end
71
85
  end
72
86
 
73
87
  def assert_idle!(team_id)
74
- condition = Phronomy::Storage::Condition::NoRows.new(resource: Phronomy::TeamStorageSchema::EXECUTIONS,
75
- index: :owner_active, equals: {owner: team_id.to_s, active: true})
76
- @view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: Phronomy::TeamStorageSchema::ROOTS, key: team_id.to_s)],
77
- conditions: [condition])
78
- rescue Phronomy::Storage::ConditionFailedError => error
79
- raise unless error.condition.equal?(condition)
80
- raise Phronomy::AgentBusyError, error.message
88
+ Phronomy::Persistence::StorageBoundary.call do
89
+ condition = Phronomy::Storage::Condition::NoRows.new(resource: Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS,
90
+ index: :owner_active, equals: {owner: team_id.to_s, active: true})
91
+ @view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS, key: team_id.to_s)],
92
+ conditions: [condition])
93
+ rescue Phronomy::Storage::ConditionFailedError => error
94
+ raise unless error.condition.equal?(condition)
95
+ raise Phronomy::MultiAgent::TeamBusyError.new(error.message, team_id: team_id), cause: error
96
+ end
81
97
  end
82
98
 
83
99
  private
84
100
 
85
101
  def attributes(execution) = {owner: execution.team_id.to_s, active: execution.active?}
86
102
 
87
- def admission
103
+ def admission(team_id)
88
104
  yield
89
105
  rescue Phronomy::Storage::UniqueConstraintError => error
90
- raise unless error.resource == Phronomy::TeamStorageSchema::EXECUTIONS.id && error.constraint == :one_active_owner
91
- raise Phronomy::AgentBusyError, error.message
106
+ raise unless error.resource == Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS.id && error.constraint == :one_active_owner
107
+ raise Phronomy::MultiAgent::TeamBusyError.new(error.message, team_id: team_id), cause: error
92
108
  end
93
109
 
94
110
  def decode(entry, team_execution_id, owner: nil, revision: nil, active: nil)
@@ -9,39 +9,47 @@ module Phronomy
9
9
  def initialize(view) = @view = view
10
10
 
11
11
  def create(root)
12
- record = Codec.encode_team_root(root)
13
- @view.atomic do |bound|
14
- entry = bound.records(Phronomy::TeamStorageSchema::ROOTS).insert(key: root.team_id.to_s,
15
- revision: Integer(root.team_revision), attributes: {}, record: record)
16
- decode(entry, root.team_id, revision: root.team_revision)
12
+ Phronomy::Persistence::StorageBoundary.call do
13
+ record = Codec.encode_team_root(root)
14
+ @view.atomic do |bound|
15
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).insert(key: root.team_id.to_s,
16
+ revision: Integer(root.team_revision), attributes: {}, record: record)
17
+ decode(entry, root.team_id, revision: root.team_revision)
18
+ end
17
19
  end
18
20
  end
19
21
 
20
22
  def load(team_id)
21
- value = @view.atomic do |bound|
22
- entry = bound.records(Phronomy::TeamStorageSchema::ROOTS).read(team_id.to_s)
23
- entry && decode(entry, team_id)
23
+ Phronomy::Persistence::StorageBoundary.call do
24
+ value = @view.atomic do |bound|
25
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).read(team_id.to_s)
26
+ entry && decode(entry, team_id)
27
+ end
28
+ value || raise(Phronomy::Storage::NotFoundError, "Team not found: #{team_id}")
24
29
  end
25
- value || raise(Phronomy::Storage::NotFoundError, "Team not found: #{team_id}")
26
30
  end
27
31
 
28
32
  def save(team_id, expected_revision:, root:)
29
- expected = Integer(expected_revision)
30
- revision = Integer(root.team_revision)
31
- raise Phronomy::Storage::ConflictError, "root save must advance revision exactly once" unless revision == expected + 1
32
- unless root.team_id.to_s == team_id.to_s
33
- raise Phronomy::Storage::SerializationError, "Team root identity mismatch"
34
- end
35
- record = Codec.encode_team_root(root)
36
- @view.atomic do |bound|
37
- entry = bound.records(Phronomy::TeamStorageSchema::ROOTS).replace(key: team_id.to_s,
38
- expected_revision: expected, next_revision: revision, attributes: {}, record: record)
39
- decode(entry, team_id, revision: revision)
33
+ Phronomy::Persistence::StorageBoundary.call do
34
+ expected = Integer(expected_revision)
35
+ revision = Integer(root.team_revision)
36
+ raise Phronomy::Storage::ConflictError, "root save must advance revision exactly once" unless revision == expected + 1
37
+ unless root.team_id.to_s == team_id.to_s
38
+ raise Phronomy::Storage::SerializationError, "Team root identity mismatch"
39
+ end
40
+ record = Codec.encode_team_root(root)
41
+ @view.atomic do |bound|
42
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).replace(key: team_id.to_s,
43
+ expected_revision: expected, next_revision: revision, attributes: {}, record: record)
44
+ decode(entry, team_id, revision: revision)
45
+ end
40
46
  end
41
47
  end
42
48
 
43
49
  def delete(team_id)
44
- @view.records(Phronomy::TeamStorageSchema::ROOTS).delete(key: team_id.to_s)
50
+ Phronomy::Persistence::StorageBoundary.call do
51
+ @view.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).delete(key: team_id.to_s)
52
+ end
45
53
  end
46
54
 
47
55
  private
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ # Owns parent reservation checks and joins the Agent's durable admission.
6
+ # Reconstructed from current coordination wiring, never serialized as a Proc.
7
+ # @api public
8
+ class ReservedChildAdmission
9
+ def initialize(persistence:, owner:)
10
+ @persistence = persistence
11
+ @owner = Phronomy::Values::Immutable.copy(owner)
12
+ raise ArgumentError, "unknown reservation kind" unless %w[team subagent handoff].include?(@owner.fetch("kind"))
13
+ freeze
14
+ end
15
+
16
+ # Parent lock, current reservation check and child admission share one scope.
17
+ # No task, runtime owner or live Agent state is published before its commit.
18
+ # @api public
19
+ def admit(admission)
20
+ unless admission.correlation == @owner
21
+ raise Phronomy::Persistence::StateConflictError, "Child admission correlation mismatch"
22
+ end
23
+ @persistence.coordinator.atomic do |scope|
24
+ @persistence.participate(scope) do |records|
25
+ case @owner.fetch("kind")
26
+ when "team" then validate_team!(records, admission)
27
+ when "subagent" then validate_subagent!(records, admission, scope)
28
+ when "handoff" then validate_handoff!(records, admission, scope)
29
+ end
30
+ admission.accept_in(scope)
31
+ end
32
+ end
33
+ nil
34
+ end
35
+
36
+ private
37
+
38
+ def validate_team!(records, admission)
39
+ records.guard_team!(@owner.fetch("team_id"))
40
+ team = records.team_executions.load(@owner.fetch("team_execution_id"))
41
+ unless team.team_id == @owner.fetch("team_id") && team.active? && !team.metadata["cancel_requested"]
42
+ raise Phronomy::CancellationError, "Team run is no longer dispatchable"
43
+ end
44
+ slot = if @owner.fetch("slot") == "coordinator"
45
+ team.coordinator
46
+ else
47
+ assignment = team.assignments.find { |entry| entry.fetch("task_id") == @owner.fetch("slot") }
48
+ worker = assignment && team.workers.fetch(assignment.fetch("worker"))
49
+ worker&.merge("execution_id" => assignment.fetch("execution_id"))
50
+ end
51
+ unless slot && slot.fetch("execution_id") == admission.execution_id && slot.fetch("agent_id") == admission.agent_id
52
+ raise Phronomy::Persistence::StateConflictError, "Team reserved child identity mismatch"
53
+ end
54
+ end
55
+
56
+ def validate_subagent!(records, admission, scope)
57
+ @persistence.agent_store.guard_agents(scope, agent_ids: [@owner.fetch("parent_agent_id"), admission.agent_id])
58
+ parent = @persistence.agent_store.observe_execution(agent_id: @owner.fetch("parent_agent_id"),
59
+ execution_id: @owner.fetch("parent_execution_id"), scope: scope)
60
+ unless parent.continuable?
61
+ raise Phronomy::CancellationError, "Parent run is no longer dispatchable"
62
+ end
63
+ extension = @persistence.agent_store.execution_extension(agent_id: parent.agent_id, execution_id: parent.execution_id,
64
+ binding_key: DurableSubagentCoordinator::BINDING_KEY, scope: scope)
65
+ snapshot = records.contents.fetch_json(extension.state_ref)
66
+ raise Phronomy::CancellationError, "Parent reservation was cancelled" if snapshot["cancel_requested"]
67
+ slot = snapshot.fetch("children").find { |entry| entry.fetch("slot") == @owner.fetch("slot") }
68
+ unless slot && slot.fetch("agent_id") == admission.agent_id && slot.fetch("execution_id") == admission.execution_id
69
+ raise Phronomy::Persistence::StateConflictError, "Parent reserved child identity mismatch"
70
+ end
71
+ end
72
+
73
+ def validate_handoff!(records, admission, scope)
74
+ routing = records.handoff_states.load_locked(@owner.fetch("main_agent_id"))
75
+ unless routing && routing.active_agent_id == admission.agent_id
76
+ raise Phronomy::Persistence::StateConflictError, "Handoff responsibility changed before admission"
77
+ end
78
+ if Array(routing.metadata["cancelled_execution_ids"]).include?(admission.execution_id)
79
+ raise Phronomy::CancellationError, "Handoff reservation was cancelled"
80
+ end
81
+ @persistence.agent_store.guard_agents(scope, agent_ids: [@owner.fetch("main_agent_id"), admission.agent_id])
82
+ if routing.phase != "stable" && routing.pending_target_execution_id != admission.execution_id
83
+ raise Phronomy::Persistence::StateConflictError, "Handoff reserved Target identity mismatch"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ # Connects domain registries to the generic Runtime shutdown protocol.
6
+ # Construction does not register participants or start worker resources.
7
+ # @api private
8
+ class EngineEnvironment < ExecutionEnvironment
9
+ def initialize(runtime: Phronomy::Runtime.instance)
10
+ @runtime = runtime
11
+ end
12
+
13
+ def admissions
14
+ @runtime.__register_shutdown_participant(
15
+ key: AdmissionRegistry, participant: AdmissionRegistry.new
16
+ )
17
+ end
18
+
19
+ def ownership
20
+ existing_ownership || @runtime.__register_shutdown_participant(
21
+ key: TeamOwnershipRegistry, participant: TeamOwnershipRegistry.new
22
+ )
23
+ end
24
+
25
+ # Lookup must not register a participant, including during shutdown.
26
+ def existing_ownership = @runtime.__shutdown_participant(key: TeamOwnershipRegistry)
27
+
28
+ def current? = @runtime.equal?(Phronomy::Runtime.instance)
29
+
30
+ def submit(**options, &operation)
31
+ Phronomy::Execution.submit(runtime: @runtime, **options, &operation)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -188,7 +188,7 @@ module Phronomy
188
188
  end
189
189
 
190
190
  def build_read_tool(store)
191
- Class.new(Phronomy::Agent::Context::Capability::Base) do
191
+ Class.new(Phronomy::Tool::Base) do
192
192
  tool_name "read_store"
193
193
  description "Read all current findings from the shared knowledge store. " \
194
194
  "Call this to see what other researchers have discovered."
@@ -199,7 +199,7 @@ module Phronomy
199
199
  end
200
200
 
201
201
  def build_write_tool(store, cycle, agent_key)
202
- Class.new(Phronomy::Agent::Context::Capability::Base) do
202
+ Class.new(Phronomy::Tool::Base) do
203
203
  tool_name "write_finding"
204
204
  description "Record a new finding into the shared knowledge store so " \
205
205
  "that other researchers can build on your discovery."
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ # MultiAgent-owned durable operations and observations. An injected record
6
+ # adapter implements storage; this framework never selects a DB or codec.
7
+ # @api public
8
+ class Store
9
+ # Composition supplies the optional configured store without constructing
10
+ # a fallback. Team and Orchestrator retain their different fallback rules.
11
+ # @api private
12
+ def self.install_configured_provider(&provider)
13
+ raise ArgumentError, "store provider requires a block" unless provider
14
+ @configured_provider = provider
15
+ nil
16
+ end
17
+
18
+ # @api private
19
+ def self.configured = @configured_provider.call
20
+
21
+ attr_reader :coordinator, :agent_store
22
+
23
+ # Record protocol for MultiAgent-owned framework operations only.
24
+ # @api private
25
+ attr_reader :contents, :teams, :team_executions, :handoff_states
26
+
27
+ def initialize(coordinator:, records:, agent_store:)
28
+ @coordinator, @record_adapter = coordinator, records
29
+
30
+ unless agent_store.coordinator.equal?(coordinator)
31
+ raise Phronomy::Persistence::TransactionError, "Team and Agent stores must share one Persistence coordinator"
32
+ end
33
+ @agent_store = agent_store
34
+ @records = coordinator.bind(records)
35
+ @contents = @records.contents
36
+ @teams = @records.teams
37
+ @team_executions = @records.team_executions
38
+ @handoff_states = @records.handoff_states
39
+ end
40
+
41
+ # A domain operation owns this short transaction. A coordinating parent
42
+ # can instead call participate with its existing shared scope.
43
+ def transaction
44
+ coordinator.atomic do |scope|
45
+ participate(scope) { |records| yield records, scope }
46
+ end
47
+ end
48
+
49
+ def participate(scope, &operation)
50
+ scope.participate(persistence: coordinator, adapter: @record_adapter, &operation)
51
+ end
52
+
53
+ def handoff_leaf(execution_id, main_agent_id:, scope: nil)
54
+ return coordinator.atomic { |current| handoff_leaf(execution_id, main_agent_id: main_agent_id, scope: current) } unless scope
55
+ routing = participate(scope) { |records| records.handoff_states.load(main_agent_id) }
56
+ raise Phronomy::Persistence::NotFoundError, "Handoff history is absent" unless routing
57
+ identity = agent_store.execution_identity(execution_id, scope: scope)
58
+ unless identity.correlation == {"kind" => "handoff", "main_agent_id" => main_agent_id}
59
+ raise Phronomy::Persistence::StateConflictError, "Execution does not belong to this Handoff anchor"
60
+ end
61
+ seen = {}
62
+ loop do
63
+ raise Phronomy::Persistence::SerializationError, "Cyclic Handoff chain" if seen[identity.execution_id]
64
+ seen[identity.execution_id] = true
65
+ observed = agent_store.observe_execution(agent_id: identity.agent_id, execution_id: identity.execution_id, scope: scope)
66
+ return observed unless observed.status == :handed_off
67
+ receipt = observed.transfer_receipt
68
+ unless receipt && receipt.fetch("owner_key") == "handoff:#{main_agent_id}"
69
+ raise Phronomy::Persistence::SerializationError, "Invalid transfer receipt"
70
+ end
71
+ identity = Phronomy::Agent::ReservedExecution.new(agent_id: receipt.fetch("target_agent_id"), execution_id: receipt.fetch("target_execution_id"))
72
+ end
73
+ end
74
+
75
+ def handoff_result(execution_id, main_agent_id:)
76
+ assert_observation_thread!
77
+ transaction do |records, scope|
78
+ leaf = handoff_leaf(execution_id, main_agent_id: main_agent_id, scope: scope)
79
+ next leaf.to_result unless leaf.absent?
80
+ routing = records.handoff_states.load(main_agent_id)
81
+ if Array(routing.metadata["cancelled_execution_ids"]).include?(leaf.execution_id)
82
+ leaf.to_result.merge(status: :cancelled).freeze
83
+ elsif routing.phase != "stable" && routing.pending_target_execution_id == leaf.execution_id
84
+ leaf.to_result.merge(status: :active, reserved: true).freeze
85
+ else
86
+ raise Phronomy::Persistence::NotFoundError, "Reserved Handoff execution is absent"
87
+ end
88
+ end
89
+ end
90
+
91
+ def forget_subagents(agent_id:, execution_id:)
92
+ transaction do |records, scope|
93
+ extension = agent_store.execution_extension(agent_id: agent_id, execution_id: execution_id,
94
+ binding_key: DurableSubagentCoordinator::BINDING_KEY, scope: scope)
95
+ raise Phronomy::Persistence::NotFoundError, "Subagent history is absent" unless extension&.state_ref
96
+ snapshot = records.contents.fetch_json(extension.state_ref)
97
+ children = snapshot.fetch("children")
98
+ ids = ([agent_id] + children.map { |child| child.fetch("agent_id") }).uniq.sort.select { |id| agent_store.exist?(id) }
99
+ agent_store.guard_agents(scope, agent_ids: ids)
100
+ parent = agent_store.observe_execution(agent_id: agent_id, execution_id: execution_id, scope: scope)
101
+ raise Phronomy::AgentBusyError, "Subagent history retains unfinished parent" unless parent.terminal?
102
+ children.each do |child|
103
+ observed = agent_store.observe_execution(agent_id: child.fetch("agent_id"), execution_id: child.fetch("execution_id"), scope: scope)
104
+ if !observed.terminal? && !(observed.absent? && snapshot["cancel_requested"])
105
+ raise Phronomy::AgentBusyError, "Subagent history retains unfinished child"
106
+ end
107
+ end
108
+ agent_store.remove_execution_extension(agent_id: agent_id, execution_id: execution_id,
109
+ binding_key: DurableSubagentCoordinator::BINDING_KEY, scope: scope)
110
+ ids.each { |id| agent_store.release_retention(agent_id: id, owner_key: "subagent:#{execution_id}", scope: scope) }
111
+ end
112
+ true
113
+ end
114
+
115
+ def forget_handoff(main_agent_id)
116
+ transaction do |records, scope|
117
+ routing = records.handoff_states.load_locked(main_agent_id)
118
+ raise Phronomy::Persistence::NotFoundError, "Handoff history is absent" unless routing
119
+ ids = (Array(routing.metadata["retained_agent_ids"]) + [main_agent_id, routing.active_agent_id]).uniq.sort
120
+ agent_store.guard_agents(scope, agent_ids: ids)
121
+ unless routing.phase == "stable" && ids.all? { |id| agent_store.active_executions(agent_id: id, scope: scope).empty? }
122
+ raise Phronomy::AgentBusyError, "Handoff history retains unfinished work"
123
+ end
124
+ records.handoff_states.delete(main_agent_id, expected_revision: routing.handoff_revision)
125
+ ids.each { |id| agent_store.release_retention(agent_id: id, owner_key: "handoff:#{main_agent_id}", scope: scope) }
126
+ end
127
+ true
128
+ end
129
+
130
+ def result(team_execution_id)
131
+ assert_observation_thread!
132
+ execution = team_executions.load(team_execution_id)
133
+ {
134
+ team_execution_id: execution.team_execution_id, team_id: execution.team_id,
135
+ status: execution.status, phase: execution.phase,
136
+ result_ref: execution.result_ref, error_ref: execution.error_ref,
137
+ result: execution.result_ref && contents.fetch_json(execution.result_ref),
138
+ error: execution.error_ref && contents.fetch_json(execution.error_ref)
139
+ }.then { |value| Phronomy::Values::Immutable.copy(value) }
140
+ end
141
+
142
+ def runs(team_id, after: nil, limit: 100)
143
+ assert_observation_thread!
144
+ team_executions.list(team_id, after: after, limit: limit)
145
+ end
146
+
147
+ private
148
+
149
+ def assert_observation_thread!
150
+ if Phronomy::WaitPolicy.blocking_forbidden?
151
+ raise Phronomy::EventLoopReentrancyError, "Durable observation cannot block EventLoop"
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ module MultiAgent
7
+ # Describes the Team itself, not a child Agent.
8
+ # @api public
9
+ class TeamBusyError < Phronomy::Error
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}: busy", code: "team.busy")
15
+ end
16
+ end
17
+ end
18
+ end