phronomy 0.27.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 (475) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +52 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +9 -3
  6. data/benchmark/bench_agent_invoke.rb +25 -50
  7. data/benchmark/bench_context_assembler.rb +14 -11
  8. data/benchmark/bench_regression.rb +1 -1
  9. data/benchmark/bench_tool_schema.rb +1 -1
  10. data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
  11. data/docs/architecture/agent-transition-ownership.md +13 -89
  12. data/docs/architecture/backend-groups.md +152 -0
  13. data/docs/architecture/before-llm-input.md +3 -3
  14. data/docs/architecture/context-management.md +1 -1
  15. data/docs/architecture/execution-abstraction-review.md +88 -0
  16. data/docs/architecture/execution-boundaries.md +28 -0
  17. data/docs/architecture/execution-metadata-and-values.md +6 -0
  18. data/docs/architecture/multi-agent-handoff.md +6 -5
  19. data/docs/architecture/persistence.md +27 -9
  20. data/docs/architecture/r8-unit1.md +104 -0
  21. data/docs/architecture/r8-unit10.md +72 -0
  22. data/docs/architecture/r8-unit11.md +71 -0
  23. data/docs/architecture/r8-unit12.md +67 -0
  24. data/docs/architecture/r8-unit13.md +61 -0
  25. data/docs/architecture/r8-unit14.md +72 -0
  26. data/docs/architecture/r8-unit15.md +65 -0
  27. data/docs/architecture/r8-unit16.md +54 -0
  28. data/docs/architecture/r8-unit17.md +73 -0
  29. data/docs/architecture/r8-unit18.md +92 -0
  30. data/docs/architecture/r8-unit19.md +61 -0
  31. data/docs/architecture/r8-unit2.md +55 -0
  32. data/docs/architecture/r8-unit20.md +51 -0
  33. data/docs/architecture/r8-unit21.md +79 -0
  34. data/docs/architecture/r8-unit22.md +74 -0
  35. data/docs/architecture/r8-unit23.md +81 -0
  36. data/docs/architecture/r8-unit3.md +130 -0
  37. data/docs/architecture/r8-unit4.md +79 -0
  38. data/docs/architecture/r8-unit5.md +115 -0
  39. data/docs/architecture/r8-unit6.md +71 -0
  40. data/docs/architecture/r8-unit7.md +70 -0
  41. data/docs/architecture/r8-unit8.md +78 -0
  42. data/docs/architecture/r8-unit9.md +73 -0
  43. data/docs/architecture/refactoring-closure.md +10 -1
  44. data/docs/architecture/remaining-refactoring-plan.md +16 -0
  45. data/docs/architecture/storage-execution-boundaries.md +54 -0
  46. data/docs/architecture/tool-schema-recording-gap.md +6 -0
  47. data/docs/architecture/tracing.md +1 -1
  48. data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
  49. data/docs/architecture.md +24 -11
  50. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  51. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  52. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
  53. data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
  54. data/docs/decisions/032-storage-backend-composition.md +5 -0
  55. data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
  56. data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
  57. data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
  58. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
  59. data/docs/decisions/040-configuration-default-composition.md +12 -1
  60. data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
  61. data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
  62. data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
  63. data/docs/decisions/058-neutral-storage-primitives.md +5 -0
  64. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  65. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  66. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  67. data/docs/decisions/062-content-store-service.md +52 -0
  68. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  69. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  70. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  71. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  72. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  73. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  74. data/docs/decisions/README.md +18 -1
  75. data/docs/execution-boundaries-preview.md +45 -0
  76. data/docs/features.md +8 -3
  77. data/docs/getting-started.md +9 -6
  78. data/docs/migrations/0.22.md +2 -2
  79. data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
  80. data/docs/migrations/handoff-runner-multi-agent.md +2 -0
  81. data/docs/migrations/persistence-failure-contracts.md +67 -0
  82. data/docs/migrations/r8-unit10.md +36 -0
  83. data/docs/migrations/r8-unit11.md +33 -0
  84. data/docs/migrations/r8-unit12.md +31 -0
  85. data/docs/migrations/r8-unit13.md +36 -0
  86. data/docs/migrations/r8-unit14.md +31 -0
  87. data/docs/migrations/r8-unit15.md +31 -0
  88. data/docs/migrations/r8-unit16.md +26 -0
  89. data/docs/migrations/r8-unit17.md +28 -0
  90. data/docs/migrations/r8-unit18.md +29 -0
  91. data/docs/migrations/r8-unit19.md +28 -0
  92. data/docs/migrations/r8-unit20.md +24 -0
  93. data/docs/migrations/r8-unit21.md +28 -0
  94. data/docs/migrations/r8-unit22.md +29 -0
  95. data/docs/migrations/r8-unit23.md +20 -0
  96. data/docs/migrations/r8-unit4.md +67 -0
  97. data/docs/migrations/r8-unit5.md +75 -0
  98. data/docs/migrations/r8-unit6.md +109 -0
  99. data/docs/migrations/r8-unit7.md +31 -0
  100. data/docs/migrations/r8-unit8.md +33 -0
  101. data/docs/migrations/r8-unit9.md +28 -0
  102. data/docs/migrations/storage-async-client.md +103 -0
  103. data/docs/migrations/vector-async-clients.md +110 -0
  104. data/docs/persistence-backends.md +38 -21
  105. data/docs/runtime-and-concurrency.md +26 -1
  106. data/lib/phronomy/agent/admission.rb +78 -0
  107. data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
  108. data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
  109. data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
  110. data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
  111. data/lib/phronomy/agent/async_event_api.rb +37 -32
  112. data/lib/phronomy/agent/base.rb +74 -72
  113. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  114. data/lib/phronomy/agent/composition/run_once.rb +1 -1
  115. data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
  116. data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
  117. data/lib/phronomy/agent/control_request.rb +21 -0
  118. data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
  119. data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
  120. data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
  121. data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
  122. data/lib/phronomy/agent/execution/exact_execution.rb +44 -23
  123. data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
  124. data/lib/phronomy/agent/execution/execution_coordinator.rb +214 -395
  125. data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
  126. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +70 -126
  127. data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
  128. data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
  129. data/lib/phronomy/agent/execution/initial_preparation.rb +18 -116
  130. data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
  131. data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
  132. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +7 -7
  133. data/lib/phronomy/agent/execution_change.rb +161 -0
  134. data/lib/phronomy/agent/execution_environment.rb +36 -0
  135. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  136. data/lib/phronomy/agent/execution_observation.rb +35 -0
  137. data/lib/phronomy/agent/filterable.rb +182 -0
  138. data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
  139. data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
  140. data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
  141. data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
  142. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  143. data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
  144. data/lib/phronomy/agent/lifecycle/ownership_registry.rb +18 -23
  145. data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
  146. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  147. data/lib/phronomy/agent/persistence/codec.rb +1 -21
  148. data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
  149. data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
  150. data/lib/phronomy/agent/persistence/records.rb +33 -0
  151. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  152. data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
  153. data/lib/phronomy/agent/persistence/watermark.rb +13 -11
  154. data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
  155. data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +10 -9
  156. data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +27 -27
  157. data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +19 -21
  158. data/lib/phronomy/agent/recovery/recovery_coordinator.rb +23 -16
  159. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  160. data/lib/phronomy/agent/recovery/recovery_support.rb +8 -11
  161. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  162. data/lib/phronomy/agent/retention.rb +17 -0
  163. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  164. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  165. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  166. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  167. data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
  168. data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
  169. data/lib/phronomy/agent/settings.rb +47 -0
  170. data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
  171. data/lib/phronomy/agent/store.rb +253 -0
  172. data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +1 -1
  173. data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
  174. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
  175. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  176. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  177. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  178. data/lib/phronomy/content_store/storage_schema.rb +1 -0
  179. data/lib/phronomy/content_store/stored_contents.rb +2 -1
  180. data/lib/phronomy/context/assembly.rb +202 -0
  181. data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
  182. data/lib/phronomy/context/candidate.rb +51 -0
  183. data/lib/phronomy/context/constraint.rb +47 -0
  184. data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
  185. data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
  186. data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
  187. data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
  188. data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
  189. data/lib/phronomy/context/default_policy.rb +140 -0
  190. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  191. data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
  192. data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
  193. data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
  194. data/lib/phronomy/context/manifest_error.rb +10 -0
  195. data/lib/phronomy/context/prompt_template.rb +98 -0
  196. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  197. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  198. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  199. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  200. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  201. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  202. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  203. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  204. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  205. data/lib/phronomy/embeddings/base.rb +43 -0
  206. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  207. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  208. data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
  209. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  210. data/lib/phronomy/engine/diagnostics.rb +1 -1
  211. data/lib/phronomy/engine/event_loop.rb +7 -7
  212. data/lib/phronomy/engine/execution_binding.rb +23 -0
  213. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  214. data/lib/phronomy/engine/fsm_protocol.rb +1 -6
  215. data/lib/phronomy/engine/fsm_session.rb +3 -3
  216. data/lib/phronomy/engine/metrics.rb +16 -14
  217. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  218. data/lib/phronomy/engine/runtime.rb +25 -12
  219. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  220. data/lib/phronomy/execution/async_operation.rb +41 -0
  221. data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
  222. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
  223. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  224. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  225. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  226. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  227. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  228. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  229. data/lib/phronomy/{engine → execution}/execution.rb +61 -27
  230. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  231. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  232. data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
  233. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  234. data/lib/phronomy/execution/runnable.rb +24 -0
  235. data/lib/phronomy/{engine → execution}/task_result.rb +3 -3
  236. data/lib/phronomy/execution/wait_policy.rb +24 -0
  237. data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +1 -1
  238. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  239. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  240. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
  241. data/lib/phronomy/generation/generator_verifier.rb +7 -7
  242. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  243. data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
  244. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  245. data/lib/phronomy/llm_adapter/base.rb +49 -68
  246. data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
  247. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  248. data/lib/phronomy/llm_adapter/message.rb +62 -0
  249. data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
  250. data/lib/phronomy/llm_adapter/request.rb +79 -0
  251. data/lib/phronomy/llm_adapter/response.rb +36 -0
  252. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  253. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  254. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  255. data/lib/phronomy/llm_adapter.rb +3 -16
  256. data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
  257. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  258. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  259. data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
  260. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
  261. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  262. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
  263. data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
  264. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
  265. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  266. data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
  267. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  268. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  269. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  270. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +58 -42
  271. data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
  272. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  273. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  274. data/lib/phronomy/multi_agent/shared_state.rb +2 -2
  275. data/lib/phronomy/multi_agent/store.rb +156 -0
  276. data/lib/phronomy/multi_agent/team_coordinator.rb +83 -74
  277. data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
  278. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  279. data/lib/phronomy/persistence/content_repository.rb +26 -0
  280. data/lib/phronomy/persistence/errors.rb +16 -0
  281. data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
  282. data/lib/phronomy/persistence/persistence.rb +23 -0
  283. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  284. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  285. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  286. data/lib/phronomy/persistence/transaction.rb +86 -0
  287. data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
  288. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  289. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  290. data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
  291. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  292. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  293. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  294. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  295. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  296. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  297. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  298. data/lib/phronomy/storage/async/async_client.rb +60 -0
  299. data/lib/phronomy/storage/backend.rb +8 -2
  300. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  301. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  302. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
  303. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
  304. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
  305. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
  306. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
  307. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
  308. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
  309. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
  310. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
  311. data/lib/phronomy/tool/authorization.rb +77 -0
  312. data/lib/phronomy/tool/base.rb +549 -9
  313. data/lib/phronomy/tool/call_request.rb +40 -0
  314. data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
  315. data/lib/phronomy/tool/operation.rb +88 -0
  316. data/lib/phronomy/tool/schema.rb +214 -0
  317. data/lib/phronomy/tool/settings.rb +31 -0
  318. data/lib/phronomy/tool/tool_error.rb +7 -0
  319. data/lib/phronomy/tool/tool_executor.rb +58 -0
  320. data/lib/phronomy/tools/agent.rb +22 -90
  321. data/lib/phronomy/tools/mcp.rb +21 -14
  322. data/lib/phronomy/tools/vector_search.rb +4 -4
  323. data/lib/phronomy/tracing/automatic.rb +11 -6
  324. data/lib/phronomy/tracing/base.rb +1 -1
  325. data/lib/phronomy/tracing/observation.rb +37 -0
  326. data/lib/phronomy/tracing/settings.rb +36 -0
  327. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  328. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  329. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  330. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  331. data/lib/phronomy/vector_store/base.rb +118 -49
  332. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  333. data/lib/phronomy/vector_store.rb +2 -17
  334. data/lib/phronomy/version.rb +1 -1
  335. data/lib/phronomy/workflow/completion.rb +11 -0
  336. data/lib/phronomy/workflow/execution/workflow.rb +7 -7
  337. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  338. data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
  339. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  340. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +3 -5
  341. data/lib/phronomy/workflow/execution/workflow_runner.rb +58 -79
  342. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  343. data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
  344. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  345. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  346. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  347. data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
  348. data/lib/phronomy.rb +57 -16
  349. data/scripts/api_snapshot.rb +35 -15
  350. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  351. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  352. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  353. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  354. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  355. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  356. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  357. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  358. data/sig/phronomy/agent/admission.rbs +19 -0
  359. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  360. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  361. data/sig/phronomy/agent/store.rbs +73 -0
  362. data/sig/phronomy/agent.rbs +14 -138
  363. data/sig/phronomy/common.rbs +7 -0
  364. data/sig/phronomy/content_store/contracts.rbs +13 -0
  365. data/sig/phronomy/context.rbs +152 -0
  366. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  367. data/sig/phronomy/embeddings/base.rbs +17 -0
  368. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  369. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  370. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  371. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  372. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  373. data/sig/phronomy/engine/observability.rbs +13 -0
  374. data/sig/phronomy/execution/async_operation.rbs +7 -0
  375. data/sig/phronomy/execution/blocking.rbs +5 -0
  376. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  377. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  378. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  379. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  380. data/sig/phronomy/execution/errors.rbs +7 -0
  381. data/sig/phronomy/execution/execution.rbs +41 -0
  382. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  383. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  384. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  385. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  386. data/sig/phronomy/execution/task_result.rbs +29 -0
  387. data/sig/phronomy/extensions.rbs +0 -21
  388. data/sig/phronomy/handoff.rbs +10 -4
  389. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  390. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  391. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  392. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  393. data/sig/phronomy/llm_context_window.rbs +10 -0
  394. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  395. data/sig/phronomy/multi_agent/store.rbs +54 -0
  396. data/sig/phronomy/multi_agent.rbs +6 -3
  397. data/sig/phronomy/persistence/errors.rbs +18 -0
  398. data/sig/phronomy/persistence/migration.rbs +13 -0
  399. data/sig/phronomy/persistence/transaction.rbs +19 -0
  400. data/sig/phronomy/persistence.rbs +1 -99
  401. data/sig/phronomy/persistence_composition.rbs +19 -0
  402. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  403. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  404. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  405. data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +2 -16
  406. data/sig/phronomy/tool.rbs +48 -4
  407. data/sig/phronomy/tracing/observation.rbs +7 -0
  408. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  409. data/sig/phronomy/vector_store/base.rbs +23 -0
  410. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  411. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  412. data/sig/phronomy/workflow/store.rbs +8 -0
  413. data/sig/phronomy/workflow.rbs +1 -1
  414. data/sig/phronomy.rbs +0 -49
  415. metadata +318 -117
  416. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  417. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  418. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  419. data/lib/phronomy/agent/context/capability/base.rb +0 -548
  420. data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
  421. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  422. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
  423. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
  424. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  425. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  426. data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
  427. data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
  428. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
  429. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
  430. data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
  431. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
  432. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
  433. data/lib/phronomy/agent/persistence/queries.rb +0 -61
  434. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  435. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  436. data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
  437. data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
  438. data/lib/phronomy/configuration/configuration.rb +0 -68
  439. data/lib/phronomy/configuration/global_configuration.rb +0 -26
  440. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  441. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  442. data/lib/phronomy/engine/execution_receiver.rb +0 -65
  443. data/lib/phronomy/engine/runnable.rb +0 -54
  444. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  445. data/lib/phronomy/llm_contract/token_usage.rb +0 -85
  446. data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
  447. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
  448. data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
  449. data/lib/phronomy/persistence/api/persistence.rb +0 -140
  450. data/lib/phronomy/persistence_composition/repositories.rb +0 -77
  451. data/lib/phronomy/recovery/recovery.rb +0 -186
  452. data/lib/phronomy/tool/contract/tool_error.rb +0 -7
  453. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  454. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  455. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  456. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
  457. data/sig/phronomy/execution_receiver.rbs +0 -34
  458. data/sig/phronomy/llm_adapter.rbs +0 -11
  459. data/sig/phronomy/runtime.rbs +0 -76
  460. /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
  461. /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
  462. /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
  463. /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
  464. /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
  465. /data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +0 -0
  466. /data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +0 -0
  467. /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
  468. /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
  469. /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
  470. /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
  471. /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
  472. /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
  473. /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
  474. /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
  475. /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
@@ -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,18 +2,11 @@
2
2
 
3
3
  module Phronomy
4
4
  module MultiAgent
5
- # Runtime-local admission for one synchronous coordination call per owner
5
+ # Environment-local admission for one synchronous coordination call per owner
6
6
  # (a Handoff main Agent or TeamCoordinator). This does not own durable runs
7
7
  # or active-agent mutation; those remain in their existing domains.
8
8
  # @api private
9
9
  class AdmissionRegistry
10
- # Construction has no side effects. Runtime returns the registered instance
11
- # when callers concurrently supply candidates for the same key.
12
- # @api private
13
- def self.for(runtime)
14
- runtime.__register_shutdown_participant(key: self, participant: new)
15
- end
16
-
17
10
  def initialize
18
11
  @mutex = Mutex.new
19
12
  @cond = ConditionVariable.new
@@ -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::Storage::NotFoundError
20
- next
21
- end
22
- raise Phronomy::Storage::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::ExecutionMetadata::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::Storage::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::Storage::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
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Phronomy
6
+ module MultiAgent
7
+ # Routing and Agent terminal state share the same outer transaction. This
8
+ # participant sees public semantic values only, never Agent records.
9
+ # @api private
10
+ class HandoffParticipant
11
+ def initialize(persistence:, main_agent_id:, expected_revision:)
12
+ @persistence, @main_agent_id, @expected_revision = persistence, main_agent_id, expected_revision
13
+ end
14
+
15
+ def binding
16
+ Phronomy::Agent::ExecutionExtensionState.new(binding_key: "phronomy.handoff", binding_version: 1)
17
+ end
18
+
19
+ def guard_change(change, scope)
20
+ @persistence.participate(scope) { |records| records.handoff_states.load_locked(@main_agent_id) }
21
+ ids = [@main_agent_id, change.agent_id, change.control_request&.target_agent_id].compact
22
+ @persistence.agent_store.guard_agents(scope, agent_ids: ids)
23
+ end
24
+
25
+ def change_evidence(change, scope)
26
+ state = @persistence.participate(scope) { |records| records.handoff_states.load(@main_agent_id) }
27
+ ids = [@main_agent_id, change.agent_id, change.control_request&.target_agent_id].compact.uniq.sort
28
+ [state&.to_h, ids.map { |id| @persistence.agent_store.retained_references(agent_id: id, scope: scope).map(&:to_h) }]
29
+ end
30
+
31
+ def commit(change)
32
+ @persistence.coordinator.atomic do |scope|
33
+ guard_change(change, scope)
34
+ change.prepare_in(scope)
35
+ @persistence.participate(scope) do |records|
36
+ routing = records.handoff_states.load(@main_agent_id)
37
+ unless routing && routing.active_agent_id == change.agent_id
38
+ raise Phronomy::Persistence::StateConflictError, "Handoff no longer owns this execution"
39
+ end
40
+ receipt = nil
41
+ if change.kind == :terminal && change.control_request
42
+ if Array(routing.metadata["cancelled_execution_ids"]).include?(change.execution_id) || change.cancel_requested
43
+ raise Phronomy::CancellationError, "Handoff Source turn was cancelled"
44
+ end
45
+ unless routing.handoff_revision == @expected_revision
46
+ raise Phronomy::Persistence::StateConflictError, "Handoff routing changed before Source transfer"
47
+ end
48
+ request = change.control_request
49
+ context = change.transfer_context(scope)
50
+ ref = records.contents.put_json(context.to_h)
51
+ target_id = "handoff-target-#{Digest::SHA256.hexdigest([change.execution_id, request.target_agent_id].join("\0"))}"
52
+ definition = @persistence.agent_store.definition(agent_id: request.target_agent_id, scope: scope)
53
+ retain(scope, change.agent_id, change.execution_id)
54
+ retain(scope, request.target_agent_id, target_id)
55
+ receipt = {"target_agent_id" => request.target_agent_id, "target_execution_id" => target_id,
56
+ "context_ref" => ref, "owner_key" => "handoff:#{@main_agent_id}"}.freeze
57
+ ids = (Array(routing.metadata["retained_agent_ids"]) + [change.agent_id, request.target_agent_id]).uniq.sort
58
+ transfer = routing.with(active_agent_id: request.target_agent_id, active_handoff_context_ref: ref,
59
+ phase: "target_pending", pending_source_execution_id: change.execution_id, pending_target_execution_id: target_id,
60
+ metadata: routing.metadata.merge("target_definition" => definition.transform_keys(&:to_s),
61
+ "source_agent_id" => change.agent_id, "retained_agent_ids" => ids))
62
+ records.handoff_states.save(@main_agent_id, expected_revision: routing.handoff_revision, state: transfer)
63
+ elsif change.finishing? && routing.phase != "stable" && routing.pending_target_execution_id == change.execution_id
64
+ records.handoff_states.save(@main_agent_id, expected_revision: routing.handoff_revision, state: routing.with(phase: "stable"))
65
+ end
66
+ change.commit_in(scope, transfer_receipt: receipt)
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def retain(scope, id, execution_id)
74
+ @persistence.agent_store.retain(Phronomy::Agent::Retention.new(agent_id: id, execution_id: execution_id,
75
+ owner_key: "handoff:#{@main_agent_id}"), scope: scope)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module Agent
4
+ module MultiAgent
5
5
  class HandoffPolicy
6
6
  CATEGORIES = %i[current_request history knowledge tool_exchanges].freeze
7
7
  MODES = %i[required forbidden selectable].freeze