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,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ module Persistence
6
+ # Cancellation is monotonic and independent of the in-flight execution
7
+ # revision. Recording intent cannot invalidate a worker's captured base.
8
+ # @api private
9
+ class CancellationRepository
10
+ include Phronomy::Storage::RecordCodec
11
+
12
+ def initialize(view) = @view = view
13
+
14
+ def requested?(agent_id, execution_id)
15
+ Phronomy::Persistence::StorageBoundary.call do
16
+ @view.atomic do |view|
17
+ entry = view.records(StorageSchema::CANCELLATIONS).read(execution_id)
18
+ next false unless entry
19
+ payload = current_payload!(entry.record, record_type: "phronomy.agent_cancellation", format_version: "0.1",
20
+ keys: %w[agent_id execution_id], label: "Agent cancellation")
21
+ unless payload == {"agent_id" => agent_id, "execution_id" => execution_id} && entry.attributes == {owner: agent_id}
22
+ raise Phronomy::Storage::SerializationError, "Cancellation owner mismatch"
23
+ end
24
+ true
25
+ end
26
+ end
27
+ end
28
+
29
+ def request(agent_id, execution_id)
30
+ Phronomy::Persistence::StorageBoundary.call do
31
+ @view.atomic do |view|
32
+ view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: StorageSchema::ROOTS, key: agent_id)], conditions: [])
33
+ unless self.class.new(view).requested?(agent_id, execution_id)
34
+ view.records(StorageSchema::CANCELLATIONS).insert(key: execution_id, revision: 0,
35
+ attributes: {owner: agent_id}, record: build_record("phronomy.agent_cancellation", "0.1",
36
+ {"agent_id" => agent_id, "execution_id" => execution_id}))
37
+ end
38
+ end
39
+ end
40
+ true
41
+ end
42
+
43
+ def delete_for_agent(agent_id)
44
+ Phronomy::Persistence::StorageBoundary.call do
45
+ @view.records(StorageSchema::CANCELLATIONS).delete_matching(index: :owner, equals: {owner: agent_id})
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -11,7 +11,7 @@ module Phronomy
11
11
  AGENT_ROOT_RECORD_TYPE = "phronomy.agent_root"
12
12
  AGENT_ROOT_FORMAT_VERSION = "0.1"
13
13
  AGENT_EXECUTION_RECORD_TYPE = "phronomy.agent_execution"
14
- AGENT_EXECUTION_FORMAT_VERSION = "0.1"
14
+ AGENT_EXECUTION_FORMAT_VERSION = "0.2"
15
15
  JOURNAL_RECORD_TYPE = "phronomy.journal_record"
16
16
  JOURNAL_FORMAT_VERSION = "0.1"
17
17
  AGENT_ROOT_KEYS = %w[
@@ -48,26 +48,6 @@ module Phronomy
48
48
 
49
49
  module_function
50
50
 
51
- def encode_handoff_state(value)
52
- payload = value.to_h
53
- Phronomy::Agent::HandoffState.from_h(payload)
54
- build_record("phronomy.handoff_state", "0.1", payload)
55
- rescue Phronomy::Storage::SerializationError
56
- raise
57
- rescue => error
58
- serialization_error("cannot encode HandoffState", error)
59
- end
60
-
61
- def decode_handoff_state(record)
62
- payload = current_payload!(record, record_type: "phronomy.handoff_state",
63
- format_version: "0.1", keys: Phronomy::Agent::HandoffState::ATTRIBUTES, label: "HandoffState")
64
- Phronomy::Agent::HandoffState.from_h(payload)
65
- rescue Phronomy::Storage::SerializationError
66
- raise
67
- rescue => error
68
- serialization_error("cannot decode HandoffState", error)
69
- end
70
-
71
51
  def encode_agent_root(root)
72
52
  payload = top_level_string_keys(root.to_h, label: "AgentRoot payload")
73
53
  payload["lifecycle_status"] = root.lifecycle_status.to_s
@@ -9,75 +9,91 @@ module Phronomy
9
9
  def initialize(view) = @view = view
10
10
 
11
11
  def create_active(execution)
12
- raise Phronomy::Storage::SerializationError, "create_active requires an active AgentExecution" unless execution.active?
13
- record = Codec.encode_agent_execution(execution)
14
- admission do
15
- @view.atomic do |bound|
16
- entry = bound.records(StorageSchema::EXECUTIONS).insert(key: execution.execution_id.to_s,
17
- revision: Integer(execution.execution_revision), attributes: attributes(execution), record: record)
18
- decode(entry, execution.execution_id, owner: execution.agent_id,
19
- revision: execution.execution_revision, active: true)
12
+ Phronomy::Persistence::StorageBoundary.call do
13
+ raise Phronomy::Storage::SerializationError, "create_active requires an active AgentExecution" unless execution.active?
14
+ record = Codec.encode_agent_execution(execution)
15
+ admission do
16
+ @view.atomic do |bound|
17
+ entry = bound.records(StorageSchema::EXECUTIONS).insert(key: execution.execution_id.to_s,
18
+ revision: Integer(execution.execution_revision), attributes: attributes(execution), record: record)
19
+ decode(entry, execution.execution_id, owner: execution.agent_id,
20
+ revision: execution.execution_revision, active: true)
21
+ end
20
22
  end
21
23
  end
22
24
  end
23
25
 
24
26
  def load(execution_id)
25
- value = @view.atomic do |bound|
26
- entry = bound.records(StorageSchema::EXECUTIONS).read(execution_id.to_s)
27
- entry && decode(entry, execution_id)
27
+ Phronomy::Persistence::StorageBoundary.call do
28
+ value = @view.atomic do |bound|
29
+ entry = bound.records(StorageSchema::EXECUTIONS).read(execution_id.to_s)
30
+ entry && decode(entry, execution_id)
31
+ end
32
+ value || raise(Phronomy::Storage::NotFoundError, "Execution not found: #{execution_id}")
28
33
  end
29
- value || raise(Phronomy::Storage::NotFoundError, "Execution not found: #{execution_id}")
30
34
  end
31
35
 
32
36
  def save(execution_id, expected_revision:, execution:)
33
- expected = Integer(expected_revision)
34
- revision = Integer(execution.execution_revision)
35
- raise Phronomy::Storage::ConflictError, "execution save must advance revision exactly once" unless revision == expected + 1
36
- unless execution.execution_id.to_s == execution_id.to_s
37
- raise Phronomy::Storage::SerializationError, "Execution identity mismatch"
38
- end
39
- record = Codec.encode_agent_execution(execution)
40
- admission do
41
- @view.atomic do |bound|
42
- entry = bound.records(StorageSchema::EXECUTIONS).replace(key: execution_id.to_s,
43
- expected_revision: expected, next_revision: revision, attributes: attributes(execution),
44
- expected_attributes: {}, record: record)
45
- decode(entry, execution_id, owner: execution.agent_id, revision: revision, active: execution.active?)
37
+ Phronomy::Persistence::StorageBoundary.call do
38
+ expected = Integer(expected_revision)
39
+ revision = Integer(execution.execution_revision)
40
+ raise Phronomy::Storage::ConflictError, "execution save must advance revision exactly once" unless revision == expected + 1
41
+ unless execution.execution_id.to_s == execution_id.to_s
42
+ raise Phronomy::Storage::SerializationError, "Execution identity mismatch"
43
+ end
44
+ record = Codec.encode_agent_execution(execution)
45
+ admission do
46
+ @view.atomic do |bound|
47
+ entry = bound.records(StorageSchema::EXECUTIONS).replace(key: execution_id.to_s,
48
+ expected_revision: expected, next_revision: revision, attributes: attributes(execution),
49
+ expected_attributes: {}, record: record)
50
+ decode(entry, execution_id, owner: execution.agent_id, revision: revision, active: execution.active?)
51
+ end
46
52
  end
47
53
  end
48
54
  end
49
55
 
50
56
  def list_active(agent_id)
51
- @view.atomic do |bound|
52
- bound.records(StorageSchema::EXECUTIONS).scan(index: :owner_active,
53
- equals: {owner: agent_id.to_s, active: true}).map { |entry| decode(entry, entry.key, owner: agent_id, active: true) }.freeze
57
+ Phronomy::Persistence::StorageBoundary.call do
58
+ @view.atomic do |bound|
59
+ bound.records(StorageSchema::EXECUTIONS).scan(index: :owner_active,
60
+ equals: {owner: agent_id.to_s, active: true}).map { |entry| decode(entry, entry.key, owner: agent_id, active: true) }.freeze
61
+ end
54
62
  end
55
63
  end
56
64
 
57
65
  def list(agent_id, after: nil, limit: 100)
58
- raise ArgumentError, "limit must be a positive Integer" unless limit.is_a?(Integer) && limit.positive?
59
- @view.atomic do |bound|
60
- bound.records(StorageSchema::EXECUTIONS).scan(index: :owner, equals: {owner: agent_id.to_s},
61
- after: after&.to_s, limit: limit).map { |entry| decode(entry, entry.key, owner: agent_id) }.freeze
66
+ Phronomy::Persistence::StorageBoundary.call do
67
+ raise ArgumentError, "limit must be a positive Integer" unless limit.is_a?(Integer) && limit.positive?
68
+ @view.atomic do |bound|
69
+ bound.records(StorageSchema::EXECUTIONS).scan(index: :owner, equals: {owner: agent_id.to_s},
70
+ after: after&.to_s, limit: limit).map { |entry| decode(entry, entry.key, owner: agent_id) }.freeze
71
+ end
62
72
  end
63
73
  end
64
74
 
65
75
  def delete(execution_id)
66
- @view.records(StorageSchema::EXECUTIONS).delete(key: execution_id.to_s)
76
+ Phronomy::Persistence::StorageBoundary.call do
77
+ @view.records(StorageSchema::EXECUTIONS).delete(key: execution_id.to_s)
78
+ end
67
79
  end
68
80
 
69
81
  def delete_for_agent(agent_id)
70
- @view.records(StorageSchema::EXECUTIONS).delete_matching(index: :owner, equals: {owner: agent_id.to_s})
82
+ Phronomy::Persistence::StorageBoundary.call do
83
+ @view.records(StorageSchema::EXECUTIONS).delete_matching(index: :owner, equals: {owner: agent_id.to_s})
84
+ end
71
85
  end
72
86
 
73
87
  def assert_idle!(agent_id)
74
- condition = Phronomy::Storage::Condition::NoRows.new(resource: StorageSchema::EXECUTIONS,
75
- index: :owner_active, equals: {owner: agent_id.to_s, active: true})
76
- @view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: StorageSchema::ROOTS, key: agent_id.to_s)],
77
- conditions: [condition])
78
- rescue Phronomy::Storage::ConditionFailedError => error
79
- raise unless error.condition.equal?(condition)
80
- raise Phronomy::AgentBusyError, error.message
88
+ Phronomy::Persistence::StorageBoundary.call do
89
+ condition = Phronomy::Storage::Condition::NoRows.new(resource: StorageSchema::EXECUTIONS,
90
+ index: :owner_active, equals: {owner: agent_id.to_s, active: true})
91
+ @view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: StorageSchema::ROOTS, key: agent_id.to_s)],
92
+ conditions: [condition])
93
+ rescue Phronomy::Storage::ConditionFailedError => error
94
+ raise unless error.condition.equal?(condition)
95
+ raise Phronomy::AgentBusyError, error.message
96
+ end
81
97
  end
82
98
 
83
99
  private
@@ -9,32 +9,41 @@ module Phronomy
9
9
  def initialize(view) = @view = view
10
10
 
11
11
  def append(agent_id, expected_position:, records:)
12
- expected = Integer(expected_position)
13
- entries = Array(records).each_with_index.map do |record, index|
14
- unless record.agent_id.to_s == agent_id.to_s
15
- raise Phronomy::Storage::SerializationError, "Journal record Agent mismatch"
12
+ Phronomy::Persistence::StorageBoundary.call do
13
+ expected = Integer(expected_position)
14
+ entries = Array(records).each_with_index.map do |record, index|
15
+ unless record.agent_id.to_s == agent_id.to_s
16
+ raise Phronomy::Storage::SerializationError, "Journal record Agent mismatch"
17
+ end
18
+ sequenced = record.with_sequence(expected + index + 1)
19
+ Phronomy::Storage::Entry::Append.new(id: sequenced.record_id.to_s, record: Codec.encode_journal_record(sequenced))
20
+ end
21
+ @view.atomic do |bound|
22
+ stored = bound.streams(StorageSchema::JOURNAL).append(stream: agent_id.to_s,
23
+ expected_head: expected, entries: entries)
24
+ decode(stored, agent_id, after: expected)
16
25
  end
17
- sequenced = record.with_sequence(expected + index + 1)
18
- Phronomy::Storage::Entry::Append.new(id: sequenced.record_id.to_s, record: Codec.encode_journal_record(sequenced))
19
- end
20
- @view.atomic do |bound|
21
- stored = bound.streams(StorageSchema::JOURNAL).append(stream: agent_id.to_s,
22
- expected_head: expected, entries: entries)
23
- decode(stored, agent_id, after: expected)
24
26
  end
25
27
  end
26
28
 
27
29
  def read(agent_id, after: nil, limit: nil)
28
- position = after.nil? ? 0 : Integer(after)
29
- count = limit.nil? ? nil : Integer(limit)
30
- @view.atomic do |bound|
31
- stored = (count == 0) ? [] : bound.streams(StorageSchema::JOURNAL).read(stream: agent_id.to_s, after: position, limit: count)
32
- decode(stored, agent_id, after: position)
30
+ Phronomy::Persistence::StorageBoundary.call do
31
+ position = after.nil? ? 0 : Integer(after)
32
+ count = limit.nil? ? nil : Integer(limit)
33
+ @view.atomic do |bound|
34
+ stored = (count == 0) ? [] : bound.streams(StorageSchema::JOURNAL).read(stream: agent_id.to_s, after: position, limit: count)
35
+ decode(stored, agent_id, after: position)
36
+ end
33
37
  end
34
38
  end
35
39
 
36
- def head(agent_id) = @view.streams(StorageSchema::JOURNAL).head(stream: agent_id.to_s)
37
- def delete(agent_id) = @view.streams(StorageSchema::JOURNAL).delete(stream: agent_id.to_s)
40
+ def head(agent_id)
41
+ Phronomy::Persistence::StorageBoundary.call { @view.streams(StorageSchema::JOURNAL).head(stream: agent_id.to_s) }
42
+ end
43
+
44
+ def delete(agent_id)
45
+ Phronomy::Persistence::StorageBoundary.call { @view.streams(StorageSchema::JOURNAL).delete(stream: agent_id.to_s) }
46
+ end
38
47
 
39
48
  private
40
49
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ module Persistence
6
+ # Storage implementation of Agent's own record operations.
7
+ # @api private
8
+ class Records
9
+ attr_reader :contents, :agents, :journals, :executions, :retentions, :cancellations
10
+
11
+ def initialize(view)
12
+ @view = view
13
+ @contents = Phronomy::Persistence::ContentRepository.new(Phronomy::ContentStore::StoredContents.new(view))
14
+ @agents = AgentRepository.new(view)
15
+ @journals = JournalRepository.new(view)
16
+ @executions = ExecutionRepository.new(view)
17
+ @retentions = RetentionRepository.new(view)
18
+ @cancellations = CancellationRepository.new(view)
19
+ end
20
+
21
+ def assert_agent_watermark!(agent_id:, agent_revision:, journal_position:)
22
+ Watermark.new(@view).check!(agent_id: agent_id, agent_revision: agent_revision, journal_position: journal_position)
23
+ end
24
+
25
+ def guard_agent!(agent_id)
26
+ Phronomy::Persistence::StorageBoundary.call do
27
+ @view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: StorageSchema::ROOTS, key: agent_id)], conditions: [])
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Phronomy
6
+ module Agent
7
+ module Persistence
8
+ # @api private
9
+ class RetentionRepository
10
+ include Phronomy::Storage::RecordCodec
11
+
12
+ def initialize(view) = @view = view
13
+
14
+ def retain(retention)
15
+ Phronomy::Persistence::StorageBoundary.call do
16
+ @view.atomic do |view|
17
+ guard!(view, retention.agent_id)
18
+ records = view.records(StorageSchema::RETENTIONS)
19
+ key = Digest::SHA256.hexdigest(Phronomy::CanonicalJSON.dump(retention.to_h))
20
+ if (existing = records.read(key))
21
+ raise Phronomy::Storage::SerializationError, "retention identity mismatch" unless decode(existing) == retention
22
+ else
23
+ records.insert(key: key, revision: 0,
24
+ attributes: {owner: retention.agent_id, holder: retention.owner_key},
25
+ record: build_record("phronomy.agent_retention", "0.1", retention.to_h))
26
+ end
27
+ end
28
+ end
29
+ retention
30
+ end
31
+
32
+ def list(agent_id)
33
+ Phronomy::Persistence::StorageBoundary.call do
34
+ @view.atomic { |view| view.records(StorageSchema::RETENTIONS).scan(index: :owner, equals: {owner: agent_id}).map { |entry| decode(entry) }.freeze }
35
+ end
36
+ end
37
+
38
+ def release(agent_id:, owner_key:)
39
+ Phronomy::Persistence::StorageBoundary.call do
40
+ @view.atomic do |view|
41
+ guard!(view, agent_id)
42
+ records = view.records(StorageSchema::RETENTIONS)
43
+ records.scan(index: :owner, equals: {owner: agent_id}).each do |entry|
44
+ value = decode(entry)
45
+ records.delete(key: entry.key, expected_revision: entry.revision) if value.owner_key == owner_key
46
+ end
47
+ end
48
+ end
49
+ nil
50
+ end
51
+
52
+ private
53
+
54
+ def guard!(view, id)
55
+ view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: StorageSchema::ROOTS, key: id)], conditions: [])
56
+ end
57
+
58
+ def decode(entry)
59
+ payload = current_payload!(entry.record, record_type: "phronomy.agent_retention", format_version: "0.1",
60
+ keys: %w[agent_id execution_id owner_key], label: "Agent retention")
61
+ value = Retention.new(**payload.transform_keys(&:to_sym))
62
+ unless entry.attributes == {owner: value.agent_id, holder: value.owner_key}
63
+ raise Phronomy::Storage::SerializationError, "retention attributes mismatch"
64
+ end
65
+ value
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -16,8 +16,13 @@ module Phronomy
16
16
 
17
17
  JOURNAL = Phronomy::Storage::Resource.new(id: "agent.journal", kind: :streams,
18
18
  guard: {resource: ROOTS.id, via: :stream})
19
- HANDOFF_STATES = Phronomy::Storage::Resource.new(id: "handoff.states", kind: :records,
20
- attributes: {active_agent_id: :string})
19
+ CANCELLATIONS = Phronomy::Storage::Resource.new(id: "agent.cancellations", kind: :records,
20
+ attributes: {owner: :string}, immutable_attributes: [:owner], indexes: {owner: [:owner]},
21
+ guard: {resource: ROOTS.id, via: :owner})
22
+ RETENTIONS = Phronomy::Storage::Resource.new(id: "agent.retentions", kind: :records,
23
+ attributes: {owner: :string, holder: :string}, immutable_attributes: [:owner, :holder],
24
+ indexes: {owner: [:owner], holder: [:holder]},
25
+ guard: {resource: ROOTS.id, via: :owner})
21
26
  end
22
27
  end
23
28
  end
@@ -9,17 +9,19 @@ module Phronomy
9
9
  def initialize(view) = @view = view
10
10
 
11
11
  def check!(agent_id:, agent_revision:, journal_position:)
12
- key = agent_id.to_s
13
- @view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: StorageSchema::ROOTS, key: key)],
14
- conditions: [
15
- Phronomy::Storage::Condition::RevisionIs.new(resource: StorageSchema::ROOTS,
16
- key: key, expected: Integer(agent_revision)),
17
- Phronomy::Storage::Condition::StreamHeadIs.new(resource: StorageSchema::JOURNAL,
18
- stream: key, expected: Integer(journal_position))
19
- ])
20
- rescue Phronomy::Storage::ConditionFailedError => error
21
- label = error.condition.is_a?(Phronomy::Storage::Condition::RevisionIs) ? "agent revision" : "journal position"
22
- raise Phronomy::Storage::ConflictError, "#{label} conflict"
12
+ Phronomy::Persistence::StorageBoundary.call do
13
+ key = agent_id.to_s
14
+ @view.check!(guards: [Phronomy::Storage::GuardRef.new(resource: StorageSchema::ROOTS, key: key)],
15
+ conditions: [
16
+ Phronomy::Storage::Condition::RevisionIs.new(resource: StorageSchema::ROOTS,
17
+ key: key, expected: Integer(agent_revision)),
18
+ Phronomy::Storage::Condition::StreamHeadIs.new(resource: StorageSchema::JOURNAL,
19
+ stream: key, expected: Integer(journal_position))
20
+ ])
21
+ rescue Phronomy::Storage::ConditionFailedError => error
22
+ label = error.condition.is_a?(Phronomy::Storage::Condition::RevisionIs) ? "agent revision" : "journal position"
23
+ raise Phronomy::Storage::ConflictError, "#{label} conflict"
24
+ end
23
25
  end
24
26
  end
25
27
  end
@@ -8,7 +8,7 @@ module Phronomy
8
8
  module InvocationRestorer
9
9
  module_function
10
10
 
11
- def build_invocation_for_suspended(agent, execution, projection, main_coordinator, listener, assistant_message:)
11
+ def build_invocation_for_suspended(agent, execution, projection, main_coordinator, listener, assistant_message:, cancellation_requested: false)
12
12
  request = execution.approval_request && Phronomy::Agent::ToolApprovalRequest.from_h(
13
13
  execution.approval_request
14
14
  )
@@ -18,10 +18,9 @@ module Phronomy
18
18
  "suspended execution #{execution.execution_id} has no durable assistant Tool Call message"
19
19
  end
20
20
 
21
- invocation, chat, config = prepare_invocation(agent, execution, projection, main_coordinator, listener)
22
- chat.messages << assistant_message
21
+ invocation, config = prepare_invocation(agent, execution, projection, main_coordinator, listener, cancellation_requested: cancellation_requested)
22
+ invocation.messages << assistant_message
23
23
 
24
- invocation.chat = chat
25
24
  invocation.user_message_sent = true
26
25
  invocation.approval_request = request
27
26
  invocation.instance_variable_set(
@@ -54,7 +53,7 @@ module Phronomy
54
53
  raise Phronomy::ExecutionRehydrationRequiredError,
55
54
  "Tool Call #{entry.fetch("tool_call_id")} is missing from the durable assistant message"
56
55
  end
57
- tool = chat.tools[entry.fetch("tool_name").to_sym]
56
+ tool = invocation.tools[entry.fetch("tool_name").to_sym]
58
57
  child = if tool
59
58
  Phronomy::Agent::ToolInvocation.new(
60
59
  execution_id: execution.execution_id,
@@ -83,24 +82,23 @@ module Phronomy
83
82
  invocation
84
83
  end
85
84
 
86
- def build_chat_for_recovery(agent, execution, projection, main_coordinator, listener, messages:)
87
- invocation, chat = prepare_invocation(agent, execution, projection, main_coordinator, listener)
85
+ def build_input_for_recovery(agent, execution, projection, main_coordinator, listener, messages:, cancellation_requested: false)
86
+ invocation, = prepare_invocation(agent, execution, projection, main_coordinator, listener, cancellation_requested: cancellation_requested)
88
87
 
89
- messages.each { |message| chat.messages << message }
88
+ messages.each { |message| invocation.messages << message }
90
89
 
91
- invocation.chat = chat
92
90
  invocation.user_message_sent = true
93
91
  invocation
94
92
  end
95
93
 
96
- def prepare_invocation(agent, execution, projection, main_coordinator, listener)
94
+ def prepare_invocation(agent, execution, projection, main_coordinator, listener, cancellation_requested:)
97
95
  config = {
98
96
  execution_id: execution.execution_id,
99
97
  phronomy_execution_coordinator: main_coordinator,
100
98
  phronomy_runtime_projection: projection
101
- }.merge(agent.__coordination_config)
99
+ }.merge(agent.__execution_wiring)
102
100
  config = agent.__invocation_config(config)
103
- if execution.metadata["coordination_cancel_requested"]
101
+ if cancellation_requested || execution.metadata["cancellation_requested"]
104
102
  config = config.merge(cancellation_token: Phronomy::Concurrency::CancellationToken.new.cancel!)
105
103
  end
106
104
  invocation = Phronomy::Agent::AgentInvocation.new(
@@ -111,20 +109,14 @@ module Phronomy
111
109
  mode: (execution.metadata[ExecutionMetadata::INVOCATION_MODE_KEY] || "invoke").to_sym,
112
110
  execution_id: execution.execution_id
113
111
  )
114
- chat = agent.send(:build_chat, model_config: projection.model_config)
115
- agent.send(
116
- :_apply_runtime_projection_to_chat,
117
- chat,
118
- projection,
119
- invocation: invocation
120
- )
112
+ agent.send(:prepare_runtime_input, projection, invocation: invocation)
121
113
  if projection.ask_message
122
- chat.messages << RubyLLM::Message.new(
114
+ invocation.messages << Phronomy::LLMAdapter::Message.new(
123
115
  role: :user,
124
116
  content: projection.ask_message
125
117
  )
126
118
  end
127
- [invocation, chat, config]
119
+ [invocation, config]
128
120
  end
129
121
  private_class_method :prepare_invocation
130
122
  end
@@ -36,19 +36,20 @@ module Phronomy
36
36
  _manifest, projection = SavedContextReader.materialize_projection(agent, execution.metadata.fetch("manifest_ref"))
37
37
  end
38
38
  records = execution.working_records.select { |record| %i[assistant_message tool_message].include?(record.kind.to_sym) }
39
- materializer = RubyLLMMaterializer.new(agent: agent, persistence: agent.persistence)
39
+ materializer = RuntimeInput.new(agent: agent, persistence: agent.persistence)
40
40
  messages = records.map { |record| materializer.materialize_journal_record(record) }.freeze
41
41
  assistant = messages.reverse.find { |message| message.role.to_sym == :assistant }
42
42
  output, usage = SavedContextReader.provider_output_and_usage(agent, execution) if execution.phase.to_sym == :recovery_provider_completed
43
43
  RecoveryMaterial.new(projection: projection, messages: messages,
44
- assistant_message: assistant, output: output, usage: usage)
44
+ assistant_message: assistant, output: output, usage: usage,
45
+ cancellation_requested: agent.persistence.cancellation_requested?(agent_id: agent.agent_id, execution_id: execution.execution_id))
45
46
  end
46
47
 
47
48
  def continue_recovery_on_event_loop(execution, completion, material:)
48
- event_loop = @runtime.event_loop
49
+ environment = @environment
49
50
  action = recovery_action(execution)
50
51
  if action == :resolution_required
51
- Phronomy::Agent::ExecutionRegistry.for(event_loop).mark_agent_execution_admission(agent.agent_id,
52
+ environment.registry.mark_agent_execution_admission(agent.agent_id,
52
53
  execution_id: execution.execution_id, state: :recovery_required)
53
54
  deliver_resolution_required(execution, coordination_recovery_descriptor(execution))
54
55
  completion.complete({execution_id: execution.execution_id,
@@ -58,18 +59,18 @@ module Phronomy
58
59
  end
59
60
 
60
61
  observe_recovery_execution(completion, execution)
61
- main = agent.send(:execution_coordinator_for, agent.__coordination_config)
62
- projection = Phronomy::Agent::ExecutionRegistry.for(event_loop).agent_execution_state(execution.execution_id).runtime_projection
62
+ main = agent.send(:execution_coordinator_for, agent.__execution_wiring)
63
+ projection = environment.registry.agent_execution_state(execution.execution_id).runtime_projection
63
64
  if action == :failed_terminal
64
65
  invocation = build_failed_recovery_invocation(execution, main)
65
66
  else
66
67
  projection = material.projection
67
68
  invocation = if action == :framework_tools
68
69
  InvocationRestorer.build_invocation_for_suspended(agent, execution, projection, main,
69
- agent.send(:_phronomy_event_listener), assistant_message: material.assistant_message)
70
+ agent.send(:_phronomy_event_listener), assistant_message: material.assistant_message, cancellation_requested: material.cancellation_requested)
70
71
  else
71
- InvocationRestorer.build_chat_for_recovery(agent, execution, projection, main,
72
- agent.send(:_phronomy_event_listener), messages: material.messages)
72
+ InvocationRestorer.build_input_for_recovery(agent, execution, projection, main,
73
+ agent.send(:_phronomy_event_listener), messages: material.messages, cancellation_requested: material.cancellation_requested)
73
74
  end
74
75
  if execution.phase.to_sym == :recovery_provider_completed
75
76
  invocation.output, invocation.usage = material.output, material.usage