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,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Agent phase vocabulary and ordered external transitions. Both the phase
6
+ # machine and FSMSession read this definition; Engine has no Agent policy.
7
+ # Automatic and external transition priority belongs to this Agent contract.
8
+ # @api private
9
+ module InvocationTransitions
10
+ ENTRY_POINT = :idle
11
+
12
+ AUTO_STATE_SET = {
13
+ idle: true,
14
+ filtering_input: true,
15
+ building_context: true,
16
+ starting_tools: true,
17
+ evaluating_tools: true,
18
+ recording_tool_results: true,
19
+ output_filtering: true
20
+ }.freeze
21
+
22
+ DECLARED_STATES = %i[
23
+ idle filtering_input building_context calling_llm starting_tools
24
+ evaluating_tools waiting_for_tools dispatching_tools
25
+ recording_tool_results suspended output_filtering handed_off completed blocked failed
26
+ ].freeze
27
+
28
+ WAIT_STATES = %i[suspended].freeze
29
+
30
+ TOOL_EVENTS = %i[
31
+ tool_authorized
32
+ tool_approval_required
33
+ tool_completed
34
+ tool_failed
35
+ tool_rejected
36
+ tool_cancelled
37
+ ].freeze
38
+
39
+ EXTERNAL_EVENTS = begin
40
+ tool_transitions = TOOL_EVENTS.to_h do |event_name|
41
+ [event_name, [{from: :waiting_for_tools, to: :evaluating_tools, guard: nil}]]
42
+ end
43
+
44
+ events = tool_transitions.merge(
45
+ llm_completed: [
46
+ {from: :calling_llm, to: :failed, guard: ->(ctx) { ctx.callback_failed? }},
47
+ {from: :calling_llm, to: :failed, guard: ->(ctx) { ctx.handoff_failed? }},
48
+ {from: :calling_llm, to: :handed_off, guard: ->(ctx) { ctx.handoff_requested? }},
49
+ {from: :calling_llm, to: :starting_tools, guard: ->(ctx) { ctx.tool_call_pending? }},
50
+ {from: :calling_llm, to: :output_filtering, guard: nil}
51
+ ],
52
+ llm_failed: [
53
+ {from: :calling_llm, to: :failed, guard: nil}
54
+ ],
55
+ llm_setup_failed: [
56
+ {from: :calling_llm, to: :failed, guard: nil}
57
+ ],
58
+ tool_setup_failed: [
59
+ {from: :dispatching_tools, to: :failed, guard: nil}
60
+ ],
61
+ tool_dispatch_prepared: [
62
+ {from: :dispatching_tools, to: :evaluating_tools, guard: nil}
63
+ ],
64
+ resume: [
65
+ {from: :suspended, to: :waiting_for_tools, guard: nil}
66
+ ],
67
+ application_callback_failed: [
68
+ {from: :filtering_input, to: :failed, guard: nil},
69
+ {from: :building_context, to: :failed, guard: nil},
70
+ {from: :calling_llm, to: :failed, guard: nil},
71
+ {from: :starting_tools, to: :failed, guard: nil},
72
+ {from: :evaluating_tools, to: :failed, guard: nil},
73
+ {from: :waiting_for_tools, to: :failed, guard: nil},
74
+ {from: :dispatching_tools, to: :failed, guard: nil},
75
+ {from: :recording_tool_results, to: :failed, guard: nil},
76
+ {from: :output_filtering, to: :failed, guard: nil}
77
+ ]
78
+ )
79
+ events.each_value do |transitions|
80
+ transitions.each(&:freeze).freeze
81
+ end
82
+ events.freeze
83
+ end
84
+
85
+ AUTOMATIC_EVENTS = {
86
+ state_completed: [
87
+ {from: :idle, to: :filtering_input, guard: nil},
88
+ {from: :filtering_input, to: :building_context, guard: ->(ctx) { ctx.input_passed? }},
89
+ {from: :filtering_input, to: :blocked, guard: ->(ctx) { ctx.input_blocked? }},
90
+ {from: :building_context, to: :calling_llm, guard: nil},
91
+ {from: :starting_tools, to: :evaluating_tools, guard: nil},
92
+ {from: :evaluating_tools, to: :failed, guard: ->(ctx) { ctx.tool_batch_failed? }},
93
+ {from: :evaluating_tools, to: :blocked, guard: ->(ctx) { ctx.tool_batch_rejected? }},
94
+ {from: :evaluating_tools, to: :recording_tool_results, guard: ->(ctx) { ctx.tool_batch_completed? }},
95
+ {from: :evaluating_tools, to: :suspended, guard: ->(ctx) { ctx.approval_required? }},
96
+ {from: :evaluating_tools, to: :dispatching_tools, guard: ->(ctx) { ctx.ready_to_dispatch? }},
97
+ {from: :evaluating_tools, to: :waiting_for_tools, guard: nil},
98
+ {from: :recording_tool_results, to: :calling_llm, guard: nil},
99
+ {from: :output_filtering, to: :completed, guard: ->(ctx) { ctx.output_passed? }},
100
+ {from: :output_filtering, to: :blocked, guard: ->(ctx) { ctx.output_blocked? }}
101
+ ].each(&:freeze).freeze
102
+ }.freeze
103
+
104
+ EVENTS = AUTOMATIC_EVENTS.merge(EXTERNAL_EVENTS).freeze
105
+
106
+ def self.next_phase(phase, event, context)
107
+ definition = EVENTS.fetch(event.to_sym, []).find do |candidate|
108
+ candidate[:from] == phase.to_sym &&
109
+ allowed?(candidate, context)
110
+ end
111
+ definition&.fetch(:to)
112
+ end
113
+
114
+ def self.allowed?(definition, context)
115
+ guard = definition[:guard]
116
+ !guard || !!(context && guard.call(context))
117
+ end
118
+
119
+ def self.recursion_limit(max_iterations)
120
+ 12 + ((max_iterations || 10) * 8)
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Encodes execution facts using the caller's existing content transaction.
6
+ # It neither opens a transaction nor changes durable or live execution state.
7
+ # @api private
8
+ module RuntimeRecordEncoder
9
+ module_function
10
+
11
+ def encode(
12
+ execution,
13
+ agent_id:,
14
+ tx:,
15
+ snapshot:,
16
+ context_candidate:,
17
+ agent_root:
18
+ )
19
+ root = agent_root
20
+ records = []
21
+ calls = []
22
+
23
+ snapshot.fetch(:llm_results).each_with_index do |item, index|
24
+ outcome = item[:response]
25
+ error = item[:error]
26
+ llm_call_id = item.fetch(:llm_call_id).to_s
27
+ tools_requested = outcome&.tool_call? || false
28
+ call_error = error
29
+ output_ref = assistant_output_ref(tx, outcome)
30
+ assistant_ref = assistant_message_ref(tx, outcome)
31
+ error_ref = if call_error
32
+ tx.contents.put_json(
33
+ "class" => call_error.class.name,
34
+ "message" => call_error.message
35
+ )
36
+ end
37
+ usage_ref = if outcome && outcome.usage.to_h.values.any? { |value| !value.nil? }
38
+ tx.contents.put_json(json_value(outcome.usage.to_h))
39
+ end
40
+ call = LLMCallRecord.new(
41
+ llm_call_id: llm_call_id,
42
+ execution_id: execution.execution_id,
43
+ sequence: execution.llm_calls.length + index + 1,
44
+ status: call_error ? :failed : :completed,
45
+ manifest_ref: item.fetch(:manifest_ref),
46
+ output_ref: output_ref,
47
+ error_ref: error_ref,
48
+ usage_ref: usage_ref,
49
+ started_at: item.fetch(:started_at),
50
+ completed_at: Time.now.utc.iso8601(6),
51
+ metadata: {
52
+ "streaming" => item[:streaming],
53
+ "tools_requested" => tools_requested,
54
+ "assistant_outcome_captured" => !outcome.nil?,
55
+ "tool_call_count" => outcome ? outcome.tool_calls.length : 0
56
+ }
57
+ )
58
+ calls << call
59
+ call_ref = tx.contents.put_json(call.to_h)
60
+ records << JournalRecord.new(
61
+ agent_id: agent_id,
62
+ execution_id: execution.execution_id,
63
+ llm_call_id: llm_call_id,
64
+ kind: :llm_call_recorded,
65
+ channel: :audit,
66
+ content_ref: call_ref,
67
+ context_generation: root.transcript_generation,
68
+ context_candidate: false
69
+ )
70
+
71
+ if assistant_ref
72
+ records << JournalRecord.new(
73
+ agent_id: agent_id,
74
+ execution_id: execution.execution_id,
75
+ llm_call_id: llm_call_id,
76
+ kind: :assistant_message,
77
+ channel: :llm,
78
+ role: :assistant,
79
+ content_ref: assistant_ref,
80
+ context_generation: root.transcript_generation,
81
+ context_candidate: context_candidate,
82
+ metadata: assistant_message_metadata(outcome)
83
+ )
84
+ end
85
+ end
86
+
87
+ if (active = snapshot[:active_call])
88
+ abandoned = LLMCallRecord.new(
89
+ llm_call_id: active.fetch(:llm_call_id),
90
+ execution_id: execution.execution_id,
91
+ sequence: execution.llm_calls.length + calls.length + 1,
92
+ status: :cancelled,
93
+ manifest_ref: active.fetch(:manifest_ref),
94
+ started_at: active.fetch(:started_at),
95
+ completed_at: Time.now.utc.iso8601(6),
96
+ metadata: {
97
+ "reason" => "execution_terminalized_before_provider_settlement"
98
+ }
99
+ )
100
+ calls << abandoned
101
+ records << JournalRecord.new(
102
+ agent_id: agent_id,
103
+ execution_id: execution.execution_id,
104
+ llm_call_id: abandoned.llm_call_id,
105
+ kind: :llm_call_recorded,
106
+ channel: :audit,
107
+ content_ref: tx.contents.put_json(abandoned.to_h),
108
+ context_generation: root.transcript_generation,
109
+ context_candidate: false
110
+ )
111
+ end
112
+
113
+ snapshot.fetch(:runtime_events).each do |event|
114
+ case event.type
115
+ when :tool_call
116
+ next
117
+ when :tool_result
118
+ payload = event.payload
119
+ llm_call_id = payload[:llm_call_id] || payload["llm_call_id"]
120
+ tool_call_id = payload.fetch(:tool_call_id).to_s
121
+ tool_name = payload.fetch(:tool_name).to_s
122
+ next if execution.working_records.any? do |record|
123
+ record.kind == :tool_message && record.causation_id.to_s == tool_call_id && record.llm_call_id.to_s == llm_call_id.to_s
124
+ end
125
+ result_ref = put_runtime_content(tx, payload.fetch(:tool_result))
126
+ records << JournalRecord.new(
127
+ agent_id: agent_id,
128
+ execution_id: execution.execution_id,
129
+ llm_call_id: llm_call_id,
130
+ kind: :tool_result,
131
+ channel: :tool,
132
+ content_ref: result_ref,
133
+ causation_id: tool_call_id,
134
+ context_generation: root.transcript_generation,
135
+ context_candidate: false,
136
+ metadata: {
137
+ "tool_call_id" => tool_call_id,
138
+ "tool_name" => tool_name
139
+ }
140
+ )
141
+
142
+ message_payload = if payload.key?(:tool_message)
143
+ payload.fetch(:tool_message)
144
+ else
145
+ payload.fetch("tool_message")
146
+ end
147
+ records << JournalRecord.new(
148
+ agent_id: agent_id,
149
+ execution_id: execution.execution_id,
150
+ llm_call_id: llm_call_id,
151
+ kind: :tool_message,
152
+ channel: :tool,
153
+ role: :tool,
154
+ content_ref: tx.contents.put_json(json_value(message_payload)),
155
+ causation_id: tool_call_id,
156
+ context_generation: root.transcript_generation,
157
+ context_candidate: context_candidate,
158
+ metadata: {
159
+ "tool_call_id" => tool_call_id,
160
+ "tool_name" => tool_name
161
+ }
162
+ )
163
+ end
164
+ end
165
+ [records, calls]
166
+ end
167
+
168
+ def assistant_message_ref(tx, outcome)
169
+ return unless outcome
170
+
171
+ payload = {
172
+ "role" => "assistant",
173
+ "content" => json_value(outcome.content),
174
+ "tool_calls" => json_value(Array(outcome.tool_calls)),
175
+ "model_id" => outcome.metadata["model_id"]
176
+ }.compact
177
+ tx.contents.put_json(payload)
178
+ end
179
+
180
+ def assistant_message_metadata(outcome)
181
+ calls = Array(outcome&.tool_calls)
182
+ {
183
+ "tool_call_ids" => calls.map(&:id),
184
+ "tool_names" => calls.map(&:name)
185
+ }
186
+ end
187
+
188
+ def assistant_output_ref(tx, outcome)
189
+ return unless outcome&.content_present?
190
+
191
+ content = outcome.content
192
+ text = content.is_a?(String) ? content : Phronomy::CanonicalJSON.dump(content)
193
+ tx.contents.put_text(text)
194
+ end
195
+
196
+ def put_runtime_content(tx, value)
197
+ value.is_a?(String) ?
198
+ tx.contents.put_text(value) : tx.contents.put_json(json_value(value))
199
+ end
200
+
201
+ def json_value(value)
202
+ Phronomy::Values::Serializable.convert(value,
203
+ unsupported_message: "unsupported canonical runtime value")
204
+ end
205
+
206
+ private_class_method :assistant_message_ref, :assistant_message_metadata,
207
+ :assistant_output_ref
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Limited synchronous participant SPI. Agent owns all record mutations,
6
+ # revision checks and outcome publication. A participant owns its own
7
+ # transaction and passes only an opaque extension and a transfer receipt.
8
+ # @api public
9
+ class ExecutionChange
10
+ attr_reader :kind, :agent_id, :execution_id, :extension, :operations,
11
+ :control_request, :cancel_requested, :recovery_required, :durable_context,
12
+ :correlation, :coordinator
13
+
14
+ # Agent execution constructs changes; applications implement participants.
15
+ # @api private
16
+ def initialize(agent:, current:, root:, kind:, updated: nil, terminal: nil, config: {}, &write)
17
+ @agent, @store, @current, @root = agent, agent.persistence, current, root
18
+ @coordinator = @store.coordinator
19
+ @kind, @updated, @write = kind, updated, write
20
+ @agent_id, @execution_id = current.agent_id, current.execution_id
21
+ @extension = current.metadata["execution_extension"] && ExecutionExtensionState.from_h(current.metadata["execution_extension"])
22
+ @correlation = Phronomy::Values::Immutable.copy(current.metadata["reservation"])
23
+ @control_request = terminal&.handoff
24
+ @cancel_requested = terminal&.cancel_requested || false
25
+ error = terminal && (terminal.callback_failure&.to_stream_callback_error || terminal.source_error || terminal.block_error || terminal.invocation_error)
26
+ @control_request = nil if error || terminal&.phase == :suspended
27
+ @recovery_required = error.is_a?(Phronomy::ExecutionRehydrationRequiredError)
28
+ @finishing = kind == :terminal && !@recovery_required && (error || terminal.phase != :suspended)
29
+ @operations = Array(updated&.metadata&.fetch(ExecutionMetadata::TOOL_BATCH_METADATA_KEY, nil)).filter_map do |entry|
30
+ next unless entry.fetch("status") == "authorized"
31
+ Store::AuthorizedOperation.new(invocation_id: entry.fetch("tool_invocation_id"), name: entry.fetch("tool_name"),
32
+ arguments: Phronomy::Values::Immutable.copy(entry.fetch("arguments").compact))
33
+ end.freeze
34
+ ref = current.metadata["durable_context_ref"]
35
+ @durable_context = ref && Phronomy::Values::Immutable.copy(@store.contents.fetch_json(ref))
36
+ @participant = config[:phronomy_execution_participant]
37
+ @thread = Thread.current
38
+ validate_binding!
39
+ end
40
+
41
+ def finishing? = !!@finishing
42
+
43
+ # Runs current wiring and proves the complete captured change after an
44
+ # ambiguous response. A savepoint never grants publication permission.
45
+ # @api private
46
+ def perform
47
+ raise Phronomy::ConfigurationError, "Execution change is single-use" if @performed
48
+ @performed = true
49
+ perform_once
50
+ end
51
+
52
+ # @api private
53
+ def perform_once
54
+ if @participant
55
+ @participant.commit(self)
56
+ else
57
+ coordinator.atomic { |scope|
58
+ prepare_in(scope)
59
+ commit_in(scope)
60
+ }
61
+ end
62
+ unless @scope&.committed? && @result
63
+ raise Phronomy::Persistence::TransactionError, "Execution change has not committed its outer scope"
64
+ end
65
+ @result
66
+ rescue => error
67
+ raise unless @after && @scope&.outermost?
68
+ outcome = Phronomy::Persistence::SaveOutcome.compare(before: @before, after: @after, original_error: error) do
69
+ coordinator.atomic do |scope|
70
+ @participant&.guard_change(self, scope)
71
+ @store.guard_agents(scope, agent_ids: [agent_id])
72
+ snapshot(scope)
73
+ end
74
+ end
75
+ return @result if outcome.disposition == :committed
76
+ raise error if outcome.disposition == :not_committed
77
+ raise Phronomy::ExecutionRehydrationRequiredError, "Execution change outcome is unknown: #{(outcome.read_error || error).message}"
78
+ end
79
+
80
+ private :perform_once
81
+
82
+ # Capture the complete pre-state after the participant has acquired its
83
+ # ordered guards, before it changes its own records.
84
+ def prepare_in(scope)
85
+ raise Phronomy::Persistence::TransactionError, "Execution change belongs to another thread" unless @thread.equal?(Thread.current)
86
+ raise Phronomy::ConfigurationError, "Execution change is single-use" if @scope
87
+ @store.guard_agents(scope, agent_ids: [agent_id])
88
+ @before = snapshot(scope)
89
+ @cancel_requested ||= @store.cancellation_requested?(agent_id: agent_id, execution_id: execution_id, scope: scope)
90
+ @scope = scope
91
+ nil
92
+ end
93
+
94
+ # Must be called once in the participant's original synchronous context.
95
+ # No arbitrary execution setter or callback is exposed to the participant.
96
+ def commit_in(scope, state: extension, transfer_receipt: nil, pending: false)
97
+ raise Phronomy::Persistence::TransactionError, "Execution change belongs to another thread" unless @thread.equal?(Thread.current)
98
+ raise Phronomy::ConfigurationError, "Execution change must use its prepared scope exactly once" unless @scope.equal?(scope) && !@used
99
+ @used = true
100
+ if state && (!extension || state.binding_key != extension.binding_key || state.binding_version != extension.binding_version)
101
+ raise Phronomy::ConfigurationError, "Execution extension identity cannot change"
102
+ end
103
+ raise ArgumentError, "dispatch cannot choose a terminal outcome" if kind == :dispatch && (transfer_receipt || pending)
104
+ raise ArgumentError, "only a control request can transfer responsibility" if transfer_receipt && !control_request
105
+ raise ArgumentError, "pending work requires cancellation or recovery" if pending && !(cancel_requested || recovery_required)
106
+ @store.participate(scope) do |records|
107
+ records.assert_agent_watermark!(agent_id: agent_id, agent_revision: @root.agent_revision, journal_position: @root.journal_position)
108
+ observed = records.executions.load(execution_id)
109
+ unless observed.agent_id == agent_id && observed.execution_revision == @current.execution_revision
110
+ raise Phronomy::Persistence::StateConflictError, "Execution changed before participation"
111
+ end
112
+ if kind == :dispatch
113
+ updated = attach_state(@updated, state)
114
+ records.executions.save(execution_id, expected_revision: @current.execution_revision, execution: updated)
115
+ @result = updated
116
+ else
117
+ @result = @write.call(records, state, transfer_receipt, pending)
118
+ end
119
+ end
120
+ @after = snapshot(scope)
121
+ nil
122
+ end
123
+
124
+ # Finalized input material; policies are interpreted by the participant.
125
+ def transfer_context(scope)
126
+ raise ArgumentError, "there is no control request" unless control_request
127
+ @store.participate(scope) do |records|
128
+ manifest = SavedContextReader.manifest_from_ref(@agent, @current.metadata.fetch("manifest_ref"))
129
+ TransferProjection.new.build(request: control_request, manifest: manifest, persistence: records, source_agent: @agent)
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def validate_binding!
136
+ return unless extension
137
+ binding = @participant&.binding
138
+ unless binding && binding.binding_key == extension.binding_key && binding.binding_version == extension.binding_version
139
+ raise Phronomy::ExecutionRehydrationRequiredError, "Execution #{execution_id} needs participant #{extension.binding_key}@#{extension.binding_version}"
140
+ end
141
+ end
142
+
143
+ def attach_state(execution, state)
144
+ execution.with(execution_revision: execution.execution_revision,
145
+ metadata: execution.metadata.merge("execution_extension" => state&.to_h).compact)
146
+ end
147
+
148
+ def snapshot(scope)
149
+ agent_state = @store.participate(scope) do |records|
150
+ root = records.agents.load(agent_id)
151
+ execution = records.executions.load(execution_id)
152
+ journal = records.journals.read(agent_id, after: @root.journal_position,
153
+ limit: [root.journal_position - @root.journal_position, 1].max)
154
+ ref = execution.metadata.dig("execution_extension", "state_ref")
155
+ [root.to_h, execution.to_h, journal.map(&:to_h), ref && records.contents.fetch_json(ref), records.cancellations.requested?(agent_id, execution_id)]
156
+ end
157
+ [agent_state, @participant&.change_evidence(self, scope)]
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Process-local execution connection selected by composition. Agent owns
6
+ # admission, progress, approval, durable outcome and recovery decisions;
7
+ # this extension supplies serial delivery, session routing and resources.
8
+ # Environments and session handles are never persistence authority.
9
+ # @api private
10
+ class ExecutionEnvironment
11
+ class << self
12
+ def install_provider(provider)
13
+ raise ArgumentError, "Agent execution provider must be callable" unless provider.respond_to?(:call)
14
+ @provider = provider
15
+ end
16
+
17
+ def current
18
+ @provider&.call || raise(Phronomy::ConfigurationError, "Agent execution environment has not been configured")
19
+ end
20
+ end
21
+
22
+ def registry = raise(NotImplementedError)
23
+ def existing_registry = raise(NotImplementedError)
24
+ def ownership = raise(NotImplementedError)
25
+ def existing_ownership = raise(NotImplementedError)
26
+ def executing? = raise(NotImplementedError)
27
+ def session_phase(id) = raise(NotImplementedError)
28
+ def submit(**options, &operation) = raise(NotImplementedError)
29
+ def submit_authorization(timeout:, cancellation_token:, &operation) = raise(NotImplementedError)
30
+ def build_llm_client(adapter:) = raise(NotImplementedError)
31
+ def build_agent_session(invocation:, resume_event: nil, resume_phase: nil) = raise(NotImplementedError)
32
+ def build_tool_session(invocation:, parent_sink:, resume_event: nil, resume_phase: nil) = raise(NotImplementedError)
33
+ def register_session(session, completion:) = raise(NotImplementedError)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # An opaque state reference owned and interpreted by its bound participant.
6
+ # @api public
7
+ ExecutionExtensionState = Data.define(:binding_key, :binding_version, :state_ref) do
8
+ def initialize(binding_key:, binding_version:, state_ref: nil)
9
+ raise ArgumentError, "binding_key is required" if binding_key.to_s.empty?
10
+ raise ArgumentError, "binding_version must be positive" unless binding_version.is_a?(Integer) && binding_version.positive?
11
+ super(binding_key: binding_key.to_s.freeze, binding_version: binding_version, state_ref: state_ref&.to_s&.freeze)
12
+ end
13
+
14
+ def to_h
15
+ {"binding_key" => binding_key, "binding_version" => binding_version, "state_ref" => state_ref}.freeze
16
+ end
17
+
18
+ def self.from_h(value) = new(**value.transform_keys(&:to_sym))
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Stable domain observation; does not expose repositories or FSM phases.
6
+ # @api public
7
+ ExecutionObservation = Data.define(:agent_id, :execution_id, :status, :result,
8
+ :error, :reservation, :transfer_receipt, :cancellation_requested) do
9
+ def initialize(**values)
10
+ super(**values.transform_values { |value| Phronomy::Values::Immutable.copy(value) })
11
+ end
12
+
13
+ def self.from_result(value)
14
+ new(agent_id: value.fetch(:agent_id), execution_id: value.fetch(:execution_id), status: value.fetch(:status),
15
+ result: value[:result], error: value[:error], reservation: nil, transfer_receipt: nil, cancellation_requested: false)
16
+ end
17
+
18
+ def absent? = status == :absent
19
+ def terminal? = !%i[absent preparing active suspended].include?(status)
20
+ def continuable? = %i[preparing active].include?(status) && !cancellation_requested
21
+ def active? = %i[preparing active suspended].include?(status)
22
+
23
+ def value!
24
+ raise Phronomy::Persistence::NotFoundError, "Execution #{execution_id} is absent" if absent?
25
+ raise RecoverySupport.error_from_failure(error) if error
26
+ to_result
27
+ end
28
+
29
+ def to_result
30
+ Phronomy::Values::Immutable.copy(execution_id: execution_id, agent_id: agent_id,
31
+ status: status, result: result, output: result, error: error)
32
+ end
33
+ end
34
+ end
35
+ end