phronomy 0.27.0 → 0.29.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 (517) 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 +28 -7
  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/application-recipes.md +229 -0
  11. data/docs/architecture/agent-chat-and-state-ownership.md +7 -1
  12. data/docs/architecture/agent-transition-ownership.md +13 -89
  13. data/docs/architecture/backend-groups.md +152 -0
  14. data/docs/architecture/before-llm-input.md +3 -3
  15. data/docs/architecture/context-management.md +1 -1
  16. data/docs/architecture/dx05-agent-scope.md +119 -0
  17. data/docs/architecture/dx05-workflow-task-state.md +134 -0
  18. data/docs/architecture/error-api-review.md +245 -0
  19. data/docs/architecture/execution-abstraction-review.md +88 -0
  20. data/docs/architecture/execution-boundaries.md +28 -0
  21. data/docs/architecture/execution-metadata-and-values.md +6 -0
  22. data/docs/architecture/multi-agent-handoff.md +6 -5
  23. data/docs/architecture/persistence.md +27 -9
  24. data/docs/architecture/r8-unit1.md +104 -0
  25. data/docs/architecture/r8-unit10.md +72 -0
  26. data/docs/architecture/r8-unit11.md +71 -0
  27. data/docs/architecture/r8-unit12.md +67 -0
  28. data/docs/architecture/r8-unit13.md +61 -0
  29. data/docs/architecture/r8-unit14.md +72 -0
  30. data/docs/architecture/r8-unit15.md +65 -0
  31. data/docs/architecture/r8-unit16.md +54 -0
  32. data/docs/architecture/r8-unit17.md +73 -0
  33. data/docs/architecture/r8-unit18.md +92 -0
  34. data/docs/architecture/r8-unit19.md +61 -0
  35. data/docs/architecture/r8-unit2.md +55 -0
  36. data/docs/architecture/r8-unit20.md +51 -0
  37. data/docs/architecture/r8-unit21.md +79 -0
  38. data/docs/architecture/r8-unit22.md +74 -0
  39. data/docs/architecture/r8-unit23.md +81 -0
  40. data/docs/architecture/r8-unit3.md +130 -0
  41. data/docs/architecture/r8-unit4.md +79 -0
  42. data/docs/architecture/r8-unit5.md +115 -0
  43. data/docs/architecture/r8-unit6.md +71 -0
  44. data/docs/architecture/r8-unit7.md +70 -0
  45. data/docs/architecture/r8-unit8.md +78 -0
  46. data/docs/architecture/r8-unit9.md +73 -0
  47. data/docs/architecture/refactoring-closure.md +10 -1
  48. data/docs/architecture/remaining-refactoring-plan.md +16 -0
  49. data/docs/architecture/storage-execution-boundaries.md +54 -0
  50. data/docs/architecture/tool-schema-recording-gap.md +6 -0
  51. data/docs/architecture/tracing.md +1 -1
  52. data/docs/architecture/workflow-terminal-ownership-design.md +2 -2
  53. data/docs/architecture.md +24 -11
  54. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +3 -3
  55. data/docs/decisions/027-llm-adapter-provider-boundary.md +2 -0
  56. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +2 -0
  57. data/docs/decisions/031-durable-multi-agent-coordination.md +2 -0
  58. data/docs/decisions/032-storage-backend-composition.md +5 -0
  59. data/docs/decisions/034-handoff-runner-coordination-ownership.md +2 -0
  60. data/docs/decisions/035-tool-executor-capability-ownership.md +1 -1
  61. data/docs/decisions/038-responsibility-based-source-layout.md +14 -0
  62. data/docs/decisions/039-runtime-configuration-lifecycle-ownership.md +8 -0
  63. data/docs/decisions/040-configuration-default-composition.md +12 -1
  64. data/docs/decisions/045-worker-input-restriction-ownership.md +4 -0
  65. data/docs/decisions/051-execution-outcome-worker-ownership.md +2 -0
  66. data/docs/decisions/056-workflow-terminal-policy-ownership.md +1 -1
  67. data/docs/decisions/058-neutral-storage-primitives.md +5 -0
  68. data/docs/decisions/059-backend-contracts-and-async-clients.md +74 -0
  69. data/docs/decisions/060-runtime-settings-and-application-configuration.md +86 -0
  70. data/docs/decisions/061-persistence-failure-contracts.md +89 -0
  71. data/docs/decisions/062-content-store-service.md +52 -0
  72. data/docs/decisions/063-execution-contracts-and-services.md +88 -0
  73. data/docs/decisions/064-agent-execution-and-multi-agent-responsibility.md +36 -0
  74. data/docs/decisions/065-vector-and-embeddings-common-operations.md +38 -0
  75. data/docs/decisions/066-llm-and-tool-operation-contracts.md +20 -0
  76. data/docs/decisions/067-agent-progress-and-engine-connection.md +29 -0
  77. data/docs/decisions/068-agent-tool-child-progress-and-tool-operation.md +36 -0
  78. data/docs/decisions/README.md +18 -1
  79. data/docs/execution-boundaries-preview.md +45 -0
  80. data/docs/failure-reason-codes.md +45 -0
  81. data/docs/features.md +12 -4
  82. data/docs/getting-started.md +52 -48
  83. data/docs/migrations/0.22.md +2 -2
  84. data/docs/migrations/durable-semantic-coordination-v2.md +2 -0
  85. data/docs/migrations/handoff-runner-multi-agent.md +2 -0
  86. data/docs/migrations/persistence-failure-contracts.md +67 -0
  87. data/docs/migrations/r8-unit10.md +36 -0
  88. data/docs/migrations/r8-unit11.md +33 -0
  89. data/docs/migrations/r8-unit12.md +31 -0
  90. data/docs/migrations/r8-unit13.md +36 -0
  91. data/docs/migrations/r8-unit14.md +31 -0
  92. data/docs/migrations/r8-unit15.md +31 -0
  93. data/docs/migrations/r8-unit16.md +26 -0
  94. data/docs/migrations/r8-unit17.md +28 -0
  95. data/docs/migrations/r8-unit18.md +29 -0
  96. data/docs/migrations/r8-unit19.md +28 -0
  97. data/docs/migrations/r8-unit20.md +24 -0
  98. data/docs/migrations/r8-unit21.md +28 -0
  99. data/docs/migrations/r8-unit22.md +29 -0
  100. data/docs/migrations/r8-unit23.md +20 -0
  101. data/docs/migrations/r8-unit4.md +67 -0
  102. data/docs/migrations/r8-unit5.md +75 -0
  103. data/docs/migrations/r8-unit6.md +109 -0
  104. data/docs/migrations/r8-unit7.md +31 -0
  105. data/docs/migrations/r8-unit8.md +33 -0
  106. data/docs/migrations/r8-unit9.md +28 -0
  107. data/docs/migrations/storage-async-client.md +103 -0
  108. data/docs/migrations/vector-async-clients.md +110 -0
  109. data/docs/ordered-event-delivery.md +54 -0
  110. data/docs/persistence-backends.md +38 -21
  111. data/docs/runtime-and-concurrency.md +26 -1
  112. data/docs/workflow-completion.md +92 -0
  113. data/gemfiles/http_minimum.gemfile +5 -0
  114. data/lib/phronomy/agent/admission.rb +78 -0
  115. data/lib/phronomy/agent/{lifecycle_contract/agent_already_exists_error.rb → agent_already_exists_error.rb} +1 -1
  116. data/lib/phronomy/agent/{lifecycle_contract/agent_busy_error.rb → agent_busy_error.rb} +1 -1
  117. data/lib/phronomy/agent/agent_not_found_error.rb +16 -0
  118. data/lib/phronomy/agent/{lifecycle_contract/agent_purged_error.rb → agent_purged_error.rb} +1 -1
  119. data/lib/phronomy/agent/agent_unloaded_error.rb +14 -0
  120. data/lib/phronomy/agent/api/agent.rb +13 -0
  121. data/lib/phronomy/{recovery → agent/api}/execution_rehydration_required_error.rb +1 -1
  122. data/lib/phronomy/agent/async_event_api.rb +132 -103
  123. data/lib/phronomy/agent/base.rb +146 -113
  124. data/lib/phronomy/agent/before_llm_input.rb +70 -0
  125. data/lib/phronomy/agent/composition/run_once.rb +1 -1
  126. data/lib/phronomy/agent/{context_assembly/context_candidate_resolver.rb → context_candidate_resolver.rb} +2 -2
  127. data/lib/phronomy/agent/{context_assembly/context_assembler.rb → context_preparation.rb} +35 -221
  128. data/lib/phronomy/agent/control_request.rb +21 -0
  129. data/lib/phronomy/agent/execution/agent_execution.rb +1 -1
  130. data/lib/phronomy/agent/execution/agent_invocation.rb +27 -53
  131. data/lib/phronomy/agent/execution/approval_resume_commit.rb +1 -1
  132. data/lib/phronomy/agent/execution/dispatch_preparation.rb +17 -127
  133. data/lib/phronomy/agent/execution/exact_execution.rb +55 -32
  134. data/lib/phronomy/agent/execution/execution_cancellation.rb +8 -4
  135. data/lib/phronomy/agent/execution/execution_coordinator.rb +229 -407
  136. data/lib/phronomy/agent/execution/execution_metadata.rb +1 -0
  137. data/lib/phronomy/agent/execution/execution_outcome_committer.rb +71 -127
  138. data/lib/phronomy/agent/execution/execution_registry.rb +6 -2
  139. data/lib/phronomy/agent/execution/execution_session_runner.rb +22 -27
  140. data/lib/phronomy/agent/execution/initial_preparation.rb +19 -120
  141. data/lib/phronomy/agent/execution/{agent_invocation_session_builder.rb → invocation_actions.rb} +54 -194
  142. data/lib/phronomy/agent/execution/invocation_transitions.rb +39 -1
  143. data/lib/phronomy/agent/execution/runtime_record_encoder.rb +8 -11
  144. data/lib/phronomy/agent/execution_change.rb +161 -0
  145. data/lib/phronomy/agent/execution_environment.rb +36 -0
  146. data/lib/phronomy/agent/execution_extension_state.rb +21 -0
  147. data/lib/phronomy/agent/execution_observation.rb +35 -0
  148. data/lib/phronomy/agent/filterable.rb +188 -0
  149. data/lib/phronomy/agent/handoff/{handoff_context.rb → transfer_context.rb} +4 -4
  150. data/lib/phronomy/agent/handoff/{handoff_projection.rb → transfer_projection.rb} +14 -33
  151. data/lib/phronomy/{llm_contract/transport_error.rb → agent/handoff_error.rb} +1 -1
  152. data/lib/phronomy/agent/journal/journal_projection.rb +1 -1
  153. data/lib/phronomy/agent/knowledge_item.rb +13 -0
  154. data/lib/phronomy/agent/lifecycle/agent_root.rb +1 -1
  155. data/lib/phronomy/agent/lifecycle/lifetime_api.rb +76 -0
  156. data/lib/phronomy/agent/lifecycle/operation_lease.rb +33 -0
  157. data/lib/phronomy/agent/lifecycle/operation_result.rb +53 -0
  158. data/lib/phronomy/agent/lifecycle/ownership_registry.rb +117 -31
  159. data/lib/phronomy/agent/lifecycle/scope.rb +198 -0
  160. data/lib/phronomy/agent/lifecycle/scope_cleanup_error.rb +19 -0
  161. data/lib/phronomy/agent/lifecycle/unload_transition.rb +78 -0
  162. data/lib/phronomy/agent/persistence/agent_repository.rb +29 -21
  163. data/lib/phronomy/agent/persistence/cancellation_repository.rb +51 -0
  164. data/lib/phronomy/agent/persistence/codec.rb +1 -21
  165. data/lib/phronomy/agent/persistence/execution_repository.rb +57 -41
  166. data/lib/phronomy/agent/persistence/journal_repository.rb +27 -18
  167. data/lib/phronomy/agent/persistence/records.rb +33 -0
  168. data/lib/phronomy/agent/persistence/retention_repository.rb +70 -0
  169. data/lib/phronomy/agent/persistence/storage_schema.rb +7 -2
  170. data/lib/phronomy/agent/persistence/watermark.rb +13 -11
  171. data/lib/phronomy/agent/recovery/invocation_restorer.rb +13 -21
  172. data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +13 -12
  173. data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +34 -34
  174. data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +33 -39
  175. data/lib/phronomy/agent/recovery/recovery_coordinator.rb +28 -25
  176. data/lib/phronomy/agent/recovery/recovery_rules.rb +119 -0
  177. data/lib/phronomy/agent/recovery/recovery_support.rb +10 -19
  178. data/lib/phronomy/agent/reserved_execution.rb +16 -0
  179. data/lib/phronomy/agent/retention.rb +17 -0
  180. data/lib/phronomy/agent/runtime_binding/engine_environment.rb +60 -0
  181. data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +34 -0
  182. data/lib/phronomy/agent/runtime_binding/phase_machine_builder.rb +64 -0
  183. data/lib/phronomy/agent/runtime_binding/tool_session_builder.rb +34 -0
  184. data/lib/phronomy/agent/{context_assembly/ruby_llm_materializer.rb → runtime_input.rb} +18 -35
  185. data/lib/phronomy/agent/{context_assembly/saved_context_reader.rb → saved_context_reader.rb} +6 -4
  186. data/lib/phronomy/agent/settings.rb +47 -0
  187. data/lib/phronomy/agent/{context_assembly/state_writer.rb → state_writer.rb} +4 -3
  188. data/lib/phronomy/agent/store.rb +253 -0
  189. data/lib/phronomy/agent/{lifecycle_contract/stream_callback_error.rb → stream_callback_error.rb} +3 -3
  190. data/lib/phronomy/agent/tool_execution/tool_binding.rb +4 -64
  191. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +50 -219
  192. data/lib/phronomy/agent/tool_execution/tool_invocation_actions.rb +107 -0
  193. data/lib/phronomy/agent/tool_execution/tool_invocation_transitions.rb +70 -0
  194. data/lib/phronomy/common/error.rb +65 -1
  195. data/lib/phronomy/configuration/runtime_settings.rb +49 -0
  196. data/lib/phronomy/content_store/base.rb +7 -2
  197. data/lib/phronomy/content_store/storage_schema.rb +1 -0
  198. data/lib/phronomy/content_store/stored_contents.rb +2 -1
  199. data/lib/phronomy/context/assembly.rb +195 -0
  200. data/lib/phronomy/{llm_contract/context_budget_exceeded_error.rb → context/budget_exceeded_error.rb} +3 -1
  201. data/lib/phronomy/context/candidate.rb +51 -0
  202. data/lib/phronomy/context/constraint.rb +47 -0
  203. data/lib/phronomy/{agent/context_contract → context}/context_plan.rb +1 -1
  204. data/lib/phronomy/{agent/context_contract → context}/context_plan_validator.rb +3 -3
  205. data/lib/phronomy/{agent/context_contract → context}/context_policy.rb +1 -1
  206. data/lib/phronomy/{agent/context_contract → context}/context_policy_input.rb +1 -1
  207. data/lib/phronomy/{agent/context_assembly → context}/context_policy_input_builder.rb +1 -1
  208. data/lib/phronomy/context/default_policy.rb +140 -0
  209. data/lib/phronomy/context/final_budget_validator.rb +33 -0
  210. data/lib/phronomy/{agent/context_contract → context}/llm_input_build_context.rb +1 -1
  211. data/lib/phronomy/{agent/context_contract → context}/llm_input_manifest.rb +19 -19
  212. data/lib/phronomy/{agent/context_contract → context}/llm_input_patch.rb +1 -1
  213. data/lib/phronomy/context/manifest_error.rb +10 -0
  214. data/lib/phronomy/context/prompt_template.rb +98 -0
  215. data/lib/phronomy/{vector_store → documents}/loader/base.rb +1 -1
  216. data/lib/phronomy/{vector_store → documents}/loader/csv_loader.rb +2 -2
  217. data/lib/phronomy/{vector_store → documents}/loader/markdown_loader.rb +3 -3
  218. data/lib/phronomy/{vector_store → documents}/loader/plain_text_loader.rb +2 -2
  219. data/lib/phronomy/{vector_store → documents}/splitter/base.rb +1 -1
  220. data/lib/phronomy/{vector_store → documents}/splitter/fixed_size_splitter.rb +2 -2
  221. data/lib/phronomy/{vector_store → documents}/splitter/recursive_splitter.rb +2 -2
  222. data/lib/phronomy/embeddings/async/async_client.rb +44 -0
  223. data/lib/phronomy/embeddings/authentication_error.rb +11 -0
  224. data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +52 -0
  225. data/lib/phronomy/embeddings/base.rb +43 -0
  226. data/lib/phronomy/embeddings/invalid_result_error.rb +11 -0
  227. data/lib/phronomy/embeddings/rate_limit_error.rb +11 -0
  228. data/lib/phronomy/embeddings/transport_error.rb +11 -0
  229. data/lib/phronomy/engine/concurrency/offload_pool.rb +36 -21
  230. data/lib/phronomy/engine/concurrency/pool_registry.rb +47 -12
  231. data/lib/phronomy/engine/diagnostics.rb +1 -1
  232. data/lib/phronomy/engine/event_loop.rb +7 -7
  233. data/lib/phronomy/engine/execution_binding.rb +23 -0
  234. data/lib/phronomy/engine/execution_receiver_binding.rb +43 -0
  235. data/lib/phronomy/engine/fsm_protocol.rb +1 -6
  236. data/lib/phronomy/engine/fsm_session.rb +3 -3
  237. data/lib/phronomy/engine/metrics.rb +16 -14
  238. data/lib/phronomy/engine/runtime/timer_queue.rb +1 -1
  239. data/lib/phronomy/engine/runtime.rb +25 -12
  240. data/lib/phronomy/engine/terminal_decision.rb +7 -0
  241. data/lib/phronomy/execution/async_operation.rb +41 -0
  242. data/lib/phronomy/{engine → execution}/blocking.rb +2 -2
  243. data/lib/phronomy/{engine → execution}/concurrency/cancellation_token.rb +1 -1
  244. data/lib/phronomy/{engine → execution}/concurrency/deadline.rb +0 -17
  245. data/lib/phronomy/{engine → execution}/concurrency/physical_completion_task.rb +1 -1
  246. data/lib/phronomy/{engine → execution}/concurrency/result_collector.rb +1 -1
  247. data/lib/phronomy/{engine → execution}/concurrency/result_composition.rb +33 -12
  248. data/lib/phronomy/{engine/concurrency/subscriptions.rb → execution/concurrency/result_subscriptions.rb} +14 -18
  249. data/lib/phronomy/execution/concurrency/subscriptions.rb +25 -0
  250. data/lib/phronomy/{engine → execution}/execution.rb +61 -27
  251. data/lib/phronomy/{engine → execution}/execution_cancellation_error.rb +2 -2
  252. data/lib/phronomy/execution/execution_receiver.rb +78 -0
  253. data/lib/phronomy/execution/execution_scope_state.rb +35 -0
  254. data/lib/phronomy/{engine → execution}/execution_timeout_error.rb +2 -2
  255. data/lib/phronomy/{engine → execution}/invocation_context.rb +0 -15
  256. data/lib/phronomy/execution/invocation_controls.rb +73 -0
  257. data/lib/phronomy/execution/runnable.rb +24 -0
  258. data/lib/phronomy/{engine → execution}/task_result.rb +9 -4
  259. data/lib/phronomy/execution/wait_policy.rb +24 -0
  260. data/lib/phronomy/filter/{contract/filter_block_error.rb → filter_block_error.rb} +3 -3
  261. data/lib/phronomy/generation/generator_verifier/agent_operation.rb +65 -0
  262. data/lib/phronomy/generation/generator_verifier/default_parser.rb +27 -0
  263. data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +2 -2
  264. data/lib/phronomy/generation/generator_verifier.rb +7 -7
  265. data/lib/phronomy/generation/low_confidence_error.rb +2 -2
  266. data/lib/phronomy/integrations/ordered_event_delivery.rb +208 -0
  267. data/lib/phronomy/llm_adapter/async/async_client.rb +47 -0
  268. data/lib/phronomy/{llm_contract → llm_adapter}/authentication_error.rb +3 -1
  269. data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +183 -0
  270. data/lib/phronomy/llm_adapter/base.rb +49 -68
  271. data/lib/phronomy/{llm_contract → llm_adapter}/context_length_error.rb +3 -1
  272. data/lib/phronomy/llm_adapter/invalid_result_error.rb +10 -0
  273. data/lib/phronomy/llm_adapter/message.rb +62 -0
  274. data/lib/phronomy/{llm_contract → llm_adapter}/rate_limit_error.rb +3 -1
  275. data/lib/phronomy/llm_adapter/request.rb +79 -0
  276. data/lib/phronomy/llm_adapter/response.rb +36 -0
  277. data/lib/phronomy/llm_adapter/stream_chunk.rb +18 -0
  278. data/lib/phronomy/llm_adapter/token_usage.rb +64 -0
  279. data/lib/phronomy/llm_adapter/transport_error.rb +9 -0
  280. data/lib/phronomy/llm_adapter.rb +3 -16
  281. data/lib/phronomy/multi_agent/admission_registry.rb +1 -8
  282. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +108 -84
  283. data/lib/phronomy/multi_agent/execution_environment.rb +29 -0
  284. data/lib/phronomy/{agent/handoff → multi_agent}/handoff.rb +2 -2
  285. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_capability_factory.rb +21 -2
  286. data/lib/phronomy/multi_agent/handoff_participant.rb +79 -0
  287. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_policy.rb +1 -1
  288. data/lib/phronomy/multi_agent/handoff_runner.rb +77 -100
  289. data/lib/phronomy/{agent/handoff → multi_agent}/handoff_state.rb +1 -1
  290. data/lib/phronomy/multi_agent/orchestrator.rb +61 -75
  291. data/lib/phronomy/multi_agent/persistence/codec.rb +20 -0
  292. data/lib/phronomy/multi_agent/persistence/handoff_state_repository.rb +77 -0
  293. data/lib/phronomy/multi_agent/persistence/records.rb +27 -0
  294. data/lib/phronomy/multi_agent/persistence/storage_schema.rb +21 -0
  295. data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +60 -44
  296. data/lib/phronomy/multi_agent/persistence/team_repository.rb +29 -21
  297. data/lib/phronomy/multi_agent/reserved_child_admission.rb +88 -0
  298. data/lib/phronomy/multi_agent/runtime_binding/engine_environment.rb +35 -0
  299. data/lib/phronomy/multi_agent/shared_state.rb +2 -2
  300. data/lib/phronomy/multi_agent/store.rb +156 -0
  301. data/lib/phronomy/multi_agent/team_busy_error.rb +18 -0
  302. data/lib/phronomy/multi_agent/team_coordinator.rb +99 -77
  303. data/lib/phronomy/multi_agent/team_not_found_error.rb +18 -0
  304. data/lib/phronomy/multi_agent/team_ownership_registry.rb +2 -12
  305. data/lib/phronomy/output_parser/parse_error.rb +7 -0
  306. data/lib/phronomy/persistence/content_repository.rb +26 -0
  307. data/lib/phronomy/persistence/errors.rb +16 -0
  308. data/lib/phronomy/persistence/migration/initial_format_migration.rb +2 -2
  309. data/lib/phronomy/persistence/persistence.rb +23 -0
  310. data/lib/phronomy/persistence/save_outcome.rb +24 -0
  311. data/lib/phronomy/persistence/snapshot_comparison.rb +81 -0
  312. data/lib/phronomy/persistence/storage_boundary.rb +32 -0
  313. data/lib/phronomy/persistence/transaction.rb +86 -0
  314. data/lib/phronomy/persistence_composition/storage_schema.rb +9 -4
  315. data/lib/phronomy/persistence_composition/stores.rb +34 -0
  316. data/lib/phronomy/persistence_composition/unit4_migration.rb +276 -0
  317. data/lib/phronomy/runtime_composition/agent_defaults.rb +5 -1
  318. data/lib/phronomy/runtime_composition/configuration.rb +146 -0
  319. data/lib/phronomy/runtime_composition/evaluation_defaults.rb +7 -0
  320. data/lib/phronomy/runtime_composition/execution_defaults.rb +5 -0
  321. data/lib/phronomy/runtime_composition/generation_defaults.rb +6 -0
  322. data/lib/phronomy/runtime_composition/global_configuration.rb +38 -0
  323. data/lib/phronomy/runtime_composition/multi_agent_defaults.rb +9 -0
  324. data/lib/phronomy/runtime_composition/workflow_defaults.rb +4 -0
  325. data/lib/phronomy/storage/async/async_client.rb +60 -0
  326. data/lib/phronomy/storage/backend.rb +8 -2
  327. data/lib/phronomy/storage/condition_failed_error.rb +2 -2
  328. data/lib/phronomy/storage/unique_constraint_error.rb +2 -2
  329. data/lib/phronomy/testing/eval/metrics.rb +1 -1
  330. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +19 -3
  331. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +2 -2
  332. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +6 -6
  333. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +55 -52
  334. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +3 -3
  335. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +7 -7
  336. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +11 -11
  337. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +57 -51
  338. data/lib/phronomy/testing/persistence_contract/neutral_storage_primitives.rb +1 -1
  339. data/lib/phronomy/testing/persistence_contract/storage_transaction_boundaries.rb +23 -22
  340. data/lib/phronomy/tool/authorization.rb +77 -0
  341. data/lib/phronomy/tool/base.rb +551 -9
  342. data/lib/phronomy/tool/call_request.rb +40 -0
  343. data/lib/phronomy/{agent/tool_execution/tool_definition_set.rb → tool/definition_set.rb} +7 -8
  344. data/lib/phronomy/tool/operation.rb +88 -0
  345. data/lib/phronomy/tool/schema.rb +214 -0
  346. data/lib/phronomy/tool/settings.rb +31 -0
  347. data/lib/phronomy/tool/tool_error.rb +7 -0
  348. data/lib/phronomy/tool/tool_executor.rb +58 -0
  349. data/lib/phronomy/tools/agent.rb +22 -90
  350. data/lib/phronomy/tools/mcp.rb +21 -14
  351. data/lib/phronomy/tools/vector_search.rb +4 -4
  352. data/lib/phronomy/tracing/automatic.rb +11 -6
  353. data/lib/phronomy/tracing/base.rb +36 -7
  354. data/lib/phronomy/tracing/observation.rb +37 -0
  355. data/lib/phronomy/tracing/open_telemetry_tracer.rb +4 -29
  356. data/lib/phronomy/tracing/settings.rb +36 -0
  357. data/lib/phronomy/vector_store/{async_backend.rb → async/async_client.rb} +35 -29
  358. data/lib/phronomy/vector_store/{in_memory.rb → backends/in_memory.rb} +12 -10
  359. data/lib/phronomy/vector_store/{pgvector.rb → backends/pgvector.rb} +44 -16
  360. data/lib/phronomy/vector_store/{redis_search.rb → backends/redis_search.rb} +74 -37
  361. data/lib/phronomy/vector_store/base.rb +118 -49
  362. data/lib/phronomy/vector_store/invalid_result_error.rb +12 -0
  363. data/lib/phronomy/vector_store.rb +2 -17
  364. data/lib/phronomy/version.rb +1 -1
  365. data/lib/phronomy/workflow/completion.rb +11 -0
  366. data/lib/phronomy/workflow/execution/workflow.rb +37 -9
  367. data/lib/phronomy/workflow/execution/workflow_action_rules.rb +67 -0
  368. data/lib/phronomy/workflow/execution/workflow_context.rb +2 -2
  369. data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +28 -0
  370. data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +77 -8
  371. data/lib/phronomy/workflow/execution/workflow_runner.rb +105 -92
  372. data/lib/phronomy/workflow/execution/workflow_settings.rb +28 -0
  373. data/lib/phronomy/workflow/execution/workflow_task_execution.rb +153 -0
  374. data/lib/phronomy/workflow/execution/workflow_task_reception.rb +72 -0
  375. data/lib/phronomy/workflow/execution/workflow_task_resume_error.rb +14 -0
  376. data/lib/phronomy/workflow/persistence/state_repository.rb +28 -22
  377. data/lib/phronomy/workflow/persistence/storage_schema.rb +13 -0
  378. data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +61 -0
  379. data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +158 -0
  380. data/lib/phronomy/workflow/runtime_binding/workflow_task_session.rb +76 -0
  381. data/lib/phronomy/workflow/{execution → runtime_binding}/workflow_terminal_policy.rb +4 -4
  382. data/lib/phronomy.rb +63 -16
  383. data/scripts/api_snapshot.rb +47 -15
  384. data/scripts/check_readme_ruby.rb +24 -29
  385. data/scripts/check_readme_runnable.rb +92 -81
  386. data/sig/_private/phronomy/agent/settings.rbs +19 -0
  387. data/sig/_private/phronomy/configuration/runtime_settings.rbs +20 -0
  388. data/sig/_private/phronomy/llm_adapter/async/async_client.rbs +10 -0
  389. data/sig/_private/phronomy/multi_agent/store.rbs +8 -0
  390. data/sig/_private/phronomy/persistence/storage_boundary.rbs +7 -0
  391. data/sig/_private/phronomy/tool/settings.rbs +13 -0
  392. data/sig/_private/phronomy/tracing/settings.rbs +15 -0
  393. data/sig/_private/phronomy/workflow/execution/workflow_settings.rbs +13 -0
  394. data/sig/phronomy/agent/admission.rbs +19 -0
  395. data/sig/phronomy/agent/agent_not_found_error.rbs +6 -0
  396. data/sig/phronomy/agent/agent_unloaded_error.rbs +6 -0
  397. data/sig/phronomy/agent/execution_contract.rbs +132 -0
  398. data/sig/phronomy/agent/execution_environment.rbs +20 -0
  399. data/sig/phronomy/agent/store.rbs +73 -0
  400. data/sig/phronomy/agent/stream_callback_error.rbs +8 -0
  401. data/sig/phronomy/agent.rbs +29 -135
  402. data/sig/phronomy/common.rbs +7 -0
  403. data/sig/phronomy/content_store/contracts.rbs +13 -0
  404. data/sig/phronomy/context.rbs +152 -0
  405. data/sig/phronomy/embeddings/async/async_client.rbs +9 -0
  406. data/sig/phronomy/embeddings/base.rbs +23 -0
  407. data/sig/phronomy/engine/concurrency/timer_bindings.rbs +22 -0
  408. data/sig/phronomy/engine/control_bindings.rbs +11 -0
  409. data/sig/phronomy/engine/control_protocols.rbs +23 -0
  410. data/sig/phronomy/engine/execution_binding.rbs +6 -0
  411. data/sig/phronomy/engine/execution_receiver.rbs +15 -0
  412. data/sig/phronomy/engine/observability.rbs +13 -0
  413. data/sig/phronomy/execution/async_operation.rbs +7 -0
  414. data/sig/phronomy/execution/blocking.rbs +5 -0
  415. data/sig/phronomy/execution/concurrency/cancellation_token.rbs +13 -0
  416. data/sig/phronomy/execution/concurrency/deadline.rbs +10 -0
  417. data/sig/phronomy/execution/concurrency/result_composition.rbs +7 -0
  418. data/sig/phronomy/execution/control_protocols.rbs +18 -0
  419. data/sig/phronomy/execution/errors.rbs +7 -0
  420. data/sig/phronomy/execution/execution.rbs +41 -0
  421. data/sig/phronomy/execution/execution_receiver.rbs +40 -0
  422. data/sig/phronomy/execution/execution_scope_state.rbs +9 -0
  423. data/sig/phronomy/execution/invocation_context.rbs +18 -0
  424. data/sig/phronomy/execution/invocation_controls.rbs +13 -0
  425. data/sig/phronomy/execution/task_result.rbs +29 -0
  426. data/sig/phronomy/extensions.rbs +1 -21
  427. data/sig/phronomy/filter/filter_block_error.rbs +6 -0
  428. data/sig/phronomy/generation/low_confidence_error.rbs +6 -0
  429. data/sig/phronomy/handoff.rbs +10 -4
  430. data/sig/phronomy/integrations.rbs +14 -0
  431. data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +6 -0
  432. data/sig/phronomy/llm_adapter/base.rbs +14 -0
  433. data/sig/phronomy/llm_adapter/usage.rbs +14 -0
  434. data/sig/phronomy/llm_adapter/values.rbs +43 -0
  435. data/sig/phronomy/llm_context_window.rbs +10 -0
  436. data/sig/phronomy/multi_agent/reserved_child_admission.rbs +8 -0
  437. data/sig/phronomy/multi_agent/store.rbs +54 -0
  438. data/sig/phronomy/multi_agent/team_busy_error.rbs +8 -0
  439. data/sig/phronomy/multi_agent/team_not_found_error.rbs +8 -0
  440. data/sig/phronomy/multi_agent.rbs +6 -3
  441. data/sig/phronomy/persistence/errors.rbs +18 -0
  442. data/sig/phronomy/persistence/migration.rbs +13 -0
  443. data/sig/phronomy/persistence/transaction.rbs +19 -0
  444. data/sig/phronomy/persistence.rbs +1 -99
  445. data/sig/phronomy/persistence_composition.rbs +19 -0
  446. data/sig/phronomy/runtime_composition/configuration.rbs +36 -0
  447. data/sig/phronomy/storage/async/async_client.rbs +8 -0
  448. data/sig/phronomy/storage/backends/in_memory.rbs +9 -0
  449. data/sig/phronomy/{storage.rbs → storage/contracts.rbs} +4 -18
  450. data/sig/phronomy/tool.rbs +48 -4
  451. data/sig/phronomy/tracing/observation.rbs +7 -0
  452. data/sig/phronomy/vector_store/async/async_client.rbs +13 -0
  453. data/sig/phronomy/vector_store/base.rbs +23 -0
  454. data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +15 -0
  455. data/sig/phronomy/workflow/runtime_binding/workflow_terminal_policy.rbs +7 -0
  456. data/sig/phronomy/workflow/store.rbs +8 -0
  457. data/sig/phronomy/workflow.rbs +5 -1
  458. data/sig/phronomy.rbs +4 -49
  459. metadata +365 -117
  460. data/lib/phronomy/agent/concerns/before_llm_input.rb +0 -66
  461. data/lib/phronomy/agent/concerns/error_translation.rb +0 -45
  462. data/lib/phronomy/agent/concerns/filterable.rb +0 -184
  463. data/lib/phronomy/agent/context/capability/base.rb +0 -548
  464. data/lib/phronomy/agent/context/capability/tool_executor.rb +0 -62
  465. data/lib/phronomy/agent/context/instruction/prompt_template.rb +0 -102
  466. data/lib/phronomy/agent/context_assembly/runtime_chat_builder.rb +0 -36
  467. data/lib/phronomy/agent/context_assembly/token_budget_resolver.rb +0 -22
  468. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +0 -37
  469. data/lib/phronomy/agent/context_policies/default.rb +0 -142
  470. data/lib/phronomy/agent/execution/phase_machine_builder.rb +0 -101
  471. data/lib/phronomy/agent/execution/provider_call_outcome.rb +0 -106
  472. data/lib/phronomy/agent/handoff/handoff_execution_coordinator.rb +0 -15
  473. data/lib/phronomy/agent/handoff/handoff_outcome_committer.rb +0 -131
  474. data/lib/phronomy/agent/handoff/handoff_request.rb +0 -45
  475. data/lib/phronomy/agent/lifecycle_contract/handoff_error.rb +0 -7
  476. data/lib/phronomy/agent/persistence/handoff_state_repository.rb +0 -58
  477. data/lib/phronomy/agent/persistence/queries.rb +0 -61
  478. data/lib/phronomy/agent/selection/candidate.rb +0 -53
  479. data/lib/phronomy/agent/selection/constraint.rb +0 -49
  480. data/lib/phronomy/agent/tool_execution/tool_call_intercepted.rb +0 -29
  481. data/lib/phronomy/agent/tool_execution/tool_invocation_session_builder.rb +0 -276
  482. data/lib/phronomy/configuration/configuration.rb +0 -68
  483. data/lib/phronomy/configuration/global_configuration.rb +0 -26
  484. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +0 -124
  485. data/lib/phronomy/engine/concurrency/operation_binding.rb +0 -43
  486. data/lib/phronomy/engine/execution_receiver.rb +0 -65
  487. data/lib/phronomy/engine/runnable.rb +0 -54
  488. data/lib/phronomy/llm_adapter/ruby_llm.rb +0 -47
  489. data/lib/phronomy/llm_contract/token_usage.rb +0 -85
  490. data/lib/phronomy/multi_agent/persistence/queries.rb +0 -30
  491. data/lib/phronomy/multi_agent/storage_contract/team_storage_schema.rb +0 -15
  492. data/lib/phronomy/output_parser/contract/parse_error.rb +0 -7
  493. data/lib/phronomy/persistence/api/persistence.rb +0 -140
  494. data/lib/phronomy/persistence_composition/repositories.rb +0 -77
  495. data/lib/phronomy/recovery/recovery.rb +0 -186
  496. data/lib/phronomy/tool/contract/tool_error.rb +0 -7
  497. data/lib/phronomy/vector_store/embeddings/base.rb +0 -44
  498. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +0 -47
  499. data/lib/phronomy/workflow/phase_machine_builder.rb +0 -205
  500. data/lib/phronomy/workflow/storage_contract/workflow_storage_schema.rb +0 -9
  501. data/sig/phronomy/execution_receiver.rbs +0 -34
  502. data/sig/phronomy/llm_adapter.rbs +0 -11
  503. data/sig/phronomy/runtime.rbs +0 -76
  504. /data/lib/phronomy/agent/{context_assembly/context_importer.rb → context_importer.rb} +0 -0
  505. /data/lib/phronomy/{engine → execution}/backpressure_error.rb +0 -0
  506. /data/lib/phronomy/{engine → execution}/cancellation_error.rb +0 -0
  507. /data/lib/phronomy/{engine → execution}/concurrency/worker_input_restricted.rb +0 -0
  508. /data/lib/phronomy/{engine → execution}/event_loop_reentrancy_error.rb +0 -0
  509. /data/lib/phronomy/{engine → execution}/invalid_async_entry_action_error.rb +0 -0
  510. /data/lib/phronomy/{engine → execution}/invalid_async_transition_action_error.rb +0 -0
  511. /data/lib/phronomy/{engine → execution}/invalid_async_workflow_action_error.rb +0 -0
  512. /data/lib/phronomy/{engine → execution}/pool_shutdown_error.rb +0 -0
  513. /data/lib/phronomy/{engine → execution}/recursion_limit_error.rb +0 -0
  514. /data/lib/phronomy/{engine → execution}/runtime_shutdown_error.rb +0 -0
  515. /data/lib/phronomy/{engine → execution}/runtime_shutdown_reentrancy_error.rb +0 -0
  516. /data/lib/phronomy/{engine → execution}/scheduler_reentrancy_error.rb +0 -0
  517. /data/lib/phronomy/{engine → execution}/timeout_error.rb +0 -0
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../engine/concurrency/worker_input_restricted"
3
+ require_relative "../execution/concurrency/worker_input_restricted"
4
4
 
5
5
  require "securerandom"
6
- require_relative "concerns/filterable"
7
- require_relative "concerns/before_llm_input"
8
- require_relative "concerns/error_translation"
6
+ require_relative "filterable"
7
+ require_relative "before_llm_input"
9
8
 
10
9
  module Phronomy
11
10
  module Agent
@@ -36,9 +35,8 @@ module Phronomy
36
35
  include Phronomy::Concurrency::WorkerInputRestricted
37
36
 
38
37
  include Phronomy::Runnable
39
- include Concerns::Filterable
40
- include Concerns::BeforeLLMInput
41
- include Concerns::ErrorTranslation
38
+ include Filterable
39
+ include BeforeLLMInput
42
40
 
43
41
  APPROVAL_CONFIGURATION_INIT_MUTEX = Mutex.new
44
42
  private_constant :APPROVAL_CONFIGURATION_INIT_MUTEX
@@ -48,7 +46,7 @@ module Phronomy
48
46
  if name
49
47
  @model = name
50
48
  else
51
- @model || Phronomy.configuration.default_model
49
+ @model || Phronomy::Agent::Settings.current.default_model
52
50
  end
53
51
  end
54
52
 
@@ -129,18 +127,18 @@ module Phronomy
129
127
  end
130
128
  end
131
129
 
132
- # Binds one Application-constructed ContextPolicy instance to this
130
+ # Binds one Application-constructed Phronomy::Context::ContextPolicy instance to this
133
131
  # Agent class. Policy binding is Application code/runtime wiring rather
134
132
  # than durable Agent state.
135
133
  def context_policy(*args)
136
134
  if args.empty?
137
135
  return @context_policy if instance_variable_defined?(:@context_policy)
138
136
  return superclass.context_policy if superclass.respond_to?(:context_policy)
139
- return ContextPolicies::Default.instance
137
+ raise Phronomy::ConfigurationError, "Agent ContextPolicy has not been configured"
140
138
  end
141
- unless args.length == 1 && args.first.is_a?(ContextPolicy)
139
+ unless args.length == 1 && args.first.is_a?(Phronomy::Context::ContextPolicy)
142
140
  raise ArgumentError,
143
- "context_policy expects one Phronomy::Agent::ContextPolicy instance"
141
+ "context_policy expects one Phronomy::Context::ContextPolicy instance"
144
142
  end
145
143
 
146
144
  @context_policy = args.first
@@ -171,6 +169,7 @@ module Phronomy
171
169
  agent_id: SecureRandom.uuid,
172
170
  context: nil,
173
171
  knowledge: [],
172
+ retention: nil,
174
173
  persistence: nil,
175
174
  metadata: {},
176
175
  on_event: nil,
@@ -180,6 +179,7 @@ module Phronomy
180
179
  agent_id: agent_id,
181
180
  context: context,
182
181
  knowledge: knowledge,
182
+ retention: retention,
183
183
  persistence: persistence,
184
184
  metadata: metadata,
185
185
  on_event: on_event,
@@ -189,7 +189,7 @@ module Phronomy
189
189
 
190
190
  # Resolves one existing logical Agent. A live process-local owner wins
191
191
  # without a Persistence reload; otherwise the durable Agent is hydrated.
192
- def load(agent_id, persistence:, on_event: nil, &event_block)
192
+ def load(agent_id, persistence:, on_event: nil, execution_wiring: {}, &event_block)
193
193
  raise ArgumentError, "persistence is required" unless persistence
194
194
  if on_event && event_block
195
195
  raise ArgumentError, "Provide either on_event: or a block, not both"
@@ -199,10 +199,9 @@ module Phronomy
199
199
  raise ArgumentError, "agent_id must not be empty" if key.empty?
200
200
 
201
201
  listener_supplied = !on_event.nil? || !event_block.nil?
202
- runtime = Phronomy::Runtime.instance
203
202
  materialized = false
204
203
 
205
- agent = OwnershipRegistry.for(runtime).load(key, expected_class: self) do |owner_runtime|
204
+ agent = OwnershipRegistry.current.load(key, expected_class: self) do |owner_runtime|
206
205
  materialized = true
207
206
  instance = __construct_owned_agent(
208
207
  owner_runtime,
@@ -210,6 +209,7 @@ module Phronomy
210
209
  agent_id: key,
211
210
  persistence: persistence,
212
211
  load_existing: true,
212
+ execution_wiring: execution_wiring,
213
213
  on_event: on_event,
214
214
  &event_block
215
215
  )
@@ -236,14 +236,14 @@ module Phronomy
236
236
  key = agent_id.to_s
237
237
  raise ArgumentError, "agent_id must not be empty" if key.empty?
238
238
 
239
- OwnershipRegistry.existing_for(Phronomy::Runtime.instance)&.get(key, expected_class: self)
239
+ OwnershipRegistry.existing_current&.get(key, expected_class: self)
240
240
  end
241
241
 
242
242
  # Resolves the live Agent instance that currently owns execution_id in
243
243
  # this process. The Runtime returns only a read-only ownership view;
244
244
  # mutable Agent execution state remains EventLoop-owned.
245
245
  def live_for_execution(execution_id)
246
- owner = Phronomy::Agent::ExecutionRegistry.existing_for(Phronomy::Runtime.instance)&.agent_execution_owner(execution_id)
246
+ owner = Phronomy::Agent::ExecutionRegistry.existing_current&.agent_execution_owner(execution_id)
247
247
  unless owner
248
248
  raise Phronomy::ExecutionRehydrationRequiredError,
249
249
  "no live execution owner for #{execution_id}; durable rehydration is required"
@@ -268,9 +268,15 @@ module Phronomy
268
268
  end
269
269
  end
270
270
 
271
+ # Bound at Agent construction; no execution may silently switch Runtime.
271
272
  # @api private
272
- def __coordination_config
273
- @_phronomy_coordination_config || {}.freeze
273
+ def __execution_environment
274
+ @_phronomy_runtime_owner || ExecutionEnvironment.current
275
+ end
276
+
277
+ # @api private
278
+ def __execution_wiring
279
+ @_phronomy_execution_wiring || {}.freeze
274
280
  end
275
281
 
276
282
  # Framework-owned idempotent Tool operations opt into exact replay.
@@ -279,26 +285,33 @@ module Phronomy
279
285
 
280
286
  # @api private
281
287
  def __framework_call?(name)
282
- __framework_tool_replayable?(name) || Array(__coordination_config[:phronomy_handoff_bindings]).any? { |binding| binding.tool_name == name }
288
+ __framework_tool_replayable?(name) || Array(__execution_wiring[:phronomy_control_bindings]).any? { |binding| binding.tool_name == name }
283
289
  end
284
290
 
285
- # Captured Tool wiring is supplied explicitly; this hook only writes semantic values.
286
- # @api private
287
- def __prepare_coordination_record(execution, tx:) = execution
288
-
289
291
  # Runtime configuration only; no value returned here is serialized.
290
292
  # @api private
291
293
  def __invocation_config(config) = config
292
294
 
295
+ def knowledge_snapshot
296
+ __with_operation { persistence.knowledge_snapshot(agent_id: agent_id) }
297
+ end
298
+
299
+ # Listener inheritance is an explicit orchestration choice, not a private getter.
300
+ def event_listener
301
+ __with_operation(read_only: true) { _phronomy_event_listener }
302
+ end
303
+
293
304
  attr_reader :agent_id, :persistence
294
305
 
295
306
  def initialize(
296
307
  agent_id: nil,
297
308
  context: nil,
298
309
  knowledge: [],
310
+ retention: nil,
299
311
  persistence: nil,
300
312
  metadata: {},
301
313
  load_existing: false,
314
+ execution_wiring: {},
302
315
  on_event: nil,
303
316
  &event_block
304
317
  )
@@ -306,6 +319,11 @@ module Phronomy
306
319
  raise ArgumentError, "Provide either on_event: or a block, not both"
307
320
  end
308
321
  @_phronomy_event_listener = on_event || event_block
322
+ @_phronomy_execution_wiring = execution_wiring.dup.freeze
323
+ @initial_retention = retention
324
+ if retention && retention.agent_id != agent_id.to_s
325
+ raise ArgumentError, "retention must refer to the new Agent"
326
+ end
309
327
 
310
328
  reserved_agent_id = @_phronomy_reserved_agent_id
311
329
  effective_agent_id = if agent_id.nil?
@@ -337,9 +355,8 @@ module Phronomy
337
355
  "load_existing: is an internal hydration option; use .load(agent_id, persistence:)"
338
356
  end
339
357
 
340
- runtime = Phronomy::Runtime.instance
341
358
  begin
342
- OwnershipRegistry.for(runtime).create(effective_agent_id, expected_class: self.class) do |owner_runtime|
359
+ OwnershipRegistry.current.create(effective_agent_id, expected_class: self.class) do |owner_runtime|
343
360
  __prepare_runtime_owner!(owner_runtime, effective_agent_id)
344
361
  initialize_owned_state(
345
362
  agent_id: effective_agent_id,
@@ -351,72 +368,88 @@ module Phronomy
351
368
  )
352
369
  self
353
370
  end
354
- rescue Phronomy::Storage::ConflictError => error
371
+ rescue Phronomy::Persistence::ConflictError => error
355
372
  raise Phronomy::AgentAlreadyExistsError,
356
373
  "Agent #{effective_agent_id.inspect} already exists durably: #{error.message}"
357
374
  end
358
375
  end
359
376
 
360
377
  def agent_root
361
- __assert_agent_accessible!
362
- @root
378
+ __with_operation(read_only: true) do
379
+ __assert_agent_accessible!
380
+ @root
381
+ end
363
382
  end
364
383
 
365
384
  def journal_projection
366
- __assert_agent_accessible!
367
- Agent::JournalProjection.new(
368
- agent_root: @root,
369
- records: _journal_records_snapshot
370
- )
385
+ __with_operation(read_only: true) do
386
+ __assert_agent_accessible!
387
+ Agent::JournalProjection.new(
388
+ agent_root: @root,
389
+ records: _journal_records_snapshot
390
+ )
391
+ end
371
392
  end
372
393
 
373
394
  def transcript
374
- journal_projection.transcript_records
395
+ __with_operation(read_only: true) do
396
+ journal_projection.transcript_records
397
+ end
375
398
  end
376
399
 
377
400
  def clear_transcript!
378
- mutate_context!(:transcript_cleared) do |root|
379
- root.with(
380
- agent_revision: root.agent_revision + 1,
381
- context_revision: root.context_revision + 1,
382
- transcript_generation: root.transcript_generation + 1
383
- )
401
+ __with_operation do
402
+ mutate_context!(:transcript_cleared) do |root|
403
+ root.with(
404
+ agent_revision: root.agent_revision + 1,
405
+ context_revision: root.context_revision + 1,
406
+ transcript_generation: root.transcript_generation + 1
407
+ )
408
+ end
384
409
  end
385
410
  end
386
411
 
387
412
  def clear_knowledge!
388
- mutate_context!(:knowledge_cleared) do |root|
389
- root.with(
390
- agent_revision: root.agent_revision + 1,
391
- context_revision: root.context_revision + 1
392
- )
413
+ __with_operation do
414
+ mutate_context!(:knowledge_cleared) do |root|
415
+ root.with(
416
+ agent_revision: root.agent_revision + 1,
417
+ context_revision: root.context_revision + 1
418
+ )
419
+ end
393
420
  end
394
421
  end
395
422
 
396
423
  def add_knowledge(content, metadata: {})
397
- __assert_live_agent!
398
- current = agent_root
399
- update = state_writer.add_knowledge(root: current, content: content, metadata: metadata)
400
- apply_state_update(update)
401
- self
424
+ __with_operation do
425
+ __assert_live_agent!
426
+ current = agent_root
427
+ update = state_writer.add_knowledge(root: current, content: content, metadata: metadata)
428
+ apply_state_update(update)
429
+ self
430
+ end
402
431
  end
403
432
 
404
433
  def reset_context!
405
- mutate_context!(:context_reset) do |root|
406
- root.with(
407
- agent_revision: root.agent_revision + 1,
408
- context_revision: root.context_revision + 1,
409
- transcript_generation: root.transcript_generation + 1
410
- )
434
+ __with_operation do
435
+ mutate_context!(:context_reset) do |root|
436
+ root.with(
437
+ agent_revision: root.agent_revision + 1,
438
+ context_revision: root.context_revision + 1,
439
+ transcript_generation: root.transcript_generation + 1
440
+ )
441
+ end
411
442
  end
412
443
  end
413
444
 
414
445
  def close!
415
- mutate_context!(:agent_closed, context_affecting: false) do |root|
416
- root.with(
417
- agent_revision: root.agent_revision + 1,
418
- lifecycle_status: :closed
419
- )
446
+ __with_operation do
447
+ mutate_context!(:agent_closed, context_affecting: false) do |root|
448
+ root.with(
449
+ agent_revision: root.agent_revision + 1,
450
+ lifecycle_status: :closed
451
+ )
452
+ end
420
453
  end
421
454
  end
422
455
 
@@ -427,7 +460,7 @@ module Phronomy
427
460
  runtime = @_phronomy_runtime_owner
428
461
  registry = OwnershipRegistry.for(runtime)
429
462
  token = registry.begin_purge(self)
430
- if Phronomy::Agent::ExecutionRegistry.existing_for(runtime)&.agent_execution_admitted?(agent_id)
463
+ if runtime.existing_registry&.agent_execution_admitted?(agent_id)
431
464
  registry.abort_purge(self, token)
432
465
  raise Phronomy::AgentBusyError,
433
466
  "Agent #{agent_id.inspect} has a nonterminal top-level execution"
@@ -436,20 +469,19 @@ module Phronomy
436
469
  begin
437
470
  persistence.transaction do |tx|
438
471
  tx.executions.assert_idle!(agent_id)
439
- if (routing = tx.handoff_states.load(agent_id))
440
- unless routing.phase == "stable" && tx.executions.list_active(routing.active_agent_id).empty?
441
- raise Phronomy::AgentBusyError, "Handoff anchor #{agent_id} owns an unfinished turn"
442
- end
443
- tx.handoff_states.delete(agent_id, expected_revision: routing.handoff_revision)
472
+ tx.guard_agent!(agent_id)
473
+ unless tx.retentions.list(agent_id).empty?
474
+ raise Phronomy::AgentBusyError, "Agent #{agent_id} is retained by durable coordination history"
444
475
  end
476
+ tx.cancellations.delete_for_agent(agent_id)
445
477
  tx.journals.delete(agent_id)
446
478
  tx.executions.delete_for_agent(agent_id)
447
479
  tx.agents.delete(agent_id)
448
480
  end
449
481
  rescue Phronomy::AgentBusyError,
450
- Phronomy::Storage::ConflictError,
451
- Phronomy::Storage::NotFoundError,
452
- Phronomy::Storage::SerializationError,
482
+ Phronomy::Persistence::ConflictError,
483
+ Phronomy::Persistence::NotFoundError,
484
+ Phronomy::Persistence::SerializationError,
453
485
  ArgumentError
454
486
  registry.abort_purge(self, token)
455
487
  raise
@@ -532,7 +564,7 @@ module Phronomy
532
564
 
533
565
  # @api private
534
566
  def __release_runtime_owner!(runtime)
535
- return self if @_phronomy_runtime_owner_state == :purged
567
+ return self if %i[purged unloaded].include?(@_phronomy_runtime_owner_state)
536
568
  return self unless @_phronomy_runtime_owner.equal?(runtime)
537
569
 
538
570
  @_phronomy_runtime_owner_state = :released
@@ -560,14 +592,19 @@ module Phronomy
560
592
  load_existing:
561
593
  )
562
594
  @persistence = persistence ||
563
- Phronomy.configuration.persistence ||
595
+ Phronomy::Agent::Settings.current.agent_store ||
564
596
  DefaultPersistence.build
565
597
  @agent_id = agent_id.to_s.freeze
566
598
 
567
599
  if load_existing
568
600
  root = records = nil
569
601
  @persistence.transaction do |tx|
570
- root = tx.agents.load(@agent_id)
602
+ begin
603
+ root = tx.agents.load(@agent_id)
604
+ rescue Phronomy::Persistence::NotFoundError => error
605
+ raise if error.is_a?(Phronomy::AgentNotFoundError)
606
+ raise Phronomy::AgentNotFoundError.new(agent_id: @agent_id), cause: error
607
+ end
571
608
  records = tx.journals.read(
572
609
  @agent_id,
573
610
  limit: root.journal_position
@@ -607,7 +644,7 @@ module Phronomy
607
644
  def create_agent_root!(context:, knowledge:, metadata:)
608
645
  definition = self.class.agent_definition
609
646
  state_writer.create_root(
610
- definition: definition, context: context, knowledge: knowledge, metadata: metadata
647
+ definition: definition, context: context, knowledge: knowledge, metadata: metadata, retention: @initial_retention
611
648
  )
612
649
  end
613
650
 
@@ -644,11 +681,13 @@ module Phronomy
644
681
  public
645
682
 
646
683
  def tool_approval_policy(&block)
647
- __assert_live_agent!
648
- raise ArgumentError, "tool_approval_policy requires a block" unless block
684
+ __with_operation do
685
+ __assert_live_agent!
686
+ raise ArgumentError, "tool_approval_policy requires a block" unless block
649
687
 
650
- _approval_configuration_mutex.synchronize { @tool_approval_policy = block }
651
- self
688
+ _approval_configuration_mutex.synchronize { @tool_approval_policy = block }
689
+ self
690
+ end
652
691
  end
653
692
 
654
693
  private
@@ -659,6 +698,8 @@ module Phronomy
659
698
  true
660
699
  when :purged
661
700
  raise Phronomy::AgentPurgedError, "Agent #{agent_id.inspect} has been purged"
701
+ when :unloaded
702
+ raise Phronomy::AgentUnloadedError.new(agent_id: agent_id)
662
703
  when :purging
663
704
  raise Phronomy::Error, "Agent #{agent_id.inspect} is being purged"
664
705
  when :recovery_required
@@ -708,7 +749,7 @@ module Phronomy
708
749
  end
709
750
 
710
751
  def _check_event_loop_reentrancy(sync_method, async_method)
711
- if Phronomy::Runtime.instance.event_loop.current?
752
+ if Phronomy::WaitPolicy.blocking_forbidden?
712
753
  raise Phronomy::EventLoopReentrancyError,
713
754
  "#{self.class.name}##{sync_method} cannot run on the EventLoop thread. " \
714
755
  "Use #{async_method} and return immediately."
@@ -716,19 +757,15 @@ module Phronomy
716
757
  end
717
758
 
718
759
  def _complete_result_task(task, result)
760
+ task.finish_operation! if task.respond_to?(:finish_operation!)
719
761
  task.complete(result)
720
762
  end
721
763
 
722
764
  def _fail_result_task(task, error)
765
+ task.finish_operation! if task.respond_to?(:finish_operation!)
723
766
  task.fail(error)
724
767
  end
725
768
 
726
- def _translated_error(error)
727
- translate_and_reraise!(error)
728
- rescue => translated
729
- translated
730
- end
731
-
732
769
  def _deliver_stream_event(listener, event)
733
770
  return unless listener
734
771
 
@@ -772,7 +809,7 @@ module Phronomy
772
809
  end
773
810
 
774
811
  def _warn_stream_callback_error(message)
775
- logger = Phronomy.configuration.logger
812
+ logger = Phronomy::RuntimeSettings.current.logger
776
813
  unless logger
777
814
  _kernel_warn_safely(message)
778
815
  return
@@ -820,36 +857,28 @@ module Phronomy
820
857
  meta
821
858
  end
822
859
 
823
- def _apply_runtime_projection_to_chat(chat, projection, invocation: nil)
824
- if projection.system
825
- apply_instructions(
826
- chat,
827
- projection.system,
828
- cache: projection.model_config["cache_instructions"],
829
- provider: projection.model_config["provider"]
830
- )
831
- end
832
- projection.tool_classes.each do |tool_class|
833
- chat.with_tools(prepare_tool_class(tool_class, invocation: invocation))
860
+ def prepare_runtime_input(projection, invocation:)
861
+ tools = projection.tool_classes.map do |tool|
862
+ prepared = prepare_tool_class(tool, invocation: invocation)
863
+ prepared.is_a?(Class) ? prepared.new : prepared
834
864
  end
835
- projection.messages.each { |message| chat.messages << message }
836
- chat
865
+ invocation.tools = tools.to_h { |tool| [tool.name.to_sym, tool] }.freeze
866
+ invocation.messages = projection.messages.dup
867
+ invocation
837
868
  end
838
869
 
839
- def build_chat(model_config: nil)
840
- config = model_config || {
841
- "model" => self.class.model,
842
- "provider" => self.class.provider,
843
- "temperature" => self.class.temperature,
844
- "max_output_tokens" => self.class.max_output_tokens
845
- }
846
- RuntimeChatBuilder.build(config)
870
+ def build_llm_request(projection, invocation:)
871
+ definitions = Phronomy::Tool::DefinitionSet.build(tools: invocation.tools.values).definitions
872
+ Phronomy::LLMAdapter::Request.new(
873
+ model_config: projection.model_config, system: projection.system,
874
+ messages: invocation.messages, tools: definitions, message: projection.ask_message
875
+ )
847
876
  end
848
877
 
849
878
  def build_instructions(input)
850
879
  instr = self.class.instructions
851
880
  case instr
852
- when Phronomy::Agent::Context::Instruction::PromptTemplate
881
+ when Phronomy::Context::PromptTemplate
853
882
  vars = input.is_a?(Hash) ? input : {input: input}
854
883
  instr.format_system(**vars) || instr.format(**vars)
855
884
  when String then instr
@@ -858,10 +887,6 @@ module Phronomy
858
887
  end
859
888
  end
860
889
 
861
- def apply_instructions(chat, text, cache: false, provider: nil)
862
- RuntimeChatBuilder.apply_instructions(chat, text, cache: cache, provider: provider)
863
- end
864
-
865
890
  def extract_message(input)
866
891
  case input
867
892
  when String then input
@@ -884,6 +909,14 @@ module Phronomy
884
909
  raise Phronomy::CancellationError, message
885
910
  end
886
911
 
912
+ def tool_definition_set(additional_tools: [])
913
+ runtime_tools = (self.class.tools + Array(additional_tools)).freeze
914
+ Phronomy::Tool::DefinitionSet.build(
915
+ tools: runtime_tools.map { |tool| prepare_tool_class(tool) },
916
+ runtime_tools: runtime_tools
917
+ )
918
+ end
919
+
887
920
  def prepare_tool_class(tool_class, invocation: nil)
888
921
  return tool_class unless tool_class.is_a?(Class)
889
922
 
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ module BeforeLLMInput
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def before_llm_input(callable = nil, &block)
12
+ if callable.nil? && !block_given?
13
+ @before_llm_input
14
+ else
15
+ @before_llm_input = callable || block
16
+ end
17
+ end
18
+
19
+ def _before_llm_input = @before_llm_input
20
+ end
21
+
22
+ def before_llm_input
23
+ __with_operation(read_only: true) { @before_llm_input }
24
+ end
25
+
26
+ def before_llm_input=(value)
27
+ __with_operation { @before_llm_input = value }
28
+ end
29
+
30
+ private
31
+
32
+ def run_before_llm_input_hooks(call_sequence:, config:)
33
+ hooks = [
34
+ Phronomy::Agent::Settings.current.before_llm_input,
35
+ self.class._before_llm_input,
36
+ @before_llm_input
37
+ ].compact
38
+ return Phronomy::Context::LLMInputPatch.empty if hooks.empty?
39
+
40
+ definition = self.class.agent_definition
41
+ context = Phronomy::Context::LLMInputBuildContext.new(
42
+ agent_id: agent_id,
43
+ agent_definition_id: definition.fetch(:id),
44
+ agent_definition_version: definition.fetch(:version),
45
+ config: config,
46
+ call_sequence: call_sequence
47
+ )
48
+ model_patch = {}
49
+ segments = []
50
+
51
+ hooks.each do |hook|
52
+ result = hook.call(context)
53
+ check_cancellation!(config, "invocation cancelled during before_llm_input hook")
54
+ next if result.nil?
55
+ unless result.is_a?(Phronomy::Context::LLMInputPatch)
56
+ raise TypeError,
57
+ "before_llm_input must return Phronomy::Context::LLMInputPatch or nil, got #{result.class}"
58
+ end
59
+ model_patch.merge!(result.model_config_patch) if result.model_config_patch
60
+ segments.concat(Array(result.segment_candidates))
61
+ end
62
+
63
+ Phronomy::Context::LLMInputPatch.new(
64
+ model_config_patch: model_patch.empty? ? nil : model_patch,
65
+ segment_candidates: segments.empty? ? nil : segments
66
+ )
67
+ end
68
+ end
69
+ end
70
+ end
@@ -18,7 +18,7 @@ module Phronomy
18
18
  raise ArgumentError, "Provide either on_event: or a block, not both"
19
19
  end
20
20
 
21
- persistence = Phronomy::Persistence.in_memory
21
+ persistence = Phronomy::PersistenceComposition.agent
22
22
  agent = definition.create(
23
23
  context: context,
24
24
  knowledge: knowledge,
@@ -43,7 +43,7 @@ module Phronomy
43
43
  "source_sequence" => record.sequence
44
44
  )
45
45
 
46
- Selection::Candidate.new(
46
+ Phronomy::Context::Candidate.new(
47
47
  candidate_id: "record:#{record.record_id}",
48
48
  source_kind: source_kind,
49
49
  category: record.kind,
@@ -55,7 +55,7 @@ module Phronomy
55
55
  llm_call_id: record.llm_call_id,
56
56
  tool_call_id: tool_call_id,
57
57
  sequence: sequence,
58
- constraint: Selection::Constraint.selectable(origin: :context_policy),
58
+ constraint: Phronomy::Context::Constraint.selectable(origin: :context_policy),
59
59
  priority: (source_kind == :working) ? 100 : 0,
60
60
  metadata: metadata
61
61
  )