phronomy 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (475) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +52 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +9 -3
  6. data/benchmark/bench_agent_invoke.rb +25 -50
  7. data/benchmark/bench_context_assembler.rb +14 -11
  8. data/benchmark/bench_regression.rb +1 -1
  9. data/benchmark/bench_tool_schema.rb +1 -1
  10. data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
  11. data/docs/architecture/agent-transition-ownership.md +13 -89
  12. data/docs/architecture/backend-groups.md +152 -0
  13. data/docs/architecture/before-llm-input.md +3 -3
  14. data/docs/architecture/context-management.md +1 -1
  15. data/docs/architecture/execution-abstraction-review.md +88 -0
  16. data/docs/architecture/execution-boundaries.md +28 -0
  17. data/docs/architecture/execution-metadata-and-values.md +6 -0
  18. data/docs/architecture/multi-agent-handoff.md +6 -5
  19. data/docs/architecture/persistence.md +27 -9
  20. data/docs/architecture/r8-unit1.md +104 -0
  21. data/docs/architecture/r8-unit10.md +72 -0
  22. data/docs/architecture/r8-unit11.md +71 -0
  23. data/docs/architecture/r8-unit12.md +67 -0
  24. data/docs/architecture/r8-unit13.md +61 -0
  25. data/docs/architecture/r8-unit14.md +72 -0
  26. data/docs/architecture/r8-unit15.md +65 -0
  27. data/docs/architecture/r8-unit16.md +54 -0
  28. data/docs/architecture/r8-unit17.md +73 -0
  29. data/docs/architecture/r8-unit18.md +92 -0
  30. data/docs/architecture/r8-unit19.md +61 -0
  31. data/docs/architecture/r8-unit2.md +55 -0
  32. data/docs/architecture/r8-unit20.md +51 -0
  33. data/docs/architecture/r8-unit21.md +79 -0
  34. data/docs/architecture/r8-unit22.md +74 -0
  35. data/docs/architecture/r8-unit23.md +81 -0
  36. data/docs/architecture/r8-unit3.md +130 -0
  37. data/docs/architecture/r8-unit4.md +79 -0
  38. data/docs/architecture/r8-unit5.md +115 -0
  39. data/docs/architecture/r8-unit6.md +71 -0
  40. data/docs/architecture/r8-unit7.md +70 -0
  41. data/docs/architecture/r8-unit8.md +78 -0
  42. data/docs/architecture/r8-unit9.md +73 -0
  43. data/docs/architecture/refactoring-closure.md +10 -1
  44. data/docs/architecture/remaining-refactoring-plan.md +16 -0
  45. data/docs/architecture/storage-execution-boundaries.md +54 -0
  46. data/docs/architecture/tool-schema-recording-gap.md +6 -0
  47. data/docs/architecture/tracing.md +1 -1
  48. data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
  49. data/docs/architecture.md +24 -11
  50. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  51. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  52. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
  53. data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
  54. data/docs/decisions/032-storage-backend-composition.md +5 -0
  55. data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
  56. data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
  57. data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
  58. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
  59. data/docs/decisions/040-configuration-default-composition.md +12 -1
  60. data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
  61. data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
  62. data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
  63. data/docs/decisions/058-neutral-storage-primitives.md +5 -0
  64. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  65. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  66. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  67. data/docs/decisions/062-content-store-service.md +52 -0
  68. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  69. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  70. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  71. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  72. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  73. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  74. data/docs/decisions/README.md +18 -1
  75. data/docs/execution-boundaries-preview.md +45 -0
  76. data/docs/features.md +8 -3
  77. data/docs/getting-started.md +9 -6
  78. data/docs/migrations/0.22.md +2 -2
  79. data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
  80. data/docs/migrations/handoff-runner-multi-agent.md +2 -0
  81. data/docs/migrations/persistence-failure-contracts.md +67 -0
  82. data/docs/migrations/r8-unit10.md +36 -0
  83. data/docs/migrations/r8-unit11.md +33 -0
  84. data/docs/migrations/r8-unit12.md +31 -0
  85. data/docs/migrations/r8-unit13.md +36 -0
  86. data/docs/migrations/r8-unit14.md +31 -0
  87. data/docs/migrations/r8-unit15.md +31 -0
  88. data/docs/migrations/r8-unit16.md +26 -0
  89. data/docs/migrations/r8-unit17.md +28 -0
  90. data/docs/migrations/r8-unit18.md +29 -0
  91. data/docs/migrations/r8-unit19.md +28 -0
  92. data/docs/migrations/r8-unit20.md +24 -0
  93. data/docs/migrations/r8-unit21.md +28 -0
  94. data/docs/migrations/r8-unit22.md +29 -0
  95. data/docs/migrations/r8-unit23.md +20 -0
  96. data/docs/migrations/r8-unit4.md +67 -0
  97. data/docs/migrations/r8-unit5.md +75 -0
  98. data/docs/migrations/r8-unit6.md +109 -0
  99. data/docs/migrations/r8-unit7.md +31 -0
  100. data/docs/migrations/r8-unit8.md +33 -0
  101. data/docs/migrations/r8-unit9.md +28 -0
  102. data/docs/migrations/storage-async-client.md +103 -0
  103. data/docs/migrations/vector-async-clients.md +110 -0
  104. data/docs/persistence-backends.md +38 -21
  105. data/docs/runtime-and-concurrency.md +26 -1
  106. data/lib/phronomy/agent/admission.rb +78 -0
  107. data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
  108. data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
  109. data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
  110. data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
  111. data/lib/phronomy/agent/async_event_api.rb +37 -32
  112. data/lib/phronomy/agent/base.rb +74 -72
  113. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  114. data/lib/phronomy/agent/composition/run_once.rb +1 -1
  115. data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
  116. data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
  117. data/lib/phronomy/agent/control_request.rb +21 -0
  118. data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
  119. data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
  120. data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
  121. data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
  122. data/lib/phronomy/agent/execution/exact_execution.rb +44 -23
  123. data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
  124. data/lib/phronomy/agent/execution/execution_coordinator.rb +214 -395
  125. data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
  126. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +70 -126
  127. data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
  128. data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
  129. data/lib/phronomy/agent/execution/initial_preparation.rb +18 -116
  130. data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
  131. data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
  132. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +7 -7
  133. data/lib/phronomy/agent/execution_change.rb +161 -0
  134. data/lib/phronomy/agent/execution_environment.rb +36 -0
  135. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  136. data/lib/phronomy/agent/execution_observation.rb +35 -0
  137. data/lib/phronomy/agent/filterable.rb +182 -0
  138. data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
  139. data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
  140. data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
  141. data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
  142. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  143. data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
  144. data/lib/phronomy/agent/lifecycle/ownership_registry.rb +18 -23
  145. data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
  146. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  147. data/lib/phronomy/agent/persistence/codec.rb +1 -21
  148. data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
  149. data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
  150. data/lib/phronomy/agent/persistence/records.rb +33 -0
  151. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  152. data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
  153. data/lib/phronomy/agent/persistence/watermark.rb +13 -11
  154. data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
  155. data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +10 -9
  156. data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +27 -27
  157. data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +19 -21
  158. data/lib/phronomy/agent/recovery/recovery_coordinator.rb +23 -16
  159. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  160. data/lib/phronomy/agent/recovery/recovery_support.rb +8 -11
  161. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  162. data/lib/phronomy/agent/retention.rb +17 -0
  163. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  164. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  165. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  166. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  167. data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
  168. data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
  169. data/lib/phronomy/agent/settings.rb +47 -0
  170. data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
  171. data/lib/phronomy/agent/store.rb +253 -0
  172. data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +1 -1
  173. data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
  174. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
  175. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  176. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  177. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  178. data/lib/phronomy/content_store/storage_schema.rb +1 -0
  179. data/lib/phronomy/content_store/stored_contents.rb +2 -1
  180. data/lib/phronomy/context/assembly.rb +202 -0
  181. data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
  182. data/lib/phronomy/context/candidate.rb +51 -0
  183. data/lib/phronomy/context/constraint.rb +47 -0
  184. data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
  185. data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
  186. data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
  187. data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
  188. data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
  189. data/lib/phronomy/context/default_policy.rb +140 -0
  190. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  191. data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
  192. data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
  193. data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
  194. data/lib/phronomy/context/manifest_error.rb +10 -0
  195. data/lib/phronomy/context/prompt_template.rb +98 -0
  196. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  197. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  198. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  199. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  200. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  201. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  202. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  203. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  204. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  205. data/lib/phronomy/embeddings/base.rb +43 -0
  206. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  207. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  208. data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
  209. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  210. data/lib/phronomy/engine/diagnostics.rb +1 -1
  211. data/lib/phronomy/engine/event_loop.rb +7 -7
  212. data/lib/phronomy/engine/execution_binding.rb +23 -0
  213. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  214. data/lib/phronomy/engine/fsm_protocol.rb +1 -6
  215. data/lib/phronomy/engine/fsm_session.rb +3 -3
  216. data/lib/phronomy/engine/metrics.rb +16 -14
  217. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  218. data/lib/phronomy/engine/runtime.rb +25 -12
  219. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  220. data/lib/phronomy/execution/async_operation.rb +41 -0
  221. data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
  222. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
  223. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  224. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  225. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  226. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  227. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  228. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  229. data/lib/phronomy/{engine → execution}/execution.rb +61 -27
  230. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  231. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  232. data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
  233. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  234. data/lib/phronomy/execution/runnable.rb +24 -0
  235. data/lib/phronomy/{engine → execution}/task_result.rb +3 -3
  236. data/lib/phronomy/execution/wait_policy.rb +24 -0
  237. data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +1 -1
  238. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  239. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  240. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
  241. data/lib/phronomy/generation/generator_verifier.rb +7 -7
  242. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  243. data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
  244. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  245. data/lib/phronomy/llm_adapter/base.rb +49 -68
  246. data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
  247. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  248. data/lib/phronomy/llm_adapter/message.rb +62 -0
  249. data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
  250. data/lib/phronomy/llm_adapter/request.rb +79 -0
  251. data/lib/phronomy/llm_adapter/response.rb +36 -0
  252. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  253. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  254. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  255. data/lib/phronomy/llm_adapter.rb +3 -16
  256. data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
  257. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  258. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  259. data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
  260. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
  261. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  262. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
  263. data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
  264. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
  265. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  266. data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
  267. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  268. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  269. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  270. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +58 -42
  271. data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
  272. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  273. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  274. data/lib/phronomy/multi_agent/shared_state.rb +2 -2
  275. data/lib/phronomy/multi_agent/store.rb +156 -0
  276. data/lib/phronomy/multi_agent/team_coordinator.rb +83 -74
  277. data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
  278. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  279. data/lib/phronomy/persistence/content_repository.rb +26 -0
  280. data/lib/phronomy/persistence/errors.rb +16 -0
  281. data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
  282. data/lib/phronomy/persistence/persistence.rb +23 -0
  283. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  284. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  285. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  286. data/lib/phronomy/persistence/transaction.rb +86 -0
  287. data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
  288. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  289. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  290. data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
  291. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  292. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  293. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  294. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  295. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  296. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  297. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  298. data/lib/phronomy/storage/async/async_client.rb +60 -0
  299. data/lib/phronomy/storage/backend.rb +8 -2
  300. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  301. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  302. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
  303. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
  304. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
  305. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
  306. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
  307. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
  308. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
  309. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
  310. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
  311. data/lib/phronomy/tool/authorization.rb +77 -0
  312. data/lib/phronomy/tool/base.rb +549 -9
  313. data/lib/phronomy/tool/call_request.rb +40 -0
  314. data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
  315. data/lib/phronomy/tool/operation.rb +88 -0
  316. data/lib/phronomy/tool/schema.rb +214 -0
  317. data/lib/phronomy/tool/settings.rb +31 -0
  318. data/lib/phronomy/tool/tool_error.rb +7 -0
  319. data/lib/phronomy/tool/tool_executor.rb +58 -0
  320. data/lib/phronomy/tools/agent.rb +22 -90
  321. data/lib/phronomy/tools/mcp.rb +21 -14
  322. data/lib/phronomy/tools/vector_search.rb +4 -4
  323. data/lib/phronomy/tracing/automatic.rb +11 -6
  324. data/lib/phronomy/tracing/base.rb +1 -1
  325. data/lib/phronomy/tracing/observation.rb +37 -0
  326. data/lib/phronomy/tracing/settings.rb +36 -0
  327. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  328. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  329. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  330. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  331. data/lib/phronomy/vector_store/base.rb +118 -49
  332. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  333. data/lib/phronomy/vector_store.rb +2 -17
  334. data/lib/phronomy/version.rb +1 -1
  335. data/lib/phronomy/workflow/completion.rb +11 -0
  336. data/lib/phronomy/workflow/execution/workflow.rb +7 -7
  337. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  338. data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
  339. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  340. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +3 -5
  341. data/lib/phronomy/workflow/execution/workflow_runner.rb +58 -79
  342. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  343. data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
  344. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  345. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  346. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  347. data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
  348. data/lib/phronomy.rb +57 -16
  349. data/scripts/api_snapshot.rb +35 -15
  350. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  351. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  352. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  353. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  354. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  355. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  356. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  357. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  358. data/sig/phronomy/agent/admission.rbs +19 -0
  359. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  360. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  361. data/sig/phronomy/agent/store.rbs +73 -0
  362. data/sig/phronomy/agent.rbs +14 -138
  363. data/sig/phronomy/common.rbs +7 -0
  364. data/sig/phronomy/content_store/contracts.rbs +13 -0
  365. data/sig/phronomy/context.rbs +152 -0
  366. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  367. data/sig/phronomy/embeddings/base.rbs +17 -0
  368. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  369. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  370. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  371. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  372. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  373. data/sig/phronomy/engine/observability.rbs +13 -0
  374. data/sig/phronomy/execution/async_operation.rbs +7 -0
  375. data/sig/phronomy/execution/blocking.rbs +5 -0
  376. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  377. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  378. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  379. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  380. data/sig/phronomy/execution/errors.rbs +7 -0
  381. data/sig/phronomy/execution/execution.rbs +41 -0
  382. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  383. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  384. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  385. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  386. data/sig/phronomy/execution/task_result.rbs +29 -0
  387. data/sig/phronomy/extensions.rbs +0 -21
  388. data/sig/phronomy/handoff.rbs +10 -4
  389. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  390. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  391. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  392. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  393. data/sig/phronomy/llm_context_window.rbs +10 -0
  394. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  395. data/sig/phronomy/multi_agent/store.rbs +54 -0
  396. data/sig/phronomy/multi_agent.rbs +6 -3
  397. data/sig/phronomy/persistence/errors.rbs +18 -0
  398. data/sig/phronomy/persistence/migration.rbs +13 -0
  399. data/sig/phronomy/persistence/transaction.rbs +19 -0
  400. data/sig/phronomy/persistence.rbs +1 -99
  401. data/sig/phronomy/persistence_composition.rbs +19 -0
  402. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  403. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  404. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  405. data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +2 -16
  406. data/sig/phronomy/tool.rbs +48 -4
  407. data/sig/phronomy/tracing/observation.rbs +7 -0
  408. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  409. data/sig/phronomy/vector_store/base.rbs +23 -0
  410. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  411. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  412. data/sig/phronomy/workflow/store.rbs +8 -0
  413. data/sig/phronomy/workflow.rbs +1 -1
  414. data/sig/phronomy.rbs +0 -49
  415. metadata +318 -117
  416. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  417. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  418. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  419. data/lib/phronomy/agent/context/capability/base.rb +0 -548
  420. data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
  421. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  422. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
  423. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
  424. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  425. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  426. data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
  427. data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
  428. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
  429. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
  430. data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
  431. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
  432. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
  433. data/lib/phronomy/agent/persistence/queries.rb +0 -61
  434. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  435. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  436. data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
  437. data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
  438. data/lib/phronomy/configuration/configuration.rb +0 -68
  439. data/lib/phronomy/configuration/global_configuration.rb +0 -26
  440. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  441. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  442. data/lib/phronomy/engine/execution_receiver.rb +0 -65
  443. data/lib/phronomy/engine/runnable.rb +0 -54
  444. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  445. data/lib/phronomy/llm_contract/token_usage.rb +0 -85
  446. data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
  447. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
  448. data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
  449. data/lib/phronomy/persistence/api/persistence.rb +0 -140
  450. data/lib/phronomy/persistence_composition/repositories.rb +0 -77
  451. data/lib/phronomy/recovery/recovery.rb +0 -186
  452. data/lib/phronomy/tool/contract/tool_error.rb +0 -7
  453. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  454. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  455. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  456. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
  457. data/sig/phronomy/execution_receiver.rbs +0 -34
  458. data/sig/phronomy/llm_adapter.rbs +0 -11
  459. data/sig/phronomy/runtime.rbs +0 -76
  460. /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
  461. /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
  462. /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
  463. /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
  464. /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
  465. /data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +0 -0
  466. /data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +0 -0
  467. /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
  468. /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
  469. /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
  470. /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
  471. /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
  472. /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
  473. /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
  474. /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
  475. /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module Agent
4
+ module Context
5
5
  # ContentStore-backed durable codec boundary for one Provider input manifest.
6
6
  # It is intentionally separate from Storage::DurableRecord.
7
7
  class LLMInputManifest
@@ -62,7 +62,7 @@ module Phronomy
62
62
  label: label
63
63
  )
64
64
  unless SEGMENT_DELIVERIES.include?(delivery)
65
- raise Phronomy::Storage::SerializationError,
65
+ raise Phronomy::Context::ManifestError,
66
66
  "LLMInputManifest segment delivery must be one of #{SEGMENT_DELIVERIES.inspect}, got #{delivery.inspect}"
67
67
  end
68
68
  tool_call_id = LLMInputManifest.send(
@@ -87,10 +87,10 @@ module Phronomy
87
87
  tool_call_id: tool_call_id,
88
88
  metadata: metadata
89
89
  )
90
- rescue Phronomy::Storage::SerializationError
90
+ rescue Phronomy::Context::ManifestError
91
91
  raise
92
92
  rescue => error
93
- raise Phronomy::Storage::SerializationError,
93
+ raise Phronomy::Context::ManifestError,
94
94
  "invalid LLMInputManifest segment: #{error.class}: #{error.message}"
95
95
  end
96
96
 
@@ -162,7 +162,7 @@ module Phronomy
162
162
  label = "LLMInputManifest"
163
163
  version = source.fetch("version")
164
164
  unless version.is_a?(String) && version == VERSION
165
- raise Phronomy::Storage::SerializationError,
165
+ raise Phronomy::Context::ManifestError,
166
166
  "unsupported LLMInputManifest version: #{version.inspect}; " \
167
167
  "current version is #{VERSION.inspect}"
168
168
  end
@@ -229,10 +229,10 @@ module Phronomy
229
229
  adapter_name: adapter_name,
230
230
  adapter_version: adapter_version
231
231
  )
232
- rescue Phronomy::Storage::SerializationError
232
+ rescue Phronomy::Context::ManifestError
233
233
  raise
234
234
  rescue => error
235
- raise Phronomy::Storage::SerializationError,
235
+ raise Phronomy::Context::ManifestError,
236
236
  "invalid LLMInputManifest: #{error.class}: #{error.message}"
237
237
  end
238
238
 
@@ -262,11 +262,11 @@ module Phronomy
262
262
 
263
263
  def strict_source!(hash, required:, optional:, label:)
264
264
  unless hash.is_a?(Hash)
265
- raise Phronomy::Storage::SerializationError,
265
+ raise Phronomy::Context::ManifestError,
266
266
  "#{label} must be a Hash"
267
267
  end
268
268
  unless hash.keys.all? { |key| key.is_a?(String) }
269
- raise Phronomy::Storage::SerializationError,
269
+ raise Phronomy::Context::ManifestError,
270
270
  "#{label} keys must all be String"
271
271
  end
272
272
 
@@ -277,13 +277,13 @@ module Phronomy
277
277
  details = []
278
278
  details << "missing=#{missing.inspect}" unless missing.empty?
279
279
  details << "unknown=#{unknown.inspect}" unless unknown.empty?
280
- raise Phronomy::Storage::SerializationError,
280
+ raise Phronomy::Context::ManifestError,
281
281
  "#{label} schema mismatch (#{details.join(", ")})"
282
282
  end
283
283
  Phronomy::CanonicalJSON.dump(hash)
284
284
  hash
285
285
  rescue ArgumentError => error
286
- raise Phronomy::Storage::SerializationError,
286
+ raise Phronomy::Context::ManifestError,
287
287
  "#{label} is not canonical JSON compatible: #{error.message}"
288
288
  end
289
289
 
@@ -291,7 +291,7 @@ module Phronomy
291
291
  value = hash.fetch(key)
292
292
  return value if value.is_a?(Integer)
293
293
 
294
- raise Phronomy::Storage::SerializationError,
294
+ raise Phronomy::Context::ManifestError,
295
295
  "#{label} #{key} must be an Integer"
296
296
  end
297
297
 
@@ -299,7 +299,7 @@ module Phronomy
299
299
  value = hash.fetch(key)
300
300
  return value if value.is_a?(Integer) && value.positive?
301
301
 
302
- raise Phronomy::Storage::SerializationError,
302
+ raise Phronomy::Context::ManifestError,
303
303
  "#{label} #{key} must be a positive Integer"
304
304
  end
305
305
 
@@ -307,7 +307,7 @@ module Phronomy
307
307
  value = hash.fetch(key)
308
308
  return value if value.is_a?(Integer) && value >= 0
309
309
 
310
- raise Phronomy::Storage::SerializationError,
310
+ raise Phronomy::Context::ManifestError,
311
311
  "#{label} #{key} must be a non-negative Integer"
312
312
  end
313
313
 
@@ -315,7 +315,7 @@ module Phronomy
315
315
  value = hash.fetch(key)
316
316
  return value if value.is_a?(String) && !value.empty?
317
317
 
318
- raise Phronomy::Storage::SerializationError,
318
+ raise Phronomy::Context::ManifestError,
319
319
  "#{label} #{key} must be a non-empty String"
320
320
  end
321
321
 
@@ -323,7 +323,7 @@ module Phronomy
323
323
  value = hash[key]
324
324
  return value if value.nil? || value.is_a?(String)
325
325
 
326
- raise Phronomy::Storage::SerializationError,
326
+ raise Phronomy::Context::ManifestError,
327
327
  "#{label} #{key} must be a String or nil"
328
328
  end
329
329
 
@@ -331,7 +331,7 @@ module Phronomy
331
331
  value = hash.fetch(key)
332
332
  return value if value.is_a?(String) && allowed.include?(value)
333
333
 
334
- raise Phronomy::Storage::SerializationError,
334
+ raise Phronomy::Context::ManifestError,
335
335
  "#{label} #{key} must be one of #{allowed.inspect}"
336
336
  end
337
337
 
@@ -339,7 +339,7 @@ module Phronomy
339
339
  value = hash.fetch(key)
340
340
  return value if value.is_a?(Array)
341
341
 
342
- raise Phronomy::Storage::SerializationError,
342
+ raise Phronomy::Context::ManifestError,
343
343
  "#{label} #{key} must be an Array"
344
344
  end
345
345
 
@@ -347,7 +347,7 @@ module Phronomy
347
347
  value = hash.fetch(key)
348
348
  return value if value.is_a?(Hash)
349
349
 
350
- raise Phronomy::Storage::SerializationError,
350
+ raise Phronomy::Context::ManifestError,
351
351
  "#{label} #{key} must be a Hash"
352
352
  end
353
353
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module Agent
4
+ module Context
5
5
  # Typed, canonicalizable input customization returned by before_llm_input.
6
6
  # v1 deliberately exposes only fields that are fully applied to every call.
7
7
  LLMInputPatch = Data.define(:model_config_patch, :segment_candidates) do
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ module Context
7
+ # Invalid canonical context input; independent of its persistence backend.
8
+ class ManifestError < Phronomy::Error; end
9
+ end
10
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Context
5
+ # A prompt template that substitutes {{variable}} placeholders in a string.
6
+ #
7
+ # @example Simple human template
8
+ # t = Phronomy::Context::PromptTemplate.new(template: "Translate to {{lang}}: {{text}}")
9
+ # t.format(lang: "French", text: "Hello")
10
+ # # => "Translate to French: Hello"
11
+ #
12
+ # @example With a system template
13
+ # t = Phronomy::Context::PromptTemplate.new(
14
+ # template: "{{question}}",
15
+ # system_template: "You are a {{role}} assistant."
16
+ # )
17
+ # t.format_system(role: "helpful")
18
+ # # => "You are a helpful assistant."
19
+ #
20
+ # As a Runnable, #invoke accepts a Hash of variables and returns a Hash
21
+ # with :prompt (and optionally :system) keys.
22
+ class PromptTemplate
23
+ include Phronomy::Runnable
24
+
25
+ PLACEHOLDER = /\{\{(\w+)\}\}/
26
+
27
+ attr_reader :template, :system_template
28
+
29
+ # @param template [String] human message template with {{var}} placeholders
30
+ # @param system_template [String, nil] optional system message template
31
+ # @api public
32
+ def initialize(template:, system_template: nil)
33
+ @template = template
34
+ @system_template = system_template
35
+ end
36
+
37
+ # Substitute all {{var}} placeholders in the human template.
38
+ #
39
+ # @param variables [Hash{Symbol => String}]
40
+ # @return [String]
41
+ # @api public
42
+ def format(**variables)
43
+ substitute(@template, variables)
44
+ end
45
+
46
+ # Substitute all {{var}} placeholders in the system template.
47
+ # Returns nil when no system template was set.
48
+ #
49
+ # @param variables [Hash{Symbol => String}]
50
+ # @return [String, nil]
51
+ # @api public
52
+ def format_system(**variables)
53
+ @system_template && substitute(@system_template, variables)
54
+ end
55
+
56
+ # Runnable interface: accepts a Hash of variable values.
57
+ # Returns { prompt: String, system: String|nil }.
58
+ #
59
+ # @param input [Hash{Symbol => String}]
60
+ # @return [Hash]
61
+ # @api public
62
+ def invoke(input, config: {})
63
+ vars = normalize_input(input)
64
+ result = {prompt: format(**vars)}
65
+ sys = format_system(**vars)
66
+ result[:system] = sys if sys
67
+ result
68
+ end
69
+
70
+ # Returns the list of placeholder names found in both templates.
71
+ #
72
+ # @return [Array<Symbol>]
73
+ # @api public
74
+ def variables
75
+ names = @template.scan(PLACEHOLDER).flatten
76
+ names += @system_template.scan(PLACEHOLDER).flatten if @system_template
77
+ names.map(&:to_sym).uniq
78
+ end
79
+
80
+ private
81
+
82
+ def substitute(text, variables)
83
+ text.gsub(PLACEHOLDER) do |match|
84
+ key = Regexp.last_match(1).to_sym
85
+ variables.fetch(key) { raise KeyError, "Missing variable: {{#{key}}}" }
86
+ end
87
+ end
88
+
89
+ def normalize_input(input)
90
+ case input
91
+ when Hash then input
92
+ when String then {input: input}
93
+ else raise ArgumentError, "PromptTemplate#invoke expects a Hash of variables, got #{input.class}"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Loader
6
6
  # Abstract base class for document loaders.
7
7
  #
@@ -3,7 +3,7 @@
3
3
  require "csv"
4
4
 
5
5
  module Phronomy
6
- module VectorStore
6
+ module Documents
7
7
  module Loader
8
8
  # Loads a CSV file, converting each row into a separate document.
9
9
  #
@@ -12,7 +12,7 @@ module Phronomy
12
12
  # a human-readable "key: value" string for embedding.
13
13
  #
14
14
  # @example
15
- # loader = Phronomy::VectorStore::Loader::CsvLoader.new
15
+ # loader = Phronomy::Documents::Loader::CsvLoader.new
16
16
  # docs = loader.load("products.csv")
17
17
  # # => [
18
18
  # # { text: "name: Widget\nprice: 9.99", metadata: { source: "...", row: 1, name: "Widget", price: "9.99" } },
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Loader
6
6
  # Loads a Markdown file, optionally splitting on top-level headings.
7
7
  #
@@ -10,12 +10,12 @@ module Phronomy
10
10
  # rather than the full file at once.
11
11
  #
12
12
  # @example Single document (heading split disabled)
13
- # loader = Phronomy::VectorStore::Loader::MarkdownLoader.new(split_on_headings: false)
13
+ # loader = Phronomy::Documents::Loader::MarkdownLoader.new(split_on_headings: false)
14
14
  # docs = loader.load("README.md")
15
15
  # # => [{ text: "# Title\n...", metadata: { source: "README.md" } }]
16
16
  #
17
17
  # @example Split per heading (default)
18
- # loader = Phronomy::VectorStore::Loader::MarkdownLoader.new
18
+ # loader = Phronomy::Documents::Loader::MarkdownLoader.new
19
19
  # docs = loader.load("guide.md")
20
20
  # # => [
21
21
  # # { text: "# Section 1\n...", metadata: { source: "guide.md", section: "Section 1" } },
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Loader
6
6
  # Loads a plain-text file as a single document.
7
7
  #
8
8
  # @example
9
- # loader = Phronomy::VectorStore::Loader::PlainTextLoader.new
9
+ # loader = Phronomy::Documents::Loader::PlainTextLoader.new
10
10
  # docs = loader.load("/path/to/file.txt")
11
11
  # # => [{ text: "...", metadata: { source: "/path/to/file.txt" } }]
12
12
  class PlainTextLoader < Base
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Splitter
6
6
  # Abstract base class for text splitters.
7
7
  #
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Splitter
6
6
  # Splits text into fixed-size character chunks with optional overlap.
7
7
  #
8
8
  # @example
9
- # splitter = Phronomy::VectorStore::Splitter::FixedSizeSplitter.new(chunk_size: 200, chunk_overlap: 20)
9
+ # splitter = Phronomy::Documents::Splitter::FixedSizeSplitter.new(chunk_size: 200, chunk_overlap: 20)
10
10
  # chunks = splitter.split({ text: long_text, metadata: { source: "doc.txt" } })
11
11
  # # => [
12
12
  # # { text: "...(200 chars)...", metadata: { source: "doc.txt", chunk: 0 } },
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Splitter
6
6
  # Splits text recursively using a prioritised list of separator strings.
7
7
  #
@@ -18,7 +18,7 @@ module Phronomy
18
18
  # 5. "" — character-level fallback
19
19
  #
20
20
  # @example
21
- # splitter = Phronomy::VectorStore::Splitter::RecursiveSplitter.new(chunk_size: 300, chunk_overlap: 30)
21
+ # splitter = Phronomy::Documents::Splitter::RecursiveSplitter.new(chunk_size: 300, chunk_overlap: 30)
22
22
  # chunks = splitter.split({ text: long_markdown, metadata: { source: "guide.md" } })
23
23
  class RecursiveSplitter < Base
24
24
  DEFAULT_SEPARATORS = ["\n\n", "\n", ". ", " ", ""].freeze
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Embeddings
5
+ # Framework-owned asynchronous execution of a synchronous embedding adapter.
6
+ #
7
+ # Construction starts no Runtime. Each operation uses the current default
8
+ # pool unless a caller-owned pool was injected. Timeout includes queue wait
9
+ # and settles the result without forcibly interrupting an executing adapter.
10
+ #
11
+ # @api public
12
+ class AsyncClient
13
+ # @param adapter [Embeddings::Base] synchronous embedding adapter
14
+ # @param pool [Concurrency::OffloadPool, nil] optional execution pool
15
+ # @api public
16
+ def initialize(adapter:, pool: nil)
17
+ @adapter = adapter
18
+ @pool = pool
19
+ end
20
+
21
+ # Submits a synchronous embed call without waiting for queue capacity.
22
+ #
23
+ # @param text [String]
24
+ # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
25
+ # @param timeout [Numeric, nil] operation-wide submit timeout
26
+ # @return [Phronomy::TaskResult] the pool's original completion handle
27
+ # @api public
28
+ def embed_async(text, cancellation_token = nil, timeout: nil)
29
+ Phronomy::Execution.submit(pool: default_pool,
30
+ timeout: timeout,
31
+ cancellation_token: cancellation_token,
32
+ on_full: :raise) do
33
+ @adapter.embed(text, cancellation_token)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def default_pool
40
+ @pool
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Embeddings
5
+ # Embeddings adapter backed by RubyLLM.
6
+ #
7
+ # Delegates to +RubyLLM.embed+ and returns the resulting vector as an
8
+ # +Array<Float>+.
9
+ #
10
+ # @example Default model
11
+ # embeddings = Phronomy::Embeddings::RubyLLMEmbeddings.new
12
+ # vector = embeddings.embed("Hello, world!")
13
+ #
14
+ # @example Explicit model
15
+ # embeddings = Phronomy::Embeddings::RubyLLMEmbeddings.new(model: "text-embedding-3-small")
16
+ # vector = embeddings.embed("Hello, world!")
17
+ class RubyLLMEmbeddings < Base
18
+ # @param model [String, nil] embedding model identifier; nil uses the RubyLLM default
19
+ # @param provider [Symbol, nil] provider override (e.g. :openai); nil uses the RubyLLM default
20
+ # @param assume_model_exists [Boolean] when true, skips RubyLLM model-registry validation
21
+ # (useful for locally hosted models not in the registry)
22
+ # @api public
23
+ def initialize(model: nil, provider: nil, assume_model_exists: false)
24
+ @model = model
25
+ @provider = provider
26
+ @assume_model_exists = assume_model_exists
27
+ end
28
+
29
+ protected
30
+
31
+ # Embed text via RubyLLM.
32
+ #
33
+ # @param text [String]
34
+ # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] optional; raises CancellationError when cancelled
35
+ # @return [Array<Float>]
36
+ # @api public
37
+ def perform_embed(text, cancellation_token = nil)
38
+ opts = {}
39
+ opts[:model] = @model if @model
40
+ opts[:provider] = @provider if @provider
41
+ opts[:assume_model_exists] = true if @assume_model_exists
42
+ ::RubyLLM.embed(text, **opts).vectors
43
+ rescue ::RubyLLM::Error => error
44
+ raise TransportError, error.message
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "invalid_result_error"
4
+
5
+ module Phronomy
6
+ module Embeddings
7
+ # Owns input validation, the cancellation checkpoint and result validation.
8
+ # Providers implement perform_embed; execution belongs to AsyncClient.
9
+ # This contract selects no provider or vector store.
10
+ # @api public
11
+ class Base
12
+ # @param text [String] empty text is allowed; model restrictions are provider-owned
13
+ # @param cancellation_token [#raise_if_cancelled!, nil]
14
+ # @return [Array<Float>] non-empty finite vector
15
+ # @raise [ArgumentError] when input is not text
16
+ # @raise [InvalidResultError] when the provider returns a malformed vector
17
+ # @api public
18
+ def embed(text, cancellation_token = nil)
19
+ cancellation_token&.raise_if_cancelled!
20
+ raise ArgumentError, "text must be a String" unless text.is_a?(String)
21
+ vector = perform_embed(text, cancellation_token)
22
+ unless vector.is_a?(Array) && !vector.empty? && vector.all? { |value|
23
+ value.is_a?(Numeric) && value.real? && value.finite? && value.to_f.finite?
24
+ }
25
+ raise InvalidResultError, "embed must return a non-empty Array of finite real numbers"
26
+ end
27
+ vector.map(&:to_f)
28
+ end
29
+
30
+ protected
31
+
32
+ # Provider extension point. Preserve cancellation and application errors;
33
+ # SDK adapters translate their own provider errors to TransportError.
34
+ # @param text [String]
35
+ # @param cancellation_token [#raise_if_cancelled!, nil]
36
+ # @return [Array<Numeric>]
37
+ # @api public
38
+ def perform_embed(text, cancellation_token = nil)
39
+ raise NotImplementedError, "#{self.class}#perform_embed is not implemented"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ module Embeddings
7
+ # A provider returned data that violates the embedding result contract.
8
+ # @api public
9
+ class InvalidResultError < Phronomy::Error; end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ module Embeddings
7
+ # An embedding provider failed; the original SDK exception remains #cause.
8
+ # @api public
9
+ class TransportError < Phronomy::Error; end
10
+ end
11
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "worker_input_restricted"
3
+ require_relative "../../execution/concurrency/worker_input_restricted"
4
4
 
5
5
  module Phronomy
6
6
  module Concurrency
@@ -81,14 +81,12 @@ module Phronomy
81
81
  @submitted_at = submitted_at ||
82
82
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
83
83
  @mutex = Mutex.new
84
- @timer_subscriptions = Subscriptions.new
84
+ @timer_subscriptions = ResultSubscriptions.new
85
+ @cancellation_subscriptions = ResultSubscriptions.new
85
86
 
86
87
  # Explicit submit cancellation is operation-wide. Deadline-only tokens are
87
88
  # promoted to cancel! by OffloadPool#submit using the Runtime timer queue.
88
- @cancellation_callback = if @cancellation_token
89
- -> { fire_cancellation! }
90
- end
91
- @cancellation_token&.on_cancel(&@cancellation_callback)
89
+ @cancellation_subscriptions.explicit_cancellation(@cancellation_token) { fire_cancellation! }
92
90
  end
93
91
 
94
92
  # @return [Boolean] true when caller-facing settlement has been claimed
@@ -260,7 +258,7 @@ module Phronomy
260
258
  def notify_abandoned
261
259
  @on_abandoned&.call(self)
262
260
  rescue => error
263
- Phronomy.configuration.logger&.error do
261
+ Phronomy::RuntimeSettings.current.logger&.error do
264
262
  "OffloadPool abandoned callback failed: #{error.class}: #{error.message}"
265
263
  end
266
264
  end
@@ -293,13 +291,7 @@ module Phronomy
293
291
 
294
292
  def detach_submit_cancellation
295
293
  @timer_subscriptions.close
296
- return unless @cancellation_token && @cancellation_callback
297
-
298
- @cancellation_token.send(
299
- :unregister_cancel_callback,
300
- @cancellation_callback
301
- )
302
- @cancellation_callback = nil
294
+ @cancellation_subscriptions.close
303
295
  end
304
296
  end
305
297
  private_constant :Operation
@@ -384,7 +376,7 @@ module Phronomy
384
376
  full_timeout: nil,
385
377
  &block
386
378
  )
387
- if Phronomy::Runtime.in_event_loop_context? && on_full != :raise
379
+ if Phronomy::WaitPolicy.blocking_forbidden? && on_full != :raise
388
380
  raise Phronomy::EventLoopReentrancyError,
389
381
  "OffloadPool admission cannot wait on EventLoop; use on_full: :raise"
390
382
  end
@@ -491,19 +483,42 @@ module Phronomy
491
483
  task
492
484
  end
493
485
 
494
- # Gracefully drains the pool and terminates all worker threads.
495
- # Waits up to +drain_timeout+ seconds for in-flight operations to finish.
486
+ # Close admission without waiting for worker termination. Already queued
487
+ # operations drain normally; running workers are never interrupted.
488
+ # @api private
489
+ def begin_shutdown
490
+ @shutdown = true
491
+ @queue.close
492
+ self
493
+ end
494
+
495
+ # Whether shutdown has closed admission and all workers have exited.
496
+ # Logical TaskResult settlement alone does not establish this fact.
497
+ # @api private
498
+ def terminated?
499
+ @shutdown && @workers.none?(&:alive?)
500
+ end
501
+
502
+ # Gracefully drains the pool and waits for worker threads to terminate.
503
+ # All workers share +drain_timeout+ or the supplied monotonic +deadline+.
504
+ # A deadline expiry leaves workers running; callers inspect terminated?.
496
505
  #
497
506
  # Closing the underlying SizedQueue signals workers to exit after draining
498
507
  # remaining items, without blocking on a full-queue push.
499
508
  #
500
509
  # @param drain_timeout [Numeric] seconds to wait for workers to finish
510
+ # @param deadline [Numeric, nil] absolute monotonic shutdown deadline
501
511
  # @return [self]
502
512
  # @api private
503
- def shutdown(drain_timeout: 30)
504
- @shutdown = true
505
- @queue.close
506
- @workers.each { |thread| thread.join(drain_timeout) }
513
+ def shutdown(drain_timeout: 30, deadline: nil)
514
+ deadline ||= Process.clock_gettime(Process::CLOCK_MONOTONIC) + drain_timeout
515
+ begin_shutdown
516
+ @workers.each do |thread|
517
+ remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
518
+ break if remaining <= 0
519
+
520
+ thread.join(remaining)
521
+ end
507
522
  self
508
523
  end
509
524