phronomy 0.25.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 (323) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -0
  3. data/CONTRIBUTING.md +4 -4
  4. data/README.md +14 -7
  5. data/VERIFY.sh +27 -27
  6. data/benchmark/bench_agent_invoke.rb +26 -22
  7. data/benchmark/bench_context_assembler.rb +4 -5
  8. data/benchmark/bench_regression.rb +1 -1
  9. data/docs/architecture/agent-chat-and-state-ownership.md +147 -0
  10. data/docs/architecture/agent-configuration-and-tool-binding.md +139 -0
  11. data/docs/architecture/agent-context.md +5 -3
  12. data/docs/architecture/agent-transition-ownership.md +89 -0
  13. data/docs/architecture/before-llm-input.md +6 -0
  14. data/docs/architecture/context-management.md +30 -0
  15. data/docs/architecture/context-preparation-steps.md +90 -0
  16. data/docs/architecture/entry-action-and-team-wording.md +72 -0
  17. data/docs/architecture/execution-metadata-and-values.md +80 -0
  18. data/docs/architecture/generator-verifier-ownership.md +111 -0
  19. data/docs/architecture/multi-agent-handoff.md +8 -2
  20. data/docs/architecture/persistence-refactoring-plan.md +50 -0
  21. data/docs/architecture/persistence.md +29 -6
  22. data/docs/architecture/refactoring-closure.md +334 -0
  23. data/docs/architecture/remaining-refactoring-plan.md +374 -0
  24. data/docs/architecture/rubyllm-2-token-ownership.md +82 -0
  25. data/docs/architecture/tool-schema-recording-gap.md +50 -0
  26. data/docs/architecture/tracing.md +4 -4
  27. data/docs/architecture/workflow-terminal-ownership-design.md +104 -0
  28. data/docs/architecture.md +209 -0
  29. data/docs/async-composition.md +205 -0
  30. data/docs/decisions/010-cooperative-first-concurrency.md +23 -23
  31. data/docs/decisions/014-unified-persistence-durable-state.md +11 -0
  32. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +5 -1
  33. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +13 -3
  34. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +5 -1
  35. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +5 -0
  36. data/docs/decisions/031-durable-multi-agent-coordination.md +5 -0
  37. data/docs/decisions/032-storage-backend-composition.md +80 -0
  38. data/docs/decisions/033-domain-persistence-ownership.md +79 -0
  39. data/docs/decisions/034-handoff-runner-coordination-ownership.md +76 -0
  40. data/docs/decisions/035-tool-executor-capability-ownership.md +68 -0
  41. data/docs/decisions/036-context-contract-ownership.md +86 -0
  42. data/docs/decisions/037-common-definition-ownership.md +62 -0
  43. data/docs/decisions/038-responsibility-based-source-layout.md +131 -0
  44. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +74 -0
  45. data/docs/decisions/040-configuration-default-composition.md +94 -0
  46. data/docs/decisions/041-feature-owned-identity-registries.md +100 -0
  47. data/docs/decisions/042-feature-owned-execution-state.md +106 -0
  48. data/docs/decisions/043-storage-execution-constraint-notifications.md +75 -0
  49. data/docs/decisions/044-agent-default-and-one-shot-composition.md +106 -0
  50. data/docs/decisions/045-worker-input-restriction-ownership.md +100 -0
  51. data/docs/decisions/046-agent-responsibility-layout-and-shared-records.md +110 -0
  52. data/docs/decisions/047-recovered-execution-continuation-contract.md +116 -0
  53. data/docs/decisions/048-dispatch-preparation-worker-ownership.md +120 -0
  54. data/docs/decisions/049-initial-preparation-worker-ownership.md +111 -0
  55. data/docs/decisions/050-approval-resume-snapshot-and-commit-ownership.md +98 -0
  56. data/docs/decisions/051-execution-outcome-worker-ownership.md +151 -0
  57. data/docs/decisions/052-tool-invocation-restoration-ownership.md +92 -0
  58. data/docs/decisions/053-shared-state-coordination-ownership.md +71 -0
  59. data/docs/decisions/054-workflow-terminal-save-single-owner.md +76 -0
  60. data/docs/decisions/055-terminal-observer-failure-settlement.md +60 -0
  61. data/docs/decisions/056-workflow-terminal-policy-ownership.md +87 -0
  62. data/docs/decisions/057-storage-transaction-boundaries.md +73 -0
  63. data/docs/decisions/058-neutral-storage-primitives.md +78 -0
  64. data/docs/decisions/README.md +35 -6
  65. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +6 -0
  66. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +6 -0
  67. data/docs/features.md +21 -12
  68. data/docs/getting-started.md +10 -10
  69. data/docs/migrations/0.15.md +5 -0
  70. data/docs/migrations/durable-semantic-coordination-v2.md +7 -1
  71. data/docs/migrations/handoff-runner-multi-agent.md +35 -0
  72. data/docs/migrations/neutral-storage-spi.md +43 -0
  73. data/docs/migrations/parallel-tool-chat-removal.md +49 -0
  74. data/docs/migrations/shared-state-multi-agent.md +44 -0
  75. data/docs/migrations/storage-backend-composition.md +117 -0
  76. data/docs/migrations/storage-transaction-boundaries.md +74 -0
  77. data/docs/persistence-backends.md +162 -204
  78. data/docs/runtime-and-concurrency.md +205 -60
  79. data/lib/phronomy/agent/api/agent.rb +17 -0
  80. data/lib/phronomy/agent/async_event_api.rb +2 -2
  81. data/lib/phronomy/agent/base.rb +55 -251
  82. data/lib/phronomy/{agent.rb → agent/composition/run_once.rb} +4 -13
  83. data/lib/phronomy/agent/context/capability/base.rb +67 -26
  84. data/lib/phronomy/agent/context/capability/tool_executor.rb +62 -0
  85. data/lib/phronomy/agent/{context_assembler.rb → context_assembly/context_assembler.rb} +143 -88
  86. data/lib/phronomy/agent/{context_importer.rb → context_assembly/context_importer.rb} +2 -2
  87. data/lib/phronomy/agent/{ruby_llm_materializer.rb → context_assembly/ruby_llm_materializer.rb} +4 -7
  88. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +36 -0
  89. data/lib/phronomy/agent/context_assembly/saved_context_reader.rb +53 -0
  90. data/lib/phronomy/agent/context_assembly/state_writer.rb +165 -0
  91. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +22 -0
  92. data/lib/phronomy/agent/{context_plan.rb → context_contract/context_plan.rb} +1 -1
  93. data/lib/phronomy/agent/{context_policy_input.rb → context_contract/context_policy_input.rb} +6 -6
  94. data/lib/phronomy/agent/{llm_input_build_context.rb → context_contract/llm_input_build_context.rb} +1 -1
  95. data/lib/phronomy/agent/{llm_input_manifest.rb → context_contract/llm_input_manifest.rb} +20 -20
  96. data/lib/phronomy/agent/{llm_input_patch.rb → context_contract/llm_input_patch.rb} +2 -2
  97. data/lib/phronomy/agent/{agent_execution.rb → execution/agent_execution.rb} +8 -4
  98. data/lib/phronomy/agent/{agent_invocation.rb → execution/agent_invocation.rb} +8 -13
  99. data/lib/phronomy/agent/{agent_invocation_session_builder.rb → execution/agent_invocation_session_builder.rb} +20 -96
  100. data/lib/phronomy/agent/execution/approval_resume_commit.rb +108 -0
  101. data/lib/phronomy/agent/execution/dispatch_preparation.rb +305 -0
  102. data/lib/phronomy/agent/{exact_execution.rb → execution/exact_execution.rb} +12 -13
  103. data/lib/phronomy/agent/{execution_cancellation.rb → execution/execution_cancellation.rb} +2 -3
  104. data/lib/phronomy/agent/execution/execution_coordinator.rb +1925 -0
  105. data/lib/phronomy/agent/execution/execution_failure.rb +30 -0
  106. data/lib/phronomy/agent/execution/execution_metadata.rb +53 -0
  107. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +344 -0
  108. data/lib/phronomy/agent/execution/execution_registry.rb +459 -0
  109. data/lib/phronomy/agent/execution/execution_session_runner.rb +118 -0
  110. data/lib/phronomy/agent/execution/initial_preparation.rb +421 -0
  111. data/lib/phronomy/agent/execution/invocation_transitions.rb +86 -0
  112. data/lib/phronomy/agent/{phase_machine_builder.rb → execution/phase_machine_builder.rb} +22 -73
  113. data/lib/phronomy/agent/{provider_call_outcome.rb → execution/provider_call_outcome.rb} +9 -9
  114. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +210 -0
  115. data/lib/phronomy/agent/{handoff_context.rb → handoff/handoff_context.rb} +2 -2
  116. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +15 -0
  117. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +131 -0
  118. data/lib/phronomy/agent/{handoff_state.rb → handoff/handoff_state.rb} +1 -1
  119. data/lib/phronomy/agent/{journal_projection.rb → journal/journal_projection.rb} +4 -0
  120. data/lib/phronomy/agent/{journal_record.rb → journal/journal_record.rb} +3 -3
  121. data/lib/phronomy/agent/{llm_call_record.rb → journal/llm_call_record.rb} +2 -2
  122. data/lib/phronomy/agent/{agent_root.rb → lifecycle/agent_root.rb} +6 -2
  123. data/lib/phronomy/agent/lifecycle/default_persistence.rb +29 -0
  124. data/lib/phronomy/{engine/runtime/agent_ownership_registry.rb → agent/lifecycle/ownership_registry.rb} +24 -10
  125. data/lib/phronomy/{agent_already_exists_error.rb → agent/lifecycle_contract/agent_already_exists_error.rb} +2 -0
  126. data/lib/phronomy/{agent_busy_error.rb → agent/lifecycle_contract/agent_busy_error.rb} +2 -0
  127. data/lib/phronomy/{agent_purged_error.rb → agent/lifecycle_contract/agent_purged_error.rb} +2 -0
  128. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +7 -0
  129. data/lib/phronomy/{stream_callback_error.rb → agent/lifecycle_contract/stream_callback_error.rb} +2 -0
  130. data/lib/phronomy/agent/persistence/agent_repository.rb +61 -0
  131. data/lib/phronomy/agent/persistence/codec.rb +358 -0
  132. data/lib/phronomy/agent/persistence/execution_repository.rb +108 -0
  133. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +58 -0
  134. data/lib/phronomy/agent/persistence/journal_repository.rb +54 -0
  135. data/lib/phronomy/agent/persistence/queries.rb +61 -0
  136. data/lib/phronomy/agent/persistence/storage_schema.rb +24 -0
  137. data/lib/phronomy/agent/persistence/watermark.rb +27 -0
  138. data/lib/phronomy/agent/recovery/invocation_restorer.rb +132 -0
  139. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/continuation.rb +21 -45
  140. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/installation.rb +36 -41
  141. data/lib/phronomy/agent/{recovery_coordinator → recovery/recovery_coordinator}/resolution.rb +37 -40
  142. data/lib/phronomy/agent/{recovery_coordinator.rb → recovery/recovery_coordinator.rb} +9 -13
  143. data/lib/phronomy/agent/recovery/recovery_support.rb +227 -0
  144. data/lib/phronomy/agent/selection/candidate.rb +1 -1
  145. data/lib/phronomy/agent/{approval_evaluation_request.rb → tool_execution/approval_evaluation_request.rb} +1 -12
  146. data/lib/phronomy/agent/{tool_approval_request.rb → tool_execution/tool_approval_request.rb} +1 -10
  147. data/lib/phronomy/agent/tool_execution/tool_binding.rb +90 -0
  148. data/lib/phronomy/agent/{tool_call_intercepted.rb → tool_execution/tool_call_intercepted.rb} +2 -2
  149. data/lib/phronomy/agent/{tool_definition_set.rb → tool_execution/tool_definition_set.rb} +9 -4
  150. data/lib/phronomy/agent/{tool_invocation.rb → tool_execution/tool_invocation.rb} +69 -34
  151. data/lib/phronomy/agent/{tool_invocation_session_builder.rb → tool_execution/tool_invocation_session_builder.rb} +2 -2
  152. data/lib/phronomy/common/configuration_error.rb +7 -0
  153. data/lib/phronomy/common/error.rb +5 -0
  154. data/lib/phronomy/{agent → common/values}/immutable.rb +9 -1
  155. data/lib/phronomy/common/values/serializable.rb +32 -0
  156. data/lib/phronomy/{configuration.rb → configuration/configuration.rb} +14 -5
  157. data/lib/phronomy/configuration/global_configuration.rb +26 -0
  158. data/lib/phronomy/content_store/storage_schema.rb +11 -0
  159. data/lib/phronomy/content_store/stored_contents.rb +43 -0
  160. data/lib/phronomy/engine/backpressure_error.rb +7 -0
  161. data/lib/phronomy/{blocking.rb → engine/blocking.rb} +15 -8
  162. data/lib/phronomy/engine/cancellation_error.rb +7 -0
  163. data/lib/phronomy/engine/concurrency/cancellation_token.rb +4 -0
  164. data/lib/phronomy/engine/concurrency/offload_pool.rb +29 -16
  165. data/lib/phronomy/engine/concurrency/operation_binding.rb +43 -0
  166. data/lib/phronomy/engine/concurrency/physical_completion_task.rb +4 -62
  167. data/lib/phronomy/engine/concurrency/result_collector.rb +99 -0
  168. data/lib/phronomy/engine/concurrency/result_composition.rb +145 -0
  169. data/lib/phronomy/engine/concurrency/subscriptions.rb +68 -0
  170. data/lib/phronomy/engine/concurrency/worker_input_restricted.rb +14 -0
  171. data/lib/phronomy/engine/event_loop.rb +160 -626
  172. data/lib/phronomy/engine/event_loop_reentrancy_error.rb +8 -0
  173. data/lib/phronomy/engine/execution.rb +229 -0
  174. data/lib/phronomy/engine/execution_cancellation_error.rb +14 -0
  175. data/lib/phronomy/engine/execution_receiver.rb +65 -0
  176. data/lib/phronomy/engine/execution_timeout_error.rb +14 -0
  177. data/lib/phronomy/engine/fsm_protocol.rb +14 -0
  178. data/lib/phronomy/engine/fsm_session.rb +38 -31
  179. data/lib/phronomy/{invalid_async_entry_action_error.rb → engine/invalid_async_entry_action_error.rb} +3 -1
  180. data/lib/phronomy/{invalid_async_transition_action_error.rb → engine/invalid_async_transition_action_error.rb} +3 -1
  181. data/lib/phronomy/{invalid_async_workflow_action_error.rb → engine/invalid_async_workflow_action_error.rb} +3 -1
  182. data/lib/phronomy/{invocation_context.rb → engine/invocation_context.rb} +13 -1
  183. data/lib/phronomy/engine/pool_shutdown_error.rb +7 -0
  184. data/lib/phronomy/engine/recursion_limit_error.rb +7 -0
  185. data/lib/phronomy/engine/runtime/timer_queue.rb +11 -0
  186. data/lib/phronomy/engine/runtime.rb +80 -84
  187. data/lib/phronomy/engine/runtime_shutdown_error.rb +7 -0
  188. data/lib/phronomy/engine/runtime_shutdown_reentrancy_error.rb +7 -0
  189. data/lib/phronomy/engine/scheduler_reentrancy_error.rb +9 -0
  190. data/lib/phronomy/engine/{task.rb → task_result.rb} +101 -42
  191. data/lib/phronomy/engine/timeout_error.rb +7 -0
  192. data/lib/phronomy/filter/contract/filter_block_error.rb +14 -0
  193. data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +89 -0
  194. data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +57 -0
  195. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +112 -0
  196. data/lib/phronomy/generation/generator_verifier.rb +118 -0
  197. data/lib/phronomy/generation/low_confidence_error.rb +14 -0
  198. data/lib/phronomy/llm_adapter/base.rb +2 -2
  199. data/lib/phronomy/llm_context_window/token_budget.rb +6 -7
  200. data/lib/phronomy/llm_contract/authentication_error.rb +7 -0
  201. data/lib/phronomy/{context_budget_exceeded_error.rb → llm_contract/context_budget_exceeded_error.rb} +2 -0
  202. data/lib/phronomy/llm_contract/context_length_error.rb +7 -0
  203. data/lib/phronomy/llm_contract/rate_limit_error.rb +7 -0
  204. data/lib/phronomy/{token_usage.rb → llm_contract/token_usage.rb} +2 -2
  205. data/lib/phronomy/llm_contract/transport_error.rb +7 -0
  206. data/lib/phronomy/multi_agent/admission_registry.rb +22 -2
  207. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +8 -8
  208. data/lib/phronomy/{agent → multi_agent}/handoff_runner.rb +18 -17
  209. data/lib/phronomy/multi_agent/orchestrator.rb +36 -68
  210. data/lib/phronomy/multi_agent/persistence/codec.rb +55 -0
  211. data/lib/phronomy/multi_agent/persistence/queries.rb +30 -0
  212. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +108 -0
  213. data/lib/phronomy/multi_agent/persistence/team_repository.rb +61 -0
  214. data/lib/phronomy/{agent → multi_agent}/shared_state.rb +56 -39
  215. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +15 -0
  216. data/lib/phronomy/multi_agent/team_coordinator.rb +21 -18
  217. data/lib/phronomy/multi_agent/team_execution.rb +1 -1
  218. data/lib/phronomy/{engine/runtime → multi_agent}/team_ownership_registry.rb +14 -4
  219. data/lib/phronomy/multi_agent/team_root.rb +1 -1
  220. data/lib/phronomy/output_parser/contract/parse_error.rb +7 -0
  221. data/lib/phronomy/persistence/api/persistence.rb +140 -0
  222. data/lib/phronomy/persistence/migration/initial_format_migration.rb +19 -19
  223. data/lib/phronomy/persistence_composition/repositories.rb +77 -0
  224. data/lib/phronomy/persistence_composition/storage_schema.rb +24 -0
  225. data/lib/phronomy/{execution_rehydration_required_error.rb → recovery/execution_rehydration_required_error.rb} +2 -0
  226. data/lib/phronomy/{recovery.rb → recovery/recovery.rb} +1 -1
  227. data/lib/phronomy/runtime_composition/agent_defaults.rb +7 -0
  228. data/lib/phronomy/runtime_composition/configuration_defaults.rb +9 -0
  229. data/lib/phronomy/runtime_composition/global_runtime.rb +19 -0
  230. data/lib/phronomy/storage/backend.rb +101 -0
  231. data/lib/phronomy/storage/backends/in_memory.rb +157 -0
  232. data/lib/phronomy/storage/blob_conflict_error.rb +10 -0
  233. data/lib/phronomy/storage/blobs.rb +31 -0
  234. data/lib/phronomy/storage/condition.rb +25 -0
  235. data/lib/phronomy/storage/condition_failed_error.rb +16 -0
  236. data/lib/phronomy/storage/conflict_error.rb +9 -0
  237. data/lib/phronomy/{persistence → storage}/durable_record.rb +12 -12
  238. data/lib/phronomy/storage/entry.rb +33 -0
  239. data/lib/phronomy/storage/guard_ref.rb +13 -0
  240. data/lib/phronomy/storage/not_found_error.rb +9 -0
  241. data/lib/phronomy/storage/record_codec.rb +177 -0
  242. data/lib/phronomy/storage/records.rb +61 -0
  243. data/lib/phronomy/storage/resource.rb +126 -0
  244. data/lib/phronomy/storage/scope.rb +25 -0
  245. data/lib/phronomy/storage/serialization_error.rb +9 -0
  246. data/lib/phronomy/storage/streams.rb +44 -0
  247. data/lib/phronomy/storage/transaction_error.rb +10 -0
  248. data/lib/phronomy/storage/unique_constraint_error.rb +17 -0
  249. data/lib/phronomy/storage/unsupported_backend_error.rb +9 -0
  250. data/lib/phronomy/storage/validation.rb +53 -0
  251. data/lib/phronomy/storage/view.rb +131 -0
  252. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +5 -3
  253. data/lib/phronomy/testing/fake_clock.rb +13 -9
  254. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +1 -1
  255. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +4 -4
  256. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +10 -7
  257. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +2 -2
  258. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +6 -6
  259. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +6 -6
  260. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +8 -8
  261. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +263 -0
  262. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +123 -0
  263. data/lib/phronomy/testing/persistence_contract.rb +4 -0
  264. data/lib/phronomy/tool/contract/tool_error.rb +7 -0
  265. data/lib/phronomy/tools/agent.rb +6 -6
  266. data/lib/phronomy/vector_store/async_backend.rb +5 -5
  267. data/lib/phronomy/vector_store/embeddings/base.rb +2 -2
  268. data/lib/phronomy/version.rb +1 -1
  269. data/lib/phronomy/{workflow.rb → workflow/execution/workflow.rb} +10 -8
  270. data/lib/phronomy/{workflow_context.rb → workflow/execution/workflow_context.rb} +4 -0
  271. data/lib/phronomy/workflow/execution/workflow_context_ownership_error.rb +7 -0
  272. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +188 -0
  273. data/lib/phronomy/{workflow_runner.rb → workflow/execution/workflow_runner.rb} +126 -75
  274. data/lib/phronomy/workflow/execution/workflow_terminal_policy.rb +40 -0
  275. data/lib/phronomy/workflow/persistence/codec.rb +153 -0
  276. data/lib/phronomy/workflow/persistence/state_repository.rb +57 -0
  277. data/lib/phronomy/workflow/phase_machine_builder.rb +8 -8
  278. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +9 -0
  279. data/lib/phronomy.rb +52 -94
  280. data/scripts/api_snapshot.rb +4 -2
  281. data/scripts/storage_spi_snapshot.rb +36 -0
  282. data/sig/phronomy/agent.rbs +4 -5
  283. data/sig/phronomy/execution_receiver.rbs +34 -0
  284. data/sig/phronomy/extensions.rbs +5 -5
  285. data/sig/phronomy/handoff.rbs +4 -2
  286. data/sig/phronomy/multi_agent.rbs +19 -3
  287. data/sig/phronomy/persistence.rbs +7 -91
  288. data/sig/phronomy/runtime.rbs +34 -7
  289. data/sig/phronomy/storage.rbs +174 -0
  290. data/sig/phronomy/tool.rbs +11 -2
  291. data/sig/phronomy/workflow.rbs +1 -1
  292. metadata +236 -99
  293. data/lib/phronomy/agent/execution_coordinator.rb +0 -3138
  294. data/lib/phronomy/agent/handoff_execution_coordinator.rb +0 -143
  295. data/lib/phronomy/agent/recovery_support.rb +0 -504
  296. data/lib/phronomy/agent/token_budget_resolver.rb +0 -70
  297. data/lib/phronomy/agent/tool_executor.rb +0 -55
  298. data/lib/phronomy/generator_verifier.rb +0 -369
  299. data/lib/phronomy/invalid_context_budget_configuration_error.rb +0 -8
  300. data/lib/phronomy/multi_agent/fan_out_invocation.rb +0 -137
  301. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +0 -118
  302. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +0 -116
  303. data/lib/phronomy/persistence/durable_codec.rb +0 -706
  304. data/lib/phronomy/persistence/in_memory.rb +0 -690
  305. data/lib/phronomy/persistence/repository_facades.rb +0 -535
  306. data/lib/phronomy/persistence.rb +0 -276
  307. data/lib/phronomy/ruby_llm_patches.rb +0 -24
  308. data/lib/phronomy/workflow_recovery.rb +0 -123
  309. /data/lib/phronomy/agent/{context_candidate_resolver.rb → context_assembly/context_candidate_resolver.rb} +0 -0
  310. /data/lib/phronomy/agent/{context_policy_input_builder.rb → context_assembly/context_policy_input_builder.rb} +0 -0
  311. /data/lib/phronomy/agent/{context_plan_validator.rb → context_contract/context_plan_validator.rb} +0 -0
  312. /data/lib/phronomy/agent/{context_policy.rb → context_contract/context_policy.rb} +0 -0
  313. /data/lib/phronomy/agent/{llm_operation_result.rb → execution/llm_operation_result.rb} +0 -0
  314. /data/lib/phronomy/agent/{handoff.rb → handoff/handoff.rb} +0 -0
  315. /data/lib/phronomy/agent/{handoff_capability_factory.rb → handoff/handoff_capability_factory.rb} +0 -0
  316. /data/lib/phronomy/agent/{handoff_policy.rb → handoff/handoff_policy.rb} +0 -0
  317. /data/lib/phronomy/agent/{handoff_projection.rb → handoff/handoff_projection.rb} +0 -0
  318. /data/lib/phronomy/agent/{handoff_request.rb → handoff/handoff_request.rb} +0 -0
  319. /data/lib/phronomy/{canonical_json.rb → common/canonical_json.rb} +0 -0
  320. /data/lib/phronomy/{diagnostics.rb → engine/diagnostics.rb} +0 -0
  321. /data/lib/phronomy/{event.rb → engine/event.rb} +0 -0
  322. /data/lib/phronomy/{metrics.rb → engine/metrics.rb} +0 -0
  323. /data/lib/phronomy/{runnable.rb → engine/runnable.rb} +0 -0
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- # Resolves a TokenBudget from the effective model configuration that will
6
- # actually be materialized for one LLM call.
7
- class TokenBudgetResolver
8
- def initialize(agent:)
9
- @agent = agent
10
- end
11
-
12
- def resolve(model_config)
13
- config = stringify_keys(model_config)
14
- model_name = config["model"]
15
- explicit_window = integer_or_nil(config["context_window"])
16
- explicit_reserve = integer_or_nil(config["max_output_tokens"])
17
-
18
- if explicit_window
19
- reserve = explicit_reserve || configured_default_reserve
20
- return build_budget(explicit_window, reserve, model_name)
21
- end
22
-
23
- return nil unless model_name
24
-
25
- model = RubyLLM.models.find(model_name)
26
- return nil unless model
27
-
28
- context_window = model.context_window.to_i
29
- registry_max = model.max_output_tokens.to_i
30
- reserve = explicit_reserve || configured_default_reserve
31
- reserve ||= registry_max if registry_max.positive? && registry_max < context_window
32
- build_budget(context_window, reserve, model_name)
33
- rescue RubyLLM::ModelNotFoundError
34
- nil
35
- end
36
-
37
- private
38
-
39
- def build_budget(context_window, reserve, model_name)
40
- unless reserve&.positive? && reserve < context_window
41
- raise Phronomy::InvalidContextBudgetConfigurationError,
42
- "Cannot determine a valid output reserve for model #{model_name.inspect}; " \
43
- "set max_output_tokens or Phronomy.configuration.default_output_reserve"
44
- end
45
-
46
- # Mandatory Manifest content is accounted separately by ContextAssembler
47
- # and the final budget validator. TokenBudget reserves output capacity only.
48
- Phronomy::LlmContextWindow::TokenBudget.new(
49
- context_window: context_window,
50
- max_output_tokens: reserve
51
- )
52
- end
53
-
54
- def configured_default_reserve
55
- integer_or_nil(Phronomy.configuration.default_output_reserve)
56
- end
57
-
58
- def integer_or_nil(value)
59
- return nil if value.nil?
60
- Integer(value)
61
- end
62
-
63
- def stringify_keys(hash)
64
- hash.to_h.each_with_object({}) do |(key, value), result|
65
- result[key.to_s] = value
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- # Routes Tool work according to the Tool execution contract.
6
- #
7
- # Both execution modes return {Phronomy::Task}; only the execution mechanism
8
- # differs.
9
- #
10
- # :cooperative Tool calls execute inline and must return quickly. call_async
11
- # wraps their result in an already-settled Task and never consumes an
12
- # OffloadPool worker.
13
- #
14
- # :offloaded Tool calls route synchronous work through OffloadPool, whose
15
- # caller-facing completion handle is also a Task. Phronomy does not distinguish
16
- # whether the reason is blocking I/O, CPU-bound work, or another long
17
- # synchronous operation.
18
- module ToolExecutor
19
- def self.call_async(
20
- tool:,
21
- args:,
22
- cancellation_token: nil,
23
- config: {},
24
- runtime: nil,
25
- on_full: :raise
26
- )
27
- mode = tool.class.execution_mode
28
-
29
- case mode
30
- when :cooperative
31
- task = Phronomy::Task.deferred(name: "tool-#{tool.name}")
32
- begin
33
- task.complete(
34
- tool.call(args, cancellation_token: cancellation_token)
35
- )
36
- rescue => error
37
- task.fail(error)
38
- end
39
- task
40
- when :offloaded
41
- runtime ||= Phronomy::Runtime.instance
42
- runtime.offload.submit(
43
- cancellation_token: cancellation_token,
44
- on_full: on_full
45
- ) do
46
- tool.call(args, cancellation_token: cancellation_token)
47
- end
48
- else
49
- raise Phronomy::ConfigurationError,
50
- "unknown Tool execution_mode: #{mode.inspect}"
51
- end
52
- end
53
- end
54
- end
55
- end
@@ -1,369 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
-
5
- module Phronomy
6
- # Implements the Generator-Verifier multi-agent coordination pattern.
7
- #
8
- # Agent completion is integrated through application-defined Workflow events;
9
- # Workflow entry actions do not return or await Agent Tasks.
10
- class GeneratorVerifier
11
- DEFAULT_CONFIDENCE_THRESHOLD = 0.7
12
- DEFAULT_MAX_ITERATIONS = 3
13
-
14
- Result = Struct.new(
15
- :output,
16
- :confidence,
17
- :citations,
18
- :iterations,
19
- :review_notes,
20
- :trusted
21
- ) do
22
- alias_method :trusted?, :trusted
23
- end
24
-
25
- class PipelineState
26
- include Phronomy::WorkflowContext
27
-
28
- field :input, type: :replace, default: -> { "" }
29
- field :draft, type: :replace, default: -> { {} }
30
- field :self_score, type: :replace, default: -> { 0.0 }
31
- field :review_score, type: :replace, default: -> { 0.0 }
32
- field :citations, type: :replace, default: -> { [] }
33
- field :review_notes, type: :append, default: -> { [] }
34
- field :iteration, type: :replace, default: -> { 0 }
35
- field :approved, type: :replace, default: -> { false }
36
- field :output, type: :replace, default: -> { {} }
37
- field :draft_request_id, type: :replace, default: nil
38
- field :review_request_id, type: :replace, default: nil
39
- field :pipeline_error, type: :replace, default: nil
40
-
41
- # Application-level event interpretation. Correlation IDs belong to this
42
- # Pipeline rather than to the generic FSMSession.
43
- def handle_fsm_event(event)
44
- case event.type
45
- when :draft_completed
46
- return :consume unless event.payload[:request_id] == draft_request_id
47
-
48
- self.draft = event.payload[:draft]
49
- self.self_score = event.payload[:self_score]
50
- self.citations = event.payload[:citations]
51
- self.iteration = iteration + 1
52
- self.draft_request_id = nil
53
- self.pipeline_error = nil
54
- when :review_completed
55
- return :consume unless event.payload[:request_id] == review_request_id
56
-
57
- self.review_score = event.payload[:review_score]
58
- self.approved = event.payload[:approved]
59
- self.review_notes = review_notes + [event.payload[:feedback]]
60
- self.review_request_id = nil
61
- self.pipeline_error = nil
62
- when :draft_failed
63
- return :consume unless event.payload[:request_id] == draft_request_id
64
-
65
- self.pipeline_error = event.payload[:error]
66
- self.draft_request_id = nil
67
- when :review_failed
68
- return :consume unless event.payload[:request_id] == review_request_id
69
-
70
- self.pipeline_error = event.payload[:error]
71
- self.review_request_id = nil
72
- end
73
- false
74
- end
75
- end
76
-
77
- private_constant :PipelineState
78
-
79
- def initialize(
80
- draft_agent:,
81
- review_agent:,
82
- draft_prompt_builder:,
83
- review_prompt_builder:,
84
- draft_result_parser: nil,
85
- review_result_parser: nil,
86
- confidence_threshold: DEFAULT_CONFIDENCE_THRESHOLD,
87
- max_iterations: DEFAULT_MAX_ITERATIONS,
88
- raise_if_untrusted: false
89
- )
90
- @draft_agent_class = draft_agent
91
- @review_agent_class = review_agent
92
- @draft_prompt_builder = draft_prompt_builder
93
- @review_prompt_builder = review_prompt_builder
94
- @draft_result_parser =
95
- draft_result_parser || method(:default_parse_draft)
96
- @review_result_parser =
97
- review_result_parser || method(:default_parse_review)
98
- @threshold = confidence_threshold.to_f
99
- @max_iterations = max_iterations.to_i
100
- @raise_if_untrusted = raise_if_untrusted
101
- @compiled_workflow = nil
102
- end
103
-
104
- def invoke(input, config: {})
105
- state = compiled_workflow.invoke({input: input}, config: config)
106
- confidence = combined_confidence(state)
107
- trusted = confidence >= @threshold
108
- result = Result.new(
109
- output: state.output || state.draft.to_s,
110
- confidence: confidence,
111
- citations: state.citations,
112
- iterations: state.iteration,
113
- review_notes: state.review_notes,
114
- trusted: trusted
115
- )
116
- if @raise_if_untrusted && !trusted
117
- raise LowConfidenceError.new(result)
118
- end
119
- result
120
- end
121
-
122
- private
123
-
124
- def combined_confidence(state)
125
- [
126
- (state.self_score || 0.0).to_f,
127
- (state.review_score || 0.0).to_f
128
- ].min
129
- end
130
-
131
- def compiled_workflow
132
- @compiled_workflow ||= build_workflow
133
- end
134
-
135
- def build_workflow
136
- draft_agent = @draft_agent_class.new
137
- review_agent = @review_agent_class.new
138
- threshold = @threshold
139
- max_iterations = @max_iterations
140
- draft_prompt_builder = @draft_prompt_builder
141
- review_prompt_builder = @review_prompt_builder
142
- draft_result_parser = @draft_result_parser
143
- review_result_parser = @review_result_parser
144
- pipeline = self
145
- workflow = nil
146
-
147
- # workflow is assigned after define so closures captured by reference see the result.
148
- workflow = Phronomy::Workflow.define(PipelineState) do
149
- initial :draft
150
-
151
- state :draft
152
- state :review
153
- state :finalize
154
- state :failed
155
-
156
- entry :draft, ->(state) {
157
- request_id = SecureRandom.uuid
158
- next_state = state.merge(draft_request_id: request_id)
159
- feedback = next_state.review_notes.last
160
- prompt = draft_prompt_builder.call(next_state.input, feedback)
161
-
162
- draft_agent.send(:__invoke_async_with_event_sink,
163
- prompt,
164
- on_event: ->(agent_event) {
165
- case agent_event.type
166
- when :done
167
- begin
168
- parsed = draft_result_parser.call(
169
- agent_event.payload[:output]
170
- )
171
- workflow.signal(
172
- workflow_instance_id: next_state.workflow_instance_id,
173
- event: :draft_completed,
174
- payload: {
175
- request_id: request_id,
176
- draft: parsed[:answer].to_s,
177
- self_score: pipeline.__send__(
178
- :clamp,
179
- parsed[:confidence]
180
- ),
181
- citations: pipeline.__send__(
182
- :normalize_citations,
183
- parsed[:citations]
184
- )
185
- }
186
- )
187
- rescue => error
188
- workflow.signal(
189
- workflow_instance_id: next_state.workflow_instance_id,
190
- event: :draft_failed,
191
- payload: {
192
- request_id: request_id,
193
- error: error
194
- }
195
- )
196
- end
197
- when :error, :timeout, :cancelled
198
- workflow.signal(
199
- workflow_instance_id: next_state.workflow_instance_id,
200
- event: :draft_failed,
201
- payload: {
202
- request_id: request_id,
203
- error:
204
- agent_event.payload[:error] ||
205
- Phronomy::Error.new(
206
- "Draft Agent ended with #{agent_event.type}"
207
- )
208
- }
209
- )
210
- when :approval_required
211
- workflow.signal(
212
- workflow_instance_id: next_state.workflow_instance_id,
213
- event: :draft_failed,
214
- payload: {
215
- request_id: request_id,
216
- error: Phronomy::Error.new(
217
- "GeneratorVerifier draft Agent suspended for approval"
218
- )
219
- }
220
- )
221
- end
222
- })
223
-
224
- next_state
225
- }
226
-
227
- entry :review, ->(state) {
228
- request_id = SecureRandom.uuid
229
- next_state = state.merge(review_request_id: request_id)
230
- prompt = review_prompt_builder.call(
231
- next_state.input,
232
- next_state.draft,
233
- next_state.citations
234
- )
235
-
236
- review_agent.send(:__invoke_async_with_event_sink,
237
- prompt,
238
- on_event: ->(agent_event) {
239
- case agent_event.type
240
- when :done
241
- begin
242
- parsed = review_result_parser.call(
243
- agent_event.payload[:output]
244
- )
245
- workflow.signal(
246
- workflow_instance_id: next_state.workflow_instance_id,
247
- event: :review_completed,
248
- payload: {
249
- request_id: request_id,
250
- review_score: pipeline.__send__(
251
- :clamp,
252
- parsed[:score]
253
- ),
254
- approved: parsed[:approved] == true,
255
- feedback: parsed[:feedback].to_s
256
- }
257
- )
258
- rescue => error
259
- workflow.signal(
260
- workflow_instance_id: next_state.workflow_instance_id,
261
- event: :review_failed,
262
- payload: {
263
- request_id: request_id,
264
- error: error
265
- }
266
- )
267
- end
268
- when :error, :timeout, :cancelled
269
- workflow.signal(
270
- workflow_instance_id: next_state.workflow_instance_id,
271
- event: :review_failed,
272
- payload: {
273
- request_id: request_id,
274
- error:
275
- agent_event.payload[:error] ||
276
- Phronomy::Error.new(
277
- "Review Agent ended with #{agent_event.type}"
278
- )
279
- }
280
- )
281
- when :approval_required
282
- workflow.signal(
283
- workflow_instance_id: next_state.workflow_instance_id,
284
- event: :review_failed,
285
- payload: {
286
- request_id: request_id,
287
- error: Phronomy::Error.new(
288
- "GeneratorVerifier review Agent suspended for approval"
289
- )
290
- }
291
- )
292
- end
293
- })
294
-
295
- next_state
296
- }
297
-
298
- entry :finalize, ->(state) {
299
- state.output = state.draft
300
- state
301
- }
302
-
303
- entry :failed, ->(state) {
304
- raise(
305
- state.pipeline_error ||
306
- Phronomy::Error.new("GeneratorVerifier Agent operation failed")
307
- )
308
- }
309
-
310
- transition from: :draft, on: :draft_completed, to: :review
311
- transition from: :draft, on: :draft_failed, to: :failed
312
-
313
- transition from: :review,
314
- on: :review_completed,
315
- guard: ->(state) {
316
- confidence = [
317
- state.self_score || 0.0,
318
- state.review_score || 0.0
319
- ].min
320
- (confidence >= threshold && state.approved) ||
321
- state.iteration >= max_iterations
322
- },
323
- to: :finalize
324
- transition from: :review,
325
- on: :review_completed,
326
- to: :draft
327
- transition from: :review,
328
- on: :review_failed,
329
- to: :failed
330
-
331
- transition from: :finalize, to: :__finish__
332
- end
333
- end
334
-
335
- def default_parse_draft(text)
336
- json_parser.parse(text)
337
- rescue Phronomy::ParseError
338
- {
339
- answer: text.to_s,
340
- confidence: 0.0,
341
- citations: []
342
- }
343
- end
344
-
345
- def default_parse_review(text)
346
- json_parser.parse(text)
347
- rescue Phronomy::ParseError
348
- {
349
- approved: false,
350
- score: 0.0,
351
- feedback: "Review output could not be parsed: #{text}"
352
- }
353
- end
354
-
355
- def json_parser
356
- @json_parser ||= Phronomy::OutputParser::JsonParser.new
357
- end
358
-
359
- def clamp(value)
360
- value.to_f.clamp(0.0, 1.0)
361
- end
362
-
363
- def normalize_citations(raw)
364
- Array(raw).filter_map do |citation|
365
- citation.is_a?(Hash) ? citation.transform_keys(&:to_sym) : nil
366
- end
367
- end
368
- end
369
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- # Raised when output token reserve cannot be determined at budget construction
5
- # time (e.g. no explicit max_output_tokens set and registry value equals the
6
- # context window).
7
- class InvalidContextBudgetConfigurationError < Error; end
8
- end
@@ -1,137 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module MultiAgent
5
- # Mutable FSM context for one fan-out operation.
6
- class FanOutInvocation
7
- Child = Data.define(:index, :agent, :input, :config)
8
-
9
- attr_reader :phase, :results
10
-
11
- def initialize(children:, max_concurrency:, on_error:)
12
- @phase = nil
13
- @children = children
14
- @max_concurrency = max_concurrency || children.length
15
- @on_error = on_error
16
- @pending = children.dup
17
- @active = {}
18
- @results = Array.new(children.length)
19
- @child_errors = Array.new(children.length)
20
- @fatal_error = nil
21
- @cancelled = false
22
- @timed_out = false
23
- end
24
-
25
- def set_graph_metadata(phase: nil)
26
- @phase = phase
27
- end
28
-
29
- def handle_fsm_event(event)
30
- case event.type
31
- when :child_completed
32
- payload = event.payload
33
- index = payload.fetch(:index)
34
- @active.delete(index)
35
- child_error = payload[:error]
36
- if child_error
37
- if @on_error == :raise
38
- @child_errors[index] = child_error
39
- cancel_active_children! unless @child_errors.any?(&:itself)
40
- end
41
- else
42
- @results[index] = payload[:result]
43
- end
44
- true
45
- when :driver_failed
46
- @fatal_error ||= event.payload.fetch(:error)
47
- cancel_active_children!
48
- true
49
- when :timeout
50
- @timed_out = true
51
- @fatal_error ||= Phronomy::TimeoutError.new(event.payload.fetch(:message))
52
- cancel_active_children!
53
- true
54
- when :cancel
55
- @cancelled = true
56
- @fatal_error ||= Phronomy::CancellationError.new("fan-out cancelled")
57
- cancel_active_children!
58
- true
59
- else
60
- false
61
- end
62
- end
63
-
64
- def start_available!(runtime, event_sink:)
65
- return self if @fatal_error
66
-
67
- while @active.length < @max_concurrency && (child = @pending.shift)
68
- child_config = build_child_config(child.config)
69
- handle = child.agent.invoke_async(
70
- child.input,
71
- config: child_config
72
- )
73
- @active[child.index] = {
74
- handle: handle,
75
- token: child_config[:cancellation_token]
76
- }
77
- [child.index].each do |captured_index|
78
- handle.on_complete do |result, error|
79
- event_sink.post(
80
- :child_completed,
81
- {
82
- index: captured_index,
83
- result: result,
84
- error: error
85
- }
86
- )
87
- end
88
- end
89
- end
90
- self
91
- rescue => caught
92
- @fatal_error ||= caught
93
- cancel_active_children!
94
- event_sink.post(:driver_failed, {error: caught})
95
- self
96
- end
97
-
98
- def completed?
99
- !failed? && @pending.empty? && @active.empty?
100
- end
101
-
102
- def failed?
103
- return true if @fatal_error
104
-
105
- @on_error == :raise &&
106
- @pending.empty? &&
107
- @active.empty? &&
108
- @child_errors.any?(&:itself)
109
- end
110
-
111
- def error
112
- @fatal_error || @child_errors.find(&:itself)
113
- end
114
-
115
- def timed_out?
116
- @timed_out
117
- end
118
-
119
- def cancelled?
120
- @cancelled
121
- end
122
-
123
- private
124
-
125
- def build_child_config(config)
126
- config.dup
127
- end
128
-
129
- def cancel_active_children!
130
- @pending.clear
131
- @active.each_value do |entry|
132
- entry[:token]&.cancel!
133
- end
134
- end
135
- end
136
- end
137
- end