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
@@ -1,143 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest"
4
-
5
- module Phronomy
6
- module Agent
7
- # Handoff-aware specialization of the normal Agent execution coordinator.
8
- # It changes only the durable terminal semantics of an invocation that
9
- # produced a typed HandoffRequest. EventLoop ownership/apply remains entirely
10
- # in Agent::ExecutionCoordinator.
11
- class HandoffExecutionCoordinator < Phronomy::Agent::ExecutionCoordinator
12
- private
13
-
14
- def compute_terminal(operation)
15
- view = operation.terminal_view
16
- if view.callback_failure
17
- return commit_failed_outcome(
18
- operation,
19
- view.callback_failure.to_stream_callback_error
20
- )
21
- end
22
- error = view.source_error || view.block_error || view.invocation_error
23
- raise error if error.is_a?(Phronomy::ExecutionRehydrationRequiredError)
24
- return commit_failed_outcome(operation, error) if error
25
- return commit_suspended(operation) if view.phase == :suspended
26
-
27
- raise view.block_error if view.input_blocked || view.output_blocked
28
- raise view.invocation_error if view.invocation_error
29
-
30
- return commit_handed_off(operation) if view.handoff
31
-
32
- commit_completed(operation)
33
- rescue => caught
34
- reconcile_terminal_error(operation, caught)
35
- end
36
-
37
- def commit_handed_off(operation)
38
- current = operation.execution
39
- root = operation.root
40
- runtime_snapshot = operation.runtime_snapshot
41
- request = operation.terminal_view.handoff
42
- handed_off = next_root = appended = nil
43
-
44
- @agent.persistence.transaction do |tx|
45
- coordination = current.metadata.fetch("coordination")
46
- main_id = coordination.fetch("main_agent_id")
47
- routing = tx.handoff_states.load(main_id)
48
- unless routing && routing.active_agent_id == @agent.agent_id && routing.handoff_revision == coordination.fetch("handoff_revision")
49
- raise Phronomy::Persistence::ConflictError, "Handoff routing changed before Source transfer"
50
- end
51
- if Array(routing.metadata["cancelled_execution_ids"]).include?(current.execution_id)
52
- raise Phronomy::CancellationError, "Handoff Source turn was cancelled"
53
- end
54
- manifest = RecoverySupport.manifest_from_ref(@agent, current.metadata.fetch("manifest_ref"))
55
- context = HandoffProjection.new.build_terminal(view: request, manifest: manifest,
56
- persistence: tx, source_agent_id: @agent.agent_id)
57
- context_ref = tx.contents.put_json(context.to_h)
58
- target_id = "handoff-target-#{Digest::SHA256.hexdigest([current.execution_id, request.target_agent_id].join("\0"))}"
59
- target_root = tx.agents.load(request.target_agent_id)
60
- target_definition = {"id" => target_root.agent_definition_id, "version" => target_root.agent_definition_version}
61
- transfer = routing.with(active_agent_id: request.target_agent_id,
62
- active_handoff_context_ref: context_ref, phase: "target_pending",
63
- pending_source_execution_id: current.execution_id, pending_target_execution_id: target_id,
64
- metadata: routing.metadata.merge("target_definition" => target_definition))
65
- tx.handoff_states.save(main_id, expected_revision: routing.handoff_revision, state: transfer)
66
- encoded_records, call_records = encode_runtime_records(
67
- current,
68
- tx: tx,
69
- snapshot: runtime_snapshot,
70
- context_candidate: false,
71
- agent_root: root
72
- )
73
- audit_ref = tx.contents.put_json(
74
- "target_agent_id" => request.target_agent_id,
75
- "responsibility" => request.responsibility,
76
- "selection_intent" => request.selection_intent.to_h do |category, included|
77
- [category.to_s, included]
78
- end
79
- )
80
- audit_record = Phronomy::Agent::JournalRecord.new(
81
- agent_id: @agent.agent_id,
82
- execution_id: current.execution_id,
83
- llm_call_id: request.llm_call_id,
84
- kind: :execution_handed_off,
85
- channel: :audit,
86
- content_ref: audit_ref,
87
- context_generation: root.transcript_generation,
88
- context_candidate: false,
89
- metadata: {
90
- "target_agent_id" => request.target_agent_id,
91
- "handoff_tool_call_id" => request.tool_call_id
92
- }.compact
93
- )
94
-
95
- all_records = current.working_records + encoded_records + [audit_record]
96
- appended = tx.journals.append(
97
- root.agent_id,
98
- expected_position: root.journal_position,
99
- records: all_records
100
- )
101
- handed_off = current.with(
102
- status: :handed_off,
103
- phase: :handed_off,
104
- working_records: [],
105
- llm_calls: current.llm_calls + call_records,
106
- approval_request: nil,
107
- terminal_reason: "handed_off",
108
- metadata: current.metadata.merge("handoff_target_agent_id" => request.target_agent_id,
109
- "handoff_target_execution_id" => target_id, "handoff_context_ref" => context_ref)
110
- )
111
- tx.executions.save(
112
- current.execution_id,
113
- expected_revision: current.execution_revision,
114
- execution: handed_off
115
- )
116
- context_changed = appended.any?(&:context_candidate)
117
- next_root = root.with(
118
- agent_revision: root.agent_revision + 1,
119
- context_revision: root.context_revision + (context_changed ? 1 : 0),
120
- journal_position: root.journal_position + appended.length,
121
- lifecycle_status: :idle
122
- )
123
- tx.agents.save(
124
- root.agent_id,
125
- expected_revision: root.agent_revision,
126
- root: next_root
127
- )
128
- end
129
-
130
- result = result_base(handed_off, next_root)
131
- TerminalOutcome.new(
132
- type: :handed_off,
133
- execution: handed_off,
134
- root: next_root,
135
- appended_records: Array(appended).freeze,
136
- result: result.freeze,
137
- error: nil,
138
- approval_request: nil
139
- )
140
- end
141
- end
142
- end
143
- end
@@ -1,504 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest"
4
-
5
- module Phronomy
6
- module Agent
7
- module RecoverySupport
8
- CONTRACT_VERSION = 1
9
- RECOVERY_METADATA_KEY = "recovery"
10
- TOOL_BATCH_METADATA_KEY = "recovery_tool_batch"
11
- PENDING_LLM_ID_KEY = "pending_llm_call_id"
12
- PENDING_LLM_STARTED_AT_KEY = "pending_llm_started_at"
13
- INVOCATION_MODE_KEY = "invocation_mode"
14
- CONTRACT_VERSION_KEY = "recovery_contract_version"
15
-
16
- module_function
17
-
18
- def canonical_copy(value)
19
- case value
20
- when Hash
21
- value.to_h { |key, child| [key.to_s, canonical_copy(child)] }
22
- when Array
23
- value.map { |child| canonical_copy(child) }
24
- when Symbol
25
- value.to_s
26
- when String, Integer, Float, TrueClass, FalseClass, NilClass
27
- value
28
- else
29
- if value.respond_to?(:to_h)
30
- canonical_copy(value.to_h)
31
- else
32
- raise ArgumentError,
33
- "Recovery value is not canonically serializable: #{value.class}"
34
- end
35
- end
36
- end
37
-
38
- def build_tool_batch_snapshot(invocation)
39
- Array(invocation.tool_invocations).map do |child|
40
- entry = {
41
- "tool_invocation_id" => child.id.to_s,
42
- "tool_call_id" => child.tool_call_id&.to_s,
43
- "tool_name" => child.tool_name.to_s,
44
- "llm_call_id" => invocation.tool_batch_llm_call_id&.to_s,
45
- "raw_arguments" => canonical_copy(child.raw_arguments || {}),
46
- "arguments" => canonical_copy(child.raw_arguments || {}),
47
- "status" => child.status.to_s
48
- }
49
- if child.execution_completed?
50
- entry["result"] = canonical_copy(child.result)
51
- end
52
- entry.compact
53
- end.freeze
54
- end
55
-
56
- def with_recovery_metadata(execution, values)
57
- execution.with(
58
- execution_revision: execution.execution_revision,
59
- metadata: execution.metadata.merge(values)
60
- )
61
- end
62
-
63
- def semantic_tool_id(execution_id:, llm_call_id:, tool_call_id:, tool_name:)
64
- source = [
65
- "tool_invocation",
66
- execution_id.to_s,
67
- llm_call_id.to_s,
68
- tool_call_id.to_s,
69
- tool_name.to_s
70
- ].join("\0")
71
- "tool_invocation-#{Digest::SHA256.hexdigest(source)}".freeze
72
- end
73
-
74
- def manifest_from_ref(agent, ref)
75
- raw = agent.persistence.contents.fetch_json(ref)
76
- Phronomy::Agent::LLMInputManifest.from_h(raw)
77
- end
78
-
79
- def materialize_projection(agent, manifest_ref)
80
- manifest = manifest_from_ref(agent, manifest_ref)
81
- projection = Phronomy::Agent::RubyLLMMaterializer.new(
82
- agent: agent,
83
- persistence: agent.persistence
84
- ).materialize(manifest: manifest, manifest_ref: manifest_ref)
85
- [manifest, projection]
86
- end
87
-
88
- def tool_calls_from_outcome(outcome)
89
- Array(outcome.tool_calls)
90
- end
91
-
92
- def normalize_provider_outcome(value)
93
- return value if value.is_a?(Phronomy::Agent::ProviderCallOutcome)
94
-
95
- if value.is_a?(Hash)
96
- return Phronomy::Agent::ProviderCallOutcome.from_h(value)
97
- end
98
-
99
- captured = Phronomy::Agent::ProviderCallOutcome.capture(value)
100
- return captured if captured
101
-
102
- raise ArgumentError,
103
- "LLM Recovery :succeeded requires a Provider result or ProviderCallOutcome-compatible Hash"
104
- end
105
-
106
- def resolution_failure(error)
107
- {
108
- "class" => error.class.name.to_s,
109
- "message" => error.message.to_s
110
- }.freeze
111
- end
112
-
113
- def error_from_failure(failure)
114
- hash = failure.to_h { |key, value| [key.to_s, value] }
115
- Phronomy::Error.new(
116
- "#{hash.fetch("class", "Error")}: #{hash.fetch("message", "Recovery-resolved failure")}"
117
- )
118
- end
119
-
120
- def event_payload(execution, descriptor)
121
- {
122
- execution_id: execution.execution_id,
123
- execution_revision: execution.execution_revision,
124
- reason: descriptor.fetch(:reason),
125
- subject: Phronomy::Recovery.normalize_subject(
126
- descriptor.fetch(:subject)
127
- ),
128
- allowed_outcomes: Array(
129
- descriptor.fetch(:allowed_outcomes)
130
- ).map(&:to_sym).freeze,
131
- facts: Phronomy::Agent::Immutable.copy(
132
- descriptor.fetch(:facts, {})
133
- )
134
- }.freeze
135
- end
136
-
137
- def pending_llm_descriptor(execution)
138
- llm_call_id = execution.metadata[PENDING_LLM_ID_KEY]
139
- return unless llm_call_id
140
-
141
- {
142
- reason: :outcome_unknown,
143
- subject: {
144
- type: :llm_call,
145
- llm_call_id: llm_call_id
146
- },
147
- allowed_outcomes: Phronomy::Recovery::OUTCOMES,
148
- facts: {
149
- manifest_ref: execution.metadata["manifest_ref"],
150
- call_sequence: execution.llm_calls.length + 1
151
- }.compact.freeze
152
- }.freeze
153
- end
154
-
155
- def recovery_hash(execution)
156
- value = execution.metadata[RECOVERY_METADATA_KEY]
157
- value.is_a?(Hash) ? value : nil
158
- end
159
-
160
- def current_tool_descriptor(execution)
161
- recovery = recovery_hash(execution)
162
- return unless recovery
163
-
164
- subjects = Array(recovery["subjects"] || recovery[:subjects])
165
- current = subjects.find do |entry|
166
- hash = entry.to_h { |key, value| [key.to_s, value] }
167
- hash.fetch("state", "unresolved") == "unresolved"
168
- end
169
- return unless current
170
-
171
- hash = current.to_h { |key, value| [key.to_s, value] }
172
- {
173
- reason: (recovery["reason"] || recovery[:reason] || "outcome_unknown").to_sym,
174
- subject: {
175
- type: :tool_invocation,
176
- tool_invocation_id: hash.fetch("tool_invocation_id")
177
- },
178
- allowed_outcomes: Array(
179
- recovery["allowed_outcomes"] ||
180
- recovery[:allowed_outcomes] ||
181
- Phronomy::Recovery::OUTCOMES
182
- ).map(&:to_sym),
183
- facts: {
184
- tool_call_id: hash["tool_call_id"],
185
- tool_name: hash["tool_name"],
186
- arguments: hash["arguments"],
187
- llm_call_id: hash["llm_call_id"]
188
- }.compact
189
- }.freeze
190
- end
191
-
192
- def pending_tool_subjects(execution)
193
- Array(execution.metadata[TOOL_BATCH_METADATA_KEY]).filter_map do |entry|
194
- hash = entry.to_h { |key, value| [key.to_s, value] }
195
- next unless %w[authorized awaiting_approval].include?(
196
- hash.fetch("status")
197
- )
198
-
199
- {
200
- "tool_invocation_id" => hash.fetch("tool_invocation_id"),
201
- "llm_call_id" => hash["llm_call_id"],
202
- "tool_call_id" => hash.fetch("tool_call_id"),
203
- "tool_name" => hash.fetch("tool_name"),
204
- "arguments" => canonical_copy(
205
- hash["arguments"] || hash["raw_arguments"] || {}
206
- ),
207
- "state" => "unresolved"
208
- }.compact.freeze
209
- end.freeze
210
- end
211
-
212
- def pending_tool_descriptor(execution)
213
- subjects = pending_tool_subjects(execution)
214
- return if subjects.empty?
215
-
216
- first = subjects.first
217
- {
218
- reason: :outcome_unknown,
219
- subject: {
220
- type: :tool_invocation,
221
- tool_invocation_id: first.fetch("tool_invocation_id")
222
- },
223
- allowed_outcomes: Phronomy::Recovery::OUTCOMES,
224
- facts: {
225
- tool_call_id: first["tool_call_id"],
226
- tool_name: first["tool_name"],
227
- arguments: first["arguments"],
228
- llm_call_id: first["llm_call_id"]
229
- }.compact
230
- }.freeze
231
- end
232
-
233
- def recovery_descriptor(execution)
234
- case execution.phase.to_sym
235
- when :calling_llm
236
- pending_llm_descriptor(execution)
237
- when :resuming
238
- approved = execution.approval_request &&
239
- (
240
- execution.approval_request["approved"] ||
241
- execution.approval_request[:approved]
242
- )
243
- approved ? pending_tool_descriptor(execution) : nil
244
- when :dispatching_tools
245
- pending_tool_descriptor(execution)
246
- when :recovery_tools
247
- current_tool_descriptor(execution)
248
- end
249
- end
250
-
251
- def build_tool_subjects(execution, llm_call_id, outcome)
252
- tool_calls_from_outcome(outcome).map do |call|
253
- call_hash = canonical_copy(call)
254
- tool_call_id = call_hash.fetch("id").to_s
255
- tool_name = call_hash.fetch("name").to_s
256
- {
257
- "tool_invocation_id" => semantic_tool_id(
258
- execution_id: execution.execution_id,
259
- llm_call_id: llm_call_id,
260
- tool_call_id: tool_call_id,
261
- tool_name: tool_name
262
- ),
263
- "llm_call_id" => llm_call_id.to_s,
264
- "tool_call_id" => tool_call_id,
265
- "tool_name" => tool_name,
266
- "arguments" => canonical_copy(call_hash.fetch("arguments", {})),
267
- "state" => "unresolved"
268
- }.freeze
269
- end.freeze
270
- end
271
-
272
- def build_recovery_hash(subjects, reason: :outcome_unknown, allowed_outcomes: Phronomy::Recovery::OUTCOMES)
273
- {
274
- "version" => CONTRACT_VERSION,
275
- "reason" => reason.to_s,
276
- "allowed_outcomes" => Array(allowed_outcomes).map(&:to_s),
277
- "subjects" => Array(subjects).map { |entry| canonical_copy(entry) }
278
- }.freeze
279
- end
280
-
281
- def update_recovery_subject(recovery, tool_invocation_id:, state:, outcome:, result_ref: nil)
282
- copy = canonical_copy(recovery)
283
- copy["subjects"] = Array(copy.fetch("subjects")).map do |entry|
284
- next entry unless entry.fetch("tool_invocation_id").to_s == tool_invocation_id.to_s
285
-
286
- entry.merge(
287
- "state" => state.to_s,
288
- "outcome" => outcome.to_s,
289
- "result_ref" => result_ref
290
- ).compact
291
- end
292
- copy.freeze
293
- end
294
-
295
- def unresolved_subjects(recovery)
296
- Array(recovery.fetch("subjects")).select do |entry|
297
- entry.fetch("state", "unresolved") == "unresolved"
298
- end
299
- end
300
-
301
- def latest_assistant_record(execution, llm_call_id: nil)
302
- Array(execution.working_records).reverse.find do |record|
303
- record.kind.to_sym == :assistant_message &&
304
- (llm_call_id.nil? || record.llm_call_id.to_s == llm_call_id.to_s)
305
- end
306
- end
307
-
308
- def build_invocation_for_suspended(agent, execution, projection, main_coordinator, listener, assistant_message:)
309
- request = execution.approval_request && Phronomy::Agent::ToolApprovalRequest.from_h(
310
- execution.approval_request
311
- )
312
- assistant_record = latest_assistant_record(execution)
313
- unless assistant_record
314
- raise Phronomy::ExecutionRehydrationRequiredError,
315
- "suspended execution #{execution.execution_id} has no durable assistant Tool Call message"
316
- end
317
-
318
- config = {
319
- execution_id: execution.execution_id,
320
- phronomy_execution_coordinator: main_coordinator,
321
- phronomy_runtime_projection: projection
322
- }.merge(agent.__coordination_config)
323
- config = agent.__invocation_config(config)
324
- if execution.metadata["coordination_cancel_requested"]
325
- config = config.merge(cancellation_token: Phronomy::Concurrency::CancellationToken.new.cancel!)
326
- end
327
- invocation = Phronomy::Agent::AgentInvocation.new(
328
- agent: agent,
329
- input: projection.ask_message,
330
- config: config,
331
- event_listener: listener,
332
- mode: (execution.metadata[INVOCATION_MODE_KEY] || "invoke").to_sym,
333
- execution_id: execution.execution_id
334
- )
335
- chat = agent.send(:build_chat, model_config: projection.model_config)
336
- agent.send(
337
- :_apply_runtime_projection_to_chat,
338
- chat,
339
- projection,
340
- invocation: invocation
341
- )
342
- if projection.ask_message
343
- chat.messages << RubyLLM::Message.new(
344
- role: :user,
345
- content: projection.ask_message
346
- )
347
- end
348
- chat.messages << assistant_message
349
-
350
- invocation.chat = chat
351
- invocation.user_message_sent = true
352
- invocation.approval_request = request
353
- invocation.instance_variable_set(
354
- :@tool_batch_llm_call_id,
355
- assistant_record.llm_call_id&.to_s
356
- )
357
-
358
- by_id = (request ? request.items : []).to_h do |item|
359
- [item.tool_invocation_id.to_s, item]
360
- end
361
- snapshots = Array(execution.metadata[TOOL_BATCH_METADATA_KEY])
362
- if snapshots.empty?
363
- raise Phronomy::ExecutionRehydrationRequiredError,
364
- "suspended execution #{execution.execution_id} predates the durable Tool batch snapshot"
365
- end
366
-
367
- calls_by_id = if assistant_message.tool_calls.respond_to?(:values)
368
- assistant_message.tool_calls.values.to_h do |call|
369
- [call.id.to_s, call]
370
- end
371
- else
372
- Array(assistant_message.tool_calls).to_h do |call|
373
- [call.id.to_s, call]
374
- end
375
- end
376
-
377
- children = snapshots.map do |snapshot|
378
- entry = snapshot.to_h { |key, value| [key.to_s, value] }
379
- call = calls_by_id.fetch(entry.fetch("tool_call_id").to_s) do
380
- raise Phronomy::ExecutionRehydrationRequiredError,
381
- "Tool Call #{entry.fetch("tool_call_id")} is missing from the durable assistant message"
382
- end
383
- tool = chat.tools[entry.fetch("tool_name").to_sym]
384
- child = if tool
385
- Phronomy::Agent::ToolInvocation.new(
386
- execution_id: execution.execution_id,
387
- agent: agent,
388
- tool: tool,
389
- tool_call: call,
390
- config: config,
391
- id: entry.fetch("tool_invocation_id")
392
- )
393
- else
394
- Phronomy::Agent::ToolInvocation.missing(
395
- execution_id: execution.execution_id,
396
- agent: agent,
397
- tool_call: call,
398
- config: config,
399
- id: entry.fetch("tool_invocation_id")
400
- )
401
- end
402
- restore_tool_snapshot!(child, entry, by_id)
403
- child
404
- end
405
- invocation.tool_invocations = children
406
- invocation
407
- end
408
-
409
- def restore_tool_snapshot!(child, entry, approval_items)
410
- status = entry.fetch("status").to_sym
411
- case status
412
- when :awaiting_approval
413
- child.validate! unless child.terminal?
414
- child.instance_variable_set(:@final_decision, :require_approval)
415
- child.mark_awaiting_approval!
416
- when :authorized
417
- child.validate! unless child.terminal?
418
- child.instance_variable_set(:@final_decision, :allow)
419
- child.mark_authorized!
420
- when :completed
421
- child.instance_variable_set(:@result, entry["result"])
422
- child.instance_variable_set(:@status, :completed)
423
- when :rejected
424
- child.mark_rejected!
425
- when :failed
426
- child.mark_framework_failed!(
427
- Phronomy::ToolError.new("durably restored Tool preflight failure")
428
- )
429
- when :cancelled
430
- child.mark_cancelled!
431
- else
432
- raise Phronomy::ExecutionRehydrationRequiredError,
433
- "unsupported durable Tool snapshot state: #{status.inspect}"
434
- end
435
-
436
- item = approval_items[child.id.to_s]
437
- if item
438
- child.instance_variable_set(:@facts, Phronomy::Agent::Immutable.copy(item.facts))
439
- child.instance_variable_set(:@authorization_reason, item.reason)
440
- end
441
- child
442
- end
443
-
444
- def build_chat_for_recovery(agent, execution, projection, main_coordinator, listener, messages:)
445
- config = {
446
- execution_id: execution.execution_id,
447
- phronomy_execution_coordinator: main_coordinator,
448
- phronomy_runtime_projection: projection
449
- }.merge(agent.__coordination_config)
450
- config = agent.__invocation_config(config)
451
- if execution.metadata["coordination_cancel_requested"]
452
- config = config.merge(cancellation_token: Phronomy::Concurrency::CancellationToken.new.cancel!)
453
- end
454
- invocation = Phronomy::Agent::AgentInvocation.new(
455
- agent: agent,
456
- input: projection.ask_message,
457
- config: config,
458
- event_listener: listener,
459
- mode: (execution.metadata[INVOCATION_MODE_KEY] || "invoke").to_sym,
460
- execution_id: execution.execution_id
461
- )
462
- chat = agent.send(:build_chat, model_config: projection.model_config)
463
- agent.send(
464
- :_apply_runtime_projection_to_chat,
465
- chat,
466
- projection,
467
- invocation: invocation
468
- )
469
- if projection.ask_message
470
- chat.messages << RubyLLM::Message.new(
471
- role: :user,
472
- content: projection.ask_message
473
- )
474
- end
475
-
476
- messages.each { |message| chat.messages << message }
477
-
478
- invocation.chat = chat
479
- invocation.user_message_sent = true
480
- invocation
481
- end
482
-
483
- def provider_output_and_usage(agent, execution)
484
- assistant = latest_assistant_record(execution)
485
- payload = assistant ? agent.persistence.contents.fetch_json(assistant.content_ref) : {}
486
- output = payload["content"]
487
-
488
- call = execution.llm_calls.last
489
- usage_hash = if call&.usage_ref
490
- agent.persistence.contents.fetch_json(call.usage_ref)
491
- else
492
- {}
493
- end
494
- usage = Phronomy::TokenUsage.new(
495
- input: usage_hash["input"] || usage_hash[:input],
496
- output: usage_hash["output"] || usage_hash[:output],
497
- cached: usage_hash["cached"] || usage_hash[:cached],
498
- cache_creation: usage_hash["cache_creation"] || usage_hash[:cache_creation]
499
- )
500
- [output, usage]
501
- end
502
- end
503
- end
504
- end