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
@@ -1,690 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "monitor"
4
- require "digest"
5
-
6
- module Phronomy
7
- class Persistence
8
- class InMemory < Persistence
9
- class Contents < Phronomy::ContentStore::Base
10
- def initialize(owner)
11
- @owner = owner
12
- end
13
-
14
- def put(bytes, canonicalization_version:)
15
- value = String(bytes).b.freeze
16
- id = content_id_for(value)
17
- @owner.synchronize do
18
- current = @owner.state[:contents][id]
19
- if current && current[:bytes] != value
20
- raise Phronomy::ContentStore::IntegrityError, "digest collision for #{id}"
21
- end
22
- @owner.state[:contents][id] ||= {
23
- bytes: value,
24
- canonicalization_version: Integer(canonicalization_version)
25
- }
26
- end
27
- id
28
- end
29
-
30
- def fetch(content_id)
31
- @owner.synchronize do
32
- record = @owner.state[:contents].fetch(content_id.to_s) do
33
- raise NotFoundError, "content not found: #{content_id}"
34
- end
35
- bytes = record[:bytes]
36
- unless content_id_for(bytes) == content_id.to_s
37
- raise Phronomy::ContentStore::IntegrityError, "content digest mismatch: #{content_id}"
38
- end
39
- bytes.dup
40
- end
41
- end
42
-
43
- def exist?(content_id)
44
- @owner.synchronize { @owner.state[:contents].key?(content_id.to_s) }
45
- end
46
- end
47
-
48
- # Backend-side Agent record repository. DurableRecord is opaque here;
49
- # identity/revision metadata is supplied explicitly by RepositoryFacades.
50
- class Agents
51
- def initialize(owner) = @owner = owner
52
-
53
- def create(agent_id:, agent_revision:, record:)
54
- record = @owner.require_durable_record!(record)
55
- key = agent_id.to_s
56
- revision = Integer(agent_revision)
57
- raise ConflictError, "agent_id must not be empty" if key.empty?
58
- raise ConflictError, "agent_revision must be non-negative" if revision.negative?
59
-
60
- @owner.synchronize do
61
- raise ConflictError, "agent already exists: #{key}" if @owner.state[:agents].key?(key)
62
- @owner.state[:agents][key] = record.copy
63
- @owner.state[:agent_revisions][key] = revision
64
- end
65
- record.copy
66
- end
67
-
68
- def load(agent_id)
69
- @owner.synchronize do
70
- @owner.state[:agents].fetch(agent_id.to_s) do
71
- raise NotFoundError, "agent not found: #{agent_id}"
72
- end.copy
73
- end
74
- end
75
-
76
- def save(agent_id, expected_revision:, next_revision:, record:)
77
- record = @owner.require_durable_record!(record)
78
- key = agent_id.to_s
79
- expected = Integer(expected_revision)
80
- next_value = Integer(next_revision)
81
- @owner.synchronize do
82
- unless @owner.state[:agents].key?(key)
83
- raise NotFoundError, "agent not found: #{agent_id}"
84
- end
85
- actual_revision = @owner.state[:agent_revisions].fetch(key)
86
- unless actual_revision == expected
87
- raise ConflictError,
88
- "agent revision conflict: expected #{expected}, actual #{actual_revision}"
89
- end
90
- unless next_value == expected + 1
91
- raise ConflictError,
92
- "agent save must advance revision exactly once: " \
93
- "expected #{expected + 1}, got #{next_value}"
94
- end
95
- @owner.state[:agents][key] = record.copy
96
- @owner.state[:agent_revisions][key] = next_value
97
- end
98
- record.copy
99
- end
100
-
101
- def delete(agent_id)
102
- @owner.synchronize do
103
- key = agent_id.to_s
104
- @owner.state[:agent_revisions].delete(key)
105
- @owner.state[:agents].delete(key)
106
- end
107
- end
108
- end
109
-
110
- class Journals
111
- def initialize(owner) = @owner = owner
112
-
113
- def append(agent_id, expected_position:, records:, record_ids:)
114
- encoded = Array(records).map { |record| @owner.require_durable_record!(record) }
115
- ids = Array(record_ids).map(&:to_s)
116
- unless encoded.length == ids.length
117
- raise ConflictError,
118
- "Journal records/record_ids length mismatch: #{encoded.length} != #{ids.length}"
119
- end
120
- if ids.any?(&:empty?)
121
- raise ConflictError, "Journal record_id must not be empty"
122
- end
123
-
124
- @owner.synchronize do
125
- key = agent_id.to_s
126
- target = (@owner.state[:journals][key] ||= [])
127
- known_ids = (@owner.state[:journal_record_ids][key] ||= {})
128
- expected = Integer(expected_position)
129
- unless target.length == expected
130
- raise ConflictError,
131
- "journal position conflict: expected #{expected}, actual #{target.length}"
132
- end
133
-
134
- incoming_ids = {}
135
- ids.each do |record_id|
136
- if known_ids[record_id] || incoming_ids[record_id]
137
- raise ConflictError, "duplicate Journal record_id: #{record_id}"
138
- end
139
- incoming_ids[record_id] = true
140
- end
141
-
142
- stored = encoded.map(&:copy)
143
- target.concat(stored)
144
- incoming_ids.each_key { |record_id| known_ids[record_id] = true }
145
- stored.map(&:copy).freeze
146
- end
147
- end
148
-
149
- def read(agent_id, after: nil, limit: nil)
150
- @owner.synchronize do
151
- result = Array(@owner.state[:journals][agent_id.to_s])
152
- result = result.drop(Integer(after)) if after
153
- result = result.first(Integer(limit)) if limit
154
- result.map(&:copy).freeze
155
- end
156
- end
157
-
158
- def head(agent_id)
159
- @owner.synchronize { Array(@owner.state[:journals][agent_id.to_s]).length }
160
- end
161
-
162
- def delete(agent_id)
163
- @owner.synchronize do
164
- key = agent_id.to_s
165
- @owner.state[:journal_record_ids].delete(key)
166
- @owner.state[:journals].delete(key)
167
- end
168
- end
169
- end
170
-
171
- class Executions
172
- def initialize(owner) = @owner = owner
173
-
174
- def create_active(execution_id:, agent_id:, execution_revision:, record:)
175
- record = @owner.require_durable_record!(record)
176
- execution_key = execution_id.to_s
177
- agent_key = agent_id.to_s
178
- revision = Integer(execution_revision)
179
- raise ConflictError, "execution_id must not be empty" if execution_key.empty?
180
- raise ConflictError, "agent_id must not be empty" if agent_key.empty?
181
- raise ConflictError, "execution_revision must be non-negative" if revision.negative?
182
-
183
- @owner.synchronize do
184
- if @owner.state[:executions].key?(execution_key)
185
- raise ConflictError, "execution already exists: #{execution_key}"
186
- end
187
- active = @owner.state[:execution_metadata].values.find do |metadata|
188
- metadata.fetch(:agent_id) == agent_key && metadata.fetch(:active)
189
- end
190
- raise Phronomy::AgentBusyError, "agent is busy: #{agent_key}" if active
191
-
192
- @owner.state[:executions][execution_key] = record.copy
193
- @owner.state[:execution_metadata][execution_key] = {
194
- agent_id: agent_key,
195
- revision: revision,
196
- active: true
197
- }.freeze
198
- end
199
- record.copy
200
- end
201
-
202
- def load(execution_id)
203
- @owner.synchronize do
204
- @owner.state[:executions].fetch(execution_id.to_s) do
205
- raise NotFoundError, "execution not found: #{execution_id}"
206
- end.copy
207
- end
208
- end
209
-
210
- def save(execution_id, expected_revision:, next_revision:, agent_id:, active:, record:)
211
- record = @owner.require_durable_record!(record)
212
- execution_key = execution_id.to_s
213
- agent_key = agent_id.to_s
214
- expected = Integer(expected_revision)
215
- next_value = Integer(next_revision)
216
- unless active.equal?(true) || active.equal?(false)
217
- raise ConflictError, "execution active metadata must be true or false"
218
- end
219
-
220
- @owner.synchronize do
221
- unless @owner.state[:executions].key?(execution_key)
222
- raise NotFoundError, "execution not found: #{execution_id}"
223
- end
224
- current = @owner.state[:execution_metadata].fetch(execution_key)
225
- actual_revision = current.fetch(:revision)
226
- unless actual_revision == expected
227
- raise ConflictError,
228
- "execution revision conflict: expected #{expected}, actual #{actual_revision}"
229
- end
230
- unless current.fetch(:agent_id) == agent_key
231
- raise ConflictError,
232
- "Execution Agent identity mismatch: #{agent_key} != #{current.fetch(:agent_id)}"
233
- end
234
- unless next_value == expected + 1
235
- raise ConflictError,
236
- "execution save must advance revision exactly once: " \
237
- "expected #{expected + 1}, got #{next_value}"
238
- end
239
-
240
- @owner.state[:executions][execution_key] = record.copy
241
- @owner.state[:execution_metadata][execution_key] = {
242
- agent_id: agent_key,
243
- revision: next_value,
244
- active: active
245
- }.freeze
246
- end
247
- record.copy
248
- end
249
-
250
- def list_active(agent_id)
251
- @owner.synchronize do
252
- agent_key = agent_id.to_s
253
- ids = @owner.state[:execution_metadata].filter_map do |execution_id, metadata|
254
- execution_id if metadata.fetch(:agent_id) == agent_key && metadata.fetch(:active)
255
- end
256
- ids.map { |execution_id| @owner.state[:executions].fetch(execution_id).copy }.freeze
257
- end
258
- end
259
-
260
- def list(agent_id, after: nil, limit: 100)
261
- @owner.synchronize do
262
- ids = @owner.state[:execution_metadata].filter_map do |id, metadata|
263
- id if metadata.fetch(:agent_id) == agent_id.to_s && (!after || id > after.to_s)
264
- end.sort.first(limit)
265
- ids.map { |id| @owner.state[:executions].fetch(id).copy }.freeze
266
- end
267
- end
268
-
269
- def delete(execution_id)
270
- @owner.synchronize do
271
- key = execution_id.to_s
272
- @owner.state[:execution_metadata].delete(key)
273
- @owner.state[:executions].delete(key)
274
- end
275
- end
276
-
277
- def delete_for_agent(agent_id)
278
- @owner.synchronize do
279
- agent_key = agent_id.to_s
280
- ids = @owner.state[:execution_metadata].filter_map do |execution_id, metadata|
281
- execution_id if metadata.fetch(:agent_id) == agent_key
282
- end
283
- ids.each do |execution_id|
284
- @owner.state[:execution_metadata].delete(execution_id)
285
- @owner.state[:executions].delete(execution_id)
286
- end
287
- end
288
- end
289
-
290
- def assert_idle!(agent_id)
291
- @owner.synchronize do
292
- active = @owner.state[:execution_metadata].values.find do |metadata|
293
- metadata.fetch(:agent_id) == agent_id.to_s && metadata.fetch(:active)
294
- end
295
- if active
296
- raise Phronomy::AgentBusyError,
297
- "agent has an active or suspended execution: #{agent_id}"
298
- end
299
- end
300
- true
301
- end
302
- end
303
-
304
- class Teams
305
- def initialize(owner) = @owner = owner
306
-
307
- def create(team_id:, team_revision:, record:)
308
- record = @owner.require_durable_record!(record)
309
- key = team_id.to_s
310
- revision = Integer(team_revision)
311
- raise ConflictError, "team_id must not be empty" if key.empty?
312
- raise ConflictError, "team_revision must be non-negative" if revision.negative?
313
-
314
- @owner.synchronize do
315
- raise ConflictError, "agent already exists: #{key}" if @owner.state[:teams].key?(key)
316
- @owner.state[:teams][key] = record.copy
317
- @owner.state[:team_revisions][key] = revision
318
- end
319
- record.copy
320
- end
321
-
322
- def load(team_id)
323
- @owner.synchronize do
324
- @owner.state[:teams].fetch(team_id.to_s) do
325
- raise NotFoundError, "agent not found: #{team_id}"
326
- end.copy
327
- end
328
- end
329
-
330
- def save(team_id, expected_revision:, next_revision:, record:)
331
- record = @owner.require_durable_record!(record)
332
- key = team_id.to_s
333
- expected = Integer(expected_revision)
334
- next_value = Integer(next_revision)
335
- @owner.synchronize do
336
- unless @owner.state[:teams].key?(key)
337
- raise NotFoundError, "agent not found: #{team_id}"
338
- end
339
- actual_revision = @owner.state[:team_revisions].fetch(key)
340
- unless actual_revision == expected
341
- raise ConflictError,
342
- "agent revision conflict: expected #{expected}, actual #{actual_revision}"
343
- end
344
- unless next_value == expected + 1
345
- raise ConflictError,
346
- "agent save must advance revision exactly once: " \
347
- "expected #{expected + 1}, got #{next_value}"
348
- end
349
- @owner.state[:teams][key] = record.copy
350
- @owner.state[:team_revisions][key] = next_value
351
- end
352
- record.copy
353
- end
354
-
355
- def delete(team_id)
356
- @owner.synchronize do
357
- key = team_id.to_s
358
- @owner.state[:team_revisions].delete(key)
359
- @owner.state[:teams].delete(key)
360
- end
361
- end
362
- end
363
-
364
- class TeamExecutions
365
- def initialize(owner) = @owner = owner
366
-
367
- def create_active(team_execution_id:, team_id:, execution_revision:, record:)
368
- record = @owner.require_durable_record!(record)
369
- execution_key = team_execution_id.to_s
370
- agent_key = team_id.to_s
371
- revision = Integer(execution_revision)
372
- raise ConflictError, "team_execution_id must not be empty" if execution_key.empty?
373
- raise ConflictError, "team_id must not be empty" if agent_key.empty?
374
- raise ConflictError, "execution_revision must be non-negative" if revision.negative?
375
-
376
- @owner.synchronize do
377
- if @owner.state[:team_executions].key?(execution_key)
378
- raise ConflictError, "execution already exists: #{execution_key}"
379
- end
380
- active = @owner.state[:team_execution_metadata].values.find do |metadata|
381
- metadata.fetch(:team_id) == agent_key && metadata.fetch(:active)
382
- end
383
- raise Phronomy::AgentBusyError, "agent is busy: #{agent_key}" if active
384
-
385
- @owner.state[:team_executions][execution_key] = record.copy
386
- @owner.state[:team_execution_metadata][execution_key] = {
387
- team_id: agent_key,
388
- revision: revision,
389
- active: true
390
- }.freeze
391
- end
392
- record.copy
393
- end
394
-
395
- def load(team_execution_id)
396
- @owner.synchronize do
397
- @owner.state[:team_executions].fetch(team_execution_id.to_s) do
398
- raise NotFoundError, "execution not found: #{team_execution_id}"
399
- end.copy
400
- end
401
- end
402
-
403
- def save(team_execution_id, expected_revision:, next_revision:, team_id:, active:, record:)
404
- record = @owner.require_durable_record!(record)
405
- execution_key = team_execution_id.to_s
406
- agent_key = team_id.to_s
407
- expected = Integer(expected_revision)
408
- next_value = Integer(next_revision)
409
- unless active.equal?(true) || active.equal?(false)
410
- raise ConflictError, "execution active metadata must be true or false"
411
- end
412
-
413
- @owner.synchronize do
414
- unless @owner.state[:team_executions].key?(execution_key)
415
- raise NotFoundError, "execution not found: #{team_execution_id}"
416
- end
417
- current = @owner.state[:team_execution_metadata].fetch(execution_key)
418
- actual_revision = current.fetch(:revision)
419
- unless actual_revision == expected
420
- raise ConflictError,
421
- "execution revision conflict: expected #{expected}, actual #{actual_revision}"
422
- end
423
- unless current.fetch(:team_id) == agent_key
424
- raise ConflictError,
425
- "Execution Team identity mismatch: #{agent_key} != #{current.fetch(:team_id)}"
426
- end
427
- if active && !current.fetch(:active)
428
- raise ConflictError, "A terminal Team execution cannot become active"
429
- end
430
- unless next_value == expected + 1
431
- raise ConflictError,
432
- "execution save must advance revision exactly once: " \
433
- "expected #{expected + 1}, got #{next_value}"
434
- end
435
-
436
- @owner.state[:team_executions][execution_key] = record.copy
437
- @owner.state[:team_execution_metadata][execution_key] = {
438
- team_id: agent_key,
439
- revision: next_value,
440
- active: active
441
- }.freeze
442
- end
443
- record.copy
444
- end
445
-
446
- def list_active(team_id)
447
- @owner.synchronize do
448
- agent_key = team_id.to_s
449
- ids = @owner.state[:team_execution_metadata].filter_map do |team_execution_id, metadata|
450
- team_execution_id if metadata.fetch(:team_id) == agent_key && metadata.fetch(:active)
451
- end
452
- ids.map { |team_execution_id| @owner.state[:team_executions].fetch(team_execution_id).copy }.freeze
453
- end
454
- end
455
-
456
- def list(team_id, after: nil, limit: 100)
457
- @owner.synchronize do
458
- ids = @owner.state[:team_execution_metadata].filter_map do |id, metadata|
459
- id if metadata.fetch(:team_id) == team_id.to_s && (!after || id > after.to_s)
460
- end.sort.first(limit)
461
- ids.map { |id| @owner.state[:team_executions].fetch(id).copy }.freeze
462
- end
463
- end
464
-
465
- def delete(team_execution_id)
466
- @owner.synchronize do
467
- key = team_execution_id.to_s
468
- @owner.state[:team_execution_metadata].delete(key)
469
- @owner.state[:team_executions].delete(key)
470
- end
471
- end
472
-
473
- def delete_for_team(team_id)
474
- @owner.synchronize do
475
- agent_key = team_id.to_s
476
- ids = @owner.state[:team_execution_metadata].filter_map do |team_execution_id, metadata|
477
- team_execution_id if metadata.fetch(:team_id) == agent_key
478
- end
479
- ids.each do |team_execution_id|
480
- @owner.state[:team_execution_metadata].delete(team_execution_id)
481
- @owner.state[:team_executions].delete(team_execution_id)
482
- end
483
- end
484
- end
485
-
486
- def assert_idle!(team_id)
487
- @owner.synchronize do
488
- active = @owner.state[:team_execution_metadata].values.find do |metadata|
489
- metadata.fetch(:team_id) == team_id.to_s && metadata.fetch(:active)
490
- end
491
- if active
492
- raise Phronomy::AgentBusyError,
493
- "agent has an active or suspended execution: #{team_id}"
494
- end
495
- end
496
- true
497
- end
498
- end
499
-
500
- class HandoffStates
501
- def initialize(owner) = @owner = owner
502
-
503
- def load(main_agent_id)
504
- @owner.synchronize do
505
- record = @owner.state[:handoff_states][main_agent_id.to_s]
506
- record&.copy
507
- end
508
- end
509
-
510
- def save(main_agent_id, expected_revision:, next_revision:, active_agent_id:, record:)
511
- record = @owner.require_durable_record!(record)
512
- raise ArgumentError, "active_agent_id is required" if active_agent_id.to_s.empty?
513
- key = main_agent_id.to_s
514
- expected = expected_revision.nil? ? nil : Integer(expected_revision)
515
- next_value = Integer(next_revision)
516
- @owner.synchronize do
517
- actual_revision = @owner.state[:handoff_revisions][key]
518
- unless actual_revision == expected
519
- raise ConflictError,
520
- "workflow state revision conflict for #{key}: " \
521
- "expected #{expected.inspect}, actual #{actual_revision.inspect}"
522
- end
523
- expected_next = expected.nil? ? 1 : expected + 1
524
- unless next_value == expected_next
525
- raise ConflictError,
526
- "workflow state save must advance revision exactly once: " \
527
- "expected #{expected_next}, got #{next_value}"
528
- end
529
-
530
- @owner.state[:handoff_states][key] = record.copy
531
- @owner.state[:handoff_revisions][key] = next_value
532
- end
533
- record.copy
534
- end
535
-
536
- def delete(main_agent_id, expected_revision:)
537
- @owner.synchronize do
538
- key = main_agent_id.to_s
539
- expected = Integer(expected_revision)
540
- actual_revision = @owner.state[:handoff_revisions][key]
541
- unless actual_revision == expected
542
- raise ConflictError,
543
- "workflow state revision conflict for #{key}: " \
544
- "expected #{expected.inspect}, actual #{actual_revision.inspect}"
545
- end
546
- @owner.state[:handoff_revisions].delete(key)
547
- @owner.state[:handoff_states].delete(key)
548
- end
549
- nil
550
- end
551
- end
552
-
553
- class WorkflowStates
554
- def initialize(owner) = @owner = owner
555
-
556
- def load(workflow_instance_id)
557
- @owner.synchronize do
558
- record = @owner.state[:workflow_states][workflow_instance_id.to_s]
559
- record&.copy
560
- end
561
- end
562
-
563
- def save(workflow_instance_id, expected_revision:, next_revision:, record:)
564
- record = @owner.require_durable_record!(record)
565
- key = workflow_instance_id.to_s
566
- expected = expected_revision.nil? ? nil : Integer(expected_revision)
567
- next_value = Integer(next_revision)
568
- @owner.synchronize do
569
- actual_revision = @owner.state[:workflow_revisions][key]
570
- unless actual_revision == expected
571
- raise ConflictError,
572
- "workflow state revision conflict for #{key}: " \
573
- "expected #{expected.inspect}, actual #{actual_revision.inspect}"
574
- end
575
- expected_next = expected.nil? ? 1 : expected + 1
576
- unless next_value == expected_next
577
- raise ConflictError,
578
- "workflow state save must advance revision exactly once: " \
579
- "expected #{expected_next}, got #{next_value}"
580
- end
581
-
582
- @owner.state[:workflow_states][key] = record.copy
583
- @owner.state[:workflow_revisions][key] = next_value
584
- end
585
- record.copy
586
- end
587
-
588
- def delete(workflow_instance_id, expected_revision:)
589
- @owner.synchronize do
590
- key = workflow_instance_id.to_s
591
- expected = Integer(expected_revision)
592
- actual_revision = @owner.state[:workflow_revisions][key]
593
- unless actual_revision == expected
594
- raise ConflictError,
595
- "workflow state revision conflict for #{key}: " \
596
- "expected #{expected.inspect}, actual #{actual_revision.inspect}"
597
- end
598
- @owner.state[:workflow_revisions].delete(key)
599
- @owner.state[:workflow_states].delete(key)
600
- end
601
- nil
602
- end
603
- end
604
-
605
- attr_reader :state
606
-
607
- def initialize
608
- @monitor = Monitor.new
609
- @state = {
610
- contents: {},
611
- agents: {},
612
- agent_revisions: {},
613
- journals: {},
614
- journal_record_ids: {},
615
- executions: {},
616
- execution_metadata: {},
617
- workflow_states: {},
618
- workflow_revisions: {},
619
- handoff_states: {}, handoff_revisions: {},
620
- teams: {}, team_revisions: {}, team_executions: {}, team_execution_metadata: {}
621
- }
622
- @contents_backend = Contents.new(self)
623
- @agents_backend = Agents.new(self)
624
- @journals_backend = Journals.new(self)
625
- @executions_backend = Executions.new(self)
626
- @workflow_states_backend = WorkflowStates.new(self)
627
- super(
628
- contents: @contents_backend,
629
- agents: @agents_backend,
630
- journals: @journals_backend,
631
- executions: @executions_backend,
632
- workflow_states: @workflow_states_backend,
633
- handoff_states: HandoffStates.new(self),
634
- teams: Teams.new(self), team_executions: TeamExecutions.new(self)
635
- )
636
- end
637
-
638
- def capabilities
639
- {atomic_all: true, atomic_admission: true, optimistic_revision: true}.freeze
640
- end
641
-
642
- def assert_agent_watermark!(agent_id:, agent_revision:, journal_position:)
643
- synchronize do
644
- key = agent_id.to_s
645
- unless @state[:agents].key?(key)
646
- raise NotFoundError, "Agent not found: #{agent_id}"
647
- end
648
-
649
- actual_revision = @state[:agent_revisions].fetch(key)
650
- if actual_revision != agent_revision
651
- raise ConflictError,
652
- "agent revision conflict: expected #{agent_revision}, actual #{actual_revision}"
653
- end
654
-
655
- actual_position = Array(@state[:journals][key]).length
656
- if actual_position != journal_position
657
- raise ConflictError,
658
- "journal position conflict: expected #{journal_position}, actual #{actual_position}"
659
- end
660
-
661
- true
662
- end
663
- end
664
-
665
- def transaction
666
- synchronize do
667
- state_snapshot = Marshal.load(Marshal.dump(@state))
668
- begin
669
- yield self
670
- rescue
671
- @state.replace(state_snapshot)
672
- raise
673
- end
674
- end
675
- end
676
-
677
- def synchronize(&block)
678
- @monitor.synchronize(&block)
679
- end
680
-
681
- # @api private
682
- def require_durable_record!(record)
683
- return record if record.is_a?(Phronomy::Persistence::DurableRecord)
684
-
685
- raise Phronomy::Persistence::SerializationError,
686
- "backend repository expected Persistence::DurableRecord, got #{record.class}"
687
- end
688
- end
689
- end
690
- end