phronomy 0.26.0 → 0.28.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 (557) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +92 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +13 -3
  6. data/VERIFY.sh +27 -27
  7. data/benchmark/bench_agent_invoke.rb +26 -47
  8. data/benchmark/bench_context_assembler.rb +17 -15
  9. data/benchmark/bench_regression.rb +1 -1
  10. data/benchmark/bench_tool_schema.rb +1 -1
  11. data/docs/architecture/agent-chat-and-state-ownership.md +153 -0
  12. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  13. data/docs/architecture/agent-context.md +4 -2
  14. data/docs/architecture/agent-transition-ownership.md +13 -0
  15. data/docs/architecture/backend-groups.md +152 -0
  16. data/docs/architecture/before-llm-input.md +9 -3
  17. data/docs/architecture/context-management.md +31 -1
  18. data/docs/architecture/context-preparation-steps.md +90 -0
  19. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  20. data/docs/architecture/execution-abstraction-review.md +88 -0
  21. data/docs/architecture/execution-boundaries.md +28 -0
  22. data/docs/architecture/execution-metadata-and-values.md +86 -0
  23. data/docs/architecture/generator-verifier-ownership.md +111 -0
  24. data/docs/architecture/multi-agent-handoff.md +11 -4
  25. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  26. data/docs/architecture/persistence.md +45 -4
  27. data/docs/architecture/r8-unit1.md +104 -0
  28. data/docs/architecture/r8-unit10.md +72 -0
  29. data/docs/architecture/r8-unit11.md +71 -0
  30. data/docs/architecture/r8-unit12.md +67 -0
  31. data/docs/architecture/r8-unit13.md +61 -0
  32. data/docs/architecture/r8-unit14.md +72 -0
  33. data/docs/architecture/r8-unit15.md +65 -0
  34. data/docs/architecture/r8-unit16.md +54 -0
  35. data/docs/architecture/r8-unit17.md +73 -0
  36. data/docs/architecture/r8-unit18.md +92 -0
  37. data/docs/architecture/r8-unit19.md +61 -0
  38. data/docs/architecture/r8-unit2.md +55 -0
  39. data/docs/architecture/r8-unit20.md +51 -0
  40. data/docs/architecture/r8-unit21.md +79 -0
  41. data/docs/architecture/r8-unit22.md +74 -0
  42. data/docs/architecture/r8-unit23.md +81 -0
  43. data/docs/architecture/r8-unit3.md +130 -0
  44. data/docs/architecture/r8-unit4.md +79 -0
  45. data/docs/architecture/r8-unit5.md +115 -0
  46. data/docs/architecture/r8-unit6.md +71 -0
  47. data/docs/architecture/r8-unit7.md +70 -0
  48. data/docs/architecture/r8-unit8.md +78 -0
  49. data/docs/architecture/r8-unit9.md +73 -0
  50. data/docs/architecture/refactoring-closure.md +343 -0
  51. data/docs/architecture/remaining-refactoring-plan.md +390 -0
  52. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  53. data/docs/architecture/storage-execution-boundaries.md +54 -0
  54. data/docs/architecture/tool-schema-recording-gap.md +56 -0
  55. data/docs/architecture/tracing.md +2 -2
  56. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  57. data/docs/architecture.md +222 -0
  58. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  59. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  60. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  61. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  62. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  63. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  64. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +7 -0
  65. data/docs/decisions/031-durable-multi-agent-coordination.md +7 -0
  66. data/docs/decisions/032-storage-backend-composition.md +85 -0
  67. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  68. data/docs/decisions/034-handoff-runner-coordination-ownership.md +78 -0
  69. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  70. data/docs/decisions/036-context-contract-ownership.md +86 -0
  71. data/docs/decisions/037-common-definition-ownership.md +62 -0
  72. data/docs/decisions/038-responsibility-based-source-layout.md +145 -0
  73. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +82 -0
  74. data/docs/decisions/040-configuration-default-composition.md +105 -0
  75. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  76. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  77. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  78. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  79. data/docs/decisions/045-worker-input-restriction-ownership.md +104 -0
  80. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  81. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  82. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  83. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  84. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  85. data/docs/decisions/051-execution-outcome-worker-ownership.md +153 -0
  86. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  87. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  88. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  89. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  90. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  91. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  92. data/docs/decisions/058-neutral-storage-primitives.md +83 -0
  93. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  94. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  95. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  96. data/docs/decisions/062-content-store-service.md +52 -0
  97. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  98. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  99. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  100. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  101. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  102. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  103. data/docs/decisions/README.md +52 -6
  104. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  105. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  106. data/docs/execution-boundaries-preview.md +45 -0
  107. data/docs/features.md +16 -5
  108. data/docs/getting-started.md +11 -8
  109. data/docs/migrations/0.15.md +5 -0
  110. data/docs/migrations/0.22.md +2 -2
  111. data/docs/migrations/durable-semantic-coordination-v2.md +8 -0
  112. data/docs/migrations/handoff-runner-multi-agent.md +37 -0
  113. data/docs/migrations/neutral-storage-spi.md +43 -0
  114. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  115. data/docs/migrations/persistence-failure-contracts.md +67 -0
  116. data/docs/migrations/r8-unit10.md +36 -0
  117. data/docs/migrations/r8-unit11.md +33 -0
  118. data/docs/migrations/r8-unit12.md +31 -0
  119. data/docs/migrations/r8-unit13.md +36 -0
  120. data/docs/migrations/r8-unit14.md +31 -0
  121. data/docs/migrations/r8-unit15.md +31 -0
  122. data/docs/migrations/r8-unit16.md +26 -0
  123. data/docs/migrations/r8-unit17.md +28 -0
  124. data/docs/migrations/r8-unit18.md +29 -0
  125. data/docs/migrations/r8-unit19.md +28 -0
  126. data/docs/migrations/r8-unit20.md +24 -0
  127. data/docs/migrations/r8-unit21.md +28 -0
  128. data/docs/migrations/r8-unit22.md +29 -0
  129. data/docs/migrations/r8-unit23.md +20 -0
  130. data/docs/migrations/r8-unit4.md +67 -0
  131. data/docs/migrations/r8-unit5.md +75 -0
  132. data/docs/migrations/r8-unit6.md +109 -0
  133. data/docs/migrations/r8-unit7.md +31 -0
  134. data/docs/migrations/r8-unit8.md +33 -0
  135. data/docs/migrations/r8-unit9.md +28 -0
  136. data/docs/migrations/shared-state-multi-agent.md +44 -0
  137. data/docs/migrations/storage-async-client.md +103 -0
  138. data/docs/migrations/storage-backend-composition.md +117 -0
  139. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  140. data/docs/migrations/vector-async-clients.md +110 -0
  141. data/docs/persistence-backends.md +190 -215
  142. data/docs/runtime-and-concurrency.md +142 -32
  143. data/lib/phronomy/agent/admission.rb +78 -0
  144. data/lib/phronomy/{agent_already_exists_error.rb → agent/agent_already_exists_error.rb} +2 -0
  145. data/lib/phronomy/{agent_busy_error.rb → agent/agent_busy_error.rb} +2 -0
  146. data/lib/phronomy/{agent_purged_error.rb → agent/agent_purged_error.rb} +2 -0
  147. data/lib/phronomy/agent/api/agent.rb +17 -0
  148. data/lib/phronomy/{execution_rehydration_required_error.rb → agent/api/execution_rehydration_required_error.rb} +2 -0
  149. data/lib/phronomy/agent/async_event_api.rb +38 -33
  150. data/lib/phronomy/agent/base.rb +106 -294
  151. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  152. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  153. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  154. data/lib/phronomy/agent/context_importer.rb +2 -2
  155. data/lib/phronomy/agent/{context_assembler.rb → context_preparation.rb} +157 -288
  156. data/lib/phronomy/agent/control_request.rb +21 -0
  157. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  158. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +32 -63
  159. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  160. data/lib/phronomy/agent/execution/dispatch_preparation.rb +195 -0
  161. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +43 -23
  162. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +8 -5
  163. data/lib/phronomy/agent/execution/execution_coordinator.rb +1744 -0
  164. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  165. data/lib/phronomy/agent/execution/execution_metadata.rb +54 -0
  166. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +288 -0
  167. data/lib/phronomy/agent/execution/execution_registry.rb +463 -0
  168. data/lib/phronomy/agent/execution/execution_session_runner.rb +113 -0
  169. data/lib/phronomy/agent/execution/initial_preparation.rb +323 -0
  170. data/lib/phronomy/agent/execution/invocation_actions.rb +368 -0
  171. data/lib/phronomy/agent/execution/invocation_transitions.rb +124 -0
  172. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  173. data/lib/phronomy/agent/execution_change.rb +161 -0
  174. data/lib/phronomy/agent/execution_environment.rb +36 -0
  175. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  176. data/lib/phronomy/agent/execution_observation.rb +35 -0
  177. data/lib/phronomy/agent/filterable.rb +182 -0
  178. data/lib/phronomy/agent/{handoff_context.rb → handoff/transfer_context.rb} +6 -6
  179. data/lib/phronomy/agent/{handoff_projection.rb → handoff/transfer_projection.rb} +14 -33
  180. data/lib/phronomy/agent/handoff_error.rb +7 -0
  181. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  182. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  183. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  184. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  185. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  186. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  187. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -15
  188. data/lib/phronomy/agent/persistence/agent_repository.rb +69 -0
  189. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  190. data/lib/phronomy/agent/persistence/codec.rb +338 -0
  191. data/lib/phronomy/agent/persistence/execution_repository.rb +124 -0
  192. data/lib/phronomy/agent/persistence/journal_repository.rb +63 -0
  193. data/lib/phronomy/agent/persistence/records.rb +33 -0
  194. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  195. data/lib/phronomy/agent/persistence/storage_schema.rb +29 -0
  196. data/lib/phronomy/agent/persistence/watermark.rb +29 -0
  197. data/lib/phronomy/agent/recovery/invocation_restorer.rb +124 -0
  198. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +28 -51
  199. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +50 -55
  200. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +38 -43
  201. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +26 -23
  202. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  203. data/lib/phronomy/agent/recovery/recovery_support.rb +224 -0
  204. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  205. data/lib/phronomy/agent/retention.rb +17 -0
  206. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  207. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  208. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  209. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  210. data/lib/phronomy/agent/{ruby_llm_materializer.rb → runtime_input.rb} +19 -39
  211. data/lib/phronomy/agent/saved_context_reader.rb +55 -0
  212. data/lib/phronomy/agent/settings.rb +47 -0
  213. data/lib/phronomy/agent/state_writer.rb +166 -0
  214. data/lib/phronomy/agent/store.rb +253 -0
  215. data/lib/phronomy/{stream_callback_error.rb → agent/stream_callback_error.rb} +2 -0
  216. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  217. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  218. data/lib/phronomy/agent/tool_execution/tool_binding.rb +30 -0
  219. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +114 -248
  220. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  221. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  222. data/lib/phronomy/common/configuration_error.rb +7 -0
  223. data/lib/phronomy/common/error.rb +5 -0
  224. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  225. data/lib/phronomy/common/values/serializable.rb +32 -0
  226. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  227. data/lib/phronomy/content_store/storage_schema.rb +12 -0
  228. data/lib/phronomy/content_store/stored_contents.rb +44 -0
  229. data/lib/phronomy/context/assembly.rb +202 -0
  230. data/lib/phronomy/{context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +5 -1
  231. data/lib/phronomy/context/candidate.rb +51 -0
  232. data/lib/phronomy/context/constraint.rb +47 -0
  233. data/lib/phronomy/{agent → context}/context_plan.rb +2 -2
  234. data/lib/phronomy/{agent → context}/context_plan_validator.rb +3 -3
  235. data/lib/phronomy/{agent → context}/context_policy.rb +1 -1
  236. data/lib/phronomy/{agent → context}/context_policy_input.rb +7 -7
  237. data/lib/phronomy/{agent → context}/context_policy_input_builder.rb +1 -1
  238. data/lib/phronomy/context/default_policy.rb +140 -0
  239. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  240. data/lib/phronomy/{agent → context}/llm_input_build_context.rb +2 -2
  241. data/lib/phronomy/{agent → context}/llm_input_manifest.rb +21 -21
  242. data/lib/phronomy/{agent → context}/llm_input_patch.rb +3 -3
  243. data/lib/phronomy/context/manifest_error.rb +10 -0
  244. data/lib/phronomy/context/prompt_template.rb +98 -0
  245. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  246. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  247. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  248. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  249. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  250. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  251. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  252. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  253. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  254. data/lib/phronomy/embeddings/base.rb +43 -0
  255. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  256. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  257. data/lib/phronomy/engine/concurrency/offload_pool.rb +39 -20
  258. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  259. data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +1 -1
  260. data/lib/phronomy/engine/event_loop.rb +164 -630
  261. data/lib/phronomy/engine/execution_binding.rb +23 -0
  262. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  263. data/lib/phronomy/engine/fsm_protocol.rb +9 -0
  264. data/lib/phronomy/engine/fsm_session.rb +37 -30
  265. data/lib/phronomy/engine/metrics.rb +28 -0
  266. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  267. data/lib/phronomy/engine/runtime.rb +104 -95
  268. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  269. data/lib/phronomy/execution/async_operation.rb +41 -0
  270. data/lib/phronomy/execution/backpressure_error.rb +7 -0
  271. data/lib/phronomy/{blocking.rb → execution/blocking.rb} +2 -2
  272. data/lib/phronomy/execution/cancellation_error.rb +7 -0
  273. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +5 -1
  274. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  275. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  276. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  277. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  278. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  279. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  280. data/lib/phronomy/execution/concurrency/worker_input_restricted.rb +14 -0
  281. data/lib/phronomy/execution/event_loop_reentrancy_error.rb +8 -0
  282. data/lib/phronomy/{execution.rb → execution/execution.rb} +57 -30
  283. data/lib/phronomy/{execution_cancellation_error.rb → execution/execution_cancellation_error.rb} +2 -0
  284. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  285. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  286. data/lib/phronomy/{execution_timeout_error.rb → execution/execution_timeout_error.rb} +2 -0
  287. data/lib/phronomy/{invalid_async_entry_action_error.rb → execution/invalid_async_entry_action_error.rb} +2 -0
  288. data/lib/phronomy/{invalid_async_transition_action_error.rb → execution/invalid_async_transition_action_error.rb} +2 -0
  289. data/lib/phronomy/{invalid_async_workflow_action_error.rb → execution/invalid_async_workflow_action_error.rb} +2 -0
  290. data/lib/phronomy/{invocation_context.rb → execution/invocation_context.rb} +0 -15
  291. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  292. data/lib/phronomy/execution/pool_shutdown_error.rb +7 -0
  293. data/lib/phronomy/execution/recursion_limit_error.rb +7 -0
  294. data/lib/phronomy/execution/runnable.rb +24 -0
  295. data/lib/phronomy/execution/runtime_shutdown_error.rb +7 -0
  296. data/lib/phronomy/execution/runtime_shutdown_reentrancy_error.rb +7 -0
  297. data/lib/phronomy/execution/scheduler_reentrancy_error.rb +9 -0
  298. data/lib/phronomy/{engine → execution}/task_result.rb +7 -3
  299. data/lib/phronomy/execution/timeout_error.rb +7 -0
  300. data/lib/phronomy/execution/wait_policy.rb +24 -0
  301. data/lib/phronomy/filter/filter_block_error.rb +14 -0
  302. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  303. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  304. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  305. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  306. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  307. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  308. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  309. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  310. data/lib/phronomy/llm_adapter/authentication_error.rb +9 -0
  311. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  312. data/lib/phronomy/llm_adapter/base.rb +49 -68
  313. data/lib/phronomy/llm_adapter/context_length_error.rb +9 -0
  314. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  315. data/lib/phronomy/llm_adapter/message.rb +62 -0
  316. data/lib/phronomy/llm_adapter/rate_limit_error.rb +9 -0
  317. data/lib/phronomy/llm_adapter/request.rb +79 -0
  318. data/lib/phronomy/llm_adapter/response.rb +36 -0
  319. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  320. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  321. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  322. data/lib/phronomy/llm_adapter.rb +3 -16
  323. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  324. data/lib/phronomy/multi_agent/admission_registry.rb +15 -2
  325. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  326. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  327. data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
  328. data/lib/phronomy/{agent → multi_agent}/handoff_capability_factory.rb +21 -2
  329. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  330. data/lib/phronomy/{agent → multi_agent}/handoff_policy.rb +1 -1
  331. data/lib/phronomy/multi_agent/handoff_runner.rb +156 -0
  332. data/lib/phronomy/{agent → multi_agent}/handoff_state.rb +2 -2
  333. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  334. data/lib/phronomy/multi_agent/persistence/codec.rb +75 -0
  335. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  336. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  337. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  338. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +124 -0
  339. data/lib/phronomy/multi_agent/persistence/team_repository.rb +69 -0
  340. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  341. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  342. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  343. data/lib/phronomy/multi_agent/store.rb +156 -0
  344. data/lib/phronomy/multi_agent/team_coordinator.rb +87 -75
  345. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  346. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +5 -5
  347. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  348. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  349. data/lib/phronomy/persistence/content_repository.rb +26 -0
  350. data/lib/phronomy/persistence/errors.rb +16 -0
  351. data/lib/phronomy/persistence/migration/initial_format_migration.rb +21 -21
  352. data/lib/phronomy/persistence/persistence.rb +23 -0
  353. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  354. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  355. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  356. data/lib/phronomy/persistence/transaction.rb +86 -0
  357. data/lib/phronomy/persistence_composition/storage_schema.rb +29 -0
  358. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  359. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  360. data/lib/phronomy/runtime_composition/agent_defaults.rb +11 -0
  361. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  362. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  363. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  364. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  365. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  366. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  367. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  368. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  369. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  370. data/lib/phronomy/storage/async/async_client.rb +60 -0
  371. data/lib/phronomy/storage/backend.rb +107 -0
  372. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  373. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  374. data/lib/phronomy/storage/blobs.rb +31 -0
  375. data/lib/phronomy/storage/condition.rb +25 -0
  376. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  377. data/lib/phronomy/storage/conflict_error.rb +9 -0
  378. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  379. data/lib/phronomy/storage/entry.rb +33 -0
  380. data/lib/phronomy/storage/guard_ref.rb +13 -0
  381. data/lib/phronomy/storage/not_found_error.rb +9 -0
  382. data/lib/phronomy/storage/record_codec.rb +177 -0
  383. data/lib/phronomy/storage/records.rb +61 -0
  384. data/lib/phronomy/storage/resource.rb +126 -0
  385. data/lib/phronomy/storage/scope.rb +25 -0
  386. data/lib/phronomy/storage/serialization_error.rb +9 -0
  387. data/lib/phronomy/storage/streams.rb +44 -0
  388. data/lib/phronomy/storage/transaction_error.rb +10 -0
  389. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  390. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  391. data/lib/phronomy/storage/validation.rb +53 -0
  392. data/lib/phronomy/storage/view.rb +131 -0
  393. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  394. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  395. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  396. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  397. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +54 -48
  398. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +1 -1
  399. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +1 -1
  400. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  401. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +52 -46
  402. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  403. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +124 -0
  404. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  405. data/lib/phronomy/tool/authorization.rb +77 -0
  406. data/lib/phronomy/tool/base.rb +549 -9
  407. data/lib/phronomy/tool/call_request.rb +40 -0
  408. data/lib/phronomy/{agent/tool_definition_set.rb → tool/definition_set.rb} +16 -12
  409. data/lib/phronomy/tool/operation.rb +88 -0
  410. data/lib/phronomy/tool/schema.rb +214 -0
  411. data/lib/phronomy/tool/settings.rb +31 -0
  412. data/lib/phronomy/tool/tool_error.rb +7 -0
  413. data/lib/phronomy/{agent → tool}/tool_executor.rb +10 -7
  414. data/lib/phronomy/tools/agent.rb +22 -90
  415. data/lib/phronomy/tools/mcp.rb +21 -14
  416. data/lib/phronomy/tools/vector_search.rb +4 -4
  417. data/lib/phronomy/tracing/automatic.rb +11 -6
  418. data/lib/phronomy/tracing/base.rb +1 -1
  419. data/lib/phronomy/tracing/observation.rb +37 -0
  420. data/lib/phronomy/tracing/settings.rb +36 -0
  421. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  422. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  423. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  424. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  425. data/lib/phronomy/vector_store/base.rb +118 -49
  426. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  427. data/lib/phronomy/vector_store.rb +2 -17
  428. data/lib/phronomy/version.rb +1 -1
  429. data/lib/phronomy/workflow/completion.rb +11 -0
  430. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
  431. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  432. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +5 -1
  433. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  434. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  435. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +186 -0
  436. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +145 -115
  437. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  438. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  439. data/lib/phronomy/workflow/persistence/state_repository.rb +63 -0
  440. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  441. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  442. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  443. data/lib/phronomy/workflow/runtime_binding/workflow_terminal_policy.rb +40 -0
  444. data/lib/phronomy.rb +97 -98
  445. data/scripts/api_snapshot.rb +36 -16
  446. data/scripts/storage_spi_snapshot.rb +36 -0
  447. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  448. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  449. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  450. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  451. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  452. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  453. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  454. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  455. data/sig/phronomy/agent/admission.rbs +19 -0
  456. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  457. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  458. data/sig/phronomy/agent/store.rbs +73 -0
  459. data/sig/phronomy/agent.rbs +14 -139
  460. data/sig/phronomy/common.rbs +7 -0
  461. data/sig/phronomy/content_store/contracts.rbs +13 -0
  462. data/sig/phronomy/context.rbs +152 -0
  463. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  464. data/sig/phronomy/embeddings/base.rbs +17 -0
  465. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  466. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  467. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  468. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  469. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  470. data/sig/phronomy/engine/observability.rbs +13 -0
  471. data/sig/phronomy/execution/async_operation.rbs +7 -0
  472. data/sig/phronomy/execution/blocking.rbs +5 -0
  473. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  474. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  475. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  476. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  477. data/sig/phronomy/execution/errors.rbs +7 -0
  478. data/sig/phronomy/execution/execution.rbs +41 -0
  479. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  480. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  481. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  482. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  483. data/sig/phronomy/execution/task_result.rbs +29 -0
  484. data/sig/phronomy/extensions.rbs +0 -21
  485. data/sig/phronomy/handoff.rbs +12 -4
  486. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  487. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  488. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  489. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  490. data/sig/phronomy/llm_context_window.rbs +10 -0
  491. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  492. data/sig/phronomy/multi_agent/store.rbs +54 -0
  493. data/sig/phronomy/multi_agent.rbs +24 -3
  494. data/sig/phronomy/persistence/errors.rbs +18 -0
  495. data/sig/phronomy/persistence/migration.rbs +13 -0
  496. data/sig/phronomy/persistence/transaction.rbs +19 -0
  497. data/sig/phronomy/persistence.rbs +3 -185
  498. data/sig/phronomy/persistence_composition.rbs +19 -0
  499. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  500. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  501. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  502. data/sig/phronomy/storage/contracts.rbs +160 -0
  503. data/sig/phronomy/tool.rbs +58 -5
  504. data/sig/phronomy/tracing/observation.rbs +7 -0
  505. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  506. data/sig/phronomy/vector_store/base.rbs +23 -0
  507. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  508. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  509. data/sig/phronomy/workflow/store.rbs +8 -0
  510. data/sig/phronomy/workflow.rbs +1 -1
  511. data/sig/phronomy.rbs +0 -49
  512. metadata +462 -130
  513. data/lib/phronomy/agent/agent_invocation_session_builder.rb +0 -584
  514. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  515. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  516. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  517. data/lib/phronomy/agent/context/capability/base.rb +0 -507
  518. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  519. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  520. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  521. data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
  522. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  523. data/lib/phronomy/agent/handoff_request.rb +0 -45
  524. data/lib/phronomy/agent/handoff_runner.rb +0 -178
  525. data/lib/phronomy/agent/phase_machine_builder.rb +0 -152
  526. data/lib/phronomy/agent/provider_call_outcome.rb +0 -106
  527. data/lib/phronomy/agent/recovery_support.rb +0 -504
  528. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  529. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  530. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  531. data/lib/phronomy/agent/tool_call_intercepted.rb +0 -29
  532. data/lib/phronomy/agent/tool_invocation_session_builder.rb +0 -276
  533. data/lib/phronomy/configuration.rb +0 -59
  534. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  535. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  536. data/lib/phronomy/generator_verifier.rb +0 -369
  537. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  538. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  539. data/lib/phronomy/metrics.rb +0 -26
  540. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  541. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  542. data/lib/phronomy/persistence/in_memory.rb +0 -690
  543. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  544. data/lib/phronomy/persistence.rb +0 -276
  545. data/lib/phronomy/recovery.rb +0 -186
  546. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  547. data/lib/phronomy/runnable.rb +0 -54
  548. data/lib/phronomy/token_usage.rb +0 -85
  549. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  550. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  551. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  552. data/lib/phronomy/workflow_recovery.rb +0 -123
  553. data/sig/phronomy/llm_adapter.rbs +0 -11
  554. data/sig/phronomy/runtime.rbs +0 -76
  555. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  556. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  557. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Domain-owned Team metadata, independent of coordination runtime loading.
5
+ # @api private
6
+ module MultiAgent
7
+ module Persistence
8
+ module StorageSchema
9
+ HANDOFF_STATES = Phronomy::Storage::Resource.new(id: "handoff.states", kind: :records,
10
+ attributes: {active_agent_id: :string})
11
+ ROOTS = Phronomy::Storage::Resource.new(id: "team.roots", kind: :records,
12
+ guard: {resource: "team.roots", via: :key})
13
+ EXECUTIONS = Phronomy::Storage::Resource.new(id: "team.executions", kind: :records,
14
+ attributes: {owner: :string, active: :boolean}, immutable_attributes: [:owner],
15
+ indexes: {owner: [:owner], owner_active: [:owner, :active]},
16
+ unique: [{name: :one_active_owner, fields: [:owner], where: {active: true}}],
17
+ guard: {resource: ROOTS.id, via: :owner})
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ module Persistence
6
+ # Owns execution activity, ownership, admission and durable record encoding.
7
+ # @api private
8
+ class TeamExecutionRepository
9
+ def initialize(view) = @view = view
10
+
11
+ def create_active(execution)
12
+ Phronomy::Persistence::StorageBoundary.call do
13
+ raise Phronomy::Storage::SerializationError, "create_active requires an active TeamExecution" unless execution.active?
14
+ record = Codec.encode_team_execution(execution)
15
+ admission do
16
+ @view.atomic do |bound|
17
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).insert(key: execution.team_execution_id.to_s,
18
+ revision: Integer(execution.execution_revision), attributes: attributes(execution), record: record)
19
+ decode(entry, execution.team_execution_id, owner: execution.team_id,
20
+ revision: execution.execution_revision, active: true)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def load(team_execution_id)
27
+ Phronomy::Persistence::StorageBoundary.call do
28
+ value = @view.atomic do |bound|
29
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).read(team_execution_id.to_s)
30
+ entry && decode(entry, team_execution_id)
31
+ end
32
+ value || raise(Phronomy::Storage::NotFoundError, "TeamExecution not found: #{team_execution_id}")
33
+ end
34
+ end
35
+
36
+ def save(team_execution_id, expected_revision:, execution:)
37
+ Phronomy::Persistence::StorageBoundary.call do
38
+ expected = Integer(expected_revision)
39
+ revision = Integer(execution.execution_revision)
40
+ raise Phronomy::Storage::ConflictError, "execution save must advance revision exactly once" unless revision == expected + 1
41
+ unless execution.team_execution_id.to_s == team_execution_id.to_s
42
+ raise Phronomy::Storage::SerializationError, "Execution identity mismatch"
43
+ end
44
+ record = Codec.encode_team_execution(execution)
45
+ admission do
46
+ @view.atomic do |bound|
47
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).replace(key: team_execution_id.to_s,
48
+ expected_revision: expected, next_revision: revision, attributes: attributes(execution),
49
+ expected_attributes: execution.active? ? {active: true} : {}, record: record)
50
+ decode(entry, team_execution_id, owner: execution.team_id, revision: revision, active: execution.active?)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ def list_active(team_id)
57
+ Phronomy::Persistence::StorageBoundary.call do
58
+ @view.atomic do |bound|
59
+ bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).scan(index: :owner_active,
60
+ equals: {owner: team_id.to_s, active: true}).map { |entry| decode(entry, entry.key, owner: team_id, active: true) }.freeze
61
+ end
62
+ end
63
+ end
64
+
65
+ def list(team_id, after: nil, limit: 100)
66
+ Phronomy::Persistence::StorageBoundary.call do
67
+ raise ArgumentError, "limit must be a positive Integer" unless limit.is_a?(Integer) && limit.positive?
68
+ @view.atomic do |bound|
69
+ bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).scan(index: :owner, equals: {owner: team_id.to_s},
70
+ after: after&.to_s, limit: limit).map { |entry| decode(entry, entry.key, owner: team_id) }.freeze
71
+ end
72
+ end
73
+ end
74
+
75
+ def delete(team_execution_id)
76
+ Phronomy::Persistence::StorageBoundary.call do
77
+ @view.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).delete(key: team_execution_id.to_s)
78
+ end
79
+ end
80
+
81
+ def delete_for_team(team_id)
82
+ Phronomy::Persistence::StorageBoundary.call do
83
+ @view.records(Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS).delete_matching(index: :owner, equals: {owner: team_id.to_s})
84
+ end
85
+ end
86
+
87
+ def assert_idle!(team_id)
88
+ Phronomy::Persistence::StorageBoundary.call do
89
+ condition = Phronomy::Storage::Condition::NoRows.new(resource: Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS,
90
+ index: :owner_active, equals: {owner: team_id.to_s, active: true})
91
+ @view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS, key: team_id.to_s)],
92
+ conditions: [condition])
93
+ rescue Phronomy::Storage::ConditionFailedError => error
94
+ raise unless error.condition.equal?(condition)
95
+ raise Phronomy::AgentBusyError, error.message
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ def attributes(execution) = {owner: execution.team_id.to_s, active: execution.active?}
102
+
103
+ def admission
104
+ yield
105
+ rescue Phronomy::Storage::UniqueConstraintError => error
106
+ raise unless error.resource == Phronomy::MultiAgent::Persistence::StorageSchema::EXECUTIONS.id && error.constraint == :one_active_owner
107
+ raise Phronomy::AgentBusyError, error.message
108
+ end
109
+
110
+ def decode(entry, team_execution_id, owner: nil, revision: nil, active: nil)
111
+ execution = Codec.decode_team_execution(entry.record)
112
+ valid = entry.key == team_execution_id.to_s && execution.team_execution_id == entry.key &&
113
+ execution.execution_revision == entry.revision && attributes(execution) == entry.attributes &&
114
+ (!owner || execution.team_id == owner.to_s) && (!revision || execution.execution_revision == revision) &&
115
+ (active.nil? || execution.active? == active)
116
+ unless valid
117
+ raise Phronomy::Storage::SerializationError, "backend returned another Team Execution identity/metadata"
118
+ end
119
+ execution
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ module Persistence
6
+ # Owns Team root encoding and validates returned storage metadata.
7
+ # @api private
8
+ class TeamRepository
9
+ def initialize(view) = @view = view
10
+
11
+ def create(root)
12
+ Phronomy::Persistence::StorageBoundary.call do
13
+ record = Codec.encode_team_root(root)
14
+ @view.atomic do |bound|
15
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).insert(key: root.team_id.to_s,
16
+ revision: Integer(root.team_revision), attributes: {}, record: record)
17
+ decode(entry, root.team_id, revision: root.team_revision)
18
+ end
19
+ end
20
+ end
21
+
22
+ def load(team_id)
23
+ Phronomy::Persistence::StorageBoundary.call do
24
+ value = @view.atomic do |bound|
25
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).read(team_id.to_s)
26
+ entry && decode(entry, team_id)
27
+ end
28
+ value || raise(Phronomy::Storage::NotFoundError, "Team not found: #{team_id}")
29
+ end
30
+ end
31
+
32
+ def save(team_id, expected_revision:, root:)
33
+ Phronomy::Persistence::StorageBoundary.call do
34
+ expected = Integer(expected_revision)
35
+ revision = Integer(root.team_revision)
36
+ raise Phronomy::Storage::ConflictError, "root save must advance revision exactly once" unless revision == expected + 1
37
+ unless root.team_id.to_s == team_id.to_s
38
+ raise Phronomy::Storage::SerializationError, "Team root identity mismatch"
39
+ end
40
+ record = Codec.encode_team_root(root)
41
+ @view.atomic do |bound|
42
+ entry = bound.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).replace(key: team_id.to_s,
43
+ expected_revision: expected, next_revision: revision, attributes: {}, record: record)
44
+ decode(entry, team_id, revision: revision)
45
+ end
46
+ end
47
+ end
48
+
49
+ def delete(team_id)
50
+ Phronomy::Persistence::StorageBoundary.call do
51
+ @view.records(Phronomy::MultiAgent::Persistence::StorageSchema::ROOTS).delete(key: team_id.to_s)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def decode(entry, team_id, revision: nil)
58
+ root = Codec.decode_team_root(entry.record)
59
+ valid = entry.key == team_id.to_s && root.team_id == entry.key &&
60
+ root.team_revision == entry.revision && (!revision || revision == entry.revision)
61
+ unless valid
62
+ raise Phronomy::Storage::SerializationError, "backend returned another Team identity/revision"
63
+ end
64
+ root
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ # Owns parent reservation checks and joins the Agent's durable admission.
6
+ # Reconstructed from current coordination wiring, never serialized as a Proc.
7
+ # @api public
8
+ class ReservedChildAdmission
9
+ def initialize(persistence:, owner:)
10
+ @persistence = persistence
11
+ @owner = Phronomy::Values::Immutable.copy(owner)
12
+ raise ArgumentError, "unknown reservation kind" unless %w[team subagent handoff].include?(@owner.fetch("kind"))
13
+ freeze
14
+ end
15
+
16
+ # Parent lock, current reservation check and child admission share one scope.
17
+ # No task, runtime owner or live Agent state is published before its commit.
18
+ # @api public
19
+ def admit(admission)
20
+ unless admission.correlation == @owner
21
+ raise Phronomy::Persistence::StateConflictError, "Child admission correlation mismatch"
22
+ end
23
+ @persistence.coordinator.atomic do |scope|
24
+ @persistence.participate(scope) do |records|
25
+ case @owner.fetch("kind")
26
+ when "team" then validate_team!(records, admission)
27
+ when "subagent" then validate_subagent!(records, admission, scope)
28
+ when "handoff" then validate_handoff!(records, admission, scope)
29
+ end
30
+ admission.accept_in(scope)
31
+ end
32
+ end
33
+ nil
34
+ end
35
+
36
+ private
37
+
38
+ def validate_team!(records, admission)
39
+ records.guard_team!(@owner.fetch("team_id"))
40
+ team = records.team_executions.load(@owner.fetch("team_execution_id"))
41
+ unless team.team_id == @owner.fetch("team_id") && team.active? && !team.metadata["cancel_requested"]
42
+ raise Phronomy::CancellationError, "Team run is no longer dispatchable"
43
+ end
44
+ slot = if @owner.fetch("slot") == "coordinator"
45
+ team.coordinator
46
+ else
47
+ assignment = team.assignments.find { |entry| entry.fetch("task_id") == @owner.fetch("slot") }
48
+ worker = assignment && team.workers.fetch(assignment.fetch("worker"))
49
+ worker&.merge("execution_id" => assignment.fetch("execution_id"))
50
+ end
51
+ unless slot && slot.fetch("execution_id") == admission.execution_id && slot.fetch("agent_id") == admission.agent_id
52
+ raise Phronomy::Persistence::StateConflictError, "Team reserved child identity mismatch"
53
+ end
54
+ end
55
+
56
+ def validate_subagent!(records, admission, scope)
57
+ @persistence.agent_store.guard_agents(scope, agent_ids: [@owner.fetch("parent_agent_id"), admission.agent_id])
58
+ parent = @persistence.agent_store.observe_execution(agent_id: @owner.fetch("parent_agent_id"),
59
+ execution_id: @owner.fetch("parent_execution_id"), scope: scope)
60
+ unless parent.continuable?
61
+ raise Phronomy::CancellationError, "Parent run is no longer dispatchable"
62
+ end
63
+ extension = @persistence.agent_store.execution_extension(agent_id: parent.agent_id, execution_id: parent.execution_id,
64
+ binding_key: DurableSubagentCoordinator::BINDING_KEY, scope: scope)
65
+ snapshot = records.contents.fetch_json(extension.state_ref)
66
+ raise Phronomy::CancellationError, "Parent reservation was cancelled" if snapshot["cancel_requested"]
67
+ slot = snapshot.fetch("children").find { |entry| entry.fetch("slot") == @owner.fetch("slot") }
68
+ unless slot && slot.fetch("agent_id") == admission.agent_id && slot.fetch("execution_id") == admission.execution_id
69
+ raise Phronomy::Persistence::StateConflictError, "Parent reserved child identity mismatch"
70
+ end
71
+ end
72
+
73
+ def validate_handoff!(records, admission, scope)
74
+ routing = records.handoff_states.load_locked(@owner.fetch("main_agent_id"))
75
+ unless routing && routing.active_agent_id == admission.agent_id
76
+ raise Phronomy::Persistence::StateConflictError, "Handoff responsibility changed before admission"
77
+ end
78
+ if Array(routing.metadata["cancelled_execution_ids"]).include?(admission.execution_id)
79
+ raise Phronomy::CancellationError, "Handoff reservation was cancelled"
80
+ end
81
+ @persistence.agent_store.guard_agents(scope, agent_ids: [@owner.fetch("main_agent_id"), admission.agent_id])
82
+ if routing.phase != "stable" && routing.pending_target_execution_id != admission.execution_id
83
+ raise Phronomy::Persistence::StateConflictError, "Handoff reserved Target identity mismatch"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ # Connects domain registries to the generic Runtime shutdown protocol.
6
+ # Construction does not register participants or start worker resources.
7
+ # @api private
8
+ class EngineEnvironment < ExecutionEnvironment
9
+ def initialize(runtime: Phronomy::Runtime.instance)
10
+ @runtime = runtime
11
+ end
12
+
13
+ def admissions
14
+ @runtime.__register_shutdown_participant(
15
+ key: AdmissionRegistry, participant: AdmissionRegistry.new
16
+ )
17
+ end
18
+
19
+ def ownership
20
+ existing_ownership || @runtime.__register_shutdown_participant(
21
+ key: TeamOwnershipRegistry, participant: TeamOwnershipRegistry.new
22
+ )
23
+ end
24
+
25
+ # Lookup must not register a participant, including during shutdown.
26
+ def existing_ownership = @runtime.__shutdown_participant(key: TeamOwnershipRegistry)
27
+
28
+ def current? = @runtime.equal?(Phronomy::Runtime.instance)
29
+
30
+ def submit(**options, &operation)
31
+ Phronomy::Execution.submit(runtime: @runtime, **options, &operation)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- module Agent
5
- # Implements peer coordination through a shared KnowledgeStore.
4
+ module MultiAgent
5
+ # Implements peer coordination through an invocation-local KnowledgeStore.
6
+ # @api public
6
7
  class SharedState
7
8
  # Semantic revision of the framework-owned SharedState instrumentation
8
9
  # applied on top of one specific researcher definition revision.
@@ -13,6 +14,8 @@ module Phronomy
13
14
  INSTRUMENTATION_DEFINITION_VERSION = 1
14
15
  private_constant :INSTRUMENTATION_DEFINITION_VERSION
15
16
 
17
+ # Findings shared by the sequential members of one invocation.
18
+ # @api public
16
19
  class KnowledgeStore
17
20
  def initialize
18
21
  @findings = []
@@ -78,8 +81,15 @@ module Phronomy
78
81
  # @api public
79
82
  def invoke(input, config: {})
80
83
  validate_termination!
81
-
82
84
  store = KnowledgeStore.new
85
+ cycles, terminated_by = coordinate_members(input, store)
86
+
87
+ {output: aggregate_findings(store), cycles: cycles, terminated_by: terminated_by}
88
+ end
89
+
90
+ private
91
+
92
+ def coordinate_members(input, store)
83
93
  max_cycles = self.class._max_cycles
84
94
  deadline = self.class._timeout ? Time.now + self.class._timeout : nil
85
95
  terminated_by = :max_cycles
@@ -87,15 +97,7 @@ module Phronomy
87
97
  cycle_limit = max_cycles || Float::INFINITY
88
98
 
89
99
  (1..cycle_limit).each do |cycle|
90
- self.class._members.each do |member_config|
91
- invoke_researcher(
92
- member_config[:klass],
93
- store,
94
- cycle,
95
- input,
96
- member_config[:instruction]
97
- )
98
- end
100
+ invoke_members(input, store, cycle)
99
101
  completed_cycles = cycle
100
102
 
101
103
  if self.class._terminate_when&.call(store)
@@ -109,17 +111,25 @@ module Phronomy
109
111
  end
110
112
  end
111
113
 
112
- output = if self.class._aggregator
114
+ [completed_cycles, terminated_by]
115
+ end
116
+
117
+ def invoke_members(input, store, cycle)
118
+ self.class._members.each do |member_config|
119
+ invoke_researcher(
120
+ member_config[:klass], store, cycle, input, member_config[:instruction]
121
+ )
122
+ end
123
+ end
124
+
125
+ def aggregate_findings(store)
126
+ if self.class._aggregator
113
127
  self.class._aggregator.call(store)
114
128
  else
115
129
  store.read_all
116
130
  end
117
-
118
- {output: output, cycles: completed_cycles, terminated_by: terminated_by}
119
131
  end
120
132
 
121
- private
122
-
123
133
  def validate_termination!
124
134
  return if self.class._max_cycles || self.class._timeout
125
135
 
@@ -148,28 +158,8 @@ module Phronomy
148
158
 
149
159
  def build_instrumented_researcher(researcher_class, store, cycle)
150
160
  agent_key = researcher_class.name&.to_sym || researcher_class.object_id.to_s.to_sym
151
-
152
- read_tool = Class.new(Phronomy::Agent::Context::Capability::Base) do
153
- tool_name "read_store"
154
- description "Read all current findings from the shared knowledge store. " \
155
- "Call this to see what other researchers have discovered."
156
- execution_mode :cooperative
157
-
158
- define_method(:execute) { store.read_all.to_json }
159
- end
160
-
161
- write_tool = Class.new(Phronomy::Agent::Context::Capability::Base) do
162
- tool_name "write_finding"
163
- description "Record a new finding into the shared knowledge store so " \
164
- "that other researchers can build on your discovery."
165
- execution_mode :cooperative
166
- param :content, type: :string, desc: "The finding to record"
167
-
168
- define_method(:execute) do |content:|
169
- store.write(agent: agent_key, content: content, cycle: cycle)
170
- "Finding recorded."
171
- end
172
- end
161
+ read_tool = build_read_tool(store)
162
+ write_tool = build_write_tool(store, cycle, agent_key)
173
163
 
174
164
  definitions = researcher_class.tools.to_h do |tool_class|
175
165
  [tool_class, researcher_class.tool_aliases[tool_class]]
@@ -197,12 +187,39 @@ module Phronomy
197
187
  end
198
188
  end
199
189
 
190
+ def build_read_tool(store)
191
+ Class.new(Phronomy::Tool::Base) do
192
+ tool_name "read_store"
193
+ description "Read all current findings from the shared knowledge store. " \
194
+ "Call this to see what other researchers have discovered."
195
+ execution_mode :cooperative
196
+
197
+ define_method(:execute) { store.read_all.to_json }
198
+ end
199
+ end
200
+
201
+ def build_write_tool(store, cycle, agent_key)
202
+ Class.new(Phronomy::Tool::Base) do
203
+ tool_name "write_finding"
204
+ description "Record a new finding into the shared knowledge store so " \
205
+ "that other researchers can build on your discovery."
206
+ execution_mode :cooperative
207
+ param :content, type: :string, desc: "The finding to record"
208
+
209
+ define_method(:execute) do |content:|
210
+ store.write(agent: agent_key, content: content, cycle: cycle)
211
+ "Finding recorded."
212
+ end
213
+ end
214
+ end
215
+
200
216
  def instrumented_definition_for(researcher_class)
201
217
  parent_def = researcher_class.agent_definition
202
218
  parent_id = parent_def.fetch(:id)
203
219
  parent_version = parent_def.fetch(:version)
204
220
 
205
221
  {
222
+ # Keep the existing semantic ID across the Ruby namespace migration.
206
223
  id: "Phronomy::Agent::SharedState::Instrumented/#{parent_id}@#{parent_version}".freeze,
207
224
  version: INSTRUMENTATION_DEFINITION_VERSION
208
225
  }.freeze
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module MultiAgent
5
+ # MultiAgent-owned durable operations and observations. An injected record
6
+ # adapter implements storage; this framework never selects a DB or codec.
7
+ # @api public
8
+ class Store
9
+ # Composition supplies the optional configured store without constructing
10
+ # a fallback. Team and Orchestrator retain their different fallback rules.
11
+ # @api private
12
+ def self.install_configured_provider(&provider)
13
+ raise ArgumentError, "store provider requires a block" unless provider
14
+ @configured_provider = provider
15
+ nil
16
+ end
17
+
18
+ # @api private
19
+ def self.configured = @configured_provider.call
20
+
21
+ attr_reader :coordinator, :agent_store
22
+
23
+ # Record protocol for MultiAgent-owned framework operations only.
24
+ # @api private
25
+ attr_reader :contents, :teams, :team_executions, :handoff_states
26
+
27
+ def initialize(coordinator:, records:, agent_store:)
28
+ @coordinator, @record_adapter = coordinator, records
29
+
30
+ unless agent_store.coordinator.equal?(coordinator)
31
+ raise Phronomy::Persistence::TransactionError, "Team and Agent stores must share one Persistence coordinator"
32
+ end
33
+ @agent_store = agent_store
34
+ @records = coordinator.bind(records)
35
+ @contents = @records.contents
36
+ @teams = @records.teams
37
+ @team_executions = @records.team_executions
38
+ @handoff_states = @records.handoff_states
39
+ end
40
+
41
+ # A domain operation owns this short transaction. A coordinating parent
42
+ # can instead call participate with its existing shared scope.
43
+ def transaction
44
+ coordinator.atomic do |scope|
45
+ participate(scope) { |records| yield records, scope }
46
+ end
47
+ end
48
+
49
+ def participate(scope, &operation)
50
+ scope.participate(persistence: coordinator, adapter: @record_adapter, &operation)
51
+ end
52
+
53
+ def handoff_leaf(execution_id, main_agent_id:, scope: nil)
54
+ return coordinator.atomic { |current| handoff_leaf(execution_id, main_agent_id: main_agent_id, scope: current) } unless scope
55
+ routing = participate(scope) { |records| records.handoff_states.load(main_agent_id) }
56
+ raise Phronomy::Persistence::NotFoundError, "Handoff history is absent" unless routing
57
+ identity = agent_store.execution_identity(execution_id, scope: scope)
58
+ unless identity.correlation == {"kind" => "handoff", "main_agent_id" => main_agent_id}
59
+ raise Phronomy::Persistence::StateConflictError, "Execution does not belong to this Handoff anchor"
60
+ end
61
+ seen = {}
62
+ loop do
63
+ raise Phronomy::Persistence::SerializationError, "Cyclic Handoff chain" if seen[identity.execution_id]
64
+ seen[identity.execution_id] = true
65
+ observed = agent_store.observe_execution(agent_id: identity.agent_id, execution_id: identity.execution_id, scope: scope)
66
+ return observed unless observed.status == :handed_off
67
+ receipt = observed.transfer_receipt
68
+ unless receipt && receipt.fetch("owner_key") == "handoff:#{main_agent_id}"
69
+ raise Phronomy::Persistence::SerializationError, "Invalid transfer receipt"
70
+ end
71
+ identity = Phronomy::Agent::ReservedExecution.new(agent_id: receipt.fetch("target_agent_id"), execution_id: receipt.fetch("target_execution_id"))
72
+ end
73
+ end
74
+
75
+ def handoff_result(execution_id, main_agent_id:)
76
+ assert_observation_thread!
77
+ transaction do |records, scope|
78
+ leaf = handoff_leaf(execution_id, main_agent_id: main_agent_id, scope: scope)
79
+ next leaf.to_result unless leaf.absent?
80
+ routing = records.handoff_states.load(main_agent_id)
81
+ if Array(routing.metadata["cancelled_execution_ids"]).include?(leaf.execution_id)
82
+ leaf.to_result.merge(status: :cancelled).freeze
83
+ elsif routing.phase != "stable" && routing.pending_target_execution_id == leaf.execution_id
84
+ leaf.to_result.merge(status: :active, reserved: true).freeze
85
+ else
86
+ raise Phronomy::Persistence::NotFoundError, "Reserved Handoff execution is absent"
87
+ end
88
+ end
89
+ end
90
+
91
+ def forget_subagents(agent_id:, execution_id:)
92
+ transaction do |records, scope|
93
+ extension = agent_store.execution_extension(agent_id: agent_id, execution_id: execution_id,
94
+ binding_key: DurableSubagentCoordinator::BINDING_KEY, scope: scope)
95
+ raise Phronomy::Persistence::NotFoundError, "Subagent history is absent" unless extension&.state_ref
96
+ snapshot = records.contents.fetch_json(extension.state_ref)
97
+ children = snapshot.fetch("children")
98
+ ids = ([agent_id] + children.map { |child| child.fetch("agent_id") }).uniq.sort.select { |id| agent_store.exist?(id) }
99
+ agent_store.guard_agents(scope, agent_ids: ids)
100
+ parent = agent_store.observe_execution(agent_id: agent_id, execution_id: execution_id, scope: scope)
101
+ raise Phronomy::AgentBusyError, "Subagent history retains unfinished parent" unless parent.terminal?
102
+ children.each do |child|
103
+ observed = agent_store.observe_execution(agent_id: child.fetch("agent_id"), execution_id: child.fetch("execution_id"), scope: scope)
104
+ if !observed.terminal? && !(observed.absent? && snapshot["cancel_requested"])
105
+ raise Phronomy::AgentBusyError, "Subagent history retains unfinished child"
106
+ end
107
+ end
108
+ agent_store.remove_execution_extension(agent_id: agent_id, execution_id: execution_id,
109
+ binding_key: DurableSubagentCoordinator::BINDING_KEY, scope: scope)
110
+ ids.each { |id| agent_store.release_retention(agent_id: id, owner_key: "subagent:#{execution_id}", scope: scope) }
111
+ end
112
+ true
113
+ end
114
+
115
+ def forget_handoff(main_agent_id)
116
+ transaction do |records, scope|
117
+ routing = records.handoff_states.load_locked(main_agent_id)
118
+ raise Phronomy::Persistence::NotFoundError, "Handoff history is absent" unless routing
119
+ ids = (Array(routing.metadata["retained_agent_ids"]) + [main_agent_id, routing.active_agent_id]).uniq.sort
120
+ agent_store.guard_agents(scope, agent_ids: ids)
121
+ unless routing.phase == "stable" && ids.all? { |id| agent_store.active_executions(agent_id: id, scope: scope).empty? }
122
+ raise Phronomy::AgentBusyError, "Handoff history retains unfinished work"
123
+ end
124
+ records.handoff_states.delete(main_agent_id, expected_revision: routing.handoff_revision)
125
+ ids.each { |id| agent_store.release_retention(agent_id: id, owner_key: "handoff:#{main_agent_id}", scope: scope) }
126
+ end
127
+ true
128
+ end
129
+
130
+ def result(team_execution_id)
131
+ assert_observation_thread!
132
+ execution = team_executions.load(team_execution_id)
133
+ {
134
+ team_execution_id: execution.team_execution_id, team_id: execution.team_id,
135
+ status: execution.status, phase: execution.phase,
136
+ result_ref: execution.result_ref, error_ref: execution.error_ref,
137
+ result: execution.result_ref && contents.fetch_json(execution.result_ref),
138
+ error: execution.error_ref && contents.fetch_json(execution.error_ref)
139
+ }.then { |value| Phronomy::Values::Immutable.copy(value) }
140
+ end
141
+
142
+ def runs(team_id, after: nil, limit: 100)
143
+ assert_observation_thread!
144
+ team_executions.list(team_id, after: after, limit: limit)
145
+ end
146
+
147
+ private
148
+
149
+ def assert_observation_thread!
150
+ if Phronomy::WaitPolicy.blocking_forbidden?
151
+ raise Phronomy::EventLoopReentrancyError, "Durable observation cannot block EventLoop"
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end