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,463 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Agent-owned admission, live execution state and physical quiescence.
6
+ # The EventLoop remains the sole writer during execution.
7
+ # @api private
8
+ class ExecutionRegistry < Phronomy::ExecutionReceiver
9
+ def self.existing_current
10
+ ExecutionEnvironment.current.existing_registry
11
+ end
12
+
13
+ # Immutable Agent-owned value. The map containing these records is the
14
+ # mutable authority; records are replaced rather than mutated in place.
15
+ AgentExecutionState = Data.define(
16
+ :execution_id,
17
+ :agent,
18
+ :coordinator,
19
+ :execution,
20
+ :runtime_projection,
21
+ :base_manifest,
22
+ :invocation,
23
+ :fsm_session_id
24
+ )
25
+ private_constant :AgentExecutionState
26
+
27
+ # Read-only process-local lookup view used by approval/live-owner APIs.
28
+ # It intentionally exposes no mutable execution, invocation, or projection.
29
+ AgentExecutionOwner = Data.define(:execution_id, :agent, :coordinator, :status)
30
+ private_constant :AgentExecutionOwner
31
+
32
+ # Agent-owned process-local top-level execution admission. This is
33
+ # separate from AgentExecutionState because admission begins before the
34
+ # durable AgentExecution exists. owner_token is coordination-only and is
35
+ # never a semantic result-authority identifier.
36
+ AgentAdmission = Data.define(:agent_id, :owner_token, :execution_id, :state)
37
+ private_constant :AgentAdmission
38
+
39
+ UNSET = Object.new.freeze
40
+ PhysicalCompletion = Data.define(:execution_id, :token)
41
+ private_constant :UNSET, :PhysicalCompletion
42
+
43
+ def initialize(channel:)
44
+ super
45
+ @agent_admissions = {}
46
+ @agent_executions = {}
47
+ @agent_completion_waiters = Hash.new { |hash, key| hash[key] = [] }
48
+ @agent_inflight_work = Hash.new { |hash, key| hash[key] = {} }
49
+ @agent_deferred_terminals = {}
50
+ end
51
+
52
+ def post(command, admission: false, completion: nil)
53
+ post_message(command, admission: admission, completion: completion)
54
+ end
55
+
56
+ def deliver(command)
57
+ assert_event_loop_thread!
58
+ if command.is_a?(PhysicalCompletion)
59
+ complete_agent_physical_work(command)
60
+ else
61
+ command.coordinator.deliver_on_event_loop(command)
62
+ end
63
+ end
64
+
65
+ # Called under the EventLoop lifecycle lock, including during shutdown.
66
+ def idle?
67
+ @agent_inflight_work.values.all?(&:empty?) &&
68
+ @agent_deferred_terminals.empty? &&
69
+ !agent_admission_transition_in_progress_locked?
70
+ end
71
+
72
+ # A dispatcher failure runs this on EventLoop; normal teardown runs it only
73
+ # after that thread has joined. Task callbacks run outside the lifecycle lock.
74
+ def shutdown(error:)
75
+ pending = synchronize do
76
+ waiters = @agent_completion_waiters.values.flatten
77
+ @agent_completion_waiters.clear
78
+ @agent_inflight_work.clear
79
+ @agent_deferred_terminals.clear
80
+ @agent_admissions.clear
81
+ @agent_executions.clear
82
+ waiters
83
+ end
84
+ return if pending.empty?
85
+
86
+ error ||= Phronomy::ExecutionRehydrationRequiredError.new(
87
+ "Runtime terminated while Agent execution remained nonterminal; " \
88
+ "process-local TaskResult handles are not rehydrated"
89
+ )
90
+ pending.each { |waiter| waiter.fail(error) }
91
+ end
92
+
93
+ # Process-local read-only admission check used by destructive Agent lifecycle
94
+ # operations. The mutable admission map itself remains Agent-owned.
95
+ def agent_execution_admitted?(agent_id)
96
+ synchronize { @agent_admissions.key?(agent_id.to_s) }
97
+ end
98
+
99
+ # Reserves the one top-level logical execution slot for agent_id before any
100
+ # Persistence execution admission is attempted.
101
+ # @api private
102
+ def admit_agent_execution(agent_id, owner_token:)
103
+ assert_event_loop_thread!
104
+ key = agent_id.to_s
105
+ raise ArgumentError, "agent_id must not be empty" if key.empty?
106
+ raise ArgumentError, "owner_token is required" unless owner_token
107
+
108
+ admit do
109
+ if @agent_admissions.key?(key)
110
+ raise Phronomy::AgentBusyError,
111
+ "Agent #{key.inspect} already has a nonterminal top-level execution"
112
+ end
113
+ @agent_admissions[key] = AgentAdmission.new(
114
+ agent_id: key.freeze,
115
+ owner_token: owner_token,
116
+ execution_id: nil,
117
+ state: :admitting
118
+ )
119
+ end
120
+ true
121
+ end
122
+
123
+ # Binds a successful durable AgentExecution identity to the earlier
124
+ # process-local admission.
125
+ # @api private
126
+ def bind_agent_execution_admission(agent_id, owner_token:, execution_id:)
127
+ assert_event_loop_thread!
128
+ key = agent_id.to_s
129
+ execution_key = execution_id.to_s
130
+ synchronize do
131
+ current = @agent_admissions.fetch(key) do
132
+ raise Phronomy::Error, "Agent #{key.inspect} has no Runtime admission"
133
+ end
134
+ unless current.owner_token.equal?(owner_token) && current.execution_id.nil?
135
+ raise Phronomy::Error, "stale Agent admission bind for #{key.inspect}"
136
+ end
137
+ @agent_admissions[key] = AgentAdmission.new(
138
+ agent_id: current.agent_id,
139
+ owner_token: current.owner_token,
140
+ execution_id: execution_key.freeze,
141
+ state: :executing
142
+ )
143
+ end
144
+ true
145
+ end
146
+
147
+ # @api private
148
+ def mark_agent_execution_admission(agent_id, execution_id:, state:)
149
+ assert_event_loop_thread!
150
+ key = agent_id.to_s
151
+ execution_key = execution_id.to_s
152
+ next_state = state.to_sym
153
+ unless %i[executing suspended resuming cancelling terminalizing recovery_required].include?(next_state)
154
+ raise ArgumentError, "unsupported Agent admission state: #{next_state.inspect}"
155
+ end
156
+
157
+ synchronize do
158
+ current = @agent_admissions.fetch(key) do
159
+ raise Phronomy::Error, "Agent #{key.inspect} has no Runtime admission"
160
+ end
161
+ unless current.execution_id.to_s == execution_key
162
+ raise Phronomy::Error, "stale Agent admission state update for #{key.inspect}"
163
+ end
164
+ @agent_admissions[key] = AgentAdmission.new(
165
+ agent_id: current.agent_id,
166
+ owner_token: current.owner_token,
167
+ execution_id: current.execution_id,
168
+ state: next_state
169
+ )
170
+ end
171
+ true
172
+ end
173
+
174
+ # @api private
175
+ def mark_agent_admission_recovery_required(agent_id, owner_token:)
176
+ assert_event_loop_thread!
177
+ key = agent_id.to_s
178
+ synchronize do
179
+ current = @agent_admissions.fetch(key) do
180
+ raise Phronomy::Error, "Agent #{key.inspect} has no Runtime admission"
181
+ end
182
+ unless current.owner_token.equal?(owner_token)
183
+ raise Phronomy::Error, "stale Agent admission recovery update for #{key.inspect}"
184
+ end
185
+ @agent_admissions[key] = AgentAdmission.new(
186
+ agent_id: current.agent_id,
187
+ owner_token: current.owner_token,
188
+ execution_id: current.execution_id,
189
+ state: :recovery_required
190
+ )
191
+ end
192
+ true
193
+ end
194
+
195
+ # Owner-aware release. Pre-durable failures release by owner_token; durable
196
+ # terminal outcomes release by execution_id.
197
+ # @api private
198
+ def release_agent_execution_admission(agent_id, owner_token: nil, execution_id: nil)
199
+ assert_event_loop_thread!
200
+ key = agent_id.to_s
201
+ synchronize do
202
+ current = @agent_admissions[key]
203
+ next false unless current
204
+
205
+ authoritative = if execution_id
206
+ current.execution_id.to_s == execution_id.to_s
207
+ elsif owner_token
208
+ current.owner_token.equal?(owner_token)
209
+ else
210
+ false
211
+ end
212
+ next false unless authoritative
213
+
214
+ @agent_admissions.delete(key)
215
+ true
216
+ end
217
+ end
218
+
219
+ # Process-local read-only owner lookup. Mutable Agent execution state never
220
+ # crosses this boundary; external callers receive only routing/ownership data.
221
+ def agent_execution_owner(execution_id)
222
+ key = execution_id.to_s
223
+ synchronize do
224
+ state = @agent_executions[key]
225
+ next nil unless state
226
+
227
+ AgentExecutionOwner.new(
228
+ execution_id: key.freeze,
229
+ agent: state.agent,
230
+ coordinator: state.coordinator,
231
+ status: state.execution.status
232
+ )
233
+ end
234
+ end
235
+
236
+ # EventLoop-only accessors below form the live Agent execution authority.
237
+ # Offload workers receive operation-specific immutable snapshots instead.
238
+ # @api private
239
+ def agent_execution_state(execution_id)
240
+ assert_event_loop_thread!
241
+ @agent_executions[execution_id.to_s]
242
+ end
243
+
244
+ # @api private
245
+ def install_agent_execution(
246
+ execution_id:,
247
+ agent:,
248
+ coordinator:,
249
+ execution:,
250
+ runtime_projection:,
251
+ base_manifest:,
252
+ invocation:,
253
+ fsm_session_id:
254
+ )
255
+ assert_event_loop_thread!
256
+ key = execution_id.to_s
257
+ state = AgentExecutionState.new(
258
+ execution_id: key.freeze,
259
+ agent: agent,
260
+ coordinator: coordinator,
261
+ execution: execution,
262
+ runtime_projection: runtime_projection,
263
+ base_manifest: base_manifest,
264
+ invocation: invocation,
265
+ fsm_session_id: fsm_session_id&.to_s&.freeze
266
+ )
267
+ synchronize do
268
+ if @agent_executions.key?(key)
269
+ raise Phronomy::Error, "Agent execution #{key.inspect} is already live"
270
+ end
271
+ @agent_executions[key] = state
272
+ end
273
+ state
274
+ end
275
+
276
+ # @api private
277
+ def replace_agent_execution(
278
+ execution_id,
279
+ execution: UNSET,
280
+ runtime_projection: UNSET,
281
+ invocation: UNSET,
282
+ fsm_session_id: UNSET
283
+ )
284
+ assert_event_loop_thread!
285
+ key = execution_id.to_s
286
+ synchronize do
287
+ current = @agent_executions.fetch(key) do
288
+ raise Phronomy::Error, "Agent execution #{key.inspect} is not live"
289
+ end
290
+ updated = AgentExecutionState.new(
291
+ execution_id: current.execution_id,
292
+ agent: current.agent,
293
+ coordinator: current.coordinator,
294
+ execution: execution.equal?(UNSET) ? current.execution : execution,
295
+ runtime_projection: runtime_projection.equal?(UNSET) ?
296
+ current.runtime_projection : runtime_projection,
297
+ base_manifest: current.base_manifest,
298
+ invocation: invocation.equal?(UNSET) ? current.invocation : invocation,
299
+ fsm_session_id: fsm_session_id.equal?(UNSET) ?
300
+ current.fsm_session_id : fsm_session_id&.to_s&.freeze
301
+ )
302
+ @agent_executions[key] = updated
303
+ updated
304
+ end
305
+ end
306
+
307
+ # Registers a caller-facing TaskResult that observes the authoritative terminal
308
+ # outcome of one logical Agent execution. Waiters are Runtime-only and are
309
+ # never persisted or rehydrated.
310
+ # @api private
311
+ def register_agent_completion_waiter(execution_id, task)
312
+ assert_event_loop_thread!
313
+ unless task.is_a?(Phronomy::TaskResult)
314
+ raise ArgumentError, "Agent completion waiter must be a Phronomy::TaskResult"
315
+ end
316
+
317
+ key = execution_id.to_s
318
+ synchronize do
319
+ waiters = @agent_completion_waiters[key]
320
+ waiters << task unless waiters.include?(task)
321
+ end
322
+ task
323
+ end
324
+
325
+ # Atomically detaches all process-local completion waiters at authoritative
326
+ # terminal delivery. A fallback TaskResult is included for pre-install terminal
327
+ # paths that never acquired a live execution directory entry.
328
+ # @api private
329
+ def take_agent_completion_waiters(execution_id, fallback: nil)
330
+ assert_event_loop_thread!
331
+ key = execution_id.to_s
332
+ synchronize do
333
+ waiters = @agent_completion_waiters.delete(key) || []
334
+ waiters << fallback if fallback && !waiters.include?(fallback)
335
+ waiters
336
+ end
337
+ end
338
+
339
+ # Registers one execution-owned asynchronous operation for physical
340
+ # quiescence supervision. OffloadPool tasks expose a private physical
341
+ # completion signal; custom asynchronous handles are required to make their
342
+ # ordinary completion mean that no residual execution-affecting work remains.
343
+ # @api private
344
+ def supervise_agent_operation(execution_id, operation)
345
+ assert_event_loop_thread!
346
+ key = execution_id.to_s
347
+ unless operation.respond_to?(:on_complete)
348
+ raise ArgumentError, "supervised operation must expose on_complete"
349
+ end
350
+
351
+ physically_done = if operation.respond_to?(:physical_complete?)
352
+ operation.physical_complete?
353
+ elsif operation.respond_to?(:done?)
354
+ operation.done?
355
+ else
356
+ false
357
+ end
358
+ return operation if physically_done
359
+
360
+ token = Object.new.freeze
361
+ synchronize do
362
+ unless @agent_executions.key?(key)
363
+ raise Phronomy::Error, "Agent execution #{key.inspect} is not live"
364
+ end
365
+ @agent_inflight_work[key][token] = true
366
+ end
367
+
368
+ callback = lambda do
369
+ accepted = post_message(PhysicalCompletion.new(execution_id: key, token: token))
370
+ unless accepted
371
+ Phronomy::RuntimeSettings.current.logger&.warn(
372
+ "[Phronomy] EventLoop rejected physical-completion delivery for #{key}"
373
+ )
374
+ end
375
+ end
376
+
377
+ if operation.respond_to?(:on_physical_complete)
378
+ operation.on_physical_complete(&callback)
379
+ else
380
+ operation.on_complete { |_value, _error| callback.call }
381
+ end
382
+ operation
383
+ end
384
+
385
+ # @api private
386
+ def agent_execution_quiescent?(execution_id)
387
+ assert_event_loop_thread!
388
+ key = execution_id.to_s
389
+ synchronize do
390
+ work = @agent_inflight_work.fetch(key, nil)
391
+ work.nil? || work.empty?
392
+ end
393
+ end
394
+
395
+ # Holds exactly one terminal continuation while cancellation/deadline has
396
+ # revoked result authority but execution-owned physical work is still live.
397
+ # @api private
398
+ def defer_agent_terminal_until_quiescent(execution_id, command)
399
+ assert_event_loop_thread!
400
+ key = execution_id.to_s
401
+ synchronize do
402
+ if @agent_deferred_terminals.key?(key)
403
+ raise Phronomy::Error, "Agent execution #{key.inspect} already has a deferred terminal"
404
+ end
405
+ @agent_deferred_terminals[key] = command
406
+ end
407
+ true
408
+ end
409
+
410
+ # @api private
411
+ def agent_inflight_work_count(execution_id)
412
+ key = execution_id.to_s
413
+ synchronize do
414
+ (@agent_inflight_work.fetch(key, nil) || {}).size
415
+ end
416
+ end
417
+
418
+ # @api private
419
+ def release_agent_execution(execution_id)
420
+ assert_event_loop_thread!
421
+ key = execution_id.to_s
422
+ synchronize do
423
+ work = @agent_inflight_work.fetch(key, nil)
424
+ unless work.nil? || work.empty?
425
+ raise Phronomy::Error,
426
+ "cannot release non-quiescent Agent execution #{key.inspect}"
427
+ end
428
+ if @agent_deferred_terminals.key?(key)
429
+ raise Phronomy::Error,
430
+ "cannot release Agent execution #{key.inspect} with deferred terminal work"
431
+ end
432
+ @agent_inflight_work.delete(key)
433
+ @agent_executions.delete(key)
434
+ end
435
+ end
436
+
437
+ private
438
+
439
+ def complete_agent_physical_work(payload)
440
+ key = payload.execution_id.to_s
441
+ token = payload.token
442
+ deferred = synchronize do
443
+ work = @agent_inflight_work.fetch(key, nil)
444
+ next nil unless work&.delete(token)
445
+
446
+ if work.empty?
447
+ @agent_inflight_work.delete(key)
448
+ command = @agent_deferred_terminals.delete(key)
449
+ command
450
+ end
451
+ end
452
+ deferred&.coordinator&.deliver_on_event_loop(deferred)
453
+ true
454
+ end
455
+
456
+ def agent_admission_transition_in_progress_locked?
457
+ @agent_admissions.values.any? do |admission|
458
+ %i[admitting executing resuming cancelling terminalizing].include?(admission.state)
459
+ end
460
+ end
461
+ end
462
+ end
463
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../execution/concurrency/worker_input_restricted"
4
+
5
+ module Phronomy
6
+ module Agent
7
+ # Registers ordinary and resumed Agent/Tool sessions on EventLoop.
8
+ # ExecutionCoordinator owns admission and terminal persistence; this object
9
+ # only wires the live sessions and reports their completion to that owner.
10
+ # @api private
11
+ class ExecutionSessionRunner
12
+ include Phronomy::Concurrency::WorkerInputRestricted
13
+
14
+ def initialize(environment:, on_complete:)
15
+ @on_complete = on_complete
16
+ @environment = environment
17
+ end
18
+
19
+ def register(session, result_task, source_name: "#{result_task.name}-source")
20
+ assert_event_loop!
21
+ source = Phronomy::TaskResult.deferred(name: source_name)
22
+ source.on_complete do |invocation, error|
23
+ @on_complete.call(
24
+ execution_id: session.context.execution_id,
25
+ result_task: result_task,
26
+ invocation: invocation || session.context,
27
+ error: error,
28
+ fsm_session_id: session.id
29
+ )
30
+ end
31
+ @environment.register_session(session, completion: source)
32
+ session
33
+ end
34
+
35
+ def resume(invocation, result_task, resume_event:, resume_phase:,
36
+ source_name: "#{result_task.name}-source")
37
+ assert_event_loop!
38
+ session = @environment.build_agent_session(
39
+ invocation: invocation,
40
+ resume_event: resume_event,
41
+ resume_phase: resume_phase
42
+ )
43
+ @environment.registry.replace_agent_execution(
44
+ invocation.execution_id,
45
+ invocation: invocation,
46
+ fsm_session_id: session.id
47
+ )
48
+ register(session, result_task, source_name: source_name)
49
+ end
50
+
51
+ def resume_approval(invocation, result_task, approved:, config:)
52
+ assert_event_loop!
53
+ invocation.merge_config!(config)
54
+ invocation.begin_approval_resume!(approved: approved)
55
+ parent = resume(invocation, result_task,
56
+ resume_event: :resume, resume_phase: :suspended)
57
+ invocation.tool_invocations.each do |child|
58
+ session = if child.awaiting_approval?
59
+ @environment.build_tool_session(
60
+ invocation: child,
61
+ parent_sink: parent.event_sink,
62
+ resume_event: approved ? :approve : :reject,
63
+ resume_phase: :awaiting_approval
64
+ )
65
+ elsif !approved && child.authorized?
66
+ @environment.build_tool_session(
67
+ invocation: child,
68
+ parent_sink: parent.event_sink,
69
+ resume_event: :cancel,
70
+ resume_phase: :authorized
71
+ )
72
+ end
73
+ register_child(child, session, parent.event_sink) if session
74
+ end
75
+ end
76
+
77
+ def resume_framework_tools(invocation, result_task)
78
+ assert_event_loop!
79
+ parent = resume(invocation, result_task,
80
+ resume_event: :resume, resume_phase: :suspended,
81
+ source_name: "framework-tool-recovery-source")
82
+ invocation.tool_invocations.select(&:authorized?).each do |child|
83
+ session = @environment.build_tool_session(
84
+ invocation: child,
85
+ parent_sink: parent.event_sink,
86
+ resume_event: :dispatch,
87
+ resume_phase: :authorized
88
+ )
89
+ register_child(child, session, parent.event_sink)
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def register_child(child, session, parent_event_sink)
96
+ completion = Phronomy::TaskResult.deferred(name: "tool-session:#{child.id}")
97
+ completion.on_complete do |_result, error|
98
+ next unless error
99
+
100
+ child.mark_framework_failed!(error)
101
+ parent_event_sink.post(:tool_failed, {tool_invocation_id: child.id})
102
+ end
103
+ @environment.register_session(session, completion: completion)
104
+ end
105
+
106
+ def assert_event_loop!
107
+ return if @environment.executing?
108
+
109
+ raise Phronomy::Error, "Agent session registration must run on EventLoop"
110
+ end
111
+ end
112
+ end
113
+ end