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,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../engine/concurrency/worker_input_restricted"
3
+ require_relative "../../execution/concurrency/worker_input_restricted"
4
4
 
5
5
  require "digest"
6
6
  require "securerandom"
@@ -196,11 +196,7 @@ module Phronomy
196
196
  def validate!
197
197
  return self if terminal?
198
198
 
199
- validated, schema_error = if @tool.respond_to?(:validate_and_coerce, true)
200
- @tool.send(:validate_and_coerce, @raw_arguments)
201
- else
202
- [@raw_arguments, nil]
203
- end
199
+ validated, schema_error = @tool.validate_arguments(@raw_arguments)
204
200
 
205
201
  if schema_error
206
202
  if @tool.class.respond_to?(:on_schema_error) && @tool.class.on_schema_error == :raise
@@ -224,27 +220,21 @@ module Phronomy
224
220
  self
225
221
  end
226
222
 
227
- def start_authorization(runtime: Phronomy::Runtime.instance, &callback)
223
+ def start_authorization(environment:, &callback)
228
224
  raise ArgumentError, "start_authorization requires a callback" unless callback
229
225
 
230
226
  command = authorization_command
231
227
  evaluator = self.class
232
228
  tool_invocation_id = @id.to_s.freeze
233
- pool = runtime.pool(
234
- :authorization,
235
- size: Phronomy.configuration.authorization_pool_size,
236
- queue_size: Phronomy.configuration.authorization_queue_size
237
- )
238
229
  timeout = @config.fetch(
239
230
  :authorization_timeout,
240
- Phronomy.configuration.authorization_timeout
231
+ Phronomy::Agent::Settings.current.authorization_timeout
241
232
  )
242
- operation = pool.submit(
233
+ operation = environment.submit_authorization(
243
234
  timeout: timeout,
244
- cancellation_token: @config[:cancellation_token],
245
- on_full: :raise
235
+ cancellation_token: @config[:cancellation_token]
246
236
  ) { evaluator.send(:evaluate_authorization_command, command) }
247
- Phronomy::Agent::ExecutionRegistry.for(runtime.event_loop).supervise_agent_operation(@execution_id, operation)
237
+ environment.registry.supervise_agent_operation(@execution_id, operation)
248
238
  operation.on_complete do |outcome, error|
249
239
  callback.call(
250
240
  error ? evaluator.send(:authorization_failure_result, tool_invocation_id, error) : outcome
@@ -258,7 +248,7 @@ module Phronomy
258
248
  self
259
249
  end
260
250
 
261
- def start_execution(runtime: Phronomy::Runtime.instance, &callback)
251
+ def start_execution(environment:, &callback)
262
252
  raise ArgumentError, "start_execution requires a callback" unless callback
263
253
  unless dispatchable?
264
254
  callback.call(ExecutionOutcome.new(
@@ -270,45 +260,24 @@ module Phronomy
270
260
  return self
271
261
  end
272
262
 
273
- trace_handle = nil
274
- case @tool.class.execution_mode
275
- when :cooperative, :offloaded
276
- trace_handle = Phronomy::Tracing::Automatic.start(
277
- "tool.execute",
278
- input: @arguments || @raw_arguments,
279
- agent_id: @agent.agent_id,
280
- execution_id: @execution_id,
281
- tool_invocation_id: @id,
282
- tool_call_id: @tool_call_id,
283
- tool_name: @tool_name,
284
- **@agent.send(:_build_caller_meta, @config)
285
- )
286
- operation = start_async_tool_operation(runtime)
287
- unless operation.respond_to?(:on_complete)
288
- raise Phronomy::ToolError,
289
- "Tool #{@tool.class.name}#call_async must return a completion handle"
290
- end
291
- Phronomy::Agent::ExecutionRegistry.for(runtime.event_loop).supervise_agent_operation(@execution_id, operation)
292
-
293
- evaluator = self.class
294
- tool_invocation_id = @id.to_s.freeze
295
- operation.on_complete do |result, error|
296
- Phronomy::Tracing::Automatic.finish(
297
- trace_handle,
298
- output: result,
299
- error: error
300
- )
301
- callback.call(
302
- evaluator.send(:build_execution_outcome, tool_invocation_id, result, error)
303
- )
304
- end
305
- else
306
- callback.call(ExecutionOutcome.new(
307
- tool_invocation_id: @id,
308
- error: Phronomy::ConfigurationError.new(
309
- "unknown Tool execution_mode: #{@tool.class.execution_mode.inspect}"
310
- )
311
- ))
263
+ trace_handle = Phronomy::Tracing::Automatic.start(
264
+ "tool.execute",
265
+ input: @arguments || @raw_arguments,
266
+ agent_id: @agent.agent_id,
267
+ execution_id: @execution_id,
268
+ tool_invocation_id: @id,
269
+ tool_call_id: @tool_call_id,
270
+ tool_name: @tool_name,
271
+ **@agent.send(:_build_caller_meta, @config)
272
+ )
273
+ operation = start_async_tool_operation(environment)
274
+ environment.registry.supervise_agent_operation(@execution_id, operation)
275
+
276
+ evaluator = self.class
277
+ tool_invocation_id = @id.to_s.freeze
278
+ operation.on_complete do |result, error|
279
+ Phronomy::Tracing::Automatic.finish(trace_handle, output: result, error: error)
280
+ callback.call(evaluator.send(:build_execution_outcome, tool_invocation_id, result, error))
312
281
  end
313
282
  self
314
283
  rescue => error
@@ -417,28 +386,20 @@ module Phronomy
417
386
  agent_definition_version: Integer(definition.fetch(:version)),
418
387
  execution_id: @execution_id,
419
388
  tool_name: @tool_name.to_s.freeze,
420
- tool_schema: self.class.send(:immutable_command_copy, tool_schema),
389
+ tool_schema: Phronomy::Tool::Authorization.snapshot(tool_schema),
421
390
  tool_invocation_id: @id.to_s.freeze,
422
391
  tool_call_id: @tool_call_id&.to_s&.freeze,
423
- arguments: self.class.send(
424
- :immutable_command_copy,
392
+ arguments: Phronomy::Tool::Authorization.snapshot(
425
393
  @arguments || {}
426
394
  ),
427
- approval_policy: self.class.send(
428
- :safe_behavior_handle, @approval_policy, "approval_policy"
429
- ),
430
- approval_facts_callable: self.class.send(
431
- :safe_behavior_handle, authorization_facts_callable, "approval_facts"
432
- ),
433
- approval_requirement: self.class.send(
434
- :safe_behavior_handle, authorization_requirement, "requires_approval"
435
- ),
436
- approval_context: self.class.send(
437
- :immutable_command_copy,
395
+ approval_policy: Phronomy::Tool::Authorization.behavior(@approval_policy, "approval_policy"),
396
+ approval_facts_callable: Phronomy::Tool::Authorization.behavior(authorization_facts_callable, "approval_facts"),
397
+ approval_requirement: Phronomy::Tool::Authorization.behavior(authorization_requirement, "requires_approval"),
398
+ approval_context: Phronomy::Tool::Authorization.snapshot(
438
399
  @approval_context
439
400
  ),
440
401
  origin: @origin,
441
- metadata: self.class.send(:immutable_command_copy, @metadata)
402
+ metadata: Phronomy::Tool::Authorization.snapshot(@metadata)
442
403
  )
443
404
  end
444
405
 
@@ -456,58 +417,18 @@ module Phronomy
456
417
 
457
418
  def self.evaluate_authorization_command(command)
458
419
  request = build_authorization_request(command, facts: {}, default_decision: nil)
459
- facts = evaluate_authorization_facts(command)
460
- request = request.with(facts: facts)
461
- default_decision = evaluate_default_authorization_decision(command, request)
462
- request = request.with(default_decision: default_decision)
463
- decision = command.approval_policy ? command.approval_policy.call(request) : default_decision
464
- decision = decision.to_sym if decision.respond_to?(:to_sym)
465
-
466
- unless ApprovalEvaluationRequest::VALID_DECISIONS.include?(decision)
467
- raise Phronomy::ConfigurationError,
468
- "tool_approval_policy must return :allow, :require_approval, or :reject " \
469
- "(got #{decision.inspect})"
470
- end
471
-
472
- reason = if decision == :require_approval
473
- (command.origin == :mcp) ?
474
- "MCP Tool execution requires approval" : "Tool execution requires approval"
475
- end
420
+ decision = Phronomy::Tool::Authorization.call(
421
+ request: request, arguments: command.arguments, context: command.approval_context,
422
+ facts: command.approval_facts_callable, requirement: command.approval_requirement,
423
+ policy: command.approval_policy
424
+ )
476
425
  AuthorizationOutcome.new(
477
426
  tool_invocation_id: command.tool_invocation_id,
478
- decision: decision,
479
- facts: facts,
480
- reason: reason
427
+ decision: decision.decision, facts: decision.facts, reason: decision.reason
481
428
  )
482
429
  end
483
430
  private_class_method :evaluate_authorization_command
484
431
 
485
- def self.evaluate_authorization_facts(command)
486
- callable = command.approval_facts_callable
487
- return {} unless callable
488
-
489
- value = callable.call(command.arguments, command.approval_context)
490
- unless value.nil? || value.is_a?(Hash)
491
- raise Phronomy::ConfigurationError,
492
- "approval_facts must return a Hash or nil (got #{value.class})"
493
- end
494
- immutable_command_copy(value || {})
495
- end
496
- private_class_method :evaluate_authorization_facts
497
-
498
- def self.evaluate_default_authorization_decision(command, request)
499
- requirement = command.approval_requirement
500
- requirement = requirement.call(request) if requirement.respond_to?(:call)
501
- case requirement
502
- when true then :require_approval
503
- when false, nil then :allow
504
- else
505
- raise Phronomy::ConfigurationError,
506
- "requires_approval callable must return true or false (got #{requirement.inspect})"
507
- end
508
- end
509
- private_class_method :evaluate_default_authorization_decision
510
-
511
432
  def self.build_authorization_request(command, facts:, default_decision:)
512
433
  ApprovalEvaluationRequest.new(
513
434
  agent_id: command.agent_id,
@@ -528,99 +449,19 @@ module Phronomy
528
449
  end
529
450
  private_class_method :build_authorization_request
530
451
 
531
- def self.immutable_command_copy(value)
532
- if phronomy_managed_live_domain_object?(value)
533
- raise Phronomy::ConfigurationError,
534
- "authorization worker snapshot cannot contain Phronomy-managed live " \
535
- "domain object #{value.class}"
536
- end
537
-
538
- case value
539
- when Hash
540
- value.each_with_object({}) do |(key, item), result|
541
- result[immutable_command_copy(key)] = immutable_command_copy(item)
542
- end.freeze
543
- when Array
544
- value.map { |item| immutable_command_copy(item) }.freeze
545
- when String
546
- value.dup.freeze
547
- else
548
- # Application-defined opaque objects are permitted by ACS-11 and remain
549
- # Application-owned. A stricter general value-type protocol is deferred.
550
- value
551
- end
552
- end
553
- private_class_method :immutable_command_copy
554
-
555
- def self.phronomy_managed_live_domain_object?(value)
556
- value.is_a?(Phronomy::Concurrency::WorkerInputRestricted)
557
- end
558
- private_class_method :phronomy_managed_live_domain_object?
559
-
560
- def self.safe_behavior_handle(value, name)
561
- return value if value.nil? || value == true || value == false
562
- if phronomy_managed_live_domain_object?(value)
563
- raise Phronomy::ConfigurationError,
564
- "#{name} must not be a Phronomy-managed live domain object (got #{value.class})"
565
- end
566
- value
567
- end
568
- private_class_method :safe_behavior_handle
569
-
570
- # Compatibility helpers for the existing private behavioral specs. These
571
- # evaluate a frozen operation command just like the worker path; they do not
572
- # reintroduce worker-side access to live mutable ToolInvocation state.
573
- def evaluate_authorization
574
- self.class.send(:evaluate_authorization_command, authorization_command)
575
- end
576
-
577
- def evaluate_facts
578
- self.class.send(:evaluate_authorization_facts, authorization_command)
579
- end
580
-
581
- def evaluate_default_decision(request)
582
- self.class.send(
583
- :evaluate_default_authorization_decision,
584
- authorization_command,
585
- request
586
- )
587
- end
588
-
589
- def build_request(facts:, default_decision:)
590
- self.class.send(
591
- :build_authorization_request,
592
- authorization_command,
593
- facts: facts,
594
- default_decision: default_decision
452
+ def start_async_tool_operation(environment)
453
+ tool_config = @tool.class.respond_to?(:__framework_owned_operation?) ?
454
+ @config.merge(phronomy_tool_invocation_id: @id, execution_id: @execution_id).freeze : @config
455
+ Phronomy::Tool::Operation.call_async(
456
+ tool: @tool,
457
+ args: @arguments,
458
+ cancellation_token: @config[:cancellation_token],
459
+ config: tool_config,
460
+ submitter: environment,
461
+ on_full: :raise
595
462
  )
596
463
  end
597
464
 
598
- def start_async_tool_operation(runtime)
599
- if uses_default_call_async?
600
- Phronomy::Agent::Context::Capability::ToolExecutor.call_async(
601
- tool: @tool,
602
- args: @arguments,
603
- cancellation_token: @config[:cancellation_token],
604
- config: @config,
605
- runtime: runtime,
606
- on_full: :raise
607
- )
608
- else
609
- tool_config = @tool.class.respond_to?(:__framework_owned_operation?) ?
610
- @config.merge(phronomy_tool_invocation_id: @id, execution_id: @execution_id).freeze : @config
611
- @tool.call_async(
612
- @arguments,
613
- cancellation_token: @config[:cancellation_token],
614
- config: tool_config
615
- )
616
- end
617
- end
618
-
619
- def uses_default_call_async?
620
- @tool.method(:call_async).owner ==
621
- Phronomy::Agent::Context::Capability::Base
622
- end
623
-
624
465
  def self.build_execution_outcome(tool_invocation_id, result, error)
625
466
  if error
626
467
  ExecutionOutcome.new(
@@ -636,7 +477,7 @@ module Phronomy
636
477
 
637
478
  def self.authorization_failure_result(tool_invocation_id, error)
638
479
  if error.is_a?(Phronomy::TimeoutError) ||
639
- error.is_a?(Phronomy::TransportError) ||
480
+ error.is_a?(Phronomy::LLMAdapter::TransportError) ||
640
481
  error.is_a?(Phronomy::BackpressureError)
641
482
  AuthorizationOutcome.new(
642
483
  tool_invocation_id: tool_invocation_id,
@@ -654,16 +495,6 @@ module Phronomy
654
495
  end
655
496
  private_class_method :authorization_failure_result
656
497
 
657
- # Private compatibility helpers used by existing behavioral specs. Runtime
658
- # callbacks use only the pure class helpers above and captured semantic IDs.
659
- def execution_outcome(result, error)
660
- self.class.send(:build_execution_outcome, @id, result, error)
661
- end
662
-
663
- def authorization_failure_outcome(error)
664
- self.class.send(:authorization_failure_result, @id, error)
665
- end
666
-
667
498
  def authoritative_tool_outcome?(outcome)
668
499
  outcome.respond_to?(:tool_invocation_id) &&
669
500
  outcome.tool_invocation_id.to_s == @id
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Agent-owned Tool child entry operations and session-local result delivery.
6
+ # Workers return outcomes; the serial owner applies live invocation state.
7
+ # @api private
8
+ class ToolInvocationActions
9
+ def self.build_entry_actions(environment, event_sink, parent_event_sink)
10
+ {
11
+ validating: [method(:validating_action)],
12
+ authorizing: [method(:authorizing_action).curry.call(environment, event_sink)],
13
+ awaiting_approval: [method(:awaiting_approval_action).curry.call(parent_event_sink)],
14
+ authorized: [method(:authorized_action).curry.call(parent_event_sink)],
15
+ queued: [method(:queued_action)],
16
+ running: [method(:running_action).curry.call(environment, event_sink)],
17
+ completed: [method(:completed_action).curry.call(parent_event_sink)],
18
+ failed: [method(:failed_action).curry.call(parent_event_sink)],
19
+ rejected: [method(:rejected_action).curry.call(parent_event_sink)],
20
+ cancelled: [method(:cancelled_action).curry.call(parent_event_sink)]
21
+ }
22
+ end
23
+
24
+ def self.validating_action(invocation)
25
+ invocation.validate!
26
+ end
27
+ private_class_method :validating_action
28
+
29
+ def self.authorizing_action(environment, event_sink, invocation)
30
+ invocation.start_authorization(environment: environment) do |outcome|
31
+ post_to_session(event_sink, :authorization_completed, outcome)
32
+ end
33
+ invocation
34
+ end
35
+ private_class_method :authorizing_action
36
+
37
+ def self.awaiting_approval_action(parent_event_sink, invocation)
38
+ invocation.mark_awaiting_approval!
39
+ notify_parent(parent_event_sink, invocation, :tool_approval_required)
40
+ invocation
41
+ end
42
+ private_class_method :awaiting_approval_action
43
+
44
+ def self.authorized_action(parent_event_sink, invocation)
45
+ invocation.mark_authorized!
46
+ notify_parent(parent_event_sink, invocation, :tool_authorized)
47
+ invocation
48
+ end
49
+ private_class_method :authorized_action
50
+
51
+ def self.queued_action(invocation)
52
+ invocation.mark_queued!
53
+ end
54
+ private_class_method :queued_action
55
+
56
+ def self.running_action(environment, event_sink, invocation)
57
+ invocation.start_execution(environment: environment) do |outcome|
58
+ post_to_session(event_sink, :execution_completed, outcome)
59
+ end
60
+ invocation.mark_running!
61
+ invocation
62
+ end
63
+ private_class_method :running_action
64
+
65
+ def self.completed_action(parent_event_sink, invocation)
66
+ notify_parent(parent_event_sink, invocation, :tool_completed)
67
+ invocation
68
+ end
69
+ private_class_method :completed_action
70
+
71
+ def self.failed_action(parent_event_sink, invocation)
72
+ notify_parent(parent_event_sink, invocation, :tool_failed)
73
+ invocation
74
+ end
75
+ private_class_method :failed_action
76
+
77
+ def self.rejected_action(parent_event_sink, invocation)
78
+ invocation.mark_rejected!
79
+ notify_parent(parent_event_sink, invocation, :tool_rejected)
80
+ invocation
81
+ end
82
+ private_class_method :rejected_action
83
+
84
+ def self.cancelled_action(parent_event_sink, invocation)
85
+ invocation.mark_cancelled!
86
+ notify_parent(parent_event_sink, invocation, :tool_cancelled)
87
+ invocation
88
+ end
89
+ private_class_method :cancelled_action
90
+
91
+ def self.post_to_session(event_sink, event_type, payload)
92
+ return if event_sink.post(event_type, payload)
93
+
94
+ Phronomy::RuntimeSettings.current.logger&.warn(
95
+ "[Phronomy] Dropped #{event_type.inspect} for " \
96
+ "FSMSession #{event_sink.fsm_session_id}"
97
+ )
98
+ end
99
+ private_class_method :post_to_session
100
+
101
+ def self.notify_parent(parent_event_sink, invocation, event_type)
102
+ parent_event_sink.post(event_type, {tool_invocation_id: invocation.id})
103
+ end
104
+ private_class_method :notify_parent
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Agent-owned Tool child progress. Ordered guards and source admission
6
+ # share one definition; no FSM library or Runtime is needed to evaluate it.
7
+ # @api private
8
+ module ToolInvocationTransitions
9
+ ENTRY_POINT = :idle
10
+ RECURSION_LIMIT = 20
11
+
12
+ AUTO_STATE_SET = {idle: true, validating: true, queued: true}.freeze
13
+
14
+ DECLARED_STATES = %i[
15
+ idle validating authorizing awaiting_approval authorized queued running
16
+ completed failed rejected cancelled
17
+ ].freeze
18
+
19
+ WAIT_STATES = %i[awaiting_approval authorized].freeze
20
+
21
+ EXTERNAL_EVENTS = {
22
+ authorization_completed: [
23
+ {from: :authorizing, to: :cancelled, guard: ->(ctx) { ctx.cancelled? }},
24
+ {from: :authorizing, to: :failed, guard: ->(ctx) { ctx.failed? }},
25
+ {from: :authorizing, to: :rejected, guard: ->(ctx) { ctx.rejected? }},
26
+ {from: :authorizing, to: :awaiting_approval, guard: ->(ctx) { ctx.awaiting_approval? }},
27
+ {from: :authorizing, to: :authorized, guard: ->(ctx) { ctx.authorized? }}
28
+ ],
29
+ execution_completed: [
30
+ {from: :running, to: :cancelled, guard: ->(ctx) { ctx.cancelled? }},
31
+ {from: :running, to: :failed, guard: ->(ctx) { ctx.failed? }},
32
+ {from: :running, to: :completed, guard: ->(ctx) { ctx.execution_completed? }}
33
+ ],
34
+ approve: [{from: :awaiting_approval, to: :authorized, guard: nil}],
35
+ reject: [{from: :awaiting_approval, to: :rejected, guard: nil}],
36
+ dispatch: [{from: :authorized, to: :queued, guard: nil}],
37
+ cancel: [
38
+ {from: :awaiting_approval, to: :cancelled, guard: nil},
39
+ {from: :authorized, to: :cancelled, guard: nil},
40
+ {from: :queued, to: :cancelled, guard: nil},
41
+ {from: :running, to: :cancelled, guard: nil}
42
+ ]
43
+ }.transform_values { |definitions| definitions.each(&:freeze).freeze }.freeze
44
+
45
+ AUTOMATIC_EVENTS = {
46
+ state_completed: [
47
+ {from: :idle, to: :validating, guard: nil},
48
+ {from: :validating, to: :failed, guard: ->(ctx) { ctx.failed? }},
49
+ {from: :validating, to: :completed, guard: ->(ctx) { ctx.validation_completed? }},
50
+ {from: :validating, to: :authorizing, guard: ->(ctx) { ctx.validation_passed? }},
51
+ {from: :queued, to: :running, guard: nil}
52
+ ].each(&:freeze).freeze
53
+ }.freeze
54
+
55
+ EVENTS = AUTOMATIC_EVENTS.merge(EXTERNAL_EVENTS).freeze
56
+
57
+ def self.next_phase(phase, event, context)
58
+ definition = EVENTS.fetch(event.to_sym, []).find do |candidate|
59
+ candidate[:from] == phase.to_sym && allowed?(candidate, context)
60
+ end
61
+ definition&.fetch(:to)
62
+ end
63
+
64
+ def self.allowed?(definition, context)
65
+ guard = definition[:guard]
66
+ !guard || !!(context && guard.call(context))
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,5 +1,69 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- class Error < StandardError; end
4
+ class Error < StandardError
5
+ # Optional stable reason supplied by the owner of the failed operation.
6
+ # It does not imply retry safety or replace execution status.
7
+ # @api public
8
+ attr_reader :code
9
+
10
+ # Existing message-only construction retains StandardError behavior.
11
+ # @api public
12
+ def initialize(message = nil, code: nil)
13
+ raise ArgumentError, "error code must be a String or nil" unless code.nil? || code.is_a?(String)
14
+ @code = code&.dup&.freeze
15
+ super(message)
16
+ end
17
+
18
+ # Generic diagnostic transport. No domain-specific interpretation.
19
+ # External exceptions are not extended with this protocol.
20
+ # @api private
21
+ def self.diagnostic(error)
22
+ diagnostic = {"class" => error.class.name, "message" => error.message}
23
+ diagnostic["code"] = error.code if error.is_a?(Phronomy::Error) && error.code
24
+ cause = error.cause
25
+ diagnostic["cause"] = cause_diagnostic(cause, remaining: 3, seen: [error.object_id]) if cause
26
+ diagnostic
27
+ end
28
+
29
+ # Restores data, never a saved class name or arbitrary instance attributes.
30
+ # Restored causes are generic errors as well. Old diagnostics remain valid.
31
+ # @api private
32
+ def self.from_diagnostic(value, remaining: 3)
33
+ hash = value.to_h { |key, item| [key.to_s, item] }
34
+ error = Phronomy::Error.new(
35
+ "#{hash.fetch("class", "Error")}: #{hash.fetch("message", "Recovery-resolved failure")}",
36
+ code: hash["code"]
37
+ )
38
+ return error unless remaining.positive? && hash["cause"].is_a?(Hash) && hash["cause"].key?("class")
39
+
40
+ cause = from_diagnostic(hash["cause"], remaining: remaining - 1)
41
+ begin
42
+ raise error, cause: cause
43
+ rescue Phronomy::Error => restored
44
+ restored.set_backtrace(nil)
45
+ restored
46
+ end
47
+ end
48
+
49
+ def self.cause_diagnostic(error, remaining:, seen:)
50
+ return {"truncated" => true} if remaining.zero? || seen.include?(error.object_id)
51
+
52
+ # Bound newly added cause data; retain the existing top-level format.
53
+ fields = {"class" => error.class.name, "message" => error.message}
54
+ fields["code"] = error.code if error.is_a?(Phronomy::Error) && error.code
55
+ diagnostic = fields.to_h do |key, value|
56
+ [key, value.is_a?(String) ? value[0, 2048] : value]
57
+ end
58
+ # Never turn a truncated reason into a different, apparently valid code.
59
+ diagnostic.delete("code") if fields["code"] && fields["code"].length > 2048
60
+ diagnostic["truncated"] = true if diagnostic != fields
61
+ if error.cause
62
+ diagnostic["cause"] = cause_diagnostic(error.cause,
63
+ remaining: remaining - 1, seen: seen + [error.object_id])
64
+ end
65
+ diagnostic
66
+ end
67
+ private_class_method :cause_diagnostic
68
+ end
5
69
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/configuration_error"
4
+
5
+ module Phronomy
6
+ # Neutral runtime values only. Application composition supplies the current
7
+ # settings object; Tool and Tracing own their domain-specific settings.
8
+ # @api private
9
+ class RuntimeSettings
10
+ # Bind the application's settings accessor without evaluating it at boot.
11
+ # @api private
12
+ def self.install_provider(&provider)
13
+ raise ArgumentError, "RuntimeSettings provider requires a block" unless provider
14
+
15
+ @provider = provider
16
+ nil
17
+ end
18
+
19
+ # Resolve on every read so configuration reset/scoped restoration is visible.
20
+ # @return [RuntimeSettings]
21
+ # @api private
22
+ def self.current
23
+ settings = @provider&.call
24
+ unless settings.is_a?(RuntimeSettings)
25
+ raise ConfigurationError, "RuntimeSettings provider must return RuntimeSettings"
26
+ end
27
+
28
+ settings
29
+ end
30
+
31
+ attr_accessor :logger
32
+ attr_accessor :event_loop_stop_grace_seconds
33
+ attr_accessor :event_loop_starvation_threshold_seconds
34
+ attr_accessor :event_loop_dispatch_threshold_seconds
35
+ attr_accessor :offload_pool_size, :offload_queue_size
36
+ attr_accessor :authorization_pool_size, :authorization_queue_size
37
+
38
+ # @api private
39
+ def initialize
40
+ @event_loop_stop_grace_seconds = 5
41
+ @event_loop_starvation_threshold_seconds = nil
42
+ @event_loop_dispatch_threshold_seconds = nil
43
+ @offload_pool_size = 10
44
+ @offload_queue_size = 100
45
+ @authorization_pool_size = 4
46
+ @authorization_queue_size = 100
47
+ end
48
+ end
49
+ end
@@ -28,13 +28,18 @@ module Phronomy
28
28
  def fetch_text(content_id)
29
29
  value = fetch(content_id).force_encoding(Encoding::UTF_8)
30
30
  unless value.valid_encoding?
31
- raise IntegrityError, "content is not UTF-8: #{content_id}"
31
+ raise IntegrityError.new("content is not UTF-8: #{content_id}", code: "content.invalid_encoding")
32
32
  end
33
33
  value
34
34
  end
35
35
 
36
36
  def fetch_json(content_id)
37
- Phronomy::CanonicalJSON.load(fetch(content_id))
37
+ bytes = fetch(content_id)
38
+ begin
39
+ Phronomy::CanonicalJSON.load(bytes)
40
+ rescue JSON::ParserError => error
41
+ raise IntegrityError.new("content is not JSON: #{content_id}", code: "content.invalid_json"), cause: error
42
+ end
38
43
  end
39
44
 
40
45
  def fetch_many(content_ids)