phronomy 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (557) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +92 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +13 -3
  6. data/VERIFY.sh +27 -27
  7. data/benchmark/bench_agent_invoke.rb +26 -47
  8. data/benchmark/bench_context_assembler.rb +17 -15
  9. data/benchmark/bench_regression.rb +1 -1
  10. data/benchmark/bench_tool_schema.rb +1 -1
  11. data/docs/architecture/agent-chat-and-state-ownership.md +153 -0
  12. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  13. data/docs/architecture/agent-context.md +4 -2
  14. data/docs/architecture/agent-transition-ownership.md +13 -0
  15. data/docs/architecture/backend-groups.md +152 -0
  16. data/docs/architecture/before-llm-input.md +9 -3
  17. data/docs/architecture/context-management.md +31 -1
  18. data/docs/architecture/context-preparation-steps.md +90 -0
  19. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  20. data/docs/architecture/execution-abstraction-review.md +88 -0
  21. data/docs/architecture/execution-boundaries.md +28 -0
  22. data/docs/architecture/execution-metadata-and-values.md +86 -0
  23. data/docs/architecture/generator-verifier-ownership.md +111 -0
  24. data/docs/architecture/multi-agent-handoff.md +11 -4
  25. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  26. data/docs/architecture/persistence.md +45 -4
  27. data/docs/architecture/r8-unit1.md +104 -0
  28. data/docs/architecture/r8-unit10.md +72 -0
  29. data/docs/architecture/r8-unit11.md +71 -0
  30. data/docs/architecture/r8-unit12.md +67 -0
  31. data/docs/architecture/r8-unit13.md +61 -0
  32. data/docs/architecture/r8-unit14.md +72 -0
  33. data/docs/architecture/r8-unit15.md +65 -0
  34. data/docs/architecture/r8-unit16.md +54 -0
  35. data/docs/architecture/r8-unit17.md +73 -0
  36. data/docs/architecture/r8-unit18.md +92 -0
  37. data/docs/architecture/r8-unit19.md +61 -0
  38. data/docs/architecture/r8-unit2.md +55 -0
  39. data/docs/architecture/r8-unit20.md +51 -0
  40. data/docs/architecture/r8-unit21.md +79 -0
  41. data/docs/architecture/r8-unit22.md +74 -0
  42. data/docs/architecture/r8-unit23.md +81 -0
  43. data/docs/architecture/r8-unit3.md +130 -0
  44. data/docs/architecture/r8-unit4.md +79 -0
  45. data/docs/architecture/r8-unit5.md +115 -0
  46. data/docs/architecture/r8-unit6.md +71 -0
  47. data/docs/architecture/r8-unit7.md +70 -0
  48. data/docs/architecture/r8-unit8.md +78 -0
  49. data/docs/architecture/r8-unit9.md +73 -0
  50. data/docs/architecture/refactoring-closure.md +343 -0
  51. data/docs/architecture/remaining-refactoring-plan.md +390 -0
  52. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  53. data/docs/architecture/storage-execution-boundaries.md +54 -0
  54. data/docs/architecture/tool-schema-recording-gap.md +56 -0
  55. data/docs/architecture/tracing.md +2 -2
  56. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  57. data/docs/architecture.md +222 -0
  58. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  59. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  60. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  61. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  62. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  63. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  64. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +7 -0
  65. data/docs/decisions/031-durable-multi-agent-coordination.md +7 -0
  66. data/docs/decisions/032-storage-backend-composition.md +85 -0
  67. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  68. data/docs/decisions/034-handoff-runner-coordination-ownership.md +78 -0
  69. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  70. data/docs/decisions/036-context-contract-ownership.md +86 -0
  71. data/docs/decisions/037-common-definition-ownership.md +62 -0
  72. data/docs/decisions/038-responsibility-based-source-layout.md +145 -0
  73. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +82 -0
  74. data/docs/decisions/040-configuration-default-composition.md +105 -0
  75. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  76. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  77. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  78. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  79. data/docs/decisions/045-worker-input-restriction-ownership.md +104 -0
  80. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  81. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  82. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  83. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  84. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  85. data/docs/decisions/051-execution-outcome-worker-ownership.md +153 -0
  86. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  87. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  88. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  89. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  90. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  91. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  92. data/docs/decisions/058-neutral-storage-primitives.md +83 -0
  93. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  94. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  95. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  96. data/docs/decisions/062-content-store-service.md +52 -0
  97. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  98. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  99. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  100. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  101. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  102. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  103. data/docs/decisions/README.md +52 -6
  104. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  105. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  106. data/docs/execution-boundaries-preview.md +45 -0
  107. data/docs/features.md +16 -5
  108. data/docs/getting-started.md +11 -8
  109. data/docs/migrations/0.15.md +5 -0
  110. data/docs/migrations/0.22.md +2 -2
  111. data/docs/migrations/durable-semantic-coordination-v2.md +8 -0
  112. data/docs/migrations/handoff-runner-multi-agent.md +37 -0
  113. data/docs/migrations/neutral-storage-spi.md +43 -0
  114. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  115. data/docs/migrations/persistence-failure-contracts.md +67 -0
  116. data/docs/migrations/r8-unit10.md +36 -0
  117. data/docs/migrations/r8-unit11.md +33 -0
  118. data/docs/migrations/r8-unit12.md +31 -0
  119. data/docs/migrations/r8-unit13.md +36 -0
  120. data/docs/migrations/r8-unit14.md +31 -0
  121. data/docs/migrations/r8-unit15.md +31 -0
  122. data/docs/migrations/r8-unit16.md +26 -0
  123. data/docs/migrations/r8-unit17.md +28 -0
  124. data/docs/migrations/r8-unit18.md +29 -0
  125. data/docs/migrations/r8-unit19.md +28 -0
  126. data/docs/migrations/r8-unit20.md +24 -0
  127. data/docs/migrations/r8-unit21.md +28 -0
  128. data/docs/migrations/r8-unit22.md +29 -0
  129. data/docs/migrations/r8-unit23.md +20 -0
  130. data/docs/migrations/r8-unit4.md +67 -0
  131. data/docs/migrations/r8-unit5.md +75 -0
  132. data/docs/migrations/r8-unit6.md +109 -0
  133. data/docs/migrations/r8-unit7.md +31 -0
  134. data/docs/migrations/r8-unit8.md +33 -0
  135. data/docs/migrations/r8-unit9.md +28 -0
  136. data/docs/migrations/shared-state-multi-agent.md +44 -0
  137. data/docs/migrations/storage-async-client.md +103 -0
  138. data/docs/migrations/storage-backend-composition.md +117 -0
  139. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  140. data/docs/migrations/vector-async-clients.md +110 -0
  141. data/docs/persistence-backends.md +190 -215
  142. data/docs/runtime-and-concurrency.md +142 -32
  143. data/lib/phronomy/agent/admission.rb +78 -0
  144. data/lib/phronomy/{agent_already_exists_error.rb → agent/agent_already_exists_error.rb} +2 -0
  145. data/lib/phronomy/{agent_busy_error.rb → agent/agent_busy_error.rb} +2 -0
  146. data/lib/phronomy/{agent_purged_error.rb → agent/agent_purged_error.rb} +2 -0
  147. data/lib/phronomy/agent/api/agent.rb +17 -0
  148. data/lib/phronomy/{execution_rehydration_required_error.rb → agent/api/execution_rehydration_required_error.rb} +2 -0
  149. data/lib/phronomy/agent/async_event_api.rb +38 -33
  150. data/lib/phronomy/agent/base.rb +106 -294
  151. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  152. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  153. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  154. data/lib/phronomy/agent/context_importer.rb +2 -2
  155. data/lib/phronomy/agent/{context_assembler.rb → context_preparation.rb} +157 -288
  156. data/lib/phronomy/agent/control_request.rb +21 -0
  157. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  158. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +32 -63
  159. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  160. data/lib/phronomy/agent/execution/dispatch_preparation.rb +195 -0
  161. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +43 -23
  162. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +8 -5
  163. data/lib/phronomy/agent/execution/execution_coordinator.rb +1744 -0
  164. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  165. data/lib/phronomy/agent/execution/execution_metadata.rb +54 -0
  166. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +288 -0
  167. data/lib/phronomy/agent/execution/execution_registry.rb +463 -0
  168. data/lib/phronomy/agent/execution/execution_session_runner.rb +113 -0
  169. data/lib/phronomy/agent/execution/initial_preparation.rb +323 -0
  170. data/lib/phronomy/agent/execution/invocation_actions.rb +368 -0
  171. data/lib/phronomy/agent/execution/invocation_transitions.rb +124 -0
  172. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  173. data/lib/phronomy/agent/execution_change.rb +161 -0
  174. data/lib/phronomy/agent/execution_environment.rb +36 -0
  175. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  176. data/lib/phronomy/agent/execution_observation.rb +35 -0
  177. data/lib/phronomy/agent/filterable.rb +182 -0
  178. data/lib/phronomy/agent/{handoff_context.rb → handoff/transfer_context.rb} +6 -6
  179. data/lib/phronomy/agent/{handoff_projection.rb → handoff/transfer_projection.rb} +14 -33
  180. data/lib/phronomy/agent/handoff_error.rb +7 -0
  181. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  182. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  183. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  184. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  185. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  186. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  187. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -15
  188. data/lib/phronomy/agent/persistence/agent_repository.rb +69 -0
  189. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  190. data/lib/phronomy/agent/persistence/codec.rb +338 -0
  191. data/lib/phronomy/agent/persistence/execution_repository.rb +124 -0
  192. data/lib/phronomy/agent/persistence/journal_repository.rb +63 -0
  193. data/lib/phronomy/agent/persistence/records.rb +33 -0
  194. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  195. data/lib/phronomy/agent/persistence/storage_schema.rb +29 -0
  196. data/lib/phronomy/agent/persistence/watermark.rb +29 -0
  197. data/lib/phronomy/agent/recovery/invocation_restorer.rb +124 -0
  198. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +28 -51
  199. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +50 -55
  200. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +38 -43
  201. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +26 -23
  202. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  203. data/lib/phronomy/agent/recovery/recovery_support.rb +224 -0
  204. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  205. data/lib/phronomy/agent/retention.rb +17 -0
  206. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  207. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  208. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  209. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  210. data/lib/phronomy/agent/{ruby_llm_materializer.rb → runtime_input.rb} +19 -39
  211. data/lib/phronomy/agent/saved_context_reader.rb +55 -0
  212. data/lib/phronomy/agent/settings.rb +47 -0
  213. data/lib/phronomy/agent/state_writer.rb +166 -0
  214. data/lib/phronomy/agent/store.rb +253 -0
  215. data/lib/phronomy/{stream_callback_error.rb → agent/stream_callback_error.rb} +2 -0
  216. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  217. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  218. data/lib/phronomy/agent/tool_execution/tool_binding.rb +30 -0
  219. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +114 -248
  220. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  221. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  222. data/lib/phronomy/common/configuration_error.rb +7 -0
  223. data/lib/phronomy/common/error.rb +5 -0
  224. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  225. data/lib/phronomy/common/values/serializable.rb +32 -0
  226. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  227. data/lib/phronomy/content_store/storage_schema.rb +12 -0
  228. data/lib/phronomy/content_store/stored_contents.rb +44 -0
  229. data/lib/phronomy/context/assembly.rb +202 -0
  230. data/lib/phronomy/{context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +5 -1
  231. data/lib/phronomy/context/candidate.rb +51 -0
  232. data/lib/phronomy/context/constraint.rb +47 -0
  233. data/lib/phronomy/{agent → context}/context_plan.rb +2 -2
  234. data/lib/phronomy/{agent → context}/context_plan_validator.rb +3 -3
  235. data/lib/phronomy/{agent → context}/context_policy.rb +1 -1
  236. data/lib/phronomy/{agent → context}/context_policy_input.rb +7 -7
  237. data/lib/phronomy/{agent → context}/context_policy_input_builder.rb +1 -1
  238. data/lib/phronomy/context/default_policy.rb +140 -0
  239. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  240. data/lib/phronomy/{agent → context}/llm_input_build_context.rb +2 -2
  241. data/lib/phronomy/{agent → context}/llm_input_manifest.rb +21 -21
  242. data/lib/phronomy/{agent → context}/llm_input_patch.rb +3 -3
  243. data/lib/phronomy/context/manifest_error.rb +10 -0
  244. data/lib/phronomy/context/prompt_template.rb +98 -0
  245. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  246. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  247. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  248. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  249. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  250. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  251. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  252. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  253. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  254. data/lib/phronomy/embeddings/base.rb +43 -0
  255. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  256. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  257. data/lib/phronomy/engine/concurrency/offload_pool.rb +39 -20
  258. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  259. data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +1 -1
  260. data/lib/phronomy/engine/event_loop.rb +164 -630
  261. data/lib/phronomy/engine/execution_binding.rb +23 -0
  262. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  263. data/lib/phronomy/engine/fsm_protocol.rb +9 -0
  264. data/lib/phronomy/engine/fsm_session.rb +37 -30
  265. data/lib/phronomy/engine/metrics.rb +28 -0
  266. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  267. data/lib/phronomy/engine/runtime.rb +104 -95
  268. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  269. data/lib/phronomy/execution/async_operation.rb +41 -0
  270. data/lib/phronomy/execution/backpressure_error.rb +7 -0
  271. data/lib/phronomy/{blocking.rb → execution/blocking.rb} +2 -2
  272. data/lib/phronomy/execution/cancellation_error.rb +7 -0
  273. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +5 -1
  274. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  275. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  276. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  277. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  278. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  279. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  280. data/lib/phronomy/execution/concurrency/worker_input_restricted.rb +14 -0
  281. data/lib/phronomy/execution/event_loop_reentrancy_error.rb +8 -0
  282. data/lib/phronomy/{execution.rb → execution/execution.rb} +57 -30
  283. data/lib/phronomy/{execution_cancellation_error.rb → execution/execution_cancellation_error.rb} +2 -0
  284. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  285. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  286. data/lib/phronomy/{execution_timeout_error.rb → execution/execution_timeout_error.rb} +2 -0
  287. data/lib/phronomy/{invalid_async_entry_action_error.rb → execution/invalid_async_entry_action_error.rb} +2 -0
  288. data/lib/phronomy/{invalid_async_transition_action_error.rb → execution/invalid_async_transition_action_error.rb} +2 -0
  289. data/lib/phronomy/{invalid_async_workflow_action_error.rb → execution/invalid_async_workflow_action_error.rb} +2 -0
  290. data/lib/phronomy/{invocation_context.rb → execution/invocation_context.rb} +0 -15
  291. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  292. data/lib/phronomy/execution/pool_shutdown_error.rb +7 -0
  293. data/lib/phronomy/execution/recursion_limit_error.rb +7 -0
  294. data/lib/phronomy/execution/runnable.rb +24 -0
  295. data/lib/phronomy/execution/runtime_shutdown_error.rb +7 -0
  296. data/lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb +7 -0
  297. data/lib/phronomy/execution/scheduler_reentrancy_error.rb +9 -0
  298. data/lib/phronomy/{engine → execution}/task_result.rb +7 -3
  299. data/lib/phronomy/execution/timeout_error.rb +7 -0
  300. data/lib/phronomy/execution/wait_policy.rb +24 -0
  301. data/lib/phronomy/filter/filter_block_error.rb +14 -0
  302. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  303. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  304. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  305. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  306. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  307. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  308. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  309. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  310. data/lib/phronomy/llm_adapter/authentication_error.rb +9 -0
  311. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  312. data/lib/phronomy/llm_adapter/base.rb +49 -68
  313. data/lib/phronomy/llm_adapter/context_length_error.rb +9 -0
  314. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  315. data/lib/phronomy/llm_adapter/message.rb +62 -0
  316. data/lib/phronomy/llm_adapter/rate_limit_error.rb +9 -0
  317. data/lib/phronomy/llm_adapter/request.rb +79 -0
  318. data/lib/phronomy/llm_adapter/response.rb +36 -0
  319. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  320. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  321. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  322. data/lib/phronomy/llm_adapter.rb +3 -16
  323. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  324. data/lib/phronomy/multi_agent/admission_registry.rb +15 -2
  325. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  326. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  327. data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
  328. data/lib/phronomy/{agent → multi_agent}/handoff_capability_factory.rb +21 -2
  329. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  330. data/lib/phronomy/{agent → multi_agent}/handoff_policy.rb +1 -1
  331. data/lib/phronomy/multi_agent/handoff_runner.rb +156 -0
  332. data/lib/phronomy/{agent → multi_agent}/handoff_state.rb +2 -2
  333. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  334. data/lib/phronomy/multi_agent/persistence/codec.rb +75 -0
  335. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  336. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  337. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  338. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +124 -0
  339. data/lib/phronomy/multi_agent/persistence/team_repository.rb +69 -0
  340. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  341. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  342. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  343. data/lib/phronomy/multi_agent/store.rb +156 -0
  344. data/lib/phronomy/multi_agent/team_coordinator.rb +87 -75
  345. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  346. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +5 -5
  347. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  348. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  349. data/lib/phronomy/persistence/content_repository.rb +26 -0
  350. data/lib/phronomy/persistence/errors.rb +16 -0
  351. data/lib/phronomy/persistence/migration/initial_format_migration.rb +21 -21
  352. data/lib/phronomy/persistence/persistence.rb +23 -0
  353. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  354. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  355. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  356. data/lib/phronomy/persistence/transaction.rb +86 -0
  357. data/lib/phronomy/persistence_composition/storage_schema.rb +29 -0
  358. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  359. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  360. data/lib/phronomy/runtime_composition/agent_defaults.rb +11 -0
  361. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  362. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  363. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  364. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  365. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  366. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  367. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  368. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  369. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  370. data/lib/phronomy/storage/async/async_client.rb +60 -0
  371. data/lib/phronomy/storage/backend.rb +107 -0
  372. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  373. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  374. data/lib/phronomy/storage/blobs.rb +31 -0
  375. data/lib/phronomy/storage/condition.rb +25 -0
  376. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  377. data/lib/phronomy/storage/conflict_error.rb +9 -0
  378. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  379. data/lib/phronomy/storage/entry.rb +33 -0
  380. data/lib/phronomy/storage/guard_ref.rb +13 -0
  381. data/lib/phronomy/storage/not_found_error.rb +9 -0
  382. data/lib/phronomy/storage/record_codec.rb +177 -0
  383. data/lib/phronomy/storage/records.rb +61 -0
  384. data/lib/phronomy/storage/resource.rb +126 -0
  385. data/lib/phronomy/storage/scope.rb +25 -0
  386. data/lib/phronomy/storage/serialization_error.rb +9 -0
  387. data/lib/phronomy/storage/streams.rb +44 -0
  388. data/lib/phronomy/storage/transaction_error.rb +10 -0
  389. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  390. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  391. data/lib/phronomy/storage/validation.rb +53 -0
  392. data/lib/phronomy/storage/view.rb +131 -0
  393. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  394. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  395. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  396. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  397. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +54 -48
  398. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +1 -1
  399. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +1 -1
  400. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  401. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +52 -46
  402. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  403. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +124 -0
  404. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  405. data/lib/phronomy/tool/authorization.rb +77 -0
  406. data/lib/phronomy/tool/base.rb +549 -9
  407. data/lib/phronomy/tool/call_request.rb +40 -0
  408. data/lib/phronomy/{agent/tool_definition_set.rb → tool/definition_set.rb} +16 -12
  409. data/lib/phronomy/tool/operation.rb +88 -0
  410. data/lib/phronomy/tool/schema.rb +214 -0
  411. data/lib/phronomy/tool/settings.rb +31 -0
  412. data/lib/phronomy/tool/tool_error.rb +7 -0
  413. data/lib/phronomy/{agent → tool}/tool_executor.rb +10 -7
  414. data/lib/phronomy/tools/agent.rb +22 -90
  415. data/lib/phronomy/tools/mcp.rb +21 -14
  416. data/lib/phronomy/tools/vector_search.rb +4 -4
  417. data/lib/phronomy/tracing/automatic.rb +11 -6
  418. data/lib/phronomy/tracing/base.rb +1 -1
  419. data/lib/phronomy/tracing/observation.rb +37 -0
  420. data/lib/phronomy/tracing/settings.rb +36 -0
  421. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  422. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  423. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  424. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  425. data/lib/phronomy/vector_store/base.rb +118 -49
  426. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  427. data/lib/phronomy/vector_store.rb +2 -17
  428. data/lib/phronomy/version.rb +1 -1
  429. data/lib/phronomy/workflow/completion.rb +11 -0
  430. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
  431. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  432. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +5 -1
  433. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  434. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  435. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +186 -0
  436. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +145 -115
  437. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  438. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  439. data/lib/phronomy/workflow/persistence/state_repository.rb +63 -0
  440. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  441. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  442. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  443. data/lib/phronomy/workflow/runtime_binding/workflow_terminal_policy.rb +40 -0
  444. data/lib/phronomy.rb +97 -98
  445. data/scripts/api_snapshot.rb +36 -16
  446. data/scripts/storage_spi_snapshot.rb +36 -0
  447. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  448. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  449. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  450. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  451. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  452. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  453. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  454. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  455. data/sig/phronomy/agent/admission.rbs +19 -0
  456. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  457. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  458. data/sig/phronomy/agent/store.rbs +73 -0
  459. data/sig/phronomy/agent.rbs +14 -139
  460. data/sig/phronomy/common.rbs +7 -0
  461. data/sig/phronomy/content_store/contracts.rbs +13 -0
  462. data/sig/phronomy/context.rbs +152 -0
  463. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  464. data/sig/phronomy/embeddings/base.rbs +17 -0
  465. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  466. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  467. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  468. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  469. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  470. data/sig/phronomy/engine/observability.rbs +13 -0
  471. data/sig/phronomy/execution/async_operation.rbs +7 -0
  472. data/sig/phronomy/execution/blocking.rbs +5 -0
  473. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  474. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  475. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  476. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  477. data/sig/phronomy/execution/errors.rbs +7 -0
  478. data/sig/phronomy/execution/execution.rbs +41 -0
  479. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  480. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  481. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  482. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  483. data/sig/phronomy/execution/task_result.rbs +29 -0
  484. data/sig/phronomy/extensions.rbs +0 -21
  485. data/sig/phronomy/handoff.rbs +12 -4
  486. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  487. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  488. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  489. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  490. data/sig/phronomy/llm_context_window.rbs +10 -0
  491. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  492. data/sig/phronomy/multi_agent/store.rbs +54 -0
  493. data/sig/phronomy/multi_agent.rbs +24 -3
  494. data/sig/phronomy/persistence/errors.rbs +18 -0
  495. data/sig/phronomy/persistence/migration.rbs +13 -0
  496. data/sig/phronomy/persistence/transaction.rbs +19 -0
  497. data/sig/phronomy/persistence.rbs +3 -185
  498. data/sig/phronomy/persistence_composition.rbs +19 -0
  499. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  500. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  501. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  502. data/sig/phronomy/storage/contracts.rbs +160 -0
  503. data/sig/phronomy/tool.rbs +58 -5
  504. data/sig/phronomy/tracing/observation.rbs +7 -0
  505. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  506. data/sig/phronomy/vector_store/base.rbs +23 -0
  507. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  508. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  509. data/sig/phronomy/workflow/store.rbs +8 -0
  510. data/sig/phronomy/workflow.rbs +1 -1
  511. data/sig/phronomy.rbs +0 -49
  512. metadata +462 -130
  513. data/lib/phronomy/agent/agent_invocation_session_builder.rb +0 -584
  514. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  515. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  516. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  517. data/lib/phronomy/agent/context/capability/base.rb +0 -507
  518. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  519. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  520. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  521. data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
  522. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  523. data/lib/phronomy/agent/handoff_request.rb +0 -45
  524. data/lib/phronomy/agent/handoff_runner.rb +0 -178
  525. data/lib/phronomy/agent/phase_machine_builder.rb +0 -152
  526. data/lib/phronomy/agent/provider_call_outcome.rb +0 -106
  527. data/lib/phronomy/agent/recovery_support.rb +0 -504
  528. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  529. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  530. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  531. data/lib/phronomy/agent/tool_call_intercepted.rb +0 -29
  532. data/lib/phronomy/agent/tool_invocation_session_builder.rb +0 -276
  533. data/lib/phronomy/configuration.rb +0 -59
  534. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  535. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  536. data/lib/phronomy/generator_verifier.rb +0 -369
  537. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  538. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  539. data/lib/phronomy/metrics.rb +0 -26
  540. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  541. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  542. data/lib/phronomy/persistence/in_memory.rb +0 -690
  543. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  544. data/lib/phronomy/persistence.rb +0 -276
  545. data/lib/phronomy/recovery.rb +0 -186
  546. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  547. data/lib/phronomy/runnable.rb +0 -54
  548. data/lib/phronomy/token_usage.rb +0 -85
  549. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  550. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  551. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  552. data/lib/phronomy/workflow_recovery.rb +0 -123
  553. data/sig/phronomy/llm_adapter.rbs +0 -11
  554. data/sig/phronomy/runtime.rbs +0 -76
  555. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  556. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  557. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
@@ -1,184 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module Concerns
6
- # Adds input, output, and tool-result filter support to an agent.
7
- #
8
- # Filters transform (or block) values at three call sites:
9
- # - *input* — the raw user input string, before the LLM is called
10
- # - *output* — the final LLM output string, before it is returned
11
- # - *tool result* — the return value of each tool call
12
- #
13
- # Each filter in the chain receives the value returned by the previous one.
14
- # A {Phronomy::FilterBlockError} raised inside any filter propagates to the
15
- # caller.
16
- #
17
- # @api private
18
- module Filterable
19
- def self.included(base)
20
- base.extend(ClassMethods)
21
- end
22
-
23
- # Class-level DSL mixed into the including agent class.
24
- module ClassMethods
25
- # Registers a filter applied to every invocation's user input.
26
- # Accepts either a {Phronomy::Filter::Base} instance or a subclass;
27
- # when a class is given it is instantiated with +.new+.
28
- # @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
29
- # @return [void]
30
- # @api public
31
- def input_filter(filter)
32
- @_class_input_filters ||= []
33
- @_class_input_filters << _resolve_filter(filter)
34
- end
35
-
36
- # Registers a filter applied to every invocation's final LLM output.
37
- # Accepts either a {Phronomy::Filter::Base} instance or a subclass;
38
- # when a class is given it is instantiated with +.new+.
39
- # @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
40
- # @return [void]
41
- # @api public
42
- def output_filter(filter)
43
- @_class_output_filters ||= []
44
- @_class_output_filters << _resolve_filter(filter)
45
- end
46
-
47
- # Registers a filter applied to every tool result for all tools.
48
- # Accepts either a {Phronomy::Filter::Base} instance or a subclass;
49
- # when a class is given it is instantiated with +.new+.
50
- # @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
51
- # @return [void]
52
- # @api public
53
- def tool_result_filter(filter)
54
- @_class_tool_result_filters ||= []
55
- @_class_tool_result_filters << _resolve_filter(filter)
56
- end
57
-
58
- # @return [Array<Phronomy::Filter::Base>]
59
- # @api private
60
- def _class_input_filters
61
- @_class_input_filters || []
62
- end
63
-
64
- # @return [Array<Phronomy::Filter::Base>]
65
- # @api private
66
- def _class_output_filters
67
- @_class_output_filters || []
68
- end
69
-
70
- # @return [Array<Phronomy::Filter::Base>]
71
- # @api private
72
- def _class_tool_result_filters
73
- @_class_tool_result_filters || []
74
- end
75
-
76
- private
77
-
78
- # Coerce +filter+ to an instance: if a Class is passed, call +.new+;
79
- # otherwise return the object as-is.
80
- # @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
81
- # @return [Phronomy::Filter::Base]
82
- # @api private
83
- def _resolve_filter(filter)
84
- filter.is_a?(Class) ? filter.new : filter
85
- end
86
- end
87
-
88
- # Registers an input filter on this instance.
89
- # Accepts either a {Phronomy::Filter::Base} instance or a subclass;
90
- # when a class is given it is instantiated with +.new+.
91
- # Runs in addition to any class-level input filters.
92
- # @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
93
- # @return [self]
94
- # @api public
95
- def add_input_filter(filter)
96
- @_instance_input_filters ||= []
97
- @_instance_input_filters << _resolve_filter(filter)
98
- self
99
- end
100
-
101
- # Registers an output filter on this instance.
102
- # Accepts either a {Phronomy::Filter::Base} instance or a subclass;
103
- # when a class is given it is instantiated with +.new+.
104
- # Runs in addition to any class-level output filters.
105
- # @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
106
- # @return [self]
107
- # @api public
108
- def add_output_filter(filter)
109
- @_instance_output_filters ||= []
110
- @_instance_output_filters << _resolve_filter(filter)
111
- self
112
- end
113
-
114
- # Registers a tool result filter on this instance.
115
- #
116
- # When called with two arguments, the filter is scoped to the given tool
117
- # class only. When called with one argument, it applies to all tools.
118
- # Accepts either a {Phronomy::Filter::Base} instance or a subclass;
119
- # when a class is given it is instantiated with +.new+.
120
- #
121
- # @overload add_tool_result_filter(filter)
122
- # @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>] applied to every tool
123
- # @overload add_tool_result_filter(tool_class, filter)
124
- # @param tool_class [Class] scope the filter to this tool
125
- # @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
126
- # @return [self]
127
- # @api public
128
- def add_tool_result_filter(tool_class_or_filter, filter = nil)
129
- if filter.nil?
130
- # Single-argument form: apply to all tools.
131
- @_instance_tool_result_filters ||= []
132
- @_instance_tool_result_filters << _resolve_filter(tool_class_or_filter)
133
- else
134
- # Two-argument form: scoped to one tool class.
135
- @_scoped_tool_result_filters ||= {}
136
- (@_scoped_tool_result_filters[tool_class_or_filter] ||= []) << _resolve_filter(filter)
137
- end
138
- self
139
- end
140
-
141
- private
142
-
143
- # Run input filters (class-level then instance-level).
144
- # @param input [String, Hash] the raw user input
145
- # @return [String, Hash] the (possibly transformed) input
146
- # @api private
147
- def run_input_filters!(input)
148
- class_filters = self.class._class_input_filters
149
- inst_filters = @_instance_input_filters || []
150
- (class_filters + inst_filters).inject(input) { |val, f| f.call(val) }
151
- end
152
-
153
- # Run output filters (class-level then instance-level).
154
- # @param output [String] the LLM output
155
- # @return [String] the (possibly transformed) output
156
- # @api private
157
- def run_output_filters!(output)
158
- class_filters = self.class._class_output_filters
159
- inst_filters = @_instance_output_filters || []
160
- (class_filters + inst_filters).inject(output) { |val, f| f.call(val) }
161
- end
162
-
163
- # Collect all tool-result filters (global + scoped) for a given tool class.
164
- # @param tool_class [Class]
165
- # @return [Array<Phronomy::Filter::Base>]
166
- # @api private
167
- def _tool_result_filters_for(tool_class)
168
- global = self.class._class_tool_result_filters + (@_instance_tool_result_filters || [])
169
- scoped = (@_scoped_tool_result_filters || {})[tool_class] || []
170
- global + scoped
171
- end
172
-
173
- # Coerce +filter+ to an instance: if a Class is passed, call +.new+;
174
- # otherwise return the object as-is.
175
- # @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
176
- # @return [Phronomy::Filter::Base]
177
- # @api private
178
- def _resolve_filter(filter)
179
- filter.is_a?(Class) ? filter.new : filter
180
- end
181
- end
182
- end
183
- end
184
- end
@@ -1,507 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module Context
6
- module Capability
7
- # Base class extending RubyLLM::Tool with Phronomy-specific DSL.
8
- class Base < RubyLLM::Tool
9
- class << self
10
- # @api public
11
- def tool_name(value = nil)
12
- return @tool_name if value.nil?
13
-
14
- @tool_name = value.to_s
15
- end
16
-
17
- # RubyLLM stores Tool descriptions in a class-instance variable.
18
- # Preserve normal class inheritance semantics so Phronomy's anonymous
19
- # decorator subclasses do not lose their parent's description.
20
- # @api public
21
- def description(text = nil)
22
- unless text
23
- return @description if instance_variable_defined?(:@description)
24
- return superclass.description if superclass.respond_to?(:description)
25
-
26
- return nil
27
- end
28
-
29
- @description = text
30
- end
31
- alias_method :desc, :description
32
-
33
- # RubyLLM stores declared parameters in a class-instance variable.
34
- # Copy the parent's registry on first access so child classes inherit
35
- # existing parameters while remaining free to add their own.
36
- # @api public
37
- def parameters
38
- return @parameters if instance_variable_defined?(:@parameters)
39
-
40
- parent = superclass.respond_to?(:parameters) ? superclass.parameters : {}
41
- @parameters = parent.dup
42
- end
43
-
44
- # RubyLLM stores an explicit .params schema definition in a
45
- # class-instance variable. Readers must fall back to the parent.
46
- # @api public
47
- def params_schema_definition
48
- return @params_schema_definition if instance_variable_defined?(:@params_schema_definition)
49
- return superclass.params_schema_definition if superclass.respond_to?(:params_schema_definition)
50
-
51
- nil
52
- end
53
-
54
- # RubyLLM provider params are also class-instance state. Copy them on
55
- # first access to preserve inheritance without sharing the top-level
56
- # mutable Hash between parent and child.
57
- # @api public
58
- def provider_params
59
- return @provider_params if instance_variable_defined?(:@provider_params)
60
-
61
- parent = superclass.respond_to?(:provider_params) ? superclass.provider_params : {}
62
- @provider_params = duplicate_configuration(parent)
63
- end
64
-
65
- # @api public
66
- def param(name, enum: nil, properties: nil, **options)
67
- super(name, **options)
68
- param_enums[name] = duplicate_configuration(enum) if enum
69
- param_schemas[name] = normalize_nested_schema(properties) if properties
70
- end
71
-
72
- # @api public
73
- def param_enums
74
- return @param_enums if instance_variable_defined?(:@param_enums)
75
-
76
- parent = superclass.respond_to?(:param_enums) ? superclass.param_enums : {}
77
- @param_enums = duplicate_configuration(parent)
78
- end
79
-
80
- # @api public
81
- def param_schemas
82
- return @param_schemas if instance_variable_defined?(:@param_schemas)
83
-
84
- parent = superclass.respond_to?(:param_schemas) ? superclass.param_schemas : {}
85
- @param_schemas = duplicate_configuration(parent)
86
- end
87
-
88
- private
89
-
90
- def duplicate_configuration(value)
91
- case value
92
- when Hash
93
- value.to_h do |key, child|
94
- [key, duplicate_configuration(child)]
95
- end
96
- when Array
97
- value.map { |child| duplicate_configuration(child) }
98
- else
99
- value
100
- end
101
- end
102
-
103
- def normalize_nested_schema(props)
104
- props.transform_keys(&:to_sym).transform_values do |spec|
105
- normalized = spec.transform_keys(&:to_sym)
106
- normalized[:type] ||= :string
107
- if normalized[:properties]
108
- normalized[:properties] = normalize_nested_schema(normalized[:properties])
109
- end
110
- normalized
111
- end
112
- end
113
-
114
- public
115
-
116
- # Declares whether Tool work is safe to run cooperatively on the EventLoop
117
- # or must be offloaded to a bounded worker pool.
118
- #
119
- # Phronomy does not classify the reason for offloading. Blocking I/O,
120
- # CPU-bound synchronous work, and other long synchronous calls all use
121
- # +:offloaded+. The application owns that workload classification.
122
- # @api public
123
- def execution_mode(value = nil)
124
- if value.nil?
125
- return @execution_mode if instance_variable_defined?(:@execution_mode)
126
- return superclass.execution_mode if superclass.respond_to?(:execution_mode)
127
-
128
- return :offloaded
129
- end
130
-
131
- valid = %i[cooperative offloaded]
132
- unless valid.include?(value)
133
- raise ArgumentError,
134
- "execution_mode must be one of #{valid.inspect}, got #{value.inspect}"
135
- end
136
- @execution_mode = value
137
- end
138
-
139
- # Configures execution-error handling. Supported values are :raise
140
- # and :suppress only.
141
- # @api public
142
- def on_error(behavior = nil)
143
- if behavior.nil?
144
- return @on_error if instance_variable_defined?(:@on_error)
145
- return superclass.on_error if superclass.respond_to?(:on_error)
146
-
147
- return :raise
148
- end
149
-
150
- valid = %i[raise suppress]
151
- unless valid.include?(behavior)
152
- raise ArgumentError,
153
- "on_error must be one of #{valid.inspect}, got #{behavior.inspect}"
154
- end
155
- @on_error = behavior
156
- end
157
-
158
- # @api public
159
- def on_schema_error(behavior = nil)
160
- if behavior.nil?
161
- return @on_schema_error if instance_variable_defined?(:@on_schema_error)
162
- return superclass.on_schema_error if superclass.respond_to?(:on_schema_error)
163
-
164
- return :return_error
165
- end
166
-
167
- @on_schema_error = behavior
168
- end
169
-
170
- # @api public
171
- def requires_approval(value = :__unset__, &block)
172
- if block
173
- unless value == :__unset__
174
- raise ArgumentError, "pass either a value or a block to requires_approval"
175
- end
176
- @requires_approval = block
177
- elsif value == :__unset__
178
- return @requires_approval if instance_variable_defined?(:@requires_approval)
179
- return superclass.requires_approval if superclass.respond_to?(:requires_approval)
180
-
181
- false
182
- else
183
- unless value == true || value == false || value.respond_to?(:call)
184
- raise ArgumentError, "requires_approval must be true, false, or callable"
185
- end
186
- @requires_approval = value
187
- end
188
- end
189
-
190
- # @api public
191
- def approval_facts(&block)
192
- if block
193
- @approval_facts = block
194
- elsif instance_variable_defined?(:@approval_facts)
195
- @approval_facts
196
- elsif superclass.respond_to?(:approval_facts)
197
- superclass.approval_facts
198
- end
199
- end
200
-
201
- # @api public
202
- def redact_params(*names)
203
- if names.empty?
204
- parent = superclass.respond_to?(:redact_params) ? superclass.redact_params : []
205
- ((@redacted_params || []) + parent).uniq
206
- else
207
- @redacted_params = ((@redacted_params || []) + names.map(&:to_sym)).uniq
208
- end
209
- end
210
-
211
- # @api public
212
- def max_result_size(value = :__unset__)
213
- if value == :__unset__
214
- return @max_result_size if instance_variable_defined?(:@max_result_size)
215
- return superclass.max_result_size if superclass.respond_to?(:max_result_size)
216
-
217
- return nil
218
- end
219
-
220
- @max_result_size = value
221
- end
222
- end
223
-
224
- def name
225
- self.class.tool_name || super
226
- end
227
-
228
- def params_schema
229
- schema = super
230
- return schema if schema.nil?
231
-
232
- properties = schema.dig("properties") || schema.dig(:properties)
233
- return schema unless properties
234
-
235
- self.class.param_enums.each do |param_name, values|
236
- key = properties.key?(param_name.to_s) ? param_name.to_s : param_name.to_sym
237
- next unless properties[key]
238
-
239
- param_type = properties[key]["type"]
240
- properties[key]["enum"] = values.map do |value|
241
- case param_type
242
- when "integer"
243
- value.is_a?(Integer) ? value : Integer(value.to_s)
244
- when "number"
245
- value.is_a?(Numeric) ? value : Float(value.to_s)
246
- when "boolean"
247
- unless value == true || value == false
248
- raise ArgumentError,
249
- "boolean enum values must be true or false (got: #{value.inspect})"
250
- end
251
- value
252
- else
253
- value.to_s
254
- end
255
- end
256
- end
257
-
258
- self.class.param_schemas.each do |param_name, nested|
259
- key = properties.key?(param_name.to_s) ? param_name.to_s : param_name.to_sym
260
- next unless properties[key]
261
- properties[key]["properties"] = nested_schema_to_json_schema(nested)
262
- end
263
-
264
- schema
265
- end
266
-
267
- # @api public
268
- def call(args, cancellation_token: nil)
269
- cancellation_token&.raise_if_cancelled!
270
- validated_args, schema_error = validate_and_coerce(args)
271
- if schema_error
272
- case self.class.on_schema_error
273
- when :raise
274
- raise Phronomy::ToolError,
275
- "#{self.class.name} schema error: #{schema_error}"
276
- else
277
- return "Schema validation failed: #{schema_error}"
278
- end
279
- end
280
-
281
- if cancellation_token && execute_accepts_cancellation_token?
282
- validated_args = validated_args.merge(cancellation_token: cancellation_token)
283
- end
284
- result = super(validated_args)
285
- truncate_result_if_needed(result)
286
- rescue Phronomy::ToolError, Phronomy::CancellationError
287
- raise
288
- rescue => error
289
- if self.class.on_error == :suppress
290
- msg = "[Phronomy] Tool #{self.class.name} suppressed error: " \
291
- "#{error.class}: #{error.message}"
292
- if Phronomy.configuration.logger
293
- Phronomy.configuration.logger.warn(msg)
294
- else
295
- warn msg
296
- end
297
- "Tool error suppressed: #{error.message}"
298
- else
299
- raise Phronomy::ToolError,
300
- "#{self.class.name} execution failed: #{error.message}"
301
- end
302
- end
303
-
304
- # @api public
305
- def call_async(
306
- args,
307
- cancellation_token: nil,
308
- config: {}
309
- )
310
- Phronomy::Agent::ToolExecutor.call_async(
311
- tool: self,
312
- args: args,
313
- cancellation_token: cancellation_token,
314
- config: config
315
- )
316
- end
317
-
318
- def requires_approval
319
- self.class.requires_approval
320
- end
321
-
322
- def requires_approval?
323
- self.class.requires_approval
324
- end
325
-
326
- # @api public
327
- def tool_origin
328
- :local
329
- end
330
-
331
- # @api public
332
- def approval_metadata
333
- {}
334
- end
335
-
336
- # @api public
337
- def execute(**_args)
338
- raise NotImplementedError, "#{self.class}#execute is not implemented"
339
- end
340
-
341
- private
342
-
343
- def execute_accepts_cancellation_token?
344
- method(:execute).parameters.any? do |type, name|
345
- name == :cancellation_token && %i[key keyreq].include?(type)
346
- end
347
- end
348
-
349
- def truncate_result_if_needed(result)
350
- max = self.class.max_result_size || Phronomy.configuration.tool_result_max_size
351
- return result unless max && result.respond_to?(:length) && result.length > max
352
-
353
- msg = "[Phronomy] Tool #{self.class.name} result truncated " \
354
- "(#{result.length} chars > #{max} limit)"
355
- if Phronomy.configuration.logger
356
- Phronomy.configuration.logger.warn(msg)
357
- else
358
- warn msg
359
- end
360
- "#{result[0, max]}...[truncated]"
361
- end
362
-
363
- def redacted_args(args)
364
- redacted = self.class.redact_params
365
- return args if redacted.empty?
366
-
367
- args.each_with_object({}) do |(key, value), result|
368
- result[key] = redacted.include?(key.to_sym) ? "[REDACTED]" : value
369
- end
370
- end
371
-
372
- def validate_and_coerce(args)
373
- return [args, nil] if self.class.parameters.empty?
374
-
375
- normalized = (args || {}).transform_keys(&:to_sym)
376
- coerce_mode = self.class.on_schema_error == :coerce
377
- result = {}
378
-
379
- self.class.parameters.each do |name, param|
380
- value = normalized[name]
381
- if value.nil?
382
- return [nil, "required parameter '#{name}' is missing"] if param.required
383
- next
384
- end
385
-
386
- if coerce_mode
387
- coerced, error = coerce_value(value, param.type)
388
- return [nil, error] if error
389
- value = coerced
390
- else
391
- error = type_error(value, param.type)
392
- return [nil, error] if error
393
- end
394
-
395
- if param.type.to_sym == :object
396
- nested_schema = self.class.param_schemas[name]
397
- if nested_schema
398
- error = validate_nested_object(value, nested_schema, name.to_s)
399
- return [nil, error] if error
400
- end
401
- end
402
-
403
- enum_vals = self.class.param_enums[name]
404
- if enum_vals && !enum_vals.map(&:to_s).include?(value.to_s)
405
- return [nil,
406
- "parameter '#{name}' must be one of: #{enum_vals.join(", ")} " \
407
- "(got: #{value.inspect})"]
408
- end
409
-
410
- result[name] = value
411
- end
412
-
413
- extra = normalized.keys - self.class.parameters.keys
414
- unless extra.empty?
415
- return [nil, "unknown parameter(s): #{extra.inspect}"]
416
- end
417
-
418
- [result, nil]
419
- end
420
-
421
- def nested_schema_to_json_schema(nested)
422
- nested.each_with_object({}) do |(prop_name, spec), result|
423
- entry = {"type" => spec[:type].to_s}
424
- entry["description"] = spec[:desc] if spec[:desc]
425
- entry["enum"] = spec[:enum] if spec[:enum]
426
- if spec[:properties]
427
- entry["properties"] = nested_schema_to_json_schema(spec[:properties])
428
- end
429
- result[prop_name.to_s] = entry
430
- end
431
- end
432
-
433
- def validate_nested_object(value, properties, path)
434
- return "field '#{path}' must be an object (Hash)" unless value.is_a?(Hash)
435
-
436
- normalized = value.transform_keys(&:to_sym)
437
- extra = normalized.keys - properties.keys
438
- unless extra.empty?
439
- return "nested field '#{path}' contains undeclared key(s): #{extra.inspect}"
440
- end
441
-
442
- properties.each do |name, spec|
443
- field_path = "#{path}.#{name}"
444
- field_value = normalized[name]
445
-
446
- if field_value.nil?
447
- return "nested required field '#{field_path}' is missing" if spec[:required]
448
- next
449
- end
450
-
451
- error = type_error(field_value, spec[:type])
452
- return "nested field '#{field_path}': #{error}" if error
453
-
454
- next unless spec[:type].to_sym == :object && spec[:properties]
455
-
456
- error = validate_nested_object(field_value, spec[:properties], field_path)
457
- return error if error
458
- end
459
- nil
460
- end
461
-
462
- def type_error(value, declared_type)
463
- return nil if value.nil?
464
-
465
- ok = case declared_type.to_sym
466
- when :string then value.is_a?(String)
467
- when :integer then value.is_a?(Integer)
468
- when :number, :float then value.is_a?(Numeric)
469
- when :boolean then [true, false].include?(value)
470
- when :array then value.is_a?(Array)
471
- when :object then value.is_a?(Hash)
472
- else true
473
- end
474
-
475
- return nil if ok
476
-
477
- shown = value.respond_to?(:keys) ? "(object)" : value.inspect
478
- "parameter '#{shown}' expected type #{declared_type}"
479
- end
480
-
481
- def coerce_value(value, declared_type)
482
- return [value, nil] if value.nil?
483
-
484
- case declared_type.to_sym
485
- when :string
486
- [value.to_s, nil]
487
- when :integer
488
- [Integer(value), nil]
489
- when :number, :float
490
- [Float(value), nil]
491
- when :boolean
492
- case value.to_s.downcase
493
- when "true" then [true, nil]
494
- when "false" then [false, nil]
495
- else [nil, "parameter cannot be coerced to boolean: #{value.inspect}"]
496
- end
497
- else
498
- [value, nil]
499
- end
500
- rescue ArgumentError, TypeError
501
- [nil, "parameter cannot be coerced to #{declared_type}: #{value.inspect}"]
502
- end
503
- end
504
- end
505
- end
506
- end
507
- end