phronomy 0.27.0 → 0.29.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 (517) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +52 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +28 -7
  6. data/benchmark/bench_agent_invoke.rb +25 -50
  7. data/benchmark/bench_context_assembler.rb +14 -11
  8. data/benchmark/bench_regression.rb +1 -1
  9. data/benchmark/bench_tool_schema.rb +1 -1
  10. data/docs/application-recipes.md +229 -0
  11. data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
  12. data/docs/architecture/agent-transition-ownership.md +13 -89
  13. data/docs/architecture/backend-groups.md +152 -0
  14. data/docs/architecture/before-llm-input.md +3 -3
  15. data/docs/architecture/context-management.md +1 -1
  16. data/docs/architecture/dx05-agent-scope.md +119 -0
  17. data/docs/architecture/dx05-workflow-task-state.md +134 -0
  18. data/docs/architecture/error-api-review.md +245 -0
  19. data/docs/architecture/execution-abstraction-review.md +88 -0
  20. data/docs/architecture/execution-boundaries.md +28 -0
  21. data/docs/architecture/execution-metadata-and-values.md +6 -0
  22. data/docs/architecture/multi-agent-handoff.md +6 -5
  23. data/docs/architecture/persistence.md +27 -9
  24. data/docs/architecture/r8-unit1.md +104 -0
  25. data/docs/architecture/r8-unit10.md +72 -0
  26. data/docs/architecture/r8-unit11.md +71 -0
  27. data/docs/architecture/r8-unit12.md +67 -0
  28. data/docs/architecture/r8-unit13.md +61 -0
  29. data/docs/architecture/r8-unit14.md +72 -0
  30. data/docs/architecture/r8-unit15.md +65 -0
  31. data/docs/architecture/r8-unit16.md +54 -0
  32. data/docs/architecture/r8-unit17.md +73 -0
  33. data/docs/architecture/r8-unit18.md +92 -0
  34. data/docs/architecture/r8-unit19.md +61 -0
  35. data/docs/architecture/r8-unit2.md +55 -0
  36. data/docs/architecture/r8-unit20.md +51 -0
  37. data/docs/architecture/r8-unit21.md +79 -0
  38. data/docs/architecture/r8-unit22.md +74 -0
  39. data/docs/architecture/r8-unit23.md +81 -0
  40. data/docs/architecture/r8-unit3.md +130 -0
  41. data/docs/architecture/r8-unit4.md +79 -0
  42. data/docs/architecture/r8-unit5.md +115 -0
  43. data/docs/architecture/r8-unit6.md +71 -0
  44. data/docs/architecture/r8-unit7.md +70 -0
  45. data/docs/architecture/r8-unit8.md +78 -0
  46. data/docs/architecture/r8-unit9.md +73 -0
  47. data/docs/architecture/refactoring-closure.md +10 -1
  48. data/docs/architecture/remaining-refactoring-plan.md +16 -0
  49. data/docs/architecture/storage-execution-boundaries.md +54 -0
  50. data/docs/architecture/tool-schema-recording-gap.md +6 -0
  51. data/docs/architecture/tracing.md +1 -1
  52. data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
  53. data/docs/architecture.md +24 -11
  54. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  55. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  56. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
  57. data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
  58. data/docs/decisions/032-storage-backend-composition.md +5 -0
  59. data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
  60. data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
  61. data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
  62. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
  63. data/docs/decisions/040-configuration-default-composition.md +12 -1
  64. data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
  65. data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
  66. data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
  67. data/docs/decisions/058-neutral-storage-primitives.md +5 -0
  68. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  69. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  70. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  71. data/docs/decisions/062-content-store-service.md +52 -0
  72. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  73. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  74. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  75. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  76. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  77. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  78. data/docs/decisions/README.md +18 -1
  79. data/docs/execution-boundaries-preview.md +45 -0
  80. data/docs/failure-reason-codes.md +45 -0
  81. data/docs/features.md +12 -4
  82. data/docs/getting-started.md +52 -48
  83. data/docs/migrations/0.22.md +2 -2
  84. data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
  85. data/docs/migrations/handoff-runner-multi-agent.md +2 -0
  86. data/docs/migrations/persistence-failure-contracts.md +67 -0
  87. data/docs/migrations/r8-unit10.md +36 -0
  88. data/docs/migrations/r8-unit11.md +33 -0
  89. data/docs/migrations/r8-unit12.md +31 -0
  90. data/docs/migrations/r8-unit13.md +36 -0
  91. data/docs/migrations/r8-unit14.md +31 -0
  92. data/docs/migrations/r8-unit15.md +31 -0
  93. data/docs/migrations/r8-unit16.md +26 -0
  94. data/docs/migrations/r8-unit17.md +28 -0
  95. data/docs/migrations/r8-unit18.md +29 -0
  96. data/docs/migrations/r8-unit19.md +28 -0
  97. data/docs/migrations/r8-unit20.md +24 -0
  98. data/docs/migrations/r8-unit21.md +28 -0
  99. data/docs/migrations/r8-unit22.md +29 -0
  100. data/docs/migrations/r8-unit23.md +20 -0
  101. data/docs/migrations/r8-unit4.md +67 -0
  102. data/docs/migrations/r8-unit5.md +75 -0
  103. data/docs/migrations/r8-unit6.md +109 -0
  104. data/docs/migrations/r8-unit7.md +31 -0
  105. data/docs/migrations/r8-unit8.md +33 -0
  106. data/docs/migrations/r8-unit9.md +28 -0
  107. data/docs/migrations/storage-async-client.md +103 -0
  108. data/docs/migrations/vector-async-clients.md +110 -0
  109. data/docs/ordered-event-delivery.md +54 -0
  110. data/docs/persistence-backends.md +38 -21
  111. data/docs/runtime-and-concurrency.md +26 -1
  112. data/docs/workflow-completion.md +92 -0
  113. data/gemfiles/http_minimum.gemfile +5 -0
  114. data/lib/phronomy/agent/admission.rb +78 -0
  115. data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
  116. data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
  117. data/lib/phronomy/agent/agent_not_found_error.rb +16 -0
  118. data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
  119. data/lib/phronomy/agent/agent_unloaded_error.rb +14 -0
  120. data/lib/phronomy/agent/api/agent.rb +13 -0
  121. data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
  122. data/lib/phronomy/agent/async_event_api.rb +132 -103
  123. data/lib/phronomy/agent/base.rb +146 -113
  124. data/lib/phronomy/agent/before_llm_input.rb +70 -0
  125. data/lib/phronomy/agent/composition/run_once.rb +1 -1
  126. data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
  127. data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
  128. data/lib/phronomy/agent/control_request.rb +21 -0
  129. data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
  130. data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
  131. data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
  132. data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
  133. data/lib/phronomy/agent/execution/exact_execution.rb +55 -32
  134. data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
  135. data/lib/phronomy/agent/execution/execution_coordinator.rb +229 -407
  136. data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
  137. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +71 -127
  138. data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
  139. data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
  140. data/lib/phronomy/agent/execution/initial_preparation.rb +19 -120
  141. data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
  142. data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
  143. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +8 -11
  144. data/lib/phronomy/agent/execution_change.rb +161 -0
  145. data/lib/phronomy/agent/execution_environment.rb +36 -0
  146. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  147. data/lib/phronomy/agent/execution_observation.rb +35 -0
  148. data/lib/phronomy/agent/filterable.rb +188 -0
  149. data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
  150. data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
  151. data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
  152. data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
  153. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  154. data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
  155. data/lib/phronomy/agent/lifecycle/lifetime_api.rb +76 -0
  156. data/lib/phronomy/agent/lifecycle/operation_lease.rb +33 -0
  157. data/lib/phronomy/agent/lifecycle/operation_result.rb +53 -0
  158. data/lib/phronomy/agent/lifecycle/ownership_registry.rb +117 -31
  159. data/lib/phronomy/agent/lifecycle/scope.rb +198 -0
  160. data/lib/phronomy/agent/lifecycle/scope_cleanup_error.rb +19 -0
  161. data/lib/phronomy/agent/lifecycle/unload_transition.rb +78 -0
  162. data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
  163. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  164. data/lib/phronomy/agent/persistence/codec.rb +1 -21
  165. data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
  166. data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
  167. data/lib/phronomy/agent/persistence/records.rb +33 -0
  168. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  169. data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
  170. data/lib/phronomy/agent/persistence/watermark.rb +13 -11
  171. data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
  172. data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +13 -12
  173. data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +34 -34
  174. data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +33 -39
  175. data/lib/phronomy/agent/recovery/recovery_coordinator.rb +28 -25
  176. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  177. data/lib/phronomy/agent/recovery/recovery_support.rb +10 -19
  178. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  179. data/lib/phronomy/agent/retention.rb +17 -0
  180. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  181. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  182. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  183. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  184. data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
  185. data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
  186. data/lib/phronomy/agent/settings.rb +47 -0
  187. data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
  188. data/lib/phronomy/agent/store.rb +253 -0
  189. data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +3 -3
  190. data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
  191. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
  192. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  193. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  194. data/lib/phronomy/common/error.rb +65 -1
  195. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  196. data/lib/phronomy/content_store/base.rb +7 -2
  197. data/lib/phronomy/content_store/storage_schema.rb +1 -0
  198. data/lib/phronomy/content_store/stored_contents.rb +2 -1
  199. data/lib/phronomy/context/assembly.rb +195 -0
  200. data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
  201. data/lib/phronomy/context/candidate.rb +51 -0
  202. data/lib/phronomy/context/constraint.rb +47 -0
  203. data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
  204. data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
  205. data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
  206. data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
  207. data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
  208. data/lib/phronomy/context/default_policy.rb +140 -0
  209. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  210. data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
  211. data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
  212. data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
  213. data/lib/phronomy/context/manifest_error.rb +10 -0
  214. data/lib/phronomy/context/prompt_template.rb +98 -0
  215. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  216. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  217. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  218. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  219. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  220. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  221. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  222. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  223. data/lib/phronomy/embeddings/authentication_error.rb +11 -0
  224. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +52 -0
  225. data/lib/phronomy/embeddings/base.rb +43 -0
  226. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  227. data/lib/phronomy/embeddings/rate_limit_error.rb +11 -0
  228. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  229. data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
  230. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  231. data/lib/phronomy/engine/diagnostics.rb +1 -1
  232. data/lib/phronomy/engine/event_loop.rb +7 -7
  233. data/lib/phronomy/engine/execution_binding.rb +23 -0
  234. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  235. data/lib/phronomy/engine/fsm_protocol.rb +1 -6
  236. data/lib/phronomy/engine/fsm_session.rb +3 -3
  237. data/lib/phronomy/engine/metrics.rb +16 -14
  238. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  239. data/lib/phronomy/engine/runtime.rb +25 -12
  240. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  241. data/lib/phronomy/execution/async_operation.rb +41 -0
  242. data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
  243. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
  244. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  245. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  246. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  247. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  248. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  249. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  250. data/lib/phronomy/{engine → execution}/execution.rb +61 -27
  251. data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +2 -2
  252. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  253. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  254. data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +2 -2
  255. data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
  256. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  257. data/lib/phronomy/execution/runnable.rb +24 -0
  258. data/lib/phronomy/{engine → execution}/task_result.rb +9 -4
  259. data/lib/phronomy/execution/wait_policy.rb +24 -0
  260. data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +3 -3
  261. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  262. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  263. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
  264. data/lib/phronomy/generation/generator_verifier.rb +7 -7
  265. data/lib/phronomy/generation/low_confidence_error.rb +2 -2
  266. data/lib/phronomy/integrations/ordered_event_delivery.rb +208 -0
  267. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  268. data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
  269. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  270. data/lib/phronomy/llm_adapter/base.rb +49 -68
  271. data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
  272. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  273. data/lib/phronomy/llm_adapter/message.rb +62 -0
  274. data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
  275. data/lib/phronomy/llm_adapter/request.rb +79 -0
  276. data/lib/phronomy/llm_adapter/response.rb +36 -0
  277. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  278. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  279. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  280. data/lib/phronomy/llm_adapter.rb +3 -16
  281. data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
  282. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  283. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  284. data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
  285. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
  286. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  287. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
  288. data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
  289. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
  290. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  291. data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
  292. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  293. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  294. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  295. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +60 -44
  296. data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
  297. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  298. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  299. data/lib/phronomy/multi_agent/shared_state.rb +2 -2
  300. data/lib/phronomy/multi_agent/store.rb +156 -0
  301. data/lib/phronomy/multi_agent/team_busy_error.rb +18 -0
  302. data/lib/phronomy/multi_agent/team_coordinator.rb +99 -77
  303. data/lib/phronomy/multi_agent/team_not_found_error.rb +18 -0
  304. data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
  305. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  306. data/lib/phronomy/persistence/content_repository.rb +26 -0
  307. data/lib/phronomy/persistence/errors.rb +16 -0
  308. data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
  309. data/lib/phronomy/persistence/persistence.rb +23 -0
  310. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  311. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  312. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  313. data/lib/phronomy/persistence/transaction.rb +86 -0
  314. data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
  315. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  316. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  317. data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
  318. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  319. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  320. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  321. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  322. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  323. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  324. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  325. data/lib/phronomy/storage/async/async_client.rb +60 -0
  326. data/lib/phronomy/storage/backend.rb +8 -2
  327. data/lib/phronomy/storage/condition_failed_error.rb +2 -2
  328. data/lib/phronomy/storage/unique_constraint_error.rb +2 -2
  329. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  330. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  331. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
  332. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
  333. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
  334. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
  335. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
  336. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
  337. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
  338. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
  339. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
  340. data/lib/phronomy/tool/authorization.rb +77 -0
  341. data/lib/phronomy/tool/base.rb +551 -9
  342. data/lib/phronomy/tool/call_request.rb +40 -0
  343. data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
  344. data/lib/phronomy/tool/operation.rb +88 -0
  345. data/lib/phronomy/tool/schema.rb +214 -0
  346. data/lib/phronomy/tool/settings.rb +31 -0
  347. data/lib/phronomy/tool/tool_error.rb +7 -0
  348. data/lib/phronomy/tool/tool_executor.rb +58 -0
  349. data/lib/phronomy/tools/agent.rb +22 -90
  350. data/lib/phronomy/tools/mcp.rb +21 -14
  351. data/lib/phronomy/tools/vector_search.rb +4 -4
  352. data/lib/phronomy/tracing/automatic.rb +11 -6
  353. data/lib/phronomy/tracing/base.rb +36 -7
  354. data/lib/phronomy/tracing/observation.rb +37 -0
  355. data/lib/phronomy/tracing/open_telemetry_tracer.rb +4 -29
  356. data/lib/phronomy/tracing/settings.rb +36 -0
  357. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  358. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  359. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  360. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  361. data/lib/phronomy/vector_store/base.rb +118 -49
  362. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  363. data/lib/phronomy/vector_store.rb +2 -17
  364. data/lib/phronomy/version.rb +1 -1
  365. data/lib/phronomy/workflow/completion.rb +11 -0
  366. data/lib/phronomy/workflow/execution/workflow.rb +37 -9
  367. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  368. data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
  369. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +28 -0
  370. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +77 -8
  371. data/lib/phronomy/workflow/execution/workflow_runner.rb +105 -92
  372. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  373. data/lib/phronomy/workflow/execution/workflow_task_execution.rb +153 -0
  374. data/lib/phronomy/workflow/execution/workflow_task_reception.rb +72 -0
  375. data/lib/phronomy/workflow/execution/workflow_task_resume_error.rb +14 -0
  376. data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
  377. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  378. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +61 -0
  379. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +158 -0
  380. data/lib/phronomy/workflow/runtime_binding/workflow_task_session.rb +76 -0
  381. data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
  382. data/lib/phronomy.rb +63 -16
  383. data/scripts/api_snapshot.rb +47 -15
  384. data/scripts/check_readme_ruby.rb +24 -29
  385. data/scripts/check_readme_runnable.rb +92 -81
  386. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  387. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  388. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  389. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  390. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  391. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  392. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  393. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  394. data/sig/phronomy/agent/admission.rbs +19 -0
  395. data/sig/phronomy/agent/agent_not_found_error.rbs +6 -0
  396. data/sig/phronomy/agent/agent_unloaded_error.rbs +6 -0
  397. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  398. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  399. data/sig/phronomy/agent/store.rbs +73 -0
  400. data/sig/phronomy/agent/stream_callback_error.rbs +8 -0
  401. data/sig/phronomy/agent.rbs +29 -135
  402. data/sig/phronomy/common.rbs +7 -0
  403. data/sig/phronomy/content_store/contracts.rbs +13 -0
  404. data/sig/phronomy/context.rbs +152 -0
  405. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  406. data/sig/phronomy/embeddings/base.rbs +23 -0
  407. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  408. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  409. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  410. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  411. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  412. data/sig/phronomy/engine/observability.rbs +13 -0
  413. data/sig/phronomy/execution/async_operation.rbs +7 -0
  414. data/sig/phronomy/execution/blocking.rbs +5 -0
  415. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  416. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  417. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  418. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  419. data/sig/phronomy/execution/errors.rbs +7 -0
  420. data/sig/phronomy/execution/execution.rbs +41 -0
  421. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  422. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  423. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  424. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  425. data/sig/phronomy/execution/task_result.rbs +29 -0
  426. data/sig/phronomy/extensions.rbs +1 -21
  427. data/sig/phronomy/filter/filter_block_error.rbs +6 -0
  428. data/sig/phronomy/generation/low_confidence_error.rbs +6 -0
  429. data/sig/phronomy/handoff.rbs +10 -4
  430. data/sig/phronomy/integrations.rbs +14 -0
  431. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  432. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  433. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  434. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  435. data/sig/phronomy/llm_context_window.rbs +10 -0
  436. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  437. data/sig/phronomy/multi_agent/store.rbs +54 -0
  438. data/sig/phronomy/multi_agent/team_busy_error.rbs +8 -0
  439. data/sig/phronomy/multi_agent/team_not_found_error.rbs +8 -0
  440. data/sig/phronomy/multi_agent.rbs +6 -3
  441. data/sig/phronomy/persistence/errors.rbs +18 -0
  442. data/sig/phronomy/persistence/migration.rbs +13 -0
  443. data/sig/phronomy/persistence/transaction.rbs +19 -0
  444. data/sig/phronomy/persistence.rbs +1 -99
  445. data/sig/phronomy/persistence_composition.rbs +19 -0
  446. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  447. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  448. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  449. data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +4 -18
  450. data/sig/phronomy/tool.rbs +48 -4
  451. data/sig/phronomy/tracing/observation.rbs +7 -0
  452. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  453. data/sig/phronomy/vector_store/base.rbs +23 -0
  454. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +15 -0
  455. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  456. data/sig/phronomy/workflow/store.rbs +8 -0
  457. data/sig/phronomy/workflow.rbs +5 -1
  458. data/sig/phronomy.rbs +4 -49
  459. metadata +365 -117
  460. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  461. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  462. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  463. data/lib/phronomy/agent/context/capability/base.rb +0 -548
  464. data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
  465. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  466. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
  467. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
  468. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  469. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  470. data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
  471. data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
  472. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
  473. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
  474. data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
  475. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
  476. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
  477. data/lib/phronomy/agent/persistence/queries.rb +0 -61
  478. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  479. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  480. data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
  481. data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
  482. data/lib/phronomy/configuration/configuration.rb +0 -68
  483. data/lib/phronomy/configuration/global_configuration.rb +0 -26
  484. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  485. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  486. data/lib/phronomy/engine/execution_receiver.rb +0 -65
  487. data/lib/phronomy/engine/runnable.rb +0 -54
  488. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  489. data/lib/phronomy/llm_contract/token_usage.rb +0 -85
  490. data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
  491. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
  492. data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
  493. data/lib/phronomy/persistence/api/persistence.rb +0 -140
  494. data/lib/phronomy/persistence_composition/repositories.rb +0 -77
  495. data/lib/phronomy/recovery/recovery.rb +0 -186
  496. data/lib/phronomy/tool/contract/tool_error.rb +0 -7
  497. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  498. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  499. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  500. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
  501. data/sig/phronomy/execution_receiver.rbs +0 -34
  502. data/sig/phronomy/llm_adapter.rbs +0 -11
  503. data/sig/phronomy/runtime.rbs +0 -76
  504. /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
  505. /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
  506. /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
  507. /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
  508. /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
  509. /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
  510. /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
  511. /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
  512. /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
  513. /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
  514. /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
  515. /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
  516. /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
  517. /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
@@ -1,548 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../../../engine/concurrency/worker_input_restricted"
4
-
5
- module Phronomy
6
- module Agent
7
- module Context
8
- module Capability
9
- # Base class extending RubyLLM::Tool with Phronomy-specific DSL.
10
- class Base < RubyLLM::Tool
11
- include Phronomy::Concurrency::WorkerInputRestricted
12
-
13
- class << self
14
- # @api public
15
- def tool_name(value = nil)
16
- return @tool_name || super() if value.nil?
17
-
18
- @tool_name = value.to_s
19
- end
20
-
21
- # RubyLLM stores Tool descriptions in a class-instance variable.
22
- # Preserve normal class inheritance semantics so Phronomy's anonymous
23
- # decorator subclasses do not lose their parent's description.
24
- # @api public
25
- def description(text = nil)
26
- unless text
27
- return @description if instance_variable_defined?(:@description)
28
- return superclass.description if superclass.respond_to?(:description)
29
-
30
- return nil
31
- end
32
-
33
- @description = text
34
- end
35
- alias_method :desc, :description
36
-
37
- # Phronomy declarations inherit through anonymous Tool decorators.
38
- # RubyLLM owns schema construction and provider request rendering.
39
- # @api public
40
- def declared_parameters
41
- return @declared_parameters if instance_variable_defined?(:@declared_parameters)
42
-
43
- @declared_parameters = superclass.declared_parameters.dup
44
- end
45
-
46
- # Keep Phronomy's parameter reader and support RubyLLM's schema DSL.
47
- # @api public
48
- def parameters(schema = :__phronomy_read__, &block)
49
- return declared_parameters if schema == :__phronomy_read__ && !block
50
-
51
- super((schema == :__phronomy_read__) ? nil : schema, &block)
52
- end
53
-
54
- # @api public
55
- def parameters_schema_definition
56
- return @parameters_schema_definition if instance_variable_defined?(:@parameters_schema_definition)
57
-
58
- superclass.parameters_schema_definition
59
- end
60
-
61
- # @api public
62
- def params_schema_definition
63
- parameters_schema_definition
64
- end
65
-
66
- # @api public
67
- def params(schema = nil, &block)
68
- parameters(schema, &block)
69
- end
70
-
71
- # @api public
72
- def provider_options(options = :__phronomy_read__)
73
- return super unless options == :__phronomy_read__
74
- return @provider_options if instance_variable_defined?(:@provider_options)
75
-
76
- @provider_options = duplicate_configuration(superclass.provider_options)
77
- end
78
-
79
- # @api public
80
- def provider_params
81
- provider_options
82
- end
83
-
84
- # @api public
85
- def with_params(**options)
86
- provider_options(options)
87
- end
88
-
89
- # @api public
90
- def param(name, enum: nil, properties: nil, desc: nil, **options)
91
- options[:description] = desc if desc
92
- parameter(name, **options)
93
- param_enums[name] = duplicate_configuration(enum) if enum
94
- param_schemas[name] = normalize_nested_schema(properties) if properties
95
- end
96
-
97
- # @api public
98
- def param_enums
99
- return @param_enums if instance_variable_defined?(:@param_enums)
100
-
101
- parent = superclass.respond_to?(:param_enums) ? superclass.param_enums : {}
102
- @param_enums = duplicate_configuration(parent)
103
- end
104
-
105
- # @api public
106
- def param_schemas
107
- return @param_schemas if instance_variable_defined?(:@param_schemas)
108
-
109
- parent = superclass.respond_to?(:param_schemas) ? superclass.param_schemas : {}
110
- @param_schemas = duplicate_configuration(parent)
111
- end
112
-
113
- private
114
-
115
- def duplicate_configuration(value)
116
- case value
117
- when Hash
118
- value.to_h do |key, child|
119
- [key, duplicate_configuration(child)]
120
- end
121
- when Array
122
- value.map { |child| duplicate_configuration(child) }
123
- else
124
- value
125
- end
126
- end
127
-
128
- def normalize_nested_schema(props)
129
- props.transform_keys(&:to_sym).transform_values do |spec|
130
- normalized = spec.transform_keys(&:to_sym)
131
- normalized[:type] ||= :string
132
- if normalized[:properties]
133
- normalized[:properties] = normalize_nested_schema(normalized[:properties])
134
- end
135
- normalized
136
- end
137
- end
138
-
139
- public
140
-
141
- # Declares whether Tool work is safe to run cooperatively on the EventLoop
142
- # or must be offloaded to a bounded worker pool.
143
- #
144
- # Phronomy does not classify the reason for offloading. Blocking I/O,
145
- # CPU-bound synchronous work, and other long synchronous calls all use
146
- # +:offloaded+. The application owns that workload classification.
147
- # @api public
148
- def execution_mode(value = nil)
149
- if value.nil?
150
- return @execution_mode if instance_variable_defined?(:@execution_mode)
151
- return superclass.execution_mode if superclass.respond_to?(:execution_mode)
152
-
153
- return :offloaded
154
- end
155
-
156
- valid = %i[cooperative offloaded]
157
- unless valid.include?(value)
158
- raise ArgumentError,
159
- "execution_mode must be one of #{valid.inspect}, got #{value.inspect}"
160
- end
161
- @execution_mode = value
162
- end
163
-
164
- # Configures execution-error handling. Supported values are :raise
165
- # and :suppress only.
166
- # @api public
167
- def on_error(behavior = nil)
168
- if behavior.nil?
169
- return @on_error if instance_variable_defined?(:@on_error)
170
- return superclass.on_error if superclass.respond_to?(:on_error)
171
-
172
- return :raise
173
- end
174
-
175
- valid = %i[raise suppress]
176
- unless valid.include?(behavior)
177
- raise ArgumentError,
178
- "on_error must be one of #{valid.inspect}, got #{behavior.inspect}"
179
- end
180
- @on_error = behavior
181
- end
182
-
183
- # @api public
184
- def on_schema_error(behavior = nil)
185
- if behavior.nil?
186
- return @on_schema_error if instance_variable_defined?(:@on_schema_error)
187
- return superclass.on_schema_error if superclass.respond_to?(:on_schema_error)
188
-
189
- return :return_error
190
- end
191
-
192
- @on_schema_error = behavior
193
- end
194
-
195
- # @api public
196
- def requires_approval(value = :__unset__, &block)
197
- if block
198
- unless value == :__unset__
199
- raise ArgumentError, "pass either a value or a block to requires_approval"
200
- end
201
- @requires_approval = block
202
- elsif value == :__unset__
203
- return @requires_approval unless @requires_approval.nil?
204
- return superclass.requires_approval if superclass < RubyLLM::Tool
205
-
206
- false
207
- else
208
- unless value == true || value == false || value.respond_to?(:call)
209
- raise ArgumentError, "requires_approval must be true, false, or callable"
210
- end
211
- @requires_approval = value
212
- end
213
- end
214
-
215
- # @api public
216
- def approval_facts(&block)
217
- if block
218
- @approval_facts = block
219
- elsif instance_variable_defined?(:@approval_facts)
220
- @approval_facts
221
- elsif superclass.respond_to?(:approval_facts)
222
- superclass.approval_facts
223
- end
224
- end
225
-
226
- # @api public
227
- def redact_params(*names)
228
- if names.empty?
229
- parent = superclass.respond_to?(:redact_params) ? superclass.redact_params : []
230
- ((@redacted_params || []) + parent).uniq
231
- else
232
- @redacted_params = ((@redacted_params || []) + names.map(&:to_sym)).uniq
233
- end
234
- end
235
-
236
- # @api public
237
- def max_result_size(value = :__unset__)
238
- if value == :__unset__
239
- return @max_result_size if instance_variable_defined?(:@max_result_size)
240
- return superclass.max_result_size if superclass.respond_to?(:max_result_size)
241
-
242
- return nil
243
- end
244
-
245
- @max_result_size = value
246
- end
247
- end
248
-
249
- def name
250
- self.class.tool_name || super
251
- end
252
-
253
- def parameters_schema
254
- schema = super
255
- return schema if schema.nil?
256
-
257
- properties = schema.dig("properties") || schema.dig(:properties)
258
- return schema unless properties
259
-
260
- self.class.param_enums.each do |param_name, values|
261
- key = properties.key?(param_name.to_s) ? param_name.to_s : param_name.to_sym
262
- next unless properties[key]
263
-
264
- param_type = properties[key]["type"]
265
- properties[key]["enum"] = values.map do |value|
266
- case param_type
267
- when "integer"
268
- value.is_a?(Integer) ? value : Integer(value.to_s)
269
- when "number"
270
- value.is_a?(Numeric) ? value : Float(value.to_s)
271
- when "boolean"
272
- unless value == true || value == false
273
- raise ArgumentError,
274
- "boolean enum values must be true or false (got: #{value.inspect})"
275
- end
276
- value
277
- else
278
- value.to_s
279
- end
280
- end
281
- end
282
-
283
- self.class.param_schemas.each do |param_name, nested|
284
- key = properties.key?(param_name.to_s) ? param_name.to_s : param_name.to_sym
285
- next unless properties[key]
286
- properties[key]["properties"] = nested_schema_to_json_schema(nested)
287
- end
288
-
289
- schema
290
- end
291
-
292
- # Phronomy retains these readers as part of its Tool contract.
293
- # @api public
294
- def params_schema
295
- parameters_schema
296
- end
297
-
298
- # @api public
299
- def provider_params
300
- provider_options
301
- end
302
-
303
- # @api public
304
- def parameters
305
- self.class.declared_parameters
306
- end
307
-
308
- # @api public
309
- def call(args, cancellation_token: nil)
310
- cancellation_token&.raise_if_cancelled!
311
- validated_args, schema_error = validate_and_coerce(args)
312
- if schema_error
313
- case self.class.on_schema_error
314
- when :raise
315
- raise Phronomy::ToolError,
316
- "#{self.class.name} schema error: #{schema_error}"
317
- else
318
- return "Schema validation failed: #{schema_error}"
319
- end
320
- end
321
-
322
- if cancellation_token && execute_accepts_cancellation_token?
323
- validated_args = validated_args.merge(cancellation_token: cancellation_token)
324
- end
325
- result = super(**(validated_args || {}).transform_keys(&:to_sym))
326
- truncate_result_if_needed(result)
327
- rescue Phronomy::ToolError, Phronomy::CancellationError
328
- raise
329
- rescue => error
330
- if self.class.on_error == :suppress
331
- msg = "[Phronomy] Tool #{self.class.name} suppressed error: " \
332
- "#{error.class}: #{error.message}"
333
- if Phronomy.configuration.logger
334
- Phronomy.configuration.logger.warn(msg)
335
- else
336
- warn msg
337
- end
338
- "Tool error suppressed: #{error.message}"
339
- else
340
- raise Phronomy::ToolError,
341
- "#{self.class.name} execution failed: #{error.message}"
342
- end
343
- end
344
-
345
- # @api public
346
- def call_async(
347
- args,
348
- cancellation_token: nil,
349
- config: {}
350
- )
351
- Phronomy::Agent::Context::Capability::ToolExecutor.call_async(
352
- tool: self,
353
- args: args,
354
- cancellation_token: cancellation_token,
355
- config: config
356
- )
357
- end
358
-
359
- def requires_approval
360
- self.class.requires_approval
361
- end
362
-
363
- def requires_approval?
364
- self.class.requires_approval
365
- end
366
-
367
- # @api public
368
- def tool_origin
369
- :local
370
- end
371
-
372
- # @api public
373
- def approval_metadata
374
- {}
375
- end
376
-
377
- # @api public
378
- def execute(**_args)
379
- raise NotImplementedError, "#{self.class}#execute is not implemented"
380
- end
381
-
382
- private
383
-
384
- def execute_accepts_cancellation_token?
385
- method(:execute).parameters.any? do |type, name|
386
- name == :cancellation_token && %i[key keyreq].include?(type)
387
- end
388
- end
389
-
390
- def truncate_result_if_needed(result)
391
- max = self.class.max_result_size || Phronomy.configuration.tool_result_max_size
392
- return result unless max && result.respond_to?(:length) && result.length > max
393
-
394
- msg = "[Phronomy] Tool #{self.class.name} result truncated " \
395
- "(#{result.length} chars > #{max} limit)"
396
- if Phronomy.configuration.logger
397
- Phronomy.configuration.logger.warn(msg)
398
- else
399
- warn msg
400
- end
401
- "#{result[0, max]}...[truncated]"
402
- end
403
-
404
- def redacted_args(args)
405
- redacted = self.class.redact_params
406
- return args if redacted.empty?
407
-
408
- args.each_with_object({}) do |(key, value), result|
409
- result[key] = redacted.include?(key.to_sym) ? "[REDACTED]" : value
410
- end
411
- end
412
-
413
- def validate_and_coerce(args)
414
- return [args, nil] if self.class.parameters.empty?
415
-
416
- normalized = (args || {}).transform_keys(&:to_sym)
417
- coerce_mode = self.class.on_schema_error == :coerce
418
- result = {}
419
-
420
- self.class.parameters.each do |name, param|
421
- value = normalized[name]
422
- if value.nil?
423
- return [nil, "required parameter '#{name}' is missing"] if param.required
424
- next
425
- end
426
-
427
- if coerce_mode
428
- coerced, error = coerce_value(value, param.type)
429
- return [nil, error] if error
430
- value = coerced
431
- else
432
- error = type_error(value, param.type)
433
- return [nil, error] if error
434
- end
435
-
436
- if param.type.to_sym == :object
437
- nested_schema = self.class.param_schemas[name]
438
- if nested_schema
439
- error = validate_nested_object(value, nested_schema, name.to_s)
440
- return [nil, error] if error
441
- end
442
- end
443
-
444
- enum_vals = self.class.param_enums[name]
445
- if enum_vals && !enum_vals.map(&:to_s).include?(value.to_s)
446
- return [nil,
447
- "parameter '#{name}' must be one of: #{enum_vals.join(", ")} " \
448
- "(got: #{value.inspect})"]
449
- end
450
-
451
- result[name] = value
452
- end
453
-
454
- extra = normalized.keys - self.class.parameters.keys
455
- unless extra.empty?
456
- return [nil, "unknown parameter(s): #{extra.inspect}"]
457
- end
458
-
459
- [result, nil]
460
- end
461
-
462
- def nested_schema_to_json_schema(nested)
463
- nested.each_with_object({}) do |(prop_name, spec), result|
464
- entry = {"type" => spec[:type].to_s}
465
- entry["description"] = spec[:desc] if spec[:desc]
466
- entry["enum"] = spec[:enum] if spec[:enum]
467
- if spec[:properties]
468
- entry["properties"] = nested_schema_to_json_schema(spec[:properties])
469
- end
470
- result[prop_name.to_s] = entry
471
- end
472
- end
473
-
474
- def validate_nested_object(value, properties, path)
475
- return "field '#{path}' must be an object (Hash)" unless value.is_a?(Hash)
476
-
477
- normalized = value.transform_keys(&:to_sym)
478
- extra = normalized.keys - properties.keys
479
- unless extra.empty?
480
- return "nested field '#{path}' contains undeclared key(s): #{extra.inspect}"
481
- end
482
-
483
- properties.each do |name, spec|
484
- field_path = "#{path}.#{name}"
485
- field_value = normalized[name]
486
-
487
- if field_value.nil?
488
- return "nested required field '#{field_path}' is missing" if spec[:required]
489
- next
490
- end
491
-
492
- error = type_error(field_value, spec[:type])
493
- return "nested field '#{field_path}': #{error}" if error
494
-
495
- next unless spec[:type].to_sym == :object && spec[:properties]
496
-
497
- error = validate_nested_object(field_value, spec[:properties], field_path)
498
- return error if error
499
- end
500
- nil
501
- end
502
-
503
- def type_error(value, declared_type)
504
- return nil if value.nil?
505
-
506
- ok = case declared_type.to_sym
507
- when :string then value.is_a?(String)
508
- when :integer then value.is_a?(Integer)
509
- when :number, :float then value.is_a?(Numeric)
510
- when :boolean then [true, false].include?(value)
511
- when :array then value.is_a?(Array)
512
- when :object then value.is_a?(Hash)
513
- else true
514
- end
515
-
516
- return nil if ok
517
-
518
- shown = value.respond_to?(:keys) ? "(object)" : value.inspect
519
- "parameter '#{shown}' expected type #{declared_type}"
520
- end
521
-
522
- def coerce_value(value, declared_type)
523
- return [value, nil] if value.nil?
524
-
525
- case declared_type.to_sym
526
- when :string
527
- [value.to_s, nil]
528
- when :integer
529
- [Integer(value), nil]
530
- when :number, :float
531
- [Float(value), nil]
532
- when :boolean
533
- case value.to_s.downcase
534
- when "true" then [true, nil]
535
- when "false" then [false, nil]
536
- else [nil, "parameter cannot be coerced to boolean: #{value.inspect}"]
537
- end
538
- else
539
- [value, nil]
540
- end
541
- rescue ArgumentError, TypeError
542
- [nil, "parameter cannot be coerced to #{declared_type}: #{value.inspect}"]
543
- end
544
- end
545
- end
546
- end
547
- end
548
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module Context
6
- module Capability
7
- # Routes Tool work according to the Tool execution contract.
8
- #
9
- # Both execution modes return {Phronomy::TaskResult}; only the execution mechanism
10
- # differs.
11
- #
12
- # :cooperative Tool calls execute inline and must return quickly. call_async
13
- # wraps their result in an already-settled TaskResult and never consumes an
14
- # OffloadPool worker.
15
- #
16
- # :offloaded Tool calls route synchronous work through OffloadPool, whose
17
- # caller-facing completion handle is also a TaskResult. Phronomy does not distinguish
18
- # whether the reason is blocking I/O, CPU-bound work, or another long
19
- # synchronous operation.
20
- #
21
- # @api private
22
- module ToolExecutor
23
- # @api private
24
- def self.call_async(
25
- tool:,
26
- args:,
27
- cancellation_token: nil,
28
- config: {},
29
- runtime: nil,
30
- on_full: :raise
31
- )
32
- mode = tool.class.execution_mode
33
-
34
- case mode
35
- when :cooperative
36
- task = Phronomy::TaskResult.deferred(name: "tool-#{tool.name}")
37
- begin
38
- task.complete(
39
- tool.call(args, cancellation_token: cancellation_token)
40
- )
41
- rescue => error
42
- task.fail(error)
43
- end
44
- task
45
- when :offloaded
46
- runtime ||= Phronomy::Runtime.instance
47
- runtime.offload.submit(
48
- cancellation_token: cancellation_token,
49
- on_full: on_full
50
- ) do
51
- tool.call(args, cancellation_token: cancellation_token)
52
- end
53
- else
54
- raise Phronomy::ConfigurationError,
55
- "unknown Tool execution_mode: #{mode.inspect}"
56
- end
57
- end
58
- end
59
- end
60
- end
61
- end
62
- end
@@ -1,102 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module Context
6
- module Instruction
7
- # A prompt template that substitutes {{variable}} placeholders in a string.
8
- #
9
- # @example Simple human template
10
- # t = Phronomy::Agent::Context::Instruction::PromptTemplate.new(template: "Translate to {{lang}}: {{text}}")
11
- # t.format(lang: "French", text: "Hello")
12
- # # => "Translate to French: Hello"
13
- #
14
- # @example With a system template
15
- # t = Phronomy::Agent::Context::Instruction::PromptTemplate.new(
16
- # template: "{{question}}",
17
- # system_template: "You are a {{role}} assistant."
18
- # )
19
- # t.format_system(role: "helpful")
20
- # # => "You are a helpful assistant."
21
- #
22
- # As a Runnable, #invoke accepts a Hash of variables and returns a Hash
23
- # with :prompt (and optionally :system) keys.
24
- class PromptTemplate
25
- include Phronomy::Runnable
26
-
27
- PLACEHOLDER = /\{\{(\w+)\}\}/
28
-
29
- attr_reader :template, :system_template
30
-
31
- # @param template [String] human message template with {{var}} placeholders
32
- # @param system_template [String, nil] optional system message template
33
- # @api public
34
- def initialize(template:, system_template: nil)
35
- @template = template
36
- @system_template = system_template
37
- end
38
-
39
- # Substitute all {{var}} placeholders in the human template.
40
- #
41
- # @param variables [Hash{Symbol => String}]
42
- # @return [String]
43
- # @api public
44
- def format(**variables)
45
- substitute(@template, variables)
46
- end
47
-
48
- # Substitute all {{var}} placeholders in the system template.
49
- # Returns nil when no system template was set.
50
- #
51
- # @param variables [Hash{Symbol => String}]
52
- # @return [String, nil]
53
- # @api public
54
- def format_system(**variables)
55
- @system_template && substitute(@system_template, variables)
56
- end
57
-
58
- # Runnable interface: accepts a Hash of variable values.
59
- # Returns { prompt: String, system: String|nil }.
60
- #
61
- # @param input [Hash{Symbol => String}]
62
- # @return [Hash]
63
- # @api public
64
- def invoke(input, config: {})
65
- vars = normalize_input(input)
66
- result = {prompt: format(**vars)}
67
- sys = format_system(**vars)
68
- result[:system] = sys if sys
69
- result
70
- end
71
-
72
- # Returns the list of placeholder names found in both templates.
73
- #
74
- # @return [Array<Symbol>]
75
- # @api public
76
- def variables
77
- names = @template.scan(PLACEHOLDER).flatten
78
- names += @system_template.scan(PLACEHOLDER).flatten if @system_template
79
- names.map(&:to_sym).uniq
80
- end
81
-
82
- private
83
-
84
- def substitute(text, variables)
85
- text.gsub(PLACEHOLDER) do |match|
86
- key = Regexp.last_match(1).to_sym
87
- variables.fetch(key) { raise KeyError, "Missing variable: {{#{key}}}" }
88
- end
89
- end
90
-
91
- def normalize_input(input)
92
- case input
93
- when Hash then input
94
- when String then {input: input}
95
- else raise ArgumentError, "PromptTemplate#invoke expects a Hash of variables, got #{input.class}"
96
- end
97
- end
98
- end
99
- end
100
- end
101
- end
102
- end