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
@@ -19,9 +19,9 @@ module Phronomy
19
19
 
20
20
  def start(name, input: nil, **metadata)
21
21
  trace_pii = false
22
- configuration = Phronomy.configuration
23
- trace_pii = !!configuration.trace_pii
24
- tracer = configuration.tracer
22
+ settings = Settings.current
23
+ trace_pii = !!settings.trace_pii
24
+ tracer = settings.tracer
25
25
  span = tracer.start_span(
26
26
  name,
27
27
  input: traced_payload(input, trace_pii: trace_pii),
@@ -47,7 +47,7 @@ module Phronomy
47
47
  error: traced_error(error, trace_pii: handle.trace_pii)
48
48
  }.freeze
49
49
 
50
- if Phronomy::Runtime.in_event_loop_context?
50
+ if Phronomy::WaitPolicy.blocking_forbidden?
51
51
  submit_finish(handle, attributes)
52
52
  else
53
53
  finish_now(handle, attributes)
@@ -130,8 +130,13 @@ module Phronomy
130
130
  private_class_method :task_usage
131
131
 
132
132
  def submit_finish(handle, attributes)
133
- Phronomy::Runtime.instance.offload.submit(on_full: :raise) do
133
+ Phronomy::Blocking.call_async do
134
134
  finish_now(handle, attributes)
135
+ end.on_complete do |_value, error|
136
+ if error
137
+ report_failure(:finish, handle.name, error,
138
+ include_message: handle.trace_pii)
139
+ end
135
140
  end
136
141
  nil
137
142
  rescue => error
@@ -164,7 +169,7 @@ module Phronomy
164
169
  message =
165
170
  "[Phronomy] automatic tracing #{stage} failed for " \
166
171
  "#{name}: #{error.class}#{detail}"
167
- logger = Phronomy.configuration.logger
172
+ logger = Phronomy::RuntimeSettings.current.logger
168
173
  logger ? logger.warn(message) : Kernel.warn(message)
169
174
  nil
170
175
  rescue
@@ -34,15 +34,44 @@ module Phronomy
34
34
  # @return [Object] the block's return value
35
35
  # @api public
36
36
  def trace(name, input: nil, **meta)
37
- span = start_span(name, input: input, **meta)
38
- result, usage = yield span
39
- finish_span(span, output: result, usage: usage)
37
+ Base.trace_with(self, name, attributes: {input: input, **meta}) { |span| yield span }
38
+ end
39
+
40
+ # Use the public start/finish SPI, including duck-typed tracers.
41
+ # Start failures do not finish; finish is attempted once after execution.
42
+ # A secondary finish/reporting failure cannot replace the block's error.
43
+ # Set record_output: false for metadata-only operations.
44
+ # @api public
45
+ def self.trace_with(tracer, name, record_output: true, attributes: {})
46
+ span = tracer.start_span(name, **attributes)
47
+ begin
48
+ result, usage = yield span
49
+ rescue => primary
50
+ begin
51
+ tracer.finish_span(span, error: primary)
52
+ rescue => secondary
53
+ report_secondary(secondary)
54
+ end
55
+ raise
56
+ end
57
+ if record_output
58
+ tracer.finish_span(span, output: result, usage: usage)
59
+ else
60
+ tracer.finish_span(span)
61
+ end
40
62
  result
41
- rescue => e
42
- finish_span(span, error: e)
43
- raise
44
63
  end
45
64
 
65
+ def self.report_secondary(error)
66
+ Phronomy::RuntimeSettings.current.logger&.warn(
67
+ "[Phronomy::Tracing] secondary finish failure: #{error.class}: #{error.message}"
68
+ )
69
+ rescue
70
+ # Diagnostics are best effort; preserve the primary business failure.
71
+ nil
72
+ end
73
+ private_class_method :report_secondary
74
+
46
75
  # Start a new span. Must return an object that will be passed to #finish_span.
47
76
  #
48
77
  # @param name [String]
@@ -57,7 +86,7 @@ module Phronomy
57
86
  #
58
87
  # @param span [Object] the span returned by #start_span
59
88
  # @param output [Object, nil] successful output value
60
- # @param usage [Phronomy::TokenUsage, nil] token usage for this span
89
+ # @param usage [Phronomy::LLMAdapter::TokenUsage, nil] token usage for this span
61
90
  # @param error [Exception, nil] exception if the block raised
62
91
  # @api public
63
92
  def finish_span(span, output: nil, usage: nil, error: nil)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Tracing
5
+ module Observation
6
+ # Convenience wrapper that delegates to the global tracer.
7
+ # Yields a span; the block must return [result, usage] where usage is a
8
+ # Phronomy::LLMAdapter::TokenUsage or nil. Returns only the result value.
9
+ #
10
+ # When +trace_pii+ is disabled, both the input and the output (LLM response,
11
+ # tool result) are replaced with the literal string "[REDACTED]" before being
12
+ # forwarded to the tracing backend. The actual result is still returned to
13
+ # the caller — only the copy sent to the tracer is redacted.
14
+ #
15
+ # @example
16
+ # trace("my_chain", input: input) { [invoke(input), nil] }
17
+ # @api public
18
+ def self.trace(name, input: nil, **meta, &block)
19
+ traced_input = Settings.current.trace_pii ? input : "[REDACTED]"
20
+
21
+ if Settings.current.trace_pii
22
+ # PII recording is allowed: pass through unchanged.
23
+ Settings.current.tracer.trace(name, input: traced_input, **meta, &block)
24
+ else
25
+ # Redact both input (above) and output before forwarding to the tracer.
26
+ # Capture the real result so callers receive the unredacted value.
27
+ real_result = nil
28
+ Settings.current.tracer.trace(name, input: traced_input, **meta) do |span|
29
+ real_result, usage = block.call(span)
30
+ ["[REDACTED]", usage]
31
+ end
32
+ real_result
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -53,38 +53,13 @@ module Phronomy
53
53
  span.finish
54
54
  end
55
55
 
56
- # Overrides Base#trace to use OTel +in_span+, which pushes the span onto
57
- # the OTel Context stack while the block runs. Nested +trace+ calls
58
- # automatically inherit the current span as their parent, establishing the
59
- # correct parent/child hierarchy in the trace tree.
60
- #
61
- # @param name [String] span name
62
- # @param input [Object, nil] input to record as a span attribute
63
- # @param meta [Hash] additional metadata (e.g. task_id, user_id)
64
- # @yield [span] the active OTel span
65
- # @return [Object] the block's return value
56
+ # Preserve the backend context while sharing C15's exactly-once finish
57
+ # and primary-error protection. Context affinity remains backend-owned.
66
58
  # @api private
67
59
  def trace(name, input: nil, **meta)
68
- attrs = {}
69
- attrs["phronomy.input"] = input.to_s if input
70
- meta.each { |k, v| attrs["phronomy.#{k}"] = v.to_s unless v.nil? }
71
-
72
- result = nil
73
- @otel_tracer.in_span(name, attributes: attrs) do |span|
74
- result, usage = yield span
75
- span.set_attribute("phronomy.output", result.to_s) if result
76
- if usage
77
- span.set_attribute("llm.usage.input_tokens", usage.input)
78
- span.set_attribute("llm.usage.output_tokens", usage.output)
79
- total = (usage.input || 0) + (usage.output || 0)
80
- span.set_attribute("llm.usage.total_tokens", total)
81
- end
82
- rescue => e
83
- span.record_exception(e)
84
- span.status = OpenTelemetry::Trace::Status.error(e.message)
85
- raise
60
+ Base.trace_with(self, name, attributes: {input: input, **meta.compact}) do |span|
61
+ OpenTelemetry::Trace.with_span(span) { yield span }
86
62
  end
87
- result
88
63
  end
89
64
  end
90
65
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/configuration_error"
4
+
5
+ module Phronomy
6
+ module Tracing
7
+ # Tracing owns recording options; application composition selects the tracer.
8
+ # No concrete backend, execution resource or application facade belongs here.
9
+ # @api private
10
+ class Settings
11
+ def self.install_provider(&provider)
12
+ raise ArgumentError, "Tracing settings provider requires a block" unless provider
13
+
14
+ @provider = provider
15
+ nil
16
+ end
17
+
18
+ # Follow reset and scoped restoration only when a consumer already reads.
19
+ def self.current
20
+ settings = @provider&.call
21
+ unless settings.is_a?(Settings)
22
+ raise ConfigurationError, "Tracing settings provider must return Tracing::Settings"
23
+ end
24
+
25
+ settings
26
+ end
27
+
28
+ attr_accessor :tracer, :trace_pii
29
+
30
+ def initialize(tracer:)
31
+ @tracer = tracer
32
+ @trace_pii = false
33
+ end
34
+ end
35
+ end
36
+ end
@@ -2,21 +2,25 @@
2
2
 
3
3
  module Phronomy
4
4
  module VectorStore
5
- # Framework-owned async convenience methods for VectorStore backends.
5
+ # Framework-owned asynchronous execution of a synchronous vector backend.
6
6
  #
7
- # The backend extension contract is the synchronous interface defined by
8
- # {VectorStore::Base}: add/search/remove/clear/size. These async convenience
9
- # methods are inherited by backends and route that synchronous work through
10
- # Phronomy's bounded {OffloadPool}. This keeps OS-thread creation, queue
11
- # backpressure, submit timeout/cancellation, and completion semantics owned by
12
- # the framework rather than by each backend.
7
+ # Backend authors implement only Base's synchronous operations. Applications
8
+ # use this client for asynchronous calls. Admission never waits for queue
9
+ # capacity; accepted calls return the pool's original TaskResult. Timeout
10
+ # includes queue wait and settles the result without interrupting running I/O.
11
+ # Construction does not start Runtime; the current default pool is resolved
12
+ # for each operation. An injected pool remains owned by its caller.
13
13
  #
14
- # A future genuine native-async backend may adapt its completion into a
15
- # {Phronomy::TaskResult} without an OffloadPool worker, but native async override is
16
- # not part of the current backend SPI.
17
- #
18
- # @api private
19
- module AsyncBackend
14
+ # @api public
15
+ class AsyncClient
16
+ # @param backend [VectorStore::Base] synchronous vector store
17
+ # @param pool [Concurrency::OffloadPool, nil] optional execution pool
18
+ # @api public
19
+ def initialize(backend:, pool: nil)
20
+ @backend = backend
21
+ @pool = pool
22
+ end
23
+
20
24
  # Async variant of {VectorStore::Base#add}.
21
25
  #
22
26
  # @param id [String]
@@ -27,12 +31,11 @@ module Phronomy
27
31
  # @return [Phronomy::TaskResult]
28
32
  # @api public
29
33
  def add_async(id:, embedding:, metadata: {}, cancellation_token: nil, timeout: nil)
30
- Phronomy::Runtime.instance.offload.submit(
34
+ Phronomy::Execution.submit(pool: default_pool,
31
35
  timeout: timeout,
32
36
  cancellation_token: cancellation_token,
33
- on_full: :raise
34
- ) do
35
- add(id: id, embedding: embedding, metadata: metadata, cancellation_token: cancellation_token)
37
+ on_full: :raise) do
38
+ @backend.add(id: id, embedding: embedding, metadata: metadata, cancellation_token: cancellation_token)
36
39
  end
37
40
  end
38
41
 
@@ -45,12 +48,11 @@ module Phronomy
45
48
  # @return [Phronomy::TaskResult]
46
49
  # @api public
47
50
  def search_async(query_embedding:, k: 5, cancellation_token: nil, timeout: nil)
48
- Phronomy::Runtime.instance.offload.submit(
51
+ Phronomy::Execution.submit(pool: default_pool,
49
52
  timeout: timeout,
50
53
  cancellation_token: cancellation_token,
51
- on_full: :raise
52
- ) do
53
- search(query_embedding: query_embedding, k: k, cancellation_token: cancellation_token)
54
+ on_full: :raise) do
55
+ @backend.search(query_embedding: query_embedding, k: k, cancellation_token: cancellation_token)
54
56
  end
55
57
  end
56
58
 
@@ -62,12 +64,11 @@ module Phronomy
62
64
  # @return [Phronomy::TaskResult]
63
65
  # @api public
64
66
  def remove_async(id:, cancellation_token: nil, timeout: nil)
65
- Phronomy::Runtime.instance.offload.submit(
67
+ Phronomy::Execution.submit(pool: default_pool,
66
68
  timeout: timeout,
67
69
  cancellation_token: cancellation_token,
68
- on_full: :raise
69
- ) do
70
- remove(id: id)
70
+ on_full: :raise) do
71
+ @backend.remove(id: id)
71
72
  end
72
73
  end
73
74
 
@@ -78,14 +79,19 @@ module Phronomy
78
79
  # @return [Phronomy::TaskResult]
79
80
  # @api public
80
81
  def clear_async(cancellation_token: nil, timeout: nil)
81
- Phronomy::Runtime.instance.offload.submit(
82
+ Phronomy::Execution.submit(pool: default_pool,
82
83
  timeout: timeout,
83
84
  cancellation_token: cancellation_token,
84
- on_full: :raise
85
- ) do
86
- clear
85
+ on_full: :raise) do
86
+ @backend.clear
87
87
  end
88
88
  end
89
+
90
+ private
91
+
92
+ def default_pool
93
+ @pool
94
+ end
89
95
  end
90
96
  end
91
97
  end
@@ -22,16 +22,16 @@ module Phronomy
22
22
  @expected_dimension = dimension
23
23
  end
24
24
 
25
+ protected
26
+
25
27
  # @param id [String]
26
28
  # @param embedding [Array<Float>]
27
29
  # @param metadata [Hash]
28
30
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
29
31
  # @api public
30
- def add(id:, embedding:, metadata: {}, cancellation_token: nil)
31
- cancellation_token&.raise_if_cancelled!
32
+ def perform_add(id:, embedding:, metadata: {}, cancellation_token: nil)
32
33
  # Establish expected dimension on first add, then validate.
33
34
  @expected_dimension ||= embedding.size
34
- validate_embedding_dimension!(embedding, @expected_dimension)
35
35
  @documents[id] = {embedding: embedding, metadata: metadata}
36
36
  self
37
37
  end
@@ -46,11 +46,8 @@ module Phronomy
46
46
  # (key always present); -r.fetch(:score) vs -r[:score] (key always present); snapshot = @documents
47
47
  # vs .dup is equivalent in single-threaded tests (GVL makes Hash#dup atomic, no behaviour
48
48
  # difference under test isolation)
49
- def search(query_embedding:, k: 5, cancellation_token: nil)
50
- cancellation_token&.raise_if_cancelled!
51
- k = validate_k!(k)
49
+ def perform_search(query_embedding:, k: 5, cancellation_token: nil)
52
50
  # search never establishes dimension; validate only when dimension is known.
53
- validate_embedding_dimension!(query_embedding, @expected_dimension)
54
51
  # Take an atomic snapshot before iterating. Hash#dup is a C-level
55
52
  # call that completes without releasing the GVL, so it is atomic with
56
53
  # respect to any other Ruby thread. Iterating the copy instead of
@@ -64,22 +61,27 @@ module Phronomy
64
61
  results.sort_by { |r| -r[:score] }.first(k)
65
62
  end
66
63
 
67
- def remove(id:)
64
+ def perform_remove(id:)
68
65
  @documents.delete(id)
69
66
  self
70
67
  end
71
68
 
72
- def clear
69
+ def perform_clear
73
70
  @documents.clear
74
71
  self
75
72
  end
76
73
 
77
74
  # @return [Integer] number of documents stored
78
75
  # @api public
79
- def size
76
+ def perform_size
80
77
  @documents.size
81
78
  end
82
79
 
80
+ # @api public
81
+ def embedding_dimension
82
+ @expected_dimension
83
+ end
84
+
83
85
  private
84
86
 
85
87
  # mutant:disable - empty-vector early-return condition variants (if false, if nil, if a.empty?,
@@ -35,14 +35,14 @@ module Phronomy
35
35
  @dimension = dimension
36
36
  end
37
37
 
38
+ protected
39
+
38
40
  # @param id [String]
39
41
  # @param embedding [Array<Float>]
40
42
  # @param metadata [Hash]
41
43
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
42
44
  # @api public
43
- def add(id:, embedding:, metadata: {}, cancellation_token: nil)
44
- cancellation_token&.raise_if_cancelled!
45
- validate_embedding_dimension!(embedding, @dimension)
45
+ def perform_add(id:, embedding:, metadata: {}, cancellation_token: nil)
46
46
  @model_class.upsert(
47
47
  {id: id, embedding: safe_vector(embedding), metadata: metadata.to_json},
48
48
  unique_by: :id
@@ -55,10 +55,7 @@ module Phronomy
55
55
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
56
56
  # @return [Array<Hash>] sorted by descending similarity score
57
57
  # @api public
58
- def search(query_embedding:, k: 5, cancellation_token: nil)
59
- cancellation_token&.raise_if_cancelled!
60
- k_safe = validate_k!(k)
61
- validate_embedding_dimension!(query_embedding, @dimension)
58
+ def perform_search(query_embedding:, k: 5, cancellation_token: nil)
62
59
  vec = safe_vector_literal(query_embedding)
63
60
  conn = @model_class.connection
64
61
  quoted_vec = "#{conn.quote(vec)}::vector"
@@ -66,48 +63,79 @@ module Phronomy
66
63
  @model_class
67
64
  .select("id, metadata, 1 - (embedding <=> #{quoted_vec}) AS score")
68
65
  .order("embedding <=> #{quoted_vec}")
69
- .limit(k_safe)
66
+ .limit(k)
70
67
  .map do |r|
71
68
  {
72
- id: r.id.to_s,
73
- score: r.score.to_f,
69
+ id: parse_id(r.id),
70
+ score: parse_score(r.score),
74
71
  metadata: parse_metadata(r.metadata)
75
72
  }
76
73
  end
77
74
  end
78
75
 
79
- def remove(id:)
76
+ def perform_remove(id:)
80
77
  @model_class.where(id: id).delete_all
81
78
  self
82
79
  end
83
80
 
84
- def clear
81
+ def perform_clear
85
82
  @model_class.delete_all
86
83
  self
87
84
  end
88
85
 
89
86
  # Returns the number of documents in the backing table.
90
- def size
87
+ def perform_size
91
88
  @model_class.count
92
89
  end
93
90
 
91
+ # @api public
92
+ def embedding_dimension
93
+ @dimension
94
+ end
95
+
94
96
  private
95
97
 
98
+ def parse_id(raw)
99
+ unless raw.is_a?(String) || raw.is_a?(Integer)
100
+ raise InvalidResultError, "Pgvector returned an invalid document id"
101
+ end
102
+ raw.to_s
103
+ end
104
+
105
+ def parse_score(raw)
106
+ valid = (raw.is_a?(String) && raw.match?(/\A[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\z/)) ||
107
+ (raw.is_a?(Numeric) && raw.real?)
108
+ value = Float(raw, exception: false) if valid
109
+ unless value&.finite?
110
+ raise InvalidResultError, "Pgvector requires a finite numeric score"
111
+ end
112
+ value
113
+ end
114
+
96
115
  # Parses a metadata value returned by the pg driver.
97
116
  # Handles NULL (nil), already-parsed Hash, and JSON string forms.
98
117
  def parse_metadata(raw)
99
118
  return {} if raw.nil?
100
119
  return symbolize_hash_keys(raw) if raw.is_a?(Hash)
101
120
 
102
- parsed = JSON.parse(raw.to_s, symbolize_names: true)
103
- parsed.is_a?(Hash) ? parsed : {}
121
+ unless raw.is_a?(String)
122
+ raise InvalidResultError, "Pgvector metadata must contain a JSON object"
123
+ end
124
+ parsed = JSON.parse(raw, symbolize_names: true)
125
+ unless parsed.is_a?(Hash)
126
+ raise InvalidResultError, "Pgvector metadata must contain a JSON object"
127
+ end
128
+ parsed
104
129
  rescue JSON::ParserError
105
- {}
130
+ raise InvalidResultError, "Pgvector metadata contains invalid JSON"
106
131
  end
107
132
 
108
133
  # Recursively symbolizes keys for an already-parsed Hash.
109
134
  def symbolize_hash_keys(hash)
110
135
  hash.each_with_object({}) do |(k, v), h|
136
+ unless k.is_a?(String) || k.is_a?(Symbol)
137
+ raise InvalidResultError, "Pgvector metadata contains an invalid object key"
138
+ end
111
139
  h[k.to_sym] = v.is_a?(Hash) ? symbolize_hash_keys(v) : v
112
140
  end
113
141
  end