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
@@ -68,7 +68,9 @@ module Phronomy
68
68
  end
69
69
 
70
70
  # @api public
71
- def get(team_id) = Phronomy::Runtime.instance.__get_team(team_id, klass: self)
71
+ def get(team_id)
72
+ TeamOwnershipRegistry.existing_for(Phronomy::Runtime.instance)&.get(team_id.to_s, klass: self)
73
+ end
72
74
 
73
75
  # @api private
74
76
  def _coordinator_model = @coordinator_model
@@ -93,9 +95,9 @@ module Phronomy
93
95
  raise Phronomy::EventLoopReentrancyError, "Team construction cannot block EventLoop" if Phronomy::Runtime.in_event_loop_context?
94
96
  key = id.to_s
95
97
  raise ArgumentError, "team_id must not be empty" if key.empty?
96
- store = persistence || Phronomy.configuration.persistence || Phronomy::Persistence::InMemory.new
98
+ store = persistence || Phronomy.configuration.persistence || Phronomy::Persistence.in_memory
97
99
  runtime = Phronomy::Runtime.instance
98
- runtime.__team_owner(key, klass: self, create: create, persistence: store) do
100
+ TeamOwnershipRegistry.for(runtime).fetch(key, klass: self, create: create, persistence: store) do
99
101
  instance = allocate
100
102
  instance.send(:initialize, key.freeze, store, metadata, listener, runtime, create: create)
101
103
  instance
@@ -159,6 +161,7 @@ module Phronomy
159
161
 
160
162
  def initialize(id, store, metadata, listener, runtime, create:)
161
163
  @team_id, @persistence, @listener, @runtime = id, store, listener, runtime
164
+ @admissions = AdmissionRegistry.for(@runtime)
162
165
  @tokens_mutex, @tokens = Mutex.new, {}
163
166
  @coordinator_classes = {}
164
167
  definition = self.class.team_definition
@@ -189,17 +192,17 @@ module Phronomy
189
192
 
190
193
  def with_admission
191
194
  assert_caller!
192
- @runtime.__admit_multi_agent(self)
195
+ @admissions.admit!(self)
193
196
  admitted = true
194
197
  yield
195
198
  ensure
196
- @runtime.__release_multi_agent(self) if admitted
199
+ @admissions.release!(self) if admitted
197
200
  end
198
201
 
199
202
  def read_execution(id)
200
203
  execution = persistence.team_executions.load(id)
201
204
  unless execution.team_id == team_id
202
- raise Phronomy::Persistence::ConflictError, "Team execution #{id} belongs to another Team"
205
+ raise Phronomy::Storage::ConflictError, "Team execution #{id} belongs to another Team"
203
206
  end
204
207
  execution
205
208
  end
@@ -235,7 +238,7 @@ module Phronomy
235
238
  raise error unless intended
236
239
  begin
237
240
  confirmed = read_execution(id)
238
- rescue Phronomy::Persistence::NotFoundError
241
+ rescue Phronomy::Storage::NotFoundError
239
242
  raise error
240
243
  end
241
244
  raise error unless confirmed.to_h == intended.to_h
@@ -251,7 +254,7 @@ module Phronomy
251
254
  intended = nil
252
255
  persistence.transaction do |tx|
253
256
  current = tx.team_executions.load(id)
254
- raise Phronomy::Persistence::ConflictError, "Team execution owner mismatch" unless current.team_id == team_id
257
+ raise Phronomy::Storage::ConflictError, "Team execution owner mismatch" unless current.team_id == team_id
255
258
  intended = yield(current, tx)
256
259
  next if intended.equal?(current)
257
260
  tx.team_executions.save(id, expected_revision: current.execution_revision, execution: intended)
@@ -264,7 +267,7 @@ module Phronomy
264
267
  rescue => error
265
268
  confirmed = read_execution(id)
266
269
  return confirmed if intended && confirmed.to_h == intended.to_h
267
- if error.is_a?(Phronomy::Persistence::ConflictError) && (attempts += 1) < 8
270
+ if error.is_a?(Phronomy::Storage::ConflictError) && (attempts += 1) < 8
268
271
  retry
269
272
  end
270
273
  raise error
@@ -383,7 +386,7 @@ module Phronomy
383
386
  begin
384
387
  persistence.agents.load(id)
385
388
  present = true
386
- rescue Phronomy::Persistence::NotFoundError
389
+ rescue Phronomy::Storage::NotFoundError
387
390
  present = false
388
391
  end
389
392
  token.raise_if_cancelled! unless present
@@ -418,7 +421,7 @@ module Phronomy
418
421
  entries = fresh.assignments.map do |entry|
419
422
  next entry unless entry.fetch("task_id") == assigned.fetch("task_id")
420
423
  next entry unless entry.fetch("state") == "reserved"
421
- raise Phronomy::Persistence::ConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
424
+ raise Phronomy::Storage::ConflictError, "Assignment execution changed" unless entry.fetch("execution_id") == outcome.fetch(:execution_id)
422
425
  entry.merge("state" => outcome.fetch(:status).to_s,
423
426
  "result_ref" => outcome[:result_ref], "error_ref" => outcome[:error_ref])
424
427
  end
@@ -483,7 +486,7 @@ module Phronomy
483
486
  param :description, type: :string, desc: "Worker task"
484
487
  param :metadata, type: :string, desc: "Optional metadata", required: false
485
488
  else
486
- param :summary, type: :string, desc: "TaskResult summary", required: false
489
+ param :summary, type: :string, desc: "Task generation summary", required: false
487
490
  end
488
491
  define_method(:call_async) do |args, cancellation_token: nil, config: {}|
489
492
  validated, schema_error = send(:validate_and_coerce, args)
@@ -512,20 +515,20 @@ module Phronomy
512
515
  operations = fresh.metadata.fetch("operations")
513
516
  if (prior = operations[key])
514
517
  unless prior.fetch("operation") == operation.to_s && prior.fetch("arguments") == argument_values
515
- raise Phronomy::Persistence::ConflictError, "Team operation #{key} identity mismatch"
518
+ raise Phronomy::Storage::ConflictError, "Team operation #{key} identity mismatch"
516
519
  end
517
520
  next fresh
518
521
  end
519
522
  raise Phronomy::CancellationError, "Team run cancelled" if fresh.metadata["cancel_requested"]
520
- raise Phronomy::Persistence::ConflictError, "Team task generation is closed" unless fresh.active? && fresh.phase == "coordinator"
523
+ raise Phronomy::Storage::ConflictError, "Team task generation is closed" unless fresh.active? && fresh.phase == "coordinator"
521
524
  coordinator = tx.executions.load(fresh.coordinator.fetch("execution_id"))
522
525
  unless coordinator.agent_id == fresh.coordinator.fetch("agent_id")
523
- raise Phronomy::Persistence::ConflictError, "Team coordinator owner mismatch"
526
+ raise Phronomy::Storage::ConflictError, "Team coordinator owner mismatch"
524
527
  end
525
- batch = Array(coordinator.metadata[Phronomy::Agent::RecoverySupport::TOOL_BATCH_METADATA_KEY])
528
+ batch = Array(coordinator.metadata[Phronomy::Agent::ExecutionMetadata::TOOL_BATCH_METADATA_KEY])
526
529
  requested = batch.find { |entry| entry.fetch("tool_invocation_id") == key }
527
530
  unless requested && requested.fetch("status") == "authorized" && requested.fetch("tool_name") == operation.to_s && requested.fetch("arguments").compact == argument_values
528
- raise Phronomy::Persistence::ConflictError, "Team operation #{key} is not the authorized call"
531
+ raise Phronomy::Storage::ConflictError, "Team operation #{key} is not the authorized call"
529
532
  end
530
533
  # All calls passed the Agent authorization barrier. Commit this finite
531
534
  # batch in Provider order, so finalize cannot overtake queued tasks.
@@ -542,7 +545,7 @@ module Phronomy
542
545
  raise Phronomy::ConfigurationError, "Cannot enqueue after finalize" if metadata["finalized"]
543
546
  task = {"id" => Digest::SHA256.hexdigest(entry_id)[0, 32], "description" => values.fetch("description"), "metadata" => values["metadata"]}
544
547
  tasks << task
545
- output = "TaskResult ##{tasks.length} enqueued: #{task.fetch("description")}"
548
+ output = "Task ##{tasks.length} enqueued: #{task.fetch("description")}"
546
549
  else
547
550
  output = "Finalized. #{tasks.size} task(s) enqueued. #{values["summary"]}".strip
548
551
  metadata["finalized"] = true
@@ -14,7 +14,7 @@ module Phronomy
14
14
  source = values.transform_keys(&:to_s)
15
15
  raise ArgumentError, "TeamExecution 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 team_execution_id" if team_execution_id.to_s.empty?
19
19
  raise ArgumentError, "invalid execution_revision" unless execution_revision.is_a?(Integer) && execution_revision >= 0
20
20
  raise ArgumentError, "invalid metadata" unless metadata.is_a?(Hash)
@@ -1,10 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- class Runtime
4
+ module MultiAgent
5
5
  # Runtime-local Team identity and construction exclusion; no execution state.
6
6
  # @api private
7
7
  class TeamOwnershipRegistry
8
+ def self.for(runtime)
9
+ existing_for(runtime) || runtime.__register_shutdown_participant(
10
+ key: self, participant: new
11
+ )
12
+ end
13
+
14
+ def self.existing_for(runtime)
15
+ runtime.__shutdown_participant(key: self)
16
+ end
17
+
8
18
  def initialize
9
19
  @mutex = Mutex.new
10
20
  @condition = ConditionVariable.new
@@ -18,7 +28,7 @@ module Phronomy
18
28
  loop do
19
29
  raise Phronomy::RuntimeShutdownError, "Runtime is draining" if @draining
20
30
  if (owner = @owners[id])
21
- raise Phronomy::Persistence::ConflictError, "Team #{id} already exists" if create
31
+ raise Phronomy::Storage::ConflictError, "Team #{id} already exists" if create
22
32
  unless owner.is_a?(klass) && owner.persistence.equal?(persistence)
23
33
  raise Phronomy::ConfigurationError, "Team #{id} owner or Persistence mismatch"
24
34
  end
@@ -58,7 +68,7 @@ module Phronomy
58
68
  end
59
69
  end
60
70
 
61
- def wait_until_stable(deadline)
71
+ def wait_until_idle(deadline)
62
72
  @mutex.synchronize do
63
73
  until @constructing.empty?
64
74
  remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -69,7 +79,7 @@ module Phronomy
69
79
  end
70
80
  end
71
81
 
72
- def shutdown!
82
+ def after_runtime_shutdown
73
83
  @mutex.synchronize { @owners.clear }
74
84
  end
75
85
  end
@@ -14,7 +14,7 @@ module Phronomy
14
14
  source = values.transform_keys(&:to_s)
15
15
  raise ArgumentError, "TeamRoot 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 team_id" if team_id.to_s.empty?
19
19
  raise ArgumentError, "invalid team_revision" unless team_revision.is_a?(Integer) && team_revision >= 0
20
20
  raise ArgumentError, "invalid metadata" unless metadata.is_a?(Hash)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../common/error"
4
+
5
+ module Phronomy
6
+ class ParseError < Error; end
7
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class Persistence
5
+ # Content-addressed immutable content repository. ContentStore has its own
6
+ # codec/canonicalization boundary and is not wrapped in DurableRecord.
7
+ #
8
+ # @return [Object]
9
+ # @api public
10
+ def contents = @repositories.contents
11
+
12
+ # Runtime/domain-facing AgentRoot repository. The injected backend is
13
+ # record-oriented; this facade owns encode/decode.
14
+ #
15
+ # @return [Object]
16
+ # @api public
17
+ def agents = @repositories.agents
18
+
19
+ # Runtime/domain-facing append-only Agent Journal repository.
20
+ #
21
+ # @return [Object]
22
+ # @api public
23
+ def journals = @repositories.journals
24
+
25
+ # Runtime/domain-facing AgentExecution repository.
26
+ #
27
+ # @return [Object]
28
+ # @api public
29
+ def executions = @repositories.executions
30
+
31
+ # Runtime/domain-facing durable Workflow snapshot repository.
32
+ #
33
+ # @return [Object]
34
+ # @api public
35
+ def workflow_states = @repositories.workflow_states
36
+
37
+ # Purpose-specific durable Handoff and Team repositories in atomic_all.
38
+ # @api public
39
+ def handoff_states = @repositories.handoff_states
40
+
41
+ # @api public
42
+ def teams = @repositories.teams
43
+
44
+ # @api public
45
+ def team_executions = @repositories.team_executions
46
+
47
+ # Read-only exact Agent execution result; never creates a live Agent owner.
48
+ # Result is nil until a result_ref exists; errors remain canonical data.
49
+ # @api public
50
+ def execution_result(execution_id)
51
+ assert_observation_thread!
52
+ Phronomy::Agent::Persistence::Queries.new(self).execution_result(execution_id)
53
+ end
54
+
55
+ # Follows one exact Handoff turn without constructing a live Agent or graph.
56
+ # A committed absent Target reservation remains active; failed reads raise.
57
+ # @api public
58
+ def handoff_result(execution_id, main_agent_id: nil)
59
+ assert_observation_thread!
60
+ Phronomy::Agent::Persistence::Queries.new(self).handoff_result(execution_id, main_agent_id: main_agent_id)
61
+ end
62
+
63
+ # Lists retained active and terminal executions in lexical ID order.
64
+ # after is an exclusive ID cursor; discovery does not imply request dedup.
65
+ # @api public
66
+ def list_executions(agent_id, after: nil, limit: 100)
67
+ assert_observation_thread!
68
+ Phronomy::Agent::Persistence::Queries.new(self).list_executions(agent_id, after: after, limit: limit)
69
+ end
70
+
71
+ # Read-only exact Team result, independent of current Team class/Proc wiring.
72
+ # @api public
73
+ def team_execution_result(team_execution_id)
74
+ assert_observation_thread!
75
+ Phronomy::MultiAgent::Persistence::Queries.new(self).team_execution_result(team_execution_id)
76
+ end
77
+
78
+ # Lists retained Team runs; no continuation or callback delivery occurs.
79
+ # @api public
80
+ def list_team_executions(team_id, after: nil, limit: 100)
81
+ assert_observation_thread!
82
+ Phronomy::MultiAgent::Persistence::Queries.new(self).list_team_executions(team_id, after: after, limit: limit)
83
+ end
84
+
85
+ # The selected raw storage backend, for backend-specific administration.
86
+ # Read and write domain records through the repository accessors above.
87
+ # @api public
88
+ attr_reader :backend
89
+
90
+ # Assembles domain repositories over one synchronous storage backend.
91
+ # @api public
92
+ def initialize(backend:)
93
+ Phronomy::Storage::Backend.validate_capabilities!(backend)
94
+ PersistenceComposition::StorageSchema.validate!(backend.view)
95
+ @backend = backend
96
+ @repositories = PersistenceComposition::Repositories.new(backend.view)
97
+ end
98
+
99
+ # Constructs an isolated in-memory storage domain with standard codecs.
100
+ # @api public
101
+ def self.in_memory
102
+ new(backend: Phronomy::Storage::Backends::InMemory.new(resources: PersistenceComposition::StorageSchema.resources))
103
+ end
104
+
105
+ # @api public
106
+ def capabilities = {atomic_all: true, atomic_admission: true, optimistic_revision: true}.freeze
107
+
108
+ # Executes a single backend transaction and exposes domain repositories
109
+ # bound to its raw view. Codec and caller failures remain inside the backend
110
+ # block so the backend can roll them back. The block result is returned.
111
+ # Explicit nested calls on this service use savepoint semantics: a failed
112
+ # inner scope rolls back and re-raises; a successful inner scope is committed
113
+ # only with its outer scope. See Storage::Backend#transaction.
114
+ # Storage failures whose commit outcome is fundamentally
115
+ # unknown remain backend/database failures; Phronomy does not claim
116
+ # exactly-once semantics for such failures.
117
+ # @api public
118
+ def transaction
119
+ backend.transaction do |raw_view|
120
+ repositories = PersistenceComposition::Repositories.new(raw_view)
121
+ yield repositories
122
+ end
123
+ end
124
+
125
+ # @api public
126
+ def assert_agent_watermark!(agent_id:, agent_revision:, journal_position:)
127
+ @repositories.assert_agent_watermark!(
128
+ agent_id: agent_id, agent_revision: agent_revision, journal_position: journal_position
129
+ )
130
+ end
131
+
132
+ private
133
+
134
+ def assert_observation_thread!
135
+ if Phronomy::Runtime.in_event_loop_context?
136
+ raise Phronomy::EventLoopReentrancyError, "Persistence observation cannot block EventLoop"
137
+ end
138
+ end
139
+ end
140
+ end
@@ -32,10 +32,10 @@ module Phronomy
32
32
  PRE_S3_AGENT_ROOT_KEYS,
33
33
  label: "pre-S3 AgentRoot"
34
34
  )
35
- DurableCodec.encode_agent_root(
35
+ Phronomy::Agent::Persistence::Codec.encode_agent_root(
36
36
  Phronomy::Agent::AgentRoot.from_h(source)
37
37
  )
38
- rescue Phronomy::Persistence::SerializationError
38
+ rescue Phronomy::Storage::SerializationError
39
39
  raise
40
40
  rescue => error
41
41
  migration_error("AgentRoot", error)
@@ -68,8 +68,8 @@ module Phronomy
68
68
  execution_id: source.fetch("execution_id")
69
69
  )
70
70
  execution = Phronomy::Agent::AgentExecution.from_h(source)
71
- DurableCodec.encode_agent_execution(execution)
72
- rescue Phronomy::Persistence::SerializationError
71
+ Phronomy::Agent::Persistence::Codec.encode_agent_execution(execution)
72
+ rescue Phronomy::Storage::SerializationError
73
73
  raise
74
74
  rescue => error
75
75
  migration_error("AgentExecution", error)
@@ -77,40 +77,40 @@ module Phronomy
77
77
 
78
78
  def journal_record(hash)
79
79
  source = normalize_legacy_journal_record(hash, label: "pre-S3 JournalRecord")
80
- DurableCodec.encode_journal_record(
80
+ Phronomy::Agent::Persistence::Codec.encode_journal_record(
81
81
  Phronomy::Agent::JournalRecord.from_h(source)
82
82
  )
83
- rescue Phronomy::Persistence::SerializationError
83
+ rescue Phronomy::Storage::SerializationError
84
84
  raise
85
85
  rescue => error
86
86
  migration_error("JournalRecord", error)
87
87
  end
88
88
 
89
89
  def workflow_state(workflow_instance_id:, revision:, snapshot:)
90
- DurableCodec.encode_workflow_state(
90
+ Phronomy::Workflow::Persistence::Codec.encode_workflow_state(
91
91
  workflow_instance_id: workflow_instance_id,
92
92
  workflow_revision: Integer(revision),
93
93
  snapshot: snapshot
94
94
  )
95
- rescue Phronomy::Persistence::SerializationError
95
+ rescue Phronomy::Storage::SerializationError
96
96
  raise
97
97
  rescue => error
98
98
  migration_error("Workflow state", error)
99
99
  end
100
100
 
101
101
  # LLMInputManifest is a ContentStore codec boundary, not a
102
- # Persistence::DurableRecord. Its pre-CG-07 integer version 1 is migrated
102
+ # Storage::DurableRecord. Its pre-CG-07 integer version 1 is migrated
103
103
  # explicitly to the pre-1.0 string version "0.1".
104
104
  def llm_input_manifest(hash)
105
105
  source = stringify_keys(hash)
106
106
  old_version = source.fetch("version")
107
107
  unless old_version == 1 || old_version == "1"
108
- raise Phronomy::Persistence::SerializationError,
108
+ raise Phronomy::Storage::SerializationError,
109
109
  "unsupported pre-S3 LLMInputManifest version: #{old_version.inspect}"
110
110
  end
111
111
  source["version"] = Phronomy::Agent::LLMInputManifest::VERSION
112
112
  Phronomy::Agent::LLMInputManifest.from_h(source).to_h
113
- rescue Phronomy::Persistence::SerializationError
113
+ rescue Phronomy::Storage::SerializationError
114
114
  raise
115
115
  rescue => error
116
116
  migration_error("LLMInputManifest", error)
@@ -141,13 +141,13 @@ module Phronomy
141
141
  )
142
142
  parents = PRE_S3_APPROVAL_PARENT_KEYS.select { |key| source.key?(key) }
143
143
  unless parents.length == 1
144
- raise Phronomy::Persistence::SerializationError,
144
+ raise Phronomy::Storage::SerializationError,
145
145
  "pre-S3 approval_request must contain exactly one parent identity, got #{parents.inspect}"
146
146
  end
147
147
 
148
148
  items = source.fetch("items")
149
149
  unless items.is_a?(Array) && !items.empty?
150
- raise Phronomy::Persistence::SerializationError,
150
+ raise Phronomy::Storage::SerializationError,
151
151
  "pre-S3 approval_request items must be a non-empty Array"
152
152
  end
153
153
  source["items"] = items.map.with_index do |item, index|
@@ -164,7 +164,7 @@ module Phronomy
164
164
  source.delete("agent_invocation_id")
165
165
  source["execution_id"] = execution_id.to_s
166
166
  elsif source.fetch("execution_id").to_s != execution_id.to_s
167
- raise Phronomy::Persistence::SerializationError,
167
+ raise Phronomy::Storage::SerializationError,
168
168
  "pre-S3 approval_request execution_id does not match AgentExecution"
169
169
  end
170
170
  source
@@ -180,7 +180,7 @@ module Phronomy
180
180
  details = []
181
181
  details << "missing=#{missing.inspect}" unless missing.empty?
182
182
  details << "unknown=#{unknown.inspect}" unless unknown.empty?
183
- raise Phronomy::Persistence::SerializationError,
183
+ raise Phronomy::Storage::SerializationError,
184
184
  "#{label} schema mismatch (#{details.join(", ")})"
185
185
  end
186
186
  private_class_method :validate_allowed_keys!
@@ -197,17 +197,17 @@ module Phronomy
197
197
 
198
198
  def stringify_keys(hash)
199
199
  unless hash.is_a?(Hash)
200
- raise Phronomy::Persistence::SerializationError,
200
+ raise Phronomy::Storage::SerializationError,
201
201
  "migration input must be a Hash"
202
202
  end
203
203
  hash.each_with_object({}) do |(key, value), result|
204
204
  unless key.is_a?(String) || key.is_a?(Symbol)
205
- raise Phronomy::Persistence::SerializationError,
205
+ raise Phronomy::Storage::SerializationError,
206
206
  "migration key must be String or Symbol, got #{key.class}"
207
207
  end
208
208
  string_key = key.to_s
209
209
  if result.key?(string_key)
210
- raise Phronomy::Persistence::SerializationError,
210
+ raise Phronomy::Storage::SerializationError,
211
211
  "duplicate migration key after normalization: #{string_key.inspect}"
212
212
  end
213
213
  result[string_key] = value
@@ -216,7 +216,7 @@ module Phronomy
216
216
  private_class_method :stringify_keys
217
217
 
218
218
  def migration_error(label, error)
219
- raise Phronomy::Persistence::SerializationError,
219
+ raise Phronomy::Storage::SerializationError,
220
220
  "cannot migrate pre-S3 #{label}: #{error.class}: #{error.message}"
221
221
  end
222
222
  private_class_method :migration_error
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module PersistenceComposition
5
+ # Assemble domain repositories over one raw backend or transaction view.
6
+ # @api private
7
+ class Repositories
8
+ attr_reader :contents
9
+
10
+ def initialize(view)
11
+ @view = view
12
+ @contents = Phronomy::ContentStore::StoredContents.new(view)
13
+ @domain_repositories = {}
14
+ @repository_lock = Mutex.new
15
+ end
16
+
17
+ def agents
18
+ domain_repository(:agents) do
19
+ Phronomy::Agent::Persistence::AgentRepository.new(@view)
20
+ end
21
+ end
22
+
23
+ def journals
24
+ domain_repository(:journals) do
25
+ Phronomy::Agent::Persistence::JournalRepository.new(@view)
26
+ end
27
+ end
28
+
29
+ def executions
30
+ domain_repository(:executions) do
31
+ Phronomy::Agent::Persistence::ExecutionRepository.new(@view)
32
+ end
33
+ end
34
+
35
+ def workflow_states
36
+ domain_repository(:workflow_states) do
37
+ Phronomy::Workflow::Persistence::StateRepository.new(@view)
38
+ end
39
+ end
40
+
41
+ def handoff_states
42
+ domain_repository(:handoff_states) do
43
+ Phronomy::Agent::Persistence::HandoffStateRepository.new(@view)
44
+ end
45
+ end
46
+
47
+ def teams
48
+ domain_repository(:teams) do
49
+ Phronomy::MultiAgent::Persistence::TeamRepository.new(@view)
50
+ end
51
+ end
52
+
53
+ def team_executions
54
+ domain_repository(:team_executions) do
55
+ Phronomy::MultiAgent::Persistence::TeamExecutionRepository.new(@view)
56
+ end
57
+ end
58
+
59
+ def assert_agent_watermark!(agent_id:, agent_revision:, journal_position:)
60
+ Phronomy::Agent::Persistence::Watermark.new(@view).check!(
61
+ agent_id: agent_id.to_s,
62
+ agent_revision: Integer(agent_revision),
63
+ journal_position: Integer(journal_position)
64
+ )
65
+ end
66
+
67
+ private
68
+
69
+ # Cache one wrapper per view without loading unused domain implementations.
70
+ def domain_repository(name)
71
+ @repository_lock.synchronize do
72
+ @domain_repositories.fetch(name) { @domain_repositories[name] = yield }
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module PersistenceComposition
5
+ # One composition catalog, supplied to a neutral storage driver.
6
+ # @api private
7
+ module StorageSchema
8
+ def self.validate!(view)
9
+ resources.each { |resource| view.public_send(resource.kind, resource) }
10
+ end
11
+
12
+ def self.resources
13
+ [Agent::Persistence::StorageSchema::ROOTS,
14
+ Agent::Persistence::StorageSchema::EXECUTIONS,
15
+ Agent::Persistence::StorageSchema::JOURNAL,
16
+ Agent::Persistence::StorageSchema::HANDOFF_STATES,
17
+ TeamStorageSchema::ROOTS,
18
+ TeamStorageSchema::EXECUTIONS,
19
+ WorkflowStorageSchema::STATES,
20
+ ContentStore::StorageSchema::CONTENTS].freeze
21
+ end
22
+ end
23
+ end
24
+ 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 ExecutionRehydrationRequiredError < Phronomy::Error; end
5
7
  end
@@ -36,7 +36,7 @@ module Phronomy
36
36
  reason: reason.to_sym,
37
37
  subject: subject && Phronomy::Recovery.normalize_subject(subject),
38
38
  allowed_outcomes: Array(allowed_outcomes).map(&:to_sym).freeze,
39
- facts: Phronomy::Agent::Immutable.copy(facts || {})
39
+ facts: Phronomy::Values::Immutable.copy(facts || {})
40
40
  )
41
41
  end
42
42
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Select the Agent default at the application composition boundary. Binding
4
+ # does not create Persistence, global configuration, or a Runtime.
5
+ Phronomy::Agent::DefaultPersistence.install_factory(
6
+ -> { Phronomy::Persistence.in_memory }
7
+ )
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Select concrete defaults at the application composition boundary. Each factory
4
+ # runs during Configuration.new, not during binding; no Runtime is started here.
5
+ # Explicitly loaded after Zeitwerk setup and before global configuration access.
6
+ Phronomy::Configuration.install_default_factories(
7
+ tracer: -> { Phronomy::Tracing::NullTracer.new },
8
+ llm_adapter: -> { Phronomy::LLMAdapter::RubyLLM.new }
9
+ )
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Application lifecycle coordination across Runtime and global configuration.
4
+ # Explicitly loaded by the application entry point; not an Engine primitive.
5
+ module Phronomy
6
+ class << self
7
+ def reset_runtime!(timeout: configuration.event_loop_stop_grace_seconds)
8
+ previous_grace = @configuration&.event_loop_stop_grace_seconds
9
+ result = Runtime.reset_default!(timeout: timeout)
10
+
11
+ new_configuration = Configuration.new
12
+ if previous_grace
13
+ new_configuration.event_loop_stop_grace_seconds = previous_grace
14
+ end
15
+ @configuration = new_configuration
16
+ result
17
+ end
18
+ end
19
+ end