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
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "message"
4
+
5
+ module Phronomy
6
+ module LLMAdapter
7
+ # Immutable input for one model turn, including inert Tool definitions.
8
+ # @api public
9
+ class Request
10
+ attr_reader :model_config, :system, :messages, :tools, :message
11
+
12
+ # @api public
13
+ def initialize(model_config: {}, system: nil, messages: [], tools: [], message: nil)
14
+ if !(model_config.is_a?(Hash) && (system.nil? || system.is_a?(String)) && (message.nil? || message.is_a?(String)))
15
+ raise ArgumentError, "invalid LLM request configuration or text"
16
+ end
17
+ unless messages.is_a?(Array) && messages.all? { |item| item.is_a?(Message) }
18
+ raise ArgumentError, "LLM request messages must be Message values"
19
+ end
20
+ unless tools.is_a?(Array) && tools.all? { |tool| tool.is_a?(Hash) }
21
+ raise ArgumentError, "LLM request tools must be definition Hashes"
22
+ end
23
+ normalized_tools = tools.map do |definition|
24
+ definition = normalize_keys(definition)
25
+ name = definition["name"]
26
+ unless name.is_a?(String) && !name.empty? && definition["description"].is_a?(String)
27
+ raise ArgumentError, "Tool definition requires name and description"
28
+ end
29
+ if definition.key?("provider_options") && !definition["provider_options"].is_a?(Hash)
30
+ raise ArgumentError, "Tool provider_options must be a Hash"
31
+ end
32
+ Phronomy::Tool::Schema.new(definition.fetch("parameters_schema"))
33
+ Phronomy::Values::Immutable.validate_canonical_json!(definition, label: "Tool definition")
34
+ definition
35
+ end
36
+ names = normalized_tools.map { |tool| tool.fetch("name") }
37
+ raise ArgumentError, "duplicate Tool definition name" unless names.uniq == names
38
+ config = normalize_keys(model_config)
39
+ Phronomy::Values::Immutable.validate_canonical_json!(config, label: "LLM model configuration")
40
+ max = config["max_output_tokens"]
41
+ if !max.nil? && (!max.is_a?(Integer) || max <= 0)
42
+ raise ArgumentError, "max_output_tokens must be a positive Integer"
43
+ end
44
+ %w[model provider].each do |key|
45
+ value = config[key]
46
+ if !(value.nil? || (value.is_a?(String) && !value.empty?))
47
+ raise ArgumentError, "#{key} must be a non-empty String or nil"
48
+ end
49
+ end
50
+ %w[assume_model_exists cache_instructions].each do |key|
51
+ raise ArgumentError, "#{key} must be boolean or nil" unless [true, false, nil].include?(config[key])
52
+ end
53
+ temperature = config["temperature"]
54
+ if !(temperature.nil? || (temperature.is_a?(Numeric) && temperature.finite?))
55
+ raise ArgumentError, "temperature must be a finite number or nil"
56
+ end
57
+ @model_config = Phronomy::Values::Immutable.copy(config)
58
+ @system = system&.dup&.freeze
59
+ @message = message&.dup&.freeze
60
+ @messages = messages.dup.freeze
61
+ @tools = Phronomy::Values::Immutable.copy(normalized_tools)
62
+ freeze
63
+ end
64
+
65
+ private
66
+
67
+ def normalize_keys(value)
68
+ value.each_with_object({}) do |(key, child), result|
69
+ unless key.is_a?(String) || key.is_a?(Symbol)
70
+ raise ArgumentError, "LLM configuration/definition keys must be Strings or Symbols"
71
+ end
72
+ name = key.to_s
73
+ raise ArgumentError, "duplicate LLM configuration/definition key: #{name}" if result.key?(name)
74
+ result[name] = child
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "message"
4
+ require_relative "token_usage"
5
+
6
+ module Phronomy
7
+ module LLMAdapter
8
+ # Completed assistant turn. Tool requests do not authorize execution.
9
+ # @api public
10
+ class Response < Message
11
+ attr_reader :usage
12
+
13
+ # @api public
14
+ def initialize(content: nil, tool_calls: [], usage: Phronomy::LLMAdapter::TokenUsage.new, metadata: {}, role: :assistant)
15
+ raise ArgumentError, "LLM response must be an assistant message" unless role == :assistant || role == "assistant"
16
+ raise ArgumentError, "LLM usage must be LLMAdapter::TokenUsage" unless usage.is_a?(Phronomy::LLMAdapter::TokenUsage)
17
+ @usage = usage
18
+ super(role: :assistant, content: content, tool_calls: tool_calls, metadata: metadata)
19
+ end
20
+
21
+ # @api public
22
+ def self.from_h(value)
23
+ source = value.transform_keys(&:to_s)
24
+ new(role: source.fetch("role", "assistant"), content: source["content"],
25
+ tool_calls: source.fetch("tool_calls", []).map { |call| Phronomy::Tool::CallRequest.from_h(call) },
26
+ usage: Phronomy::LLMAdapter::TokenUsage.new(**source.fetch("usage", {}).transform_keys(&:to_sym)),
27
+ metadata: source.fetch("metadata", {}))
28
+ end
29
+
30
+ # @api public
31
+ def to_h
32
+ super.merge("usage" => usage.to_h.transform_keys(&:to_s))
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module LLMAdapter
5
+ # Intermediate text notification; the final Response is authoritative.
6
+ # @api public
7
+ class StreamChunk
8
+ attr_reader :content
9
+
10
+ # @api public
11
+ def initialize(content:)
12
+ raise ArgumentError, "stream content must be a String or nil" unless content.nil? || content.is_a?(String)
13
+ @content = content&.dup&.freeze
14
+ freeze
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module LLMAdapter
5
+ # Immutable usage counts. An omitted count remains nil; zero is a reported
6
+ # measurement. SDK field translation belongs to the backend.
7
+ # @api public
8
+ class TokenUsage
9
+ attr_reader :input, :output, :cached, :cache_creation
10
+
11
+ def initialize(input: nil, output: nil, cached: nil, cache_creation: nil)
12
+ [input, output, cached, cache_creation].each do |value|
13
+ if !(value.nil? || (value.is_a?(Integer) && value >= 0))
14
+ raise ArgumentError, "token usage must be non-negative Integers or nil"
15
+ end
16
+ end
17
+ @input = input
18
+ @output = output
19
+ @cached = cached
20
+ @cache_creation = cache_creation
21
+ freeze
22
+ end
23
+
24
+ # Returns a zero-valued TokenUsage suitable as an accumulator seed.
25
+ def self.zero
26
+ new(input: 0, output: 0, cached: 0, cache_creation: 0)
27
+ end
28
+
29
+ # Adds two TokenUsage instances. nil fields are treated as 0 so that
30
+ # partially-reported usage accumulates correctly.
31
+ # When other is nil, returns self unchanged.
32
+ def +(other)
33
+ return self if other.nil?
34
+
35
+ self.class.new(
36
+ input: _add(input, other.input),
37
+ output: _add(output, other.output),
38
+ cached: _add(cached, other.cached),
39
+ cache_creation: _add(cache_creation, other.cache_creation)
40
+ )
41
+ end
42
+
43
+ def ==(other)
44
+ other.is_a?(TokenUsage) &&
45
+ input == other.input &&
46
+ output == other.output &&
47
+ cached == other.cached &&
48
+ cache_creation == other.cache_creation
49
+ end
50
+
51
+ def to_h
52
+ {input: input, output: output, cached: cached, cache_creation: cache_creation}
53
+ end
54
+
55
+ private
56
+
57
+ def _add(a, b)
58
+ return nil if a.nil? && b.nil?
59
+
60
+ (a || 0) + (b || 0)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ module LLMAdapter
7
+ class TransportError < Phronomy::Error; end
8
+ end
9
+ end
@@ -1,22 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- # Namespace for LLM call adapters.
5
- #
6
- # The public extension boundary is {LLMAdapter::Base#complete} and
7
- # {LLMAdapter::Base#stream}. Those methods receive Phronomy's currently
8
- # materialized chat/runtime object and perform the provider call. Phronomy owns
9
- # the async/offload bridge around that synchronous contract.
10
- #
11
- # This SPI does not by itself make the complete input-materialization pipeline
12
- # provider-neutral: the current Agent pipeline still materializes the canonical
13
- # LLM input through RubyLLM-specific runtime objects before invoking the adapter.
14
- #
15
- # @example Configuring a custom adapter
16
- # Phronomy.configure do |c|
17
- # c.llm_adapter = MyCustomAdapter.new
18
- # end
19
- #
4
+ # LLM domain operations and public request, response, usage and failure values.
5
+ # A backend implements Base's protected hooks. SDK objects remain inside the
6
+ # backend; AsyncClient executes the entire synchronous operation on a worker.
20
7
  # @api public
21
8
  module LLMAdapter
22
9
  end
@@ -2,20 +2,19 @@
2
2
 
3
3
  module Phronomy
4
4
  module LlmContextWindow
5
- # Immutable arithmetic value for one resolved model context budget.
6
- # Model-registry lookup belongs to Agent::TokenBudgetResolver.
5
+ # Immutable input-budget arithmetic. RubyLLM owns model capabilities.
7
6
  class TokenBudget
8
- attr_reader :context_window, :max_output_tokens
7
+ attr_reader :max_input_tokens
9
8
 
10
9
  # @api private
11
- def initialize(context_window:, max_output_tokens:)
12
- @context_window = Integer(context_window)
13
- @max_output_tokens = Integer(max_output_tokens)
10
+ def initialize(max_input_tokens:)
11
+ @max_input_tokens = Integer(max_input_tokens)
12
+ raise ArgumentError, "max_input_tokens must be positive" unless @max_input_tokens.positive?
14
13
  end
15
14
 
16
15
  # @api private
17
16
  def effective_input_limit
18
- [@context_window - @max_output_tokens, 0].max
17
+ @max_input_tokens
19
18
  end
20
19
 
21
20
  # @api private
@@ -2,18 +2,25 @@
2
2
 
3
3
  module Phronomy
4
4
  module MultiAgent
5
- # Runtime-local admission for one user-facing coordination turn per main Agent.
6
- # This owns exclusion only; active-agent mutation still belongs to EventLoop.
5
+ # Environment-local admission for one synchronous coordination call per owner
6
+ # (a Handoff main Agent or TeamCoordinator). This does not own durable runs
7
+ # or active-agent mutation; those remain in their existing domains.
8
+ # @api private
7
9
  class AdmissionRegistry
8
10
  def initialize
9
11
  @mutex = Mutex.new
10
12
  @cond = ConditionVariable.new
11
13
  @owners = {}
14
+ @draining = false
12
15
  end
13
16
 
14
17
  def admit!(coordinator)
15
18
  key = coordinator.object_id
16
19
  @mutex.synchronize do
20
+ if @draining
21
+ raise Phronomy::RuntimeShutdownError,
22
+ "Runtime is shutting down or failed; new Multi-Agent turns are not accepted"
23
+ end
17
24
  if @owners.key?(key)
18
25
  raise Phronomy::HandoffError,
19
26
  "a Multi-Agent turn is already active for this main Agent instance"
@@ -36,6 +43,12 @@ module Phronomy
36
43
  @mutex.synchronize { @owners.empty? }
37
44
  end
38
45
 
46
+ # Close admission atomically with respect to admit!, without waiting for
47
+ # admitted callers or calling back into Runtime. Repeated closure is safe.
48
+ def begin_draining
49
+ @mutex.synchronize { @draining = true }
50
+ end
51
+
39
52
  def wait_until_idle(deadline)
40
53
  @mutex.synchronize do
41
54
  until @owners.empty?
@@ -1,127 +1,151 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "securerandom"
3
+ require "digest"
4
4
 
5
5
  module Phronomy
6
6
  module MultiAgent
7
- # Agent-owned transactions reserve child work; TaskResult callbacks only wake observers.
7
+ # Owns slot reservations and import progress. The Agent participant stores
8
+ # only this binding's opaque state reference alongside its own transition.
8
9
  # @api private
9
10
  class DurableSubagentCoordinator
10
- KEY = "multi_agent_coordination_ref"
11
+ BINDING_KEY = "phronomy.subagent"
12
+ attr_reader :persistence
11
13
 
12
- def self.prepare(parent, execution, tx:)
13
- ref = execution.metadata[KEY]
14
- snapshot = ref ? tx.contents.fetch_json(ref) : {"kind" => "static_subagent", "children" => []}
15
- children = snapshot.fetch("children").map(&:dup)
16
- children.each do |child|
17
- begin
18
- exact = tx.executions.load(child.fetch("execution_id"))
19
- rescue Phronomy::Persistence::NotFoundError
20
- next
21
- end
22
- raise Phronomy::Persistence::ConflictError, "Child owner mismatch" unless exact.agent_id == child.fetch("agent_id")
23
- child.merge!("state" => exact.status.to_s, "result_ref" => exact.result_ref, "error_ref" => exact.error_ref)
14
+ def initialize(agent_class:, persistence:)
15
+ @agent_class, @persistence = agent_class, persistence
16
+ @definitions = agent_class.registered_subagents
17
+ end
18
+
19
+ def binding
20
+ Phronomy::Agent::ExecutionExtensionState.new(binding_key: BINDING_KEY, binding_version: 1)
21
+ end
22
+
23
+ def guard_change(change, scope)
24
+ snapshot = state_for(change.extension)
25
+ ids = [change.agent_id] + snapshot.fetch("children").filter_map do |child|
26
+ child.fetch("agent_id") if persistence.agent_store.exist?(child.fetch("agent_id"))
24
27
  end
25
- Array(execution.metadata[Phronomy::Agent::RecoverySupport::TOOL_BATCH_METADATA_KEY]).each do |tool|
26
- registration = parent.class.registered_subagents.find { |name, _| "dispatch_to_#{name}" == tool.fetch("tool_name") }
27
- next unless registration
28
- next if children.any? { |child| child.fetch("slot") == tool.fetch("tool_invocation_id") }
29
- name, settings = registration
30
- definition = settings.fetch(:agent_class).agent_definition
31
- knowledge = settings.fetch(:inherit_knowledge) ? parent.send(:active_knowledge_snapshot) : []
32
- children << {
33
- "slot" => tool.fetch("tool_invocation_id"), "name" => name.to_s,
34
- "definition" => definition.transform_keys(&:to_s),
35
- "agent_id" => SecureRandom.uuid, "execution_id" => SecureRandom.uuid,
36
- "input_ref" => tx.contents.put_text(tool.fetch("arguments").fetch("input")),
37
- "durable_context_ref" => execution.metadata["durable_context_ref"],
38
- "knowledge_ref" => tx.contents.put_json(Phronomy::Agent::RecoverySupport.canonical_copy(knowledge)),
39
- "state" => "reserved", "result_ref" => nil, "error_ref" => nil,
40
- "on_error" => settings.fetch(:on_error).to_s
41
- }
28
+ persistence.agent_store.guard_agents(scope, agent_ids: ids)
29
+ end
30
+
31
+ # The referenced content is immutable and is part of Agent's commit proof.
32
+ def change_evidence(change, scope)
33
+ persistence.agent_store.retained_references(agent_id: change.agent_id, scope: scope).map(&:to_h)
34
+ end
35
+
36
+ def commit(change)
37
+ persistence.coordinator.atomic do |scope|
38
+ guard_change(change, scope)
39
+ change.prepare_in(scope)
40
+ snapshot = state_for(change.extension)
41
+ children = snapshot.fetch("children").map(&:dup)
42
+ cancelled = snapshot["cancel_requested"] || change.cancel_requested ||
43
+ persistence.agent_store.cancellation_requested?(agent_id: change.agent_id, execution_id: change.execution_id, scope: scope)
44
+ children.each do |child|
45
+ observed = persistence.agent_store.observe_execution(agent_id: child.fetch("agent_id"), execution_id: child.fetch("execution_id"), scope: scope)
46
+ # This is import progress, not a replicated child execution state.
47
+ child["imported"] = true if observed.terminal? && change.kind == :dispatch && change.operations.empty?
48
+ end
49
+ change.operations.each do |operation|
50
+ entry = @definitions.find { |name, _| "dispatch_to_#{name}" == operation.name }
51
+ next unless entry
52
+ next if children.any? { |child| child.fetch("slot") == operation.invocation_id }
53
+ name, settings = entry
54
+ digest = Digest::SHA256.hexdigest(Phronomy::CanonicalJSON.dump(
55
+ ["phronomy-subagent-v1", change.agent_id, change.execution_id, operation.invocation_id]
56
+ ))
57
+ knowledge = settings.fetch(:inherit_knowledge) ? persistence.agent_store.knowledge_snapshot(agent_id: change.agent_id, scope: scope) : []
58
+ children << {"slot" => operation.invocation_id, "name" => name.to_s,
59
+ "definition" => settings.fetch(:agent_class).agent_definition.transform_keys(&:to_s),
60
+ "agent_id" => "subagent-#{digest}", "execution_id" => "subagent-execution-#{digest}",
61
+ "input" => operation.arguments.fetch("input"), "durable_context" => change.durable_context,
62
+ "knowledge" => knowledge.map { |item| {"content" => item.content, "metadata" => item.metadata} },
63
+ "on_error" => settings.fetch(:on_error).to_s, "imported" => false}
64
+ end
65
+ unless children.empty?
66
+ persistence.agent_store.retain(Phronomy::Agent::Retention.new(agent_id: change.agent_id,
67
+ execution_id: change.execution_id, owner_key: "subagent:#{change.execution_id}"), scope: scope)
68
+ end
69
+ unresolved = children.any? do |child|
70
+ observed = persistence.agent_store.observe_execution(agent_id: child.fetch("agent_id"), execution_id: child.fetch("execution_id"), scope: scope)
71
+ observed.active? || (observed.absent? && !cancelled)
72
+ end
73
+ persistence.participate(scope) do |records|
74
+ ref = records.contents.put_json(snapshot.merge("children" => children, "cancel_requested" => !!cancelled))
75
+ state = Phronomy::Agent::ExecutionExtensionState.new(binding_key: BINDING_KEY, binding_version: 1, state_ref: ref)
76
+ change.commit_in(scope, state: state,
77
+ pending: change.kind == :terminal && unresolved && (change.cancel_requested || change.recovery_required))
78
+ end
42
79
  end
43
- return execution if children.empty?
44
- value = tx.contents.put_json(snapshot.merge("children" => children))
45
- execution.with(execution_revision: execution.execution_revision, metadata: execution.metadata.merge(KEY => value))
80
+ end
81
+
82
+ def state_for(extension)
83
+ extension&.state_ref ? persistence.contents.fetch_json(extension.state_ref) : {"children" => [], "cancel_requested" => false}
46
84
  end
47
85
 
48
86
  def self.start(parent:, tool_invocation_id:, parent_execution_id:, config:)
49
- runtime = Phronomy::Runtime.instance
50
87
  completion = Phronomy::TaskResult.deferred(name: "durable-subagent:#{tool_invocation_id}")
51
- preparation = runtime.offload.submit(on_full: :raise) do
52
- current = parent.persistence.executions.load(parent_execution_id)
53
- raise Phronomy::Persistence::ConflictError, "Parent owner mismatch" unless current.agent_id == parent.agent_id
54
- snapshot = parent.persistence.contents.fetch_json(current.metadata.fetch(KEY))
88
+ preparation = Phronomy::Execution.submit(on_full: :raise) do
89
+ store = parent.coordination_store
90
+ extension = parent.persistence.execution_extension(agent_id: parent.agent_id, execution_id: parent_execution_id, binding_key: BINDING_KEY)
91
+ raise Phronomy::ExecutionRehydrationRequiredError, "Missing subagent reservation state" unless extension&.state_ref
92
+ snapshot = store.contents.fetch_json(extension.state_ref)
55
93
  child = snapshot.fetch("children").find { |entry| entry.fetch("slot") == tool_invocation_id }
56
94
  raise Phronomy::ExecutionRehydrationRequiredError, "Missing reserved child slot" unless child
57
- definition = parent.class.registered_subagents.find { |name, _| name.to_s == child.fetch("name") }&.last
58
- unless definition && definition.fetch(:agent_class).agent_definition.transform_keys(&:to_s) == child.fetch("definition")
59
- raise Phronomy::ConfigurationError, "Registered child definition changed: #{child.fetch("name")}"
95
+ settings = parent.class.registered_subagents.find { |name, _| name.to_s == child.fetch("name") }&.last
96
+ unless settings && settings.fetch(:agent_class).agent_definition.transform_keys(&:to_s) == child.fetch("definition")
97
+ raise Phronomy::ConfigurationError, "Registered child definition changed"
60
98
  end
61
- klass = definition.fetch(:agent_class)
99
+ klass = settings.fetch(:agent_class)
62
100
  id = child.fetch("agent_id")
101
+ retention = Phronomy::Agent::Retention.new(agent_id: id, execution_id: child.fetch("execution_id"), owner_key: "subagent:#{parent_execution_id}")
63
102
  agent = klass.get(id)
64
103
  unless agent
65
- begin
66
- parent.persistence.agents.load(id)
67
- exists = true
68
- rescue Phronomy::Persistence::NotFoundError
69
- exists = false
70
- end
71
- listener = parent.send(:_phronomy_event_listener)
72
- agent = if exists
73
- klass.load(id, persistence: parent.persistence, on_event: listener)
104
+ if parent.persistence.exist?(id)
105
+ agent = klass.load(id, persistence: parent.persistence, on_event: parent.event_listener)
74
106
  else
75
- knowledge = parent.persistence.contents.fetch_json(child.fetch("knowledge_ref"))
76
- context = Phronomy::Agent::ContextImporter::ImportedContext.new(records: knowledge.map do |item|
77
- Phronomy::Agent::ContextImporter::ImportedRecord.new(kind: :knowledge,
78
- channel: :context, role: :user, content: item.fetch("content"),
79
- content_format: :text, metadata: item.fetch("metadata"))
80
- end)
81
- klass.create(agent_id: id, persistence: parent.persistence,
82
- context: context, on_event: listener)
107
+ raise Phronomy::CancellationError, "Parent reservation was cancelled" if snapshot["cancel_requested"]
108
+ knowledge = child.fetch("knowledge").map { |item| Phronomy::Agent::KnowledgeItem.new(content: item.fetch("content"), metadata: item.fetch("metadata")) }
109
+ agent = klass.create(agent_id: id, persistence: parent.persistence, knowledge: knowledge,
110
+ retention: retention, on_event: parent.event_listener)
83
111
  end
84
112
  end
85
- unless agent.persistence.equal?(parent.persistence)
86
- raise Phronomy::ConfigurationError, "Child Persistence instance mismatch"
87
- end
88
- durable_context = child["durable_context_ref"] && parent.persistence.contents.fetch_json(child["durable_context_ref"])
89
- [child, agent, parent.persistence.contents.fetch_text(child.fetch("input_ref")), durable_context]
113
+ raise Phronomy::ConfigurationError, "Child Persistence mismatch" unless agent.persistence.equal?(parent.persistence)
114
+ [child, agent, store]
90
115
  end
91
116
  preparation.on_complete do |prepared, failure|
92
117
  if failure
93
- completion.fail(Phronomy::ExecutionRehydrationRequiredError.new("Child reservation requires recovery: #{failure.message}"))
118
+ completion.fail(failure.is_a?(Phronomy::CancellationError) ? failure : Phronomy::ExecutionRehydrationRequiredError.new("Child reservation requires recovery: #{failure.message}"))
94
119
  next
95
120
  end
96
- child, agent, input, durable_context = prepared
121
+ child, agent, store = prepared
122
+ owner = {"kind" => "subagent", "parent_execution_id" => parent_execution_id,
123
+ "parent_agent_id" => parent.agent_id, "slot" => tool_invocation_id}
97
124
  child_config = {cancellation_token: config[:cancellation_token] || Phronomy::Concurrency::CancellationToken.new,
98
125
  invocation_context: config[:invocation_context],
99
- phronomy_coordination: {"kind" => "subagent", "parent_execution_id" => parent_execution_id,
100
- "parent_agent_id" => parent.agent_id, "slot" => tool_invocation_id}}.compact
101
- child_config = child_config.merge(durable_context: durable_context) if child["durable_context_ref"]
102
- source = Phronomy::Agent::ExactExecution.start(agent: agent,
103
- execution_id: child.fetch("execution_id"), input: input, config: child_config)
104
- source.on_complete do |result, error|
126
+ phronomy_admission: ReservedChildAdmission.new(persistence: store, owner: owner)}.compact
127
+ child_config[:durable_context] = child["durable_context"] if child["durable_context"]
128
+ reservation = Phronomy::Agent::ReservedExecution.new(agent_id: agent.agent_id, execution_id: child.fetch("execution_id"), correlation: owner)
129
+ agent.start_reserved_async(reservation: reservation, input: child.fetch("input"), config: child_config).on_complete do |result, error|
105
130
  if error
106
- if error.is_a?(Phronomy::CancellationError)
107
- completion.fail(error)
108
- else
109
- completion.fail(Phronomy::ExecutionRehydrationRequiredError.new(
110
- "Child #{child.fetch("execution_id")} is unfinished: #{error.message}"
111
- ))
112
- end
131
+ completion.fail(error.is_a?(Phronomy::CancellationError) ? error : Phronomy::ExecutionRehydrationRequiredError.new("Child #{reservation.execution_id} is unfinished: #{error.message}"))
113
132
  elsif result[:error]
114
- if child.fetch("on_error") == "skip"
133
+ if child.fetch("on_error") == "skip" && result[:status] != :cancelled
115
134
  completion.complete(nil)
116
135
  else
117
- completion.fail(Phronomy::Agent::RecoverySupport.error_from_failure(result[:error]))
136
+ observed = Phronomy::Agent::ExecutionObservation.from_result(result)
137
+ begin
138
+ observed.value!
139
+ rescue => failure
140
+ completion.fail(failure)
141
+ end
118
142
  end
119
143
  else
120
144
  completion.complete(result[:output])
121
145
  end
122
146
  end
123
147
  rescue => error
124
- completion.fail(Phronomy::ExecutionRehydrationRequiredError.new("Child coordination requires recovery: #{error.message}"))
148
+ completion.fail(error)
125
149
  end
126
150
  completion
127
151
  rescue => error
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ # Process-local connection selected by composition. MultiAgent owns the
6
+ # admission and Team identity rules; the environment connects their
7
+ # registries to shutdown and submits work to the originating executor.
8
+ # Environments and registries are never durable execution authority.
9
+ # @api private
10
+ class ExecutionEnvironment
11
+ class << self
12
+ def install_provider(provider)
13
+ raise ArgumentError, "MultiAgent execution provider must be callable" unless provider.respond_to?(:call)
14
+ @provider = provider
15
+ end
16
+
17
+ def current
18
+ @provider&.call || raise(Phronomy::ConfigurationError, "MultiAgent execution environment has not been configured")
19
+ end
20
+ end
21
+
22
+ def admissions = raise(NotImplementedError)
23
+ def ownership = raise(NotImplementedError)
24
+ def existing_ownership = raise(NotImplementedError)
25
+ def current? = raise(NotImplementedError)
26
+ def submit(**options, &operation) = raise(NotImplementedError)
27
+ end
28
+ end
29
+ end
@@ -3,7 +3,7 @@
3
3
  require "digest"
4
4
 
5
5
  module Phronomy
6
- module Agent
6
+ module MultiAgent
7
7
  # Application-defined semantic edge for transferring active responsibility
8
8
  # from one live Agent instance to another.
9
9
  class Handoff
@@ -19,7 +19,7 @@ module Phronomy
19
19
  raise ArgumentError, "Handoff source_agent and target_agent must be different instances"
20
20
  end
21
21
  unless policy.is_a?(HandoffPolicy)
22
- raise ArgumentError, "policy must be a Phronomy::Agent::HandoffPolicy"
22
+ raise ArgumentError, "policy must be a Phronomy::MultiAgent::HandoffPolicy"
23
23
  end
24
24
 
25
25
  @source_agent = source_agent
@@ -1,9 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module Agent
4
+ module MultiAgent
5
5
  class HandoffCapabilityFactory
6
6
  Binding = Data.define(:handoff, :tool_class, :tool_name) do
7
+ def source_agent_id = handoff.source_agent.agent_id
8
+
9
+ def request(arguments:, llm_call_id:, tool_call_id:)
10
+ args = arguments.transform_keys(&:to_sym)
11
+ allowed = [:responsibility] + handoff.policy.selectable_categories.map { |category| :"include_#{category}" }
12
+ raise ArgumentError, "Unknown Handoff argument" unless (args.keys - allowed).empty?
13
+ selection = HandoffPolicy::CATEGORIES.to_h do |category|
14
+ value = if handoff.policy.selectable?(category)
15
+ args.fetch(:"include_#{category}") { handoff.policy.default_include?(category) }
16
+ else
17
+ handoff.policy.required?(category)
18
+ end
19
+ [category, value]
20
+ end
21
+ Phronomy::Agent::ControlRequest.new(target_agent_id: handoff.target_agent.agent_id,
22
+ responsibility: args.fetch(:responsibility), selection: selection,
23
+ llm_call_id: llm_call_id, tool_call_id: tool_call_id)
24
+ end
25
+
7
26
  def initialize(handoff:, tool_class:, tool_name:)
8
27
  super(handoff: handoff, tool_class: tool_class, tool_name: tool_name.to_s.freeze)
9
28
  freeze
@@ -16,7 +35,7 @@ module Phronomy
16
35
  description = handoff.description
17
36
  policy = handoff.policy
18
37
 
19
- klass = Class.new(Phronomy::Agent::Context::Capability::Base) do
38
+ klass = Class.new(Phronomy::Tool::Base) do
20
39
  self.tool_name(tool_name)
21
40
  self.description(description)
22
41
  execution_mode :cooperative