phronomy 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (475) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +9 -9
  3. data/CHANGELOG.md +52 -0
  4. data/CONTRIBUTING.md +8 -8
  5. data/README.md +9 -3
  6. data/benchmark/bench_agent_invoke.rb +25 -50
  7. data/benchmark/bench_context_assembler.rb +14 -11
  8. data/benchmark/bench_regression.rb +1 -1
  9. data/benchmark/bench_tool_schema.rb +1 -1
  10. data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
  11. data/docs/architecture/agent-transition-ownership.md +13 -89
  12. data/docs/architecture/backend-groups.md +152 -0
  13. data/docs/architecture/before-llm-input.md +3 -3
  14. data/docs/architecture/context-management.md +1 -1
  15. data/docs/architecture/execution-abstraction-review.md +88 -0
  16. data/docs/architecture/execution-boundaries.md +28 -0
  17. data/docs/architecture/execution-metadata-and-values.md +6 -0
  18. data/docs/architecture/multi-agent-handoff.md +6 -5
  19. data/docs/architecture/persistence.md +27 -9
  20. data/docs/architecture/r8-unit1.md +104 -0
  21. data/docs/architecture/r8-unit10.md +72 -0
  22. data/docs/architecture/r8-unit11.md +71 -0
  23. data/docs/architecture/r8-unit12.md +67 -0
  24. data/docs/architecture/r8-unit13.md +61 -0
  25. data/docs/architecture/r8-unit14.md +72 -0
  26. data/docs/architecture/r8-unit15.md +65 -0
  27. data/docs/architecture/r8-unit16.md +54 -0
  28. data/docs/architecture/r8-unit17.md +73 -0
  29. data/docs/architecture/r8-unit18.md +92 -0
  30. data/docs/architecture/r8-unit19.md +61 -0
  31. data/docs/architecture/r8-unit2.md +55 -0
  32. data/docs/architecture/r8-unit20.md +51 -0
  33. data/docs/architecture/r8-unit21.md +79 -0
  34. data/docs/architecture/r8-unit22.md +74 -0
  35. data/docs/architecture/r8-unit23.md +81 -0
  36. data/docs/architecture/r8-unit3.md +130 -0
  37. data/docs/architecture/r8-unit4.md +79 -0
  38. data/docs/architecture/r8-unit5.md +115 -0
  39. data/docs/architecture/r8-unit6.md +71 -0
  40. data/docs/architecture/r8-unit7.md +70 -0
  41. data/docs/architecture/r8-unit8.md +78 -0
  42. data/docs/architecture/r8-unit9.md +73 -0
  43. data/docs/architecture/refactoring-closure.md +10 -1
  44. data/docs/architecture/remaining-refactoring-plan.md +16 -0
  45. data/docs/architecture/storage-execution-boundaries.md +54 -0
  46. data/docs/architecture/tool-schema-recording-gap.md +6 -0
  47. data/docs/architecture/tracing.md +1 -1
  48. data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
  49. data/docs/architecture.md +24 -11
  50. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  51. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  52. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
  53. data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
  54. data/docs/decisions/032-storage-backend-composition.md +5 -0
  55. data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
  56. data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
  57. data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
  58. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
  59. data/docs/decisions/040-configuration-default-composition.md +12 -1
  60. data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
  61. data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
  62. data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
  63. data/docs/decisions/058-neutral-storage-primitives.md +5 -0
  64. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  65. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  66. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  67. data/docs/decisions/062-content-store-service.md +52 -0
  68. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  69. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  70. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  71. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  72. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  73. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  74. data/docs/decisions/README.md +18 -1
  75. data/docs/execution-boundaries-preview.md +45 -0
  76. data/docs/features.md +8 -3
  77. data/docs/getting-started.md +9 -6
  78. data/docs/migrations/0.22.md +2 -2
  79. data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
  80. data/docs/migrations/handoff-runner-multi-agent.md +2 -0
  81. data/docs/migrations/persistence-failure-contracts.md +67 -0
  82. data/docs/migrations/r8-unit10.md +36 -0
  83. data/docs/migrations/r8-unit11.md +33 -0
  84. data/docs/migrations/r8-unit12.md +31 -0
  85. data/docs/migrations/r8-unit13.md +36 -0
  86. data/docs/migrations/r8-unit14.md +31 -0
  87. data/docs/migrations/r8-unit15.md +31 -0
  88. data/docs/migrations/r8-unit16.md +26 -0
  89. data/docs/migrations/r8-unit17.md +28 -0
  90. data/docs/migrations/r8-unit18.md +29 -0
  91. data/docs/migrations/r8-unit19.md +28 -0
  92. data/docs/migrations/r8-unit20.md +24 -0
  93. data/docs/migrations/r8-unit21.md +28 -0
  94. data/docs/migrations/r8-unit22.md +29 -0
  95. data/docs/migrations/r8-unit23.md +20 -0
  96. data/docs/migrations/r8-unit4.md +67 -0
  97. data/docs/migrations/r8-unit5.md +75 -0
  98. data/docs/migrations/r8-unit6.md +109 -0
  99. data/docs/migrations/r8-unit7.md +31 -0
  100. data/docs/migrations/r8-unit8.md +33 -0
  101. data/docs/migrations/r8-unit9.md +28 -0
  102. data/docs/migrations/storage-async-client.md +103 -0
  103. data/docs/migrations/vector-async-clients.md +110 -0
  104. data/docs/persistence-backends.md +38 -21
  105. data/docs/runtime-and-concurrency.md +26 -1
  106. data/lib/phronomy/agent/admission.rb +78 -0
  107. data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
  108. data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
  109. data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
  110. data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
  111. data/lib/phronomy/agent/async_event_api.rb +37 -32
  112. data/lib/phronomy/agent/base.rb +74 -72
  113. data/lib/phronomy/agent/before_llm_input.rb +64 -0
  114. data/lib/phronomy/agent/composition/run_once.rb +1 -1
  115. data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
  116. data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
  117. data/lib/phronomy/agent/control_request.rb +21 -0
  118. data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
  119. data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
  120. data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
  121. data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
  122. data/lib/phronomy/agent/execution/exact_execution.rb +44 -23
  123. data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
  124. data/lib/phronomy/agent/execution/execution_coordinator.rb +214 -395
  125. data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
  126. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +70 -126
  127. data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
  128. data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
  129. data/lib/phronomy/agent/execution/initial_preparation.rb +18 -116
  130. data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
  131. data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
  132. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +7 -7
  133. data/lib/phronomy/agent/execution_change.rb +161 -0
  134. data/lib/phronomy/agent/execution_environment.rb +36 -0
  135. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  136. data/lib/phronomy/agent/execution_observation.rb +35 -0
  137. data/lib/phronomy/agent/filterable.rb +182 -0
  138. data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
  139. data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
  140. data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
  141. data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
  142. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  143. data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
  144. data/lib/phronomy/agent/lifecycle/ownership_registry.rb +18 -23
  145. data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
  146. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  147. data/lib/phronomy/agent/persistence/codec.rb +1 -21
  148. data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
  149. data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
  150. data/lib/phronomy/agent/persistence/records.rb +33 -0
  151. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  152. data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
  153. data/lib/phronomy/agent/persistence/watermark.rb +13 -11
  154. data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
  155. data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +10 -9
  156. data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +27 -27
  157. data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +19 -21
  158. data/lib/phronomy/agent/recovery/recovery_coordinator.rb +23 -16
  159. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  160. data/lib/phronomy/agent/recovery/recovery_support.rb +8 -11
  161. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  162. data/lib/phronomy/agent/retention.rb +17 -0
  163. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  164. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  165. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  166. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  167. data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
  168. data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
  169. data/lib/phronomy/agent/settings.rb +47 -0
  170. data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
  171. data/lib/phronomy/agent/store.rb +253 -0
  172. data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +1 -1
  173. data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
  174. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
  175. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  176. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  177. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  178. data/lib/phronomy/content_store/storage_schema.rb +1 -0
  179. data/lib/phronomy/content_store/stored_contents.rb +2 -1
  180. data/lib/phronomy/context/assembly.rb +202 -0
  181. data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
  182. data/lib/phronomy/context/candidate.rb +51 -0
  183. data/lib/phronomy/context/constraint.rb +47 -0
  184. data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
  185. data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
  186. data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
  187. data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
  188. data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
  189. data/lib/phronomy/context/default_policy.rb +140 -0
  190. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  191. data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
  192. data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
  193. data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
  194. data/lib/phronomy/context/manifest_error.rb +10 -0
  195. data/lib/phronomy/context/prompt_template.rb +98 -0
  196. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  197. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  198. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  199. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  200. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  201. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  202. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  203. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  204. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +48 -0
  205. data/lib/phronomy/embeddings/base.rb +43 -0
  206. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  207. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  208. data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
  209. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  210. data/lib/phronomy/engine/diagnostics.rb +1 -1
  211. data/lib/phronomy/engine/event_loop.rb +7 -7
  212. data/lib/phronomy/engine/execution_binding.rb +23 -0
  213. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  214. data/lib/phronomy/engine/fsm_protocol.rb +1 -6
  215. data/lib/phronomy/engine/fsm_session.rb +3 -3
  216. data/lib/phronomy/engine/metrics.rb +16 -14
  217. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  218. data/lib/phronomy/engine/runtime.rb +25 -12
  219. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  220. data/lib/phronomy/execution/async_operation.rb +41 -0
  221. data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
  222. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
  223. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  224. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  225. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  226. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  227. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  228. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  229. data/lib/phronomy/{engine → execution}/execution.rb +61 -27
  230. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  231. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  232. data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
  233. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  234. data/lib/phronomy/execution/runnable.rb +24 -0
  235. data/lib/phronomy/{engine → execution}/task_result.rb +3 -3
  236. data/lib/phronomy/execution/wait_policy.rb +24 -0
  237. data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +1 -1
  238. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  239. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  240. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
  241. data/lib/phronomy/generation/generator_verifier.rb +7 -7
  242. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  243. data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
  244. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  245. data/lib/phronomy/llm_adapter/base.rb +49 -68
  246. data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
  247. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  248. data/lib/phronomy/llm_adapter/message.rb +62 -0
  249. data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
  250. data/lib/phronomy/llm_adapter/request.rb +79 -0
  251. data/lib/phronomy/llm_adapter/response.rb +36 -0
  252. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  253. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  254. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  255. data/lib/phronomy/llm_adapter.rb +3 -16
  256. data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
  257. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  258. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  259. data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
  260. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
  261. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  262. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
  263. data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
  264. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
  265. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  266. data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
  267. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  268. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  269. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  270. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +58 -42
  271. data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
  272. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  273. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  274. data/lib/phronomy/multi_agent/shared_state.rb +2 -2
  275. data/lib/phronomy/multi_agent/store.rb +156 -0
  276. data/lib/phronomy/multi_agent/team_coordinator.rb +83 -74
  277. data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
  278. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  279. data/lib/phronomy/persistence/content_repository.rb +26 -0
  280. data/lib/phronomy/persistence/errors.rb +16 -0
  281. data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
  282. data/lib/phronomy/persistence/persistence.rb +23 -0
  283. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  284. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  285. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  286. data/lib/phronomy/persistence/transaction.rb +86 -0
  287. data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
  288. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  289. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  290. data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
  291. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  292. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  293. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  294. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  295. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  296. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  297. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  298. data/lib/phronomy/storage/async/async_client.rb +60 -0
  299. data/lib/phronomy/storage/backend.rb +8 -2
  300. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  301. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  302. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
  303. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
  304. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
  305. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
  306. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
  307. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
  308. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
  309. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
  310. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
  311. data/lib/phronomy/tool/authorization.rb +77 -0
  312. data/lib/phronomy/tool/base.rb +549 -9
  313. data/lib/phronomy/tool/call_request.rb +40 -0
  314. data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
  315. data/lib/phronomy/tool/operation.rb +88 -0
  316. data/lib/phronomy/tool/schema.rb +214 -0
  317. data/lib/phronomy/tool/settings.rb +31 -0
  318. data/lib/phronomy/tool/tool_error.rb +7 -0
  319. data/lib/phronomy/tool/tool_executor.rb +58 -0
  320. data/lib/phronomy/tools/agent.rb +22 -90
  321. data/lib/phronomy/tools/mcp.rb +21 -14
  322. data/lib/phronomy/tools/vector_search.rb +4 -4
  323. data/lib/phronomy/tracing/automatic.rb +11 -6
  324. data/lib/phronomy/tracing/base.rb +1 -1
  325. data/lib/phronomy/tracing/observation.rb +37 -0
  326. data/lib/phronomy/tracing/settings.rb +36 -0
  327. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  328. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  329. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  330. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  331. data/lib/phronomy/vector_store/base.rb +118 -49
  332. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  333. data/lib/phronomy/vector_store.rb +2 -17
  334. data/lib/phronomy/version.rb +1 -1
  335. data/lib/phronomy/workflow/completion.rb +11 -0
  336. data/lib/phronomy/workflow/execution/workflow.rb +7 -7
  337. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  338. data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
  339. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +27 -0
  340. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +3 -5
  341. data/lib/phronomy/workflow/execution/workflow_runner.rb +58 -79
  342. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  343. data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
  344. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  345. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +38 -0
  346. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +156 -0
  347. data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
  348. data/lib/phronomy.rb +57 -16
  349. data/scripts/api_snapshot.rb +35 -15
  350. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  351. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  352. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  353. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  354. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  355. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  356. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  357. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  358. data/sig/phronomy/agent/admission.rbs +19 -0
  359. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  360. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  361. data/sig/phronomy/agent/store.rbs +73 -0
  362. data/sig/phronomy/agent.rbs +14 -138
  363. data/sig/phronomy/common.rbs +7 -0
  364. data/sig/phronomy/content_store/contracts.rbs +13 -0
  365. data/sig/phronomy/context.rbs +152 -0
  366. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  367. data/sig/phronomy/embeddings/base.rbs +17 -0
  368. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  369. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  370. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  371. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  372. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  373. data/sig/phronomy/engine/observability.rbs +13 -0
  374. data/sig/phronomy/execution/async_operation.rbs +7 -0
  375. data/sig/phronomy/execution/blocking.rbs +5 -0
  376. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  377. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  378. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  379. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  380. data/sig/phronomy/execution/errors.rbs +7 -0
  381. data/sig/phronomy/execution/execution.rbs +41 -0
  382. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  383. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  384. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  385. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  386. data/sig/phronomy/execution/task_result.rbs +29 -0
  387. data/sig/phronomy/extensions.rbs +0 -21
  388. data/sig/phronomy/handoff.rbs +10 -4
  389. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  390. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  391. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  392. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  393. data/sig/phronomy/llm_context_window.rbs +10 -0
  394. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  395. data/sig/phronomy/multi_agent/store.rbs +54 -0
  396. data/sig/phronomy/multi_agent.rbs +6 -3
  397. data/sig/phronomy/persistence/errors.rbs +18 -0
  398. data/sig/phronomy/persistence/migration.rbs +13 -0
  399. data/sig/phronomy/persistence/transaction.rbs +19 -0
  400. data/sig/phronomy/persistence.rbs +1 -99
  401. data/sig/phronomy/persistence_composition.rbs +19 -0
  402. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  403. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  404. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  405. data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +2 -16
  406. data/sig/phronomy/tool.rbs +48 -4
  407. data/sig/phronomy/tracing/observation.rbs +7 -0
  408. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  409. data/sig/phronomy/vector_store/base.rbs +23 -0
  410. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +14 -0
  411. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  412. data/sig/phronomy/workflow/store.rbs +8 -0
  413. data/sig/phronomy/workflow.rbs +1 -1
  414. data/sig/phronomy.rbs +0 -49
  415. metadata +318 -117
  416. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  417. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  418. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  419. data/lib/phronomy/agent/context/capability/base.rb +0 -548
  420. data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
  421. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  422. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
  423. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
  424. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  425. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  426. data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
  427. data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
  428. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
  429. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
  430. data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
  431. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
  432. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
  433. data/lib/phronomy/agent/persistence/queries.rb +0 -61
  434. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  435. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  436. data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
  437. data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
  438. data/lib/phronomy/configuration/configuration.rb +0 -68
  439. data/lib/phronomy/configuration/global_configuration.rb +0 -26
  440. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  441. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  442. data/lib/phronomy/engine/execution_receiver.rb +0 -65
  443. data/lib/phronomy/engine/runnable.rb +0 -54
  444. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  445. data/lib/phronomy/llm_contract/token_usage.rb +0 -85
  446. data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
  447. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
  448. data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
  449. data/lib/phronomy/persistence/api/persistence.rb +0 -140
  450. data/lib/phronomy/persistence_composition/repositories.rb +0 -77
  451. data/lib/phronomy/recovery/recovery.rb +0 -186
  452. data/lib/phronomy/tool/contract/tool_error.rb +0 -7
  453. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  454. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  455. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  456. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
  457. data/sig/phronomy/execution_receiver.rbs +0 -34
  458. data/sig/phronomy/llm_adapter.rbs +0 -11
  459. data/sig/phronomy/runtime.rbs +0 -76
  460. /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
  461. /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
  462. /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
  463. /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
  464. /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
  465. /data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +0 -0
  466. /data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +0 -0
  467. /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
  468. /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
  469. /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
  470. /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
  471. /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
  472. /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
  473. /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
  474. /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
  475. /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Adapts the concrete Engine lifecycle and sessions to Agent's connection.
6
+ # Construction alone starts no Runtime resources and creates no registry.
7
+ # @api private
8
+ class EngineEnvironment < ExecutionEnvironment
9
+ def initialize(runtime: Phronomy::Runtime.instance)
10
+ @runtime = runtime
11
+ end
12
+
13
+ def registry = ExecutionRegistry.for(@runtime.event_loop)
14
+ def existing_registry = ExecutionRegistry.existing_for(@runtime)
15
+
16
+ def ownership
17
+ existing_ownership || @runtime.__register_shutdown_participant(
18
+ key: OwnershipRegistry, participant: OwnershipRegistry.new(environment: self)
19
+ )
20
+ end
21
+
22
+ def existing_ownership = @runtime.__shutdown_participant(key: OwnershipRegistry)
23
+ def executing? = @runtime.event_loop.current?
24
+ def session_phase(id) = @runtime.event_loop.fsm_session_state(id)
25
+
26
+ def submit(**options, &operation)
27
+ Phronomy::Execution.submit(runtime: @runtime, **options, &operation)
28
+ end
29
+
30
+ # Pool sizing is an execution resource choice, separate from Agent's
31
+ # authorization decision and timeout selection. Read at submission time.
32
+ def submit_authorization(timeout:, cancellation_token:, &operation)
33
+ submit(pool_name: :authorization,
34
+ size: Phronomy::RuntimeSettings.current.authorization_pool_size,
35
+ queue_size: Phronomy::RuntimeSettings.current.authorization_queue_size,
36
+ timeout: timeout, cancellation_token: cancellation_token,
37
+ on_full: :raise, &operation)
38
+ end
39
+
40
+ def build_llm_client(adapter:)
41
+ Phronomy::LLMAdapter::AsyncClient.new(adapter: adapter, submitter: self)
42
+ end
43
+
44
+ def build_agent_session(invocation:, resume_event: nil, resume_phase: nil)
45
+ EngineSessionBuilder.build(invocation: invocation, environment: self,
46
+ event_loop: @runtime.event_loop, resume_event: resume_event, resume_phase: resume_phase)
47
+ end
48
+
49
+ def build_tool_session(invocation:, parent_sink:, resume_event: nil, resume_phase: nil)
50
+ ToolSessionBuilder.build(invocation: invocation, environment: self,
51
+ event_loop: @runtime.event_loop, parent_event_sink: parent_sink,
52
+ resume_event: resume_event, resume_phase: resume_phase)
53
+ end
54
+
55
+ def register_session(session, completion:)
56
+ @runtime.event_loop.register(session, completion: completion)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Engine-only construction. State vocabulary, ordered guards and entry
6
+ # operations are supplied by the Agent framework, not defined here.
7
+ # @api private
8
+ class EngineSessionBuilder
9
+ def self.build(invocation:, environment:, event_loop:, resume_event: nil, resume_phase: nil)
10
+ event_sink = Phronomy::FSMSession::EventSink.new(event_loop: event_loop)
11
+ invocation.bind_event_sink!(event_sink)
12
+ actions = InvocationActions.build_entry_actions(invocation.agent,
13
+ environment, mode: invocation.mode, event_sink: event_sink)
14
+ phase_machine = PhaseMachineBuilder.new(entry_actions: actions).build
15
+
16
+ Phronomy::FSMSession.new(
17
+ context: invocation,
18
+ event_sink: event_sink,
19
+ entry_point: InvocationTransitions::ENTRY_POINT,
20
+ phase_machine_class: phase_machine,
21
+ entry_actions: {},
22
+ auto_state_set: InvocationTransitions::AUTO_STATE_SET,
23
+ declared_states: InvocationTransitions::DECLARED_STATES,
24
+ wait_state_names: InvocationTransitions::WAIT_STATES,
25
+ external_events: InvocationTransitions::EXTERNAL_EVENTS,
26
+ recursion_limit: InvocationTransitions.recursion_limit(invocation.agent.class.max_iterations),
27
+ event_loop: event_loop,
28
+ resume_event: resume_event,
29
+ resume_phase: resume_phase
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "state_machines"
4
+
5
+ module Phronomy
6
+ module Agent
7
+ class PhaseMachineBuilder
8
+ def initialize(entry_actions: {}, transitions: InvocationTransitions, operation_name: "Agent")
9
+ @entry_actions = entry_actions
10
+ @transitions = transitions
11
+ @operation_name = operation_name
12
+ end
13
+
14
+ def build
15
+ entry_actions = @entry_actions
16
+ callback_builder = method(:build_entry_callback)
17
+ transitions = @transitions
18
+ declared_states = transitions::DECLARED_STATES
19
+ entry_point = transitions::ENTRY_POINT
20
+
21
+ Class.new do
22
+ attr_accessor :context, :current_event
23
+
24
+ state_machine :phase, initial: entry_point do
25
+ declared_states.each { |state_name| state state_name }
26
+
27
+ transitions::EVENTS.each do |event_name, definitions|
28
+ event event_name do
29
+ definitions.each do |definition|
30
+ transition definition[:from] => definition[:to],
31
+ :if => ->(machine) {
32
+ transitions.allowed?(definition, machine.context)
33
+ }
34
+ end
35
+ end
36
+ end
37
+
38
+ entry_actions.each do |state_name, callables|
39
+ callables.each do |callable|
40
+ after_transition(
41
+ to: state_name,
42
+ do: callback_builder.call(callable, state_name)
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def build_entry_callback(callable, state_name)
53
+ ->(machine) {
54
+ result = callable.call(machine.context)
55
+ if result.is_a?(Phronomy::TaskResult)
56
+ raise Phronomy::InvalidAsyncEntryActionError,
57
+ "#{@operation_name} entry action for #{state_name.inspect} returned Phronomy::TaskResult"
58
+ end
59
+ machine.context = result if result.respond_to?(:set_graph_metadata)
60
+ }
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Binds Agent Tool child progress to a concrete FSM session. Session routing
6
+ # is fresh for each incarnation and never replaces a durable Tool identity.
7
+ # @api private
8
+ class ToolSessionBuilder
9
+ def self.build(invocation:, environment:, event_loop:, parent_event_sink:,
10
+ resume_event: nil, resume_phase: nil)
11
+ event_sink = Phronomy::FSMSession::EventSink.new(event_loop: event_loop)
12
+ actions = ToolInvocationActions.build_entry_actions(environment, event_sink, parent_event_sink)
13
+ phase_machine = PhaseMachineBuilder.new(entry_actions: actions,
14
+ transitions: ToolInvocationTransitions, operation_name: "Tool").build
15
+
16
+ Phronomy::FSMSession.new(
17
+ context: invocation,
18
+ event_sink: event_sink,
19
+ entry_point: ToolInvocationTransitions::ENTRY_POINT,
20
+ phase_machine_class: phase_machine,
21
+ entry_actions: {},
22
+ auto_state_set: ToolInvocationTransitions::AUTO_STATE_SET,
23
+ declared_states: ToolInvocationTransitions::DECLARED_STATES,
24
+ wait_state_names: ToolInvocationTransitions::WAIT_STATES,
25
+ external_events: ToolInvocationTransitions::EXTERNAL_EVENTS,
26
+ recursion_limit: ToolInvocationTransitions::RECURSION_LIMIT,
27
+ event_loop: event_loop,
28
+ resume_event: resume_event,
29
+ resume_phase: resume_phase
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Phronomy
4
4
  module Agent
5
- class RubyLLMMaterializer
5
+ class RuntimeInput
6
6
  RuntimeProjection = Data.define(
7
7
  :system, :messages, :tool_classes, :ask_message,
8
8
  :model_config, :manifest, :manifest_ref
@@ -63,7 +63,7 @@ module Phronomy
63
63
  private
64
64
 
65
65
  def segment_from_record(record, position: 0)
66
- LLMInputManifest::Segment.new(
66
+ Phronomy::Context::LLMInputManifest::Segment.new(
67
67
  position: position,
68
68
  category: record.kind,
69
69
  role: record.role,
@@ -88,7 +88,7 @@ module Phronomy
88
88
  end
89
89
 
90
90
  def verify_adapter!(manifest)
91
- actual = Phronomy.configuration.llm_adapter.class.name
91
+ actual = Phronomy::Agent::Settings.current.llm_adapter.class.name
92
92
  return if manifest.adapter_name.nil? || manifest.adapter_name == actual
93
93
 
94
94
  raise Phronomy::ConfigurationError,
@@ -97,19 +97,16 @@ module Phronomy
97
97
 
98
98
  def verify_tool_definitions!(manifest)
99
99
  unless manifest.tool_definitions_ref
100
- return ToolDefinitionSet.build(
101
- @agent,
102
- additional_tools: @additional_tools
103
- )
100
+ return @agent.send(:tool_definition_set, additional_tools: @additional_tools)
104
101
  end
105
102
 
106
103
  expected_definitions = fetch_json(manifest.tool_definitions_ref)
107
104
  additional = @additional_tools
108
105
  if additional.empty?
109
- additional = Array(@agent.__coordination_config[:phronomy_handoff_bindings]).map(&:tool_class)
106
+ additional = Array(@agent.__execution_wiring[:phronomy_control_bindings]).map(&:tool_class)
110
107
  end
111
108
 
112
- ToolDefinitionSet.build(@agent, additional_tools: additional)
109
+ @agent.send(:tool_definition_set, additional_tools: additional)
113
110
  .select_definitions(expected_definitions)
114
111
  end
115
112
 
@@ -130,7 +127,7 @@ module Phronomy
130
127
  when :tool_result
131
128
  raise ArgumentError, "raw Tool result is not an LLM message"
132
129
  else
133
- RubyLLM::Message.new(
130
+ Phronomy::LLMAdapter::Message.new(
134
131
  role: segment.role,
135
132
  content: @persistence.contents.fetch_text(segment.content_ref),
136
133
  tool_call_id: segment.tool_call_id
@@ -151,39 +148,25 @@ module Phronomy
151
148
  end
152
149
 
153
150
  tool_calls = materialize_tool_calls(payload["tool_calls"])
154
- RubyLLM::Message.new(
151
+ Phronomy::LLMAdapter::Message.new(
155
152
  role: role,
156
- content: initial_content_for(role, payload.fetch("content", nil), tool_calls),
157
- tool_calls: tool_calls.empty? ? nil : tool_calls,
153
+ content: payload.fetch("content", nil),
154
+ tool_calls: tool_calls,
158
155
  tool_call_id: payload["tool_call_id"],
159
- model: payload["model_id"]
156
+ metadata: {"model_id" => payload["model_id"]}.compact
160
157
  )
161
158
  end
162
159
 
163
- def initial_content_for(role, content, tool_calls)
164
- return "" if role == :assistant && content.nil? && !tool_calls.empty?
165
-
166
- (content.is_a?(Hash) || content.is_a?(Array)) ? JSON.generate(content) : content
167
- end
168
-
169
160
  def materialize_tool_calls(payloads)
170
- Array(payloads).each_with_object({}) do |payload, result|
171
- tool_call_id = payload.fetch("id").to_s
172
- if result.key?(tool_call_id)
173
- raise ArgumentError, "duplicate Tool Call in assistant message: #{tool_call_id}"
174
- end
175
- result[tool_call_id] = materialize_tool_call(payload)
161
+ Array(payloads).map do |payload|
162
+ # Read the pre-unit6 durable format explicitly; SDK objects are never
163
+ # reconstituted during recovery. New records use opaque metadata.
164
+ metadata = payload.fetch("metadata", {}).dup
165
+ metadata["thought_signature"] = payload["thought_signature"] if payload["thought_signature"]
166
+ Phronomy::Tool::CallRequest.new(id: payload.fetch("id"), name: payload.fetch("name"),
167
+ arguments: payload.fetch("arguments", {}), metadata: metadata)
176
168
  end
177
169
  end
178
-
179
- def materialize_tool_call(payload)
180
- RubyLLM::ToolCall.new(
181
- id: payload.fetch("id").to_s,
182
- name: payload.fetch("name"),
183
- arguments: payload.fetch("arguments", {}),
184
- thought_signature: payload["thought_signature"]
185
- )
186
- end
187
170
  end
188
171
  end
189
172
  end
@@ -9,13 +9,15 @@ module Phronomy
9
9
  module_function
10
10
 
11
11
  def manifest_from_ref(agent, ref)
12
- raw = agent.persistence.contents.fetch_json(ref)
13
- Phronomy::Agent::LLMInputManifest.from_h(raw)
12
+ Phronomy::Persistence::StorageBoundary.call do
13
+ raw = agent.persistence.contents.fetch_json(ref)
14
+ Phronomy::Context::LLMInputManifest.from_h(raw)
15
+ end
14
16
  end
15
17
 
16
18
  def materialize_projection(agent, manifest_ref)
17
19
  manifest = manifest_from_ref(agent, manifest_ref)
18
- projection = Phronomy::Agent::RubyLLMMaterializer.new(
20
+ projection = Phronomy::Agent::RuntimeInput.new(
19
21
  agent: agent,
20
22
  persistence: agent.persistence
21
23
  ).materialize(manifest: manifest, manifest_ref: manifest_ref)
@@ -40,7 +42,7 @@ module Phronomy
40
42
  else
41
43
  {}
42
44
  end
43
- usage = Phronomy::TokenUsage.new(
45
+ usage = Phronomy::LLMAdapter::TokenUsage.new(
44
46
  input: usage_hash["input"] || usage_hash[:input] || usage_hash["input_tokens"] || usage_hash[:input_tokens],
45
47
  output: usage_hash["output"] || usage_hash[:output] || usage_hash["output_tokens"] || usage_hash[:output_tokens],
46
48
  cached: usage_hash["cached"] || usage_hash[:cached] || usage_hash["cache_read_tokens"] || usage_hash[:cache_read_tokens] || usage_hash["cached_tokens"] || usage_hash[:cached_tokens],
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/configuration_error"
4
+
5
+ module Phronomy
6
+ module Agent
7
+ # Agent-owned option values and policy validation. Composition supplies
8
+ # concrete components and default values; execution reads at existing sites.
9
+ # @api private
10
+ class Settings
11
+ class << self
12
+ # Bind at boot without reading settings or starting runtime resources.
13
+ def install_provider(&provider)
14
+ raise ArgumentError, "settings provider requires a block" unless provider
15
+ @provider = provider
16
+ nil
17
+ end
18
+
19
+ # Read only when requested; no cache or change notification is involved.
20
+ def current
21
+ @provider.call
22
+ end
23
+ end
24
+
25
+ STREAM_CALLBACK_ERROR_POLICIES = %i[report fail_task].freeze
26
+ private_constant :STREAM_CALLBACK_ERROR_POLICIES
27
+
28
+ attr_accessor :default_model, :before_llm_input, :agent_store, :llm_adapter, :authorization_timeout
29
+ attr_reader :stream_callback_error_policy
30
+
31
+ def initialize(llm_adapter:, authorization_timeout:, stream_callback_error_policy:)
32
+ @llm_adapter = llm_adapter
33
+ @authorization_timeout = authorization_timeout
34
+ self.stream_callback_error_policy = stream_callback_error_policy
35
+ end
36
+
37
+ def stream_callback_error_policy=(value)
38
+ unless STREAM_CALLBACK_ERROR_POLICIES.include?(value)
39
+ allowed = STREAM_CALLBACK_ERROR_POLICIES.map(&:inspect).join(", ")
40
+ raise Phronomy::ConfigurationError,
41
+ "stream_callback_error_policy must be one of: #{allowed}"
42
+ end
43
+ @stream_callback_error_policy = value
44
+ end
45
+ end
46
+ end
47
+ end
@@ -14,7 +14,7 @@ module Phronomy
14
14
  @agent_id = agent_id
15
15
  end
16
16
 
17
- def create_root(definition:, context:, knowledge:, metadata:)
17
+ def create_root(definition:, context:, knowledge:, metadata:, retention: nil)
18
18
  root = Agent::AgentRoot.create(
19
19
  agent_id: agent_id,
20
20
  agent_definition_id: definition.fetch(:id),
@@ -23,6 +23,7 @@ module Phronomy
23
23
  )
24
24
  persistence.transaction do |tx|
25
25
  tx.agents.create(root)
26
+ tx.retentions.retain(retention) if retention
26
27
  records = initial_context_records(tx: tx, root: root, context: context)
27
28
  records.concat(initial_knowledge_records(tx: tx, root: root, knowledge: knowledge))
28
29
  unless records.empty?
@@ -137,8 +138,8 @@ module Phronomy
137
138
  build_knowledge_record(
138
139
  tx: tx,
139
140
  root: root,
140
- content: content,
141
- metadata: {}
141
+ content: content.is_a?(KnowledgeItem) ? content.content : content,
142
+ metadata: content.is_a?(KnowledgeItem) ? content.metadata : {}
142
143
  )
143
144
  end
144
145
  end
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Agent-owned durable operations and observations. An injected record
6
+ # adapter implements storage; this framework never selects a DB or codec.
7
+ # @api public
8
+ class Store
9
+ # Provider-ordered authorized calls, with no internal execution metadata.
10
+ AuthorizedOperation = Data.define(:invocation_id, :name, :arguments)
11
+
12
+ attr_reader :coordinator
13
+
14
+ # Record protocol for Agent-owned framework operations only.
15
+ # @api private
16
+ attr_reader :contents, :agents, :journals, :executions, :retentions
17
+
18
+ def initialize(coordinator:, records:)
19
+ @coordinator, @record_adapter = coordinator, records
20
+ @records = coordinator.bind(records)
21
+ @contents = @records.contents
22
+ @agents = @records.agents
23
+ @journals = @records.journals
24
+ @executions = @records.executions
25
+ @retentions = @records.retentions
26
+ end
27
+
28
+ # A domain operation owns this short transaction. A coordinating parent
29
+ # can instead call participate with its existing shared scope.
30
+ def transaction
31
+ coordinator.atomic do |scope|
32
+ participate(scope) { |records| yield records, scope }
33
+ end
34
+ end
35
+
36
+ def participate(scope, &operation)
37
+ scope.participate(persistence: coordinator, adapter: @record_adapter, &operation)
38
+ end
39
+
40
+ # Authoritative presence only. Failed reads propagate, never mean absent.
41
+ def exist?(agent_id)
42
+ assert_observation_thread!
43
+ agents.load(agent_id)
44
+ true
45
+ rescue Phronomy::Persistence::NotFoundError
46
+ false
47
+ end
48
+
49
+ # Validate the exact requested call and project its authorized batch in
50
+ # Provider order from one execution snapshot in the caller's transaction.
51
+ # The receiving domain owns application and replay of these operations.
52
+ def authorized_operations(scope, agent_id:, execution_id:, invocation_id:, name:, arguments:, names:)
53
+ participate(scope) do |records|
54
+ execution = records.executions.load(execution_id)
55
+ unless execution.agent_id == agent_id
56
+ raise Phronomy::Persistence::StateConflictError, "Authorized operation owner mismatch"
57
+ end
58
+ batch = Array(execution.metadata[ExecutionMetadata::TOOL_BATCH_METADATA_KEY])
59
+ requested = batch.find { |entry| entry.fetch("tool_invocation_id") == invocation_id }
60
+ unless names.include?(name) && requested && requested.fetch("status") == "authorized" &&
61
+ requested.fetch("tool_name") == name && authorized_argument_values(requested) == arguments
62
+ raise Phronomy::Persistence::StateConflictError, "Operation #{invocation_id} is not the authorized call"
63
+ end
64
+ batch.filter_map do |entry|
65
+ next unless entry.fetch("status") == "authorized" && names.include?(entry.fetch("tool_name"))
66
+ AuthorizedOperation.new(
67
+ invocation_id: Phronomy::Values::Immutable.copy(entry.fetch("tool_invocation_id")),
68
+ name: Phronomy::Values::Immutable.copy(entry.fetch("tool_name")),
69
+ arguments: Phronomy::Values::Immutable.copy(authorized_argument_values(entry))
70
+ )
71
+ end.freeze
72
+ end
73
+ end
74
+
75
+ def result(execution_id)
76
+ assert_observation_thread!
77
+ execution = executions.load(execution_id)
78
+ {
79
+ execution_id: execution.execution_id, agent_id: execution.agent_id,
80
+ status: execution.status, phase: execution.phase,
81
+ result_ref: execution.result_ref, error_ref: execution.error_ref,
82
+ result: execution.result_ref && contents.fetch_text(execution.result_ref),
83
+ error: execution.error_ref && contents.fetch_json(execution.error_ref)
84
+ }.then { |value| Phronomy::Values::Immutable.copy(value) }
85
+ end
86
+
87
+ # Exact, immutable observation. Only authoritative NotFound means absent.
88
+ def observe_execution(agent_id:, execution_id:, scope: nil)
89
+ with_scope(scope) do |records|
90
+ begin
91
+ execution = records.executions.load(execution_id)
92
+ rescue Phronomy::Persistence::NotFoundError
93
+ next ExecutionObservation.new(agent_id: agent_id, execution_id: execution_id,
94
+ status: :absent, result: nil, error: nil, reservation: nil,
95
+ transfer_receipt: nil, cancellation_requested: false)
96
+ end
97
+ assert_execution_owner!(execution, agent_id)
98
+ ExecutionObservation.new(agent_id: execution.agent_id, execution_id: execution.execution_id,
99
+ status: execution.status,
100
+ result: execution.result_ref && records.contents.fetch_text(execution.result_ref),
101
+ error: execution.error_ref && Phronomy::Values::Immutable.copy(records.contents.fetch_json(execution.error_ref)),
102
+ reservation: execution.metadata["reservation"] && ReservedExecution.new(agent_id: agent_id,
103
+ execution_id: execution_id, correlation: execution.metadata["reservation"]),
104
+ transfer_receipt: Phronomy::Values::Immutable.copy(execution.metadata["transfer_receipt"]),
105
+ cancellation_requested: records.cancellations.requested?(agent_id, execution_id) || execution.metadata["cancellation_requested"] == true)
106
+ end
107
+ end
108
+
109
+ def active_executions(agent_id:, scope: nil)
110
+ with_scope(scope) do |records|
111
+ records.executions.list_active(agent_id).map do |execution|
112
+ observe_execution(agent_id: agent_id, execution_id: execution.execution_id, scope: scope)
113
+ end.freeze
114
+ end
115
+ end
116
+
117
+ def definition(agent_id:, scope: nil)
118
+ with_scope(scope) do |records|
119
+ root = records.agents.load(agent_id)
120
+ {id: root.agent_definition_id, version: root.agent_definition_version}.freeze
121
+ end
122
+ end
123
+
124
+ def knowledge_snapshot(agent_id:, scope: nil)
125
+ with_scope(scope) do |records|
126
+ root = records.agents.load(agent_id)
127
+ journal = records.journals.read(agent_id, limit: root.journal_position)
128
+ JournalProjection.new(agent_root: root, records: journal).context_records.filter_map do |record|
129
+ next unless record.kind == :knowledge
130
+ KnowledgeItem.new(content: records.contents.fetch_text(record.content_ref), metadata: record.metadata)
131
+ end.freeze
132
+ end
133
+ end
134
+
135
+ # Participant SPI: opaque extension state, never an AgentExecution getter.
136
+ def execution_extension(agent_id:, execution_id:, binding_key:, scope: nil)
137
+ with_scope(scope) do |records|
138
+ execution = records.executions.load(execution_id)
139
+ assert_execution_owner!(execution, agent_id)
140
+ raw = execution.metadata["execution_extension"]
141
+ next nil unless raw
142
+ extension = ExecutionExtensionState.from_h(raw)
143
+ unless extension.binding_key == binding_key
144
+ raise Phronomy::ConfigurationError, "Execution participant binding mismatch"
145
+ end
146
+ extension
147
+ end
148
+ end
149
+
150
+ # Coordination guards are acquired first by the participant. All Agent
151
+ # roots then use the same lexical order, including admission and purge.
152
+ def guard_agents(scope, agent_ids:)
153
+ participate(scope) { |records| agent_ids.map(&:to_s).uniq.sort.each { |id| records.guard_agent!(id) } }
154
+ nil
155
+ end
156
+
157
+ # History release is terminal-only and preserves execution/result identity.
158
+ def remove_execution_extension(agent_id:, execution_id:, binding_key:, scope:)
159
+ participate(scope) do |records|
160
+ records.guard_agent!(agent_id)
161
+ execution = records.executions.load(execution_id)
162
+ assert_execution_owner!(execution, agent_id)
163
+ raise Phronomy::AgentBusyError, "Active execution history cannot be released" unless execution.terminal?
164
+ extension = execution.metadata["execution_extension"]
165
+ unless extension && extension.fetch("binding_key") == binding_key
166
+ raise Phronomy::Persistence::StateConflictError, "Extension history identity mismatch"
167
+ end
168
+ updated = execution.with(metadata: execution.metadata.except("execution_extension"))
169
+ records.executions.save(execution_id, expected_revision: execution.execution_revision, execution: updated)
170
+ end
171
+ nil
172
+ end
173
+
174
+ def execution_identity(execution_id, scope: nil)
175
+ with_scope(scope) do |records|
176
+ execution = records.executions.load(execution_id)
177
+ ReservedExecution.new(agent_id: execution.agent_id, execution_id: execution.execution_id,
178
+ correlation: execution.metadata["reservation"])
179
+ end
180
+ end
181
+
182
+ def retained_references(agent_id:, scope: nil)
183
+ with_scope(scope) { |records| records.retentions.list(agent_id) }
184
+ end
185
+
186
+ def retain(retention, scope: nil)
187
+ with_scope(scope) do |records|
188
+ records.guard_agent!(retention.agent_id)
189
+ records.agents.load(retention.agent_id)
190
+ records.retentions.retain(retention)
191
+ end
192
+ end
193
+
194
+ def release_retention(agent_id:, owner_key:, scope:)
195
+ participate(scope) { |records| records.retentions.release(agent_id: agent_id, owner_key: owner_key) }
196
+ end
197
+
198
+ def cancellation_requested?(agent_id:, execution_id:, scope: nil)
199
+ with_scope(scope) { |records| records.cancellations.requested?(agent_id, execution_id) }
200
+ end
201
+
202
+ def request_cancellation(agent_id:, execution_id:, scope: nil)
203
+ with_scope(scope) do |records|
204
+ records.guard_agent!(agent_id)
205
+ begin
206
+ current = records.executions.load(execution_id)
207
+ rescue Phronomy::Persistence::NotFoundError
208
+ next false
209
+ end
210
+ assert_execution_owner!(current, agent_id)
211
+ next false if current.terminal?
212
+ records.cancellations.request(agent_id, execution_id)
213
+ true
214
+ end
215
+ end
216
+
217
+ def runs(agent_id, after: nil, limit: 100)
218
+ assert_observation_thread!
219
+ executions.list(agent_id, after: after, limit: limit)
220
+ end
221
+
222
+ # Agent's snapshot fence, independent of any physical schema.
223
+ def assert_agent_watermark!(agent_id:, agent_revision:, journal_position:)
224
+ @records.assert_agent_watermark!(agent_id: agent_id, agent_revision: agent_revision, journal_position: journal_position)
225
+ end
226
+
227
+ private
228
+
229
+ def authorized_argument_values(entry)
230
+ # Unit 6 persists the exact validated snapshot. Older records encoded
231
+ # legacy raw params, whose explicit optional nil meant omission.
232
+ entry.key?("validated_arguments") ? entry.fetch("validated_arguments") : entry.fetch("arguments").compact
233
+ end
234
+
235
+ def with_scope(scope, &block)
236
+ assert_observation_thread!
237
+ scope ? participate(scope, &block) : transaction(&block)
238
+ end
239
+
240
+ def assert_execution_owner!(execution, agent_id)
241
+ unless execution.agent_id == agent_id.to_s
242
+ raise Phronomy::Persistence::StateConflictError, "Execution owner mismatch"
243
+ end
244
+ end
245
+
246
+ def assert_observation_thread!
247
+ if Phronomy::WaitPolicy.blocking_forbidden?
248
+ raise Phronomy::EventLoopReentrancyError, "Durable observation cannot block EventLoop"
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../common/error"
3
+ require_relative "../common/error"
4
4
 
5
5
  module Phronomy
6
6
  # Raised when an Application stream callback fails and