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,15 +1,557 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../execution/concurrency/worker_input_restricted"
4
+ require_relative "../execution/cancellation_error"
5
+ require_relative "../configuration/runtime_settings"
6
+ require_relative "settings"
7
+ require_relative "tool_error"
8
+ require_relative "schema"
9
+
3
10
  module Phronomy
4
11
  module Tool
5
- # Public authoring façade for Phronomy Tools.
6
- #
7
- # This constant intentionally refers to the exact same Class object as
8
- # {Phronomy::Agent::Context::Capability::Base}. The implementation namespace
9
- # remains canonical internally so the existing Tool DSL state, inheritance,
10
- # built-in Tools, and compatibility surface are not duplicated.
11
- #
12
- # @api public
13
- Base = Phronomy::Agent::Context::Capability::Base
12
+ # Owns Tool declarations, argument validation and the execution template.
13
+ class Base
14
+ include Phronomy::Concurrency::WorkerInputRestricted
15
+
16
+ Parameter = Data.define(:name, :type, :description, :required)
17
+
18
+ class << self
19
+ # @api public
20
+ def parameter(name, type: :string, description: nil, required: true)
21
+ declared_parameters[name.to_sym] = Parameter.new(name: name.to_sym,
22
+ type: type, description: description, required: required)
23
+ end
24
+
25
+ # @api public
26
+ def tool_name(value = nil)
27
+ if value.nil?
28
+ return @tool_name if instance_variable_defined?(:@tool_name)
29
+ ascii = name.to_s.unicode_normalize(:nfkd).encode("ASCII", replace: "").gsub(/[^a-zA-Z0-9_-]/, "-")
30
+ return ascii.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.delete_suffix("_tool")
31
+ end
32
+
33
+ @tool_name = value.to_s
34
+ end
35
+
36
+ # Tool descriptions inherit through application and decorator classes.
37
+ # Preserve normal class inheritance semantics so Phronomy's anonymous
38
+ # decorator subclasses do not lose their parent's description.
39
+ # @api public
40
+ def description(text = nil)
41
+ unless text
42
+ return @description if instance_variable_defined?(:@description)
43
+ return superclass.description if superclass.respond_to?(:description)
44
+
45
+ return nil
46
+ end
47
+
48
+ @description = text
49
+ end
50
+ alias_method :desc, :description
51
+
52
+ # Phronomy declarations inherit through anonymous Tool decorators.
53
+ # The Tool contract owns schema construction and validation.
54
+ # @api public
55
+ def declared_parameters
56
+ return @declared_parameters if instance_variable_defined?(:@declared_parameters)
57
+
58
+ @declared_parameters = superclass.respond_to?(:declared_parameters) ? superclass.declared_parameters.dup : {}
59
+ end
60
+
61
+ # Preserve the parameter reader and accept an explicit schema or schema DSL.
62
+ # @api public
63
+ def parameters(schema = :__phronomy_read__, &block)
64
+ return declared_parameters if schema == :__phronomy_read__ && !block
65
+
66
+ @parameters_schema_definition = Schema.new((schema == :__phronomy_read__) ? nil : schema, &block)
67
+ self
68
+ end
69
+
70
+ # @api public
71
+ def parameters_schema_definition
72
+ return @parameters_schema_definition if instance_variable_defined?(:@parameters_schema_definition)
73
+
74
+ superclass.parameters_schema_definition if superclass.respond_to?(:parameters_schema_definition)
75
+ end
76
+
77
+ # @api public
78
+ def params_schema_definition
79
+ parameters_schema_definition
80
+ end
81
+
82
+ # @api public
83
+ def params(schema = nil, &block)
84
+ parameters(schema, &block)
85
+ end
86
+
87
+ # @api public
88
+ def provider_options(options = :__phronomy_read__)
89
+ unless options == :__phronomy_read__
90
+ raise ArgumentError, "provider_options must be a Hash" unless options.is_a?(Hash)
91
+ @provider_options = duplicate_configuration(options)
92
+ return self
93
+ end
94
+ return @provider_options if instance_variable_defined?(:@provider_options)
95
+
96
+ @provider_options = superclass.respond_to?(:provider_options) ? duplicate_configuration(superclass.provider_options) : {}
97
+ end
98
+
99
+ # @api public
100
+ def provider_params
101
+ provider_options
102
+ end
103
+
104
+ # @api public
105
+ def with_params(**options)
106
+ provider_options(options)
107
+ end
108
+
109
+ # @api public
110
+ def param(name, enum: nil, properties: nil, desc: nil, **options)
111
+ options[:description] = desc if desc
112
+ parameter(name, **options)
113
+ param_enums[name] = duplicate_configuration(enum) if enum
114
+ param_schemas[name] = normalize_nested_schema(properties) if properties
115
+ end
116
+
117
+ # @api public
118
+ def param_enums
119
+ return @param_enums if instance_variable_defined?(:@param_enums)
120
+
121
+ parent = superclass.respond_to?(:param_enums) ? superclass.param_enums : {}
122
+ @param_enums = duplicate_configuration(parent)
123
+ end
124
+
125
+ # @api public
126
+ def param_schemas
127
+ return @param_schemas if instance_variable_defined?(:@param_schemas)
128
+
129
+ parent = superclass.respond_to?(:param_schemas) ? superclass.param_schemas : {}
130
+ @param_schemas = duplicate_configuration(parent)
131
+ end
132
+
133
+ private
134
+
135
+ def duplicate_configuration(value)
136
+ case value
137
+ when Hash
138
+ value.to_h do |key, child|
139
+ [key, duplicate_configuration(child)]
140
+ end
141
+ when Array
142
+ value.map { |child| duplicate_configuration(child) }
143
+ else
144
+ value
145
+ end
146
+ end
147
+
148
+ def normalize_nested_schema(props)
149
+ props.transform_keys(&:to_sym).transform_values do |spec|
150
+ normalized = spec.transform_keys(&:to_sym)
151
+ normalized[:type] ||= :string
152
+ if normalized[:properties]
153
+ normalized[:properties] = normalize_nested_schema(normalized[:properties])
154
+ end
155
+ normalized
156
+ end
157
+ end
158
+
159
+ public
160
+
161
+ # Declares whether Tool work is safe to run cooperatively on the EventLoop
162
+ # or must be offloaded to a bounded worker pool.
163
+ #
164
+ # Phronomy does not classify the reason for offloading. Blocking I/O,
165
+ # CPU-bound synchronous work, and other long synchronous calls all use
166
+ # +:offloaded+. The application owns that workload classification.
167
+ # @api public
168
+ def execution_mode(value = nil)
169
+ if value.nil?
170
+ return @execution_mode if instance_variable_defined?(:@execution_mode)
171
+ return superclass.execution_mode if superclass.respond_to?(:execution_mode)
172
+
173
+ return :offloaded
174
+ end
175
+
176
+ valid = %i[cooperative offloaded]
177
+ unless valid.include?(value)
178
+ raise ArgumentError,
179
+ "execution_mode must be one of #{valid.inspect}, got #{value.inspect}"
180
+ end
181
+ @execution_mode = value
182
+ end
183
+
184
+ # Configures execution-error handling. Supported values are :raise
185
+ # and :suppress only.
186
+ # @api public
187
+ def on_error(behavior = nil)
188
+ if behavior.nil?
189
+ return @on_error if instance_variable_defined?(:@on_error)
190
+ return superclass.on_error if superclass.respond_to?(:on_error)
191
+
192
+ return :raise
193
+ end
194
+
195
+ valid = %i[raise suppress]
196
+ unless valid.include?(behavior)
197
+ raise ArgumentError,
198
+ "on_error must be one of #{valid.inspect}, got #{behavior.inspect}"
199
+ end
200
+ @on_error = behavior
201
+ end
202
+
203
+ # @api public
204
+ def on_schema_error(behavior = nil)
205
+ if behavior.nil?
206
+ return @on_schema_error if instance_variable_defined?(:@on_schema_error)
207
+ return superclass.on_schema_error if superclass.respond_to?(:on_schema_error)
208
+
209
+ return :return_error
210
+ end
211
+
212
+ @on_schema_error = behavior
213
+ end
214
+
215
+ # @api public
216
+ def requires_approval(value = :__unset__, &block)
217
+ if block
218
+ unless value == :__unset__
219
+ raise ArgumentError, "pass either a value or a block to requires_approval"
220
+ end
221
+ @requires_approval = block
222
+ elsif value == :__unset__
223
+ return @requires_approval unless @requires_approval.nil?
224
+ return superclass.requires_approval if superclass.respond_to?(:requires_approval)
225
+
226
+ false
227
+ else
228
+ unless value == true || value == false || value.respond_to?(:call)
229
+ raise ArgumentError, "requires_approval must be true, false, or callable"
230
+ end
231
+ @requires_approval = value
232
+ end
233
+ end
234
+
235
+ # @api public
236
+ def approval_facts(&block)
237
+ if block
238
+ @approval_facts = block
239
+ elsif instance_variable_defined?(:@approval_facts)
240
+ @approval_facts
241
+ elsif superclass.respond_to?(:approval_facts)
242
+ superclass.approval_facts
243
+ end
244
+ end
245
+
246
+ # @api public
247
+ def redact_params(*names)
248
+ if names.empty?
249
+ parent = superclass.respond_to?(:redact_params) ? superclass.redact_params : []
250
+ ((@redacted_params || []) + parent).uniq
251
+ else
252
+ @redacted_params = ((@redacted_params || []) + names.map(&:to_sym)).uniq
253
+ end
254
+ end
255
+
256
+ # @api public
257
+ def max_result_size(value = :__unset__)
258
+ if value == :__unset__
259
+ return @max_result_size if instance_variable_defined?(:@max_result_size)
260
+ return superclass.max_result_size if superclass.respond_to?(:max_result_size)
261
+
262
+ return nil
263
+ end
264
+
265
+ @max_result_size = value
266
+ end
267
+ end
268
+
269
+ def name
270
+ self.class.tool_name
271
+ end
272
+
273
+ def description
274
+ self.class.description
275
+ end
276
+
277
+ def provider_options
278
+ self.class.provider_options
279
+ end
280
+
281
+ def parameters_schema
282
+ definition = self.class.parameters_schema_definition
283
+ return definition.json_schema if definition
284
+
285
+ declarations = self.class.declared_parameters
286
+ if declarations.empty?
287
+ declarations = method(:execute).parameters.filter_map do |kind, name|
288
+ next unless %i[key keyreq].include?(kind)
289
+ next if %i[cancellation_token tool_call].include?(name)
290
+ [name, Parameter.new(name: name, type: :string, description: nil, required: kind == :keyreq)]
291
+ end.to_h
292
+ end
293
+ properties = declarations.to_h do |name, parameter|
294
+ type = parameter.type.to_s
295
+ type = "number" if %w[float double].include?(type)
296
+ type = "integer" if type == "int"
297
+ value = {"type" => type, "description" => parameter.description}.compact
298
+ # An unqualified array declaration permits any JSON item. Explicit
299
+ # item constraints belong to an explicit schema.
300
+ value["items"] = {} if type == "array"
301
+ value["type"] = [type, "null"] unless parameter.required
302
+ [name.to_s, value]
303
+ end
304
+ schema = {"type" => "object", "properties" => properties,
305
+ "required" => declarations.select { |_, parameter| parameter.required }.keys.map(&:to_s),
306
+ "additionalProperties" => false}
307
+
308
+ properties = schema.dig("properties") || schema.dig(:properties)
309
+ return schema unless properties
310
+
311
+ self.class.param_enums.each do |param_name, values|
312
+ key = properties.key?(param_name.to_s) ? param_name.to_s : param_name.to_sym
313
+ next unless properties[key]
314
+
315
+ param_type = Array(properties[key]["type"]).find { |type| type != "null" }
316
+ properties[key]["enum"] = values.map do |value|
317
+ case param_type
318
+ when "integer"
319
+ value.is_a?(Integer) ? value : Integer(value.to_s)
320
+ when "number"
321
+ value.is_a?(Numeric) ? value : Float(value.to_s)
322
+ when "boolean"
323
+ unless value == true || value == false
324
+ raise ArgumentError,
325
+ "boolean enum values must be true or false (got: #{value.inspect})"
326
+ end
327
+ value
328
+ else
329
+ value.to_s
330
+ end
331
+ end
332
+ properties[key]["enum"] << nil unless declarations.fetch(param_name.to_sym).required
333
+ end
334
+
335
+ self.class.param_schemas.each do |param_name, nested|
336
+ key = properties.key?(param_name.to_s) ? param_name.to_s : param_name.to_sym
337
+ next unless properties[key]
338
+ properties[key]["properties"] = nested_schema_to_json_schema(nested)
339
+ properties[key]["required"] = nested.select { |_, spec| spec[:required] }.keys.map(&:to_s)
340
+ properties[key]["additionalProperties"] = false
341
+ end
342
+
343
+ schema
344
+ end
345
+
346
+ # Phronomy retains these readers as part of its Tool contract.
347
+ # @api public
348
+ def params_schema
349
+ parameters_schema
350
+ end
351
+
352
+ # @api public
353
+ def provider_params
354
+ provider_options
355
+ end
356
+
357
+ # @api public
358
+ def parameters
359
+ self.class.declared_parameters
360
+ end
361
+
362
+ # Validate/coerce without executing, so approval and execution share inputs.
363
+ # @api public
364
+ def validate_arguments(args)
365
+ schema = parameters_schema
366
+ if !@validation_schema || @validation_schema.json_schema != schema
367
+ @validation_schema = Schema.new(schema)
368
+ end
369
+ values, error = @validation_schema.validate(args || {}, coerce: self.class.on_schema_error == :coerce)
370
+ if values && !self.class.parameters_schema_definition
371
+ # Legacy optional param nil means omission, preserving Ruby defaults.
372
+ self.class.declared_parameters.each do |name, parameter|
373
+ values.delete(name) if !parameter.required && values[name].nil?
374
+ end
375
+ end
376
+ [values, error]
377
+ end
378
+
379
+ # @api public
380
+ def call(args, cancellation_token: nil)
381
+ cancellation_token&.raise_if_cancelled!
382
+ validated_args, schema_error = validate_arguments(args)
383
+ if schema_error
384
+ case self.class.on_schema_error
385
+ when :raise
386
+ raise Phronomy::ToolError.new(
387
+ "#{self.class.name} schema error: #{schema_error}", code: "tool.invalid_arguments"
388
+ )
389
+ else
390
+ return "Schema validation failed: #{schema_error}"
391
+ end
392
+ end
393
+
394
+ if cancellation_token && execute_accepts_cancellation_token?
395
+ validated_args = validated_args.merge(cancellation_token: cancellation_token)
396
+ end
397
+ result = execute(**(validated_args || {}).transform_keys(&:to_sym))
398
+ truncate_result_if_needed(result)
399
+ rescue Phronomy::ToolError, Phronomy::CancellationError
400
+ raise
401
+ rescue => error
402
+ if self.class.on_error == :suppress
403
+ report_suppressed_error(error)
404
+ "Tool error suppressed: #{error.message}"
405
+ else
406
+ raise Phronomy::ToolError.new(
407
+ "#{self.class.name} execution failed: #{error.message}", code: "tool.execution_failed"
408
+ ), cause: error
409
+ end
410
+ end
411
+
412
+ # @api public
413
+ def call_async(
414
+ args,
415
+ cancellation_token: nil,
416
+ config: {}
417
+ )
418
+ Phronomy::Tool::ToolExecutor.call_async(
419
+ tool: self,
420
+ args: args,
421
+ cancellation_token: cancellation_token,
422
+ config: config,
423
+ synchronous_call: Operation.synchronous_delegate(self)
424
+ )
425
+ end
426
+
427
+ def requires_approval
428
+ self.class.requires_approval
429
+ end
430
+
431
+ def requires_approval?
432
+ self.class.requires_approval
433
+ end
434
+
435
+ # @api public
436
+ def tool_origin
437
+ :local
438
+ end
439
+
440
+ # @api public
441
+ def approval_metadata
442
+ {}
443
+ end
444
+
445
+ # @api public
446
+ def execute(**_args)
447
+ raise NotImplementedError, "#{self.class}#execute is not implemented"
448
+ end
449
+
450
+ private
451
+
452
+ # Apply the Tool contract to an implementation that starts asynchronous
453
+ # work. The owner supplies a completion handle; this helper never waits or
454
+ # chooses the implementation, and preserves physical completion via C13.
455
+ def call_async_operation(args, cancellation_token:, operation_name:)
456
+ cancellation_token&.raise_if_cancelled!
457
+ validated_args, schema_error = validate_arguments(args)
458
+ if schema_error
459
+ return Phronomy::AsyncOperation.capture(name: "#{operation_name}-schema") do
460
+ if self.class.on_schema_error == :raise
461
+ raise Phronomy::ToolError.new(
462
+ "#{self.class.name} schema error: #{schema_error}", code: "tool.invalid_arguments"
463
+ )
464
+ end
465
+ "Schema validation failed: #{schema_error}"
466
+ end
467
+ end
468
+ Phronomy::AsyncOperation.call(name: operation_name,
469
+ on_error: method(:async_error_value),
470
+ transform: method(:truncate_result_if_needed)) do
471
+ source = yield(validated_args || {})
472
+ unless source.respond_to?(:on_complete)
473
+ raise Phronomy::ToolError.new(
474
+ "#{self.class.name} asynchronous execution must return a completion handle", code: "tool.invalid_async_result"
475
+ )
476
+ end
477
+ source
478
+ end
479
+ rescue => error
480
+ Phronomy::AsyncOperation.capture(name: operation_name,
481
+ on_error: method(:async_error_value)) { raise error }
482
+ end
483
+
484
+ def async_error_value(error)
485
+ if error.is_a?(Phronomy::ToolError) || error.is_a?(Phronomy::CancellationError)
486
+ raise error
487
+ end
488
+
489
+ if self.class.on_error == :suppress
490
+ report_suppressed_error(error)
491
+ "Tool error suppressed: #{error.message}"
492
+ else
493
+ wrapped = Phronomy::ToolError.new(
494
+ "#{self.class.name} execution failed: #{error.message}", code: "tool.execution_failed"
495
+ )
496
+ wrapped.set_backtrace(error.backtrace)
497
+ raise wrapped, cause: error
498
+ end
499
+ end
500
+
501
+ def report_suppressed_error(error)
502
+ message = "[Phronomy] Tool #{self.class.name} suppressed error: #{error.class}: #{error.message}"
503
+ logger = Phronomy::RuntimeSettings.current.logger
504
+ logger ? logger.warn(message) : warn(message)
505
+ rescue
506
+ # A diagnostic failure must not change the selected suppression policy.
507
+ nil
508
+ end
509
+
510
+ def execute_accepts_cancellation_token?
511
+ method(:execute).parameters.any? do |type, name|
512
+ name == :cancellation_token && %i[key keyreq].include?(type)
513
+ end
514
+ end
515
+
516
+ def truncate_result_if_needed(result)
517
+ max = self.class.max_result_size || Settings.current.max_result_size
518
+ return result unless max && result.respond_to?(:length) && result.length > max
519
+
520
+ msg = "[Phronomy] Tool #{self.class.name} result truncated " \
521
+ "(#{result.length} chars > #{max} limit)"
522
+ if Phronomy::RuntimeSettings.current.logger
523
+ Phronomy::RuntimeSettings.current.logger.warn(msg)
524
+ else
525
+ warn msg
526
+ end
527
+ "#{result[0, max]}...[truncated]"
528
+ end
529
+
530
+ def redacted_args(args)
531
+ redacted = self.class.redact_params
532
+ return args if redacted.empty?
533
+
534
+ args.each_with_object({}) do |(key, value), result|
535
+ result[key] = redacted.include?(key.to_sym) ? "[REDACTED]" : value
536
+ end
537
+ end
538
+
539
+ def nested_schema_to_json_schema(nested)
540
+ nested.each_with_object({}) do |(prop_name, spec), result|
541
+ type = spec[:type].to_s
542
+ type = "number" if %w[float double].include?(type)
543
+ type = "integer" if type == "int"
544
+ entry = {"type" => spec[:required] ? type : [type, "null"]}
545
+ entry["description"] = spec[:desc] if spec[:desc]
546
+ entry["enum"] = spec[:required] ? spec[:enum] : (spec[:enum] + [nil]).uniq if spec[:enum]
547
+ if spec[:properties]
548
+ entry["properties"] = nested_schema_to_json_schema(spec[:properties])
549
+ entry["required"] = spec[:properties].select { |_, child| child[:required] }.keys.map(&:to_s)
550
+ entry["additionalProperties"] = false
551
+ end
552
+ result[prop_name.to_s] = entry
553
+ end
554
+ end
555
+ end
14
556
  end
15
557
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "schema"
4
+
5
+ module Phronomy
6
+ module Tool
7
+ # A request is a value, not an executable capability or an approval grant.
8
+ # @api public
9
+ class CallRequest
10
+ attr_reader :id, :name, :arguments, :metadata
11
+
12
+ # @api public
13
+ def initialize(id:, name:, arguments:, metadata: {})
14
+ unless id.is_a?(String) && !id.empty? && name.is_a?(String) && !name.empty?
15
+ raise ArgumentError, "Tool request requires non-empty String id and name"
16
+ end
17
+ unless arguments.is_a?(Hash) && metadata.is_a?(Hash)
18
+ raise ArgumentError, "Tool arguments and metadata must be Hashes"
19
+ end
20
+ @id = id.dup.freeze
21
+ @name = name.dup.freeze
22
+ @arguments = Phronomy::Values::Immutable.copy(Schema.json_value(arguments))
23
+ @metadata = Phronomy::Values::Immutable.copy(Schema.json_value(metadata))
24
+ freeze
25
+ end
26
+
27
+ # @api public
28
+ def self.from_h(value)
29
+ source = Schema.json_value(value)
30
+ new(id: source.fetch("id"), name: source.fetch("name"),
31
+ arguments: source.fetch("arguments"), metadata: source.fetch("metadata", {}))
32
+ end
33
+
34
+ # @api public
35
+ def to_h
36
+ {"id" => id, "name" => name, "arguments" => arguments, "metadata" => metadata}
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module Agent
5
- class ToolDefinitionSet
4
+ module Tool
5
+ class DefinitionSet
6
6
  attr_reader :runtime_tools, :definitions
7
7
 
8
- def self.build(agent, additional_tools: [])
9
- runtime_tools = (agent.class.tools + Array(additional_tools)).freeze
10
- definitions = runtime_tools.map do |tool_class|
11
- prepared = agent.send(:prepare_tool_class, tool_class)
8
+ def self.build(tools:, runtime_tools: tools)
9
+ runtime_tools = Array(runtime_tools).freeze
10
+ definitions = tools.map do |prepared|
12
11
  tool = prepared.is_a?(Class) ? prepared.new : prepared
13
12
  {
14
13
  "name" => tool.name.to_s,
@@ -68,7 +67,7 @@ module Phronomy
68
67
 
69
68
  current_definition, runtime_tool = current_by_name.fetch(name) do
70
69
  raise Phronomy::ConfigurationError,
71
- "ContextPolicy selected Tool not present in current Agent configuration: #{name}"
70
+ "Phronomy::Context::ContextPolicy selected Tool not present in current Agent configuration: #{name}"
72
71
  end
73
72
  if definition["parameters_schema"] == {} && current_definition["parameters_schema"] != {}
74
73
  raise Phronomy::ConfigurationError,
@@ -78,7 +77,7 @@ module Phronomy
78
77
  unless Phronomy::CanonicalJSON.dump(current_definition) ==
79
78
  Phronomy::CanonicalJSON.dump(definition)
80
79
  raise Phronomy::ConfigurationError,
81
- "Agent Tool definition changed after ContextPolicy selection: #{name}"
80
+ "Agent Tool definition changed after Phronomy::Context::ContextPolicy selection: #{name}"
82
81
  end
83
82
 
84
83
  selected_tools << runtime_tool