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,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Context
5
+ class DefaultPolicy < ContextPolicy
6
+ CONVERSATION_SHARE = 0.60
7
+
8
+ def self.instance
9
+ @instance ||= new.freeze
10
+ end
11
+
12
+ def call(input)
13
+ unless input.is_a?(ContextPolicyInput)
14
+ raise ArgumentError, "Default ContextPolicy expected ContextPolicyInput"
15
+ end
16
+
17
+ return all_context(input) unless input.token_budget
18
+
19
+ limit = input.token_budget.effective_input_limit
20
+ required_knowledge = input.knowledge.select(&:required?)
21
+ optional_knowledge = input.knowledge.reject(&:required?)
22
+ required_conversation = input.conversation.select { |group| group.any?(&:required?) }
23
+ optional_conversation = input.conversation.reject { |group| group.any?(&:required?) }
24
+
25
+ fixed_cost = item_cost(input.instruction) + item_cost(input.tools) +
26
+ item_cost(required_knowledge) + group_cost(required_conversation)
27
+ if fixed_cost > limit
28
+ raise Phronomy::Context::BudgetExceededError,
29
+ "Required Context (estimated #{fixed_cost} tokens) exceeds " \
30
+ "available input budget (#{limit} tokens)"
31
+ end
32
+
33
+ remaining = limit - fixed_cost
34
+ conversation_budget = (remaining * CONVERSATION_SHARE).floor
35
+ knowledge_budget = remaining - conversation_budget
36
+
37
+ selected_conversation, older_conversation, conversation_used =
38
+ select_recent_suffix(optional_conversation, conversation_budget)
39
+ selected_knowledge, remaining_knowledge, knowledge_used =
40
+ select_stable_fit(optional_knowledge, knowledge_budget)
41
+
42
+ reusable = remaining - conversation_used - knowledge_used
43
+ if reusable.positive? && !older_conversation.empty?
44
+ more_conversation, _, used =
45
+ select_recent_suffix(older_conversation, reusable)
46
+ selected_conversation = more_conversation + selected_conversation
47
+ reusable -= used
48
+ end
49
+ if reusable.positive? && !remaining_knowledge.empty?
50
+ more_knowledge, _remaining_knowledge, used =
51
+ select_stable_fit(remaining_knowledge, reusable)
52
+ selected_knowledge += more_knowledge
53
+ reusable -= used
54
+ end
55
+
56
+ conversation = (required_conversation + selected_conversation)
57
+ .uniq
58
+ .sort_by { |group| group_sequence(group) }
59
+ knowledge_ids = (required_knowledge + selected_knowledge).to_h { |item| [item.id, true] }
60
+ knowledge = input.knowledge.select { |item| knowledge_ids[item.id] }
61
+
62
+ plan(
63
+ instruction: input.instruction,
64
+ knowledge: knowledge,
65
+ tools: input.tools,
66
+ conversation: conversation,
67
+ metadata: {
68
+ "default_policy" => true,
69
+ "estimated_fixed_tokens" => fixed_cost,
70
+ "estimated_unused_tokens" => reusable
71
+ }
72
+ )
73
+ end
74
+
75
+ private
76
+
77
+ def all_context(input)
78
+ plan(
79
+ instruction: input.instruction,
80
+ knowledge: input.knowledge,
81
+ tools: input.tools,
82
+ conversation: input.conversation,
83
+ metadata: {"default_policy" => true}
84
+ )
85
+ end
86
+
87
+ def item_cost(items)
88
+ Array(items).sum { |item| Integer(item.estimated_tokens || 0) }
89
+ end
90
+
91
+ def group_cost(groups)
92
+ Array(groups).sum { |group| item_cost(group) }
93
+ end
94
+
95
+ def group_sequence(group)
96
+ group.filter_map(&:sequence).min || 0
97
+ end
98
+
99
+ def select_recent_suffix(groups, budget)
100
+ ordered = Array(groups).sort_by { |group| group_sequence(group) }
101
+ selected = []
102
+ remaining = Integer(budget)
103
+ stop_index = -1
104
+
105
+ (ordered.length - 1).downto(0) do |index|
106
+ cost = item_cost(ordered[index])
107
+ if cost > remaining
108
+ stop_index = index
109
+ break
110
+ end
111
+ selected.unshift(ordered[index])
112
+ remaining -= cost
113
+ end
114
+
115
+ older = if stop_index >= 0
116
+ ordered[0..stop_index]
117
+ else
118
+ []
119
+ end
120
+ [selected.freeze, older.freeze, Integer(budget) - remaining]
121
+ end
122
+
123
+ def select_stable_fit(items, budget)
124
+ remaining = Integer(budget)
125
+ selected = []
126
+ unselected = []
127
+ Array(items).each do |item|
128
+ cost = Integer(item.estimated_tokens || 0)
129
+ if cost <= remaining
130
+ selected << item
131
+ remaining -= cost
132
+ else
133
+ unselected << item
134
+ end
135
+ end
136
+ [selected.freeze, unselected.freeze, Integer(budget) - remaining]
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Context
5
+ class FinalBudgetValidator
6
+ def initialize(content_loader:)
7
+ @content_loader = content_loader
8
+ end
9
+
10
+ def validate!(token_budget:, segments:, extra_values: [])
11
+ return 0 unless token_budget
12
+
13
+ estimated = Array(segments).sum do |segment|
14
+ Phronomy::LlmContextWindow::TokenEstimator.estimate(
15
+ @content_loader.call(segment.fetch(:content_ref))
16
+ )
17
+ end
18
+ estimated += Array(extra_values).sum do |value|
19
+ bytes = value.is_a?(String) ? value : Phronomy::CanonicalJSON.dump(value)
20
+ Phronomy::LlmContextWindow::TokenEstimator.estimate(bytes)
21
+ end
22
+
23
+ limit = token_budget.effective_input_limit
24
+ if estimated > limit
25
+ raise Phronomy::Context::BudgetExceededError,
26
+ "Canonical LLM input (estimated #{estimated} tokens) exceeds " \
27
+ "available input budget (#{limit} tokens)"
28
+ end
29
+ estimated
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module Agent
4
+ module Context
5
5
  # Immutable metadata passed to before_llm_input hooks. Runtime objects such
6
6
  # as RubyLLM::Chat, messages and the mutable Agent instance are not exposed.
7
7
  LLMInputBuildContext = Data.define(
@@ -9,7 +9,7 @@ module Phronomy
9
9
  :config, :call_sequence
10
10
  ) do
11
11
  def initialize(**values)
12
- super(**values.merge(config: Immutable.copy(values.fetch(:config, {}))))
12
+ super(**values.merge(config: Phronomy::Values::Immutable.copy(values.fetch(:config, {}))))
13
13
  freeze
14
14
  end
15
15
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module Agent
4
+ module Context
5
5
  # ContentStore-backed durable codec boundary for one Provider input manifest.
6
- # It is intentionally separate from Persistence::DurableRecord.
6
+ # It is intentionally separate from Storage::DurableRecord.
7
7
  class LLMInputManifest
8
8
  VERSION = "0.1"
9
9
  CALL_MODES = %i[ask complete].freeze
@@ -18,7 +18,7 @@ module Phronomy
18
18
  if delivery.nil? || !SEGMENT_DELIVERIES.include?(delivery.to_s)
19
19
  raise ArgumentError, "unknown Segment delivery: #{delivery.inspect}"
20
20
  end
21
- super(**values.merge(metadata: Immutable.copy(values[:metadata] || {})))
21
+ super(**values.merge(metadata: Phronomy::Values::Immutable.copy(values[:metadata] || {})))
22
22
  freeze
23
23
  end
24
24
 
@@ -62,7 +62,7 @@ module Phronomy
62
62
  label: label
63
63
  )
64
64
  unless SEGMENT_DELIVERIES.include?(delivery)
65
- raise Phronomy::Persistence::SerializationError,
65
+ raise Phronomy::Context::ManifestError,
66
66
  "LLMInputManifest segment delivery must be one of #{SEGMENT_DELIVERIES.inspect}, got #{delivery.inspect}"
67
67
  end
68
68
  tool_call_id = LLMInputManifest.send(
@@ -87,10 +87,10 @@ module Phronomy
87
87
  tool_call_id: tool_call_id,
88
88
  metadata: metadata
89
89
  )
90
- rescue Phronomy::Persistence::SerializationError
90
+ rescue Phronomy::Context::ManifestError
91
91
  raise
92
92
  rescue => error
93
- raise Phronomy::Persistence::SerializationError,
93
+ raise Phronomy::Context::ManifestError,
94
94
  "invalid LLMInputManifest segment: #{error.class}: #{error.message}"
95
95
  end
96
96
 
@@ -162,7 +162,7 @@ module Phronomy
162
162
  label = "LLMInputManifest"
163
163
  version = source.fetch("version")
164
164
  unless version.is_a?(String) && version == VERSION
165
- raise Phronomy::Persistence::SerializationError,
165
+ raise Phronomy::Context::ManifestError,
166
166
  "unsupported LLMInputManifest version: #{version.inspect}; " \
167
167
  "current version is #{VERSION.inspect}"
168
168
  end
@@ -229,10 +229,10 @@ module Phronomy
229
229
  adapter_name: adapter_name,
230
230
  adapter_version: adapter_version
231
231
  )
232
- rescue Phronomy::Persistence::SerializationError
232
+ rescue Phronomy::Context::ManifestError
233
233
  raise
234
234
  rescue => error
235
- raise Phronomy::Persistence::SerializationError,
235
+ raise Phronomy::Context::ManifestError,
236
236
  "invalid LLMInputManifest: #{error.class}: #{error.message}"
237
237
  end
238
238
 
@@ -262,11 +262,11 @@ module Phronomy
262
262
 
263
263
  def strict_source!(hash, required:, optional:, label:)
264
264
  unless hash.is_a?(Hash)
265
- raise Phronomy::Persistence::SerializationError,
265
+ raise Phronomy::Context::ManifestError,
266
266
  "#{label} must be a Hash"
267
267
  end
268
268
  unless hash.keys.all? { |key| key.is_a?(String) }
269
- raise Phronomy::Persistence::SerializationError,
269
+ raise Phronomy::Context::ManifestError,
270
270
  "#{label} keys must all be String"
271
271
  end
272
272
 
@@ -277,13 +277,13 @@ module Phronomy
277
277
  details = []
278
278
  details << "missing=#{missing.inspect}" unless missing.empty?
279
279
  details << "unknown=#{unknown.inspect}" unless unknown.empty?
280
- raise Phronomy::Persistence::SerializationError,
280
+ raise Phronomy::Context::ManifestError,
281
281
  "#{label} schema mismatch (#{details.join(", ")})"
282
282
  end
283
283
  Phronomy::CanonicalJSON.dump(hash)
284
284
  hash
285
285
  rescue ArgumentError => error
286
- raise Phronomy::Persistence::SerializationError,
286
+ raise Phronomy::Context::ManifestError,
287
287
  "#{label} is not canonical JSON compatible: #{error.message}"
288
288
  end
289
289
 
@@ -291,7 +291,7 @@ module Phronomy
291
291
  value = hash.fetch(key)
292
292
  return value if value.is_a?(Integer)
293
293
 
294
- raise Phronomy::Persistence::SerializationError,
294
+ raise Phronomy::Context::ManifestError,
295
295
  "#{label} #{key} must be an Integer"
296
296
  end
297
297
 
@@ -299,7 +299,7 @@ module Phronomy
299
299
  value = hash.fetch(key)
300
300
  return value if value.is_a?(Integer) && value.positive?
301
301
 
302
- raise Phronomy::Persistence::SerializationError,
302
+ raise Phronomy::Context::ManifestError,
303
303
  "#{label} #{key} must be a positive Integer"
304
304
  end
305
305
 
@@ -307,7 +307,7 @@ module Phronomy
307
307
  value = hash.fetch(key)
308
308
  return value if value.is_a?(Integer) && value >= 0
309
309
 
310
- raise Phronomy::Persistence::SerializationError,
310
+ raise Phronomy::Context::ManifestError,
311
311
  "#{label} #{key} must be a non-negative Integer"
312
312
  end
313
313
 
@@ -315,7 +315,7 @@ module Phronomy
315
315
  value = hash.fetch(key)
316
316
  return value if value.is_a?(String) && !value.empty?
317
317
 
318
- raise Phronomy::Persistence::SerializationError,
318
+ raise Phronomy::Context::ManifestError,
319
319
  "#{label} #{key} must be a non-empty String"
320
320
  end
321
321
 
@@ -323,7 +323,7 @@ module Phronomy
323
323
  value = hash[key]
324
324
  return value if value.nil? || value.is_a?(String)
325
325
 
326
- raise Phronomy::Persistence::SerializationError,
326
+ raise Phronomy::Context::ManifestError,
327
327
  "#{label} #{key} must be a String or nil"
328
328
  end
329
329
 
@@ -331,7 +331,7 @@ module Phronomy
331
331
  value = hash.fetch(key)
332
332
  return value if value.is_a?(String) && allowed.include?(value)
333
333
 
334
- raise Phronomy::Persistence::SerializationError,
334
+ raise Phronomy::Context::ManifestError,
335
335
  "#{label} #{key} must be one of #{allowed.inspect}"
336
336
  end
337
337
 
@@ -339,7 +339,7 @@ module Phronomy
339
339
  value = hash.fetch(key)
340
340
  return value if value.is_a?(Array)
341
341
 
342
- raise Phronomy::Persistence::SerializationError,
342
+ raise Phronomy::Context::ManifestError,
343
343
  "#{label} #{key} must be an Array"
344
344
  end
345
345
 
@@ -347,7 +347,7 @@ module Phronomy
347
347
  value = hash.fetch(key)
348
348
  return value if value.is_a?(Hash)
349
349
 
350
- raise Phronomy::Persistence::SerializationError,
350
+ raise Phronomy::Context::ManifestError,
351
351
  "#{label} #{key} must be a Hash"
352
352
  end
353
353
  end
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module Agent
4
+ module Context
5
5
  # Typed, canonicalizable input customization returned by before_llm_input.
6
6
  # v1 deliberately exposes only fields that are fully applied to every call.
7
7
  LLMInputPatch = Data.define(:model_config_patch, :segment_candidates) do
8
8
  def initialize(model_config_patch: nil, segment_candidates: nil)
9
9
  super(
10
- model_config_patch: model_config_patch && Immutable.copy(model_config_patch),
11
- segment_candidates: segment_candidates && Immutable.copy(Array(segment_candidates))
10
+ model_config_patch: model_config_patch && Phronomy::Values::Immutable.copy(model_config_patch),
11
+ segment_candidates: segment_candidates && Phronomy::Values::Immutable.copy(Array(segment_candidates))
12
12
  )
13
13
  freeze
14
14
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ module Context
7
+ # Invalid canonical context input; independent of its persistence backend.
8
+ class ManifestError < Phronomy::Error; end
9
+ end
10
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Context
5
+ # A prompt template that substitutes {{variable}} placeholders in a string.
6
+ #
7
+ # @example Simple human template
8
+ # t = Phronomy::Context::PromptTemplate.new(template: "Translate to {{lang}}: {{text}}")
9
+ # t.format(lang: "French", text: "Hello")
10
+ # # => "Translate to French: Hello"
11
+ #
12
+ # @example With a system template
13
+ # t = Phronomy::Context::PromptTemplate.new(
14
+ # template: "{{question}}",
15
+ # system_template: "You are a {{role}} assistant."
16
+ # )
17
+ # t.format_system(role: "helpful")
18
+ # # => "You are a helpful assistant."
19
+ #
20
+ # As a Runnable, #invoke accepts a Hash of variables and returns a Hash
21
+ # with :prompt (and optionally :system) keys.
22
+ class PromptTemplate
23
+ include Phronomy::Runnable
24
+
25
+ PLACEHOLDER = /\{\{(\w+)\}\}/
26
+
27
+ attr_reader :template, :system_template
28
+
29
+ # @param template [String] human message template with {{var}} placeholders
30
+ # @param system_template [String, nil] optional system message template
31
+ # @api public
32
+ def initialize(template:, system_template: nil)
33
+ @template = template
34
+ @system_template = system_template
35
+ end
36
+
37
+ # Substitute all {{var}} placeholders in the human template.
38
+ #
39
+ # @param variables [Hash{Symbol => String}]
40
+ # @return [String]
41
+ # @api public
42
+ def format(**variables)
43
+ substitute(@template, variables)
44
+ end
45
+
46
+ # Substitute all {{var}} placeholders in the system template.
47
+ # Returns nil when no system template was set.
48
+ #
49
+ # @param variables [Hash{Symbol => String}]
50
+ # @return [String, nil]
51
+ # @api public
52
+ def format_system(**variables)
53
+ @system_template && substitute(@system_template, variables)
54
+ end
55
+
56
+ # Runnable interface: accepts a Hash of variable values.
57
+ # Returns { prompt: String, system: String|nil }.
58
+ #
59
+ # @param input [Hash{Symbol => String}]
60
+ # @return [Hash]
61
+ # @api public
62
+ def invoke(input, config: {})
63
+ vars = normalize_input(input)
64
+ result = {prompt: format(**vars)}
65
+ sys = format_system(**vars)
66
+ result[:system] = sys if sys
67
+ result
68
+ end
69
+
70
+ # Returns the list of placeholder names found in both templates.
71
+ #
72
+ # @return [Array<Symbol>]
73
+ # @api public
74
+ def variables
75
+ names = @template.scan(PLACEHOLDER).flatten
76
+ names += @system_template.scan(PLACEHOLDER).flatten if @system_template
77
+ names.map(&:to_sym).uniq
78
+ end
79
+
80
+ private
81
+
82
+ def substitute(text, variables)
83
+ text.gsub(PLACEHOLDER) do |match|
84
+ key = Regexp.last_match(1).to_sym
85
+ variables.fetch(key) { raise KeyError, "Missing variable: {{#{key}}}" }
86
+ end
87
+ end
88
+
89
+ def normalize_input(input)
90
+ case input
91
+ when Hash then input
92
+ when String then {input: input}
93
+ else raise ArgumentError, "PromptTemplate#invoke expects a Hash of variables, got #{input.class}"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Loader
6
6
  # Abstract base class for document loaders.
7
7
  #
@@ -3,7 +3,7 @@
3
3
  require "csv"
4
4
 
5
5
  module Phronomy
6
- module VectorStore
6
+ module Documents
7
7
  module Loader
8
8
  # Loads a CSV file, converting each row into a separate document.
9
9
  #
@@ -12,7 +12,7 @@ module Phronomy
12
12
  # a human-readable "key: value" string for embedding.
13
13
  #
14
14
  # @example
15
- # loader = Phronomy::VectorStore::Loader::CsvLoader.new
15
+ # loader = Phronomy::Documents::Loader::CsvLoader.new
16
16
  # docs = loader.load("products.csv")
17
17
  # # => [
18
18
  # # { text: "name: Widget\nprice: 9.99", metadata: { source: "...", row: 1, name: "Widget", price: "9.99" } },
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Loader
6
6
  # Loads a Markdown file, optionally splitting on top-level headings.
7
7
  #
@@ -10,12 +10,12 @@ module Phronomy
10
10
  # rather than the full file at once.
11
11
  #
12
12
  # @example Single document (heading split disabled)
13
- # loader = Phronomy::VectorStore::Loader::MarkdownLoader.new(split_on_headings: false)
13
+ # loader = Phronomy::Documents::Loader::MarkdownLoader.new(split_on_headings: false)
14
14
  # docs = loader.load("README.md")
15
15
  # # => [{ text: "# Title\n...", metadata: { source: "README.md" } }]
16
16
  #
17
17
  # @example Split per heading (default)
18
- # loader = Phronomy::VectorStore::Loader::MarkdownLoader.new
18
+ # loader = Phronomy::Documents::Loader::MarkdownLoader.new
19
19
  # docs = loader.load("guide.md")
20
20
  # # => [
21
21
  # # { text: "# Section 1\n...", metadata: { source: "guide.md", section: "Section 1" } },
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Loader
6
6
  # Loads a plain-text file as a single document.
7
7
  #
8
8
  # @example
9
- # loader = Phronomy::VectorStore::Loader::PlainTextLoader.new
9
+ # loader = Phronomy::Documents::Loader::PlainTextLoader.new
10
10
  # docs = loader.load("/path/to/file.txt")
11
11
  # # => [{ text: "...", metadata: { source: "/path/to/file.txt" } }]
12
12
  class PlainTextLoader < Base
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Splitter
6
6
  # Abstract base class for text splitters.
7
7
  #
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Splitter
6
6
  # Splits text into fixed-size character chunks with optional overlap.
7
7
  #
8
8
  # @example
9
- # splitter = Phronomy::VectorStore::Splitter::FixedSizeSplitter.new(chunk_size: 200, chunk_overlap: 20)
9
+ # splitter = Phronomy::Documents::Splitter::FixedSizeSplitter.new(chunk_size: 200, chunk_overlap: 20)
10
10
  # chunks = splitter.split({ text: long_text, metadata: { source: "doc.txt" } })
11
11
  # # => [
12
12
  # # { text: "...(200 chars)...", metadata: { source: "doc.txt", chunk: 0 } },
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module VectorStore
4
+ module Documents
5
5
  module Splitter
6
6
  # Splits text recursively using a prioritised list of separator strings.
7
7
  #
@@ -18,7 +18,7 @@ module Phronomy
18
18
  # 5. "" — character-level fallback
19
19
  #
20
20
  # @example
21
- # splitter = Phronomy::VectorStore::Splitter::RecursiveSplitter.new(chunk_size: 300, chunk_overlap: 30)
21
+ # splitter = Phronomy::Documents::Splitter::RecursiveSplitter.new(chunk_size: 300, chunk_overlap: 30)
22
22
  # chunks = splitter.split({ text: long_markdown, metadata: { source: "guide.md" } })
23
23
  class RecursiveSplitter < Base
24
24
  DEFAULT_SEPARATORS = ["\n\n", "\n", ". ", " ", ""].freeze
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Embeddings
5
+ # Framework-owned asynchronous execution of a synchronous embedding adapter.
6
+ #
7
+ # Construction starts no Runtime. Each operation uses the current default
8
+ # pool unless a caller-owned pool was injected. Timeout includes queue wait
9
+ # and settles the result without forcibly interrupting an executing adapter.
10
+ #
11
+ # @api public
12
+ class AsyncClient
13
+ # @param adapter [Embeddings::Base] synchronous embedding adapter
14
+ # @param pool [Concurrency::OffloadPool, nil] optional execution pool
15
+ # @api public
16
+ def initialize(adapter:, pool: nil)
17
+ @adapter = adapter
18
+ @pool = pool
19
+ end
20
+
21
+ # Submits a synchronous embed call without waiting for queue capacity.
22
+ #
23
+ # @param text [String]
24
+ # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
25
+ # @param timeout [Numeric, nil] operation-wide submit timeout
26
+ # @return [Phronomy::TaskResult] the pool's original completion handle
27
+ # @api public
28
+ def embed_async(text, cancellation_token = nil, timeout: nil)
29
+ Phronomy::Execution.submit(pool: default_pool,
30
+ timeout: timeout,
31
+ cancellation_token: cancellation_token,
32
+ on_full: :raise) do
33
+ @adapter.embed(text, cancellation_token)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def default_pool
40
+ @pool
41
+ end
42
+ end
43
+ end
44
+ end