phronomy 0.26.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 (557) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +92 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +13 -3
  6. data/VERIFY.sh +27 -27
  7. data/benchmark/bench_agent_invoke.rb +26 -47
  8. data/benchmark/bench_context_assembler.rb +17 -15
  9. data/benchmark/bench_regression.rb +1 -1
  10. data/benchmark/bench_tool_schema.rb +1 -1
  11. data/docs/architecture/agent-chat-and-state-ownership.md +153 -0
  12. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  13. data/docs/architecture/agent-context.md +4 -2
  14. data/docs/architecture/agent-transition-ownership.md +13 -0
  15. data/docs/architecture/backend-groups.md +152 -0
  16. data/docs/architecture/before-llm-input.md +9 -3
  17. data/docs/architecture/context-management.md +31 -1
  18. data/docs/architecture/context-preparation-steps.md +90 -0
  19. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  20. data/docs/architecture/execution-abstraction-review.md +88 -0
  21. data/docs/architecture/execution-boundaries.md +28 -0
  22. data/docs/architecture/execution-metadata-and-values.md +86 -0
  23. data/docs/architecture/generator-verifier-ownership.md +111 -0
  24. data/docs/architecture/multi-agent-handoff.md +11 -4
  25. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  26. data/docs/architecture/persistence.md +45 -4
  27. data/docs/architecture/r8-unit1.md +104 -0
  28. data/docs/architecture/r8-unit10.md +72 -0
  29. data/docs/architecture/r8-unit11.md +71 -0
  30. data/docs/architecture/r8-unit12.md +67 -0
  31. data/docs/architecture/r8-unit13.md +61 -0
  32. data/docs/architecture/r8-unit14.md +72 -0
  33. data/docs/architecture/r8-unit15.md +65 -0
  34. data/docs/architecture/r8-unit16.md +54 -0
  35. data/docs/architecture/r8-unit17.md +73 -0
  36. data/docs/architecture/r8-unit18.md +92 -0
  37. data/docs/architecture/r8-unit19.md +61 -0
  38. data/docs/architecture/r8-unit2.md +55 -0
  39. data/docs/architecture/r8-unit20.md +51 -0
  40. data/docs/architecture/r8-unit21.md +79 -0
  41. data/docs/architecture/r8-unit22.md +74 -0
  42. data/docs/architecture/r8-unit23.md +81 -0
  43. data/docs/architecture/r8-unit3.md +130 -0
  44. data/docs/architecture/r8-unit4.md +79 -0
  45. data/docs/architecture/r8-unit5.md +115 -0
  46. data/docs/architecture/r8-unit6.md +71 -0
  47. data/docs/architecture/r8-unit7.md +70 -0
  48. data/docs/architecture/r8-unit8.md +78 -0
  49. data/docs/architecture/r8-unit9.md +73 -0
  50. data/docs/architecture/refactoring-closure.md +343 -0
  51. data/docs/architecture/remaining-refactoring-plan.md +390 -0
  52. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  53. data/docs/architecture/storage-execution-boundaries.md +54 -0
  54. data/docs/architecture/tool-schema-recording-gap.md +56 -0
  55. data/docs/architecture/tracing.md +2 -2
  56. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  57. data/docs/architecture.md +222 -0
  58. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  59. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  60. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  61. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  62. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  63. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  64. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +7 -0
  65. data/docs/decisions/031-durable-multi-agent-coordination.md +7 -0
  66. data/docs/decisions/032-storage-backend-composition.md +85 -0
  67. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  68. data/docs/decisions/034-handoff-runner-coordination-ownership.md +78 -0
  69. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  70. data/docs/decisions/036-context-contract-ownership.md +86 -0
  71. data/docs/decisions/037-common-definition-ownership.md +62 -0
  72. data/docs/decisions/038-responsibility-based-source-layout.md +145 -0
  73. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +82 -0
  74. data/docs/decisions/040-configuration-default-composition.md +105 -0
  75. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  76. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  77. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  78. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  79. data/docs/decisions/045-worker-input-restriction-ownership.md +104 -0
  80. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  81. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  82. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  83. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  84. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  85. data/docs/decisions/051-execution-outcome-worker-ownership.md +153 -0
  86. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  87. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  88. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  89. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  90. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  91. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  92. data/docs/decisions/058-neutral-storage-primitives.md +83 -0
  93. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  94. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  95. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  96. data/docs/decisions/062-content-store-service.md +52 -0
  97. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  98. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  99. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  100. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  101. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  102. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  103. data/docs/decisions/README.md +52 -6
  104. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  105. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  106. data/docs/execution-boundaries-preview.md +45 -0
  107. data/docs/features.md +16 -5
  108. data/docs/getting-started.md +11 -8
  109. data/docs/migrations/0.15.md +5 -0
  110. data/docs/migrations/0.22.md +2 -2
  111. data/docs/migrations/durable-semantic-coordination-v2.md +8 -0
  112. data/docs/migrations/handoff-runner-multi-agent.md +37 -0
  113. data/docs/migrations/neutral-storage-spi.md +43 -0
  114. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  115. data/docs/migrations/persistence-failure-contracts.md +67 -0
  116. data/docs/migrations/r8-unit10.md +36 -0
  117. data/docs/migrations/r8-unit11.md +33 -0
  118. data/docs/migrations/r8-unit12.md +31 -0
  119. data/docs/migrations/r8-unit13.md +36 -0
  120. data/docs/migrations/r8-unit14.md +31 -0
  121. data/docs/migrations/r8-unit15.md +31 -0
  122. data/docs/migrations/r8-unit16.md +26 -0
  123. data/docs/migrations/r8-unit17.md +28 -0
  124. data/docs/migrations/r8-unit18.md +29 -0
  125. data/docs/migrations/r8-unit19.md +28 -0
  126. data/docs/migrations/r8-unit20.md +24 -0
  127. data/docs/migrations/r8-unit21.md +28 -0
  128. data/docs/migrations/r8-unit22.md +29 -0
  129. data/docs/migrations/r8-unit23.md +20 -0
  130. data/docs/migrations/r8-unit4.md +67 -0
  131. data/docs/migrations/r8-unit5.md +75 -0
  132. data/docs/migrations/r8-unit6.md +109 -0
  133. data/docs/migrations/r8-unit7.md +31 -0
  134. data/docs/migrations/r8-unit8.md +33 -0
  135. data/docs/migrations/r8-unit9.md +28 -0
  136. data/docs/migrations/shared-state-multi-agent.md +44 -0
  137. data/docs/migrations/storage-async-client.md +103 -0
  138. data/docs/migrations/storage-backend-composition.md +117 -0
  139. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  140. data/docs/migrations/vector-async-clients.md +110 -0
  141. data/docs/persistence-backends.md +190 -215
  142. data/docs/runtime-and-concurrency.md +142 -32
  143. data/lib/phronomy/agent/admission.rb +78 -0
  144. data/lib/phronomy/{agent_already_exists_error.rb → agent/agent_already_exists_error.rb} +2 -0
  145. data/lib/phronomy/{agent_busy_error.rb → agent/agent_busy_error.rb} +2 -0
  146. data/lib/phronomy/{agent_purged_error.rb → agent/agent_purged_error.rb} +2 -0
  147. data/lib/phronomy/agent/api/agent.rb +17 -0
  148. data/lib/phronomy/{execution_rehydration_required_error.rb → agent/api/execution_rehydration_required_error.rb} +2 -0
  149. data/lib/phronomy/agent/async_event_api.rb +38 -33
  150. data/lib/phronomy/agent/base.rb +106 -294
  151. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  152. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  153. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  154. data/lib/phronomy/agent/context_importer.rb +2 -2
  155. data/lib/phronomy/agent/{context_assembler.rb → context_preparation.rb} +157 -288
  156. data/lib/phronomy/agent/control_request.rb +21 -0
  157. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  158. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +32 -63
  159. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  160. data/lib/phronomy/agent/execution/dispatch_preparation.rb +195 -0
  161. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +43 -23
  162. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +8 -5
  163. data/lib/phronomy/agent/execution/execution_coordinator.rb +1744 -0
  164. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  165. data/lib/phronomy/agent/execution/execution_metadata.rb +54 -0
  166. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +288 -0
  167. data/lib/phronomy/agent/execution/execution_registry.rb +463 -0
  168. data/lib/phronomy/agent/execution/execution_session_runner.rb +113 -0
  169. data/lib/phronomy/agent/execution/initial_preparation.rb +323 -0
  170. data/lib/phronomy/agent/execution/invocation_actions.rb +368 -0
  171. data/lib/phronomy/agent/execution/invocation_transitions.rb +124 -0
  172. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  173. data/lib/phronomy/agent/execution_change.rb +161 -0
  174. data/lib/phronomy/agent/execution_environment.rb +36 -0
  175. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  176. data/lib/phronomy/agent/execution_observation.rb +35 -0
  177. data/lib/phronomy/agent/filterable.rb +182 -0
  178. data/lib/phronomy/agent/{handoff_context.rb → handoff/transfer_context.rb} +6 -6
  179. data/lib/phronomy/agent/{handoff_projection.rb → handoff/transfer_projection.rb} +14 -33
  180. data/lib/phronomy/agent/handoff_error.rb +7 -0
  181. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  182. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  183. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  184. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  185. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  186. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  187. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -15
  188. data/lib/phronomy/agent/persistence/agent_repository.rb +69 -0
  189. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  190. data/lib/phronomy/agent/persistence/codec.rb +338 -0
  191. data/lib/phronomy/agent/persistence/execution_repository.rb +124 -0
  192. data/lib/phronomy/agent/persistence/journal_repository.rb +63 -0
  193. data/lib/phronomy/agent/persistence/records.rb +33 -0
  194. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  195. data/lib/phronomy/agent/persistence/storage_schema.rb +29 -0
  196. data/lib/phronomy/agent/persistence/watermark.rb +29 -0
  197. data/lib/phronomy/agent/recovery/invocation_restorer.rb +124 -0
  198. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +28 -51
  199. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +50 -55
  200. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +38 -43
  201. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +26 -23
  202. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  203. data/lib/phronomy/agent/recovery/recovery_support.rb +224 -0
  204. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  205. data/lib/phronomy/agent/retention.rb +17 -0
  206. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  207. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  208. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  209. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  210. data/lib/phronomy/agent/{ruby_llm_materializer.rb → runtime_input.rb} +19 -39
  211. data/lib/phronomy/agent/saved_context_reader.rb +55 -0
  212. data/lib/phronomy/agent/settings.rb +47 -0
  213. data/lib/phronomy/agent/state_writer.rb +166 -0
  214. data/lib/phronomy/agent/store.rb +253 -0
  215. data/lib/phronomy/{stream_callback_error.rb → agent/stream_callback_error.rb} +2 -0
  216. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  217. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  218. data/lib/phronomy/agent/tool_execution/tool_binding.rb +30 -0
  219. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +114 -248
  220. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  221. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  222. data/lib/phronomy/common/configuration_error.rb +7 -0
  223. data/lib/phronomy/common/error.rb +5 -0
  224. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  225. data/lib/phronomy/common/values/serializable.rb +32 -0
  226. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  227. data/lib/phronomy/content_store/storage_schema.rb +12 -0
  228. data/lib/phronomy/content_store/stored_contents.rb +44 -0
  229. data/lib/phronomy/context/assembly.rb +202 -0
  230. data/lib/phronomy/{context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +5 -1
  231. data/lib/phronomy/context/candidate.rb +51 -0
  232. data/lib/phronomy/context/constraint.rb +47 -0
  233. data/lib/phronomy/{agent → context}/context_plan.rb +2 -2
  234. data/lib/phronomy/{agent → context}/context_plan_validator.rb +3 -3
  235. data/lib/phronomy/{agent → context}/context_policy.rb +1 -1
  236. data/lib/phronomy/{agent → context}/context_policy_input.rb +7 -7
  237. data/lib/phronomy/{agent → context}/context_policy_input_builder.rb +1 -1
  238. data/lib/phronomy/context/default_policy.rb +140 -0
  239. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  240. data/lib/phronomy/{agent → context}/llm_input_build_context.rb +2 -2
  241. data/lib/phronomy/{agent → context}/llm_input_manifest.rb +21 -21
  242. data/lib/phronomy/{agent → context}/llm_input_patch.rb +3 -3
  243. data/lib/phronomy/context/manifest_error.rb +10 -0
  244. data/lib/phronomy/context/prompt_template.rb +98 -0
  245. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  246. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  247. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  248. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  249. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  250. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  251. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  252. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  253. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  254. data/lib/phronomy/embeddings/base.rb +43 -0
  255. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  256. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  257. data/lib/phronomy/engine/concurrency/offload_pool.rb +39 -20
  258. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  259. data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +1 -1
  260. data/lib/phronomy/engine/event_loop.rb +164 -630
  261. data/lib/phronomy/engine/execution_binding.rb +23 -0
  262. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  263. data/lib/phronomy/engine/fsm_protocol.rb +9 -0
  264. data/lib/phronomy/engine/fsm_session.rb +37 -30
  265. data/lib/phronomy/engine/metrics.rb +28 -0
  266. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  267. data/lib/phronomy/engine/runtime.rb +104 -95
  268. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  269. data/lib/phronomy/execution/async_operation.rb +41 -0
  270. data/lib/phronomy/execution/backpressure_error.rb +7 -0
  271. data/lib/phronomy/{blocking.rb → execution/blocking.rb} +2 -2
  272. data/lib/phronomy/execution/cancellation_error.rb +7 -0
  273. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +5 -1
  274. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  275. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  276. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  277. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  278. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  279. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  280. data/lib/phronomy/execution/concurrency/worker_input_restricted.rb +14 -0
  281. data/lib/phronomy/execution/event_loop_reentrancy_error.rb +8 -0
  282. data/lib/phronomy/{execution.rb → execution/execution.rb} +57 -30
  283. data/lib/phronomy/{execution_cancellation_error.rb → execution/execution_cancellation_error.rb} +2 -0
  284. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  285. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  286. data/lib/phronomy/{execution_timeout_error.rb → execution/execution_timeout_error.rb} +2 -0
  287. data/lib/phronomy/{invalid_async_entry_action_error.rb → execution/invalid_async_entry_action_error.rb} +2 -0
  288. data/lib/phronomy/{invalid_async_transition_action_error.rb → execution/invalid_async_transition_action_error.rb} +2 -0
  289. data/lib/phronomy/{invalid_async_workflow_action_error.rb → execution/invalid_async_workflow_action_error.rb} +2 -0
  290. data/lib/phronomy/{invocation_context.rb → execution/invocation_context.rb} +0 -15
  291. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  292. data/lib/phronomy/execution/pool_shutdown_error.rb +7 -0
  293. data/lib/phronomy/execution/recursion_limit_error.rb +7 -0
  294. data/lib/phronomy/execution/runnable.rb +24 -0
  295. data/lib/phronomy/execution/runtime_shutdown_error.rb +7 -0
  296. data/lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb +7 -0
  297. data/lib/phronomy/execution/scheduler_reentrancy_error.rb +9 -0
  298. data/lib/phronomy/{engine → execution}/task_result.rb +7 -3
  299. data/lib/phronomy/execution/timeout_error.rb +7 -0
  300. data/lib/phronomy/execution/wait_policy.rb +24 -0
  301. data/lib/phronomy/filter/filter_block_error.rb +14 -0
  302. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  303. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  304. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  305. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  306. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  307. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  308. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  309. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  310. data/lib/phronomy/llm_adapter/authentication_error.rb +9 -0
  311. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  312. data/lib/phronomy/llm_adapter/base.rb +49 -68
  313. data/lib/phronomy/llm_adapter/context_length_error.rb +9 -0
  314. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  315. data/lib/phronomy/llm_adapter/message.rb +62 -0
  316. data/lib/phronomy/llm_adapter/rate_limit_error.rb +9 -0
  317. data/lib/phronomy/llm_adapter/request.rb +79 -0
  318. data/lib/phronomy/llm_adapter/response.rb +36 -0
  319. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  320. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  321. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  322. data/lib/phronomy/llm_adapter.rb +3 -16
  323. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  324. data/lib/phronomy/multi_agent/admission_registry.rb +15 -2
  325. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  326. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  327. data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
  328. data/lib/phronomy/{agent → multi_agent}/handoff_capability_factory.rb +21 -2
  329. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  330. data/lib/phronomy/{agent → multi_agent}/handoff_policy.rb +1 -1
  331. data/lib/phronomy/multi_agent/handoff_runner.rb +156 -0
  332. data/lib/phronomy/{agent → multi_agent}/handoff_state.rb +2 -2
  333. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  334. data/lib/phronomy/multi_agent/persistence/codec.rb +75 -0
  335. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  336. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  337. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  338. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +124 -0
  339. data/lib/phronomy/multi_agent/persistence/team_repository.rb +69 -0
  340. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  341. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  342. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  343. data/lib/phronomy/multi_agent/store.rb +156 -0
  344. data/lib/phronomy/multi_agent/team_coordinator.rb +87 -75
  345. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  346. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +5 -5
  347. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  348. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  349. data/lib/phronomy/persistence/content_repository.rb +26 -0
  350. data/lib/phronomy/persistence/errors.rb +16 -0
  351. data/lib/phronomy/persistence/migration/initial_format_migration.rb +21 -21
  352. data/lib/phronomy/persistence/persistence.rb +23 -0
  353. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  354. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  355. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  356. data/lib/phronomy/persistence/transaction.rb +86 -0
  357. data/lib/phronomy/persistence_composition/storage_schema.rb +29 -0
  358. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  359. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  360. data/lib/phronomy/runtime_composition/agent_defaults.rb +11 -0
  361. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  362. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  363. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  364. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  365. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  366. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  367. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  368. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  369. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  370. data/lib/phronomy/storage/async/async_client.rb +60 -0
  371. data/lib/phronomy/storage/backend.rb +107 -0
  372. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  373. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  374. data/lib/phronomy/storage/blobs.rb +31 -0
  375. data/lib/phronomy/storage/condition.rb +25 -0
  376. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  377. data/lib/phronomy/storage/conflict_error.rb +9 -0
  378. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  379. data/lib/phronomy/storage/entry.rb +33 -0
  380. data/lib/phronomy/storage/guard_ref.rb +13 -0
  381. data/lib/phronomy/storage/not_found_error.rb +9 -0
  382. data/lib/phronomy/storage/record_codec.rb +177 -0
  383. data/lib/phronomy/storage/records.rb +61 -0
  384. data/lib/phronomy/storage/resource.rb +126 -0
  385. data/lib/phronomy/storage/scope.rb +25 -0
  386. data/lib/phronomy/storage/serialization_error.rb +9 -0
  387. data/lib/phronomy/storage/streams.rb +44 -0
  388. data/lib/phronomy/storage/transaction_error.rb +10 -0
  389. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  390. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  391. data/lib/phronomy/storage/validation.rb +53 -0
  392. data/lib/phronomy/storage/view.rb +131 -0
  393. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  394. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  395. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  396. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  397. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +54 -48
  398. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +1 -1
  399. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +1 -1
  400. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  401. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +52 -46
  402. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  403. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +124 -0
  404. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  405. data/lib/phronomy/tool/authorization.rb +77 -0
  406. data/lib/phronomy/tool/base.rb +549 -9
  407. data/lib/phronomy/tool/call_request.rb +40 -0
  408. data/lib/phronomy/{agent/tool_definition_set.rb → tool/definition_set.rb} +16 -12
  409. data/lib/phronomy/tool/operation.rb +88 -0
  410. data/lib/phronomy/tool/schema.rb +214 -0
  411. data/lib/phronomy/tool/settings.rb +31 -0
  412. data/lib/phronomy/tool/tool_error.rb +7 -0
  413. data/lib/phronomy/{agent → tool}/tool_executor.rb +10 -7
  414. data/lib/phronomy/tools/agent.rb +22 -90
  415. data/lib/phronomy/tools/mcp.rb +21 -14
  416. data/lib/phronomy/tools/vector_search.rb +4 -4
  417. data/lib/phronomy/tracing/automatic.rb +11 -6
  418. data/lib/phronomy/tracing/base.rb +1 -1
  419. data/lib/phronomy/tracing/observation.rb +37 -0
  420. data/lib/phronomy/tracing/settings.rb +36 -0
  421. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  422. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  423. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  424. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  425. data/lib/phronomy/vector_store/base.rb +118 -49
  426. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  427. data/lib/phronomy/vector_store.rb +2 -17
  428. data/lib/phronomy/version.rb +1 -1
  429. data/lib/phronomy/workflow/completion.rb +11 -0
  430. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
  431. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  432. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +5 -1
  433. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  434. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  435. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +186 -0
  436. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +145 -115
  437. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  438. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  439. data/lib/phronomy/workflow/persistence/state_repository.rb +63 -0
  440. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  441. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  442. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  443. data/lib/phronomy/workflow/runtime_binding/workflow_terminal_policy.rb +40 -0
  444. data/lib/phronomy.rb +97 -98
  445. data/scripts/api_snapshot.rb +36 -16
  446. data/scripts/storage_spi_snapshot.rb +36 -0
  447. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  448. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  449. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  450. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  451. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  452. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  453. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  454. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  455. data/sig/phronomy/agent/admission.rbs +19 -0
  456. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  457. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  458. data/sig/phronomy/agent/store.rbs +73 -0
  459. data/sig/phronomy/agent.rbs +14 -139
  460. data/sig/phronomy/common.rbs +7 -0
  461. data/sig/phronomy/content_store/contracts.rbs +13 -0
  462. data/sig/phronomy/context.rbs +152 -0
  463. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  464. data/sig/phronomy/embeddings/base.rbs +17 -0
  465. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  466. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  467. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  468. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  469. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  470. data/sig/phronomy/engine/observability.rbs +13 -0
  471. data/sig/phronomy/execution/async_operation.rbs +7 -0
  472. data/sig/phronomy/execution/blocking.rbs +5 -0
  473. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  474. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  475. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  476. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  477. data/sig/phronomy/execution/errors.rbs +7 -0
  478. data/sig/phronomy/execution/execution.rbs +41 -0
  479. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  480. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  481. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  482. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  483. data/sig/phronomy/execution/task_result.rbs +29 -0
  484. data/sig/phronomy/extensions.rbs +0 -21
  485. data/sig/phronomy/handoff.rbs +12 -4
  486. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  487. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  488. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  489. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  490. data/sig/phronomy/llm_context_window.rbs +10 -0
  491. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  492. data/sig/phronomy/multi_agent/store.rbs +54 -0
  493. data/sig/phronomy/multi_agent.rbs +24 -3
  494. data/sig/phronomy/persistence/errors.rbs +18 -0
  495. data/sig/phronomy/persistence/migration.rbs +13 -0
  496. data/sig/phronomy/persistence/transaction.rbs +19 -0
  497. data/sig/phronomy/persistence.rbs +3 -185
  498. data/sig/phronomy/persistence_composition.rbs +19 -0
  499. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  500. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  501. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  502. data/sig/phronomy/storage/contracts.rbs +160 -0
  503. data/sig/phronomy/tool.rbs +58 -5
  504. data/sig/phronomy/tracing/observation.rbs +7 -0
  505. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  506. data/sig/phronomy/vector_store/base.rbs +23 -0
  507. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  508. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  509. data/sig/phronomy/workflow/store.rbs +8 -0
  510. data/sig/phronomy/workflow.rbs +1 -1
  511. data/sig/phronomy.rbs +0 -49
  512. metadata +462 -130
  513. data/lib/phronomy/agent/agent_invocation_session_builder.rb +0 -584
  514. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  515. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  516. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  517. data/lib/phronomy/agent/context/capability/base.rb +0 -507
  518. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  519. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  520. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  521. data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
  522. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  523. data/lib/phronomy/agent/handoff_request.rb +0 -45
  524. data/lib/phronomy/agent/handoff_runner.rb +0 -178
  525. data/lib/phronomy/agent/phase_machine_builder.rb +0 -152
  526. data/lib/phronomy/agent/provider_call_outcome.rb +0 -106
  527. data/lib/phronomy/agent/recovery_support.rb +0 -504
  528. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  529. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  530. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  531. data/lib/phronomy/agent/tool_call_intercepted.rb +0 -29
  532. data/lib/phronomy/agent/tool_invocation_session_builder.rb +0 -276
  533. data/lib/phronomy/configuration.rb +0 -59
  534. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  535. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  536. data/lib/phronomy/generator_verifier.rb +0 -369
  537. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  538. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  539. data/lib/phronomy/metrics.rb +0 -26
  540. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  541. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  542. data/lib/phronomy/persistence/in_memory.rb +0 -690
  543. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  544. data/lib/phronomy/persistence.rb +0 -276
  545. data/lib/phronomy/recovery.rb +0 -186
  546. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  547. data/lib/phronomy/runnable.rb +0 -54
  548. data/lib/phronomy/token_usage.rb +0 -85
  549. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  550. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  551. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  552. data/lib/phronomy/workflow_recovery.rb +0 -123
  553. data/sig/phronomy/llm_adapter.rbs +0 -11
  554. data/sig/phronomy/runtime.rbs +0 -76
  555. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  556. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  557. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
@@ -1,87 +1,156 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "invalid_result_error"
4
+
3
5
  module Phronomy
4
6
  module VectorStore
5
- # Public extension SPI for vector stores.
6
- #
7
- # Backends implement the synchronous add/search/remove/clear/size contract.
8
- # Phronomy supplies async convenience methods through {AsyncBackend}; blocking
9
- # backend work is offloaded through the framework-owned bounded OffloadPool.
10
- #
7
+ # Owns synchronous operations, input validation and the public result shape.
8
+ # Backends implement protected perform_* operations and embedding_dimension.
9
+ # No execution client, SDK or embedding provider is selected by this class.
11
10
  # @api public
12
11
  class Base
13
- include AsyncBackend
14
-
15
- # Add a document with its vector embedding.
16
- #
17
- # @param id [String] unique document identifier
18
- # @param embedding [Array<Float>] vector embedding
19
- # @param metadata [Hash] arbitrary metadata
20
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
12
+ # Validate before invoking the backend. Cancellation is checked before the
13
+ # write, never reported after a successful write. No operation is retried.
14
+ # @param id [String]
15
+ # @param embedding [Array<Numeric>]
16
+ # @param metadata [Hash]
17
+ # @param cancellation_token [#raise_if_cancelled!, nil]
18
+ # @return [self]
21
19
  # @api public
22
20
  def add(id:, embedding:, metadata: {}, cancellation_token: nil)
23
21
  cancellation_token&.raise_if_cancelled!
24
- raise NotImplementedError, "#{self.class}#add is not implemented"
22
+ validate_id!(id)
23
+ raise ArgumentError, "metadata must be a Hash" unless metadata.is_a?(Hash)
24
+ vector = validate_vector!(embedding)
25
+ perform_add(id: id, embedding: vector, metadata: metadata, cancellation_token: cancellation_token)
26
+ self
25
27
  end
26
28
 
27
- # Return the k most similar documents to the query embedding.
28
- #
29
- # @param query_embedding [Array<Float>]
30
- # @param k [Integer] number of results
31
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
32
- # @return [Array<Hash>] each element: { id:, score:, metadata: }
29
+ # Return at most k results ordered by descending similarity. Malformed
30
+ # backend output raises InvalidResultError; transport errors propagate.
31
+ # @param query_embedding [Array<Numeric>]
32
+ # @param k [Integer, String] positive integer or its decimal string
33
+ # @param cancellation_token [#raise_if_cancelled!, nil]
34
+ # @return [Array<Hash>]
33
35
  # @api public
34
36
  def search(query_embedding:, k: 5, cancellation_token: nil)
35
37
  cancellation_token&.raise_if_cancelled!
36
- raise NotImplementedError, "#{self.class}#search is not implemented"
38
+ limit = validate_k!(k)
39
+ vector = validate_vector!(query_embedding)
40
+ results = perform_search(query_embedding: vector, k: limit, cancellation_token: cancellation_token)
41
+ validate_results!(results, limit)
42
+ results
37
43
  end
38
44
 
39
- # Remove a single document by id.
40
- #
41
- # @param id [String] document identifier
45
+ # Remove a document; an absent id is a successful no-op.
46
+ # @param id [String]
47
+ # @return [self]
42
48
  # @api public
43
49
  def remove(id:)
44
- raise NotImplementedError, "#{self.class}#remove is not implemented"
50
+ validate_id!(id)
51
+ perform_remove(id: id)
52
+ self
45
53
  end
46
54
 
47
- # Remove all documents.
55
+ # Clear documents without changing the configured dimension.
56
+ # @return [self]
48
57
  # @api public
49
58
  def clear
50
- raise NotImplementedError, "#{self.class}#clear is not implemented"
59
+ perform_clear
60
+ self
51
61
  end
52
62
 
53
- # Return the number of documents stored.
54
- #
55
- # @return [Integer]
63
+ # @return [Integer] non-negative document count
56
64
  # @api public
57
65
  def size
58
- raise NotImplementedError, "#{self.class}#size is not implemented"
66
+ count = perform_size
67
+ unless count.is_a?(Integer) && count >= 0
68
+ raise InvalidResultError, "size must return a non-negative Integer"
69
+ end
70
+ count
71
+ end
72
+
73
+ protected
74
+
75
+ # @return [Integer, nil] nil delegates dimension enforcement to the backend
76
+ # @api public
77
+ def embedding_dimension
78
+ nil
79
+ end
80
+
81
+ # Storage extension point; inputs have passed common validation.
82
+ # @api public
83
+ def perform_add(id:, embedding:, metadata:, cancellation_token:)
84
+ raise NotImplementedError, "#{self.class}#perform_add is not implemented"
85
+ end
86
+
87
+ # Search extension point; return the documented result hashes in order.
88
+ # @api public
89
+ def perform_search(query_embedding:, k:, cancellation_token:)
90
+ raise NotImplementedError, "#{self.class}#perform_search is not implemented"
91
+ end
92
+
93
+ # @api public
94
+ def perform_remove(id:)
95
+ raise NotImplementedError, "#{self.class}#perform_remove is not implemented"
96
+ end
97
+
98
+ # @api public
99
+ def perform_clear
100
+ raise NotImplementedError, "#{self.class}#perform_clear is not implemented"
101
+ end
102
+
103
+ # @api public
104
+ def perform_size
105
+ raise NotImplementedError, "#{self.class}#perform_size is not implemented"
59
106
  end
60
107
 
61
108
  private
62
109
 
63
- # Validates that embedding has the expected dimension.
64
- # Raises ArgumentError if sizes differ.
65
- # A nil expected_dimension is a no-op (dimension not yet established).
66
- def validate_embedding_dimension!(embedding, expected_dimension)
67
- return unless expected_dimension
110
+ def validate_id!(id)
111
+ raise ArgumentError, "id must be a String" unless id.is_a?(String)
112
+ end
68
113
 
69
- actual = embedding.size
70
- return if actual == expected_dimension
114
+ def finite_real?(value)
115
+ value.is_a?(Numeric) && value.real? && value.finite? && value.to_f.finite?
116
+ end
71
117
 
72
- raise ArgumentError,
73
- "Embedding dimension mismatch: expected #{expected_dimension}, got #{actual}"
118
+ def validate_vector!(embedding)
119
+ unless embedding.is_a?(Array) && embedding.all? { |value| finite_real?(value) }
120
+ raise ArgumentError, "embedding must be an Array of finite real numbers"
121
+ end
122
+ dimension = embedding_dimension
123
+ if dimension && embedding.size != dimension
124
+ raise ArgumentError, "Embedding dimension mismatch: expected #{dimension}, got #{embedding.size}"
125
+ end
126
+ embedding.map(&:to_f)
74
127
  end
75
128
 
76
- # Validates that k is a positive integer.
77
- # Accepts any value accepted by Integer() (e.g. "5"), but raises
78
- # ArgumentError for non-integer strings, zero, and negative values.
79
129
  def validate_k!(k)
80
- int_k = Integer(k)
81
- raise ArgumentError, "k must be a positive integer, got #{int_k}" unless int_k >= 1
82
- int_k
83
- rescue ArgumentError => e
84
- raise ArgumentError, "k must be a positive integer: #{e.message}"
130
+ valid = k.is_a?(Integer) || (k.is_a?(String) && k.match?(/\A\+?\d+\z/))
131
+ unless valid
132
+ raise ArgumentError, "k must be a positive integer"
133
+ end
134
+ value = k.is_a?(String) ? Integer(k, 10) : k
135
+ raise ArgumentError, "k must be a positive integer" unless value >= 1
136
+ value
137
+ end
138
+
139
+ def validate_results!(results, limit)
140
+ unless results.is_a?(Array) && results.size <= limit
141
+ raise InvalidResultError, "search must return an Array with at most k results"
142
+ end
143
+ previous = Float::INFINITY
144
+ results.each do |result|
145
+ unless result.is_a?(Hash) && result[:id].is_a?(String) &&
146
+ result[:metadata].is_a?(Hash) && finite_real?(result[:score])
147
+ raise InvalidResultError, "search results require a String id, finite score and Hash metadata"
148
+ end
149
+ if result[:score] > previous
150
+ raise InvalidResultError, "search results must be ordered by descending score"
151
+ end
152
+ previous = result[:score]
153
+ end
85
154
  end
86
155
  end
87
156
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ module VectorStore
7
+ # A backend could not produce a valid synchronous result. This includes
8
+ # malformed external responses that must not be replaced with success values.
9
+ # @api public
10
+ class InvalidResultError < Phronomy::Error; end
11
+ end
12
+ end
@@ -1,23 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- # Vector store infrastructure: backends, embeddings adapters, document loaders,
5
- # and text splitters.
6
- #
7
- # Sub-namespaces are auto-loaded by Zeitwerk:
8
- # Phronomy::VectorStore::Base
9
- # Phronomy::VectorStore::InMemory
10
- # Phronomy::VectorStore::Pgvector
11
- # Phronomy::VectorStore::RedisSearch
12
- # Phronomy::VectorStore::Embeddings::Base
13
- # Phronomy::VectorStore::Embeddings::RubyLLMEmbeddings
14
- # Phronomy::VectorStore::Loader::Base
15
- # Phronomy::VectorStore::Loader::PlainTextLoader
16
- # Phronomy::VectorStore::Loader::MarkdownLoader
17
- # Phronomy::VectorStore::Loader::CsvLoader
18
- # Phronomy::VectorStore::Splitter::Base
19
- # Phronomy::VectorStore::Splitter::FixedSizeSplitter
20
- # Phronomy::VectorStore::Splitter::RecursiveSplitter
4
+ # Synchronous vector search/storage contract, its backends and execution client.
5
+ # Embeddings and Documents are independent Phronomy responsibilities.
21
6
  module VectorStore
22
7
  end
23
8
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- VERSION = "0.26.0"
4
+ VERSION = "0.28.0"
5
5
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class Workflow
5
+ # Workflow definition contract shared with its execution runner.
6
+ # @api private
7
+ module Completion
8
+ FINISH = :__finish__
9
+ end
10
+ end
11
+ end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../execution/concurrency/worker_input_restricted"
4
+
3
5
  require_relative "workflow_runner"
4
- require_relative "runnable"
6
+ require_relative "../../execution/runnable"
5
7
 
6
8
  module Phronomy
7
9
  # StateChart-style Workflow definition DSL.
8
10
  class Workflow
11
+ include Phronomy::Concurrency::WorkerInputRestricted
12
+
9
13
  include Phronomy::Runnable
10
14
 
11
15
  def self.define(context_class, persistence: nil, &block)
@@ -63,7 +67,7 @@ module Phronomy
63
67
  def _apply_invocation_context(config, invocation_context)
64
68
  effective = config.merge(invocation_context: invocation_context)
65
69
  if effective[:cancellation_token].nil?
66
- token = invocation_context.effective_timeout_token
70
+ token = Phronomy::InvocationControls.effective_timeout_token(invocation_context)
67
71
  effective = effective.merge(cancellation_token: token) if token
68
72
  end
69
73
  effective
@@ -72,8 +76,6 @@ module Phronomy
72
76
  public
73
77
 
74
78
  class Builder
75
- FINISH = Phronomy::WorkflowRunner::FINISH
76
-
77
79
  def initialize(context_class, persistence: nil)
78
80
  @context_class = context_class
79
81
  @persistence = persistence
@@ -119,7 +121,7 @@ module Phronomy
119
121
  # They may start asynchronous work and register listeners, but returning
120
122
  # Phronomy::TaskResult is an error; completion must arrive as a later event.
121
123
  def transition(from:, to:, guard: nil, on: nil, action: nil)
122
- destination = (to == :__finish__) ? FINISH : to
124
+ destination = to
123
125
  @transitions << {
124
126
  from: from,
125
127
  to: destination,
@@ -184,7 +186,7 @@ module Phronomy
184
186
 
185
187
  undefined_targets = @transitions
186
188
  .map { |transition| transition[:to] }
187
- .reject { |target| target == FINISH } - all_states
189
+ .reject { |target| target == Completion::FINISH } - all_states
188
190
  unless undefined_targets.empty?
189
191
  raise ArgumentError,
190
192
  "Workflow transition(s) reference undefined state(s): " \
@@ -205,7 +207,7 @@ module Phronomy
205
207
  current = queue.shift
206
208
  @transitions.each do |transition|
207
209
  next unless transition[:from] == current
208
- next if transition[:to] == FINISH
210
+ next if transition[:to] == Completion::FINISH
209
211
  next if reachable.include?(transition[:to])
210
212
 
211
213
  reachable.add(transition[:to])
@@ -220,7 +222,7 @@ module Phronomy
220
222
  "[Phronomy] Workflow has unreachable state(s): " \
221
223
  "#{unreachable.sort.inspect}. These states can never be entered " \
222
224
  "from the initial state #{entry_point.inspect}."
223
- logger = Phronomy.configuration.logger
225
+ logger = Phronomy::RuntimeSettings.current.logger
224
226
  logger ? logger.warn(message) : Kernel.warn(message)
225
227
  end
226
228
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Workflow's synchronous callback rules, independent of FSM representation.
5
+ # Guards and transition actions may receive the triggering application event;
6
+ # actions may initiate async work but must not return a TaskResult.
7
+ # @api private
8
+ module WorkflowActionRules
9
+ module_function
10
+
11
+ def allowed?(guard, context, event)
12
+ guard.nil? || call_with_optional_event(guard, context, event)
13
+ end
14
+
15
+ def transition(callable, context, event, metadata:)
16
+ return context if callable.nil?
17
+
18
+ result = call_with_optional_event(callable, context, event)
19
+ if result.is_a?(Phronomy::TaskResult)
20
+ raise Phronomy::InvalidAsyncTransitionActionError,
21
+ "Transition action " \
22
+ "#{metadata[:from].inspect} --#{metadata[:event].inspect}--> " \
23
+ "#{metadata[:to].inspect} returned Phronomy::TaskResult. " \
24
+ "Start the asynchronous operation, register its callback/listener, " \
25
+ "and return the WorkflowContext or nil."
26
+ end
27
+ workflow_context_result?(result) ? result : context
28
+ end
29
+
30
+ def entry(callable, context, state_name)
31
+ result = callable.call(context)
32
+ if result.is_a?(Phronomy::TaskResult)
33
+ raise Phronomy::InvalidAsyncEntryActionError,
34
+ "Entry action for state #{state_name.inspect} returned Phronomy::TaskResult. " \
35
+ "Start the asynchronous operation, register its callback/listener, " \
36
+ "and return the WorkflowContext or nil."
37
+ end
38
+ result
39
+ end
40
+
41
+ def apply_entry(callable, context, state_name)
42
+ result = entry(callable, context, state_name)
43
+ workflow_context_result?(result) ? result : context
44
+ end
45
+
46
+ def exit(callable, context, state_name)
47
+ result = callable.call(context)
48
+ if result.is_a?(Phronomy::TaskResult)
49
+ raise Phronomy::InvalidAsyncEntryActionError,
50
+ "Exit action for state #{state_name.inspect} returned Phronomy::TaskResult. " \
51
+ "Exit actions are synchronous Run-to-Completion callbacks."
52
+ end
53
+ nil
54
+ end
55
+
56
+ def call_with_optional_event(callable, context, event)
57
+ parameters = callable.respond_to?(:parameters) ? callable.parameters : callable.method(:call).parameters
58
+ (parameters.length >= 2) ? callable.call(context, event) : callable.call(context)
59
+ end
60
+ private_class_method :call_with_optional_event
61
+
62
+ def workflow_context_result?(result)
63
+ result.respond_to?(:set_graph_metadata)
64
+ end
65
+ private_class_method :workflow_context_result?
66
+ end
67
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../execution/concurrency/worker_input_restricted"
4
+
3
5
  module Phronomy
4
6
  # Module for defining Workflow context data.
5
7
  #
@@ -12,6 +14,8 @@ module Phronomy
12
14
  # evaluation, return a replacement WorkflowContext, or return +:consume+ to
13
15
  # discard a stale/unrelated event without firing a transition.
14
16
  module WorkflowContext
17
+ include Phronomy::Concurrency::WorkerInputRestricted
18
+
15
19
  def self.included(base)
16
20
  base.extend(ClassMethods)
17
21
  base.instance_variable_set(:@fields, {})
@@ -134,7 +138,7 @@ module Phronomy
134
138
  # dispatch thread. All Workflow execution APIs now use that same path; there
135
139
  # is no caller-thread synchronous exception.
136
140
  def _assert_write_permitted!
137
- return if Phronomy::Runtime.in_event_loop_context?
141
+ return if Phronomy::WaitPolicy.blocking_forbidden?
138
142
 
139
143
  raise Phronomy::WorkflowContextOwnershipError,
140
144
  "WorkflowContext fields may only be mutated from the EventLoop dispatch " \
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../common/error"
4
+
5
+ module Phronomy
6
+ class WorkflowContextOwnershipError < Error; end
7
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Workflow-owned connection requirements. Progress, admission and durable
5
+ # outcome interpretation remain in Workflow; composition selects transport.
6
+ # @api private
7
+ class WorkflowExecutionEnvironment
8
+ class << self
9
+ def install_provider(provider)
10
+ raise ArgumentError, "Workflow execution provider must be callable" unless provider.respond_to?(:call)
11
+ @provider = provider
12
+ end
13
+
14
+ def current
15
+ @provider&.call || raise(Phronomy::ConfigurationError, "Workflow execution environment has not been configured")
16
+ end
17
+ end
18
+
19
+ def registry = raise(NotImplementedError)
20
+ def existing_registry = raise(NotImplementedError)
21
+ def executing? = raise(NotImplementedError)
22
+ def submit(**options, &operation) = raise(NotImplementedError)
23
+ def compile_transitions(**definition) = raise(NotImplementedError)
24
+ def build_session(**options) = raise(NotImplementedError)
25
+ def register_session(session, completion:) = raise(NotImplementedError)
26
+ end
27
+ end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Workflow-owned segment admission and routing, mutated only on EventLoop.
5
+ # @api private
6
+ class WorkflowExecutionRegistry < Phronomy::ExecutionReceiver
7
+ # Workflow-owned process-local Workflow execution-segment admission. The
8
+ # owner token is a Runtime coordination capability, not a domain identity or
9
+ # routing identity. fsm_session_id is bound only after durable load/hydration
10
+ # completes and a concrete FSMSession has been constructed.
11
+ WorkflowAdmission = Data.define(
12
+ :workflow_instance_id, :owner_token, :fsm_session_id, :state
13
+ )
14
+ private_constant :WorkflowAdmission
15
+
16
+ def initialize(channel:)
17
+ super
18
+ @workflow_admissions = {}
19
+ end
20
+
21
+ def post(command, admission: false, completion: nil)
22
+ post_message(command, admission: admission, completion: completion)
23
+ end
24
+
25
+ def deliver(command)
26
+ assert_event_loop_thread!
27
+ command.runner.deliver_on_event_loop(command)
28
+ end
29
+
30
+ def idle?
31
+ !workflow_admission_transition_in_progress_locked?
32
+ end
33
+
34
+ def session_retired(fsm_session_id, reason:)
35
+ assert_event_loop_thread!
36
+ mark_workflow_recovery_required_for_session(fsm_session_id) if reason == :recovery_required
37
+ end
38
+
39
+ def shutdown(error:)
40
+ synchronize { @workflow_admissions.clear }
41
+ end
42
+
43
+ # Reserves one logical Workflow execution segment before durable load or
44
+ # hydration. The owner token is independent from any later FSMSession id.
45
+ # @api private
46
+ def admit_workflow(workflow_instance_id, owner_token:)
47
+ assert_event_loop_thread!
48
+ key = workflow_instance_id.to_s
49
+ raise ArgumentError, "workflow_instance_id must not be empty" if key.empty?
50
+ raise ArgumentError, "owner_token is required" unless owner_token
51
+
52
+ admit do
53
+ if @workflow_admissions.key?(key)
54
+ raise Phronomy::Error,
55
+ "Workflow instance #{key.inspect} already has a live execution segment"
56
+ end
57
+ @workflow_admissions[key] = WorkflowAdmission.new(
58
+ workflow_instance_id: key.freeze,
59
+ owner_token: owner_token,
60
+ fsm_session_id: nil,
61
+ state: :admitting
62
+ )
63
+ end
64
+ true
65
+ end
66
+
67
+ # Binds the concrete routing identity after admission and durable hydration.
68
+ # @api private
69
+ def bind_workflow_session(workflow_instance_id, owner_token:, fsm_session_id:)
70
+ assert_event_loop_thread!
71
+ key = workflow_instance_id.to_s
72
+ fsm_key = fsm_session_id.to_s
73
+ raise ArgumentError, "fsm_session_id must not be empty" if fsm_key.empty?
74
+
75
+ synchronize do
76
+ current = @workflow_admissions.fetch(key) do
77
+ raise Phronomy::Error, "Workflow instance #{key.inspect} has no Runtime admission"
78
+ end
79
+ unless current.owner_token.equal?(owner_token) && current.fsm_session_id.nil?
80
+ raise Phronomy::Error, "stale Workflow admission bind for #{key.inspect}"
81
+ end
82
+ @workflow_admissions[key] = WorkflowAdmission.new(
83
+ workflow_instance_id: current.workflow_instance_id,
84
+ owner_token: current.owner_token,
85
+ fsm_session_id: fsm_key.freeze,
86
+ state: :executing
87
+ )
88
+ end
89
+ true
90
+ end
91
+
92
+ # @api private
93
+ def mark_workflow_admission(workflow_instance_id, owner_token:, state:)
94
+ assert_event_loop_thread!
95
+ key = workflow_instance_id.to_s
96
+ next_state = state.to_sym
97
+ unless %i[executing persisting_terminal recovery_required].include?(next_state)
98
+ raise ArgumentError, "unsupported Workflow admission state: #{next_state.inspect}"
99
+ end
100
+
101
+ synchronize do
102
+ current = @workflow_admissions.fetch(key) do
103
+ raise Phronomy::Error, "Workflow instance #{key.inspect} has no Runtime admission"
104
+ end
105
+ unless current.owner_token.equal?(owner_token)
106
+ raise Phronomy::Error, "stale Workflow admission update for #{key.inspect}"
107
+ end
108
+ @workflow_admissions[key] = WorkflowAdmission.new(
109
+ workflow_instance_id: current.workflow_instance_id,
110
+ owner_token: current.owner_token,
111
+ fsm_session_id: current.fsm_session_id,
112
+ state: next_state
113
+ )
114
+ end
115
+ true
116
+ end
117
+
118
+ # Owner-aware release. A competing or stale attempt cannot release the
119
+ # current Workflow execution segment.
120
+ # @api private
121
+ def release_workflow(workflow_instance_id, owner_token:)
122
+ assert_event_loop_thread!
123
+ key = workflow_instance_id.to_s
124
+ synchronize do
125
+ current = @workflow_admissions[key]
126
+ next false unless current&.owner_token&.equal?(owner_token)
127
+
128
+ @workflow_admissions.delete(key)
129
+ true
130
+ end
131
+ end
132
+
133
+ # Read-only diagnostics used by internal tests and routing assertions.
134
+ def workflow_admission_owner(workflow_instance_id)
135
+ synchronize do
136
+ @workflow_admissions[workflow_instance_id.to_s]&.owner_token
137
+ end
138
+ end
139
+
140
+ def workflow_admission_fsm_session_id(workflow_instance_id)
141
+ synchronize do
142
+ @workflow_admissions[workflow_instance_id.to_s]&.fsm_session_id
143
+ end
144
+ end
145
+
146
+ def workflow_admission_state(workflow_instance_id)
147
+ synchronize do
148
+ @workflow_admissions[workflow_instance_id.to_s]&.state
149
+ end
150
+ end
151
+
152
+ def post_to_workflow(workflow_instance_id:, event:, payload: nil)
153
+ route_event(type: event.to_sym, payload: payload) do
154
+ admission = @workflow_admissions[workflow_instance_id.to_s]
155
+ next unless admission&.state == :executing && admission.fsm_session_id
156
+
157
+ admission.fsm_session_id
158
+ end
159
+ end
160
+
161
+ private
162
+
163
+ def mark_workflow_recovery_required_for_session(fsm_session_id)
164
+ synchronize do
165
+ key, admission = @workflow_admissions.find do |_workflow_instance_id, candidate|
166
+ candidate.fsm_session_id == fsm_session_id.to_s
167
+ end
168
+ return false unless admission
169
+
170
+ @workflow_admissions[key] = WorkflowAdmission.new(
171
+ workflow_instance_id: admission.workflow_instance_id,
172
+ owner_token: admission.owner_token,
173
+ fsm_session_id: nil,
174
+ state: :recovery_required
175
+ )
176
+ end
177
+ true
178
+ end
179
+
180
+ def workflow_admission_transition_in_progress_locked?
181
+ @workflow_admissions.values.any? do |admission|
182
+ %i[admitting executing persisting_terminal].include?(admission.state)
183
+ end
184
+ end
185
+ end
186
+ end