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