phronomy 0.22.0 → 0.24.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 (178) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +3 -4
  3. data/CHANGELOG.md +215 -10
  4. data/CONTRIBUTING.md +81 -9
  5. data/README.md +15 -6
  6. data/VERIFY.sh +587 -0
  7. data/benchmark/bench_agent_invoke.rb +2 -2
  8. data/benchmark/bench_context_assembler.rb +39 -68
  9. data/benchmark/bench_regression.rb +2 -2
  10. data/docs/architecture/agent-context.md +174 -0
  11. data/docs/architecture/before-llm-input.md +78 -0
  12. data/docs/architecture/context-management.md +232 -0
  13. data/docs/architecture/knowledge-and-rag.md +130 -0
  14. data/docs/architecture/multi-agent-handoff.md +147 -0
  15. data/docs/architecture/persistence.md +186 -0
  16. data/docs/architecture/removed/agent-context.md +72 -0
  17. data/docs/architecture/security-boundaries.md +173 -0
  18. data/docs/architecture/tracing.md +194 -0
  19. data/docs/architecture.md +89 -0
  20. data/docs/archive/design/archived/04_api_design.md +507 -0
  21. data/docs/archive/design/archived/09_guardrails.md +186 -0
  22. data/docs/archive/design/archived/17_rails_integration.md +175 -0
  23. data/docs/archive/design/historical/00_design_philosophy.md +122 -0
  24. data/docs/archive/design/historical/01_rubyllm_evaluation.md +178 -0
  25. data/docs/archive/design/historical/06_design_decisions.md +143 -0
  26. data/docs/changelog/0.14-and-earlier.md +1 -1
  27. data/docs/decisions/001-rubyllm-as-provider-layer.md +6 -1
  28. data/docs/decisions/002-workflow-context-immutability.md +26 -1
  29. data/docs/decisions/006-no-built-in-guardrails.md +2 -1
  30. data/docs/decisions/012-canonical-execution-log-and-context-policy.md +120 -38
  31. data/docs/decisions/014-unified-persistence-durable-state.md +9 -2
  32. data/docs/decisions/016-semantic-multi-agent-handoff.md +114 -0
  33. data/docs/decisions/017-design-authority-and-adr-governance.md +200 -0
  34. data/docs/decisions/018-durability-guarantees-and-failure-model.md +488 -0
  35. data/docs/decisions/019-filter-contract-and-security-boundaries.md +229 -0
  36. data/docs/decisions/020-canonical-workflow-instance-identity.md +177 -0
  37. data/docs/decisions/021-generic-agent-invocation-identity-removal.md +119 -0
  38. data/docs/decisions/022-agent-execution-parent-identity-and-runtime-routing-boundary.md +193 -0
  39. data/docs/decisions/023-fsm-session-incarnation-identity-and-routing.md +139 -0
  40. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +188 -0
  41. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +249 -0
  42. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +257 -0
  43. data/docs/decisions/027-llm-adapter-provider-boundary.md +93 -0
  44. data/docs/decisions/028-preparing-recovery-replay-contract.md +106 -0
  45. data/docs/decisions/029-semantic-completion-and-application-effect-boundary.md +220 -0
  46. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +235 -0
  47. data/docs/decisions/031-durable-multi-agent-coordination.md +301 -0
  48. data/docs/decisions/README.md +176 -0
  49. data/docs/design/durable-semantic-coordination/CHANGELOG_V2_REVISION_2.md +33 -0
  50. data/docs/design/durable-semantic-coordination/CONTINUATION_DECISION_REFACTOR.md +191 -0
  51. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +862 -0
  52. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +106 -0
  53. data/docs/design/durable-semantic-coordination/RECOVERY_CONTRACT_CLARIFICATIONS.md +179 -0
  54. data/docs/design/durable-semantic-coordination/RESPONSIBILITY_BOUNDARY_REVIEW.md +302 -0
  55. data/docs/features.md +65 -11
  56. data/docs/getting-started.md +77 -45
  57. data/docs/migrations/0.19.md +14 -7
  58. data/docs/migrations/0.22.md +390 -0
  59. data/docs/migrations/durable-semantic-coordination-v2.md +65 -0
  60. data/docs/persistence-backends.md +129 -40
  61. data/docs/runtime-and-concurrency.md +227 -33
  62. data/examples/README.md +13 -0
  63. data/lib/phronomy/agent/agent_execution.rb +21 -17
  64. data/lib/phronomy/agent/agent_invocation.rb +288 -93
  65. data/lib/phronomy/agent/agent_invocation_session_builder.rb +236 -202
  66. data/lib/phronomy/agent/agent_root.rb +3 -3
  67. data/lib/phronomy/agent/approval_evaluation_request.rb +37 -19
  68. data/lib/phronomy/agent/async_event_api.rb +162 -72
  69. data/lib/phronomy/agent/base.rb +416 -181
  70. data/lib/phronomy/agent/concerns/before_llm_input.rb +1 -1
  71. data/lib/phronomy/agent/context_assembler.rb +437 -178
  72. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  73. data/lib/phronomy/agent/context_plan.rb +18 -13
  74. data/lib/phronomy/agent/context_plan_validator.rb +246 -88
  75. data/lib/phronomy/agent/context_policies/default.rb +123 -34
  76. data/lib/phronomy/agent/context_policy.rb +109 -3
  77. data/lib/phronomy/agent/context_policy_input.rb +244 -0
  78. data/lib/phronomy/agent/context_policy_input_builder.rb +241 -0
  79. data/lib/phronomy/agent/exact_execution.rb +153 -0
  80. data/lib/phronomy/agent/execution_cancellation.rb +25 -0
  81. data/lib/phronomy/agent/execution_coordinator.rb +2417 -572
  82. data/lib/phronomy/agent/handoff.rb +43 -0
  83. data/lib/phronomy/agent/handoff_capability_factory.rb +45 -0
  84. data/lib/phronomy/agent/handoff_context.rb +120 -0
  85. data/lib/phronomy/agent/handoff_execution_coordinator.rb +143 -0
  86. data/lib/phronomy/agent/handoff_policy.rb +143 -0
  87. data/lib/phronomy/agent/handoff_projection.rb +207 -0
  88. data/lib/phronomy/agent/handoff_request.rb +45 -0
  89. data/lib/phronomy/agent/handoff_runner.rb +178 -0
  90. data/lib/phronomy/agent/handoff_state.rb +43 -0
  91. data/lib/phronomy/agent/journal_record.rb +17 -4
  92. data/lib/phronomy/agent/llm_input_build_context.rb +1 -1
  93. data/lib/phronomy/agent/llm_input_manifest.rb +277 -2
  94. data/lib/phronomy/agent/llm_operation_result.rb +12 -7
  95. data/lib/phronomy/agent/phase_machine_builder.rb +19 -7
  96. data/lib/phronomy/agent/provider_call_outcome.rb +23 -7
  97. data/lib/phronomy/agent/recovery_coordinator/continuation.rb +174 -0
  98. data/lib/phronomy/agent/recovery_coordinator/installation.rb +381 -0
  99. data/lib/phronomy/agent/recovery_coordinator/resolution.rb +511 -0
  100. data/lib/phronomy/agent/recovery_coordinator.rb +217 -0
  101. data/lib/phronomy/agent/recovery_support.rb +504 -0
  102. data/lib/phronomy/agent/ruby_llm_materializer.rb +16 -13
  103. data/lib/phronomy/agent/selection/candidate.rb +53 -0
  104. data/lib/phronomy/agent/selection/constraint.rb +49 -0
  105. data/lib/phronomy/agent/shared_state.rb +38 -1
  106. data/lib/phronomy/agent/tool_approval_request.rb +33 -5
  107. data/lib/phronomy/agent/tool_definition_set.rb +49 -3
  108. data/lib/phronomy/agent/tool_invocation.rb +339 -103
  109. data/lib/phronomy/agent/tool_invocation_session_builder.rb +49 -45
  110. data/lib/phronomy/agent.rb +20 -2
  111. data/lib/phronomy/agent_already_exists_error.rb +5 -0
  112. data/lib/phronomy/agent_purged_error.rb +5 -0
  113. data/lib/phronomy/engine/concurrency/offload_pool.rb +17 -3
  114. data/lib/phronomy/engine/concurrency/physical_completion_task.rb +135 -0
  115. data/lib/phronomy/engine/event_loop.rb +622 -63
  116. data/lib/phronomy/engine/fsm_session.rb +194 -21
  117. data/lib/phronomy/engine/runtime/agent_ownership_registry.rb +352 -0
  118. data/lib/phronomy/engine/runtime/team_ownership_registry.rb +77 -0
  119. data/lib/phronomy/engine/runtime.rb +92 -20
  120. data/lib/phronomy/generator_verifier.rb +12 -14
  121. data/lib/phronomy/invocation_context.rb +9 -29
  122. data/lib/phronomy/multi_agent/admission_registry.rb +51 -0
  123. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +134 -0
  124. data/lib/phronomy/multi_agent/fan_out_invocation.rb +24 -33
  125. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +12 -19
  126. data/lib/phronomy/multi_agent/orchestrator.rb +71 -26
  127. data/lib/phronomy/multi_agent/team_coordinator.rb +473 -125
  128. data/lib/phronomy/multi_agent/team_execution.rb +44 -0
  129. data/lib/phronomy/multi_agent/team_root.rb +41 -0
  130. data/lib/phronomy/persistence/durable_codec.rb +706 -0
  131. data/lib/phronomy/persistence/durable_record.rb +117 -0
  132. data/lib/phronomy/persistence/in_memory.rb +471 -133
  133. data/lib/phronomy/persistence/migration/initial_format_migration.rb +226 -0
  134. data/lib/phronomy/persistence/repository_facades.rb +535 -0
  135. data/lib/phronomy/persistence.rb +174 -40
  136. data/lib/phronomy/recovery.rb +186 -0
  137. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  138. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +2 -1
  139. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +19 -19
  140. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +3 -3
  141. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  142. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +137 -0
  143. data/lib/phronomy/testing/persistence_contract.rb +5 -0
  144. data/lib/phronomy/tools/agent.rb +1 -1
  145. data/lib/phronomy/tracing/automatic.rb +176 -0
  146. data/lib/phronomy/tracing/base.rb +11 -2
  147. data/lib/phronomy/tracing/langfuse_tracer.rb +20 -12
  148. data/lib/phronomy/version.rb +1 -1
  149. data/lib/phronomy/workflow.rb +3 -6
  150. data/lib/phronomy/workflow_context.rb +14 -5
  151. data/lib/phronomy/workflow_recovery.rb +123 -0
  152. data/lib/phronomy/workflow_runner.rb +468 -256
  153. data/lib/phronomy.rb +6 -0
  154. data/scripts/api_snapshot.rb +12 -0
  155. data/sig/phronomy/agent.rbs +209 -7
  156. data/sig/phronomy/handoff.rbs +41 -0
  157. data/sig/phronomy/multi_agent.rbs +35 -0
  158. data/sig/phronomy/persistence.rbs +123 -4
  159. data/sig/phronomy/runtime.rbs +1 -4
  160. data/sig/phronomy/workflow.rbs +2 -2
  161. data/sig/phronomy.rbs +10 -0
  162. metadata +84 -18
  163. data/examples/workflows/agent_event_mapping.rb +0 -101
  164. data/examples/workflows/generic_task_event_mapping.rb +0 -66
  165. data/lib/phronomy/agent/activation_registry.rb +0 -28
  166. data/lib/phronomy/agent/agent_execution_activation.rb +0 -172
  167. data/lib/phronomy/agent/context_candidate.rb +0 -47
  168. data/lib/phronomy/agent/context_parts/budget/token_budget_packer.rb +0 -53
  169. data/lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb +0 -56
  170. data/lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb +0 -30
  171. data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +0 -118
  172. data/lib/phronomy/agent/context_policy_descriptor.rb +0 -49
  173. data/lib/phronomy/agent/context_policy_registry.rb +0 -46
  174. data/lib/phronomy/agent/context_request.rb +0 -35
  175. data/lib/phronomy/agent/context_selection_unit.rb +0 -38
  176. data/lib/phronomy/agent/derived_content_spec.rb +0 -34
  177. data/lib/phronomy/agent/runner.rb +0 -97
  178. data/lib/phronomy/multi_agent/handoff.rb +0 -64
@@ -0,0 +1,381 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ class RecoveryCoordinator
6
+ # Installation portion of durable Agent Recovery.
7
+ # @api private
8
+ module Installation
9
+ private
10
+
11
+ def framework_batch?(execution)
12
+ return false unless %i[dispatching_tools resuming recovery_tools].include?(execution.phase.to_sym)
13
+ return false if execution.status == :suspended
14
+ entries = Array(execution.metadata[RecoverySupport::TOOL_BATCH_METADATA_KEY])
15
+ pending = entries.reject { |entry| %w[completed rejected failed cancelled].include?(entry.fetch("status")) }
16
+ !pending.empty? && pending.all? do |entry|
17
+ entry.fetch("status") == "authorized" && agent.__framework_tool_replayable?(entry.fetch("tool_name"))
18
+ end
19
+ end
20
+
21
+ # Framework operations never become Application factual questions. In a
22
+ # mixed batch, external facts are resolved first; authorized framework
23
+ # slots then resume through their normal Tool sessions.
24
+ def coordination_recovery_descriptor(execution)
25
+ metadata = execution.metadata.dup
26
+ if metadata[RecoverySupport::TOOL_BATCH_METADATA_KEY]
27
+ metadata[RecoverySupport::TOOL_BATCH_METADATA_KEY] = metadata[RecoverySupport::TOOL_BATCH_METADATA_KEY].sort_by do |entry|
28
+ agent.__framework_tool_replayable?(entry.fetch("tool_name")) ? 1 : 0
29
+ end
30
+ end
31
+ if (recovery = metadata[RecoverySupport::RECOVERY_METADATA_KEY]) && recovery["subjects"]
32
+ metadata[RecoverySupport::RECOVERY_METADATA_KEY] = recovery.merge("subjects" => recovery["subjects"].sort_by do |entry|
33
+ agent.__framework_tool_replayable?(entry.fetch("tool_name")) ? 1 : 0
34
+ end)
35
+ end
36
+ RecoverySupport.recovery_descriptor(execution.with(execution_revision: execution.execution_revision,
37
+ updated_at: execution.updated_at, metadata: metadata))
38
+ end
39
+
40
+ def prepare_plan(execution)
41
+ root = agent.agent_root
42
+ manifest_ref = execution.metadata["manifest_ref"]
43
+ base_ref = execution.metadata["base_manifest_ref"] ||
44
+ manifest_ref
45
+ manifest = base_manifest = projection = nil
46
+ if manifest_ref
47
+ manifest, projection =
48
+ RecoverySupport.materialize_projection(
49
+ agent,
50
+ manifest_ref
51
+ )
52
+ base_manifest = if base_ref.to_s == manifest_ref.to_s
53
+ manifest
54
+ else
55
+ RecoverySupport.manifest_from_ref(agent, base_ref)
56
+ end
57
+ end
58
+
59
+ classification = classify(execution)
60
+ RecoveryPlan.new(
61
+ execution: execution,
62
+ root: root,
63
+ manifest: manifest,
64
+ base_manifest: base_manifest,
65
+ projection: projection,
66
+ classification: classification,
67
+ material: prepare_recovery_material(execution, projection: projection)
68
+ )
69
+ end
70
+
71
+ def classify(execution)
72
+ if framework_batch?(execution)
73
+ return Phronomy::Recovery::Classification.new(disposition: Phronomy::Recovery::RESUMABLE, reason: :framework_tools)
74
+ end
75
+ if execution.status == :preparing &&
76
+ execution.phase.to_sym == :preparing
77
+ unless execution.metadata["preparation_replayable"] == true
78
+ raise Phronomy::ExecutionRehydrationRequiredError,
79
+ "execution #{execution.execution_id} is :preparing but replay safety " \
80
+ "was not durably established"
81
+ end
82
+ return Phronomy::Recovery::Classification.new(
83
+ disposition: Phronomy::Recovery::RESUMABLE,
84
+ reason: :initial_preparation
85
+ )
86
+ end
87
+
88
+ if execution.status == :suspended &&
89
+ execution.phase.to_sym == :approval
90
+ return Phronomy::Recovery::Classification.new(
91
+ disposition: Phronomy::Recovery::RESUMABLE,
92
+ reason: :approval_wait,
93
+ facts: {
94
+ approval_request_id:
95
+ execution.approval_request &&
96
+ (
97
+ execution.approval_request["id"] ||
98
+ execution.approval_request[:id]
99
+ )
100
+ }.compact
101
+ )
102
+ end
103
+
104
+ case execution.phase.to_sym
105
+ when :recovery_provider_completed,
106
+ :recovery_tools_completed,
107
+ :recovery_resolved_failed
108
+ return Phronomy::Recovery::Classification.new(
109
+ disposition: Phronomy::Recovery::RESUMABLE,
110
+ reason: execution.phase
111
+ )
112
+ when :resuming
113
+ approved = execution.approval_request &&
114
+ (
115
+ execution.approval_request["approved"] ||
116
+ execution.approval_request[:approved]
117
+ )
118
+ unless approved
119
+ return Phronomy::Recovery::Classification.new(
120
+ disposition: Phronomy::Recovery::RESUMABLE,
121
+ reason: :approval_rejection_committed
122
+ )
123
+ end
124
+ end
125
+
126
+ descriptor = coordination_recovery_descriptor(
127
+ execution
128
+ )
129
+ if descriptor
130
+ return Phronomy::Recovery::Classification.new(
131
+ disposition: Phronomy::Recovery::RESOLUTION_REQUIRED,
132
+ reason: descriptor.fetch(:reason),
133
+ subject: descriptor.fetch(:subject),
134
+ allowed_outcomes:
135
+ descriptor.fetch(:allowed_outcomes),
136
+ facts: descriptor.fetch(:facts)
137
+ )
138
+ end
139
+
140
+ raise Phronomy::ExecutionRehydrationRequiredError,
141
+ "execution #{execution.execution_id} is not at an restart-safe durable continuation point " \
142
+ "(status=#{execution.status.inspect}, phase=#{execution.phase.inspect})"
143
+ end
144
+
145
+ def install_on_event_loop(command)
146
+ event_loop = @runtime.event_loop
147
+ plan = command.plan
148
+ execution = plan.execution
149
+ main = agent.send(:execution_coordinator_for, agent.__coordination_config)
150
+ admitted = false
151
+ bound = false
152
+ installed = false
153
+
154
+ event_loop.admit_agent_execution(
155
+ agent.agent_id,
156
+ owner_token: command.owner_token
157
+ )
158
+ admitted = true
159
+ event_loop.bind_agent_execution_admission(
160
+ agent.agent_id,
161
+ owner_token: command.owner_token,
162
+ execution_id: execution.execution_id
163
+ )
164
+ bound = true
165
+
166
+ invocation = nil
167
+ if execution.status == :suspended || execution.phase.to_sym == :resuming
168
+ invocation = RecoverySupport.build_invocation_for_suspended(
169
+ agent, execution, plan.projection, main, agent.send(:_phronomy_event_listener),
170
+ assistant_message: plan.material.assistant_message
171
+ )
172
+ end
173
+
174
+ event_loop.install_agent_execution(
175
+ execution_id: execution.execution_id,
176
+ agent: agent,
177
+ coordinator: main,
178
+ execution: execution,
179
+ runtime_projection: plan.projection,
180
+ base_manifest: plan.base_manifest,
181
+ invocation: invocation,
182
+ fsm_session_id: nil
183
+ )
184
+ installed = true
185
+
186
+ if execution.status == :suspended
187
+ event_loop.mark_agent_execution_admission(
188
+ agent.agent_id,
189
+ execution_id: execution.execution_id,
190
+ state: :suspended
191
+ )
192
+ request = invocation.approval_request
193
+ callback_error = agent.send(
194
+ :_deliver_stream_event,
195
+ agent.send(:_phronomy_event_listener),
196
+ StreamEvent.new(
197
+ type: :approval_required,
198
+ payload: {request: request}.freeze
199
+ )
200
+ )
201
+ if callback_error
202
+ raise agent.send(
203
+ :_build_stream_callback_error,
204
+ event_type: :approval_required,
205
+ callback_error: callback_error,
206
+ result: {
207
+ execution_id: execution.execution_id,
208
+ suspended: true
209
+ }
210
+ )
211
+ end
212
+ command.completion.complete(agent)
213
+ return
214
+ end
215
+
216
+ case plan.classification.disposition
217
+ when Phronomy::Recovery::RESOLUTION_REQUIRED
218
+ event_loop.mark_agent_execution_admission(
219
+ agent.agent_id,
220
+ execution_id: execution.execution_id,
221
+ state: :recovery_required
222
+ )
223
+ payload = RecoverySupport.event_payload(
224
+ execution,
225
+ {
226
+ reason: plan.classification.reason,
227
+ subject: plan.classification.subject,
228
+ allowed_outcomes:
229
+ plan.classification.allowed_outcomes,
230
+ facts: plan.classification.facts
231
+ }
232
+ )
233
+ callback_error = agent.send(
234
+ :_deliver_stream_event,
235
+ agent.send(:_phronomy_event_listener),
236
+ StreamEvent.new(
237
+ type: :recovery_resolution_required,
238
+ payload: payload
239
+ )
240
+ )
241
+ if callback_error
242
+ raise agent.send(
243
+ :_build_stream_callback_error,
244
+ event_type: :recovery_resolution_required,
245
+ callback_error: callback_error,
246
+ result: payload
247
+ )
248
+ end
249
+ command.completion.complete(agent)
250
+ when Phronomy::Recovery::RESUMABLE
251
+ continue_resumable_on_event_loop(
252
+ execution,
253
+ completion: command.completion, material: plan.material
254
+ )
255
+ else
256
+ raise Phronomy::Error,
257
+ "unsupported Recovery installation disposition: #{plan.classification.disposition.inspect}"
258
+ end
259
+ rescue => caught
260
+ # simplecov:disable
261
+ if installed
262
+ begin
263
+ event_loop.release_agent_execution(
264
+ execution.execution_id
265
+ )
266
+ rescue
267
+ nil
268
+ end
269
+ end
270
+ if admitted
271
+ if bound
272
+ begin
273
+ event_loop.release_agent_execution_admission(
274
+ agent.agent_id,
275
+ execution_id: execution&.execution_id
276
+ )
277
+ rescue
278
+ nil
279
+ end
280
+ else
281
+ begin
282
+ event_loop.release_agent_execution_admission(
283
+ agent.agent_id,
284
+ owner_token: command.owner_token
285
+ )
286
+ rescue
287
+ nil
288
+ end
289
+ end
290
+ end
291
+ command.completion.fail(caught)
292
+ # simplecov:enable
293
+ end
294
+
295
+ def observe_recovery_execution(task, execution)
296
+ Phronomy::Tracing::Automatic.observe_task(
297
+ task,
298
+ "agent.execution",
299
+ agent_id: agent.agent_id,
300
+ execution_id: execution.execution_id,
301
+ mode: (
302
+ execution.metadata[
303
+ RecoverySupport::INVOCATION_MODE_KEY
304
+ ] || "invoke"
305
+ ).to_sym
306
+ )
307
+ end
308
+
309
+ def continue_resumable_on_event_loop(
310
+ execution,
311
+ completion:, material:
312
+ )
313
+ # simplecov:disable
314
+ event_loop = @runtime.event_loop
315
+ main = agent.send(:execution_coordinator_for, agent.__coordination_config)
316
+
317
+ if framework_batch?(execution)
318
+ internal = Phronomy::Task.deferred(name: "framework-tool-recovery:#{execution.execution_id}")
319
+ continue_recovery_on_event_loop(execution, internal, material: material)
320
+ completion.complete(agent)
321
+ return
322
+ end
323
+
324
+ case execution.phase.to_sym
325
+ when :preparing
326
+ internal_task = Phronomy::Task.deferred(
327
+ name: "agent-recovery-auto:#{execution.execution_id}"
328
+ )
329
+ observe_recovery_execution(internal_task, execution)
330
+ event_loop.mark_agent_execution_admission(
331
+ agent.agent_id,
332
+ execution_id: execution.execution_id,
333
+ state: :executing
334
+ )
335
+ main.send(
336
+ :start_initial_preparation_recovery_on_event_loop,
337
+ execution,
338
+ internal_task,
339
+ load_completion: completion
340
+ )
341
+ when :resuming
342
+ approved = execution.approval_request &&
343
+ (
344
+ execution.approval_request["approved"] ||
345
+ execution.approval_request[:approved]
346
+ )
347
+ if approved
348
+ raise Phronomy::ExecutionRehydrationRequiredError,
349
+ "approved resuming execution requires Tool outcome resolution"
350
+ end
351
+ internal_task = Phronomy::Task.deferred(
352
+ name: "agent-recovery-auto:#{execution.execution_id}"
353
+ )
354
+ observe_recovery_execution(internal_task, execution)
355
+ event_loop.mark_agent_execution_admission(
356
+ agent.agent_id,
357
+ execution_id: execution.execution_id,
358
+ state: :executing
359
+ )
360
+ main.send(
361
+ :start_resume_on_event_loop,
362
+ execution.execution_id,
363
+ internal_task,
364
+ approved: false,
365
+ config: {}
366
+ )
367
+ completion.complete(agent)
368
+ when :recovery_tools_completed, :recovery_provider_completed, :recovery_resolved_failed
369
+ internal_task = Phronomy::Task.deferred(name: "agent-recovery-auto:#{execution.execution_id}")
370
+ continue_recovery_on_event_loop(execution, internal_task, material: material)
371
+ completion.complete(agent)
372
+ else
373
+ raise Phronomy::ExecutionRehydrationRequiredError,
374
+ "no automatic continuation for #{execution.phase.inspect}"
375
+ end
376
+ # simplecov:enable
377
+ end
378
+ end
379
+ end
380
+ end
381
+ end