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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '095ac1376e77f34201dceeafec7dc114340aa2cae7cd8f1bb2bb7c2552e64e87'
4
- data.tar.gz: 4d900c3bdc20e3fb0210046daffc4d78d1b990f118281c04037cc45beca2d699
3
+ metadata.gz: 6ee0a6e2cc02a9014a1f6b4b0587474255697960b0e1589070a7846d038452ad
4
+ data.tar.gz: eb866c788b4dbaf5c560ec0a6a9fe1ef641d630932c7f4309c5ca0741dce2ad0
5
5
  SHA512:
6
- metadata.gz: 3108c48424885f72ea256e6c7cd7dfa8a7230647727f288f9fce840ee9309a51b523e64748663a7d1341e88b0c05cfe5432cb91a521039b16f28b0e2c14a4714
7
- data.tar.gz: 51795e8c019cb92d8a17eadb864b726f9359ed1c963b06acfb52cb7440b99234755ad2c7e169a5afb8e5958ea45b99635d9035eea4d3c4ceb84ac4d554fdc814
6
+ metadata.gz: a4970e936adca3d87ee8a8a18469630a563df8fb8a07f06796f4a4bfcae3b1e97b67a51869fdbb68760892201deb12f5f765c082cdfe8559c5f6d0c5ad10dd1a
7
+ data.tar.gz: 10c461ef9e86a476cc73020dc1167d6b80564f02f47c3da0161a7da8a34953be96efa236ebcfdf938c7b8a5bfeb2ea6ad3fdaca137d6f70cd5238a71bd6cb829
data/.mutant.yml CHANGED
@@ -9,14 +9,14 @@ matcher:
9
9
  subjects:
10
10
  - Phronomy::WorkflowContext
11
11
  - Phronomy::WorkflowRunner
12
- # Phronomy::Tool::Base is a public constant alias to this same Class object.
13
- # Use the implementation canonical name returned by Class#name for Mutant.
14
- - Phronomy::Agent::Context::Capability::Base
12
+ - Phronomy::Tool::Base
13
+ - Phronomy::Tool::Schema
15
14
  - Phronomy::LlmContextWindow::TokenBudget
16
- - Phronomy::Agent::ContextAssembler
17
- - Phronomy::Agent::ContextPolicies::Default
18
- - Phronomy::Agent::ContextPolicyInputBuilder
19
- - Phronomy::Agent::ContextPlanValidator
20
- - Phronomy::Agent::HandoffPolicy
21
- - Phronomy::Agent::HandoffProjection
15
+ - Phronomy::Context::Assembly
16
+ - Phronomy::Agent::ContextPreparation
17
+ - Phronomy::Context::DefaultPolicy
18
+ - Phronomy::Context::ContextPolicyInputBuilder
19
+ - Phronomy::Context::ContextPlanValidator
20
+ - Phronomy::MultiAgent::HandoffPolicy
21
+ - Phronomy::Agent::TransferProjection
22
22
  - Phronomy::VectorStore::InMemory
data/CHANGELOG.md CHANGED
@@ -12,6 +12,58 @@ Release history for 0.14.0 and earlier is archived in
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ### r8 first implementation unit (breaking, unreleased)
16
+
17
+ - Move Context policy/plan/manifest/budget/prompt operations into `Phronomy::Context`.
18
+ Agent retains snapshot, journal and lifecycle ownership. Manifest and execution
19
+ references remain written within the same transaction.
20
+ - Make `Tool::Base` the actual implementation; move common definition and
21
+ authorization operations into Tool. Remove the old Agent Context namespace.
22
+ - Move chat construction and SDK failure translation into the LLM adapter.
23
+ Custom synchronous adapters must implement the expanded construction SPI.
24
+ - Unite execution contracts and services in `execution/`; move FSM values and
25
+ scheduling bindings to Engine, observation to Tracing. Keep result/control
26
+ rules independent of Engine startup. Update Ruby/RBS analysis and diagrams.
27
+ - Remove empty contract-only subdivisions and old API aliases. This unit does
28
+ not complete r8 Persistence, parent admission or all domain FSM separation.
29
+ See [the scope and migration report](docs/architecture/r8-unit1.md).
30
+
31
+ ### Changed
32
+
33
+ - Add public Beta `Storage::AsyncClient#transaction_async` and route 16 existing
34
+ durable Agent/Workflow/coordination execution units through its private submit
35
+ boundary. Preserve transaction/savepoint, result reconciliation and admission
36
+ policy; do not add cancellation or timeout to existing terminal writes.
37
+ - Move StoredContents into `content_store/backends/` without changing its
38
+ constant or behavior. Complete P4; see the
39
+ [Storage client guide](docs/migrations/storage-async-client.md).
40
+ - Preserve transparent text panels and module outlines in generated diagrams.
41
+ Hide arrows into M33/M41/M44 as a display choice; keep their measured source
42
+ dependencies, evidence and complete matrix.
43
+
44
+ - **Beta API receiver migration:** move VectorStore `add_async`, `search_async`,
45
+ `remove_async`, `clear_async` and Embeddings `embed_async` onto the public
46
+ feature-specific `AsyncClient` objects. Synchronous backend methods and
47
+ concrete class names remain unchanged. Remove the inherited async methods
48
+ and `VectorStore::AsyncBackend`; see the
49
+ [migration guide](docs/migrations/vector-async-clients.md).
50
+ - Split VectorStore/Embeddings implementations into collapsed `backends/`
51
+ directories. Synchronous RBS uses structural cancellation; separate client
52
+ signatures retain TaskResult and the full execution token. Complete P3 and
53
+ remove the remaining backend-to-Engine edges from the measured graph.
54
+
55
+ - Separate the synchronous LLMAdapter SPI, RubyLLM implementation and internal
56
+ AsyncClient. Adapter authors implement only `complete` and `stream`; default
57
+ configuration, provider behavior, admission/cancellation and Agent event
58
+ delivery remain unchanged. See
59
+ [ADR-059](docs/decisions/059-backend-contracts-and-async-clients.md).
60
+ - Separate responsibility groups from the diagram's B1-B6 display layers. Add
61
+ the source-derived SVG/matrix generator and Architecture CI boundary gate;
62
+ retain individual module boxes, stable module IDs and the earlier horizontal
63
+ layer layout with topical columns. Separated Contracts belong in B5 beside
64
+ the independent Engine group; clients, implementations and pending mixed
65
+ backends are displayed in B4. Storage/ContentStore migration remains pending.
66
+
15
67
  ---
16
68
 
17
69
  ## [0.27.0] - 2026-09-24
data/CONTRIBUTING.md CHANGED
@@ -219,14 +219,14 @@ The authoritative subject list is `.mutant.yml`. It currently includes:
219
219
 
220
220
  - `Phronomy::WorkflowContext`
221
221
  - `Phronomy::WorkflowRunner`
222
- - `Phronomy::Agent::Context::Capability::Base`
222
+ - `Phronomy::Tool::Base`
223
223
  - `Phronomy::LlmContextWindow::TokenBudget`
224
- - `Phronomy::Agent::ContextAssembler`
225
- - `Phronomy::Agent::ContextPolicies::Default`
226
- - `Phronomy::Agent::ContextPolicyInputBuilder`
227
- - `Phronomy::Agent::ContextPlanValidator`
228
- - `Phronomy::Agent::HandoffPolicy`
229
- - `Phronomy::Agent::HandoffProjection`
224
+ - `Phronomy::Agent::ContextPreparation`
225
+ - `Phronomy::Context::DefaultPolicy`
226
+ - `Phronomy::Context::ContextPolicyInputBuilder`
227
+ - `Phronomy::Context::ContextPlanValidator`
228
+ - `Phronomy::MultiAgent::HandoffPolicy`
229
+ - `Phronomy::Agent::TransferProjection`
230
230
  - `Phronomy::VectorStore::InMemory`
231
231
 
232
232
  The nightly mutation matrix mirrors this authoritative list so each subject can
@@ -235,7 +235,7 @@ configuration-consistency guard that fails if the nightly subject set diverges
235
235
  from `.mutant.yml`.
236
236
 
237
237
  The Tool mutation subject intentionally uses
238
- `Phronomy::Agent::Context::Capability::Base`, which is the implementation
238
+ `Phronomy::Tool::Base`, which is the implementation
239
239
  canonical name returned by the single Class object's runtime `Class#name`.
240
240
  `Phronomy::Tool::Base` is the public facade constant alias to that same Class
241
241
  object; it is not a second Tool base class.
data/README.md CHANGED
@@ -8,7 +8,11 @@
8
8
 
9
9
  **Phronomy** is a Ruby AI agent framework for stateful Agents, Workflows, Tools,
10
10
  context management, filtering, tracing, and multi-agent coordination. Large Language
11
- Model (LLM) access is provided through [RubyLLM](https://github.com/crmne/ruby_llm).
11
+ Model (LLM) operations use `Phronomy::LLMAdapter`; the default backend uses
12
+ [RubyLLM](https://github.com/crmne/ruby_llm). Custom backends implement the
13
+ [SDK-independent LLM contract](docs/migrations/r8-unit6.md).
14
+ Tool declarations and argument validation belong to `Phronomy::Tool`, independently
15
+ of the selected LLM backend.
12
16
 
13
17
  Phronomy is pre-1.0. Pin to a released gem version for production use rather than
14
18
  tracking `main` directly.
@@ -16,8 +20,8 @@ tracking `main` directly.
16
20
  ## Core concepts
17
21
 
18
22
  - **Agent** — stateful, persistence-backed LLM agent with canonical execution history.
19
- - **Persistence** — unified durable backend for Agent state and Workflow `workflow_states`.
20
- - **Workflow** — state-machine-driven application workflow with explicit events and wait states.
23
+ - **Persistence** — shared atomic scopes with domain-owned Agent/Team stores and Workflow checkpoints.
24
+ - **Workflow** — state-machine-driven application workflow with task states, explicit events and wait states.
21
25
  - **Tool / Capability** — callable application capability exposed to an Agent; application-defined Tools subclass `Phronomy::Tool::Base`.
22
26
  - **Multi-Agent Handoff** — semantic Source-to-Target responsibility transfer with policy-bounded Context projection and persisted active responsibility and exact Target recovery within one Persistence domain.
23
27
  - **EventLoop + FSMSession** — the framework control plane for logical lifecycle coordination.
@@ -31,6 +35,19 @@ tracking `main` directly.
31
35
 
32
36
  See [Features and Application Programming Interface (API) stability](docs/features.md) for the full feature matrix.
33
37
 
38
+ The following application APIs are part of unreleased development:
39
+
40
+ | API | Stability | Contract |
41
+ |---|---|---|
42
+ | `Agent.open_scope`, `Agent::Scope`, `Agent::Base#unload_async / #unload!`, `AgentUnloadedError` and `Agent::ScopeCleanupError` | Beta | Runtime-bound ownership and safe release of idle Agent incarnations without changing saved history or closing the Store; [contract](docs/architecture/dx05-agent-scope.md) |
43
+ | `Workflow::Builder#task_state` and `WorkflowTaskResumeError` | Beta | State-entry-scoped Task completion, live cancellation and explicit restart rejection; [contract](docs/architecture/dx05-workflow-task-state.md) |
44
+ | `Phronomy::Integrations::OrderedEventDelivery` | Beta | Optional ordered delivery with bounded admission and explicit drain lifetime; [usage](docs/ordered-event-delivery.md) |
45
+ | `Phronomy::Error#code` and error constructor `code:` | Beta | Optional reason codes and bounded causes retained in saved failure diagnostics; [meaning and compatibility](docs/architecture/error-api-review.md) |
46
+ | `Phronomy::AgentNotFoundError#agent_id` | Beta | Identifies absence of the requested Agent root; referenced history and Content failures retain their own errors; [migration](docs/architecture/error-api-review.md) |
47
+ | `Phronomy::MultiAgent::TeamNotFoundError` and `TeamBusyError`, with `team_id` | Beta | Team-owned absence and busy errors; child Agent failures retain their Agent classification; [migration](docs/architecture/error-api-review.md) |
48
+ | `Phronomy::Embeddings::AuthenticationError` and `RateLimitError` | Beta | Embeddings-owned transport error subclasses retaining the provider cause; [migration](docs/architecture/error-api-review.md) |
49
+ | `Phronomy::Tracing::Base.trace_with` | Beta | Uses the tracer start/finish SPI, attempts finish once, and preserves a primary application error; [contract](docs/architecture/error-api-review.md) |
50
+
34
51
  ## Installation
35
52
 
36
53
  Add Phronomy to your Gemfile:
@@ -45,7 +62,10 @@ Then run:
45
62
  bundle install
46
63
  ```
47
64
 
48
- This refactoring branch requires RubyLLM 2.0.x. See the
65
+ Phronomy 0.28 requires RubyLLM 2.0.x. Faraday 2.1.0 is the tested minimum
66
+ supported version; the `~> 2.1` requirement allows versions from 2.1 up to,
67
+ but excluding, 3.0. CI checks fresh dependency resolution and the exact minimum
68
+ with MCP 1.0.0. See the
49
69
  [RubyLLM 2 and token-ownership migration](docs/architecture/rubyllm-2-token-ownership.md)
50
70
  for removed input-budget overrides and the in-flight Tool-manifest upgrade boundary.
51
71
 
@@ -87,9 +107,7 @@ result = ResearchAgent.new.invoke("What happened in AI research this week?")
87
107
  puts result[:output]
88
108
  ```
89
109
 
90
- `Phronomy::Tool::Base` is the public authoring name for the existing Tool base
91
- class. The legacy `Phronomy::Agent::Context::Capability::Base` constant remains
92
- valid for compatibility.
110
+ `Phronomy::Tool::Base` is the public base class for application-defined Tools.
93
111
 
94
112
  For non-blocking top-level use, call `invoke_async` and keep the returned
95
113
  `Phronomy::TaskResult`. Inside Phronomy lifecycle callbacks, do not block waiting for
@@ -148,6 +166,7 @@ contracts, timeout/cancellation semantics, metrics, and callback rules.
148
166
  ## Documentation
149
167
 
150
168
  - [Getting started](docs/getting-started.md) — installation, RubyLLM setup, Agent/Workflow basics, persistence, streaming.
169
+ - [Application recipes](docs/application-recipes.md) — complete persistence, streaming, and Workflow examples, including explicit failure handling.
151
170
  - [Features and API stability](docs/features.md) — public feature matrix and stability labels.
152
171
  - [Architecture](docs/architecture.md) — canonical current explanatory architecture entry and authority navigation.
153
172
  - [Runtime and concurrency](docs/runtime-and-concurrency.md) — EventLoop, FSMSession, TaskResult, OffloadPool, cancellation, observability.
@@ -155,6 +174,8 @@ contracts, timeout/cancellation semantics, metrics, and callback rules.
155
174
  - [MCP client](docs/mcp-client.md) — Model Context Protocol (MCP) integration and supported schema subset.
156
175
  - [Migration from 0.15-era APIs](docs/migrations/0.15.md).
157
176
  - [0.16 cleanup migration](docs/migrations/0.16.md).
177
+ - [r8 persistence boundary migration](docs/architecture/r8-unit3.md) — domain stores, shared scopes, and removed facade APIs.
178
+ - [Persistence failure contract migration](docs/migrations/persistence-failure-contracts.md) — domain rescue clauses and unchanged raw backend errors.
158
179
  - [0.19 unified Persistence migration](docs/migrations/0.19.md).
159
180
  - [0.22 semantic Multi-Agent Handoff migration](docs/migrations/0.22.md).
160
181
  - [Architecture Decision Records](docs/decisions/) — design rationale and superseding decisions.
@@ -19,49 +19,21 @@ require_relative "../lib/phronomy"
19
19
  # Shared stubs
20
20
  # ---------------------------------------------------------------------------
21
21
 
22
- module BenchAgentMessage
23
- def self.assistant(content = "done")
24
- RubyLLM::Message.new(
25
- role: :assistant, content: content,
26
- tokens: RubyLLM::Tokens.new(input: 5, output: 5, cache_read: 0, cache_write: 0)
27
- )
28
- end
29
- end
30
-
31
- # A minimal stub Chat that returns a pre-built response immediately.
32
- class BenchStubChat
33
- attr_reader :messages
34
-
22
+ # A stub backend that preserves the adapter's validation/cancellation template.
23
+ class BenchStubLLMAdapter < Phronomy::LLMAdapter::Base
35
24
  def initialize(response)
36
25
  @response = response
37
- @messages = []
38
- end
39
-
40
- def with_instructions(_, **_options) = self
41
- def with_tools(*) = self
42
- def with_temperature(_) = self
43
- def with_cache_instructions(_) = self
44
- def with_output_schema(_) = self
45
- def last_message = @response
46
-
47
- def after_message(&block)
48
- @after_message = block
49
- self
50
26
  end
51
27
 
52
- def ask(_)
53
- complete
54
- end
28
+ protected
55
29
 
56
- def complete
57
- @messages << @response
58
- @after_message&.call(@response)
30
+ def perform_complete(request, cancellation_token:)
59
31
  @response
60
32
  end
61
33
  end
62
34
 
63
35
  # A stub tool that does nothing but conforms to the Tool::Base interface.
64
- class BenchNullTool < Phronomy::Agent::Context::Capability::Base
36
+ class BenchNullTool < Phronomy::Tool::Base
65
37
  description "No-op benchmark tool"
66
38
  param :x, type: :string, desc: "input"
67
39
 
@@ -74,38 +46,41 @@ end
74
46
  # Agent classes
75
47
  # ---------------------------------------------------------------------------
76
48
 
77
- BENCH_RESP = BenchAgentMessage.assistant("benchmark complete")
49
+ BENCH_RESP = Phronomy::LLMAdapter::Response.new(
50
+ content: "benchmark complete",
51
+ usage: Phronomy::LLMAdapter::TokenUsage.new(input: 5, output: 5, cached: 0, cache_creation: 0)
52
+ )
78
53
 
79
54
  bench_minimal_class = Class.new(Phronomy::Agent::Base) do
80
55
  agent_definition id: "bench-minimal", version: 1
81
56
  model "stub-model"
82
-
83
- define_method(:build_chat) { |*| BenchStubChat.new(BENCH_RESP) }
84
57
  end
85
58
 
86
59
  bench_tool_class = Class.new(Phronomy::Agent::Base) do
87
60
  agent_definition id: "bench-tool", version: 1
88
61
  model "stub-model"
89
62
  tools(BenchNullTool => nil)
90
-
91
- define_method(:build_chat) { |*| BenchStubChat.new(BENCH_RESP) }
92
63
  end
93
64
 
94
65
  AGENT_INVOKE_ITERATIONS = 200
95
- BENCH_AGENTS_MINIMAL = Array.new(AGENT_INVOKE_ITERATIONS) { bench_minimal_class.new }.freeze
96
- BENCH_AGENTS_TOOLS = Array.new(AGENT_INVOKE_ITERATIONS) { bench_tool_class.new }.freeze
97
-
98
- puts "=== bench_agent_invoke ==="
99
- Benchmark.bm(50) do |x|
100
- x.report("Agent#invoke — fresh, no tools, #{AGENT_INVOKE_ITERATIONS} iters") do
101
- BENCH_AGENTS_MINIMAL.each do |agent|
102
- agent.invoke("ping")
66
+
67
+ Phronomy.with_configuration do |config|
68
+ config.llm_adapter = BenchStubLLMAdapter.new(BENCH_RESP)
69
+ bench_agents_minimal = Array.new(AGENT_INVOKE_ITERATIONS) { bench_minimal_class.new }.freeze
70
+ bench_agents_tools = Array.new(AGENT_INVOKE_ITERATIONS) { bench_tool_class.new }.freeze
71
+
72
+ puts "=== bench_agent_invoke ==="
73
+ Benchmark.bm(50) do |x|
74
+ x.report("Agent#invoke — fresh, no tools, #{AGENT_INVOKE_ITERATIONS} iters") do
75
+ bench_agents_minimal.each do |agent|
76
+ agent.invoke("ping")
77
+ end
103
78
  end
104
- end
105
79
 
106
- x.report("Agent#invoke — fresh, tool-aware, #{AGENT_INVOKE_ITERATIONS} iters") do
107
- BENCH_AGENTS_TOOLS.each do |agent|
108
- agent.invoke("ping")
80
+ x.report("Agent#invoke — fresh, tool-aware, #{AGENT_INVOKE_ITERATIONS} iters") do
81
+ bench_agents_tools.each do |agent|
82
+ agent.invoke("ping")
83
+ end
109
84
  end
110
85
  end
111
86
  end
@@ -14,7 +14,7 @@ module BenchContextAssembler
14
14
  module_function
15
15
 
16
16
  def provenance
17
- Phronomy::Agent::ContextPolicyInput::Provenance.new(origin: :journal)
17
+ Phronomy::Context::ContextPolicyInput::Provenance.new(origin: :journal)
18
18
  end
19
19
 
20
20
  def policy_input(item_count)
@@ -22,13 +22,13 @@ module BenchContextAssembler
22
22
  conversation = []
23
23
  item_count.times do |index|
24
24
  if (index % 10).zero?
25
- knowledge << Phronomy::Agent::ContextPolicyInput::KnowledgeItem.new(
25
+ knowledge << Phronomy::Context::ContextPolicyInput::KnowledgeItem.new(
26
26
  id: "knowledge-#{index}", kind: :knowledge, role: :user,
27
27
  content: "knowledge #{index}", content_format: :text,
28
28
  estimated_tokens: 8, required: false, provenance: provenance, metadata: {}
29
29
  )
30
30
  else
31
- conversation << [Phronomy::Agent::ContextPolicyInput::ConversationItem.new(
31
+ conversation << [Phronomy::Context::ContextPolicyInput::ConversationItem.new(
32
32
  id: "message-#{index}", kind: :external_message, role: :user,
33
33
  content: "message #{index}", content_format: :text,
34
34
  sequence: index, estimated_tokens: 8, required: false,
@@ -38,7 +38,7 @@ module BenchContextAssembler
38
38
  end
39
39
  end
40
40
 
41
- Phronomy::Agent::ContextPolicyInput.new(
41
+ Phronomy::Context::ContextPolicyInput.new(
42
42
  agent_id: "bench-agent", execution_id: "bench-execution",
43
43
  call_sequence: 2, call_mode: :complete,
44
44
  instruction: [], knowledge: knowledge, tools: [], conversation: conversation,
@@ -50,7 +50,7 @@ module BenchContextAssembler
50
50
  end
51
51
 
52
52
  def assembler_fixture
53
- persistence = Phronomy::Persistence.in_memory
53
+ persistence = Phronomy::PersistenceComposition.in_memory.agent
54
54
  agent_class = Class.new(Phronomy::Agent::Base) do
55
55
  agent_definition id: "bench-manifest-context-assembler", version: 1
56
56
  # Resolve capabilities from RubyLLM's bundled registry; no provider call.
@@ -86,9 +86,10 @@ module BenchContextAssembler
86
86
  )
87
87
 
88
88
  [
89
- Phronomy::Agent::ContextAssembler.new(agent: agent, persistence: persistence),
89
+ Phronomy::Agent::ContextPreparation.new(agent: agent, persistence: persistence),
90
90
  root,
91
- execution
91
+ execution,
92
+ persistence
92
93
  ]
93
94
  end
94
95
  end
@@ -97,7 +98,7 @@ puts "Typed Context Policy benchmark"
97
98
  puts "Ruby #{RUBY_VERSION} on #{RUBY_PLATFORM}"
98
99
  puts "=" * 72
99
100
 
100
- policy = Phronomy::Agent::ContextPolicies::Default.instance
101
+ policy = Phronomy::Context::DefaultPolicy.instance
101
102
  policy_inputs = [10, 100, 1_000].to_h do |count|
102
103
  [count, BenchContextAssembler.policy_input(count)]
103
104
  end
@@ -110,15 +111,17 @@ Benchmark.bm(46) do |x|
110
111
  end
111
112
  end
112
113
 
113
- assembler, root, execution = BenchContextAssembler.assembler_fixture
114
- x.report("ContextAssembler prepare/finalize x500") do
114
+ assembler, root, execution, persistence = BenchContextAssembler.assembler_fixture
115
+ x.report("Context prepare/store x500") do
115
116
  500.times do
116
117
  prepared = assembler.prepare_initial(
117
118
  input: "benchmark input",
118
119
  agent_root: root,
119
120
  execution: execution
120
121
  )
121
- assembler.finalize(prepared)
122
+ persistence.transaction do |tx|
123
+ Phronomy::Context::Assembly.new.store(prepared, contents: tx.contents)
124
+ end
122
125
  end
123
126
  end
124
127
  end
@@ -69,7 +69,7 @@ end
69
69
  # ---------------------------------------------------------------------------
70
70
  # Target 3: Tool::Base#params_schema generation (10 params)
71
71
  # ---------------------------------------------------------------------------
72
- tool_class = Class.new(Phronomy::Agent::Context::Capability::Base) do
72
+ tool_class = Class.new(Phronomy::Tool::Base) do
73
73
  description "Test tool with 10 params"
74
74
  param :p1, type: :string, desc: "param 1"
75
75
  param :p2, type: :string, desc: "param 2"
@@ -7,7 +7,7 @@
7
7
  require "benchmark"
8
8
  require_relative "../lib/phronomy"
9
9
 
10
- class BenchTool10Params < Phronomy::Agent::Context::Capability::Base
10
+ class BenchTool10Params < Phronomy::Tool::Base
11
11
  description "A tool with 10 parameters for benchmarking purposes"
12
12
  param :param1, type: :string, desc: "First parameter"
13
13
  param :param2, type: :integer, desc: "Second parameter"
@@ -0,0 +1,229 @@
1
+ # Application recipes
2
+
3
+ Start with [Getting started](getting-started.md). Every `ruby runnable` block
4
+ below is a complete example after `require "phronomy"` and provider configuration.
5
+ CI substitutes only the LLM adapter; Agent, Tool and Workflow operations are real.
6
+
7
+ ## Choose the completion channel
8
+
9
+ | Need | Public API | Caller responsibility |
10
+ |---|---|---|
11
+ | One final answer | `agent.invoke(input)` | Call from an external thread |
12
+ | Nonblocking final answer | `agent.invoke_async(input)` | Observe the returned TaskResult |
13
+ | Intermediate events | Construction-time `on_event:` | Keep the callback short |
14
+ | Streamed tokens | `stream` / `stream_async` with a listener | Move network delivery out of the callback |
15
+ | Durable approval | `approve_async` with the request IDs | Apply the application's authorization decision |
16
+ | State-owned asynchronous result | `task_state(name, start:, apply:)` | Framework correlates completion to state entry |
17
+ | Live Workflow notification | `workflow.signal(...)` | Check admission; correlate the waiting operation |
18
+
19
+ `TaskResult#wait_result` waits at an external application boundary. Completion
20
+ callbacks and Agent listeners must not block EventLoop. A wait timeout does not
21
+ cancel the underlying operation. The caller owns cancellation and cleanup.
22
+
23
+ ## Create, load and get
24
+
25
+ ```ruby runnable
26
+ class ConversationAgent < Phronomy::Agent::Base
27
+ agent_definition id: "recipe-conversation", version: 1
28
+ model "gpt-4o-mini"
29
+ instructions "Answer briefly."
30
+ end
31
+
32
+ store = Phronomy::PersistenceComposition.in_memory.agent
33
+ agent = ConversationAgent.create(agent_id: "recipe-session", persistence: store)
34
+ puts agent.invoke("Hello").fetch(:output)
35
+
36
+ # A live lookup is not a database read. Loading a live identity returns its owner.
37
+ raise "Unexpected owner" unless ConversationAgent.get("recipe-session").equal?(agent)
38
+ raise "Unexpected load" unless ConversationAgent.load("recipe-session", persistence: store).equal?(agent)
39
+
40
+ def load_existing_agent(agent_class:, agent_id:, persistence:)
41
+ agent_class.load(agent_id, persistence: persistence)
42
+ rescue Phronomy::AgentNotFoundError => error
43
+ raise unless error.agent_id == agent_id.to_s
44
+ nil
45
+ end
46
+
47
+ missing = load_existing_agent(
48
+ agent_class: ConversationAgent, agent_id: "missing-session", persistence: store
49
+ )
50
+ raise "Expected a missing Agent" unless missing.nil?
51
+ ```
52
+
53
+ This application treats only absence as `nil`. Storage unavailability, state
54
+ conflicts and recovery requirements remain errors. `create` is not upsert; do
55
+ not catch every persistence failure and create a replacement conversation.
56
+ The in-memory store is for this example; a durable backend must outlive Runtime
57
+ to support process restart. See [persistence failures](migrations/persistence-failure-contracts.md).
58
+
59
+ ## Streaming and final results
60
+
61
+ ```ruby runnable
62
+ class StreamingAgent < Phronomy::Agent::Base
63
+ agent_definition id: "recipe-streaming", version: 1
64
+ model "gpt-4o-mini"
65
+ instructions "Explain briefly."
66
+ end
67
+
68
+ events = Queue.new
69
+ agent = StreamingAgent.new(on_event: ->(event) {
70
+ events << event if event.type == :token
71
+ })
72
+
73
+ task = agent.stream_async("Explain asynchronous results")
74
+ result = task.wait_result
75
+ puts result.fetch(:output)
76
+ puts "Received #{events.size} token events"
77
+ ```
78
+
79
+ The unbounded Queue is an in-process teaching example. A Web application needs
80
+ bounded admission and ordered delivery outside the listener, such as Example 18.
81
+ Register the listener at `new`/`create`/first `load`; per-invocation listener
82
+ arguments or blocks are rejected. Do not replace a listener by loading an
83
+ already-live Agent. See [approval](getting-started.md#human-in-the-loop-approval)
84
+ for the separate durable approval identifiers and application decision.
85
+
86
+ ## Workflow task states
87
+
88
+ When a Workflow owns the start of an operation, use `task_state` to keep the
89
+ application focused on its operation, result fields and routing decisions.
90
+ The example below returns the original error to its caller if either invocation
91
+ fails; it does not silently retry or convert a failed invocation into an answer.
92
+
93
+ ```ruby runnable
94
+ class TwoAnswersState
95
+ include Phronomy::WorkflowContext
96
+ field :question, type: :replace
97
+ field :answers, type: :replace, default: -> { [] }
98
+ end
99
+ class TwoAnswersAgent < Phronomy::Agent::Base
100
+ agent_definition id: "recipe-two-answers", version: 1
101
+ model "gpt-4o-mini"
102
+ instructions "Answer briefly."
103
+ end
104
+ agent = TwoAnswersAgent.new
105
+ workflow = Phronomy::Workflow.define(TwoAnswersState) do
106
+ initial :asking
107
+ task_state :asking,
108
+ start: ->(state) { agent.invoke_async(state.question) },
109
+ apply: ->(state, result) { state.merge(answers: state.answers + [result.fetch(:output)]) }
110
+ state :decide
111
+ transition from: :asking, to: :decide
112
+ transition from: :decide, to: :__finish__, guard: ->(state) { state.answers.length >= 2 }
113
+ transition from: :decide, to: :asking
114
+ end
115
+ puts workflow.invoke({question: "Explain completion handles"}).answers
116
+ ```
117
+
118
+ Expected business failures can be converted into application values before
119
+ returning the source Task, for example using `TaskResult.all_settled` and `map`.
120
+ Phronomy does not select a fallback or retry policy. See the
121
+ [full contract](architecture/dx05-workflow-task-state.md).
122
+
123
+ ## Workflow completion and failures
124
+
125
+ The following **legacy low-level signal recipe** is retained for existing users
126
+ and its compatibility tests. New state-owned asynchronous operations should use
127
+ `task_state` above. The restrictions below apply to this manual recipe, not to
128
+ the new task-state API.
129
+
130
+ This recipe targets **one live waiting stage**. Do not restart, reenter or replace
131
+ that stage while a result is pending. A logical `workflow_instance_id` alone does
132
+ not identify a particular execution or waiting operation. For those wider cases,
133
+ application operation IDs and receiver-side guards are necessary.
134
+
135
+ ```ruby runnable
136
+ def request_answer(agent:, workflow:, workflow_instance_id:, question:,
137
+ on_delivery_error:)
138
+ task = agent.invoke_async(question).map do |result|
139
+ {answer: result.fetch(:output)}
140
+ end
141
+
142
+ task.on_complete do |payload, operation_error|
143
+ delivery_error = nil
144
+ begin
145
+ accepted = workflow.signal(
146
+ workflow_instance_id: workflow_instance_id,
147
+ event: :answer_ready,
148
+ payload: operation_error ? {error: operation_error} : payload
149
+ )
150
+ unless accepted
151
+ delivery_error = Phronomy::Error.new("Workflow did not accept answer_ready")
152
+ end
153
+ rescue => error
154
+ delivery_error = error
155
+ end
156
+ on_delivery_error.call(delivery_error) if delivery_error
157
+ end
158
+
159
+ nil
160
+ end
161
+
162
+ class AnswerAgent < Phronomy::Agent::Base
163
+ agent_definition id: "recipe-answer", version: 1
164
+ model "gpt-4o-mini"
165
+ instructions "Answer briefly."
166
+ end
167
+
168
+ class AnswerState
169
+ include Phronomy::WorkflowContext
170
+ field :question, type: :replace
171
+ field :answer, type: :replace
172
+ field :error_message, type: :replace
173
+ end
174
+
175
+ agent = AnswerAgent.new
176
+ delivery_errors = Queue.new
177
+ workflow = nil
178
+ workflow = Phronomy::Workflow.define(AnswerState) do
179
+ initial :asking
180
+ state :asking
181
+ state :done
182
+ entry :asking, ->(state) {
183
+ request_answer(
184
+ agent: agent, workflow: workflow,
185
+ workflow_instance_id: state.workflow_instance_id, question: state.question,
186
+ on_delivery_error: ->(error) { delivery_errors << error }
187
+ )
188
+ state
189
+ }
190
+ transition from: :asking, on: :answer_ready, to: :done,
191
+ action: ->(state, event) {
192
+ error = event.payload[:error]
193
+ error ? state.merge(error_message: error.message) : state.merge(answer: event.payload.fetch(:answer))
194
+ }
195
+ transition from: :done, to: :__finish__
196
+ end
197
+
198
+ final = workflow.invoke(
199
+ {question: "What is a completion handle?"},
200
+ config: {workflow_instance_id: "recipe-answer"}
201
+ )
202
+ puts final.error_message || final.answer
203
+ raise delivery_errors.pop unless delivery_errors.empty?
204
+ ```
205
+
206
+ The receiver handles both answer and error; changing only the sender would be
207
+ incomplete. Store a serializable diagnostic, not the exception object, in the
208
+ Workflow context. Result mapping failures (such as a missing `:output`) also
209
+ become operation failures. Synchronous submission errors still propagate to
210
+ the caller of `request_answer`.
211
+
212
+ `signal == true` means admission, not transition completion or durable success.
213
+ `false` and signal exceptions reach `on_delivery_error` once without automatic
214
+ retry. This callback must be short and nonblocking. Its own exceptions follow
215
+ TaskResult's existing callback logging policy; they do not change the settled
216
+ operation result. Logging depends on the configured logger.
217
+
218
+ Delivery rejection does not itself settle a waiting Workflow. The application
219
+ must observe its diagnostic channel and decide whether to cancel or recover.
220
+ The synchronous invocation above illustrates successful delivery. Production
221
+ callers needing a wait limit should use `invoke_async` and manage both the
222
+ Workflow TaskResult and the diagnostic channel at their external boundary.
223
+
224
+ No generic Workflow forwarding helper is required by these recipes. A helper
225
+ must first establish how an old result is prevented from reaching a new waiting
226
+ stage; internal execution generations or automatic recovery are not implied.
227
+
228
+ For an application-local extraction with the same limited lifetime, see the
229
+ [bounded completion recipe](workflow-completion.md). It is not a new library API.