phronomy 0.26.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 (304) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +4 -0
  4. data/VERIFY.sh +27 -27
  5. data/benchmark/bench_agent_invoke.rb +26 -22
  6. data/benchmark/bench_context_assembler.rb +4 -5
  7. data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
  8. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  9. data/docs/architecture/agent-context.md +4 -2
  10. data/docs/architecture/agent-transition-ownership.md +89 -0
  11. data/docs/architecture/before-llm-input.md +6 -0
  12. data/docs/architecture/context-management.md +30 -0
  13. data/docs/architecture/context-preparation-steps.md +90 -0
  14. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  15. data/docs/architecture/execution-metadata-and-values.md +80 -0
  16. data/docs/architecture/generator-verifier-ownership.md +111 -0
  17. data/docs/architecture/multi-agent-handoff.md +8 -2
  18. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  19. data/docs/architecture/persistence.md +27 -4
  20. data/docs/architecture/refactoring-closure.md +334 -0
  21. data/docs/architecture/remaining-refactoring-plan.md +374 -0
  22. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  23. data/docs/architecture/tool-schema-recording-gap.md +50 -0
  24. data/docs/architecture/tracing.md +1 -1
  25. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  26. data/docs/architecture.md +209 -0
  27. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  28. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  29. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  30. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  31. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
  32. data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
  33. data/docs/decisions/032-storage-backend-composition.md +80 -0
  34. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  35. data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -0
  36. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  37. data/docs/decisions/036-context-contract-ownership.md +86 -0
  38. data/docs/decisions/037-common-definition-ownership.md +62 -0
  39. data/docs/decisions/038-responsibility-based-source-layout.md +131 -0
  40. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
  41. data/docs/decisions/040-configuration-default-composition.md +94 -0
  42. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  43. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  44. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  45. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  46. data/docs/decisions/045-worker-input-restriction-ownership.md +100 -0
  47. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  48. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  49. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  50. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  51. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  52. data/docs/decisions/051-execution-outcome-worker-ownership.md +151 -0
  53. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  54. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  55. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  56. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  57. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  58. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  59. data/docs/decisions/058-neutral-storage-primitives.md +78 -0
  60. data/docs/decisions/README.md +35 -6
  61. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  62. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  63. data/docs/features.md +9 -3
  64. data/docs/getting-started.md +4 -4
  65. data/docs/migrations/0.15.md +5 -0
  66. data/docs/migrations/durable-semantic-coordination-v2.md +6 -0
  67. data/docs/migrations/handoff-runner-multi-agent.md +35 -0
  68. data/docs/migrations/neutral-storage-spi.md +43 -0
  69. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  70. data/docs/migrations/shared-state-multi-agent.md +44 -0
  71. data/docs/migrations/storage-backend-composition.md +117 -0
  72. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  73. data/docs/persistence-backends.md +162 -204
  74. data/docs/runtime-and-concurrency.md +117 -32
  75. data/lib/phronomy/agent/api/agent.rb +17 -0
  76. data/lib/phronomy/agent/async_event_api.rb +2 -2
  77. data/lib/phronomy/agent/base.rb +51 -241
  78. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  79. data/lib/phronomy/agent/context/capability/base.rb +67 -26
  80. data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
  81. data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
  82. data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
  83. data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
  84. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
  85. data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
  86. data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
  87. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
  88. data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
  89. data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
  90. data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
  91. data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
  92. data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
  93. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  94. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
  95. data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +19 -95
  96. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  97. data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
  98. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +9 -10
  99. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
  100. data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
  101. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  102. data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
  103. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
  104. data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
  105. data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
  106. data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
  107. data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
  108. data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +20 -71
  109. data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
  110. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  111. data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
  112. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
  113. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
  114. data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
  115. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  116. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  117. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  118. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  119. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  120. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -10
  121. data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
  122. data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
  123. data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
  124. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
  125. data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
  126. data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
  127. data/lib/phronomy/agent/persistence/codec.rb +358 -0
  128. data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
  129. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
  130. data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
  131. data/lib/phronomy/agent/persistence/queries.rb +61 -0
  132. data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
  133. data/lib/phronomy/agent/persistence/watermark.rb +27 -0
  134. data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
  135. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
  136. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +32 -37
  137. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
  138. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +6 -10
  139. data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
  140. data/lib/phronomy/agent/selection/candidate.rb +1 -1
  141. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  142. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  143. data/lib/phronomy/agent/tool_execution/tool_binding.rb +90 -0
  144. data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
  145. data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
  146. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
  147. data/lib/phronomy/common/configuration_error.rb +7 -0
  148. data/lib/phronomy/common/error.rb +5 -0
  149. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  150. data/lib/phronomy/common/values/serializable.rb +32 -0
  151. data/lib/phronomy/{configuration.rb → configuration/configuration.rb} +14 -5
  152. data/lib/phronomy/configuration/global_configuration.rb +26 -0
  153. data/lib/phronomy/content_store/storage_schema.rb +11 -0
  154. data/lib/phronomy/content_store/stored_contents.rb +43 -0
  155. data/lib/phronomy/engine/backpressure_error.rb +7 -0
  156. data/lib/phronomy/{blocking.rb → engine/blocking.rb} +1 -1
  157. data/lib/phronomy/engine/cancellation_error.rb +7 -0
  158. data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
  159. data/lib/phronomy/engine/concurrency/offload_pool.rb +4 -0
  160. data/lib/phronomy/engine/concurrency/operation_binding.rb +1 -1
  161. data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
  162. data/lib/phronomy/engine/event_loop.rb +158 -624
  163. data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
  164. data/lib/phronomy/{execution.rb → engine/execution.rb} +0 -7
  165. data/lib/phronomy/{execution_cancellation_error.rb → engine/execution_cancellation_error.rb} +2 -0
  166. data/lib/phronomy/engine/execution_receiver.rb +65 -0
  167. data/lib/phronomy/{execution_timeout_error.rb → engine/execution_timeout_error.rb} +2 -0
  168. data/lib/phronomy/engine/fsm_protocol.rb +14 -0
  169. data/lib/phronomy/engine/fsm_session.rb +35 -28
  170. data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +2 -0
  171. data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +2 -0
  172. data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +2 -0
  173. data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
  174. data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
  175. data/lib/phronomy/engine/runtime.rb +80 -84
  176. data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
  177. data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
  178. data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
  179. data/lib/phronomy/engine/task_result.rb +4 -0
  180. data/lib/phronomy/engine/timeout_error.rb +7 -0
  181. data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
  182. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  183. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  184. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  185. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  186. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  187. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  188. data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
  189. data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
  190. data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
  191. data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
  192. data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
  193. data/lib/phronomy/llm_contract/transport_error.rb +7 -0
  194. data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
  195. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +5 -5
  196. data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
  197. data/lib/phronomy/multi_agent/orchestrator.rb +2 -2
  198. data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
  199. data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
  200. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
  201. data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
  202. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  203. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
  204. data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
  205. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  206. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
  207. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  208. data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
  209. data/lib/phronomy/persistence/api/persistence.rb +140 -0
  210. data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
  211. data/lib/phronomy/persistence_composition/repositories.rb +77 -0
  212. data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
  213. data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
  214. data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
  215. data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
  216. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  217. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  218. data/lib/phronomy/storage/backend.rb +101 -0
  219. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  220. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  221. data/lib/phronomy/storage/blobs.rb +31 -0
  222. data/lib/phronomy/storage/condition.rb +25 -0
  223. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  224. data/lib/phronomy/storage/conflict_error.rb +9 -0
  225. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  226. data/lib/phronomy/storage/entry.rb +33 -0
  227. data/lib/phronomy/storage/guard_ref.rb +13 -0
  228. data/lib/phronomy/storage/not_found_error.rb +9 -0
  229. data/lib/phronomy/storage/record_codec.rb +177 -0
  230. data/lib/phronomy/storage/records.rb +61 -0
  231. data/lib/phronomy/storage/resource.rb +126 -0
  232. data/lib/phronomy/storage/scope.rb +25 -0
  233. data/lib/phronomy/storage/serialization_error.rb +9 -0
  234. data/lib/phronomy/storage/streams.rb +44 -0
  235. data/lib/phronomy/storage/transaction_error.rb +10 -0
  236. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  237. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  238. data/lib/phronomy/storage/validation.rb +53 -0
  239. data/lib/phronomy/storage/view.rb +131 -0
  240. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  241. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
  242. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
  243. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
  244. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
  245. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
  246. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
  247. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  248. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
  249. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  250. data/lib/phronomy/tool/contract/tool_error.rb +7 -0
  251. data/lib/phronomy/tools/agent.rb +1 -1
  252. data/lib/phronomy/version.rb +1 -1
  253. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +8 -6
  254. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
  255. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  256. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
  257. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +122 -71
  258. data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
  259. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  260. data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
  261. data/lib/phronomy/workflow/phase_machine_builder.rb +2 -2
  262. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
  263. data/lib/phronomy.rb +52 -94
  264. data/scripts/api_snapshot.rb +1 -1
  265. data/scripts/storage_spi_snapshot.rb +36 -0
  266. data/sig/phronomy/agent.rbs +0 -1
  267. data/sig/phronomy/execution_receiver.rbs +34 -0
  268. data/sig/phronomy/handoff.rbs +4 -2
  269. data/sig/phronomy/multi_agent.rbs +18 -0
  270. data/sig/phronomy/persistence.rbs +7 -91
  271. data/sig/phronomy/storage.rbs +174 -0
  272. data/sig/phronomy/tool.rbs +10 -1
  273. metadata +230 -99
  274. data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
  275. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  276. data/lib/phronomy/agent/recovery_support.rb +0 -504
  277. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  278. data/lib/phronomy/agent/tool_executor.rb +0 -55
  279. data/lib/phronomy/generator_verifier.rb +0 -369
  280. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  281. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  282. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  283. data/lib/phronomy/persistence/in_memory.rb +0 -690
  284. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  285. data/lib/phronomy/persistence.rb +0 -276
  286. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  287. data/lib/phronomy/workflow_recovery.rb +0 -123
  288. /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
  289. /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
  290. /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
  291. /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
  292. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  293. /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
  294. /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
  295. /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
  296. /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
  297. /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
  298. /data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +0 -0
  299. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  300. /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
  301. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
  302. /data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +0 -0
  303. /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
  304. /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
@@ -3,33 +3,6 @@
3
3
  module Phronomy
4
4
  module Agent
5
5
  class AgentInvocationSessionBuilder
6
- AUTO_STATE_SET = {
7
- idle: true,
8
- filtering_input: true,
9
- building_context: true,
10
- starting_tools: true,
11
- evaluating_tools: true,
12
- recording_tool_results: true,
13
- output_filtering: true
14
- }.freeze
15
-
16
- DECLARED_STATES = %i[
17
- idle filtering_input building_context calling_llm starting_tools
18
- evaluating_tools waiting_for_tools dispatching_tools
19
- recording_tool_results suspended output_filtering handed_off completed blocked failed
20
- ].freeze
21
-
22
- WAIT_STATES = %i[suspended].freeze
23
-
24
- TOOL_EVENTS = %i[
25
- tool_authorized
26
- tool_approval_required
27
- tool_completed
28
- tool_failed
29
- tool_rejected
30
- tool_cancelled
31
- ].freeze
32
-
33
6
  def self.build(
34
7
  agent:,
35
8
  input:,
@@ -93,13 +66,13 @@ module Phronomy
93
66
  Phronomy::FSMSession.new(
94
67
  context: agent_invocation,
95
68
  event_sink: event_sink,
96
- entry_point: :idle,
69
+ entry_point: InvocationTransitions::ENTRY_POINT,
97
70
  phase_machine_class: phase_machine,
98
71
  entry_actions: {},
99
- auto_state_set: AUTO_STATE_SET,
100
- declared_states: DECLARED_STATES,
101
- wait_state_names: WAIT_STATES,
102
- external_events: external_events,
72
+ auto_state_set: InvocationTransitions::AUTO_STATE_SET,
73
+ declared_states: InvocationTransitions::DECLARED_STATES,
74
+ wait_state_names: InvocationTransitions::WAIT_STATES,
75
+ external_events: InvocationTransitions::EXTERNAL_EVENTS,
103
76
  recursion_limit: 12 + (iterations * 8),
104
77
  event_loop: runtime.event_loop,
105
78
  resume_event: resume_event,
@@ -108,49 +81,6 @@ module Phronomy
108
81
  end
109
82
  private_class_method :build_session
110
83
 
111
- def self.external_events
112
- tool_transitions = TOOL_EVENTS.to_h do |event_name|
113
- [event_name, [{from: :waiting_for_tools, to: :evaluating_tools, guard: nil}]]
114
- end
115
-
116
- tool_transitions.merge(
117
- llm_completed: [
118
- {from: :calling_llm, to: :failed, guard: ->(ctx) { ctx.callback_failed? }},
119
- {from: :calling_llm, to: :failed, guard: ->(ctx) { ctx.handoff_failed? }},
120
- {from: :calling_llm, to: :handed_off, guard: ->(ctx) { ctx.handoff_requested? }},
121
- {from: :calling_llm, to: :starting_tools, guard: ->(ctx) { ctx.tool_call_pending? }},
122
- {from: :calling_llm, to: :output_filtering, guard: nil}
123
- ],
124
- llm_failed: [
125
- {from: :calling_llm, to: :failed, guard: nil}
126
- ],
127
- llm_setup_failed: [
128
- {from: :calling_llm, to: :failed, guard: nil}
129
- ],
130
- tool_setup_failed: [
131
- {from: :dispatching_tools, to: :failed, guard: nil}
132
- ],
133
- tool_dispatch_prepared: [
134
- {from: :dispatching_tools, to: :evaluating_tools, guard: nil}
135
- ],
136
- resume: [
137
- {from: :suspended, to: :waiting_for_tools, guard: nil}
138
- ],
139
- application_callback_failed: [
140
- {from: :filtering_input, to: :failed, guard: nil},
141
- {from: :building_context, to: :failed, guard: nil},
142
- {from: :calling_llm, to: :failed, guard: nil},
143
- {from: :starting_tools, to: :failed, guard: nil},
144
- {from: :evaluating_tools, to: :failed, guard: nil},
145
- {from: :waiting_for_tools, to: :failed, guard: nil},
146
- {from: :dispatching_tools, to: :failed, guard: nil},
147
- {from: :recording_tool_results, to: :failed, guard: nil},
148
- {from: :output_filtering, to: :failed, guard: nil}
149
- ]
150
- )
151
- end
152
- private_class_method :external_events
153
-
154
84
  def self.build_entry_actions(agent, runtime, mode:, event_sink:)
155
85
  calling_action = if mode.to_sym == :stream
156
86
  method(:calling_llm_stream_action).curry.call(agent, runtime, event_sink)
@@ -159,8 +89,8 @@ module Phronomy
159
89
  end
160
90
 
161
91
  {
162
- filtering_input: [method(:filtering_input_action).curry.call(agent)],
163
- building_context: [method(:building_context_action).curry.call(agent)],
92
+ filtering_input: [method(:apply_prepared_input_action).curry.call(agent)],
93
+ building_context: [method(:build_runtime_chat_action).curry.call(agent)],
164
94
  calling_llm: [calling_action],
165
95
  starting_tools: [method(:starting_tools_action).curry.call(runtime, event_sink)],
166
96
  dispatching_tools: [method(:dispatching_tools_action).curry.call(runtime, event_sink)],
@@ -172,13 +102,13 @@ module Phronomy
172
102
  end
173
103
  private_class_method :build_entry_actions
174
104
 
175
- def self.filtering_input_action(_agent, invocation)
105
+ def self.apply_prepared_input_action(_agent, invocation)
176
106
  invocation.input = invocation.config.fetch(:phronomy_filtered_input)
177
107
  invocation
178
108
  end
179
- private_class_method :filtering_input_action
109
+ private_class_method :apply_prepared_input_action
180
110
 
181
- def self.building_context_action(agent, invocation)
111
+ def self.build_runtime_chat_action(agent, invocation)
182
112
  projection = invocation.config.fetch(:phronomy_runtime_projection)
183
113
  invocation.chat = agent.send(:build_chat, model_config: projection.model_config)
184
114
  agent.send(
@@ -189,22 +119,16 @@ module Phronomy
189
119
  )
190
120
  invocation
191
121
  end
192
- private_class_method :building_context_action
122
+ private_class_method :build_runtime_chat_action
193
123
 
194
124
  def self.install_tool_interceptors(chat, llm_call_id:)
195
- unless chat.respond_to?(:before_tool_call)
196
- raise Phronomy::ConfigurationError,
197
- "Agent-owned Tool execution requires RubyLLM >= 1.15 (before_tool_call callback)"
198
- end
199
-
200
- if chat.respond_to?(:on_tool_call_batch)
201
- chat.on_tool_call_batch do |tool_calls|
202
- raise build_tool_interception(chat, tool_calls, llm_call_id)
203
- end
204
- end
125
+ # RubyLLM 2 records the complete assistant message before this callback,
126
+ # and checks its own approval gate afterwards. Capture here so Phronomy
127
+ # owns authorization and dispatch for the entire Tool batch.
128
+ chat.after_message do |message|
129
+ next unless message.tool_call?
205
130
 
206
- chat.before_tool_call do |tool_call|
207
- raise build_tool_interception(chat, [tool_call], llm_call_id)
131
+ raise build_tool_interception(chat, [], llm_call_id)
208
132
  end
209
133
  end
210
134
  private_class_method :install_tool_interceptors
@@ -347,7 +271,7 @@ module Phronomy
347
271
  chat, message, config: config
348
272
  )
349
273
  end
350
- runtime.event_loop.supervise_agent_operation(
274
+ Phronomy::Agent::ExecutionRegistry.for(runtime.event_loop).supervise_agent_operation(
351
275
  invocation.execution_id,
352
276
  operation
353
277
  )
@@ -456,7 +380,7 @@ module Phronomy
456
380
  children = invocation.pending_tool_calls.map do |tool_call|
457
381
  tool = invocation.chat.tools[tool_call.name.to_sym]
458
382
  tool_invocation_id =
459
- RecoverySupport.semantic_tool_id(
383
+ ToolInvocation.semantic_id(
460
384
  execution_id: invocation.execution_id,
461
385
  llm_call_id: invocation.tool_batch_llm_call_id,
462
386
  tool_call_id: (
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../engine/concurrency/worker_input_restricted"
4
+
5
+ module Phronomy
6
+ module Agent
7
+ # Persists one approval decision with its captured recovery facts.
8
+ # Admission, live state and session resumption stay with the execution owner.
9
+ # @api private
10
+ class ApprovalResumeCommit
11
+ include Phronomy::Concurrency::WorkerInputRestricted
12
+
13
+ Command = Data.define(
14
+ :execution_id, :expected_execution_revision,
15
+ :root, :execution, :approval_request_id, :approved, :tool_batch_snapshot
16
+ )
17
+ Result = Data.define(:execution, :root)
18
+
19
+ # @api private
20
+ def initialize(agent_id:, persistence:)
21
+ @agent_id = agent_id
22
+ @persistence = persistence
23
+ end
24
+
25
+ # @api private
26
+ def commit(operation)
27
+ request = validated_approval_request(operation)
28
+ execution = stage_recovery_snapshot(operation)
29
+ persist_decision(execution, operation.root, request, operation.approved)
30
+ end
31
+
32
+ private
33
+
34
+ def validated_approval_request(operation)
35
+ request = operation.execution.approval_request || {}
36
+ request_id = request["id"] || request[:id]
37
+ unless request_id.to_s == operation.approval_request_id
38
+ raise ArgumentError,
39
+ "approval request does not match execution #{operation.execution.execution_id}"
40
+ end
41
+ request
42
+ end
43
+
44
+ def stage_recovery_snapshot(operation)
45
+ return operation.execution unless operation.tool_batch_snapshot
46
+
47
+ ExecutionMetadata.with_values(
48
+ operation.execution,
49
+ ExecutionMetadata::TOOL_BATCH_METADATA_KEY => operation.tool_batch_snapshot
50
+ )
51
+ end
52
+
53
+ def persist_decision(execution, root, request, approved)
54
+ updated = next_root = nil
55
+ @persistence.transaction do |tx|
56
+ decision = record_decision(tx, execution, root, request, approved)
57
+ updated = save_resumed_execution(tx, execution, request, approved, decision)
58
+ next_root = save_resumed_root(tx, root)
59
+ end
60
+ Result.new(execution: updated, root: next_root)
61
+ end
62
+
63
+ def record_decision(tx, execution, root, request, approved)
64
+ decision_ref = tx.contents.put_json(
65
+ "approval_request_id" => (request["id"] || request[:id]).to_s,
66
+ "approved" => approved
67
+ )
68
+ JournalRecord.new(
69
+ agent_id: @agent_id,
70
+ execution_id: execution.execution_id,
71
+ kind: :approval_decided,
72
+ channel: :approval,
73
+ content_ref: decision_ref,
74
+ context_generation: root.transcript_generation,
75
+ context_candidate: false
76
+ )
77
+ end
78
+
79
+ def save_resumed_execution(tx, current, request, approved, decision)
80
+ updated = current.with(
81
+ status: :active,
82
+ phase: :resuming,
83
+ working_records: current.working_records + [decision],
84
+ approval_request: request.merge("approved" => approved)
85
+ )
86
+ tx.executions.save(
87
+ current.execution_id,
88
+ expected_revision: current.execution_revision,
89
+ execution: updated
90
+ )
91
+ updated
92
+ end
93
+
94
+ def save_resumed_root(tx, current)
95
+ updated = current.with(
96
+ agent_revision: current.agent_revision + 1,
97
+ lifecycle_status: :active
98
+ )
99
+ tx.agents.save(
100
+ current.agent_id,
101
+ expected_revision: current.agent_revision,
102
+ root: updated
103
+ )
104
+ updated
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,305 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../engine/concurrency/worker_input_restricted"
4
+
5
+ module Phronomy
6
+ module Agent
7
+ # Commits the operation-specific prerequisites for Provider/Tool dispatch.
8
+ # The Agent supplies existing context/hook services, never live-state writes.
9
+ # Inputs and results belong to this worker boundary; delivery stays with the
10
+ # execution owner. No operation-local state is retained between calls.
11
+ # @api private
12
+ class DispatchPreparation
13
+ include Phronomy::Concurrency::WorkerInputRestricted
14
+
15
+ ProviderCommand = Data.define(
16
+ :execution_id, :fsm_session_id, :expected_execution_revision,
17
+ :root, :journal_records, :execution, :base_manifest,
18
+ :invocation_config, :runtime_snapshot, :streaming,
19
+ :pending_llm_call_id, :pending_llm_started_at
20
+ )
21
+ ToolCommand = Data.define(
22
+ :execution_id, :fsm_session_id, :expected_execution_revision,
23
+ :root, :execution, :runtime_snapshot, :tool_batch_snapshot
24
+ )
25
+ ProviderReconciliationCommand = Data.define(
26
+ :operation, :intended_result, :original_error
27
+ )
28
+ ToolReconciliationCommand = Data.define(
29
+ :operation, :intended_result, :original_error
30
+ )
31
+ ProviderResult = Data.define(:execution, :runtime_projection, :error)
32
+ ToolResult = Data.define(:execution)
33
+ ProviderReconciliationResult = Data.define(:disposition, :preparation_result)
34
+ ToolReconciliationResult = Data.define(:disposition, :preparation_result)
35
+
36
+ # A failed response does not prove that the execution save rolled back.
37
+ # @api private
38
+ class OutcomeUnknownError < Phronomy::Error
39
+ attr_reader :original_error, :intended_result
40
+
41
+ def initialize(original_error, intended_result)
42
+ @original_error = original_error
43
+ @intended_result = intended_result
44
+ super(
45
+ "durable dispatch preparation outcome is uncertain: " \
46
+ "#{original_error.class}: #{original_error.message}"
47
+ )
48
+ set_backtrace(original_error.backtrace)
49
+ end
50
+ end
51
+
52
+ # @api private
53
+ def initialize(agent:, persistence:)
54
+ @agent = agent
55
+ @persistence = persistence
56
+ end
57
+
58
+ # @api private
59
+ def prepare_provider(operation)
60
+ staged = stage_provider_call(operation)
61
+ recorded = encode_provider_records(operation, staged)
62
+ assembler, prepared = prepare_provider_context(operation, recorded)
63
+ committed, manifest, manifest_ref = commit_provider_preparation(
64
+ operation, recorded, assembler, prepared
65
+ )
66
+ materialize_provider_result(committed.execution, manifest, manifest_ref)
67
+ end
68
+
69
+ # @api private
70
+ def prepare_tools(operation)
71
+ intended = nil
72
+ begin
73
+ @persistence.transaction do |tx|
74
+ assert_local_durable_base!(tx, operation.root)
75
+ updated = build_tool_dispatch_execution(operation, tx)
76
+ updated = @agent.__prepare_coordination_record(updated, tx: tx)
77
+ tx.executions.save(
78
+ operation.execution.execution_id,
79
+ expected_revision: operation.execution.execution_revision,
80
+ execution: updated
81
+ )
82
+ intended = ToolResult.new(execution: updated)
83
+ end
84
+ rescue => caught
85
+ raise if known_durable_failure?(caught) || intended.nil?
86
+
87
+ raise OutcomeUnknownError.new(caught, intended)
88
+ end
89
+ intended
90
+ end
91
+
92
+ # @api private
93
+ def reconcile_provider(command)
94
+ disposition, current = reconcile_preparation(
95
+ command.operation, command.intended_result.execution
96
+ )
97
+ result = restore_provider_result(current) if disposition == :committed
98
+ ProviderReconciliationResult.new(
99
+ disposition: disposition, preparation_result: result
100
+ )
101
+ end
102
+
103
+ # @api private
104
+ def reconcile_tools(command)
105
+ disposition, current = reconcile_preparation(
106
+ command.operation, command.intended_result.execution
107
+ )
108
+ result = ToolResult.new(execution: current) if disposition == :committed
109
+ ToolReconciliationResult.new(
110
+ disposition: disposition, preparation_result: result
111
+ )
112
+ end
113
+
114
+ private
115
+
116
+ def stage_provider_call(operation)
117
+ metadata = operation.execution.metadata.dup
118
+ metadata.delete(ExecutionMetadata::TOOL_BATCH_METADATA_KEY)
119
+ metadata.delete(ExecutionMetadata::RECOVERY_METADATA_KEY)
120
+ metadata[ExecutionMetadata::PENDING_LLM_ID_KEY] = operation.pending_llm_call_id
121
+ metadata[ExecutionMetadata::PENDING_LLM_STARTED_AT_KEY] = operation.pending_llm_started_at
122
+ metadata[ExecutionMetadata::CONTRACT_VERSION_KEY] = ExecutionMetadata::CONTRACT_VERSION
123
+ operation.execution.with(
124
+ execution_revision: operation.execution.execution_revision,
125
+ metadata: metadata
126
+ )
127
+ end
128
+
129
+ def encode_provider_records(operation, staged)
130
+ encoded_records = call_records = nil
131
+ # Content-addressed records may be added here; Execution is not advanced.
132
+ @persistence.transaction do |tx|
133
+ assert_local_durable_base!(tx, operation.root)
134
+ encoded_records, call_records = RuntimeRecordEncoder.encode(
135
+ staged,
136
+ agent_id: @agent.agent_id,
137
+ tx: tx,
138
+ snapshot: operation.runtime_snapshot,
139
+ context_candidate: true,
140
+ agent_root: operation.root
141
+ )
142
+ end
143
+ staged.with(
144
+ execution_revision: staged.execution_revision,
145
+ phase: :preparing_llm_call,
146
+ working_records: staged.working_records + encoded_records,
147
+ llm_calls: staged.llm_calls + call_records
148
+ )
149
+ end
150
+
151
+ def prepare_provider_context(operation, recorded)
152
+ patch = @agent.send(
153
+ :run_before_llm_input_hooks,
154
+ call_sequence: recorded.llm_calls.length + 1,
155
+ config: operation.invocation_config
156
+ )
157
+ assembler = ContextAssembler.new(
158
+ agent: @agent,
159
+ persistence: @persistence,
160
+ journal_records: operation.journal_records
161
+ )
162
+ prepared = assembler.prepare_followup(
163
+ base_manifest: operation.base_manifest,
164
+ agent_root: operation.root,
165
+ execution: recorded,
166
+ config: operation.invocation_config,
167
+ patch: patch
168
+ )
169
+ @agent.send(
170
+ :check_cancellation!,
171
+ operation.invocation_config,
172
+ "invocation cancelled after context policy"
173
+ )
174
+ [assembler, prepared]
175
+ end
176
+
177
+ def commit_provider_preparation(operation, recorded, assembler, prepared)
178
+ manifest = manifest_ref = intended = nil
179
+ begin
180
+ @persistence.transaction do |tx|
181
+ assert_local_durable_base!(tx, operation.root)
182
+ manifest, manifest_ref = assembler.finalize(prepared, persistence: tx)
183
+ updated = execution_with_provider_manifest(recorded, manifest_ref)
184
+ updated = @agent.__prepare_coordination_record(updated, tx: tx)
185
+ tx.executions.save(
186
+ operation.execution.execution_id,
187
+ expected_revision: operation.execution.execution_revision,
188
+ execution: updated
189
+ )
190
+ intended = ProviderResult.new(
191
+ execution: updated, runtime_projection: nil, error: nil
192
+ )
193
+ end
194
+ rescue => caught
195
+ raise if known_durable_failure?(caught) || intended.nil?
196
+
197
+ raise OutcomeUnknownError.new(caught, intended)
198
+ end
199
+ [intended, manifest, manifest_ref]
200
+ end
201
+
202
+ def execution_with_provider_manifest(recorded, manifest_ref)
203
+ refs = Array(recorded.metadata["manifest_refs"]) + [manifest_ref]
204
+ recorded.with(
205
+ phase: :calling_llm,
206
+ metadata: recorded.metadata.merge(
207
+ "manifest_ref" => manifest_ref,
208
+ "manifest_refs" => refs
209
+ )
210
+ )
211
+ end
212
+
213
+ def materialize_provider_result(execution, manifest, manifest_ref)
214
+ projection = materialization_error = nil
215
+ begin
216
+ projection = RubyLLMMaterializer.new(
217
+ agent: @agent, persistence: @persistence
218
+ ).materialize(manifest: manifest, manifest_ref: manifest_ref)
219
+ rescue => error
220
+ materialization_error = error
221
+ end
222
+ ProviderResult.new(
223
+ execution: execution, runtime_projection: projection,
224
+ error: materialization_error
225
+ )
226
+ end
227
+
228
+ def restore_provider_result(execution)
229
+ projection = materialization_error = nil
230
+ begin
231
+ _manifest, projection = SavedContextReader.materialize_projection(
232
+ @agent, execution.metadata.fetch("manifest_ref")
233
+ )
234
+ rescue => error
235
+ materialization_error = error
236
+ end
237
+ ProviderResult.new(
238
+ execution: execution, runtime_projection: projection,
239
+ error: materialization_error
240
+ )
241
+ end
242
+
243
+ def build_tool_dispatch_execution(operation, tx)
244
+ encoded_records, call_records = RuntimeRecordEncoder.encode(
245
+ operation.execution,
246
+ agent_id: @agent.agent_id,
247
+ tx: tx,
248
+ snapshot: operation.runtime_snapshot,
249
+ context_candidate: true,
250
+ agent_root: operation.root
251
+ )
252
+ operation.execution.with(
253
+ phase: :dispatching_tools,
254
+ working_records: operation.execution.working_records + encoded_records,
255
+ llm_calls: operation.execution.llm_calls + call_records,
256
+ metadata: tool_dispatch_metadata(operation)
257
+ )
258
+ end
259
+
260
+ def tool_dispatch_metadata(operation)
261
+ metadata = operation.execution.metadata.dup
262
+ metadata.delete(ExecutionMetadata::PENDING_LLM_ID_KEY)
263
+ metadata.delete(ExecutionMetadata::PENDING_LLM_STARTED_AT_KEY)
264
+ metadata.delete(ExecutionMetadata::RECOVERY_METADATA_KEY)
265
+ metadata.delete("framework_calls_pending")
266
+ metadata[ExecutionMetadata::TOOL_BATCH_METADATA_KEY] =
267
+ Phronomy::Values::Serializable.convert(operation.tool_batch_snapshot,
268
+ unsupported_message: "Recovery value is not canonically serializable")
269
+ metadata[ExecutionMetadata::CONTRACT_VERSION_KEY] = ExecutionMetadata::CONTRACT_VERSION
270
+ metadata
271
+ end
272
+
273
+ def reconcile_preparation(operation, intended_execution)
274
+ current = @persistence.executions.load(operation.execution_id)
275
+ if current.execution_revision == intended_execution.execution_revision &&
276
+ current.to_h == intended_execution.to_h
277
+ return [:committed, current]
278
+ end
279
+ if current.execution_revision == operation.execution.execution_revision &&
280
+ current.to_h == operation.execution.to_h
281
+ return [:not_committed, current]
282
+ end
283
+
284
+ [:conflict, current]
285
+ end
286
+
287
+ def known_durable_failure?(error)
288
+ error.is_a?(Phronomy::Storage::ConflictError) ||
289
+ error.is_a?(Phronomy::Storage::NotFoundError) ||
290
+ error.is_a?(Phronomy::Storage::SerializationError) ||
291
+ error.is_a?(Phronomy::Storage::UnsupportedBackendError) ||
292
+ error.is_a?(ArgumentError) ||
293
+ error.is_a?(Phronomy::ConfigurationError)
294
+ end
295
+
296
+ def assert_local_durable_base!(tx, root)
297
+ tx.assert_agent_watermark!(
298
+ agent_id: root.agent_id,
299
+ agent_revision: root.agent_revision,
300
+ journal_position: root.journal_position
301
+ )
302
+ end
303
+ end
304
+ end
305
+ end
@@ -26,7 +26,7 @@ module Phronomy
26
26
  if execution&.terminal?
27
27
  [:terminal, materialize(execution)]
28
28
  elsif execution
29
- unless @runtime.__agent_execution_owner(@id)
29
+ unless Phronomy::Agent::ExecutionRegistry.existing_for(@runtime)&.agent_execution_owner(@id)
30
30
  @agent.instance_variable_set(:@_phronomy_coordination_config, @config)
31
31
  begin
32
32
  RecoveryCoordinator.new(@agent).recover_on_load!
@@ -55,8 +55,7 @@ module Phronomy
55
55
  source.on_complete { |_value, failure| reconcile(failure) }
56
56
  when :active
57
57
  command = Wait.new(coordinator: self, agent: @agent, execution_id: @id)
58
- posted = @runtime.event_loop.post(Phronomy::Event.new(type: :agent_terminal_ready,
59
- target_id: Phronomy::EventLoop::SYSTEM_CHANNEL_ID, payload: {command: command}))
58
+ posted = ExecutionRegistry.for(@runtime.event_loop).post(command, completion: @completion)
60
59
  @completion.fail(Phronomy::RuntimeShutdownError.new("Exact execution observer rejected")) unless posted
61
60
  end
62
61
  end
@@ -70,7 +69,7 @@ module Phronomy
70
69
  end
71
70
 
72
71
  def deliver_on_event_loop(command)
73
- state = @runtime.event_loop.agent_execution_state(command.execution_id)
72
+ state = Phronomy::Agent::ExecutionRegistry.for(@runtime.event_loop).agent_execution_state(command.execution_id)
74
73
  unless state
75
74
  # Ownership can be released between the durable read and this command.
76
75
  # Reinstall once with the caller's current wiring/cancellation request.
@@ -81,7 +80,7 @@ module Phronomy
81
80
  return reconcile(nil)
82
81
  end
83
82
  unless state.agent.equal?(command.agent)
84
- raise Phronomy::Persistence::ConflictError, "Exact execution #{@id} owner mismatch"
83
+ raise Phronomy::Storage::ConflictError, "Exact execution #{@id} owner mismatch"
85
84
  end
86
85
  if @config[:cancellation_token]&.cancelled?
87
86
  state.invocation&.config&.fetch(:cancellation_token, nil)&.cancel!
@@ -94,7 +93,7 @@ module Phronomy
94
93
  state.invocation.merge_config!(phronomy_exact_observers: (observers + [@completion]).uniq.freeze)
95
94
  waiter = Phronomy::TaskResult.deferred(name: "exact-wait:#{@id}")
96
95
  waiter.on_complete { |_result, failure| reconcile(failure) }
97
- @runtime.event_loop.register_agent_completion_waiter(@id, waiter)
96
+ Phronomy::Agent::ExecutionRegistry.for(@runtime.event_loop).register_agent_completion_waiter(@id, waiter)
98
97
  rescue => failure
99
98
  @completion.fail(failure)
100
99
  end
@@ -104,11 +103,11 @@ module Phronomy
104
103
  def read_execution
105
104
  begin
106
105
  execution = @agent.persistence.executions.load(@id)
107
- rescue Phronomy::Persistence::NotFoundError
106
+ rescue Phronomy::Storage::NotFoundError
108
107
  return nil
109
108
  end
110
109
  unless execution.agent_id == @agent.agent_id
111
- raise Phronomy::Persistence::ConflictError, "Reserved execution #{@id} belongs to another Agent"
110
+ raise Phronomy::Storage::ConflictError, "Reserved execution #{@id} belongs to another Agent"
112
111
  end
113
112
  expected = @config[:phronomy_coordination]
114
113
  stored = execution.metadata["coordination"]
@@ -116,12 +115,12 @@ module Phronomy
116
115
  actual_identity = stored&.except("handoff_revision")
117
116
  expected_identity = expected.except("handoff_revision")
118
117
  unless actual_identity == expected_identity
119
- raise Phronomy::Persistence::ConflictError, "Reserved execution #{@id} coordination mismatch"
118
+ raise Phronomy::Storage::ConflictError, "Reserved execution #{@id} coordination mismatch"
120
119
  end
121
120
  end
122
121
  ref = execution.metadata["current_input_ref"]
123
122
  if ref && @agent.persistence.contents.fetch_text(ref) != @input
124
- raise Phronomy::Persistence::ConflictError, "Reserved execution #{@id} input mismatch"
123
+ raise Phronomy::Storage::ConflictError, "Reserved execution #{@id} input mismatch"
125
124
  end
126
125
  execution
127
126
  end
@@ -11,12 +11,11 @@ module Phronomy
11
11
 
12
12
  def self.signal(execution_id, agent_id)
13
13
  command = Command.new(coordinator: new, execution_id: execution_id, agent_id: agent_id)
14
- Phronomy::Runtime.instance.event_loop.post(Phronomy::Event.new(type: :agent_terminal_ready,
15
- target_id: Phronomy::EventLoop::SYSTEM_CHANNEL_ID, payload: {command: command}))
14
+ ExecutionRegistry.for(Phronomy::Runtime.instance.event_loop).post(command)
16
15
  end
17
16
 
18
17
  def deliver_on_event_loop(command)
19
- state = Phronomy::Runtime.instance.event_loop.agent_execution_state(command.execution_id)
18
+ state = Phronomy::Agent::ExecutionRegistry.for(Phronomy::Runtime.instance.event_loop).agent_execution_state(command.execution_id)
20
19
  return unless state && state.agent.agent_id == command.agent_id
21
20
  state.invocation&.config&.fetch(:cancellation_token, nil)&.cancel!
22
21
  end