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
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Implements the Generator-Verifier multi-agent coordination pattern.
5
+ #
6
+ # Agent completion is integrated through application-defined Workflow events;
7
+ # Workflow entry actions do not return or await Agent Tasks.
8
+ class GeneratorVerifier
9
+ DEFAULT_CONFIDENCE_THRESHOLD = 0.7
10
+ DEFAULT_MAX_ITERATIONS = 3
11
+
12
+ Result = Struct.new(
13
+ :output,
14
+ :confidence,
15
+ :citations,
16
+ :iterations,
17
+ :review_notes,
18
+ :trusted
19
+ ) do
20
+ alias_method :trusted?, :trusted
21
+ end
22
+
23
+ private_constant :PipelineState, :WorkflowBuilder, :AgentResultReceiver, :AgentOperation
24
+
25
+ def initialize(
26
+ draft_agent:,
27
+ review_agent:,
28
+ draft_prompt_builder:,
29
+ review_prompt_builder:,
30
+ draft_result_parser: nil,
31
+ review_result_parser: nil,
32
+ confidence_threshold: DEFAULT_CONFIDENCE_THRESHOLD,
33
+ max_iterations: DEFAULT_MAX_ITERATIONS,
34
+ raise_if_untrusted: false
35
+ )
36
+ @draft_agent_class = draft_agent
37
+ @review_agent_class = review_agent
38
+ @draft_prompt_builder = draft_prompt_builder
39
+ @review_prompt_builder = review_prompt_builder
40
+ @draft_result_parser =
41
+ draft_result_parser || method(:default_parse_draft)
42
+ @review_result_parser =
43
+ review_result_parser || method(:default_parse_review)
44
+ @threshold = confidence_threshold.to_f
45
+ @max_iterations = max_iterations.to_i
46
+ @raise_if_untrusted = raise_if_untrusted
47
+ @compiled_workflow = nil
48
+ end
49
+
50
+ def invoke(input, config: {})
51
+ state = compiled_workflow.invoke({input: input}, config: config)
52
+ confidence = combined_confidence(state)
53
+ trusted = confidence >= @threshold
54
+ result = Result.new(
55
+ output: state.output || state.draft.to_s,
56
+ confidence: confidence,
57
+ citations: state.citations,
58
+ iterations: state.iteration,
59
+ review_notes: state.review_notes,
60
+ trusted: trusted
61
+ )
62
+ if @raise_if_untrusted && !trusted
63
+ raise LowConfidenceError.new(result)
64
+ end
65
+ result
66
+ end
67
+
68
+ private
69
+
70
+ def combined_confidence(state)
71
+ [
72
+ (state.self_score || 0.0).to_f,
73
+ (state.review_score || 0.0).to_f
74
+ ].min
75
+ end
76
+
77
+ def compiled_workflow
78
+ @compiled_workflow ||= build_workflow
79
+ end
80
+
81
+ def build_workflow
82
+ WorkflowBuilder.new(
83
+ draft_agent: AgentOperation.new(@draft_agent_class),
84
+ review_agent: AgentOperation.new(@review_agent_class),
85
+ draft_prompt_builder: @draft_prompt_builder,
86
+ review_prompt_builder: @review_prompt_builder,
87
+ draft_result_parser: @draft_result_parser,
88
+ review_result_parser: @review_result_parser,
89
+ threshold: @threshold,
90
+ max_iterations: @max_iterations
91
+ ).build
92
+ end
93
+
94
+ def default_parse_draft(text)
95
+ default_parser.parse(text)
96
+ rescue Phronomy::ParseError
97
+ {
98
+ answer: text.to_s,
99
+ confidence: 0.0,
100
+ citations: []
101
+ }
102
+ end
103
+
104
+ def default_parse_review(text)
105
+ default_parser.parse(text)
106
+ rescue Phronomy::ParseError
107
+ {
108
+ approved: false,
109
+ score: 0.0,
110
+ feedback: "Review output could not be parsed: #{text}"
111
+ }
112
+ end
113
+
114
+ def default_parser
115
+ @default_parser ||= DefaultParser.build
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ class LowConfidenceError < Error
7
+ attr_reader :result
8
+
9
+ def initialize(result)
10
+ @result = result
11
+ super("Answer confidence #{result.confidence} is below the required threshold")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module LLMAdapter
5
+ # Offloads the entire operation, including SDK initialization and decoding.
6
+ # Construction does not start a Runtime.
7
+ # @api private
8
+ class AsyncClient
9
+ # @api private
10
+ def initialize(adapter:, pool: nil, submitter: Phronomy::Execution)
11
+ @adapter = adapter
12
+ @pool = pool
13
+ @submitter = submitter
14
+ end
15
+
16
+ # @api private
17
+ def complete_async(request, cancellation_token: nil, pool: @pool)
18
+ submit(pool, cancellation_token) do
19
+ @adapter.complete(request, cancellation_token: cancellation_token)
20
+ end
21
+ end
22
+
23
+ # The block is an internal event sink, never an Application callback.
24
+ # @api private
25
+ def stream_async(request, cancellation_token: nil, pool: @pool, &block)
26
+ raise ArgumentError, "stream_async requires a block" unless block
27
+ submit(pool, cancellation_token) do
28
+ @adapter.stream(request, cancellation_token: cancellation_token) do |chunk|
29
+ cancellation_token&.raise_if_cancelled!
30
+ block.call(chunk)
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def submit(pool, cancellation_token, &operation)
38
+ if pool
39
+ Phronomy::Execution.submit(pool: pool, cancellation_token: cancellation_token,
40
+ on_full: :raise, &operation)
41
+ else
42
+ @submitter.submit(cancellation_token: cancellation_token, on_full: :raise, &operation)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "transport_error"
4
+
5
+ module Phronomy
6
+ module LLMAdapter
7
+ class AuthenticationError < TransportError; end
8
+ end
9
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_llm"
4
+
5
+ module Phronomy
6
+ module LLMAdapter
7
+ # SDK objects are operation-local. Only Phronomy request/result values cross
8
+ # the backend boundary; a Tool definition never contains executable work.
9
+ # @api public
10
+ class RubyLLM < Base
11
+ ToolsRequested = Class.new(StandardError) do
12
+ attr_reader :message_value
13
+ def initialize(message_value)
14
+ @message_value = message_value
15
+ super("assistant requested Tools")
16
+ end
17
+ end
18
+ private_constant :ToolsRequested
19
+
20
+ # @api public
21
+ def identity
22
+ super.merge("sdk_version" => ::RubyLLM::VERSION).freeze
23
+ end
24
+
25
+ # @api public
26
+ def input_budget(model_config)
27
+ config = model_config.to_h.transform_keys(&:to_s)
28
+ return nil unless config["model"]
29
+ model = ::RubyLLM.models.find(config["model"], provider: config["provider"])
30
+ limit = model&.context_window
31
+ return nil unless limit.is_a?(Integer) && limit.positive?
32
+ Phronomy::LlmContextWindow::TokenBudget.new(max_input_tokens: limit)
33
+ rescue ::RubyLLM::ModelNotFoundError
34
+ nil
35
+ rescue => error
36
+ raise_provider_error(error)
37
+ end
38
+
39
+ protected
40
+
41
+ # @api public
42
+ def perform_complete(request, cancellation_token:)
43
+ execute_request(request, cancellation_token)
44
+ end
45
+
46
+ # @api public
47
+ def perform_stream(request, cancellation_token:, &block)
48
+ execute_request(request, cancellation_token, &block)
49
+ end
50
+
51
+ private
52
+
53
+ def execute_request(request, token)
54
+ callback_error = nil
55
+ chat = build_chat(request)
56
+ token&.raise_if_cancelled!
57
+ chat.after_message do |message|
58
+ raise ToolsRequested.new(message) if message.tool_call?
59
+ end
60
+ response = if block_given?
61
+ sink = proc do |chunk|
62
+ token&.raise_if_cancelled!
63
+ value = stream_value(chunk)
64
+ begin
65
+ yield value
66
+ rescue => error
67
+ callback_error = error
68
+ raise
69
+ end
70
+ end
71
+ request.message ? chat.ask(request.message, &sink) : chat.complete(&sink)
72
+ else
73
+ request.message ? chat.ask(request.message) : chat.complete
74
+ end
75
+ response_value(response)
76
+ rescue ToolsRequested => requested
77
+ response_value(requested.message_value)
78
+ rescue => error
79
+ raise error if error.equal?(callback_error)
80
+ raise_provider_error(error)
81
+ end
82
+
83
+ def build_chat(request)
84
+ config = request.model_config
85
+ options = {}
86
+ options[:model] = config["model"] if config["model"]
87
+ if config["provider"]
88
+ options[:provider] = config["provider"].to_sym
89
+ options[:assume_model_exists] = config.fetch("assume_model_exists", true)
90
+ elsif config.key?("assume_model_exists")
91
+ options[:assume_model_exists] = config["assume_model_exists"]
92
+ end
93
+ chat = ::RubyLLM.chat(**options)
94
+ chat.with_temperature(config["temperature"]) if config["temperature"]
95
+ chat.with_max_output_tokens(config["max_output_tokens"]) if config["max_output_tokens"]
96
+ if request.system
97
+ chat.with_instructions(request.system, cache_until_here: config["cache_instructions"])
98
+ end
99
+ request.tools.each { |definition| chat.with_tools(tool_declaration(definition)) }
100
+ request.messages.each { |value| chat.messages << sdk_message(value) }
101
+ chat
102
+ end
103
+
104
+ def tool_declaration(definition)
105
+ name = definition.fetch("name")
106
+ klass = Class.new(::RubyLLM::Tool) do
107
+ define_singleton_method(:tool_name) { name }
108
+ define_method(:execute) do |**_arguments|
109
+ raise InvalidResultError, "SDK attempted to execute a declaration-only Tool"
110
+ end
111
+ end
112
+ klass.description(definition.fetch("description"))
113
+ klass.parameters(definition.fetch("parameters_schema"))
114
+ klass.provider_options(definition.fetch("provider_options", {}))
115
+ klass
116
+ end
117
+
118
+ def sdk_message(value)
119
+ calls = value.tool_calls.to_h do |call|
120
+ [call.id, ::RubyLLM::ToolCall.new(id: call.id, name: call.name,
121
+ arguments: call.arguments, thought_signature: call.metadata["thought_signature"])]
122
+ end
123
+ content = value.content
124
+ content = JSON.generate(content) if content.is_a?(Hash) || content.is_a?(Array)
125
+ content = "" if value.role == :assistant && content.nil? && !calls.empty?
126
+ ::RubyLLM::Message.new(role: value.role, content: content,
127
+ tool_calls: calls.empty? ? nil : calls, tool_call_id: value.tool_call_id,
128
+ model: value.metadata["model_id"])
129
+ end
130
+
131
+ def response_value(message)
132
+ unless message.respond_to?(:content) && message.respond_to?(:role) && message.role.to_sym == :assistant
133
+ raise InvalidResultError, "provider returned an invalid assistant message"
134
+ end
135
+ raw_calls = message.tool_calls if message.respond_to?(:tool_calls)
136
+ calls = raw_calls.is_a?(Hash) ? raw_calls.values : (raw_calls || [])
137
+ raise InvalidResultError, "provider returned invalid Tool calls" unless calls.is_a?(Array)
138
+ calls = calls.map do |call|
139
+ metadata = {}
140
+ if call.respond_to?(:thought_signature) && call.thought_signature
141
+ metadata["thought_signature"] = call.thought_signature
142
+ end
143
+ Phronomy::Tool::CallRequest.new(id: call.id, name: call.name,
144
+ arguments: call.arguments, metadata: metadata)
145
+ end
146
+ tokens = message.tokens if message.respond_to?(:tokens)
147
+ usage = Phronomy::LLMAdapter::TokenUsage.new(
148
+ input: tokens&.input, output: tokens&.output,
149
+ cached: tokens&.cache_read, cache_creation: tokens&.cache_write
150
+ )
151
+ metadata = {}
152
+ metadata["model_id"] = message.model if message.respond_to?(:model) && message.model
153
+ Response.new(content: message.content, tool_calls: calls, usage: usage, metadata: metadata)
154
+ rescue ArgumentError, TypeError, NoMethodError => error
155
+ raise InvalidResultError, "malformed provider response: #{error.message}"
156
+ end
157
+
158
+ def stream_value(chunk)
159
+ raise InvalidResultError, "provider returned an invalid stream chunk" unless chunk.respond_to?(:content)
160
+ StreamChunk.new(content: chunk.content)
161
+ rescue ArgumentError => error
162
+ raise InvalidResultError, "malformed provider chunk: #{error.message}"
163
+ end
164
+
165
+ # Translate SDK errors only. Cancellation and callback failures retain
166
+ # identity; raising here preserves the original provider exception cause.
167
+ def raise_provider_error(error)
168
+ case error
169
+ when ::RubyLLM::RateLimitError
170
+ raise Phronomy::LLMAdapter::RateLimitError, error.message
171
+ when ::RubyLLM::UnauthorizedError, ::RubyLLM::ForbiddenError
172
+ raise Phronomy::LLMAdapter::AuthenticationError, error.message
173
+ when ::RubyLLM::ContextLengthExceededError
174
+ raise Phronomy::LLMAdapter::ContextLengthError, error.message
175
+ when ::RubyLLM::Error
176
+ raise Phronomy::LLMAdapter::TransportError, error.message
177
+ else
178
+ raise error
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -1,91 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "request"
4
+ require_relative "response"
5
+ require_relative "stream_chunk"
6
+ require_relative "invalid_result_error"
7
+
3
8
  module Phronomy
4
9
  module LLMAdapter
5
- # Beta extension SPI for LLM call adapters.
6
- #
7
- # External adapters implement {#complete} and {#stream}. The adapter or the
8
- # underlying provider client owns transport timeout, retry, backoff, and
9
- # rate-limit behavior. Phronomy owns cooperative cancellation and isolates
10
- # synchronous provider calls in {OffloadPool}.
11
- #
12
- # The Agent pipeline calls the framework-owned {#complete_async} and
13
- # {#stream_async} wrappers. Adapter implementers do not need to depend on
14
- # EventLoop, FSMSession, OffloadPool, AgentInvocation, or ExecutionCoordinator.
15
- #
16
- # The current input to this SPI is the configured/materialized chat runtime
17
- # object. Formalizing this SPI therefore does not imply a provider-neutral
18
- # replacement for RubyLLMMaterializer.
19
- #
10
+ # Owns one-turn operations. Backends implement protected synchronous hooks.
11
+ # SDK construction and translation happen entirely inside those hooks.
20
12
  # @api public
21
13
  class Base
22
- # Performs a blocking (non-streaming) LLM completion.
23
- #
24
- # Implementors call the configured chat/runtime client and return its
25
- # response object. Transport/retry policy remains adapter-owned.
26
- #
27
- # @param chat [Object] the configured/materialized chat runtime object
28
- # @param message [String, nil] user message, or nil to continue without adding a new user turn
29
- # @param config [Hash] invocation config (e.g. +:cancellation_token+)
30
- # @return [Object] LLM response object
31
- # @raise [NotImplementedError]
32
14
  # @api public
33
- def complete(chat, message, config: {})
34
- raise NotImplementedError, "#{self.class}#complete is not implemented"
15
+ def identity
16
+ {"adapter_name" => self.class.name}.freeze
17
+ end
18
+
19
+ # Local model metadata only. Missing capacity is permitted.
20
+ # @api public
21
+ def input_budget(model_config)
22
+ nil
35
23
  end
36
24
 
37
- # Performs a blocking streaming LLM completion.
38
- #
39
- # @param chat [Object] the configured/materialized chat runtime object
40
- # @param message [String, nil] user message, or nil to continue without adding a new user turn
41
- # @param config [Hash] invocation config
42
- # @yield [chunk] streaming chunk from the LLM
43
- # @return [Object] LLM response object
44
- # @raise [NotImplementedError]
45
25
  # @api public
46
- def stream(chat, message, config: {}, &block)
47
- raise NotImplementedError, "#{self.class}#stream is not implemented"
26
+ def complete(request, cancellation_token: nil)
27
+ validate_request!(request, cancellation_token)
28
+ response = perform_complete(request, cancellation_token: cancellation_token)
29
+ cancellation_token&.raise_if_cancelled!
30
+ validate_response!(response)
48
31
  end
49
32
 
50
- # Submits a non-streaming LLM call to {OffloadPool}.
51
- #
52
- # Transport timeout and retry remain the responsibility of the adapter or
53
- # provider client; Phronomy does not attach an additional operation timeout.
54
- #
55
- # @return [Phronomy::TaskResult] caller-facing completion handle
56
- # @api private
57
- def complete_async(chat, message, config: {}, pool: default_pool)
58
- token = config[:cancellation_token]
59
- pool.submit(cancellation_token: token, on_full: :raise) do
60
- complete(chat, message, config: config)
33
+ # Notifications run on the caller thread. AsyncClient supplies an internal
34
+ # event sink; application callbacks remain with their owning domain.
35
+ # @api public
36
+ def stream(request, cancellation_token: nil)
37
+ raise ArgumentError, "stream requires a block" unless block_given?
38
+ validate_request!(request, cancellation_token)
39
+ response = perform_stream(request, cancellation_token: cancellation_token) do |chunk|
40
+ cancellation_token&.raise_if_cancelled!
41
+ raise InvalidResultError, "stream must yield StreamChunk values" unless chunk.is_a?(StreamChunk)
42
+ yield chunk
61
43
  end
44
+ cancellation_token&.raise_if_cancelled!
45
+ validate_response!(response)
62
46
  end
63
47
 
64
- # Submits a streaming LLM call to {OffloadPool}.
65
- #
66
- # The block is invoked on an OffloadPool worker thread. Agent code must pass
67
- # only a lightweight internal sink that posts a value to EventLoop;
68
- # application callbacks must never be passed directly to this method.
69
- #
70
- # @yield [chunk] streaming chunk on the worker thread
71
- # @return [Phronomy::TaskResult] caller-facing completion handle
72
- # @api private
73
- def stream_async(chat, message, config: {}, pool: default_pool, &block)
74
- raise ArgumentError, "stream_async requires a block" unless block
48
+ protected
75
49
 
76
- token = config[:cancellation_token]
77
- pool.submit(cancellation_token: token, on_full: :raise) do
78
- stream(chat, message, config: config) do |chunk|
79
- token&.raise_if_cancelled!("invocation cancelled during streaming")
80
- block.call(chunk)
81
- end
82
- end
50
+ # @api public
51
+ def perform_complete(request, cancellation_token:)
52
+ raise NotImplementedError, "#{self.class}#perform_complete is not implemented"
53
+ end
54
+
55
+ # @api public
56
+ def perform_stream(request, cancellation_token:, &block)
57
+ raise NotImplementedError, "#{self.class}#perform_stream is not implemented"
83
58
  end
84
59
 
85
60
  private
86
61
 
87
- def default_pool
88
- Phronomy::Runtime.instance.offload
62
+ def validate_request!(request, token)
63
+ token&.raise_if_cancelled!
64
+ raise ArgumentError, "LLM operation requires a Request" unless request.is_a?(Request)
65
+ end
66
+
67
+ def validate_response!(response)
68
+ raise InvalidResultError, "LLM operation must return a Response" unless response.is_a?(Response)
69
+ response
89
70
  end
90
71
  end
91
72
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ module LLMAdapter
7
+ class ContextLengthError < Phronomy::Error; end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ module LLMAdapter
7
+ # @api public
8
+ class InvalidResultError < Phronomy::Error; end
9
+ end
10
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/values/immutable"
4
+ require_relative "../tool/schema"
5
+ require_relative "../tool/call_request"
6
+
7
+ module Phronomy
8
+ module LLMAdapter
9
+ # Provider-independent conversation value, with no executable capabilities.
10
+ # @api public
11
+ class Message
12
+ attr_reader :role, :content, :tool_calls, :tool_call_id, :metadata
13
+
14
+ # @api public
15
+ def initialize(role:, content: nil, tool_calls: [], tool_call_id: nil, metadata: {})
16
+ unless role.respond_to?(:to_sym) && %i[system user assistant tool].include?(role.to_sym)
17
+ raise ArgumentError, "invalid LLM message role"
18
+ end
19
+ unless content.nil? || content.is_a?(String) || content.is_a?(Array) || content.is_a?(Hash)
20
+ raise ArgumentError, "LLM content must be text, structured JSON or nil"
21
+ end
22
+ unless tool_calls.is_a?(Array) && tool_calls.all? { |call| call.is_a?(Phronomy::Tool::CallRequest) }
23
+ raise ArgumentError, "LLM Tool calls must be Tool::CallRequest values"
24
+ end
25
+ ids = tool_calls.map(&:id)
26
+ raise ArgumentError, "duplicate LLM Tool call id" unless ids.uniq == ids
27
+ raise ArgumentError, "only assistant messages can request Tools" if !tool_calls.empty? && role.to_sym != :assistant
28
+ if role.to_sym == :tool && (!tool_call_id.is_a?(String) || tool_call_id.empty?)
29
+ raise ArgumentError, "Tool message requires tool_call_id"
30
+ end
31
+ unless tool_call_id.nil? || tool_call_id.is_a?(String)
32
+ raise ArgumentError, "tool_call_id must be a String"
33
+ end
34
+ raise ArgumentError, "LLM metadata must be a Hash" unless metadata.is_a?(Hash)
35
+ Phronomy::Values::Immutable.validate_canonical_json!(content, label: "LLM content")
36
+ Phronomy::Values::Immutable.validate_canonical_json!(metadata, label: "LLM metadata")
37
+ @role = role.to_sym
38
+ @content = Phronomy::Values::Immutable.copy(content)
39
+ @tool_calls = tool_calls.dup.freeze
40
+ @tool_call_id = tool_call_id&.dup&.freeze
41
+ @metadata = Phronomy::Values::Immutable.copy(metadata)
42
+ freeze
43
+ end
44
+
45
+ # @api public
46
+ def tool_call?
47
+ !tool_calls.empty?
48
+ end
49
+
50
+ # @api public
51
+ def content_present?
52
+ !content.nil? && !content.empty?
53
+ end
54
+
55
+ # @api public
56
+ def to_h
57
+ {"role" => role.to_s, "content" => content, "tool_calls" => tool_calls.map(&:to_h),
58
+ "tool_call_id" => tool_call_id, "metadata" => metadata}.compact
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "transport_error"
4
+
5
+ module Phronomy
6
+ module LLMAdapter
7
+ class RateLimitError < TransportError; end
8
+ end
9
+ end