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,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- # Constructs provider chat objects from already selected model settings.
6
- # Agent hooks retain projection installation and invocation-specific Tools.
7
- # @api private
8
- class RuntimeChatBuilder
9
- def self.build(config)
10
- chat = RubyLLM.chat(**chat_options(config))
11
- chat.with_temperature(config["temperature"]) if config["temperature"]
12
- if config["max_output_tokens"]
13
- chat.with_max_output_tokens(config["max_output_tokens"])
14
- end
15
- chat
16
- end
17
-
18
- def self.apply_instructions(chat, text, cache:, provider:)
19
- chat.with_instructions(text, cache_until_here: cache)
20
- end
21
-
22
- def self.chat_options(config)
23
- opts = {}
24
- model = config["model"]
25
- opts[:model] = model if model
26
- provider = config["provider"]
27
- if provider
28
- opts[:provider] = provider.to_sym
29
- opts[:assume_model_exists] = true
30
- end
31
- opts
32
- end
33
- private_class_method :chat_options
34
- end
35
- end
36
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- # Resolves the input limit from RubyLLM's authoritative model registry.
6
- class TokenBudgetResolver
7
- def resolve(model_config)
8
- config = model_config.to_h.transform_keys(&:to_s)
9
- model_name = config["model"]
10
- return nil unless model_name
11
-
12
- model = RubyLLM.models.find(model_name, provider: config["provider"])
13
- limit = model&.context_window
14
- return nil unless limit.is_a?(Integer) && limit.positive?
15
-
16
- Phronomy::LlmContextWindow::TokenBudget.new(max_input_tokens: limit)
17
- rescue RubyLLM::ModelNotFoundError
18
- nil
19
- end
20
- end
21
- end
22
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module ContextParts
6
- module Validators
7
- class FinalBudgetValidator
8
- def initialize(content_loader:)
9
- @content_loader = content_loader
10
- end
11
-
12
- def validate!(token_budget:, segments:, extra_values: [])
13
- return 0 unless token_budget
14
-
15
- estimated = Array(segments).sum do |segment|
16
- Phronomy::LlmContextWindow::TokenEstimator.estimate(
17
- @content_loader.call(segment.fetch(:content_ref))
18
- )
19
- end
20
- estimated += Array(extra_values).sum do |value|
21
- bytes = value.is_a?(String) ? value : Phronomy::CanonicalJSON.dump(value)
22
- Phronomy::LlmContextWindow::TokenEstimator.estimate(bytes)
23
- end
24
-
25
- limit = token_budget.effective_input_limit
26
- if estimated > limit
27
- raise Phronomy::ContextBudgetExceededError,
28
- "Canonical LLM input (estimated #{estimated} tokens) exceeds " \
29
- "available input budget (#{limit} tokens)"
30
- end
31
- estimated
32
- end
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,142 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module ContextPolicies
6
- class Default < ContextPolicy
7
- CONVERSATION_SHARE = 0.60
8
-
9
- def self.instance
10
- @instance ||= new.freeze
11
- end
12
-
13
- def call(input)
14
- unless input.is_a?(ContextPolicyInput)
15
- raise ArgumentError, "Default ContextPolicy expected ContextPolicyInput"
16
- end
17
-
18
- return all_context(input) unless input.token_budget
19
-
20
- limit = input.token_budget.effective_input_limit
21
- required_knowledge = input.knowledge.select(&:required?)
22
- optional_knowledge = input.knowledge.reject(&:required?)
23
- required_conversation = input.conversation.select { |group| group.any?(&:required?) }
24
- optional_conversation = input.conversation.reject { |group| group.any?(&:required?) }
25
-
26
- fixed_cost = item_cost(input.instruction) + item_cost(input.tools) +
27
- item_cost(required_knowledge) + group_cost(required_conversation)
28
- if fixed_cost > limit
29
- raise Phronomy::ContextBudgetExceededError,
30
- "Required Context (estimated #{fixed_cost} tokens) exceeds " \
31
- "available input budget (#{limit} tokens)"
32
- end
33
-
34
- remaining = limit - fixed_cost
35
- conversation_budget = (remaining * CONVERSATION_SHARE).floor
36
- knowledge_budget = remaining - conversation_budget
37
-
38
- selected_conversation, older_conversation, conversation_used =
39
- select_recent_suffix(optional_conversation, conversation_budget)
40
- selected_knowledge, remaining_knowledge, knowledge_used =
41
- select_stable_fit(optional_knowledge, knowledge_budget)
42
-
43
- reusable = remaining - conversation_used - knowledge_used
44
- if reusable.positive? && !older_conversation.empty?
45
- more_conversation, _, used =
46
- select_recent_suffix(older_conversation, reusable)
47
- selected_conversation = more_conversation + selected_conversation
48
- reusable -= used
49
- end
50
- if reusable.positive? && !remaining_knowledge.empty?
51
- more_knowledge, _remaining_knowledge, used =
52
- select_stable_fit(remaining_knowledge, reusable)
53
- selected_knowledge += more_knowledge
54
- reusable -= used
55
- end
56
-
57
- conversation = (required_conversation + selected_conversation)
58
- .uniq
59
- .sort_by { |group| group_sequence(group) }
60
- knowledge_ids = (required_knowledge + selected_knowledge).to_h { |item| [item.id, true] }
61
- knowledge = input.knowledge.select { |item| knowledge_ids[item.id] }
62
-
63
- plan(
64
- instruction: input.instruction,
65
- knowledge: knowledge,
66
- tools: input.tools,
67
- conversation: conversation,
68
- metadata: {
69
- "default_policy" => true,
70
- "estimated_fixed_tokens" => fixed_cost,
71
- "estimated_unused_tokens" => reusable
72
- }
73
- )
74
- end
75
-
76
- private
77
-
78
- def all_context(input)
79
- plan(
80
- instruction: input.instruction,
81
- knowledge: input.knowledge,
82
- tools: input.tools,
83
- conversation: input.conversation,
84
- metadata: {"default_policy" => true}
85
- )
86
- end
87
-
88
- def item_cost(items)
89
- Array(items).sum { |item| Integer(item.estimated_tokens || 0) }
90
- end
91
-
92
- def group_cost(groups)
93
- Array(groups).sum { |group| item_cost(group) }
94
- end
95
-
96
- def group_sequence(group)
97
- group.filter_map(&:sequence).min || 0
98
- end
99
-
100
- def select_recent_suffix(groups, budget)
101
- ordered = Array(groups).sort_by { |group| group_sequence(group) }
102
- selected = []
103
- remaining = Integer(budget)
104
- stop_index = -1
105
-
106
- (ordered.length - 1).downto(0) do |index|
107
- cost = item_cost(ordered[index])
108
- if cost > remaining
109
- stop_index = index
110
- break
111
- end
112
- selected.unshift(ordered[index])
113
- remaining -= cost
114
- end
115
-
116
- older = if stop_index >= 0
117
- ordered[0..stop_index]
118
- else
119
- []
120
- end
121
- [selected.freeze, older.freeze, Integer(budget) - remaining]
122
- end
123
-
124
- def select_stable_fit(items, budget)
125
- remaining = Integer(budget)
126
- selected = []
127
- unselected = []
128
- Array(items).each do |item|
129
- cost = Integer(item.estimated_tokens || 0)
130
- if cost <= remaining
131
- selected << item
132
- remaining -= cost
133
- else
134
- unselected << item
135
- end
136
- end
137
- [selected.freeze, unselected.freeze, Integer(budget) - remaining]
138
- end
139
- end
140
- end
141
- end
142
- end
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "state_machines"
4
-
5
- module Phronomy
6
- module Agent
7
- class PhaseMachineBuilder
8
- def initialize(entry_actions: {})
9
- @entry_actions = entry_actions
10
- end
11
-
12
- def build
13
- entry_actions = @entry_actions
14
- callback_builder = method(:build_entry_callback)
15
- condition_builder = method(:build_transition_condition)
16
- external_events = InvocationTransitions::EXTERNAL_EVENTS
17
- declared_states = InvocationTransitions::DECLARED_STATES
18
- entry_point = InvocationTransitions::ENTRY_POINT
19
-
20
- Class.new do
21
- attr_accessor :context, :current_event
22
-
23
- state_machine :phase, initial: entry_point do
24
- declared_states.each { |state_name| state state_name }
25
-
26
- event :state_completed do
27
- transition idle: :filtering_input
28
-
29
- transition filtering_input: :building_context,
30
- if: ->(machine) { machine.context&.input_passed? }
31
- transition filtering_input: :blocked,
32
- if: ->(machine) { machine.context&.input_blocked? }
33
-
34
- transition building_context: :calling_llm
35
- transition starting_tools: :evaluating_tools
36
-
37
- transition evaluating_tools: :failed,
38
- if: ->(machine) { machine.context&.tool_batch_failed? }
39
- transition evaluating_tools: :blocked,
40
- if: ->(machine) { machine.context&.tool_batch_rejected? }
41
- transition evaluating_tools: :recording_tool_results,
42
- if: ->(machine) { machine.context&.tool_batch_completed? }
43
- transition evaluating_tools: :suspended,
44
- if: ->(machine) { machine.context&.approval_required? }
45
- transition evaluating_tools: :dispatching_tools,
46
- if: ->(machine) { machine.context&.ready_to_dispatch? }
47
- transition evaluating_tools: :waiting_for_tools
48
-
49
- transition recording_tool_results: :calling_llm
50
-
51
- transition output_filtering: :completed,
52
- if: ->(machine) { machine.context&.output_passed? }
53
- transition output_filtering: :blocked,
54
- if: ->(machine) { machine.context&.output_blocked? }
55
- end
56
-
57
- external_events.each do |event_name, transitions|
58
- event event_name do
59
- transitions.each do |definition|
60
- transition(
61
- definition[:from] => definition[:to],
62
- :if => condition_builder.call(definition)
63
- )
64
- end
65
- end
66
- end
67
-
68
- entry_actions.each do |state_name, callables|
69
- callables.each do |callable|
70
- after_transition(
71
- to: state_name,
72
- do: callback_builder.call(callable, state_name)
73
- )
74
- end
75
- end
76
- end
77
- end
78
- end
79
-
80
- private
81
-
82
- def build_transition_condition(definition)
83
- guard = definition[:guard]
84
- ->(machine) {
85
- guard.nil? || (!machine.context.nil? && guard.call(machine.context))
86
- }
87
- end
88
-
89
- def build_entry_callback(callable, state_name)
90
- ->(machine) {
91
- result = callable.call(machine.context)
92
- if result.is_a?(Phronomy::TaskResult)
93
- raise Phronomy::InvalidAsyncEntryActionError,
94
- "Agent entry action for #{state_name.inspect} returned Phronomy::TaskResult"
95
- end
96
- machine.context = result if result.respond_to?(:set_graph_metadata)
97
- }
98
- end
99
- end
100
- end
101
- end
@@ -1,106 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- # Immutable, Phronomy-owned snapshot of one completed Provider assistant output.
6
- ProviderCallOutcome = Data.define(:role, :content, :tool_calls, :usage, :metadata) do
7
- def self.capture(message)
8
- return if message.nil?
9
- return message if message.is_a?(self)
10
-
11
- calls = if message.respond_to?(:tool_calls) && message.tool_calls
12
- source = message.tool_calls.respond_to?(:values) ? message.tool_calls.values : Array(message.tool_calls)
13
- source.map { |call| normalize(tool_call_hash(call)) }
14
- else
15
- []
16
- end
17
- usage = if message.respond_to?(:tokens) && message.tokens
18
- normalize(message.tokens.respond_to?(:to_h) ? message.tokens.to_h : message.tokens)
19
- else
20
- {}
21
- end
22
- metadata = {}
23
- metadata["model_id"] = message.model.to_s if message.respond_to?(:model) && message.model
24
-
25
- new(
26
- role: message.respond_to?(:role) ? message.role : :assistant,
27
- content: normalize(message.respond_to?(:content) ? message.content : nil),
28
- tool_calls: calls,
29
- usage: usage,
30
- metadata: metadata
31
- )
32
- end
33
-
34
- def self.from_h(hash)
35
- source = hash.to_h { |key, value| [key.to_s, value] }
36
- new(
37
- role: source.fetch("role", "assistant"),
38
- content: source["content"],
39
- tool_calls: source.fetch("tool_calls", []),
40
- usage: source.fetch("usage", {}),
41
- metadata: source.fetch("metadata", {})
42
- )
43
- end
44
-
45
- def self.tool_call_hash(call)
46
- return call.to_h if call.respond_to?(:to_h)
47
-
48
- {
49
- "id" => call.respond_to?(:id) ? call.id : nil,
50
- "name" => call.respond_to?(:name) ? call.name : nil,
51
- "arguments" => call.respond_to?(:arguments) ? call.arguments : {}
52
- }.compact
53
- end
54
- private_class_method :tool_call_hash
55
-
56
- def self.normalize(value)
57
- case value
58
- when Hash
59
- value.to_h { |key, child| [key.to_s, normalize(child)] }
60
- when Array
61
- value.map { |child| normalize(child) }
62
- when Symbol
63
- value.to_s
64
- when String, Integer, Float, TrueClass, FalseClass, NilClass
65
- value
66
- else
67
- value.respond_to?(:to_h) ? normalize(value.to_h) : value.to_s
68
- end
69
- end
70
- private_class_method :normalize
71
-
72
- def initialize(role:, content:, tool_calls:, usage: {}, metadata: {})
73
- super(
74
- role: role&.to_sym,
75
- content: Phronomy::Values::Immutable.copy(content),
76
- tool_calls: Phronomy::Values::Immutable.copy(Array(tool_calls)),
77
- usage: Phronomy::Values::Immutable.copy(usage || {}),
78
- metadata: Phronomy::Values::Immutable.copy(metadata || {})
79
- )
80
- Phronomy::Values::Immutable.validate_canonical_json!(content, label: "Provider assistant content")
81
- Phronomy::Values::Immutable.validate_canonical_json!(tool_calls, label: "Provider Tool Calls")
82
- Phronomy::Values::Immutable.validate_canonical_json!(usage, label: "Provider usage")
83
- Phronomy::Values::Immutable.validate_canonical_json!(metadata, label: "Provider outcome metadata")
84
- freeze
85
- end
86
-
87
- def to_h
88
- {
89
- "role" => role&.to_s,
90
- "content" => content,
91
- "tool_calls" => tool_calls,
92
- "usage" => usage,
93
- "metadata" => metadata
94
- }.compact
95
- end
96
-
97
- def content_present?
98
- !content.nil? && !(content.respond_to?(:empty?) && content.empty?)
99
- end
100
-
101
- def tool_call?
102
- !tool_calls.empty?
103
- end
104
- end
105
- end
106
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- # Selects Handoff persistence; execution ownership and delivery remain in
6
- # ExecutionCoordinator. This is not a public subclass extension contract.
7
- class HandoffExecutionCoordinator < Phronomy::Agent::ExecutionCoordinator
8
- private
9
-
10
- def build_outcome_committer
11
- HandoffOutcomeCommitter.new(agent: @agent, persistence: @agent.persistence)
12
- end
13
- end
14
- end
15
- end
@@ -1,131 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest"
4
-
5
- module Phronomy
6
- module Agent
7
- # Adds atomic Source transfer while sharing ordinary result persistence.
8
- # Outcome selection deliberately retains Handoff-specific precedence.
9
- # @api private
10
- class HandoffOutcomeCommitter < ExecutionOutcomeCommitter
11
- # @api private
12
- def commit_outcome(operation)
13
- view = operation.terminal_view
14
- if view.callback_failure
15
- return commit_failed_outcome(
16
- operation,
17
- view.callback_failure.to_stream_callback_error
18
- )
19
- end
20
- error = view.source_error || view.block_error || view.invocation_error
21
- raise error if error.is_a?(Phronomy::ExecutionRehydrationRequiredError)
22
- return commit_failed_outcome(operation, error) if error
23
- return commit_suspended(operation) if view.phase == :suspended
24
-
25
- raise view.block_error if view.input_blocked || view.output_blocked
26
- raise view.invocation_error if view.invocation_error
27
-
28
- return commit_handed_off(operation) if view.handoff
29
-
30
- commit_completed(operation)
31
- rescue => caught
32
- reconcile_terminal_error(operation, caught)
33
- end
34
-
35
- private
36
-
37
- def commit_handed_off(operation)
38
- handed_off = next_root = appended = nil
39
- @persistence.transaction do |tx|
40
- routing = validated_source_routing(tx, operation.execution)
41
- context_ref = persist_handoff_context(tx, operation)
42
- target_id = transfer_routing(tx, operation, routing, context_ref)
43
- records, handed_off = encode_handoff(tx, operation, target_id, context_ref)
44
- appended = append_journal(tx, operation.root, records)
45
- save_execution(tx, operation.execution, handed_off)
46
- next_root = save_terminal_root(tx, operation.root, appended)
47
- end
48
- Outcome.new(type: :handed_off, execution: handed_off, root: next_root,
49
- appended_records: Array(appended).freeze,
50
- result: result_base(handed_off, next_root).freeze,
51
- error: nil, approval_request: nil)
52
- end
53
-
54
- def validated_source_routing(tx, current)
55
- coordination = current.metadata.fetch("coordination")
56
- routing = tx.handoff_states.load(coordination.fetch("main_agent_id"))
57
- unless routing && routing.active_agent_id == @agent.agent_id && routing.handoff_revision == coordination.fetch("handoff_revision")
58
- raise Phronomy::Storage::ConflictError, "Handoff routing changed before Source transfer"
59
- end
60
- if Array(routing.metadata["cancelled_execution_ids"]).include?(current.execution_id)
61
- raise Phronomy::CancellationError, "Handoff Source turn was cancelled"
62
- end
63
- routing
64
- end
65
-
66
- def persist_handoff_context(tx, operation)
67
- manifest = SavedContextReader.manifest_from_ref(@agent, operation.execution.metadata.fetch("manifest_ref"))
68
- context = HandoffProjection.new.build_terminal(view: operation.terminal_view.handoff,
69
- manifest: manifest, persistence: tx, source_agent_id: @agent.agent_id)
70
- tx.contents.put_json(context.to_h)
71
- end
72
-
73
- def transfer_routing(tx, operation, routing, context_ref)
74
- request = operation.terminal_view.handoff
75
- target_id = "handoff-target-#{Digest::SHA256.hexdigest([operation.execution.execution_id, request.target_agent_id].join("\0"))}"
76
- target_root = tx.agents.load(request.target_agent_id)
77
- target_definition = {"id" => target_root.agent_definition_id, "version" => target_root.agent_definition_version}
78
- transfer = routing.with(active_agent_id: request.target_agent_id,
79
- active_handoff_context_ref: context_ref, phase: "target_pending",
80
- pending_source_execution_id: operation.execution.execution_id, pending_target_execution_id: target_id,
81
- metadata: routing.metadata.merge("target_definition" => target_definition))
82
- tx.handoff_states.save(routing.main_agent_id, expected_revision: routing.handoff_revision, state: transfer)
83
- target_id
84
- end
85
-
86
- def encode_handoff(tx, operation, target_id, context_ref)
87
- current = operation.execution
88
- request = operation.terminal_view.handoff
89
- encoded_records, call_records = encode_runtime_records(tx, operation, context_candidate: false)
90
- audit_record = encode_handoff_audit(tx, operation)
91
- records = current.working_records + encoded_records + [audit_record]
92
- handed_off = current.with(
93
- status: :handed_off,
94
- phase: :handed_off,
95
- working_records: [],
96
- llm_calls: current.llm_calls + call_records,
97
- approval_request: nil,
98
- terminal_reason: "handed_off",
99
- metadata: current.metadata.merge("handoff_target_agent_id" => request.target_agent_id,
100
- "handoff_target_execution_id" => target_id, "handoff_context_ref" => context_ref)
101
- )
102
- [records, handed_off]
103
- end
104
-
105
- def encode_handoff_audit(tx, operation)
106
- request = operation.terminal_view.handoff
107
- audit_ref = tx.contents.put_json(
108
- "target_agent_id" => request.target_agent_id,
109
- "responsibility" => request.responsibility,
110
- "selection_intent" => request.selection_intent.to_h do |category, included|
111
- [category.to_s, included]
112
- end
113
- )
114
- JournalRecord.new(
115
- agent_id: @agent.agent_id,
116
- execution_id: operation.execution.execution_id,
117
- llm_call_id: request.llm_call_id,
118
- kind: :execution_handed_off,
119
- channel: :audit,
120
- content_ref: audit_ref,
121
- context_generation: operation.root.transcript_generation,
122
- context_candidate: false,
123
- metadata: {
124
- "target_agent_id" => request.target_agent_id,
125
- "handoff_tool_call_id" => request.tool_call_id
126
- }.compact
127
- )
128
- end
129
- end
130
- end
131
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- # Typed private control request produced from one intercepted Handoff capability.
6
- # @api private
7
- HandoffRequest = Data.define(
8
- :handoff, :responsibility, :selection_intent, :llm_call_id, :tool_call_id
9
- ) do
10
- def initialize(handoff:, responsibility:, selection_intent:, llm_call_id: nil, tool_call_id: nil)
11
- responsibility = responsibility.to_s.strip
12
- raise ArgumentError, "Handoff responsibility must not be empty" if responsibility.empty?
13
- unless handoff.is_a?(Handoff)
14
- raise ArgumentError, "handoff must be a Phronomy::Agent::Handoff"
15
- end
16
-
17
- normalized = handoff.policy.selectable_categories.to_h do |category|
18
- raw = selection_intent.fetch(category) do
19
- handoff.policy.default_include?(category)
20
- end
21
- unless raw == true || raw == false
22
- raise ArgumentError,
23
- "Handoff selection for #{category.inspect} must be boolean"
24
- end
25
- [category, raw]
26
- end.freeze
27
-
28
- unknown = selection_intent.keys.map(&:to_sym) - handoff.policy.selectable_categories
29
- unless unknown.empty?
30
- raise ArgumentError,
31
- "Handoff selection attempts to override non-selectable categories: #{unknown.inspect}"
32
- end
33
-
34
- super(
35
- handoff: handoff,
36
- responsibility: responsibility.freeze,
37
- selection_intent: normalized,
38
- llm_call_id: llm_call_id&.to_s&.freeze,
39
- tool_call_id: tool_call_id&.to_s&.freeze
40
- )
41
- freeze
42
- end
43
- end
44
- end
45
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../../common/error"
4
-
5
- module Phronomy
6
- class HandoffError < Error; end
7
- end