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