phronomy 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +4 -0
  4. data/VERIFY.sh +27 -27
  5. data/benchmark/bench_agent_invoke.rb +26 -22
  6. data/benchmark/bench_context_assembler.rb +4 -5
  7. data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
  8. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  9. data/docs/architecture/agent-context.md +4 -2
  10. data/docs/architecture/agent-transition-ownership.md +89 -0
  11. data/docs/architecture/before-llm-input.md +6 -0
  12. data/docs/architecture/context-management.md +30 -0
  13. data/docs/architecture/context-preparation-steps.md +90 -0
  14. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  15. data/docs/architecture/execution-metadata-and-values.md +80 -0
  16. data/docs/architecture/generator-verifier-ownership.md +111 -0
  17. data/docs/architecture/multi-agent-handoff.md +8 -2
  18. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  19. data/docs/architecture/persistence.md +27 -4
  20. data/docs/architecture/refactoring-closure.md +334 -0
  21. data/docs/architecture/remaining-refactoring-plan.md +374 -0
  22. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  23. data/docs/architecture/tool-schema-recording-gap.md +50 -0
  24. data/docs/architecture/tracing.md +1 -1
  25. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  26. data/docs/architecture.md +209 -0
  27. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  28. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  29. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  30. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  31. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
  32. data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
  33. data/docs/decisions/032-storage-backend-composition.md +80 -0
  34. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  35. data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -0
  36. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  37. data/docs/decisions/036-context-contract-ownership.md +86 -0
  38. data/docs/decisions/037-common-definition-ownership.md +62 -0
  39. data/docs/decisions/038-responsibility-based-source-layout.md +131 -0
  40. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
  41. data/docs/decisions/040-configuration-default-composition.md +94 -0
  42. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  43. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  44. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  45. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  46. data/docs/decisions/045-worker-input-restriction-ownership.md +100 -0
  47. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  48. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  49. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  50. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  51. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  52. data/docs/decisions/051-execution-outcome-worker-ownership.md +151 -0
  53. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  54. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  55. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  56. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  57. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  58. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  59. data/docs/decisions/058-neutral-storage-primitives.md +78 -0
  60. data/docs/decisions/README.md +35 -6
  61. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  62. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  63. data/docs/features.md +9 -3
  64. data/docs/getting-started.md +4 -4
  65. data/docs/migrations/0.15.md +5 -0
  66. data/docs/migrations/durable-semantic-coordination-v2.md +6 -0
  67. data/docs/migrations/handoff-runner-multi-agent.md +35 -0
  68. data/docs/migrations/neutral-storage-spi.md +43 -0
  69. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  70. data/docs/migrations/shared-state-multi-agent.md +44 -0
  71. data/docs/migrations/storage-backend-composition.md +117 -0
  72. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  73. data/docs/persistence-backends.md +162 -204
  74. data/docs/runtime-and-concurrency.md +117 -32
  75. data/lib/phronomy/agent/api/agent.rb +17 -0
  76. data/lib/phronomy/agent/async_event_api.rb +2 -2
  77. data/lib/phronomy/agent/base.rb +51 -241
  78. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  79. data/lib/phronomy/agent/context/capability/base.rb +67 -26
  80. data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
  81. data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
  82. data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
  83. data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
  84. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
  85. data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
  86. data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
  87. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
  88. data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
  89. data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
  90. data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
  91. data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
  92. data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
  93. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  94. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
  95. data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +19 -95
  96. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  97. data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
  98. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +9 -10
  99. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
  100. data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
  101. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  102. data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
  103. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
  104. data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
  105. data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
  106. data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
  107. data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
  108. data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +20 -71
  109. data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
  110. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  111. data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
  112. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
  113. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
  114. data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
  115. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  116. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  117. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  118. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  119. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  120. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -10
  121. data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
  122. data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
  123. data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
  124. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
  125. data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
  126. data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
  127. data/lib/phronomy/agent/persistence/codec.rb +358 -0
  128. data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
  129. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
  130. data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
  131. data/lib/phronomy/agent/persistence/queries.rb +61 -0
  132. data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
  133. data/lib/phronomy/agent/persistence/watermark.rb +27 -0
  134. data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
  135. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
  136. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +32 -37
  137. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
  138. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +6 -10
  139. data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
  140. data/lib/phronomy/agent/selection/candidate.rb +1 -1
  141. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  142. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  143. data/lib/phronomy/agent/tool_execution/tool_binding.rb +90 -0
  144. data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
  145. data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
  146. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
  147. data/lib/phronomy/common/configuration_error.rb +7 -0
  148. data/lib/phronomy/common/error.rb +5 -0
  149. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  150. data/lib/phronomy/common/values/serializable.rb +32 -0
  151. data/lib/phronomy/{configuration.rb → configuration/configuration.rb} +14 -5
  152. data/lib/phronomy/configuration/global_configuration.rb +26 -0
  153. data/lib/phronomy/content_store/storage_schema.rb +11 -0
  154. data/lib/phronomy/content_store/stored_contents.rb +43 -0
  155. data/lib/phronomy/engine/backpressure_error.rb +7 -0
  156. data/lib/phronomy/{blocking.rb → engine/blocking.rb} +1 -1
  157. data/lib/phronomy/engine/cancellation_error.rb +7 -0
  158. data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
  159. data/lib/phronomy/engine/concurrency/offload_pool.rb +4 -0
  160. data/lib/phronomy/engine/concurrency/operation_binding.rb +1 -1
  161. data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
  162. data/lib/phronomy/engine/event_loop.rb +158 -624
  163. data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
  164. data/lib/phronomy/{execution.rb → engine/execution.rb} +0 -7
  165. data/lib/phronomy/{execution_cancellation_error.rb → engine/execution_cancellation_error.rb} +2 -0
  166. data/lib/phronomy/engine/execution_receiver.rb +65 -0
  167. data/lib/phronomy/{execution_timeout_error.rb → engine/execution_timeout_error.rb} +2 -0
  168. data/lib/phronomy/engine/fsm_protocol.rb +14 -0
  169. data/lib/phronomy/engine/fsm_session.rb +35 -28
  170. data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +2 -0
  171. data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +2 -0
  172. data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +2 -0
  173. data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
  174. data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
  175. data/lib/phronomy/engine/runtime.rb +80 -84
  176. data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
  177. data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
  178. data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
  179. data/lib/phronomy/engine/task_result.rb +4 -0
  180. data/lib/phronomy/engine/timeout_error.rb +7 -0
  181. data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
  182. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  183. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  184. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  185. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  186. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  187. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  188. data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
  189. data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
  190. data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
  191. data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
  192. data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
  193. data/lib/phronomy/llm_contract/transport_error.rb +7 -0
  194. data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
  195. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +5 -5
  196. data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
  197. data/lib/phronomy/multi_agent/orchestrator.rb +2 -2
  198. data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
  199. data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
  200. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
  201. data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
  202. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  203. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
  204. data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
  205. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  206. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
  207. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  208. data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
  209. data/lib/phronomy/persistence/api/persistence.rb +140 -0
  210. data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
  211. data/lib/phronomy/persistence_composition/repositories.rb +77 -0
  212. data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
  213. data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
  214. data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
  215. data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
  216. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  217. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  218. data/lib/phronomy/storage/backend.rb +101 -0
  219. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  220. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  221. data/lib/phronomy/storage/blobs.rb +31 -0
  222. data/lib/phronomy/storage/condition.rb +25 -0
  223. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  224. data/lib/phronomy/storage/conflict_error.rb +9 -0
  225. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  226. data/lib/phronomy/storage/entry.rb +33 -0
  227. data/lib/phronomy/storage/guard_ref.rb +13 -0
  228. data/lib/phronomy/storage/not_found_error.rb +9 -0
  229. data/lib/phronomy/storage/record_codec.rb +177 -0
  230. data/lib/phronomy/storage/records.rb +61 -0
  231. data/lib/phronomy/storage/resource.rb +126 -0
  232. data/lib/phronomy/storage/scope.rb +25 -0
  233. data/lib/phronomy/storage/serialization_error.rb +9 -0
  234. data/lib/phronomy/storage/streams.rb +44 -0
  235. data/lib/phronomy/storage/transaction_error.rb +10 -0
  236. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  237. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  238. data/lib/phronomy/storage/validation.rb +53 -0
  239. data/lib/phronomy/storage/view.rb +131 -0
  240. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  241. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
  242. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
  243. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
  244. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
  245. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
  246. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
  247. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  248. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
  249. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  250. data/lib/phronomy/tool/contract/tool_error.rb +7 -0
  251. data/lib/phronomy/tools/agent.rb +1 -1
  252. data/lib/phronomy/version.rb +1 -1
  253. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +8 -6
  254. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
  255. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  256. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
  257. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +122 -71
  258. data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
  259. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  260. data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
  261. data/lib/phronomy/workflow/phase_machine_builder.rb +2 -2
  262. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
  263. data/lib/phronomy.rb +52 -94
  264. data/scripts/api_snapshot.rb +1 -1
  265. data/scripts/storage_spi_snapshot.rb +36 -0
  266. data/sig/phronomy/agent.rbs +0 -1
  267. data/sig/phronomy/execution_receiver.rbs +34 -0
  268. data/sig/phronomy/handoff.rbs +4 -2
  269. data/sig/phronomy/multi_agent.rbs +18 -0
  270. data/sig/phronomy/persistence.rbs +7 -91
  271. data/sig/phronomy/storage.rbs +174 -0
  272. data/sig/phronomy/tool.rbs +10 -1
  273. metadata +230 -99
  274. data/lib/phronomy/agent/execution_coordinator.rb +0 -3151
  275. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  276. data/lib/phronomy/agent/recovery_support.rb +0 -504
  277. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  278. data/lib/phronomy/agent/tool_executor.rb +0 -55
  279. data/lib/phronomy/generator_verifier.rb +0 -369
  280. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  281. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  282. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  283. data/lib/phronomy/persistence/in_memory.rb +0 -690
  284. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  285. data/lib/phronomy/persistence.rb +0 -276
  286. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  287. data/lib/phronomy/workflow_recovery.rb +0 -123
  288. /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
  289. /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
  290. /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
  291. /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
  292. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  293. /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
  294. /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
  295. /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
  296. /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
  297. /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
  298. /data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +0 -0
  299. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  300. /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
  301. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
  302. /data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +0 -0
  303. /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
  304. /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Workflow-owned segment admission and routing, mutated only on EventLoop.
5
+ # @api private
6
+ class WorkflowExecutionRegistry < Phronomy::ExecutionReceiver
7
+ # Workflow-owned process-local Workflow execution-segment admission. The
8
+ # owner token is a Runtime coordination capability, not a domain identity or
9
+ # routing identity. fsm_session_id is bound only after durable load/hydration
10
+ # completes and a concrete FSMSession has been constructed.
11
+ WorkflowAdmission = Data.define(
12
+ :workflow_instance_id, :owner_token, :fsm_session_id, :state
13
+ )
14
+ private_constant :WorkflowAdmission
15
+
16
+ def initialize(event_loop:)
17
+ super
18
+ @workflow_admissions = {}
19
+ end
20
+
21
+ def post(command, admission: false, completion: nil)
22
+ post_message(command, admission: admission, completion: completion)
23
+ end
24
+
25
+ def deliver(command)
26
+ assert_event_loop_thread!
27
+ command.runner.deliver_on_event_loop(command)
28
+ end
29
+
30
+ def idle?
31
+ !workflow_admission_transition_in_progress_locked?
32
+ end
33
+
34
+ def session_retired(fsm_session_id, reason:)
35
+ assert_event_loop_thread!
36
+ mark_workflow_recovery_required_for_session(fsm_session_id) if reason == :recovery_required
37
+ end
38
+
39
+ def shutdown(error:)
40
+ synchronize { @workflow_admissions.clear }
41
+ end
42
+
43
+ # Reserves one logical Workflow execution segment before durable load or
44
+ # hydration. The owner token is independent from any later FSMSession id.
45
+ # @api private
46
+ def admit_workflow(workflow_instance_id, owner_token:)
47
+ assert_event_loop_thread!
48
+ key = workflow_instance_id.to_s
49
+ raise ArgumentError, "workflow_instance_id must not be empty" if key.empty?
50
+ raise ArgumentError, "owner_token is required" unless owner_token
51
+
52
+ admit do
53
+ if @workflow_admissions.key?(key)
54
+ raise Phronomy::Error,
55
+ "Workflow instance #{key.inspect} already has a live execution segment"
56
+ end
57
+ @workflow_admissions[key] = WorkflowAdmission.new(
58
+ workflow_instance_id: key.freeze,
59
+ owner_token: owner_token,
60
+ fsm_session_id: nil,
61
+ state: :admitting
62
+ )
63
+ end
64
+ true
65
+ end
66
+
67
+ # Binds the concrete routing identity after admission and durable hydration.
68
+ # @api private
69
+ def bind_workflow_session(workflow_instance_id, owner_token:, fsm_session_id:)
70
+ assert_event_loop_thread!
71
+ key = workflow_instance_id.to_s
72
+ fsm_key = fsm_session_id.to_s
73
+ raise ArgumentError, "fsm_session_id must not be empty" if fsm_key.empty?
74
+
75
+ synchronize do
76
+ current = @workflow_admissions.fetch(key) do
77
+ raise Phronomy::Error, "Workflow instance #{key.inspect} has no Runtime admission"
78
+ end
79
+ unless current.owner_token.equal?(owner_token) && current.fsm_session_id.nil?
80
+ raise Phronomy::Error, "stale Workflow admission bind for #{key.inspect}"
81
+ end
82
+ @workflow_admissions[key] = WorkflowAdmission.new(
83
+ workflow_instance_id: current.workflow_instance_id,
84
+ owner_token: current.owner_token,
85
+ fsm_session_id: fsm_key.freeze,
86
+ state: :executing
87
+ )
88
+ end
89
+ true
90
+ end
91
+
92
+ # @api private
93
+ def mark_workflow_admission(workflow_instance_id, owner_token:, state:)
94
+ assert_event_loop_thread!
95
+ key = workflow_instance_id.to_s
96
+ next_state = state.to_sym
97
+ unless %i[executing persisting_terminal recovery_required].include?(next_state)
98
+ raise ArgumentError, "unsupported Workflow admission state: #{next_state.inspect}"
99
+ end
100
+
101
+ synchronize do
102
+ current = @workflow_admissions.fetch(key) do
103
+ raise Phronomy::Error, "Workflow instance #{key.inspect} has no Runtime admission"
104
+ end
105
+ unless current.owner_token.equal?(owner_token)
106
+ raise Phronomy::Error, "stale Workflow admission update for #{key.inspect}"
107
+ end
108
+ @workflow_admissions[key] = WorkflowAdmission.new(
109
+ workflow_instance_id: current.workflow_instance_id,
110
+ owner_token: current.owner_token,
111
+ fsm_session_id: current.fsm_session_id,
112
+ state: next_state
113
+ )
114
+ end
115
+ true
116
+ end
117
+
118
+ # Owner-aware release. A competing or stale attempt cannot release the
119
+ # current Workflow execution segment.
120
+ # @api private
121
+ def release_workflow(workflow_instance_id, owner_token:)
122
+ assert_event_loop_thread!
123
+ key = workflow_instance_id.to_s
124
+ synchronize do
125
+ current = @workflow_admissions[key]
126
+ next false unless current&.owner_token&.equal?(owner_token)
127
+
128
+ @workflow_admissions.delete(key)
129
+ true
130
+ end
131
+ end
132
+
133
+ # Read-only diagnostics used by internal tests and routing assertions.
134
+ def workflow_admission_owner(workflow_instance_id)
135
+ synchronize do
136
+ @workflow_admissions[workflow_instance_id.to_s]&.owner_token
137
+ end
138
+ end
139
+
140
+ def workflow_admission_fsm_session_id(workflow_instance_id)
141
+ synchronize do
142
+ @workflow_admissions[workflow_instance_id.to_s]&.fsm_session_id
143
+ end
144
+ end
145
+
146
+ def workflow_admission_state(workflow_instance_id)
147
+ synchronize do
148
+ @workflow_admissions[workflow_instance_id.to_s]&.state
149
+ end
150
+ end
151
+
152
+ def post_to_workflow(workflow_instance_id:, event:, payload: nil)
153
+ @event_loop.__route_execution_event(self) do
154
+ admission = @workflow_admissions[workflow_instance_id.to_s]
155
+ next unless admission&.state == :executing && admission.fsm_session_id
156
+
157
+ Phronomy::Event.new(
158
+ type: event.to_sym, target_id: admission.fsm_session_id, payload: payload
159
+ )
160
+ end
161
+ end
162
+
163
+ private
164
+
165
+ def mark_workflow_recovery_required_for_session(fsm_session_id)
166
+ synchronize do
167
+ key, admission = @workflow_admissions.find do |_workflow_instance_id, candidate|
168
+ candidate.fsm_session_id == fsm_session_id.to_s
169
+ end
170
+ return false unless admission
171
+
172
+ @workflow_admissions[key] = WorkflowAdmission.new(
173
+ workflow_instance_id: admission.workflow_instance_id,
174
+ owner_token: admission.owner_token,
175
+ fsm_session_id: nil,
176
+ state: :recovery_required
177
+ )
178
+ end
179
+ true
180
+ end
181
+
182
+ def workflow_admission_transition_in_progress_locked?
183
+ @workflow_admissions.values.any? do |admission|
184
+ %i[admitting executing persisting_terminal].include?(admission.state)
185
+ end
186
+ end
187
+ end
188
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../engine/concurrency/worker_input_restricted"
4
+
3
5
  require "securerandom"
4
6
 
5
7
  module Phronomy
@@ -16,9 +18,9 @@ module Phronomy
16
18
  # to the same FSMSession before that session becomes halted/completed.
17
19
  # @api private
18
20
  class WorkflowRunner
19
- include Phronomy::Runnable
21
+ include Phronomy::Concurrency::WorkerInputRestricted
20
22
 
21
- FINISH = :__end__
23
+ include Phronomy::Runnable
22
24
 
23
25
  Execution = Data.define(
24
26
  :context,
@@ -138,7 +140,7 @@ module Phronomy
138
140
  end
139
141
 
140
142
  # Posts an application-defined event to the currently live Workflow owner.
141
- # EventLoop resolves workflow_instance_id to the current concrete
143
+ # WorkflowExecutionRegistry resolves workflow_instance_id to the current concrete
142
144
  # fsm_session_id. The opaque admission owner token is deliberately not a
143
145
  # routing identity and is not exposed here.
144
146
  def signal(workflow_instance_id:, event:, payload: nil)
@@ -151,7 +153,10 @@ module Phronomy
151
153
  "Valid events: #{@external_events.keys.inspect}"
152
154
  end
153
155
 
154
- Phronomy::Runtime.instance.event_loop.post_to_workflow(
156
+ registry = Phronomy::WorkflowExecutionRegistry.existing_for(Phronomy::Runtime.instance)
157
+ return false unless registry
158
+
159
+ registry.post_to_workflow(
155
160
  workflow_instance_id: workflow_instance_id,
156
161
  event: event_name,
157
162
  payload: payload
@@ -277,7 +282,7 @@ module Phronomy
277
282
  runtime = Phronomy::Runtime.instance
278
283
  event_loop = runtime.event_loop
279
284
  admitted = false
280
- event_loop.admit_workflow(
285
+ Phronomy::WorkflowExecutionRegistry.for(event_loop).admit_workflow(
281
286
  request.workflow_instance_id,
282
287
  owner_token: request.owner_token
283
288
  )
@@ -318,7 +323,7 @@ module Phronomy
318
323
  )
319
324
  rescue => error
320
325
  if admitted
321
- event_loop.release_workflow(
326
+ Phronomy::WorkflowExecutionRegistry.for(event_loop).release_workflow(
322
327
  request.workflow_instance_id,
323
328
  owner_token: request.owner_token
324
329
  )
@@ -330,7 +335,7 @@ module Phronomy
330
335
  runtime = Phronomy::Runtime.instance
331
336
  event_loop = runtime.event_loop
332
337
  admitted = false
333
- event_loop.admit_workflow(
338
+ Phronomy::WorkflowExecutionRegistry.for(event_loop).admit_workflow(
334
339
  request.workflow_instance_id,
335
340
  owner_token: request.owner_token
336
341
  )
@@ -366,7 +371,7 @@ module Phronomy
366
371
  )
367
372
  rescue => error
368
373
  if admitted
369
- event_loop.release_workflow(
374
+ Phronomy::WorkflowExecutionRegistry.for(event_loop).release_workflow(
370
375
  request.workflow_instance_id,
371
376
  owner_token: request.owner_token
372
377
  )
@@ -503,7 +508,7 @@ module Phronomy
503
508
  local_snapshot = normalize_snapshot(snapshot_for(state))
504
509
  return durable_revision if normalized_durable == local_snapshot
505
510
 
506
- raise Phronomy::Persistence::ConflictError,
511
+ raise Phronomy::Storage::ConflictError,
507
512
  "Workflow state changed since the supplied halted context for " \
508
513
  "workflow_instance_id #{state.workflow_instance_id.inspect}; explicit reload/reconciliation is required"
509
514
  end
@@ -576,7 +581,7 @@ module Phronomy
576
581
  resume_phase: resume_phase,
577
582
  stable_observer: stable_observer
578
583
  )
579
- event_loop.bind_workflow_session(
584
+ Phronomy::WorkflowExecutionRegistry.for(event_loop).bind_workflow_session(
580
585
  execution.workflow_instance_id,
581
586
  owner_token: execution.owner_token,
582
587
  fsm_session_id: session.id
@@ -591,11 +596,12 @@ module Phronomy
591
596
  error: error
592
597
  )
593
598
  end
594
- event_loop.register(session, completion: source_task)
599
+ event_loop.register(session, completion: source_task,
600
+ receiver: Phronomy::WorkflowExecutionRegistry.for(event_loop))
595
601
  result_task
596
602
  rescue => error
597
603
  if event_loop&.current?
598
- event_loop&.release_workflow(
604
+ Phronomy::WorkflowExecutionRegistry.for(event_loop).release_workflow(
599
605
  execution.workflow_instance_id,
600
606
  owner_token: execution.owner_token
601
607
  )
@@ -608,7 +614,7 @@ module Phronomy
608
614
  def finalize_execution(execution:, result_task:, result:, error:)
609
615
  event_loop = Phronomy::Runtime.instance.event_loop
610
616
  assert_event_loop!(event_loop)
611
- event_loop.release_workflow(
617
+ Phronomy::WorkflowExecutionRegistry.for(event_loop).release_workflow(
612
618
  execution.workflow_instance_id,
613
619
  owner_token: execution.owner_token
614
620
  )
@@ -624,73 +630,117 @@ module Phronomy
624
630
  runtime = Phronomy::Runtime.instance
625
631
  event_loop = runtime.event_loop
626
632
  assert_event_loop!(event_loop)
627
- event_loop.mark_workflow_admission(
633
+ Phronomy::WorkflowExecutionRegistry.for(event_loop).mark_workflow_admission(
628
634
  execution.workflow_instance_id,
629
635
  owner_token: execution.owner_token,
630
636
  state: :persisting_terminal
631
637
  )
632
638
 
633
- operation = WorkflowTerminalPersistenceCommand.new(
639
+ operation = Phronomy::WorkflowRunner::WorkflowTerminalPersistenceCommand.new(
634
640
  repository: execution.repository,
635
641
  workflow_instance_id: execution.workflow_instance_id.to_s.freeze,
636
642
  expected_revision: execution.expected_revision,
637
643
  snapshot: deep_immutable_copy(snapshot_for(context))
638
644
  )
645
+
639
646
  task = runtime.offload.submit(on_full: :raise) do
640
- revision = operation.repository.save(
641
- operation.workflow_instance_id,
642
- expected_revision: operation.expected_revision,
643
- snapshot: operation.snapshot
647
+ persist_terminal_snapshot(operation)
648
+ end
649
+ task.on_complete do |result, operation_error|
650
+ deliver_terminal_persistence_result(execution, event_sink, result, operation_error)
651
+ end
652
+ terminal_type
653
+ end
654
+
655
+ def persist_terminal_snapshot(operation)
656
+ revision = operation.repository.save(
657
+ operation.workflow_instance_id,
658
+ expected_revision: operation.expected_revision,
659
+ snapshot: operation.snapshot
660
+ )
661
+ Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
662
+ outcome: :success,
663
+ revision: revision,
664
+ error: nil
665
+ )
666
+ rescue Phronomy::Storage::ConflictError,
667
+ Phronomy::Storage::NotFoundError,
668
+ Phronomy::Storage::SerializationError,
669
+ Phronomy::Storage::UnsupportedBackendError => error
670
+ Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
671
+ outcome: :known_failure,
672
+ revision: nil,
673
+ error: error
674
+ )
675
+ rescue => error
676
+ reconcile_workflow_terminal_f1(operation, error)
677
+ end
678
+
679
+ def deliver_terminal_persistence_result(execution, event_sink, result, operation_error)
680
+ delivery = if operation_error
681
+ Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
682
+ outcome: :outcome_unknown,
683
+ revision: nil,
684
+ error: operation_error
644
685
  )
645
- WorkflowTerminalPersistenceResult.new(
686
+ else
687
+ result
688
+ end
689
+ accepted = event_sink.post(:workflow_terminal_persistence_result, delivery)
690
+ unless accepted
691
+ Phronomy.configuration.logger&.warn(
692
+ "[Phronomy] EventLoop rejected Workflow terminal persistence result " \
693
+ "for #{execution.workflow_instance_id.inspect}"
694
+ )
695
+ end
696
+ end
697
+
698
+ def reconcile_workflow_terminal_f1(operation, original_error)
699
+ record = operation.repository.load(operation.workflow_instance_id)
700
+ case Phronomy::Recovery.compare_revisioned_snapshot(
701
+ record: record,
702
+ expected_pre_revision: operation.expected_revision,
703
+ intended_snapshot: operation.snapshot
704
+ )
705
+ when :post_state
706
+ revision = Phronomy::Recovery.fetch_value(record, :revision)
707
+ Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
646
708
  outcome: :success,
647
709
  revision: revision,
648
710
  error: nil
649
711
  )
650
- rescue Phronomy::Persistence::ConflictError,
651
- Phronomy::Persistence::NotFoundError,
652
- Phronomy::Persistence::SerializationError,
653
- Phronomy::Persistence::UnsupportedBackendError => error
654
- WorkflowTerminalPersistenceResult.new(
712
+ when :pre_state
713
+ # The uncertain save is now known not to have committed. Do not blindly
714
+ # retry it here; surface a known failure through the ordinary terminal
715
+ # barrier semantics.
716
+ Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
655
717
  outcome: :known_failure,
656
718
  revision: nil,
657
- error: error
719
+ error: original_error
658
720
  )
659
- rescue => error
660
- # The Backend SPI does not currently provide a portable commit-outcome
661
- # classifier for arbitrary storage/transport failures. If non-commit is
662
- # not guaranteed by the Phronomy error contract, fail closed as F1.
663
- WorkflowTerminalPersistenceResult.new(
721
+ else
722
+ conflict = Phronomy::Storage::ConflictError.new(
723
+ "Workflow terminal Persistence outcome conflicts with both expected " \
724
+ "pre-state and intended post-state for #{operation.workflow_instance_id.inspect}"
725
+ )
726
+ Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
664
727
  outcome: :outcome_unknown,
665
728
  revision: nil,
666
- error: error
729
+ error: conflict
667
730
  )
668
731
  end
669
- task.on_complete do |result, operation_error|
670
- delivery = if operation_error
671
- WorkflowTerminalPersistenceResult.new(
672
- outcome: :outcome_unknown,
673
- revision: nil,
674
- error: operation_error
675
- )
676
- else
677
- result
678
- end
679
- accepted = event_sink.post(:workflow_terminal_persistence_result, delivery)
680
- unless accepted
681
- Phronomy.configuration.logger&.warn(
682
- "[Phronomy] EventLoop rejected Workflow terminal persistence result " \
683
- "for #{execution.workflow_instance_id.inspect}"
684
- )
685
- end
686
- end
687
- terminal_type
732
+ rescue => reconciliation_error
733
+ Phronomy::WorkflowRunner::WorkflowTerminalPersistenceResult.new(
734
+ outcome: :outcome_unknown,
735
+ revision: nil,
736
+ error: reconciliation_error
737
+ )
688
738
  end
689
739
 
690
740
  def release_and_fail(request, error)
691
741
  event_loop = Phronomy::Runtime.instance.event_loop
692
742
  assert_event_loop!(event_loop)
693
- event_loop.release_workflow(
743
+ Phronomy::WorkflowExecutionRegistry.for(event_loop).release_workflow(
694
744
  request.workflow_instance_id,
695
745
  owner_token: request.owner_token
696
746
  )
@@ -711,17 +761,6 @@ module Phronomy
711
761
  resume_phase: nil,
712
762
  stable_observer: nil
713
763
  )
714
- terminal_barrier = if execution.repository && execution.persist
715
- ->(terminal_type:, context:, event_sink:) {
716
- begin_terminal_persistence_on_event_loop(
717
- execution,
718
- terminal_type: terminal_type,
719
- context: context,
720
- event_sink: event_sink
721
- )
722
- }
723
- end
724
-
725
764
  Phronomy::FSMSession.new(
726
765
  context: execution.context,
727
766
  context_metadata: {
@@ -739,7 +778,22 @@ module Phronomy
739
778
  resume_event: resume_event,
740
779
  resume_phase: resume_phase,
741
780
  stable_observer: stable_observer,
742
- terminal_barrier: terminal_barrier
781
+ terminal_policy: build_terminal_policy(execution)
782
+ )
783
+ end
784
+
785
+ def build_terminal_policy(execution)
786
+ return unless execution.repository && execution.persist
787
+
788
+ Phronomy::WorkflowTerminalPolicy.new(
789
+ persist: ->(terminal_type:, context:, event_sink:) {
790
+ begin_terminal_persistence_on_event_loop(
791
+ execution,
792
+ terminal_type: terminal_type,
793
+ context: context,
794
+ event_sink: event_sink
795
+ )
796
+ }
743
797
  )
744
798
  end
745
799
 
@@ -766,13 +820,10 @@ module Phronomy
766
820
  end
767
821
 
768
822
  def post_control(runtime, command)
769
- runtime.event_loop.post(
770
- Phronomy::Event.new(
771
- type: :workflow_control,
772
- target_id: Phronomy::EventLoop::SYSTEM_CHANNEL_ID,
773
- payload: {command: command}
774
- )
775
- )
823
+ admission = command.is_a?(StartCommand) || command.is_a?(ResumeCommand)
824
+ completion = admission ? command.result_task : command.request.result_task
825
+ Phronomy::WorkflowExecutionRegistry.for(runtime.event_loop).post(command,
826
+ admission: admission, completion: completion)
776
827
  end
777
828
 
778
829
  def runtime_rejected_error(action)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Interprets durable Workflow results for the shared FSM terminal gate.
5
+ # The session owns pending state; Runner owns persistence and reconciliation.
6
+ # @api private
7
+ class WorkflowTerminalPolicy
8
+ include Phronomy::Concurrency::WorkerInputRestricted
9
+
10
+ def initialize(persist:)
11
+ @persist = persist
12
+ end
13
+
14
+ def start(terminal_type:, context:, event_sink:)
15
+ @persist.call(terminal_type: terminal_type, context: context, event_sink: event_sink)
16
+ end
17
+
18
+ def handles?(event)
19
+ event.type == :workflow_terminal_persistence_result
20
+ end
21
+
22
+ def decision_for(event)
23
+ result = event.payload
24
+ case result.outcome
25
+ when :success
26
+ FSMProtocol::TerminalDecision.new(action: :complete, error: nil)
27
+ when :known_failure
28
+ FSMProtocol::TerminalDecision.new(
29
+ action: :fail,
30
+ error: result.error || Phronomy::Error.new("Workflow terminal persistence failed")
31
+ )
32
+ when :outcome_unknown
33
+ FSMProtocol::TerminalDecision.new(action: :retire, error: result.error)
34
+ else
35
+ raise Phronomy::Error,
36
+ "unknown Workflow terminal persistence outcome: #{result.outcome.inspect}"
37
+ end
38
+ end
39
+ end
40
+ end