phronomy 0.26.0 → 0.28.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 (557) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +92 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +13 -3
  6. data/VERIFY.sh +27 -27
  7. data/benchmark/bench_agent_invoke.rb +26 -47
  8. data/benchmark/bench_context_assembler.rb +17 -15
  9. data/benchmark/bench_regression.rb +1 -1
  10. data/benchmark/bench_tool_schema.rb +1 -1
  11. data/docs/architecture/agent-chat-and-state-ownership.md +153 -0
  12. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  13. data/docs/architecture/agent-context.md +4 -2
  14. data/docs/architecture/agent-transition-ownership.md +13 -0
  15. data/docs/architecture/backend-groups.md +152 -0
  16. data/docs/architecture/before-llm-input.md +9 -3
  17. data/docs/architecture/context-management.md +31 -1
  18. data/docs/architecture/context-preparation-steps.md +90 -0
  19. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  20. data/docs/architecture/execution-abstraction-review.md +88 -0
  21. data/docs/architecture/execution-boundaries.md +28 -0
  22. data/docs/architecture/execution-metadata-and-values.md +86 -0
  23. data/docs/architecture/generator-verifier-ownership.md +111 -0
  24. data/docs/architecture/multi-agent-handoff.md +11 -4
  25. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  26. data/docs/architecture/persistence.md +45 -4
  27. data/docs/architecture/r8-unit1.md +104 -0
  28. data/docs/architecture/r8-unit10.md +72 -0
  29. data/docs/architecture/r8-unit11.md +71 -0
  30. data/docs/architecture/r8-unit12.md +67 -0
  31. data/docs/architecture/r8-unit13.md +61 -0
  32. data/docs/architecture/r8-unit14.md +72 -0
  33. data/docs/architecture/r8-unit15.md +65 -0
  34. data/docs/architecture/r8-unit16.md +54 -0
  35. data/docs/architecture/r8-unit17.md +73 -0
  36. data/docs/architecture/r8-unit18.md +92 -0
  37. data/docs/architecture/r8-unit19.md +61 -0
  38. data/docs/architecture/r8-unit2.md +55 -0
  39. data/docs/architecture/r8-unit20.md +51 -0
  40. data/docs/architecture/r8-unit21.md +79 -0
  41. data/docs/architecture/r8-unit22.md +74 -0
  42. data/docs/architecture/r8-unit23.md +81 -0
  43. data/docs/architecture/r8-unit3.md +130 -0
  44. data/docs/architecture/r8-unit4.md +79 -0
  45. data/docs/architecture/r8-unit5.md +115 -0
  46. data/docs/architecture/r8-unit6.md +71 -0
  47. data/docs/architecture/r8-unit7.md +70 -0
  48. data/docs/architecture/r8-unit8.md +78 -0
  49. data/docs/architecture/r8-unit9.md +73 -0
  50. data/docs/architecture/refactoring-closure.md +343 -0
  51. data/docs/architecture/remaining-refactoring-plan.md +390 -0
  52. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  53. data/docs/architecture/storage-execution-boundaries.md +54 -0
  54. data/docs/architecture/tool-schema-recording-gap.md +56 -0
  55. data/docs/architecture/tracing.md +2 -2
  56. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  57. data/docs/architecture.md +222 -0
  58. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  59. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  60. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  61. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  62. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  63. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  64. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +7 -0
  65. data/docs/decisions/031-durable-multi-agent-coordination.md +7 -0
  66. data/docs/decisions/032-storage-backend-composition.md +85 -0
  67. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  68. data/docs/decisions/034-handoff-runner-coordination-ownership.md +78 -0
  69. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  70. data/docs/decisions/036-context-contract-ownership.md +86 -0
  71. data/docs/decisions/037-common-definition-ownership.md +62 -0
  72. data/docs/decisions/038-responsibility-based-source-layout.md +145 -0
  73. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +82 -0
  74. data/docs/decisions/040-configuration-default-composition.md +105 -0
  75. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  76. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  77. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  78. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  79. data/docs/decisions/045-worker-input-restriction-ownership.md +104 -0
  80. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  81. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  82. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  83. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  84. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  85. data/docs/decisions/051-execution-outcome-worker-ownership.md +153 -0
  86. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  87. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  88. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  89. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  90. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  91. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  92. data/docs/decisions/058-neutral-storage-primitives.md +83 -0
  93. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  94. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  95. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  96. data/docs/decisions/062-content-store-service.md +52 -0
  97. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  98. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  99. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  100. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  101. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  102. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  103. data/docs/decisions/README.md +52 -6
  104. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  105. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  106. data/docs/execution-boundaries-preview.md +45 -0
  107. data/docs/features.md +16 -5
  108. data/docs/getting-started.md +11 -8
  109. data/docs/migrations/0.15.md +5 -0
  110. data/docs/migrations/0.22.md +2 -2
  111. data/docs/migrations/durable-semantic-coordination-v2.md +8 -0
  112. data/docs/migrations/handoff-runner-multi-agent.md +37 -0
  113. data/docs/migrations/neutral-storage-spi.md +43 -0
  114. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  115. data/docs/migrations/persistence-failure-contracts.md +67 -0
  116. data/docs/migrations/r8-unit10.md +36 -0
  117. data/docs/migrations/r8-unit11.md +33 -0
  118. data/docs/migrations/r8-unit12.md +31 -0
  119. data/docs/migrations/r8-unit13.md +36 -0
  120. data/docs/migrations/r8-unit14.md +31 -0
  121. data/docs/migrations/r8-unit15.md +31 -0
  122. data/docs/migrations/r8-unit16.md +26 -0
  123. data/docs/migrations/r8-unit17.md +28 -0
  124. data/docs/migrations/r8-unit18.md +29 -0
  125. data/docs/migrations/r8-unit19.md +28 -0
  126. data/docs/migrations/r8-unit20.md +24 -0
  127. data/docs/migrations/r8-unit21.md +28 -0
  128. data/docs/migrations/r8-unit22.md +29 -0
  129. data/docs/migrations/r8-unit23.md +20 -0
  130. data/docs/migrations/r8-unit4.md +67 -0
  131. data/docs/migrations/r8-unit5.md +75 -0
  132. data/docs/migrations/r8-unit6.md +109 -0
  133. data/docs/migrations/r8-unit7.md +31 -0
  134. data/docs/migrations/r8-unit8.md +33 -0
  135. data/docs/migrations/r8-unit9.md +28 -0
  136. data/docs/migrations/shared-state-multi-agent.md +44 -0
  137. data/docs/migrations/storage-async-client.md +103 -0
  138. data/docs/migrations/storage-backend-composition.md +117 -0
  139. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  140. data/docs/migrations/vector-async-clients.md +110 -0
  141. data/docs/persistence-backends.md +190 -215
  142. data/docs/runtime-and-concurrency.md +142 -32
  143. data/lib/phronomy/agent/admission.rb +78 -0
  144. data/lib/phronomy/{agent_already_exists_error.rb → agent/agent_already_exists_error.rb} +2 -0
  145. data/lib/phronomy/{agent_busy_error.rb → agent/agent_busy_error.rb} +2 -0
  146. data/lib/phronomy/{agent_purged_error.rb → agent/agent_purged_error.rb} +2 -0
  147. data/lib/phronomy/agent/api/agent.rb +17 -0
  148. data/lib/phronomy/{execution_rehydration_required_error.rb → agent/api/execution_rehydration_required_error.rb} +2 -0
  149. data/lib/phronomy/agent/async_event_api.rb +38 -33
  150. data/lib/phronomy/agent/base.rb +106 -294
  151. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  152. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  153. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  154. data/lib/phronomy/agent/context_importer.rb +2 -2
  155. data/lib/phronomy/agent/{context_assembler.rb → context_preparation.rb} +157 -288
  156. data/lib/phronomy/agent/control_request.rb +21 -0
  157. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  158. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +32 -63
  159. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  160. data/lib/phronomy/agent/execution/dispatch_preparation.rb +195 -0
  161. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +43 -23
  162. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +8 -5
  163. data/lib/phronomy/agent/execution/execution_coordinator.rb +1744 -0
  164. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  165. data/lib/phronomy/agent/execution/execution_metadata.rb +54 -0
  166. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +288 -0
  167. data/lib/phronomy/agent/execution/execution_registry.rb +463 -0
  168. data/lib/phronomy/agent/execution/execution_session_runner.rb +113 -0
  169. data/lib/phronomy/agent/execution/initial_preparation.rb +323 -0
  170. data/lib/phronomy/agent/execution/invocation_actions.rb +368 -0
  171. data/lib/phronomy/agent/execution/invocation_transitions.rb +124 -0
  172. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  173. data/lib/phronomy/agent/execution_change.rb +161 -0
  174. data/lib/phronomy/agent/execution_environment.rb +36 -0
  175. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  176. data/lib/phronomy/agent/execution_observation.rb +35 -0
  177. data/lib/phronomy/agent/filterable.rb +182 -0
  178. data/lib/phronomy/agent/{handoff_context.rb → handoff/transfer_context.rb} +6 -6
  179. data/lib/phronomy/agent/{handoff_projection.rb → handoff/transfer_projection.rb} +14 -33
  180. data/lib/phronomy/agent/handoff_error.rb +7 -0
  181. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  182. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  183. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  184. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  185. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  186. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  187. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -15
  188. data/lib/phronomy/agent/persistence/agent_repository.rb +69 -0
  189. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  190. data/lib/phronomy/agent/persistence/codec.rb +338 -0
  191. data/lib/phronomy/agent/persistence/execution_repository.rb +124 -0
  192. data/lib/phronomy/agent/persistence/journal_repository.rb +63 -0
  193. data/lib/phronomy/agent/persistence/records.rb +33 -0
  194. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  195. data/lib/phronomy/agent/persistence/storage_schema.rb +29 -0
  196. data/lib/phronomy/agent/persistence/watermark.rb +29 -0
  197. data/lib/phronomy/agent/recovery/invocation_restorer.rb +124 -0
  198. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +28 -51
  199. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +50 -55
  200. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +38 -43
  201. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +26 -23
  202. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  203. data/lib/phronomy/agent/recovery/recovery_support.rb +224 -0
  204. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  205. data/lib/phronomy/agent/retention.rb +17 -0
  206. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  207. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  208. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  209. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  210. data/lib/phronomy/agent/{ruby_llm_materializer.rb → runtime_input.rb} +19 -39
  211. data/lib/phronomy/agent/saved_context_reader.rb +55 -0
  212. data/lib/phronomy/agent/settings.rb +47 -0
  213. data/lib/phronomy/agent/state_writer.rb +166 -0
  214. data/lib/phronomy/agent/store.rb +253 -0
  215. data/lib/phronomy/{stream_callback_error.rb → agent/stream_callback_error.rb} +2 -0
  216. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  217. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  218. data/lib/phronomy/agent/tool_execution/tool_binding.rb +30 -0
  219. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +114 -248
  220. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  221. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  222. data/lib/phronomy/common/configuration_error.rb +7 -0
  223. data/lib/phronomy/common/error.rb +5 -0
  224. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  225. data/lib/phronomy/common/values/serializable.rb +32 -0
  226. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  227. data/lib/phronomy/content_store/storage_schema.rb +12 -0
  228. data/lib/phronomy/content_store/stored_contents.rb +44 -0
  229. data/lib/phronomy/context/assembly.rb +202 -0
  230. data/lib/phronomy/{context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +5 -1
  231. data/lib/phronomy/context/candidate.rb +51 -0
  232. data/lib/phronomy/context/constraint.rb +47 -0
  233. data/lib/phronomy/{agent → context}/context_plan.rb +2 -2
  234. data/lib/phronomy/{agent → context}/context_plan_validator.rb +3 -3
  235. data/lib/phronomy/{agent → context}/context_policy.rb +1 -1
  236. data/lib/phronomy/{agent → context}/context_policy_input.rb +7 -7
  237. data/lib/phronomy/{agent → context}/context_policy_input_builder.rb +1 -1
  238. data/lib/phronomy/context/default_policy.rb +140 -0
  239. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  240. data/lib/phronomy/{agent → context}/llm_input_build_context.rb +2 -2
  241. data/lib/phronomy/{agent → context}/llm_input_manifest.rb +21 -21
  242. data/lib/phronomy/{agent → context}/llm_input_patch.rb +3 -3
  243. data/lib/phronomy/context/manifest_error.rb +10 -0
  244. data/lib/phronomy/context/prompt_template.rb +98 -0
  245. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  246. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  247. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  248. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  249. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  250. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  251. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  252. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  253. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  254. data/lib/phronomy/embeddings/base.rb +43 -0
  255. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  256. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  257. data/lib/phronomy/engine/concurrency/offload_pool.rb +39 -20
  258. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  259. data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +1 -1
  260. data/lib/phronomy/engine/event_loop.rb +164 -630
  261. data/lib/phronomy/engine/execution_binding.rb +23 -0
  262. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  263. data/lib/phronomy/engine/fsm_protocol.rb +9 -0
  264. data/lib/phronomy/engine/fsm_session.rb +37 -30
  265. data/lib/phronomy/engine/metrics.rb +28 -0
  266. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  267. data/lib/phronomy/engine/runtime.rb +104 -95
  268. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  269. data/lib/phronomy/execution/async_operation.rb +41 -0
  270. data/lib/phronomy/execution/backpressure_error.rb +7 -0
  271. data/lib/phronomy/{blocking.rb → execution/blocking.rb} +2 -2
  272. data/lib/phronomy/execution/cancellation_error.rb +7 -0
  273. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +5 -1
  274. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  275. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  276. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  277. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  278. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  279. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  280. data/lib/phronomy/execution/concurrency/worker_input_restricted.rb +14 -0
  281. data/lib/phronomy/execution/event_loop_reentrancy_error.rb +8 -0
  282. data/lib/phronomy/{execution.rb → execution/execution.rb} +57 -30
  283. data/lib/phronomy/{execution_cancellation_error.rb → execution/execution_cancellation_error.rb} +2 -0
  284. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  285. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  286. data/lib/phronomy/{execution_timeout_error.rb → execution/execution_timeout_error.rb} +2 -0
  287. data/lib/phronomy/{invalid_async_entry_action_error.rb → execution/invalid_async_entry_action_error.rb} +2 -0
  288. data/lib/phronomy/{invalid_async_transition_action_error.rb → execution/invalid_async_transition_action_error.rb} +2 -0
  289. data/lib/phronomy/{invalid_async_workflow_action_error.rb → execution/invalid_async_workflow_action_error.rb} +2 -0
  290. data/lib/phronomy/{invocation_context.rb → execution/invocation_context.rb} +0 -15
  291. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  292. data/lib/phronomy/execution/pool_shutdown_error.rb +7 -0
  293. data/lib/phronomy/execution/recursion_limit_error.rb +7 -0
  294. data/lib/phronomy/execution/runnable.rb +24 -0
  295. data/lib/phronomy/execution/runtime_shutdown_error.rb +7 -0
  296. data/lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb +7 -0
  297. data/lib/phronomy/execution/scheduler_reentrancy_error.rb +9 -0
  298. data/lib/phronomy/{engine → execution}/task_result.rb +7 -3
  299. data/lib/phronomy/execution/timeout_error.rb +7 -0
  300. data/lib/phronomy/execution/wait_policy.rb +24 -0
  301. data/lib/phronomy/filter/filter_block_error.rb +14 -0
  302. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  303. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  304. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  305. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  306. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  307. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  308. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  309. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  310. data/lib/phronomy/llm_adapter/authentication_error.rb +9 -0
  311. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  312. data/lib/phronomy/llm_adapter/base.rb +49 -68
  313. data/lib/phronomy/llm_adapter/context_length_error.rb +9 -0
  314. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  315. data/lib/phronomy/llm_adapter/message.rb +62 -0
  316. data/lib/phronomy/llm_adapter/rate_limit_error.rb +9 -0
  317. data/lib/phronomy/llm_adapter/request.rb +79 -0
  318. data/lib/phronomy/llm_adapter/response.rb +36 -0
  319. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  320. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  321. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  322. data/lib/phronomy/llm_adapter.rb +3 -16
  323. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  324. data/lib/phronomy/multi_agent/admission_registry.rb +15 -2
  325. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  326. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  327. data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
  328. data/lib/phronomy/{agent → multi_agent}/handoff_capability_factory.rb +21 -2
  329. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  330. data/lib/phronomy/{agent → multi_agent}/handoff_policy.rb +1 -1
  331. data/lib/phronomy/multi_agent/handoff_runner.rb +156 -0
  332. data/lib/phronomy/{agent → multi_agent}/handoff_state.rb +2 -2
  333. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  334. data/lib/phronomy/multi_agent/persistence/codec.rb +75 -0
  335. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  336. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  337. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  338. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +124 -0
  339. data/lib/phronomy/multi_agent/persistence/team_repository.rb +69 -0
  340. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  341. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  342. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  343. data/lib/phronomy/multi_agent/store.rb +156 -0
  344. data/lib/phronomy/multi_agent/team_coordinator.rb +87 -75
  345. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  346. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +5 -5
  347. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  348. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  349. data/lib/phronomy/persistence/content_repository.rb +26 -0
  350. data/lib/phronomy/persistence/errors.rb +16 -0
  351. data/lib/phronomy/persistence/migration/initial_format_migration.rb +21 -21
  352. data/lib/phronomy/persistence/persistence.rb +23 -0
  353. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  354. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  355. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  356. data/lib/phronomy/persistence/transaction.rb +86 -0
  357. data/lib/phronomy/persistence_composition/storage_schema.rb +29 -0
  358. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  359. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  360. data/lib/phronomy/runtime_composition/agent_defaults.rb +11 -0
  361. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  362. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  363. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  364. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  365. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  366. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  367. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  368. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  369. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  370. data/lib/phronomy/storage/async/async_client.rb +60 -0
  371. data/lib/phronomy/storage/backend.rb +107 -0
  372. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  373. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  374. data/lib/phronomy/storage/blobs.rb +31 -0
  375. data/lib/phronomy/storage/condition.rb +25 -0
  376. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  377. data/lib/phronomy/storage/conflict_error.rb +9 -0
  378. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  379. data/lib/phronomy/storage/entry.rb +33 -0
  380. data/lib/phronomy/storage/guard_ref.rb +13 -0
  381. data/lib/phronomy/storage/not_found_error.rb +9 -0
  382. data/lib/phronomy/storage/record_codec.rb +177 -0
  383. data/lib/phronomy/storage/records.rb +61 -0
  384. data/lib/phronomy/storage/resource.rb +126 -0
  385. data/lib/phronomy/storage/scope.rb +25 -0
  386. data/lib/phronomy/storage/serialization_error.rb +9 -0
  387. data/lib/phronomy/storage/streams.rb +44 -0
  388. data/lib/phronomy/storage/transaction_error.rb +10 -0
  389. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  390. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  391. data/lib/phronomy/storage/validation.rb +53 -0
  392. data/lib/phronomy/storage/view.rb +131 -0
  393. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  394. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  395. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  396. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  397. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +54 -48
  398. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +1 -1
  399. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +1 -1
  400. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  401. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +52 -46
  402. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  403. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +124 -0
  404. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  405. data/lib/phronomy/tool/authorization.rb +77 -0
  406. data/lib/phronomy/tool/base.rb +549 -9
  407. data/lib/phronomy/tool/call_request.rb +40 -0
  408. data/lib/phronomy/{agent/tool_definition_set.rb → tool/definition_set.rb} +16 -12
  409. data/lib/phronomy/tool/operation.rb +88 -0
  410. data/lib/phronomy/tool/schema.rb +214 -0
  411. data/lib/phronomy/tool/settings.rb +31 -0
  412. data/lib/phronomy/tool/tool_error.rb +7 -0
  413. data/lib/phronomy/{agent → tool}/tool_executor.rb +10 -7
  414. data/lib/phronomy/tools/agent.rb +22 -90
  415. data/lib/phronomy/tools/mcp.rb +21 -14
  416. data/lib/phronomy/tools/vector_search.rb +4 -4
  417. data/lib/phronomy/tracing/automatic.rb +11 -6
  418. data/lib/phronomy/tracing/base.rb +1 -1
  419. data/lib/phronomy/tracing/observation.rb +37 -0
  420. data/lib/phronomy/tracing/settings.rb +36 -0
  421. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  422. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  423. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  424. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  425. data/lib/phronomy/vector_store/base.rb +118 -49
  426. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  427. data/lib/phronomy/vector_store.rb +2 -17
  428. data/lib/phronomy/version.rb +1 -1
  429. data/lib/phronomy/workflow/completion.rb +11 -0
  430. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
  431. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  432. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +5 -1
  433. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  434. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  435. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +186 -0
  436. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +145 -115
  437. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  438. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  439. data/lib/phronomy/workflow/persistence/state_repository.rb +63 -0
  440. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  441. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  442. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  443. data/lib/phronomy/workflow/runtime_binding/workflow_terminal_policy.rb +40 -0
  444. data/lib/phronomy.rb +97 -98
  445. data/scripts/api_snapshot.rb +36 -16
  446. data/scripts/storage_spi_snapshot.rb +36 -0
  447. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  448. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  449. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  450. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  451. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  452. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  453. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  454. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  455. data/sig/phronomy/agent/admission.rbs +19 -0
  456. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  457. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  458. data/sig/phronomy/agent/store.rbs +73 -0
  459. data/sig/phronomy/agent.rbs +14 -139
  460. data/sig/phronomy/common.rbs +7 -0
  461. data/sig/phronomy/content_store/contracts.rbs +13 -0
  462. data/sig/phronomy/context.rbs +152 -0
  463. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  464. data/sig/phronomy/embeddings/base.rbs +17 -0
  465. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  466. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  467. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  468. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  469. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  470. data/sig/phronomy/engine/observability.rbs +13 -0
  471. data/sig/phronomy/execution/async_operation.rbs +7 -0
  472. data/sig/phronomy/execution/blocking.rbs +5 -0
  473. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  474. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  475. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  476. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  477. data/sig/phronomy/execution/errors.rbs +7 -0
  478. data/sig/phronomy/execution/execution.rbs +41 -0
  479. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  480. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  481. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  482. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  483. data/sig/phronomy/execution/task_result.rbs +29 -0
  484. data/sig/phronomy/extensions.rbs +0 -21
  485. data/sig/phronomy/handoff.rbs +12 -4
  486. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  487. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  488. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  489. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  490. data/sig/phronomy/llm_context_window.rbs +10 -0
  491. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  492. data/sig/phronomy/multi_agent/store.rbs +54 -0
  493. data/sig/phronomy/multi_agent.rbs +24 -3
  494. data/sig/phronomy/persistence/errors.rbs +18 -0
  495. data/sig/phronomy/persistence/migration.rbs +13 -0
  496. data/sig/phronomy/persistence/transaction.rbs +19 -0
  497. data/sig/phronomy/persistence.rbs +3 -185
  498. data/sig/phronomy/persistence_composition.rbs +19 -0
  499. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  500. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  501. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  502. data/sig/phronomy/storage/contracts.rbs +160 -0
  503. data/sig/phronomy/tool.rbs +58 -5
  504. data/sig/phronomy/tracing/observation.rbs +7 -0
  505. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  506. data/sig/phronomy/vector_store/base.rbs +23 -0
  507. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  508. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  509. data/sig/phronomy/workflow/store.rbs +8 -0
  510. data/sig/phronomy/workflow.rbs +1 -1
  511. data/sig/phronomy.rbs +0 -49
  512. metadata +462 -130
  513. data/lib/phronomy/agent/agent_invocation_session_builder.rb +0 -584
  514. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  515. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  516. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  517. data/lib/phronomy/agent/context/capability/base.rb +0 -507
  518. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  519. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  520. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  521. data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
  522. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  523. data/lib/phronomy/agent/handoff_request.rb +0 -45
  524. data/lib/phronomy/agent/handoff_runner.rb +0 -178
  525. data/lib/phronomy/agent/phase_machine_builder.rb +0 -152
  526. data/lib/phronomy/agent/provider_call_outcome.rb +0 -106
  527. data/lib/phronomy/agent/recovery_support.rb +0 -504
  528. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  529. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  530. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  531. data/lib/phronomy/agent/tool_call_intercepted.rb +0 -29
  532. data/lib/phronomy/agent/tool_invocation_session_builder.rb +0 -276
  533. data/lib/phronomy/configuration.rb +0 -59
  534. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  535. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  536. data/lib/phronomy/generator_verifier.rb +0 -369
  537. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  538. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  539. data/lib/phronomy/metrics.rb +0 -26
  540. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  541. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  542. data/lib/phronomy/persistence/in_memory.rb +0 -690
  543. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  544. data/lib/phronomy/persistence.rb +0 -276
  545. data/lib/phronomy/recovery.rb +0 -186
  546. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  547. data/lib/phronomy/runnable.rb +0 -54
  548. data/lib/phronomy/token_usage.rb +0 -85
  549. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  550. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  551. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  552. data/lib/phronomy/workflow_recovery.rb +0 -123
  553. data/sig/phronomy/llm_adapter.rbs +0 -11
  554. data/sig/phronomy/runtime.rbs +0 -76
  555. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  556. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  557. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
@@ -1,143 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest"
4
-
5
- module Phronomy
6
- module Agent
7
- # Handoff-aware specialization of the normal Agent execution coordinator.
8
- # It changes only the durable terminal semantics of an invocation that
9
- # produced a typed HandoffRequest. EventLoop ownership/apply remains entirely
10
- # in Agent::ExecutionCoordinator.
11
- class HandoffExecutionCoordinator < Phronomy::Agent::ExecutionCoordinator
12
- private
13
-
14
- def compute_terminal(operation)
15
- view = operation.terminal_view
16
- if view.callback_failure
17
- return commit_failed_outcome(
18
- operation,
19
- view.callback_failure.to_stream_callback_error
20
- )
21
- end
22
- error = view.source_error || view.block_error || view.invocation_error
23
- raise error if error.is_a?(Phronomy::ExecutionRehydrationRequiredError)
24
- return commit_failed_outcome(operation, error) if error
25
- return commit_suspended(operation) if view.phase == :suspended
26
-
27
- raise view.block_error if view.input_blocked || view.output_blocked
28
- raise view.invocation_error if view.invocation_error
29
-
30
- return commit_handed_off(operation) if view.handoff
31
-
32
- commit_completed(operation)
33
- rescue => caught
34
- reconcile_terminal_error(operation, caught)
35
- end
36
-
37
- def commit_handed_off(operation)
38
- current = operation.execution
39
- root = operation.root
40
- runtime_snapshot = operation.runtime_snapshot
41
- request = operation.terminal_view.handoff
42
- handed_off = next_root = appended = nil
43
-
44
- @agent.persistence.transaction do |tx|
45
- coordination = current.metadata.fetch("coordination")
46
- main_id = coordination.fetch("main_agent_id")
47
- routing = tx.handoff_states.load(main_id)
48
- unless routing && routing.active_agent_id == @agent.agent_id && routing.handoff_revision == coordination.fetch("handoff_revision")
49
- raise Phronomy::Persistence::ConflictError, "Handoff routing changed before Source transfer"
50
- end
51
- if Array(routing.metadata["cancelled_execution_ids"]).include?(current.execution_id)
52
- raise Phronomy::CancellationError, "Handoff Source turn was cancelled"
53
- end
54
- manifest = RecoverySupport.manifest_from_ref(@agent, current.metadata.fetch("manifest_ref"))
55
- context = HandoffProjection.new.build_terminal(view: request, manifest: manifest,
56
- persistence: tx, source_agent_id: @agent.agent_id)
57
- context_ref = tx.contents.put_json(context.to_h)
58
- target_id = "handoff-target-#{Digest::SHA256.hexdigest([current.execution_id, request.target_agent_id].join("\0"))}"
59
- target_root = tx.agents.load(request.target_agent_id)
60
- target_definition = {"id" => target_root.agent_definition_id, "version" => target_root.agent_definition_version}
61
- transfer = routing.with(active_agent_id: request.target_agent_id,
62
- active_handoff_context_ref: context_ref, phase: "target_pending",
63
- pending_source_execution_id: current.execution_id, pending_target_execution_id: target_id,
64
- metadata: routing.metadata.merge("target_definition" => target_definition))
65
- tx.handoff_states.save(main_id, expected_revision: routing.handoff_revision, state: transfer)
66
- encoded_records, call_records = encode_runtime_records(
67
- current,
68
- tx: tx,
69
- snapshot: runtime_snapshot,
70
- context_candidate: false,
71
- agent_root: root
72
- )
73
- audit_ref = tx.contents.put_json(
74
- "target_agent_id" => request.target_agent_id,
75
- "responsibility" => request.responsibility,
76
- "selection_intent" => request.selection_intent.to_h do |category, included|
77
- [category.to_s, included]
78
- end
79
- )
80
- audit_record = Phronomy::Agent::JournalRecord.new(
81
- agent_id: @agent.agent_id,
82
- execution_id: current.execution_id,
83
- llm_call_id: request.llm_call_id,
84
- kind: :execution_handed_off,
85
- channel: :audit,
86
- content_ref: audit_ref,
87
- context_generation: root.transcript_generation,
88
- context_candidate: false,
89
- metadata: {
90
- "target_agent_id" => request.target_agent_id,
91
- "handoff_tool_call_id" => request.tool_call_id
92
- }.compact
93
- )
94
-
95
- all_records = current.working_records + encoded_records + [audit_record]
96
- appended = tx.journals.append(
97
- root.agent_id,
98
- expected_position: root.journal_position,
99
- records: all_records
100
- )
101
- handed_off = current.with(
102
- status: :handed_off,
103
- phase: :handed_off,
104
- working_records: [],
105
- llm_calls: current.llm_calls + call_records,
106
- approval_request: nil,
107
- terminal_reason: "handed_off",
108
- metadata: current.metadata.merge("handoff_target_agent_id" => request.target_agent_id,
109
- "handoff_target_execution_id" => target_id, "handoff_context_ref" => context_ref)
110
- )
111
- tx.executions.save(
112
- current.execution_id,
113
- expected_revision: current.execution_revision,
114
- execution: handed_off
115
- )
116
- context_changed = appended.any?(&:context_candidate)
117
- next_root = root.with(
118
- agent_revision: root.agent_revision + 1,
119
- context_revision: root.context_revision + (context_changed ? 1 : 0),
120
- journal_position: root.journal_position + appended.length,
121
- lifecycle_status: :idle
122
- )
123
- tx.agents.save(
124
- root.agent_id,
125
- expected_revision: root.agent_revision,
126
- root: next_root
127
- )
128
- end
129
-
130
- result = result_base(handed_off, next_root)
131
- TerminalOutcome.new(
132
- type: :handed_off,
133
- execution: handed_off,
134
- root: next_root,
135
- appended_records: Array(appended).freeze,
136
- result: result.freeze,
137
- error: nil,
138
- approval_request: nil
139
- )
140
- end
141
- end
142
- end
143
- 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,178 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "time"
4
-
5
- module Phronomy
6
- module Agent
7
- # Durable Agent-domain responsibility routing within one Persistence instance.
8
- # @api public
9
- class HandoffRunner
10
- MAX_HANDOFFS = 20
11
-
12
- # @api public
13
- attr_reader :main_agent, :handoffs
14
-
15
- # @api public
16
- def initialize(main_agent:, handoffs: [])
17
- @main_agent, @handoffs = main_agent, Array(handoffs).freeze
18
- @persistence = main_agent.persistence
19
- raise ArgumentError, "handoffs must contain Agent::Handoff" unless @handoffs.all? { |h| h.is_a?(Handoff) }
20
- @agents = ([main_agent] + @handoffs.flat_map { |edge| [edge.source_agent, edge.target_agent] }).uniq.to_h { |a| [a.agent_id, a] }.freeze
21
- unless @agents.values.all? { |a| a.persistence.equal?(@persistence) }
22
- raise Phronomy::ConfigurationError, "Durable Handoff graph requires one Persistence instance"
23
- end
24
- if @handoffs.group_by { |h| [h.source_agent.agent_id, h.target_agent.agent_id] }.any? { |_, edges| edges.size > 1 }
25
- raise ArgumentError, "Duplicate Source/Target Handoff edges"
26
- end
27
- @bindings = @handoffs.group_by { |h| h.source_agent.agent_id }.transform_values { |edges| edges.map { |h| HandoffCapabilityFactory.build(h) }.freeze }.freeze
28
- @runtime = Phronomy::Runtime.instance
29
- end
30
-
31
- # @api public
32
- def invoke(input, config: {})
33
- trace_handle = Phronomy::Tracing::Automatic.start("multi_agent.turn",
34
- input: input, main_agent_id: main_agent.agent_id,
35
- **main_agent.send(:_build_caller_meta, config))
36
- operation_error = result = nil
37
- if Phronomy::Runtime.in_event_loop_context?
38
- raise Phronomy::EventLoopReentrancyError, "HandoffRunner#invoke cannot block EventLoop"
39
- end
40
- unless Phronomy::Runtime.instance.equal?(@runtime)
41
- raise Phronomy::RuntimeShutdownError, "HandoffRunner belongs to a previous Runtime"
42
- end
43
- config = config.merge(cancellation_token: config[:cancellation_token] || Phronomy::Concurrency::CancellationToken.new)
44
- @runtime.__admit_multi_agent(main_agent)
45
- admitted = true
46
- state = load_state
47
- count = 0
48
- loop do
49
- active = @agents.fetch(state.active_agent_id) do
50
- raise Phronomy::ExecutionRehydrationRequiredError, "Handoff graph lacks active Agent #{state.active_agent_id}"
51
- end
52
- context = state.active_handoff_context_ref && HandoffContext.from_h(@persistence.contents.fetch_json(state.active_handoff_context_ref))
53
- wiring = config.merge(phronomy_handoff_bindings: @bindings.fetch(active.agent_id, []),
54
- phronomy_handoff_context: context,
55
- phronomy_coordination: {"kind" => "handoff", "main_agent_id" => main_agent.agent_id, "handoff_revision" => state.handoff_revision}).freeze
56
- active.instance_variable_set(:@_phronomy_coordination_config, wiring)
57
- result = if state.phase == "stable"
58
- unfinished = @persistence.executions.list_active(active.agent_id)
59
- if unfinished.empty?
60
- active.invoke(input, config: wiring)
61
- else
62
- exact = unfinished.fetch(0)
63
- unless unfinished.size == 1 && exact.metadata.dig("coordination", "main_agent_id") == main_agent.agent_id
64
- raise Phronomy::Persistence::ConflictError, "Active Agent execution belongs to another coordination turn"
65
- end
66
- wiring[:cancellation_token].cancel! if Array(state.metadata["cancelled_execution_ids"]).include?(exact.execution_id)
67
- stored_input = @persistence.contents.fetch_text(exact.metadata.fetch("current_input_ref"))
68
- ExactExecution.start(agent: active, execution_id: exact.execution_id, input: stored_input, config: wiring).wait_result
69
- end
70
- else
71
- source = @persistence.executions.load(state.pending_source_execution_id)
72
- unless @handoffs.any? { |edge| edge.source_agent.agent_id == source.agent_id && edge.target_agent.agent_id == state.active_agent_id }
73
- raise Phronomy::ExecutionRehydrationRequiredError, "Handoff graph lacks committed Source/Target edge"
74
- end
75
- definition = state.metadata.fetch("target_definition")
76
- unless active.class.agent_definition == definition.transform_keys(&:to_sym)
77
- raise Phronomy::ConfigurationError, "Handoff Target definition mismatch"
78
- end
79
- wiring[:cancellation_token].cancel! if Array(state.metadata["cancelled_execution_ids"]).include?(state.pending_target_execution_id)
80
- ExactExecution.start(agent: active, execution_id: state.pending_target_execution_id,
81
- input: context.responsibility, config: wiring).wait_result
82
- end
83
- execution = @persistence.executions.load(result.fetch(:execution_id))
84
- if execution.status == :handed_off
85
- count += 1
86
- raise Phronomy::HandoffError, "Exceeded maximum Handoffs in one turn" if count > MAX_HANDOFFS
87
- state = @persistence.handoff_states.load(main_agent.agent_id)
88
- next
89
- end
90
- if execution.active?
91
- raise Phronomy::ExecutionRehydrationRequiredError, "Handoff execution requires approval or recovery"
92
- end
93
- raise RecoverySupport.error_from_failure(result[:error]) if result[:error]
94
- return result.reject { |key, _| key.to_s.start_with?("_phronomy_") || key == :handoff_request }.merge(agent: active)
95
- end
96
- rescue Phronomy::CancellationError => error
97
- operation_error = error
98
- cancel(state.pending_source_execution_id) if state && state.phase != "stable"
99
- raise
100
- rescue => error
101
- operation_error = error
102
- raise
103
- ensure
104
- @runtime.__release_multi_agent(main_agent) if admitted
105
- Phronomy::Tracing::Automatic.finish(trace_handle, output: result && result[:output], error: operation_error)
106
- end
107
-
108
- # Durably scopes cancellation to an exact turn, including a transferred
109
- # Target reservation. A completed older turn never cancels a later turn.
110
- # @api public
111
- def cancel(execution_id)
112
- if Phronomy::Runtime.in_event_loop_context?
113
- raise Phronomy::EventLoopReentrancyError, "HandoffRunner#cancel cannot block EventLoop"
114
- end
115
- intended = leaf = leaf_id = nil
116
- begin
117
- @persistence.transaction do |tx|
118
- first = tx.executions.load(execution_id)
119
- unless first.metadata.dig("coordination", "main_agent_id") == main_agent.agent_id
120
- raise Phronomy::Persistence::ConflictError, "Execution does not belong to this Handoff anchor"
121
- end
122
- leaf = first
123
- leaf_id = first.execution_id
124
- seen = {}
125
- while leaf&.status == :handed_off
126
- raise Phronomy::Persistence::SerializationError, "Cyclic Handoff chain" if seen[leaf_id]
127
- seen[leaf_id] = true
128
- leaf_id = leaf.metadata.fetch("handoff_target_execution_id")
129
- begin
130
- leaf = tx.executions.load(leaf_id)
131
- rescue Phronomy::Persistence::NotFoundError
132
- leaf = nil
133
- end
134
- end
135
- next if leaf&.terminal?
136
- routing = tx.handoff_states.load(main_agent.agent_id)
137
- unless routing && (leaf ? routing.active_agent_id == leaf.agent_id : routing.pending_target_execution_id == leaf_id)
138
- raise Phronomy::Persistence::ConflictError, "Handoff routing no longer owns the requested turn"
139
- end
140
- ids = (Array(routing.metadata["cancelled_execution_ids"]) + [leaf_id]).uniq
141
- intended = routing.with(phase: leaf ? routing.phase : "stable",
142
- metadata: routing.metadata.merge("cancelled_execution_ids" => ids))
143
- tx.handoff_states.save(main_agent.agent_id, expected_revision: routing.handoff_revision, state: intended)
144
- end
145
- rescue => error
146
- confirmed = @persistence.handoff_states.load(main_agent.agent_id)
147
- raise error unless intended && confirmed && Array(confirmed.metadata["cancelled_execution_ids"]).include?(leaf_id)
148
- end
149
- return @persistence.execution_result(leaf_id) if leaf&.terminal?
150
- ExecutionCancellation.signal(leaf_id, leaf.agent_id) if leaf&.active?
151
- {execution_id: leaf_id, cancellation_requested: !leaf&.terminal?}.freeze
152
- end
153
-
154
- # Reads a specified source/Target chain without current graph continuation.
155
- # @api public
156
- def result(execution_id)
157
- @persistence.handoff_result(execution_id, main_agent_id: main_agent.agent_id)
158
- end
159
-
160
- private
161
-
162
- def load_state
163
- state = @persistence.handoff_states.load(main_agent.agent_id)
164
- return state if state
165
- now = Time.now.utc.iso8601(6)
166
- initial = HandoffState.new(main_agent_id: main_agent.agent_id, handoff_revision: 1,
167
- active_agent_id: main_agent.agent_id, active_handoff_context_ref: nil,
168
- phase: "stable", pending_source_execution_id: nil, pending_target_execution_id: nil,
169
- created_at: now, updated_at: now, metadata: {})
170
- @persistence.transaction { |tx| tx.handoff_states.save(main_agent.agent_id, expected_revision: nil, state: initial) }
171
- rescue => error
172
- confirmed = @persistence.handoff_states.load(main_agent.agent_id)
173
- raise error unless confirmed
174
- confirmed
175
- end
176
- end
177
- end
178
- end
@@ -1,152 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "state_machines"
4
-
5
- module Phronomy
6
- module Agent
7
- class PhaseMachineBuilder
8
- TOOL_EVENTS = %i[
9
- tool_authorized
10
- tool_approval_required
11
- tool_completed
12
- tool_failed
13
- tool_rejected
14
- tool_cancelled
15
- ].freeze
16
-
17
- def initialize(entry_actions: {})
18
- @entry_actions = entry_actions
19
- end
20
-
21
- def build
22
- entry_actions = @entry_actions
23
- callback_builder = method(:build_entry_callback)
24
-
25
- Class.new do
26
- attr_accessor :context, :current_event
27
-
28
- state_machine :phase, initial: :idle do
29
- state :idle
30
- state :filtering_input
31
- state :building_context
32
- state :calling_llm
33
- state :starting_tools
34
- state :evaluating_tools
35
- state :waiting_for_tools
36
- state :dispatching_tools
37
- state :recording_tool_results
38
- state :suspended
39
- state :output_filtering
40
- state :handed_off
41
- state :completed
42
- state :blocked
43
- state :failed
44
-
45
- event :state_completed do
46
- transition idle: :filtering_input
47
-
48
- transition filtering_input: :building_context,
49
- if: ->(machine) { machine.context&.input_passed? }
50
- transition filtering_input: :blocked,
51
- if: ->(machine) { machine.context&.input_blocked? }
52
-
53
- transition building_context: :calling_llm
54
- transition starting_tools: :evaluating_tools
55
-
56
- transition evaluating_tools: :failed,
57
- if: ->(machine) { machine.context&.tool_batch_failed? }
58
- transition evaluating_tools: :blocked,
59
- if: ->(machine) { machine.context&.tool_batch_rejected? }
60
- transition evaluating_tools: :recording_tool_results,
61
- if: ->(machine) { machine.context&.tool_batch_completed? }
62
- transition evaluating_tools: :suspended,
63
- if: ->(machine) { machine.context&.approval_required? }
64
- transition evaluating_tools: :dispatching_tools,
65
- if: ->(machine) { machine.context&.ready_to_dispatch? }
66
- transition evaluating_tools: :waiting_for_tools
67
-
68
- transition recording_tool_results: :calling_llm
69
-
70
- transition output_filtering: :completed,
71
- if: ->(machine) { machine.context&.output_passed? }
72
- transition output_filtering: :blocked,
73
- if: ->(machine) { machine.context&.output_blocked? }
74
- end
75
-
76
- event :llm_completed do
77
- transition calling_llm: :failed,
78
- if: ->(machine) { machine.context&.callback_failed? }
79
- transition calling_llm: :failed,
80
- if: ->(machine) { machine.context&.handoff_failed? }
81
- transition calling_llm: :handed_off,
82
- if: ->(machine) { machine.context&.handoff_requested? }
83
- transition calling_llm: :starting_tools,
84
- if: ->(machine) { machine.context&.tool_call_pending? }
85
- transition calling_llm: :output_filtering
86
- end
87
-
88
- event :llm_failed do
89
- transition calling_llm: :failed
90
- end
91
-
92
- event :llm_setup_failed do
93
- transition calling_llm: :failed
94
- end
95
-
96
- event :tool_setup_failed do
97
- transition dispatching_tools: :failed
98
- end
99
-
100
- event :tool_dispatch_prepared do
101
- transition dispatching_tools: :evaluating_tools
102
- end
103
-
104
- TOOL_EVENTS.each do |event_name|
105
- event event_name do
106
- transition waiting_for_tools: :evaluating_tools
107
- end
108
- end
109
-
110
- event :resume do
111
- transition suspended: :waiting_for_tools
112
- end
113
-
114
- event :application_callback_failed do
115
- transition filtering_input: :failed
116
- transition building_context: :failed
117
- transition calling_llm: :failed
118
- transition starting_tools: :failed
119
- transition evaluating_tools: :failed
120
- transition waiting_for_tools: :failed
121
- transition dispatching_tools: :failed
122
- transition recording_tool_results: :failed
123
- transition output_filtering: :failed
124
- end
125
-
126
- entry_actions.each do |state_name, callables|
127
- callables.each do |callable|
128
- after_transition(
129
- to: state_name,
130
- do: callback_builder.call(callable, state_name)
131
- )
132
- end
133
- end
134
- end
135
- end
136
- end
137
-
138
- private
139
-
140
- def build_entry_callback(callable, state_name)
141
- ->(machine) {
142
- result = callable.call(machine.context)
143
- if result.is_a?(Phronomy::TaskResult)
144
- raise Phronomy::InvalidAsyncEntryActionError,
145
- "Agent entry action for #{state_name.inspect} returned Phronomy::TaskResult"
146
- end
147
- machine.context = result if result.respond_to?(:set_graph_metadata)
148
- }
149
- end
150
- end
151
- end
152
- 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_id.to_s if message.respond_to?(:model_id) && message.model_id
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: Immutable.copy(content),
76
- tool_calls: Immutable.copy(Array(tool_calls)),
77
- usage: Immutable.copy(usage || {}),
78
- metadata: Immutable.copy(metadata || {})
79
- )
80
- Immutable.validate_canonical_json!(content, label: "Provider assistant content")
81
- Immutable.validate_canonical_json!(tool_calls, label: "Provider Tool Calls")
82
- Immutable.validate_canonical_json!(usage, label: "Provider usage")
83
- 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