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
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Encodes execution facts using the caller's existing content transaction.
6
+ # It neither opens a transaction nor changes durable or live execution state.
7
+ # @api private
8
+ module RuntimeRecordEncoder
9
+ module_function
10
+
11
+ def encode(
12
+ execution,
13
+ agent_id:,
14
+ tx:,
15
+ snapshot:,
16
+ context_candidate:,
17
+ agent_root:
18
+ )
19
+ root = agent_root
20
+ records = []
21
+ calls = []
22
+
23
+ snapshot.fetch(:llm_results).each_with_index do |item, index|
24
+ outcome = item[:response]
25
+ error = item[:error]
26
+ llm_call_id = item.fetch(:llm_call_id).to_s
27
+ intercepted = error.is_a?(ToolCallIntercepted)
28
+ call_error = intercepted ? nil : error
29
+ output_ref = assistant_output_ref(tx, outcome)
30
+ assistant_ref = assistant_message_ref(tx, outcome)
31
+ error_ref = if call_error
32
+ tx.contents.put_json(
33
+ "class" => call_error.class.name,
34
+ "message" => call_error.message
35
+ )
36
+ end
37
+ usage_ref = if outcome && !outcome.usage.empty?
38
+ tx.contents.put_json(json_value(outcome.usage))
39
+ end
40
+ call = LLMCallRecord.new(
41
+ llm_call_id: llm_call_id,
42
+ execution_id: execution.execution_id,
43
+ sequence: execution.llm_calls.length + index + 1,
44
+ status: call_error ? :failed : :completed,
45
+ manifest_ref: item.fetch(:manifest_ref),
46
+ output_ref: output_ref,
47
+ error_ref: error_ref,
48
+ usage_ref: usage_ref,
49
+ started_at: item.fetch(:started_at),
50
+ completed_at: Time.now.utc.iso8601(6),
51
+ metadata: {
52
+ "streaming" => item[:streaming],
53
+ "tool_call_intercepted" => intercepted,
54
+ "assistant_outcome_captured" => !outcome.nil?,
55
+ "tool_call_count" => outcome ? outcome.tool_calls.length : 0
56
+ }
57
+ )
58
+ calls << call
59
+ call_ref = tx.contents.put_json(call.to_h)
60
+ records << JournalRecord.new(
61
+ agent_id: agent_id,
62
+ execution_id: execution.execution_id,
63
+ llm_call_id: llm_call_id,
64
+ kind: :llm_call_recorded,
65
+ channel: :audit,
66
+ content_ref: call_ref,
67
+ context_generation: root.transcript_generation,
68
+ context_candidate: false
69
+ )
70
+
71
+ if assistant_ref
72
+ records << JournalRecord.new(
73
+ agent_id: agent_id,
74
+ execution_id: execution.execution_id,
75
+ llm_call_id: llm_call_id,
76
+ kind: :assistant_message,
77
+ channel: :llm,
78
+ role: :assistant,
79
+ content_ref: assistant_ref,
80
+ context_generation: root.transcript_generation,
81
+ context_candidate: context_candidate,
82
+ metadata: assistant_message_metadata(outcome)
83
+ )
84
+ end
85
+ end
86
+
87
+ if (active = snapshot[:active_call])
88
+ abandoned = LLMCallRecord.new(
89
+ llm_call_id: active.fetch(:llm_call_id),
90
+ execution_id: execution.execution_id,
91
+ sequence: execution.llm_calls.length + calls.length + 1,
92
+ status: :cancelled,
93
+ manifest_ref: active.fetch(:manifest_ref),
94
+ started_at: active.fetch(:started_at),
95
+ completed_at: Time.now.utc.iso8601(6),
96
+ metadata: {
97
+ "reason" => "execution_terminalized_before_provider_settlement"
98
+ }
99
+ )
100
+ calls << abandoned
101
+ records << JournalRecord.new(
102
+ agent_id: agent_id,
103
+ execution_id: execution.execution_id,
104
+ llm_call_id: abandoned.llm_call_id,
105
+ kind: :llm_call_recorded,
106
+ channel: :audit,
107
+ content_ref: tx.contents.put_json(abandoned.to_h),
108
+ context_generation: root.transcript_generation,
109
+ context_candidate: false
110
+ )
111
+ end
112
+
113
+ snapshot.fetch(:runtime_events).each do |event|
114
+ case event.type
115
+ when :tool_call
116
+ next
117
+ when :tool_result
118
+ payload = event.payload
119
+ llm_call_id = payload[:llm_call_id] || payload["llm_call_id"]
120
+ tool_call_id = payload.fetch(:tool_call_id).to_s
121
+ tool_name = payload.fetch(:tool_name).to_s
122
+ next if execution.working_records.any? do |record|
123
+ record.kind == :tool_message && record.causation_id.to_s == tool_call_id && record.llm_call_id.to_s == llm_call_id.to_s
124
+ end
125
+ result_ref = put_runtime_content(tx, payload.fetch(:tool_result))
126
+ records << JournalRecord.new(
127
+ agent_id: agent_id,
128
+ execution_id: execution.execution_id,
129
+ llm_call_id: llm_call_id,
130
+ kind: :tool_result,
131
+ channel: :tool,
132
+ content_ref: result_ref,
133
+ causation_id: tool_call_id,
134
+ context_generation: root.transcript_generation,
135
+ context_candidate: false,
136
+ metadata: {
137
+ "tool_call_id" => tool_call_id,
138
+ "tool_name" => tool_name
139
+ }
140
+ )
141
+
142
+ message_payload = if payload.key?(:tool_message)
143
+ payload.fetch(:tool_message)
144
+ else
145
+ payload.fetch("tool_message")
146
+ end
147
+ records << JournalRecord.new(
148
+ agent_id: agent_id,
149
+ execution_id: execution.execution_id,
150
+ llm_call_id: llm_call_id,
151
+ kind: :tool_message,
152
+ channel: :tool,
153
+ role: :tool,
154
+ content_ref: tx.contents.put_json(json_value(message_payload)),
155
+ causation_id: tool_call_id,
156
+ context_generation: root.transcript_generation,
157
+ context_candidate: context_candidate,
158
+ metadata: {
159
+ "tool_call_id" => tool_call_id,
160
+ "tool_name" => tool_name
161
+ }
162
+ )
163
+ end
164
+ end
165
+ [records, calls]
166
+ end
167
+
168
+ def assistant_message_ref(tx, outcome)
169
+ return unless outcome
170
+
171
+ payload = {
172
+ "role" => "assistant",
173
+ "content" => json_value(outcome.content),
174
+ "tool_calls" => json_value(Array(outcome.tool_calls)),
175
+ "model_id" => outcome.metadata["model_id"]
176
+ }.compact
177
+ tx.contents.put_json(payload)
178
+ end
179
+
180
+ def assistant_message_metadata(outcome)
181
+ calls = Array(outcome&.tool_calls)
182
+ {
183
+ "tool_call_ids" => calls.map { |call| call.fetch("id").to_s },
184
+ "tool_names" => calls.map { |call| call.fetch("name").to_s }
185
+ }
186
+ end
187
+
188
+ def assistant_output_ref(tx, outcome)
189
+ return unless outcome&.content_present?
190
+
191
+ content = outcome.content
192
+ text = content.is_a?(String) ? content : Phronomy::CanonicalJSON.dump(content)
193
+ tx.contents.put_text(text)
194
+ end
195
+
196
+ def put_runtime_content(tx, value)
197
+ value.is_a?(String) ?
198
+ tx.contents.put_text(value) : tx.contents.put_json(json_value(value))
199
+ end
200
+
201
+ def json_value(value)
202
+ Phronomy::Values::Serializable.convert(value,
203
+ unsupported_message: "unsupported canonical runtime value")
204
+ end
205
+
206
+ private_class_method :assistant_message_ref, :assistant_message_metadata,
207
+ :assistant_output_ref
208
+ end
209
+ end
210
+ end
@@ -69,11 +69,11 @@ module Phronomy
69
69
  candidate_category: values.fetch(:candidate_category).to_sym,
70
70
  policy_category: values.fetch(:policy_category).to_sym,
71
71
  role: values[:role]&.to_sym,
72
- content: Phronomy::Agent::Immutable.copy(values.fetch(:content)),
72
+ content: Phronomy::Values::Immutable.copy(values.fetch(:content)),
73
73
  content_format: format,
74
74
  tool_call_id: values[:tool_call_id]&.to_s&.freeze,
75
75
  provenance: provenance,
76
- metadata: Phronomy::Agent::Immutable.copy(values[:metadata] || {})
76
+ metadata: Phronomy::Values::Immutable.copy(values[:metadata] || {})
77
77
  ))
78
78
  freeze
79
79
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Selects Handoff persistence; execution ownership and delivery remain in
6
+ # ExecutionCoordinator. This is not a public subclass extension contract.
7
+ class HandoffExecutionCoordinator < Phronomy::Agent::ExecutionCoordinator
8
+ private
9
+
10
+ def build_outcome_committer
11
+ HandoffOutcomeCommitter.new(agent: @agent, persistence: @agent.persistence)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Phronomy
6
+ module Agent
7
+ # Adds atomic Source transfer while sharing ordinary result persistence.
8
+ # Outcome selection deliberately retains Handoff-specific precedence.
9
+ # @api private
10
+ class HandoffOutcomeCommitter < ExecutionOutcomeCommitter
11
+ # @api private
12
+ def commit_outcome(operation)
13
+ view = operation.terminal_view
14
+ if view.callback_failure
15
+ return commit_failed_outcome(
16
+ operation,
17
+ view.callback_failure.to_stream_callback_error
18
+ )
19
+ end
20
+ error = view.source_error || view.block_error || view.invocation_error
21
+ raise error if error.is_a?(Phronomy::ExecutionRehydrationRequiredError)
22
+ return commit_failed_outcome(operation, error) if error
23
+ return commit_suspended(operation) if view.phase == :suspended
24
+
25
+ raise view.block_error if view.input_blocked || view.output_blocked
26
+ raise view.invocation_error if view.invocation_error
27
+
28
+ return commit_handed_off(operation) if view.handoff
29
+
30
+ commit_completed(operation)
31
+ rescue => caught
32
+ reconcile_terminal_error(operation, caught)
33
+ end
34
+
35
+ private
36
+
37
+ def commit_handed_off(operation)
38
+ handed_off = next_root = appended = nil
39
+ @persistence.transaction do |tx|
40
+ routing = validated_source_routing(tx, operation.execution)
41
+ context_ref = persist_handoff_context(tx, operation)
42
+ target_id = transfer_routing(tx, operation, routing, context_ref)
43
+ records, handed_off = encode_handoff(tx, operation, target_id, context_ref)
44
+ appended = append_journal(tx, operation.root, records)
45
+ save_execution(tx, operation.execution, handed_off)
46
+ next_root = save_terminal_root(tx, operation.root, appended)
47
+ end
48
+ Outcome.new(type: :handed_off, execution: handed_off, root: next_root,
49
+ appended_records: Array(appended).freeze,
50
+ result: result_base(handed_off, next_root).freeze,
51
+ error: nil, approval_request: nil)
52
+ end
53
+
54
+ def validated_source_routing(tx, current)
55
+ coordination = current.metadata.fetch("coordination")
56
+ routing = tx.handoff_states.load(coordination.fetch("main_agent_id"))
57
+ unless routing && routing.active_agent_id == @agent.agent_id && routing.handoff_revision == coordination.fetch("handoff_revision")
58
+ raise Phronomy::Storage::ConflictError, "Handoff routing changed before Source transfer"
59
+ end
60
+ if Array(routing.metadata["cancelled_execution_ids"]).include?(current.execution_id)
61
+ raise Phronomy::CancellationError, "Handoff Source turn was cancelled"
62
+ end
63
+ routing
64
+ end
65
+
66
+ def persist_handoff_context(tx, operation)
67
+ manifest = SavedContextReader.manifest_from_ref(@agent, operation.execution.metadata.fetch("manifest_ref"))
68
+ context = HandoffProjection.new.build_terminal(view: operation.terminal_view.handoff,
69
+ manifest: manifest, persistence: tx, source_agent_id: @agent.agent_id)
70
+ tx.contents.put_json(context.to_h)
71
+ end
72
+
73
+ def transfer_routing(tx, operation, routing, context_ref)
74
+ request = operation.terminal_view.handoff
75
+ target_id = "handoff-target-#{Digest::SHA256.hexdigest([operation.execution.execution_id, request.target_agent_id].join("\0"))}"
76
+ target_root = tx.agents.load(request.target_agent_id)
77
+ target_definition = {"id" => target_root.agent_definition_id, "version" => target_root.agent_definition_version}
78
+ transfer = routing.with(active_agent_id: request.target_agent_id,
79
+ active_handoff_context_ref: context_ref, phase: "target_pending",
80
+ pending_source_execution_id: operation.execution.execution_id, pending_target_execution_id: target_id,
81
+ metadata: routing.metadata.merge("target_definition" => target_definition))
82
+ tx.handoff_states.save(routing.main_agent_id, expected_revision: routing.handoff_revision, state: transfer)
83
+ target_id
84
+ end
85
+
86
+ def encode_handoff(tx, operation, target_id, context_ref)
87
+ current = operation.execution
88
+ request = operation.terminal_view.handoff
89
+ encoded_records, call_records = encode_runtime_records(tx, operation, context_candidate: false)
90
+ audit_record = encode_handoff_audit(tx, operation)
91
+ records = current.working_records + encoded_records + [audit_record]
92
+ handed_off = current.with(
93
+ status: :handed_off,
94
+ phase: :handed_off,
95
+ working_records: [],
96
+ llm_calls: current.llm_calls + call_records,
97
+ approval_request: nil,
98
+ terminal_reason: "handed_off",
99
+ metadata: current.metadata.merge("handoff_target_agent_id" => request.target_agent_id,
100
+ "handoff_target_execution_id" => target_id, "handoff_context_ref" => context_ref)
101
+ )
102
+ [records, handed_off]
103
+ end
104
+
105
+ def encode_handoff_audit(tx, operation)
106
+ request = operation.terminal_view.handoff
107
+ audit_ref = tx.contents.put_json(
108
+ "target_agent_id" => request.target_agent_id,
109
+ "responsibility" => request.responsibility,
110
+ "selection_intent" => request.selection_intent.to_h do |category, included|
111
+ [category.to_s, included]
112
+ end
113
+ )
114
+ JournalRecord.new(
115
+ agent_id: @agent.agent_id,
116
+ execution_id: operation.execution.execution_id,
117
+ llm_call_id: request.llm_call_id,
118
+ kind: :execution_handed_off,
119
+ channel: :audit,
120
+ content_ref: audit_ref,
121
+ context_generation: operation.root.transcript_generation,
122
+ context_candidate: false,
123
+ metadata: {
124
+ "target_agent_id" => request.target_agent_id,
125
+ "handoff_tool_call_id" => request.tool_call_id
126
+ }.compact
127
+ )
128
+ end
129
+ end
130
+ end
131
+ end
@@ -14,7 +14,7 @@ module Phronomy
14
14
  source = values.transform_keys(&:to_s)
15
15
  raise ArgumentError, "HandoffState schema mismatch" unless source.keys.sort == ATTRIBUTES.sort
16
16
  canonical = Phronomy::CanonicalJSON.load(Phronomy::CanonicalJSON.dump(source))
17
- ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::Agent::Immutable.copy(canonical.fetch(key))) }
17
+ ATTRIBUTES.each { |key| instance_variable_set("@#{key}", Phronomy::Values::Immutable.copy(canonical.fetch(key))) }
18
18
  raise ArgumentError, "missing main_agent_id" if main_agent_id.to_s.empty?
19
19
  raise ArgumentError, "invalid handoff_revision" unless handoff_revision.is_a?(Integer) && handoff_revision >= 0
20
20
  raise ArgumentError, "invalid metadata" unless metadata.is_a?(Hash)
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../engine/concurrency/worker_input_restricted"
4
+
3
5
  module Phronomy
4
6
  module Agent
5
7
  class JournalProjection
8
+ include Phronomy::Concurrency::WorkerInputRestricted
9
+
6
10
  MESSAGE_KINDS = %i[
7
11
  external_message
8
12
  assistant_message
@@ -41,9 +41,9 @@ module Phronomy
41
41
  ATTRIBUTES.each do |name|
42
42
  value = values.fetch(name)
43
43
  value = value.to_sym if %i[kind channel role visibility].include?(name) && value
44
- instance_variable_set("@#{name}", Immutable.copy(value))
44
+ instance_variable_set("@#{name}", Phronomy::Values::Immutable.copy(value))
45
45
  end
46
- Immutable.validate_canonical_json!(metadata, label: "Journal metadata")
46
+ Phronomy::Values::Immutable.validate_canonical_json!(metadata, label: "Journal metadata")
47
47
  freeze
48
48
  end
49
49
 
@@ -52,7 +52,7 @@ module Phronomy
52
52
  end
53
53
 
54
54
  # Current semantic payload representation. Persistence format identity and
55
- # compatibility validation are owned by Persistence::DurableCodec.
55
+ # compatibility validation are owned by Agent::Persistence::Codec.
56
56
  def to_h
57
57
  ATTRIBUTES.to_h { |name| [name.to_s, public_send(name)] }
58
58
  end
@@ -31,11 +31,11 @@ module Phronomy
31
31
  ATTRIBUTES.each do |name|
32
32
  value = values.fetch(name)
33
33
  value = value.to_sym if name == :status
34
- instance_variable_set("@#{name}", Immutable.copy(value))
34
+ instance_variable_set("@#{name}", Phronomy::Values::Immutable.copy(value))
35
35
  end
36
36
  raise ArgumentError, "unknown LLM Call status: #{status.inspect}" unless STATUSES.include?(status)
37
37
  raise ArgumentError, "LLM Call sequence must be positive" unless sequence.positive?
38
- Immutable.validate_canonical_json!(metadata, label: "LLM Call metadata")
38
+ Phronomy::Values::Immutable.validate_canonical_json!(metadata, label: "LLM Call metadata")
39
39
  freeze
40
40
  end
41
41
 
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../engine/concurrency/worker_input_restricted"
4
+
3
5
  require "time"
4
6
 
5
7
  module Phronomy
6
8
  module Agent
7
9
  class AgentRoot
10
+ include Phronomy::Concurrency::WorkerInputRestricted
11
+
8
12
  LIFECYCLE_STATUSES = %i[idle active suspended closed invalidated].freeze
9
13
 
10
14
  ATTRIBUTES = %i[
@@ -36,7 +40,7 @@ module Phronomy
36
40
  ATTRIBUTES.each do |name|
37
41
  value = attributes.fetch(name)
38
42
  value = value.to_sym if name == :lifecycle_status
39
- instance_variable_set("@#{name}", Immutable.copy(value))
43
+ instance_variable_set("@#{name}", Phronomy::Values::Immutable.copy(value))
40
44
  end
41
45
  unless LIFECYCLE_STATUSES.include?(lifecycle_status)
42
46
  raise ArgumentError, "unknown Agent lifecycle status: #{lifecycle_status.inspect}"
@@ -44,7 +48,7 @@ module Phronomy
44
48
  raise ArgumentError, "agent_revision must be non-negative" if agent_revision.negative?
45
49
  raise ArgumentError, "context_revision must be non-negative" if context_revision.negative?
46
50
  raise ArgumentError, "journal_position must be non-negative" if journal_position.negative?
47
- Immutable.validate_canonical_json!(metadata, label: "Agent metadata")
51
+ Phronomy::Values::Immutable.validate_canonical_json!(metadata, label: "Agent metadata")
48
52
  freeze
49
53
  end
50
54
 
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Agent owns this fresh-instance construction contract. Application
6
+ # composition supplies the concrete default once during loading.
7
+ # @api private
8
+ module DefaultPersistence
9
+ FACTORIES = {}
10
+ private_constant :FACTORIES
11
+
12
+ # A private boot binding, not an application extension registry.
13
+ # @api private
14
+ def self.install_factory(factory)
15
+ raise ArgumentError, "factory must respond to call" unless factory.respond_to?(:call)
16
+
17
+ FACTORIES.replace(persistence: factory).freeze
18
+ nil
19
+ end
20
+
21
+ # Invoke only after explicit and configured instances have been excluded.
22
+ # No instance is cached, shared, or installed in global configuration.
23
+ # @api private
24
+ def self.build
25
+ FACTORIES.fetch(:persistence).call
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,13 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- class Runtime
4
+ module Agent
5
5
  # Process-local authoritative owner registry for mutable Agent instances.
6
6
  #
7
7
  # This is not a cache. While a Runtime is alive, one agent_id maps to at most
8
8
  # one mutable live Agent object. Construction is reserved before durable
9
9
  # create/load so concurrent callers cannot materialize independent objects.
10
- class AgentOwnershipRegistry
10
+ # @api private
11
+ class OwnershipRegistry
12
+ # Candidates have no side effects. Runtime retains the first registration.
13
+ # Existing instances remain available for lookup and transition completion
14
+ # after admission closes; their own gate rejects new ownership changes.
15
+ def self.for(runtime)
16
+ existing_for(runtime) || runtime.__register_shutdown_participant(
17
+ key: self, participant: new(runtime: runtime)
18
+ )
19
+ end
20
+
21
+ def self.existing_for(runtime)
22
+ runtime.__shutdown_participant(key: self)
23
+ end
24
+
11
25
  Entry = Data.define(:state, :agent, :token)
12
26
  private_constant :Entry
13
27
 
@@ -26,9 +40,9 @@ module Phronomy
26
40
  begin
27
41
  agent = yield(@runtime)
28
42
  rescue Phronomy::AgentAlreadyExistsError,
29
- Phronomy::Persistence::ConflictError,
30
- Phronomy::Persistence::NotFoundError,
31
- Phronomy::Persistence::SerializationError,
43
+ Phronomy::Storage::ConflictError,
44
+ Phronomy::Storage::NotFoundError,
45
+ Phronomy::Storage::SerializationError,
32
46
  ArgumentError,
33
47
  Phronomy::ConfigurationError
34
48
  release_construction!(key, token)
@@ -156,10 +170,10 @@ module Phronomy
156
170
  @mutex.synchronize do
157
171
  entry = @entries[key]
158
172
  validate_purge_entry!(entry, agent, token, key)
173
+ agent.send(:__mark_purged!, @runtime)
159
174
  @entries.delete(key)
160
175
  @condition.broadcast
161
176
  end
162
- agent.send(:__mark_purged!, @runtime)
163
177
  true
164
178
  end
165
179
 
@@ -169,10 +183,10 @@ module Phronomy
169
183
  @mutex.synchronize do
170
184
  entry = @entries[key]
171
185
  validate_purge_entry!(entry, agent, token, key)
186
+ agent.send(:__restore_live_after_purge_abort!, @runtime)
172
187
  @entries[key] = Entry.new(state: :live, agent: agent, token: nil)
173
188
  @condition.broadcast
174
189
  end
175
- agent.send(:__restore_live_after_purge_abort!, @runtime)
176
190
  true
177
191
  end
178
192
 
@@ -183,6 +197,7 @@ module Phronomy
183
197
  key = normalize_agent_id(agent.agent_id)
184
198
  @mutex.synchronize do
185
199
  validate_purge_entry!(@entries[key], agent, token, key)
200
+ agent.send(:__mark_ownership_recovery_required!, @runtime)
186
201
  @entries[key] = Entry.new(
187
202
  state: :recovery_required,
188
203
  agent: agent,
@@ -190,7 +205,6 @@ module Phronomy
190
205
  )
191
206
  @condition.broadcast
192
207
  end
193
- agent.send(:__mark_ownership_recovery_required!, @runtime)
194
208
  true
195
209
  end
196
210
 
@@ -204,7 +218,7 @@ module Phronomy
204
218
 
205
219
  # Live/recovery-required entries do not prevent Runtime shutdown. Only an
206
220
  # ownership transition that is still actively changing state must settle.
207
- def wait_until_stable(deadline)
221
+ def wait_until_idle(deadline)
208
222
  @mutex.synchronize do
209
223
  while @entries.values.any? { |entry| %i[constructing purging].include?(entry.state) }
210
224
  remaining = deadline - monotonic_now
@@ -217,7 +231,7 @@ module Phronomy
217
231
  end
218
232
  end
219
233
 
220
- def shutdown!
234
+ def after_runtime_shutdown
221
235
  agents = @mutex.synchronize do
222
236
  @state = :terminated
223
237
  owned = @entries.values.filter_map(&:agent).uniq
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../common/error"
4
+
3
5
  module Phronomy
4
6
  class AgentAlreadyExistsError < Phronomy::Error; end
5
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
  class AgentBusyError < Phronomy::Error; end
5
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
  class AgentPurgedError < Phronomy::Error; end
5
7
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../common/error"
4
+
5
+ module Phronomy
6
+ class HandoffError < Error; 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 an Application stream callback fails and
5
7
  # +stream_callback_error_policy+ is +:fail_task+.