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
@@ -2,105 +2,29 @@
2
2
 
3
3
  module Phronomy
4
4
  module Agent
5
- class AgentInvocationSessionBuilder
6
- def self.build(
7
- agent:,
8
- input:,
9
- config:,
10
- approval_policy: nil,
11
- approval_listener: nil,
12
- mode: :invoke,
13
- on_event: nil,
14
- runtime: Phronomy::Runtime.instance
15
- )
16
- invocation = AgentInvocation.new(
17
- agent: agent,
18
- input: input,
19
- config: config,
20
- approval_policy: approval_policy,
21
- approval_listener: approval_listener,
22
- event_listener: on_event,
23
- mode: mode,
24
- execution_id: config.fetch(:execution_id)
25
- )
26
- build_session(
27
- agent_invocation: invocation,
28
- runtime: runtime,
29
- mode: mode
30
- )
31
- end
32
-
33
- def self.build_for_resume(
34
- agent_invocation:,
35
- resume_event:,
36
- resume_phase:,
37
- runtime: Phronomy::Runtime.instance
38
- )
39
- build_session(
40
- agent_invocation: agent_invocation,
41
- runtime: runtime,
42
- mode: agent_invocation.mode,
43
- resume_event: resume_event,
44
- resume_phase: resume_phase
45
- )
46
- end
47
-
48
- def self.build_session(
49
- agent_invocation:,
50
- runtime:,
51
- mode:,
52
- resume_event: nil,
53
- resume_phase: nil
54
- )
55
- agent = agent_invocation.agent
56
- event_sink = Phronomy::FSMSession::EventSink.new(
57
- event_loop: runtime.event_loop
58
- )
59
- agent_invocation.bind_event_sink!(event_sink)
60
- actions = build_entry_actions(
61
- agent, runtime, mode: mode, event_sink: event_sink
62
- )
63
- phase_machine = Agent::PhaseMachineBuilder.new(entry_actions: actions).build
64
- iterations = agent.class.max_iterations || 10
65
-
66
- Phronomy::FSMSession.new(
67
- context: agent_invocation,
68
- event_sink: event_sink,
69
- entry_point: InvocationTransitions::ENTRY_POINT,
70
- phase_machine_class: phase_machine,
71
- entry_actions: {},
72
- auto_state_set: InvocationTransitions::AUTO_STATE_SET,
73
- declared_states: InvocationTransitions::DECLARED_STATES,
74
- wait_state_names: InvocationTransitions::WAIT_STATES,
75
- external_events: InvocationTransitions::EXTERNAL_EVENTS,
76
- recursion_limit: 12 + (iterations * 8),
77
- event_loop: runtime.event_loop,
78
- resume_event: resume_event,
79
- resume_phase: resume_phase
80
- )
81
- end
82
- private_class_method :build_session
83
-
84
- def self.build_entry_actions(agent, runtime, mode:, event_sink:)
5
+ # Agent-owned entry operations and external-result delivery.
6
+ # Session construction and resource selection are supplied by the environment.
7
+ # @api private
8
+ class InvocationActions
9
+ def self.build_entry_actions(agent, environment, mode:, event_sink:)
85
10
  calling_action = if mode.to_sym == :stream
86
- method(:calling_llm_stream_action).curry.call(agent, runtime, event_sink)
11
+ method(:calling_llm_stream_action).curry.call(agent, environment, event_sink)
87
12
  else
88
- method(:calling_llm_action).curry.call(agent, runtime, event_sink)
13
+ method(:calling_llm_action).curry.call(agent, environment, event_sink)
89
14
  end
90
15
 
91
16
  {
92
17
  filtering_input: [method(:apply_prepared_input_action).curry.call(agent)],
93
- building_context: [method(:build_runtime_chat_action).curry.call(agent)],
18
+ building_context: [method(:prepare_runtime_input_action).curry.call(agent)],
94
19
  calling_llm: [calling_action],
95
- starting_tools: [method(:starting_tools_action).curry.call(runtime, event_sink)],
96
- dispatching_tools: [method(:dispatching_tools_action).curry.call(runtime, event_sink)],
20
+ starting_tools: [method(:starting_tools_action).curry.call(environment, event_sink)],
21
+ dispatching_tools: [method(:dispatching_tools_action).curry.call(environment, event_sink)],
97
22
  recording_tool_results: [method(:recording_tool_results_action)],
98
23
  suspended: [method(:suspended_action)],
99
24
  output_filtering: [method(:output_filtering_action).curry.call(agent)],
100
25
  failed: [method(:failed_action)]
101
26
  }
102
27
  end
103
- private_class_method :build_entry_actions
104
28
 
105
29
  def self.apply_prepared_input_action(_agent, invocation)
106
30
  invocation.input = invocation.config.fetch(:phronomy_filtered_input)
@@ -108,66 +32,25 @@ module Phronomy
108
32
  end
109
33
  private_class_method :apply_prepared_input_action
110
34
 
111
- def self.build_runtime_chat_action(agent, invocation)
35
+ def self.prepare_runtime_input_action(agent, invocation)
112
36
  projection = invocation.config.fetch(:phronomy_runtime_projection)
113
- invocation.chat = agent.send(:build_chat, model_config: projection.model_config)
114
- agent.send(
115
- :_apply_runtime_projection_to_chat,
116
- invocation.chat,
117
- projection,
118
- invocation: invocation
119
- )
120
- invocation
121
- end
122
- private_class_method :build_runtime_chat_action
123
-
124
- def self.install_tool_interceptors(chat, llm_call_id:)
125
- # RubyLLM 2 records the complete assistant message before this callback,
126
- # and checks its own approval gate afterwards. Capture here so Phronomy
127
- # owns authorization and dispatch for the entire Tool batch.
128
- chat.after_message do |message|
129
- next unless message.tool_call?
130
-
131
- raise build_tool_interception(chat, [], llm_call_id)
132
- end
133
- end
134
- private_class_method :install_tool_interceptors
135
-
136
- def self.build_tool_interception(chat, fallback_tool_calls, llm_call_id)
137
- assistant_message = chat.messages.last
138
- unless assistant_message&.respond_to?(:role) &&
139
- assistant_message.role.to_sym == :assistant &&
140
- assistant_message.respond_to?(:tool_calls)
141
- raise Phronomy::Error,
142
- "RubyLLM Tool callback fired before the complete assistant message was observable"
143
- end
144
-
145
- message_tool_calls = assistant_message.tool_calls
146
- tool_calls = message_tool_calls.respond_to?(:values) ? message_tool_calls.values : Array(message_tool_calls)
147
- tool_calls = Array(fallback_tool_calls) if tool_calls.empty?
148
-
149
- ToolCallIntercepted.new(
150
- tool_calls,
151
- assistant_message: assistant_message,
152
- assistant_outcome: ProviderCallOutcome.capture(assistant_message),
153
- llm_call_id: llm_call_id
154
- )
37
+ agent.send(:prepare_runtime_input, projection, invocation: invocation)
155
38
  end
156
- private_class_method :build_tool_interception
39
+ private_class_method :prepare_runtime_input_action
157
40
 
158
- def self.calling_llm_action(agent, runtime, event_sink, invocation)
159
- prepare_and_start_llm_call(agent, runtime, event_sink, invocation, streaming: false)
41
+ def self.calling_llm_action(agent, environment, event_sink, invocation)
42
+ prepare_and_start_llm_call(agent, environment, event_sink, invocation, streaming: false)
160
43
  invocation
161
44
  end
162
45
  private_class_method :calling_llm_action
163
46
 
164
- def self.calling_llm_stream_action(agent, runtime, event_sink, invocation)
165
- prepare_and_start_llm_call(agent, runtime, event_sink, invocation, streaming: true)
47
+ def self.calling_llm_stream_action(agent, environment, event_sink, invocation)
48
+ prepare_and_start_llm_call(agent, environment, event_sink, invocation, streaming: true)
166
49
  invocation
167
50
  end
168
51
  private_class_method :calling_llm_stream_action
169
52
 
170
- def self.prepare_and_start_llm_call(agent, runtime, event_sink, invocation, streaming:)
53
+ def self.prepare_and_start_llm_call(agent, environment, event_sink, invocation, streaming:)
171
54
  # Callback failure is recorded synchronously on EventLoop before its
172
55
  # explicit failure event is queued. Do not start a competing durable
173
56
  # follow-up operation from the same execution revision while that failure
@@ -183,7 +66,7 @@ module Phronomy
183
66
  else
184
67
  start_provider_call(
185
68
  agent,
186
- runtime,
69
+ environment,
187
70
  event_sink,
188
71
  invocation,
189
72
  invocation.config.fetch(:phronomy_runtime_projection),
@@ -201,7 +84,7 @@ module Phronomy
201
84
  # @api private
202
85
  def self.start_prepared_provider_call(
203
86
  agent:,
204
- runtime:,
87
+ environment:,
205
88
  event_sink:,
206
89
  invocation:,
207
90
  projection:,
@@ -209,7 +92,7 @@ module Phronomy
209
92
  )
210
93
  start_provider_call(
211
94
  agent,
212
- runtime,
95
+ environment,
213
96
  event_sink,
214
97
  invocation,
215
98
  projection,
@@ -219,30 +102,22 @@ module Phronomy
219
102
  end
220
103
 
221
104
  def self.start_provider_call(
222
- agent, runtime, event_sink, invocation, projection,
105
+ agent, environment, event_sink, invocation, projection,
223
106
  streaming:, replace_messages:
224
107
  )
225
108
  call_context = trace_handle = nil
226
109
  config = invocation.config
227
110
  agent.send(:check_cancellation!, config, "invocation cancelled before LLM call")
228
111
  if replace_messages
229
- invocation.chat = agent.send(:build_chat, model_config: projection.model_config)
230
- agent.send(
231
- :_apply_runtime_projection_to_chat,
232
- invocation.chat,
233
- projection,
234
- invocation: invocation
235
- )
112
+ agent.send(:prepare_runtime_input, projection, invocation: invocation)
236
113
  invocation.config[:phronomy_runtime_projection] = projection
237
114
  end
238
115
 
239
- call_context = invocation.begin_llm_call!(projection)
240
- install_tool_interceptors(
241
- invocation.chat,
242
- llm_call_id: call_context.fetch(:llm_call_id)
243
- )
244
- message = projection.ask_message
245
- chat = invocation.chat
116
+ request = agent.send(:build_llm_request, projection, invocation: invocation)
117
+ state = environment.registry.agent_execution_state(invocation.execution_id)
118
+ call_context = invocation.begin_llm_call!(projection,
119
+ llm_call_id: state&.execution&.metadata&.fetch(ExecutionMetadata::PENDING_LLM_ID_KEY, nil))
120
+ message = request.message
246
121
  trace_handle = Phronomy::Tracing::Automatic.start(
247
122
  "llm.call",
248
123
  input: message,
@@ -254,24 +129,26 @@ module Phronomy
254
129
  **agent.send(:_build_caller_meta, config)
255
130
  )
256
131
 
132
+ client = environment.build_llm_client(adapter: Phronomy::Agent::Settings.current.llm_adapter)
133
+ token = config[:cancellation_token]
134
+ llm_call_id = call_context.fetch(:llm_call_id)
257
135
  operation = if streaming
258
- Phronomy.configuration.llm_adapter.stream_async(
259
- chat, message, config: config
136
+ client.stream_async(
137
+ request, cancellation_token: config[:cancellation_token]
260
138
  ) do |chunk|
261
- token = config[:cancellation_token]
262
139
  token&.raise_if_cancelled!("invocation cancelled during streaming")
263
140
  post_stream_chunk(
264
141
  event_sink,
265
- call_context.fetch(:llm_call_id),
142
+ llm_call_id,
266
143
  chunk.content
267
144
  )
268
145
  end
269
146
  else
270
- Phronomy.configuration.llm_adapter.complete_async(
271
- chat, message, config: config
147
+ client.complete_async(
148
+ request, cancellation_token: config[:cancellation_token]
272
149
  )
273
150
  end
274
- Phronomy::Agent::ExecutionRegistry.for(runtime.event_loop).supervise_agent_operation(
151
+ environment.registry.supervise_agent_operation(
275
152
  invocation.execution_id,
276
153
  operation
277
154
  )
@@ -319,20 +196,8 @@ module Phronomy
319
196
  private_class_method :observe_manifest_call
320
197
 
321
198
  def self.finish_provider_trace(trace_handle, response, error)
322
- control_transfer = error.is_a?(ToolCallIntercepted)
323
- trace_output = control_transfer ?
324
- (error.assistant_outcome || error.assistant_message) : response
325
- trace_error = control_transfer ? nil : error
326
- usage_source = control_transfer ? error.assistant_message : response
327
- usage = if usage_source&.respond_to?(:tokens)
328
- Phronomy::TokenUsage.from_tokens(usage_source.tokens)
329
- end
330
- Phronomy::Tracing::Automatic.finish(
331
- trace_handle,
332
- output: trace_output,
333
- usage: usage,
334
- error: trace_error
335
- )
199
+ Phronomy::Tracing::Automatic.finish(trace_handle,
200
+ output: response, usage: response&.usage, error: error)
336
201
  end
337
202
  private_class_method :finish_provider_trace
338
203
 
@@ -343,7 +208,7 @@ module Phronomy
343
208
  error: error,
344
209
  streaming: streaming
345
210
  )
346
- event_type = if error && !error.is_a?(ToolCallIntercepted)
211
+ event_type = if error
347
212
  :llm_failed
348
213
  else
349
214
  :llm_completed
@@ -369,16 +234,16 @@ module Phronomy
369
234
  def self.post_session_event!(event_sink, event_type, payload)
370
235
  return if event_sink.post(event_type, payload)
371
236
 
372
- Phronomy.configuration.logger&.warn(
237
+ Phronomy::RuntimeSettings.current.logger&.warn(
373
238
  "[Phronomy] Dropped late #{event_type.inspect} for " \
374
239
  "FSMSession #{event_sink.fsm_session_id}"
375
240
  )
376
241
  end
377
242
  private_class_method :post_session_event!
378
243
 
379
- def self.starting_tools_action(runtime, parent_event_sink, invocation)
244
+ def self.starting_tools_action(environment, parent_event_sink, invocation)
380
245
  children = invocation.pending_tool_calls.map do |tool_call|
381
- tool = invocation.chat.tools[tool_call.name.to_sym]
246
+ tool = invocation.tools[tool_call.name.to_sym]
382
247
  tool_invocation_id =
383
248
  ToolInvocation.semantic_id(
384
249
  execution_id: invocation.execution_id,
@@ -413,13 +278,9 @@ module Phronomy
413
278
  invocation.tool_invocations = children
414
279
 
415
280
  children.reject(&:terminal?).each do |child|
416
- session = ToolInvocationSessionBuilder.build(
417
- tool_invocation: child,
418
- parent_event_sink: parent_event_sink,
419
- runtime: runtime
420
- )
281
+ session = environment.build_tool_session(invocation: child, parent_sink: parent_event_sink)
421
282
  register_child_session(
422
- runtime,
283
+ environment,
423
284
  child,
424
285
  session,
425
286
  parent_event_sink
@@ -429,7 +290,7 @@ module Phronomy
429
290
  end
430
291
  private_class_method :starting_tools_action
431
292
 
432
- def self.dispatching_tools_action(_runtime, parent_event_sink, invocation)
293
+ def self.dispatching_tools_action(_environment, parent_event_sink, invocation)
433
294
  invocation.config.fetch(:phronomy_execution_coordinator).prepare_tool_dispatch(
434
295
  invocation,
435
296
  event_sink: parent_event_sink
@@ -442,20 +303,19 @@ module Phronomy
442
303
  # preparation has been confirmed and applied by ExecutionCoordinator.
443
304
  # @api private
444
305
  def self.start_prepared_tool_dispatch(
445
- runtime:,
306
+ environment:,
446
307
  event_sink:,
447
308
  invocation:
448
309
  )
449
310
  invocation.tool_invocations.select(&:authorized?).each do |child|
450
- session = ToolInvocationSessionBuilder.build_for_resume(
451
- tool_invocation: child,
452
- parent_event_sink: event_sink,
311
+ session = environment.build_tool_session(
312
+ invocation: child,
313
+ parent_sink: event_sink,
453
314
  resume_event: :dispatch,
454
- resume_phase: :authorized,
455
- runtime: runtime
315
+ resume_phase: :authorized
456
316
  )
457
317
  begin
458
- register_child_session(runtime, child, session, event_sink)
318
+ register_child_session(environment, child, session, event_sink)
459
319
  rescue => error
460
320
  child.mark_framework_failed!(error)
461
321
  event_sink.post(:tool_failed, {tool_invocation_id: child.id})
@@ -465,7 +325,7 @@ module Phronomy
465
325
  invocation
466
326
  end
467
327
 
468
- def self.register_child_session(runtime, child, session, parent_event_sink)
328
+ def self.register_child_session(environment, child, session, parent_event_sink)
469
329
  completion = Phronomy::TaskResult.deferred(name: "tool-session:#{child.id}")
470
330
  completion.on_complete do |_result, error|
471
331
  next unless error
@@ -475,7 +335,7 @@ module Phronomy
475
335
  child.mark_framework_failed!(error)
476
336
  parent_event_sink.post(:tool_failed, {tool_invocation_id: child.id})
477
337
  end
478
- runtime.event_loop.register(session, completion: completion)
338
+ environment.register_session(session, completion: completion)
479
339
  end
480
340
  private_class_method :register_child_session
481
341
 
@@ -4,7 +4,7 @@ module Phronomy
4
4
  module Agent
5
5
  # Agent phase vocabulary and ordered external transitions. Both the phase
6
6
  # machine and FSMSession read this definition; Engine has no Agent policy.
7
- # Automatic transition behavior and entry actions retain their own owners.
7
+ # Automatic and external transition priority belongs to this Agent contract.
8
8
  # @api private
9
9
  module InvocationTransitions
10
10
  ENTRY_POINT = :idle
@@ -81,6 +81,44 @@ module Phronomy
81
81
  end
82
82
  events.freeze
83
83
  end
84
+
85
+ AUTOMATIC_EVENTS = {
86
+ state_completed: [
87
+ {from: :idle, to: :filtering_input, guard: nil},
88
+ {from: :filtering_input, to: :building_context, guard: ->(ctx) { ctx.input_passed? }},
89
+ {from: :filtering_input, to: :blocked, guard: ->(ctx) { ctx.input_blocked? }},
90
+ {from: :building_context, to: :calling_llm, guard: nil},
91
+ {from: :starting_tools, to: :evaluating_tools, guard: nil},
92
+ {from: :evaluating_tools, to: :failed, guard: ->(ctx) { ctx.tool_batch_failed? }},
93
+ {from: :evaluating_tools, to: :blocked, guard: ->(ctx) { ctx.tool_batch_rejected? }},
94
+ {from: :evaluating_tools, to: :recording_tool_results, guard: ->(ctx) { ctx.tool_batch_completed? }},
95
+ {from: :evaluating_tools, to: :suspended, guard: ->(ctx) { ctx.approval_required? }},
96
+ {from: :evaluating_tools, to: :dispatching_tools, guard: ->(ctx) { ctx.ready_to_dispatch? }},
97
+ {from: :evaluating_tools, to: :waiting_for_tools, guard: nil},
98
+ {from: :recording_tool_results, to: :calling_llm, guard: nil},
99
+ {from: :output_filtering, to: :completed, guard: ->(ctx) { ctx.output_passed? }},
100
+ {from: :output_filtering, to: :blocked, guard: ->(ctx) { ctx.output_blocked? }}
101
+ ].each(&:freeze).freeze
102
+ }.freeze
103
+
104
+ EVENTS = AUTOMATIC_EVENTS.merge(EXTERNAL_EVENTS).freeze
105
+
106
+ def self.next_phase(phase, event, context)
107
+ definition = EVENTS.fetch(event.to_sym, []).find do |candidate|
108
+ candidate[:from] == phase.to_sym &&
109
+ allowed?(candidate, context)
110
+ end
111
+ definition&.fetch(:to)
112
+ end
113
+
114
+ def self.allowed?(definition, context)
115
+ guard = definition[:guard]
116
+ !guard || !!(context && guard.call(context))
117
+ end
118
+
119
+ def self.recursion_limit(max_iterations)
120
+ 12 + ((max_iterations || 10) * 8)
121
+ end
84
122
  end
85
123
  end
86
124
  end
@@ -24,18 +24,15 @@ module Phronomy
24
24
  outcome = item[:response]
25
25
  error = item[:error]
26
26
  llm_call_id = item.fetch(:llm_call_id).to_s
27
- intercepted = error.is_a?(ToolCallIntercepted)
28
- call_error = intercepted ? nil : error
27
+ tools_requested = outcome&.tool_call? || false
28
+ call_error = error
29
29
  output_ref = assistant_output_ref(tx, outcome)
30
30
  assistant_ref = assistant_message_ref(tx, outcome)
31
31
  error_ref = if call_error
32
- tx.contents.put_json(
33
- "class" => call_error.class.name,
34
- "message" => call_error.message
35
- )
32
+ tx.contents.put_json(Phronomy::Error.diagnostic(call_error))
36
33
  end
37
- usage_ref = if outcome && !outcome.usage.empty?
38
- tx.contents.put_json(json_value(outcome.usage))
34
+ usage_ref = if outcome && outcome.usage.to_h.values.any? { |value| !value.nil? }
35
+ tx.contents.put_json(json_value(outcome.usage.to_h))
39
36
  end
40
37
  call = LLMCallRecord.new(
41
38
  llm_call_id: llm_call_id,
@@ -50,7 +47,7 @@ module Phronomy
50
47
  completed_at: Time.now.utc.iso8601(6),
51
48
  metadata: {
52
49
  "streaming" => item[:streaming],
53
- "tool_call_intercepted" => intercepted,
50
+ "tools_requested" => tools_requested,
54
51
  "assistant_outcome_captured" => !outcome.nil?,
55
52
  "tool_call_count" => outcome ? outcome.tool_calls.length : 0
56
53
  }
@@ -180,8 +177,8 @@ module Phronomy
180
177
  def assistant_message_metadata(outcome)
181
178
  calls = Array(outcome&.tool_calls)
182
179
  {
183
- "tool_call_ids" => calls.map { |call| call.fetch("id").to_s },
184
- "tool_names" => calls.map { |call| call.fetch("name").to_s }
180
+ "tool_call_ids" => calls.map(&:id),
181
+ "tool_names" => calls.map(&:name)
185
182
  }
186
183
  end
187
184
 
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Limited synchronous participant SPI. Agent owns all record mutations,
6
+ # revision checks and outcome publication. A participant owns its own
7
+ # transaction and passes only an opaque extension and a transfer receipt.
8
+ # @api public
9
+ class ExecutionChange
10
+ attr_reader :kind, :agent_id, :execution_id, :extension, :operations,
11
+ :control_request, :cancel_requested, :recovery_required, :durable_context,
12
+ :correlation, :coordinator
13
+
14
+ # Agent execution constructs changes; applications implement participants.
15
+ # @api private
16
+ def initialize(agent:, current:, root:, kind:, updated: nil, terminal: nil, config: {}, &write)
17
+ @agent, @store, @current, @root = agent, agent.persistence, current, root
18
+ @coordinator = @store.coordinator
19
+ @kind, @updated, @write = kind, updated, write
20
+ @agent_id, @execution_id = current.agent_id, current.execution_id
21
+ @extension = current.metadata["execution_extension"] && ExecutionExtensionState.from_h(current.metadata["execution_extension"])
22
+ @correlation = Phronomy::Values::Immutable.copy(current.metadata["reservation"])
23
+ @control_request = terminal&.handoff
24
+ @cancel_requested = terminal&.cancel_requested || false
25
+ error = terminal && (terminal.callback_failure&.to_stream_callback_error || terminal.source_error || terminal.block_error || terminal.invocation_error)
26
+ @control_request = nil if error || terminal&.phase == :suspended
27
+ @recovery_required = error.is_a?(Phronomy::ExecutionRehydrationRequiredError)
28
+ @finishing = kind == :terminal && !@recovery_required && (error || terminal.phase != :suspended)
29
+ @operations = Array(updated&.metadata&.fetch(ExecutionMetadata::TOOL_BATCH_METADATA_KEY, nil)).filter_map do |entry|
30
+ next unless entry.fetch("status") == "authorized"
31
+ Store::AuthorizedOperation.new(invocation_id: entry.fetch("tool_invocation_id"), name: entry.fetch("tool_name"),
32
+ arguments: Phronomy::Values::Immutable.copy(entry.fetch("arguments").compact))
33
+ end.freeze
34
+ ref = current.metadata["durable_context_ref"]
35
+ @durable_context = ref && Phronomy::Values::Immutable.copy(@store.contents.fetch_json(ref))
36
+ @participant = config[:phronomy_execution_participant]
37
+ @thread = Thread.current
38
+ validate_binding!
39
+ end
40
+
41
+ def finishing? = !!@finishing
42
+
43
+ # Runs current wiring and proves the complete captured change after an
44
+ # ambiguous response. A savepoint never grants publication permission.
45
+ # @api private
46
+ def perform
47
+ raise Phronomy::ConfigurationError, "Execution change is single-use" if @performed
48
+ @performed = true
49
+ perform_once
50
+ end
51
+
52
+ # @api private
53
+ def perform_once
54
+ if @participant
55
+ @participant.commit(self)
56
+ else
57
+ coordinator.atomic { |scope|
58
+ prepare_in(scope)
59
+ commit_in(scope)
60
+ }
61
+ end
62
+ unless @scope&.committed? && @result
63
+ raise Phronomy::Persistence::TransactionError, "Execution change has not committed its outer scope"
64
+ end
65
+ @result
66
+ rescue => error
67
+ raise unless @after && @scope&.outermost?
68
+ outcome = Phronomy::Persistence::SaveOutcome.compare(before: @before, after: @after, original_error: error) do
69
+ coordinator.atomic do |scope|
70
+ @participant&.guard_change(self, scope)
71
+ @store.guard_agents(scope, agent_ids: [agent_id])
72
+ snapshot(scope)
73
+ end
74
+ end
75
+ return @result if outcome.disposition == :committed
76
+ raise error if outcome.disposition == :not_committed
77
+ raise Phronomy::ExecutionRehydrationRequiredError, "Execution change outcome is unknown: #{(outcome.read_error || error).message}"
78
+ end
79
+
80
+ private :perform_once
81
+
82
+ # Capture the complete pre-state after the participant has acquired its
83
+ # ordered guards, before it changes its own records.
84
+ def prepare_in(scope)
85
+ raise Phronomy::Persistence::TransactionError, "Execution change belongs to another thread" unless @thread.equal?(Thread.current)
86
+ raise Phronomy::ConfigurationError, "Execution change is single-use" if @scope
87
+ @store.guard_agents(scope, agent_ids: [agent_id])
88
+ @before = snapshot(scope)
89
+ @cancel_requested ||= @store.cancellation_requested?(agent_id: agent_id, execution_id: execution_id, scope: scope)
90
+ @scope = scope
91
+ nil
92
+ end
93
+
94
+ # Must be called once in the participant's original synchronous context.
95
+ # No arbitrary execution setter or callback is exposed to the participant.
96
+ def commit_in(scope, state: extension, transfer_receipt: nil, pending: false)
97
+ raise Phronomy::Persistence::TransactionError, "Execution change belongs to another thread" unless @thread.equal?(Thread.current)
98
+ raise Phronomy::ConfigurationError, "Execution change must use its prepared scope exactly once" unless @scope.equal?(scope) && !@used
99
+ @used = true
100
+ if state && (!extension || state.binding_key != extension.binding_key || state.binding_version != extension.binding_version)
101
+ raise Phronomy::ConfigurationError, "Execution extension identity cannot change"
102
+ end
103
+ raise ArgumentError, "dispatch cannot choose a terminal outcome" if kind == :dispatch && (transfer_receipt || pending)
104
+ raise ArgumentError, "only a control request can transfer responsibility" if transfer_receipt && !control_request
105
+ raise ArgumentError, "pending work requires cancellation or recovery" if pending && !(cancel_requested || recovery_required)
106
+ @store.participate(scope) do |records|
107
+ records.assert_agent_watermark!(agent_id: agent_id, agent_revision: @root.agent_revision, journal_position: @root.journal_position)
108
+ observed = records.executions.load(execution_id)
109
+ unless observed.agent_id == agent_id && observed.execution_revision == @current.execution_revision
110
+ raise Phronomy::Persistence::StateConflictError, "Execution changed before participation"
111
+ end
112
+ if kind == :dispatch
113
+ updated = attach_state(@updated, state)
114
+ records.executions.save(execution_id, expected_revision: @current.execution_revision, execution: updated)
115
+ @result = updated
116
+ else
117
+ @result = @write.call(records, state, transfer_receipt, pending)
118
+ end
119
+ end
120
+ @after = snapshot(scope)
121
+ nil
122
+ end
123
+
124
+ # Finalized input material; policies are interpreted by the participant.
125
+ def transfer_context(scope)
126
+ raise ArgumentError, "there is no control request" unless control_request
127
+ @store.participate(scope) do |records|
128
+ manifest = SavedContextReader.manifest_from_ref(@agent, @current.metadata.fetch("manifest_ref"))
129
+ TransferProjection.new.build(request: control_request, manifest: manifest, persistence: records, source_agent: @agent)
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def validate_binding!
136
+ return unless extension
137
+ binding = @participant&.binding
138
+ unless binding && binding.binding_key == extension.binding_key && binding.binding_version == extension.binding_version
139
+ raise Phronomy::ExecutionRehydrationRequiredError, "Execution #{execution_id} needs participant #{extension.binding_key}@#{extension.binding_version}"
140
+ end
141
+ end
142
+
143
+ def attach_state(execution, state)
144
+ execution.with(execution_revision: execution.execution_revision,
145
+ metadata: execution.metadata.merge("execution_extension" => state&.to_h).compact)
146
+ end
147
+
148
+ def snapshot(scope)
149
+ agent_state = @store.participate(scope) do |records|
150
+ root = records.agents.load(agent_id)
151
+ execution = records.executions.load(execution_id)
152
+ journal = records.journals.read(agent_id, after: @root.journal_position,
153
+ limit: [root.journal_position - @root.journal_position, 1].max)
154
+ ref = execution.metadata.dig("execution_extension", "state_ref")
155
+ [root.to_h, execution.to_h, journal.map(&:to_h), ref && records.contents.fetch_json(ref), records.cancellations.requested?(agent_id, execution_id)]
156
+ end
157
+ [agent_state, @participant&.change_evidence(self, scope)]
158
+ end
159
+ end
160
+ end
161
+ end