phronomy 0.25.0 → 0.27.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 (323) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -0
  3. data/CONTRIBUTING.md +4 -4
  4. data/README.md +14 -7
  5. data/VERIFY.sh +27 -27
  6. data/benchmark/bench_agent_invoke.rb +26 -22
  7. data/benchmark/bench_context_assembler.rb +4 -5
  8. data/benchmark/bench_regression.rb +1 -1
  9. data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
  10. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  11. data/docs/architecture/agent-context.md +5 -3
  12. data/docs/architecture/agent-transition-ownership.md +89 -0
  13. data/docs/architecture/before-llm-input.md +6 -0
  14. data/docs/architecture/context-management.md +30 -0
  15. data/docs/architecture/context-preparation-steps.md +90 -0
  16. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  17. data/docs/architecture/execution-metadata-and-values.md +80 -0
  18. data/docs/architecture/generator-verifier-ownership.md +111 -0
  19. data/docs/architecture/multi-agent-handoff.md +8 -2
  20. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  21. data/docs/architecture/persistence.md +29 -6
  22. data/docs/architecture/refactoring-closure.md +334 -0
  23. data/docs/architecture/remaining-refactoring-plan.md +374 -0
  24. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  25. data/docs/architecture/tool-schema-recording-gap.md +50 -0
  26. data/docs/architecture/tracing.md +4 -4
  27. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  28. data/docs/architecture.md +209 -0
  29. data/docs/async-composition.md +205 -0
  30. data/docs/decisions/010-cooperative-first-concurrency.md +23 -23
  31. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  32. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  33. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  34. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  35. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
  36. data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
  37. data/docs/decisions/032-storage-backend-composition.md +80 -0
  38. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  39. data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -0
  40. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  41. data/docs/decisions/036-context-contract-ownership.md +86 -0
  42. data/docs/decisions/037-common-definition-ownership.md +62 -0
  43. data/docs/decisions/038-responsibility-based-source-layout.md +131 -0
  44. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
  45. data/docs/decisions/040-configuration-default-composition.md +94 -0
  46. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  47. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  48. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  49. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  50. data/docs/decisions/045-worker-input-restriction-ownership.md +100 -0
  51. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  52. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  53. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  54. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  55. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  56. data/docs/decisions/051-execution-outcome-worker-ownership.md +151 -0
  57. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  58. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  59. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  60. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  61. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  62. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  63. data/docs/decisions/058-neutral-storage-primitives.md +78 -0
  64. data/docs/decisions/README.md +35 -6
  65. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  66. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  67. data/docs/features.md +21 -12
  68. data/docs/getting-started.md +10 -10
  69. data/docs/migrations/0.15.md +5 -0
  70. data/docs/migrations/durable-semantic-coordination-v2.md +7 -1
  71. data/docs/migrations/handoff-runner-multi-agent.md +35 -0
  72. data/docs/migrations/neutral-storage-spi.md +43 -0
  73. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  74. data/docs/migrations/shared-state-multi-agent.md +44 -0
  75. data/docs/migrations/storage-backend-composition.md +117 -0
  76. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  77. data/docs/persistence-backends.md +162 -204
  78. data/docs/runtime-and-concurrency.md +205 -60
  79. data/lib/phronomy/agent/api/agent.rb +17 -0
  80. data/lib/phronomy/agent/async_event_api.rb +2 -2
  81. data/lib/phronomy/agent/base.rb +55 -251
  82. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  83. data/lib/phronomy/agent/context/capability/base.rb +67 -26
  84. data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
  85. data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
  86. data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
  87. data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
  88. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
  89. data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
  90. data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
  91. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
  92. data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
  93. data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
  94. data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
  95. data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
  96. data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
  97. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  98. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
  99. data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +20 -96
  100. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  101. data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
  102. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +12 -13
  103. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
  104. data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
  105. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  106. data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
  107. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
  108. data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
  109. data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
  110. data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
  111. data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
  112. data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +22 -73
  113. data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
  114. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  115. data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
  116. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
  117. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
  118. data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
  119. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  120. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  121. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  122. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  123. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  124. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -10
  125. data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
  126. data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
  127. data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
  128. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
  129. data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
  130. data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
  131. data/lib/phronomy/agent/persistence/codec.rb +358 -0
  132. data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
  133. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
  134. data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
  135. data/lib/phronomy/agent/persistence/queries.rb +61 -0
  136. data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
  137. data/lib/phronomy/agent/persistence/watermark.rb +27 -0
  138. data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
  139. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
  140. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +36 -41
  141. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
  142. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +9 -13
  143. data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
  144. data/lib/phronomy/agent/selection/candidate.rb +1 -1
  145. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  146. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  147. data/lib/phronomy/agent/tool_execution/tool_binding.rb +90 -0
  148. data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
  149. data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
  150. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
  151. data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +2 -2
  152. data/lib/phronomy/common/configuration_error.rb +7 -0
  153. data/lib/phronomy/common/error.rb +5 -0
  154. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  155. data/lib/phronomy/common/values/serializable.rb +32 -0
  156. data/lib/phronomy/{configuration.rb → configuration/configuration.rb} +14 -5
  157. data/lib/phronomy/configuration/global_configuration.rb +26 -0
  158. data/lib/phronomy/content_store/storage_schema.rb +11 -0
  159. data/lib/phronomy/content_store/stored_contents.rb +43 -0
  160. data/lib/phronomy/engine/backpressure_error.rb +7 -0
  161. data/lib/phronomy/{blocking.rb → engine/blocking.rb} +15 -8
  162. data/lib/phronomy/engine/cancellation_error.rb +7 -0
  163. data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
  164. data/lib/phronomy/engine/concurrency/offload_pool.rb +29 -16
  165. data/lib/phronomy/engine/concurrency/operation_binding.rb +43 -0
  166. data/lib/phronomy/engine/concurrency/physical_completion_task.rb +4 -62
  167. data/lib/phronomy/engine/concurrency/result_collector.rb +99 -0
  168. data/lib/phronomy/engine/concurrency/result_composition.rb +145 -0
  169. data/lib/phronomy/engine/concurrency/subscriptions.rb +68 -0
  170. data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
  171. data/lib/phronomy/engine/event_loop.rb +160 -626
  172. data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
  173. data/lib/phronomy/engine/execution.rb +229 -0
  174. data/lib/phronomy/engine/execution_cancellation_error.rb +14 -0
  175. data/lib/phronomy/engine/execution_receiver.rb +65 -0
  176. data/lib/phronomy/engine/execution_timeout_error.rb +14 -0
  177. data/lib/phronomy/engine/fsm_protocol.rb +14 -0
  178. data/lib/phronomy/engine/fsm_session.rb +38 -31
  179. data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +3 -1
  180. data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +3 -1
  181. data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +3 -1
  182. data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +13 -1
  183. data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
  184. data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
  185. data/lib/phronomy/engine/runtime/timer_queue.rb +11 -0
  186. data/lib/phronomy/engine/runtime.rb +80 -84
  187. data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
  188. data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
  189. data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
  190. data/lib/phronomy/engine/{task.rb → task_result.rb} +101 -42
  191. data/lib/phronomy/engine/timeout_error.rb +7 -0
  192. data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
  193. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  194. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  195. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  196. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  197. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  198. data/lib/phronomy/llm_adapter/base.rb +2 -2
  199. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  200. data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
  201. data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
  202. data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
  203. data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
  204. data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
  205. data/lib/phronomy/llm_contract/transport_error.rb +7 -0
  206. data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
  207. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +8 -8
  208. data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
  209. data/lib/phronomy/multi_agent/orchestrator.rb +36 -68
  210. data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
  211. data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
  212. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
  213. data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
  214. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  215. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
  216. data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
  217. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  218. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
  219. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  220. data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
  221. data/lib/phronomy/persistence/api/persistence.rb +140 -0
  222. data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
  223. data/lib/phronomy/persistence_composition/repositories.rb +77 -0
  224. data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
  225. data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
  226. data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
  227. data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
  228. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  229. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  230. data/lib/phronomy/storage/backend.rb +101 -0
  231. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  232. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  233. data/lib/phronomy/storage/blobs.rb +31 -0
  234. data/lib/phronomy/storage/condition.rb +25 -0
  235. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  236. data/lib/phronomy/storage/conflict_error.rb +9 -0
  237. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  238. data/lib/phronomy/storage/entry.rb +33 -0
  239. data/lib/phronomy/storage/guard_ref.rb +13 -0
  240. data/lib/phronomy/storage/not_found_error.rb +9 -0
  241. data/lib/phronomy/storage/record_codec.rb +177 -0
  242. data/lib/phronomy/storage/records.rb +61 -0
  243. data/lib/phronomy/storage/resource.rb +126 -0
  244. data/lib/phronomy/storage/scope.rb +25 -0
  245. data/lib/phronomy/storage/serialization_error.rb +9 -0
  246. data/lib/phronomy/storage/streams.rb +44 -0
  247. data/lib/phronomy/storage/transaction_error.rb +10 -0
  248. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  249. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  250. data/lib/phronomy/storage/validation.rb +53 -0
  251. data/lib/phronomy/storage/view.rb +131 -0
  252. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +5 -3
  253. data/lib/phronomy/testing/fake_clock.rb +13 -9
  254. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  255. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
  256. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
  257. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
  258. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
  259. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
  260. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
  261. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  262. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
  263. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  264. data/lib/phronomy/tool/contract/tool_error.rb +7 -0
  265. data/lib/phronomy/tools/agent.rb +6 -6
  266. data/lib/phronomy/vector_store/async_backend.rb +5 -5
  267. data/lib/phronomy/vector_store/embeddings/base.rb +2 -2
  268. data/lib/phronomy/version.rb +1 -1
  269. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
  270. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
  271. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  272. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
  273. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +126 -75
  274. data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
  275. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  276. data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
  277. data/lib/phronomy/workflow/phase_machine_builder.rb +8 -8
  278. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
  279. data/lib/phronomy.rb +52 -94
  280. data/scripts/api_snapshot.rb +4 -2
  281. data/scripts/storage_spi_snapshot.rb +36 -0
  282. data/sig/phronomy/agent.rbs +4 -5
  283. data/sig/phronomy/execution_receiver.rbs +34 -0
  284. data/sig/phronomy/extensions.rbs +5 -5
  285. data/sig/phronomy/handoff.rbs +4 -2
  286. data/sig/phronomy/multi_agent.rbs +19 -3
  287. data/sig/phronomy/persistence.rbs +7 -91
  288. data/sig/phronomy/runtime.rbs +34 -7
  289. data/sig/phronomy/storage.rbs +174 -0
  290. data/sig/phronomy/tool.rbs +11 -2
  291. data/sig/phronomy/workflow.rbs +1 -1
  292. metadata +236 -99
  293. data/lib/phronomy/agent/execution_coordinator.rb +0 -3138
  294. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  295. data/lib/phronomy/agent/recovery_support.rb +0 -504
  296. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  297. data/lib/phronomy/agent/tool_executor.rb +0 -55
  298. data/lib/phronomy/generator_verifier.rb +0 -369
  299. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  300. data/lib/phronomy/multi_agent/fan_out_invocation.rb +0 -137
  301. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +0 -118
  302. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  303. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  304. data/lib/phronomy/persistence/in_memory.rb +0 -690
  305. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  306. data/lib/phronomy/persistence.rb +0 -276
  307. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  308. data/lib/phronomy/workflow_recovery.rb +0 -123
  309. /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
  310. /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
  311. /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
  312. /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
  313. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  314. /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
  315. /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
  316. /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
  317. /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
  318. /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
  319. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  320. /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
  321. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
  322. /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
  323. /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class GeneratorVerifier
5
+ class PipelineState
6
+ include Phronomy::WorkflowContext
7
+
8
+ field :input, type: :replace, default: -> { "" }
9
+ field :draft, type: :replace, default: -> { {} }
10
+ field :self_score, type: :replace, default: -> { 0.0 }
11
+ field :review_score, type: :replace, default: -> { 0.0 }
12
+ field :citations, type: :replace, default: -> { [] }
13
+ field :review_notes, type: :append, default: -> { [] }
14
+ field :iteration, type: :replace, default: -> { 0 }
15
+ field :approved, type: :replace, default: -> { false }
16
+ field :output, type: :replace, default: -> { {} }
17
+ field :draft_request_id, type: :replace, default: nil
18
+ field :review_request_id, type: :replace, default: nil
19
+ field :pipeline_error, type: :replace, default: nil
20
+
21
+ # Application-level event interpretation. Correlation IDs belong to this
22
+ # Pipeline rather than to the generic FSMSession.
23
+ def handle_fsm_event(event)
24
+ case event.type
25
+ when :draft_completed
26
+ return :consume unless event.payload[:request_id] == draft_request_id
27
+
28
+ self.draft = event.payload[:draft]
29
+ self.self_score = event.payload[:self_score]
30
+ self.citations = event.payload[:citations]
31
+ self.iteration = iteration + 1
32
+ self.draft_request_id = nil
33
+ self.pipeline_error = nil
34
+ when :review_completed
35
+ return :consume unless event.payload[:request_id] == review_request_id
36
+
37
+ self.review_score = event.payload[:review_score]
38
+ self.approved = event.payload[:approved]
39
+ self.review_notes = review_notes + [event.payload[:feedback]]
40
+ self.review_request_id = nil
41
+ self.pipeline_error = nil
42
+ when :draft_failed
43
+ return :consume unless event.payload[:request_id] == draft_request_id
44
+
45
+ self.pipeline_error = event.payload[:error]
46
+ self.draft_request_id = nil
47
+ when :review_failed
48
+ return :consume unless event.payload[:request_id] == review_request_id
49
+
50
+ self.pipeline_error = event.payload[:error]
51
+ self.review_request_id = nil
52
+ end
53
+ false
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Phronomy
6
+ class GeneratorVerifier
7
+ # Builds the generation/review loop; result reception does not mutate state.
8
+ class WorkflowBuilder
9
+ def initialize(
10
+ draft_agent:, review_agent:,
11
+ draft_prompt_builder:, review_prompt_builder:,
12
+ draft_result_parser:, review_result_parser:,
13
+ threshold:, max_iterations:
14
+ )
15
+ @draft_agent = draft_agent
16
+ @review_agent = review_agent
17
+ @draft_prompt_builder = draft_prompt_builder
18
+ @review_prompt_builder = review_prompt_builder
19
+ @threshold = threshold
20
+ @max_iterations = max_iterations
21
+ @result_receiver = AgentResultReceiver.new(
22
+ draft_result_parser: draft_result_parser,
23
+ review_result_parser: review_result_parser
24
+ )
25
+ end
26
+
27
+ def build
28
+ # The entry closures see the Workflow assigned after definition.
29
+ workflow = nil
30
+ draft_entry = ->(state) { start_draft(state, workflow) }
31
+ review_entry = ->(state) { start_review(state, workflow) }
32
+ finalize_entry = method(:finalize)
33
+ failed_entry = method(:fail_pipeline)
34
+ ready_to_finalize = method(:ready_to_finalize?)
35
+
36
+ workflow = Phronomy::Workflow.define(PipelineState) do
37
+ initial :draft
38
+
39
+ state :draft
40
+ state :review
41
+ state :finalize
42
+ state :failed
43
+
44
+ entry :draft, draft_entry
45
+ entry :review, review_entry
46
+ entry :finalize, finalize_entry
47
+ entry :failed, failed_entry
48
+
49
+ transition from: :draft, on: :draft_completed, to: :review
50
+ transition from: :draft, on: :draft_failed, to: :failed
51
+ transition from: :review,
52
+ on: :review_completed,
53
+ guard: ready_to_finalize,
54
+ to: :finalize
55
+ transition from: :review, on: :review_completed, to: :draft
56
+ transition from: :review, on: :review_failed, to: :failed
57
+
58
+ transition from: :finalize, to: :__finish__
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def start_draft(state, workflow)
65
+ request_id = SecureRandom.uuid
66
+ next_state = state.merge(draft_request_id: request_id)
67
+ feedback = next_state.review_notes.last
68
+ prompt = @draft_prompt_builder.call(next_state.input, feedback)
69
+ listener = @result_receiver.draft_listener(
70
+ workflow: workflow,
71
+ workflow_instance_id: next_state.workflow_instance_id,
72
+ request_id: request_id
73
+ )
74
+ @draft_agent.send(:__invoke_async_with_event_sink, prompt, on_event: listener)
75
+ next_state
76
+ end
77
+
78
+ def start_review(state, workflow)
79
+ request_id = SecureRandom.uuid
80
+ next_state = state.merge(review_request_id: request_id)
81
+ prompt = @review_prompt_builder.call(
82
+ next_state.input, next_state.draft, next_state.citations
83
+ )
84
+ listener = @result_receiver.review_listener(
85
+ workflow: workflow,
86
+ workflow_instance_id: next_state.workflow_instance_id,
87
+ request_id: request_id
88
+ )
89
+ @review_agent.send(:__invoke_async_with_event_sink, prompt, on_event: listener)
90
+ next_state
91
+ end
92
+
93
+ def ready_to_finalize?(state)
94
+ confidence = [state.self_score || 0.0, state.review_score || 0.0].min
95
+ (confidence >= @threshold && state.approved) ||
96
+ state.iteration >= @max_iterations
97
+ end
98
+
99
+ def finalize(state)
100
+ state.output = state.draft
101
+ state
102
+ end
103
+
104
+ def fail_pipeline(state)
105
+ raise(
106
+ state.pipeline_error ||
107
+ Phronomy::Error.new("GeneratorVerifier Agent operation failed")
108
+ )
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Implements the Generator-Verifier multi-agent coordination pattern.
5
+ #
6
+ # Agent completion is integrated through application-defined Workflow events;
7
+ # Workflow entry actions do not return or await Agent Tasks.
8
+ class GeneratorVerifier
9
+ DEFAULT_CONFIDENCE_THRESHOLD = 0.7
10
+ DEFAULT_MAX_ITERATIONS = 3
11
+
12
+ Result = Struct.new(
13
+ :output,
14
+ :confidence,
15
+ :citations,
16
+ :iterations,
17
+ :review_notes,
18
+ :trusted
19
+ ) do
20
+ alias_method :trusted?, :trusted
21
+ end
22
+
23
+ private_constant :PipelineState, :WorkflowBuilder, :AgentResultReceiver
24
+
25
+ def initialize(
26
+ draft_agent:,
27
+ review_agent:,
28
+ draft_prompt_builder:,
29
+ review_prompt_builder:,
30
+ draft_result_parser: nil,
31
+ review_result_parser: nil,
32
+ confidence_threshold: DEFAULT_CONFIDENCE_THRESHOLD,
33
+ max_iterations: DEFAULT_MAX_ITERATIONS,
34
+ raise_if_untrusted: false
35
+ )
36
+ @draft_agent_class = draft_agent
37
+ @review_agent_class = review_agent
38
+ @draft_prompt_builder = draft_prompt_builder
39
+ @review_prompt_builder = review_prompt_builder
40
+ @draft_result_parser =
41
+ draft_result_parser || method(:default_parse_draft)
42
+ @review_result_parser =
43
+ review_result_parser || method(:default_parse_review)
44
+ @threshold = confidence_threshold.to_f
45
+ @max_iterations = max_iterations.to_i
46
+ @raise_if_untrusted = raise_if_untrusted
47
+ @compiled_workflow = nil
48
+ end
49
+
50
+ def invoke(input, config: {})
51
+ state = compiled_workflow.invoke({input: input}, config: config)
52
+ confidence = combined_confidence(state)
53
+ trusted = confidence >= @threshold
54
+ result = Result.new(
55
+ output: state.output || state.draft.to_s,
56
+ confidence: confidence,
57
+ citations: state.citations,
58
+ iterations: state.iteration,
59
+ review_notes: state.review_notes,
60
+ trusted: trusted
61
+ )
62
+ if @raise_if_untrusted && !trusted
63
+ raise LowConfidenceError.new(result)
64
+ end
65
+ result
66
+ end
67
+
68
+ private
69
+
70
+ def combined_confidence(state)
71
+ [
72
+ (state.self_score || 0.0).to_f,
73
+ (state.review_score || 0.0).to_f
74
+ ].min
75
+ end
76
+
77
+ def compiled_workflow
78
+ @compiled_workflow ||= build_workflow
79
+ end
80
+
81
+ def build_workflow
82
+ WorkflowBuilder.new(
83
+ draft_agent: @draft_agent_class.new,
84
+ review_agent: @review_agent_class.new,
85
+ draft_prompt_builder: @draft_prompt_builder,
86
+ review_prompt_builder: @review_prompt_builder,
87
+ draft_result_parser: @draft_result_parser,
88
+ review_result_parser: @review_result_parser,
89
+ threshold: @threshold,
90
+ max_iterations: @max_iterations
91
+ ).build
92
+ end
93
+
94
+ def default_parse_draft(text)
95
+ json_parser.parse(text)
96
+ rescue Phronomy::ParseError
97
+ {
98
+ answer: text.to_s,
99
+ confidence: 0.0,
100
+ citations: []
101
+ }
102
+ end
103
+
104
+ def default_parse_review(text)
105
+ json_parser.parse(text)
106
+ rescue Phronomy::ParseError
107
+ {
108
+ approved: false,
109
+ score: 0.0,
110
+ feedback: "Review output could not be parsed: #{text}"
111
+ }
112
+ end
113
+
114
+ def json_parser
115
+ @json_parser ||= Phronomy::OutputParser::JsonParser.new
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ class LowConfidenceError < Error
7
+ attr_reader :result
8
+
9
+ def initialize(result)
10
+ @result = result
11
+ super("Answer confidence #{result.confidence} is below the required threshold")
12
+ end
13
+ end
14
+ end
@@ -52,7 +52,7 @@ module Phronomy
52
52
  # Transport timeout and retry remain the responsibility of the adapter or
53
53
  # provider client; Phronomy does not attach an additional operation timeout.
54
54
  #
55
- # @return [Phronomy::Task] caller-facing completion handle
55
+ # @return [Phronomy::TaskResult] caller-facing completion handle
56
56
  # @api private
57
57
  def complete_async(chat, message, config: {}, pool: default_pool)
58
58
  token = config[:cancellation_token]
@@ -68,7 +68,7 @@ module Phronomy
68
68
  # application callbacks must never be passed directly to this method.
69
69
  #
70
70
  # @yield [chunk] streaming chunk on the worker thread
71
- # @return [Phronomy::Task] caller-facing completion handle
71
+ # @return [Phronomy::TaskResult] caller-facing completion handle
72
72
  # @api private
73
73
  def stream_async(chat, message, config: {}, pool: default_pool, &block)
74
74
  raise ArgumentError, "stream_async requires a block" unless block
@@ -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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "transport_error"
4
+
5
+ module Phronomy
6
+ class AuthenticationError < TransportError; end
7
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../common/error"
4
+
3
5
  module Phronomy
4
6
  # Raised when mandatory context content (system instructions, tool definitions,
5
7
  # current input) exhausts the model's context window, leaving no room for
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ class ContextLengthError < Error; end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "transport_error"
4
+
5
+ module Phronomy
6
+ class RateLimitError < TransportError; end
7
+ end
@@ -43,8 +43,8 @@ module Phronomy
43
43
  new(
44
44
  input: tokens.input || 0,
45
45
  output: tokens.output || 0,
46
- cached: tokens.cached || 0,
47
- cache_creation: tokens.cache_creation || 0
46
+ cached: tokens.cache_read || 0,
47
+ cache_creation: tokens.cache_write || 0
48
48
  )
49
49
  end
50
50
 
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/error"
4
+
5
+ module Phronomy
6
+ class TransportError < Error; end
7
+ end
@@ -2,18 +2,32 @@
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
+ # Runtime-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
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
+
8
17
  def initialize
9
18
  @mutex = Mutex.new
10
19
  @cond = ConditionVariable.new
11
20
  @owners = {}
21
+ @draining = false
12
22
  end
13
23
 
14
24
  def admit!(coordinator)
15
25
  key = coordinator.object_id
16
26
  @mutex.synchronize do
27
+ if @draining
28
+ raise Phronomy::RuntimeShutdownError,
29
+ "Runtime is shutting down or failed; new Multi-Agent turns are not accepted"
30
+ end
17
31
  if @owners.key?(key)
18
32
  raise Phronomy::HandoffError,
19
33
  "a Multi-Agent turn is already active for this main Agent instance"
@@ -36,6 +50,12 @@ module Phronomy
36
50
  @mutex.synchronize { @owners.empty? }
37
51
  end
38
52
 
53
+ # Close admission atomically with respect to admit!, without waiting for
54
+ # admitted callers or calling back into Runtime. Repeated closure is safe.
55
+ def begin_draining
56
+ @mutex.synchronize { @draining = true }
57
+ end
58
+
39
59
  def wait_until_idle(deadline)
40
60
  @mutex.synchronize do
41
61
  until @owners.empty?
@@ -4,7 +4,7 @@ require "securerandom"
4
4
 
5
5
  module Phronomy
6
6
  module MultiAgent
7
- # Agent-owned transactions reserve child work; Task callbacks only wake observers.
7
+ # Agent-owned transactions reserve child work; TaskResult callbacks only wake observers.
8
8
  # @api private
9
9
  class DurableSubagentCoordinator
10
10
  KEY = "multi_agent_coordination_ref"
@@ -16,13 +16,13 @@ module Phronomy
16
16
  children.each do |child|
17
17
  begin
18
18
  exact = tx.executions.load(child.fetch("execution_id"))
19
- rescue Phronomy::Persistence::NotFoundError
19
+ rescue Phronomy::Storage::NotFoundError
20
20
  next
21
21
  end
22
- raise Phronomy::Persistence::ConflictError, "Child owner mismatch" unless exact.agent_id == child.fetch("agent_id")
22
+ raise Phronomy::Storage::ConflictError, "Child owner mismatch" unless exact.agent_id == child.fetch("agent_id")
23
23
  child.merge!("state" => exact.status.to_s, "result_ref" => exact.result_ref, "error_ref" => exact.error_ref)
24
24
  end
25
- Array(execution.metadata[Phronomy::Agent::RecoverySupport::TOOL_BATCH_METADATA_KEY]).each do |tool|
25
+ Array(execution.metadata[Phronomy::Agent::ExecutionMetadata::TOOL_BATCH_METADATA_KEY]).each do |tool|
26
26
  registration = parent.class.registered_subagents.find { |name, _| "dispatch_to_#{name}" == tool.fetch("tool_name") }
27
27
  next unless registration
28
28
  next if children.any? { |child| child.fetch("slot") == tool.fetch("tool_invocation_id") }
@@ -47,10 +47,10 @@ module Phronomy
47
47
 
48
48
  def self.start(parent:, tool_invocation_id:, parent_execution_id:, config:)
49
49
  runtime = Phronomy::Runtime.instance
50
- completion = Phronomy::Task.deferred(name: "durable-subagent:#{tool_invocation_id}")
50
+ completion = Phronomy::TaskResult.deferred(name: "durable-subagent:#{tool_invocation_id}")
51
51
  preparation = runtime.offload.submit(on_full: :raise) do
52
52
  current = parent.persistence.executions.load(parent_execution_id)
53
- raise Phronomy::Persistence::ConflictError, "Parent owner mismatch" unless current.agent_id == parent.agent_id
53
+ raise Phronomy::Storage::ConflictError, "Parent owner mismatch" unless current.agent_id == parent.agent_id
54
54
  snapshot = parent.persistence.contents.fetch_json(current.metadata.fetch(KEY))
55
55
  child = snapshot.fetch("children").find { |entry| entry.fetch("slot") == tool_invocation_id }
56
56
  raise Phronomy::ExecutionRehydrationRequiredError, "Missing reserved child slot" unless child
@@ -65,7 +65,7 @@ module Phronomy
65
65
  begin
66
66
  parent.persistence.agents.load(id)
67
67
  exists = true
68
- rescue Phronomy::Persistence::NotFoundError
68
+ rescue Phronomy::Storage::NotFoundError
69
69
  exists = false
70
70
  end
71
71
  listener = parent.send(:_phronomy_event_listener)
@@ -125,7 +125,7 @@ module Phronomy
125
125
  end
126
126
  completion
127
127
  rescue => error
128
- completion ||= Phronomy::Task.deferred(name: "durable-subagent")
128
+ completion ||= Phronomy::TaskResult.deferred(name: "durable-subagent")
129
129
  completion.fail(error)
130
130
  completion
131
131
  end
@@ -3,8 +3,8 @@
3
3
  require "time"
4
4
 
5
5
  module Phronomy
6
- module Agent
7
- # Durable Agent-domain responsibility routing within one Persistence instance.
6
+ module MultiAgent
7
+ # Coordinates durable handoffs across Agents using one Persistence instance.
8
8
  # @api public
9
9
  class HandoffRunner
10
10
  MAX_HANDOFFS = 20
@@ -16,7 +16,7 @@ module Phronomy
16
16
  def initialize(main_agent:, handoffs: [])
17
17
  @main_agent, @handoffs = main_agent, Array(handoffs).freeze
18
18
  @persistence = main_agent.persistence
19
- raise ArgumentError, "handoffs must contain Agent::Handoff" unless @handoffs.all? { |h| h.is_a?(Handoff) }
19
+ raise ArgumentError, "handoffs must contain Agent::Handoff" unless @handoffs.all? { |h| h.is_a?(Phronomy::Agent::Handoff) }
20
20
  @agents = ([main_agent] + @handoffs.flat_map { |edge| [edge.source_agent, edge.target_agent] }).uniq.to_h { |a| [a.agent_id, a] }.freeze
21
21
  unless @agents.values.all? { |a| a.persistence.equal?(@persistence) }
22
22
  raise Phronomy::ConfigurationError, "Durable Handoff graph requires one Persistence instance"
@@ -24,8 +24,9 @@ module Phronomy
24
24
  if @handoffs.group_by { |h| [h.source_agent.agent_id, h.target_agent.agent_id] }.any? { |_, edges| edges.size > 1 }
25
25
  raise ArgumentError, "Duplicate Source/Target Handoff edges"
26
26
  end
27
- @bindings = @handoffs.group_by { |h| h.source_agent.agent_id }.transform_values { |edges| edges.map { |h| HandoffCapabilityFactory.build(h) }.freeze }.freeze
27
+ @bindings = @handoffs.group_by { |h| h.source_agent.agent_id }.transform_values { |edges| edges.map { |h| Phronomy::Agent::HandoffCapabilityFactory.build(h) }.freeze }.freeze
28
28
  @runtime = Phronomy::Runtime.instance
29
+ @admissions = Phronomy::MultiAgent::AdmissionRegistry.for(@runtime)
29
30
  end
30
31
 
31
32
  # @api public
@@ -41,7 +42,7 @@ module Phronomy
41
42
  raise Phronomy::RuntimeShutdownError, "HandoffRunner belongs to a previous Runtime"
42
43
  end
43
44
  config = config.merge(cancellation_token: config[:cancellation_token] || Phronomy::Concurrency::CancellationToken.new)
44
- @runtime.__admit_multi_agent(main_agent)
45
+ @admissions.admit!(main_agent)
45
46
  admitted = true
46
47
  state = load_state
47
48
  count = 0
@@ -49,7 +50,7 @@ module Phronomy
49
50
  active = @agents.fetch(state.active_agent_id) do
50
51
  raise Phronomy::ExecutionRehydrationRequiredError, "Handoff graph lacks active Agent #{state.active_agent_id}"
51
52
  end
52
- context = state.active_handoff_context_ref && HandoffContext.from_h(@persistence.contents.fetch_json(state.active_handoff_context_ref))
53
+ context = state.active_handoff_context_ref && Phronomy::Agent::HandoffContext.from_h(@persistence.contents.fetch_json(state.active_handoff_context_ref))
53
54
  wiring = config.merge(phronomy_handoff_bindings: @bindings.fetch(active.agent_id, []),
54
55
  phronomy_handoff_context: context,
55
56
  phronomy_coordination: {"kind" => "handoff", "main_agent_id" => main_agent.agent_id, "handoff_revision" => state.handoff_revision}).freeze
@@ -61,11 +62,11 @@ module Phronomy
61
62
  else
62
63
  exact = unfinished.fetch(0)
63
64
  unless unfinished.size == 1 && exact.metadata.dig("coordination", "main_agent_id") == main_agent.agent_id
64
- raise Phronomy::Persistence::ConflictError, "Active Agent execution belongs to another coordination turn"
65
+ raise Phronomy::Storage::ConflictError, "Active Agent execution belongs to another coordination turn"
65
66
  end
66
67
  wiring[:cancellation_token].cancel! if Array(state.metadata["cancelled_execution_ids"]).include?(exact.execution_id)
67
68
  stored_input = @persistence.contents.fetch_text(exact.metadata.fetch("current_input_ref"))
68
- ExactExecution.start(agent: active, execution_id: exact.execution_id, input: stored_input, config: wiring).wait_result
69
+ Phronomy::Agent::ExactExecution.start(agent: active, execution_id: exact.execution_id, input: stored_input, config: wiring).wait_result
69
70
  end
70
71
  else
71
72
  source = @persistence.executions.load(state.pending_source_execution_id)
@@ -77,7 +78,7 @@ module Phronomy
77
78
  raise Phronomy::ConfigurationError, "Handoff Target definition mismatch"
78
79
  end
79
80
  wiring[:cancellation_token].cancel! if Array(state.metadata["cancelled_execution_ids"]).include?(state.pending_target_execution_id)
80
- ExactExecution.start(agent: active, execution_id: state.pending_target_execution_id,
81
+ Phronomy::Agent::ExactExecution.start(agent: active, execution_id: state.pending_target_execution_id,
81
82
  input: context.responsibility, config: wiring).wait_result
82
83
  end
83
84
  execution = @persistence.executions.load(result.fetch(:execution_id))
@@ -90,7 +91,7 @@ module Phronomy
90
91
  if execution.active?
91
92
  raise Phronomy::ExecutionRehydrationRequiredError, "Handoff execution requires approval or recovery"
92
93
  end
93
- raise RecoverySupport.error_from_failure(result[:error]) if result[:error]
94
+ raise Phronomy::Agent::RecoverySupport.error_from_failure(result[:error]) if result[:error]
94
95
  return result.reject { |key, _| key.to_s.start_with?("_phronomy_") || key == :handoff_request }.merge(agent: active)
95
96
  end
96
97
  rescue Phronomy::CancellationError => error
@@ -101,7 +102,7 @@ module Phronomy
101
102
  operation_error = error
102
103
  raise
103
104
  ensure
104
- @runtime.__release_multi_agent(main_agent) if admitted
105
+ @admissions.release!(main_agent) if admitted
105
106
  Phronomy::Tracing::Automatic.finish(trace_handle, output: result && result[:output], error: operation_error)
106
107
  end
107
108
 
@@ -117,25 +118,25 @@ module Phronomy
117
118
  @persistence.transaction do |tx|
118
119
  first = tx.executions.load(execution_id)
119
120
  unless first.metadata.dig("coordination", "main_agent_id") == main_agent.agent_id
120
- raise Phronomy::Persistence::ConflictError, "Execution does not belong to this Handoff anchor"
121
+ raise Phronomy::Storage::ConflictError, "Execution does not belong to this Handoff anchor"
121
122
  end
122
123
  leaf = first
123
124
  leaf_id = first.execution_id
124
125
  seen = {}
125
126
  while leaf&.status == :handed_off
126
- raise Phronomy::Persistence::SerializationError, "Cyclic Handoff chain" if seen[leaf_id]
127
+ raise Phronomy::Storage::SerializationError, "Cyclic Handoff chain" if seen[leaf_id]
127
128
  seen[leaf_id] = true
128
129
  leaf_id = leaf.metadata.fetch("handoff_target_execution_id")
129
130
  begin
130
131
  leaf = tx.executions.load(leaf_id)
131
- rescue Phronomy::Persistence::NotFoundError
132
+ rescue Phronomy::Storage::NotFoundError
132
133
  leaf = nil
133
134
  end
134
135
  end
135
136
  next if leaf&.terminal?
136
137
  routing = tx.handoff_states.load(main_agent.agent_id)
137
138
  unless routing && (leaf ? routing.active_agent_id == leaf.agent_id : routing.pending_target_execution_id == leaf_id)
138
- raise Phronomy::Persistence::ConflictError, "Handoff routing no longer owns the requested turn"
139
+ raise Phronomy::Storage::ConflictError, "Handoff routing no longer owns the requested turn"
139
140
  end
140
141
  ids = (Array(routing.metadata["cancelled_execution_ids"]) + [leaf_id]).uniq
141
142
  intended = routing.with(phase: leaf ? routing.phase : "stable",
@@ -147,7 +148,7 @@ module Phronomy
147
148
  raise error unless intended && confirmed && Array(confirmed.metadata["cancelled_execution_ids"]).include?(leaf_id)
148
149
  end
149
150
  return @persistence.execution_result(leaf_id) if leaf&.terminal?
150
- ExecutionCancellation.signal(leaf_id, leaf.agent_id) if leaf&.active?
151
+ Phronomy::Agent::ExecutionCancellation.signal(leaf_id, leaf.agent_id) if leaf&.active?
151
152
  {execution_id: leaf_id, cancellation_requested: !leaf&.terminal?}.freeze
152
153
  end
153
154
 
@@ -163,7 +164,7 @@ module Phronomy
163
164
  state = @persistence.handoff_states.load(main_agent.agent_id)
164
165
  return state if state
165
166
  now = Time.now.utc.iso8601(6)
166
- initial = HandoffState.new(main_agent_id: main_agent.agent_id, handoff_revision: 1,
167
+ initial = Phronomy::Agent::HandoffState.new(main_agent_id: main_agent.agent_id, handoff_revision: 1,
167
168
  active_agent_id: main_agent.agent_id, active_handoff_context_ref: nil,
168
169
  phase: "stable", pending_source_execution_id: nil, pending_target_execution_id: nil,
169
170
  created_at: now, updated_at: now, metadata: {})