phronomy 0.18.0 → 0.19.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +2 -0
- data/docs/decisions/009-state-store-abstraction.md +1 -1
- data/docs/decisions/014-unified-persistence-durable-state.md +268 -0
- data/docs/features.md +7 -1
- data/docs/getting-started.md +37 -1
- data/docs/migrations/0.19.md +148 -0
- data/docs/runtime-and-concurrency.md +93 -2
- data/lib/phronomy/agent/base.rb +81 -36
- data/lib/phronomy/agent/context_assembler.rb +13 -3
- data/lib/phronomy/agent/execution_coordinator.rb +420 -249
- data/lib/phronomy/agent/journal_projection.rb +5 -1
- data/lib/phronomy/configuration.rb +2 -1
- data/lib/phronomy/engine/event_loop.rb +86 -8
- data/lib/phronomy/engine/fsm_session.rb +6 -4
- data/lib/phronomy/engine/runtime.rb +7 -0
- data/lib/phronomy/persistence/in_memory.rb +113 -8
- data/lib/phronomy/persistence.rb +12 -3
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow.rb +10 -9
- data/lib/phronomy/workflow_runner.rb +361 -95
- data/lib/phronomy.rb +3 -0
- metadata +4 -4
- data/lib/phronomy/state_store/base.rb +0 -48
- data/lib/phronomy/state_store/in_memory.rb +0 -62
|
@@ -26,10 +26,14 @@ module Phronomy
|
|
|
26
26
|
if error
|
|
27
27
|
post_prep_failure(runtime, on_event, result_task, translated(error))
|
|
28
28
|
else
|
|
29
|
-
register(
|
|
29
|
+
register(
|
|
30
|
+
prepared,
|
|
31
|
+
result_task,
|
|
32
|
+
mode: mode,
|
|
30
33
|
approval_policy: approval_policy,
|
|
31
34
|
approval_listener: approval_listener,
|
|
32
|
-
on_event: on_event
|
|
35
|
+
on_event: on_event
|
|
36
|
+
)
|
|
33
37
|
end
|
|
34
38
|
end
|
|
35
39
|
result_task
|
|
@@ -52,27 +56,37 @@ module Phronomy
|
|
|
52
56
|
name: "agent-approval-resume:#{execution_id}"
|
|
53
57
|
)
|
|
54
58
|
runtime = Phronomy::Runtime.instance
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
activation = @agent.persistence.activations.fetch(execution_id)
|
|
63
|
-
unless activation
|
|
64
|
-
raise Phronomy::ExecutionRehydrationRequiredError,
|
|
59
|
+
activation = runtime.__agent_activations.fetch(execution_id)
|
|
60
|
+
unless activation
|
|
61
|
+
fail_task(
|
|
62
|
+
result_task,
|
|
63
|
+
Phronomy::ExecutionRehydrationRequiredError.new(
|
|
65
64
|
"no live activation for #{execution_id}; durable rehydration is required"
|
|
66
|
-
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
return result_task
|
|
68
|
+
end
|
|
67
69
|
|
|
70
|
+
unless activation.agent.equal?(@agent) &&
|
|
71
|
+
activation.execution.status == :suspended
|
|
72
|
+
fail_task(
|
|
73
|
+
result_task,
|
|
74
|
+
ArgumentError.new(
|
|
75
|
+
"execution is not a suspended execution of this agent: #{execution_id}"
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
return result_task
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
preparation = runtime.offload.submit(on_full: :raise) do
|
|
68
82
|
mark_resuming!(
|
|
69
|
-
|
|
83
|
+
activation,
|
|
70
84
|
approval_request_id: approval_request_id,
|
|
71
85
|
approved: approved
|
|
72
86
|
)
|
|
73
87
|
activation
|
|
74
88
|
end
|
|
75
|
-
preparation.on_complete do |
|
|
89
|
+
preparation.on_complete do |prepared_activation, error|
|
|
76
90
|
if error
|
|
77
91
|
fail_task(result_task, translated(error))
|
|
78
92
|
next
|
|
@@ -80,20 +94,24 @@ module Phronomy
|
|
|
80
94
|
|
|
81
95
|
begin
|
|
82
96
|
start_resume(
|
|
83
|
-
|
|
97
|
+
prepared_activation,
|
|
84
98
|
result_task,
|
|
85
99
|
approved: approved,
|
|
86
100
|
config: config
|
|
87
101
|
)
|
|
88
102
|
rescue => start_error
|
|
89
103
|
commit = runtime.offload.submit(on_full: :raise) do
|
|
90
|
-
commit_failed(
|
|
104
|
+
commit_failed(
|
|
105
|
+
prepared_activation,
|
|
106
|
+
prepared_activation.invocation,
|
|
107
|
+
start_error
|
|
108
|
+
)
|
|
91
109
|
end
|
|
92
110
|
commit.on_complete do |terminal, commit_error|
|
|
93
111
|
if commit_error
|
|
94
112
|
fail_task(result_task, commit_error)
|
|
95
113
|
else
|
|
96
|
-
|
|
114
|
+
runtime.__agent_activations.delete(execution_id)
|
|
97
115
|
fail_task(result_task, terminal.fetch(:error))
|
|
98
116
|
end
|
|
99
117
|
end
|
|
@@ -101,22 +119,27 @@ module Phronomy
|
|
|
101
119
|
end
|
|
102
120
|
result_task
|
|
103
121
|
rescue => error
|
|
104
|
-
fail_task(result_task, translated(error))
|
|
122
|
+
fail_task(result_task, translated(error)) if result_task
|
|
105
123
|
result_task
|
|
106
124
|
end
|
|
107
125
|
|
|
108
|
-
# Persists
|
|
109
|
-
#
|
|
110
|
-
# This method is executed on an OffloadPool worker, never on EventLoop.
|
|
126
|
+
# Persists facts from the previous Provider/Tool cycle and fixes the next
|
|
127
|
+
# Manifest from the current local Agent state. This runs on OffloadPool.
|
|
111
128
|
def prepare_next_llm_call(activation)
|
|
112
129
|
snapshot = activation.runtime_snapshot
|
|
113
|
-
|
|
130
|
+
root = @agent.agent_root
|
|
131
|
+
current = activation.execution
|
|
132
|
+
journal_records = @agent.send(:_journal_records_snapshot)
|
|
133
|
+
manifest = manifest_ref = updated = nil
|
|
114
134
|
|
|
115
135
|
@agent.persistence.transaction do |tx|
|
|
116
|
-
root
|
|
117
|
-
current = tx.executions.load(activation.execution_id)
|
|
136
|
+
assert_local_durable_base!(tx, root)
|
|
118
137
|
encoded_records, call_records = encode_runtime_records(
|
|
119
|
-
activation,
|
|
138
|
+
activation,
|
|
139
|
+
tx: tx,
|
|
140
|
+
snapshot: snapshot,
|
|
141
|
+
context_candidate: true,
|
|
142
|
+
agent_root: root
|
|
120
143
|
)
|
|
121
144
|
staged = current.with(
|
|
122
145
|
execution_revision: current.execution_revision,
|
|
@@ -131,28 +154,38 @@ module Phronomy
|
|
|
131
154
|
config: invocation_config
|
|
132
155
|
)
|
|
133
156
|
manifest, manifest_ref = ContextAssembler.new(
|
|
134
|
-
agent: @agent,
|
|
157
|
+
agent: @agent,
|
|
158
|
+
persistence: tx,
|
|
159
|
+
policy: context_policy_for(staged),
|
|
160
|
+
journal_records: journal_records
|
|
135
161
|
).build_followup(
|
|
136
162
|
base_manifest: activation.base_manifest,
|
|
137
|
-
agent_root: root,
|
|
138
|
-
|
|
163
|
+
agent_root: root,
|
|
164
|
+
execution: staged,
|
|
165
|
+
config: invocation_config,
|
|
166
|
+
patch: patch
|
|
139
167
|
)
|
|
140
168
|
refs = Array(staged.metadata["manifest_refs"]) + [manifest_ref]
|
|
141
169
|
updated = staged.with(
|
|
142
170
|
phase: :calling_llm,
|
|
143
171
|
metadata: staged.metadata.merge(
|
|
144
|
-
"manifest_ref" => manifest_ref,
|
|
172
|
+
"manifest_ref" => manifest_ref,
|
|
173
|
+
"manifest_refs" => refs
|
|
145
174
|
)
|
|
146
175
|
)
|
|
147
|
-
tx.executions.save(
|
|
148
|
-
|
|
176
|
+
tx.executions.save(
|
|
177
|
+
current.execution_id,
|
|
178
|
+
expected_revision: current.execution_revision,
|
|
179
|
+
execution: updated
|
|
180
|
+
)
|
|
149
181
|
end
|
|
150
182
|
|
|
151
183
|
activation.replace_execution(updated)
|
|
152
184
|
activation.acknowledge_runtime_snapshot(snapshot)
|
|
153
185
|
|
|
154
186
|
projection = RubyLLMMaterializer.new(
|
|
155
|
-
agent: @agent,
|
|
187
|
+
agent: @agent,
|
|
188
|
+
persistence: @agent.persistence
|
|
156
189
|
).materialize(manifest: manifest, manifest_ref: manifest_ref)
|
|
157
190
|
activation.replace_runtime_projection(projection)
|
|
158
191
|
projection
|
|
@@ -160,8 +193,12 @@ module Phronomy
|
|
|
160
193
|
|
|
161
194
|
def prepare(input, thread_id:, config:)
|
|
162
195
|
raw_message = @agent.send(:extract_message, input)
|
|
163
|
-
execution, active_root = admit_execution(
|
|
196
|
+
execution, active_root = admit_execution(
|
|
197
|
+
raw_message,
|
|
198
|
+
thread_id: thread_id
|
|
199
|
+
)
|
|
164
200
|
@agent.__replace_root(active_root)
|
|
201
|
+
current_execution = execution
|
|
165
202
|
|
|
166
203
|
begin
|
|
167
204
|
effective_config = thread_id ? config.merge(thread_id: thread_id) : config
|
|
@@ -177,29 +214,27 @@ module Phronomy
|
|
|
177
214
|
"invocation cancelled before context assembly"
|
|
178
215
|
)
|
|
179
216
|
filtered_message = @agent.send(:extract_message, filtered_input)
|
|
180
|
-
manifest = nil
|
|
181
|
-
|
|
182
|
-
active_execution = nil
|
|
217
|
+
manifest = manifest_ref = active_execution = nil
|
|
218
|
+
local_journal = @agent.send(:_journal_records_snapshot)
|
|
183
219
|
|
|
184
220
|
@agent.persistence.transaction do |tx|
|
|
185
|
-
|
|
186
|
-
current = tx.executions.load(execution.execution_id)
|
|
221
|
+
assert_local_durable_base!(tx, active_root)
|
|
187
222
|
filtered_ref = tx.contents.put_text(filtered_message)
|
|
188
223
|
input_record = JournalRecord.new(
|
|
189
224
|
agent_id: @agent.agent_id,
|
|
190
|
-
execution_id:
|
|
225
|
+
execution_id: current_execution.execution_id,
|
|
191
226
|
kind: :external_message,
|
|
192
227
|
channel: :external,
|
|
193
228
|
role: :user,
|
|
194
229
|
content_ref: filtered_ref,
|
|
195
230
|
correlation_id: thread_id,
|
|
196
|
-
context_generation:
|
|
231
|
+
context_generation: active_root.transcript_generation,
|
|
197
232
|
context_candidate: true
|
|
198
233
|
)
|
|
199
|
-
staged =
|
|
200
|
-
execution_revision:
|
|
201
|
-
working_records:
|
|
202
|
-
metadata:
|
|
234
|
+
staged = current_execution.with(
|
|
235
|
+
execution_revision: current_execution.execution_revision,
|
|
236
|
+
working_records: current_execution.working_records + [input_record],
|
|
237
|
+
metadata: current_execution.metadata.merge(
|
|
203
238
|
"current_input_ref" => filtered_ref,
|
|
204
239
|
"current_input_record_id" => input_record.record_id
|
|
205
240
|
)
|
|
@@ -207,10 +242,11 @@ module Phronomy
|
|
|
207
242
|
manifest, manifest_ref = ContextAssembler.new(
|
|
208
243
|
agent: @agent,
|
|
209
244
|
persistence: tx,
|
|
210
|
-
policy: context_policy_for(staged)
|
|
245
|
+
policy: context_policy_for(staged),
|
|
246
|
+
journal_records: local_journal
|
|
211
247
|
).build_initial(
|
|
212
248
|
input: filtered_input,
|
|
213
|
-
agent_root:
|
|
249
|
+
agent_root: active_root,
|
|
214
250
|
execution: staged,
|
|
215
251
|
config: effective_config,
|
|
216
252
|
patch: @agent.send(
|
|
@@ -229,34 +265,35 @@ module Phronomy
|
|
|
229
265
|
)
|
|
230
266
|
)
|
|
231
267
|
tx.executions.save(
|
|
232
|
-
|
|
233
|
-
expected_revision:
|
|
268
|
+
current_execution.execution_id,
|
|
269
|
+
expected_revision: current_execution.execution_revision,
|
|
234
270
|
execution: active_execution
|
|
235
271
|
)
|
|
236
272
|
end
|
|
273
|
+
current_execution = active_execution
|
|
237
274
|
|
|
238
275
|
projection = RubyLLMMaterializer.new(
|
|
239
276
|
agent: @agent,
|
|
240
277
|
persistence: @agent.persistence
|
|
241
278
|
).materialize(manifest: manifest, manifest_ref: manifest_ref)
|
|
242
|
-
Prepared.new(
|
|
279
|
+
Phronomy::Agent::ExecutionCoordinator::Prepared.new(
|
|
243
280
|
execution: active_execution,
|
|
244
281
|
runtime_projection: projection,
|
|
245
282
|
filtered_input: filtered_input,
|
|
246
283
|
config: effective_config
|
|
247
284
|
)
|
|
248
285
|
rescue => error
|
|
249
|
-
commit_preparation_failure(
|
|
286
|
+
commit_preparation_failure(current_execution, error)
|
|
250
287
|
raise
|
|
251
288
|
end
|
|
252
289
|
end
|
|
253
290
|
|
|
254
291
|
def admit_execution(raw_message, thread_id:)
|
|
255
|
-
|
|
256
|
-
root
|
|
292
|
+
root = @agent.agent_root
|
|
293
|
+
raise Phronomy::Error, "agent is closed: #{@agent.agent_id}" if root.lifecycle_status == :closed
|
|
294
|
+
|
|
295
|
+
execution = next_root = nil
|
|
257
296
|
@agent.persistence.transaction do |tx|
|
|
258
|
-
root = tx.agents.load(@agent.agent_id)
|
|
259
|
-
raise Phronomy::Error, "agent is closed: #{@agent.agent_id}" if root.lifecycle_status == :closed
|
|
260
297
|
input_ref = tx.contents.put_text(raw_message)
|
|
261
298
|
input_record = JournalRecord.new(
|
|
262
299
|
agent_id: @agent.agent_id,
|
|
@@ -290,29 +327,34 @@ module Phronomy
|
|
|
290
327
|
agent_revision: root.agent_revision + 1,
|
|
291
328
|
lifecycle_status: :active
|
|
292
329
|
)
|
|
293
|
-
tx.agents.save(
|
|
294
|
-
|
|
330
|
+
tx.agents.save(
|
|
331
|
+
root.agent_id,
|
|
332
|
+
expected_revision: root.agent_revision,
|
|
333
|
+
root: next_root
|
|
334
|
+
)
|
|
295
335
|
end
|
|
296
|
-
[execution,
|
|
336
|
+
[execution, next_root]
|
|
297
337
|
end
|
|
298
338
|
|
|
299
339
|
def commit_preparation_failure(execution, error)
|
|
300
340
|
translated_error = translated(error)
|
|
301
|
-
root =
|
|
341
|
+
root = @agent.agent_root
|
|
342
|
+
failed = next_root = appended = nil
|
|
343
|
+
|
|
302
344
|
@agent.persistence.transaction do |tx|
|
|
303
|
-
current = tx.executions.load(execution.execution_id)
|
|
304
|
-
root = tx.agents.load(@agent.agent_id)
|
|
305
345
|
error_ref = tx.contents.put_json(
|
|
306
346
|
"class" => translated_error.class.name,
|
|
307
347
|
"message" => translated_error.message
|
|
308
348
|
)
|
|
309
|
-
audit_records =
|
|
310
|
-
JournalRecord.from_h(
|
|
349
|
+
audit_records = execution.working_records.map do |record|
|
|
350
|
+
JournalRecord.from_h(
|
|
351
|
+
record.to_h.merge("context_candidate" => false)
|
|
352
|
+
)
|
|
311
353
|
end
|
|
312
354
|
terminal_status = terminal_status_for(translated_error)
|
|
313
355
|
audit_records << JournalRecord.new(
|
|
314
356
|
agent_id: @agent.agent_id,
|
|
315
|
-
execution_id:
|
|
357
|
+
execution_id: execution.execution_id,
|
|
316
358
|
kind: execution_terminal_kind(terminal_status),
|
|
317
359
|
channel: :audit,
|
|
318
360
|
content_ref: error_ref,
|
|
@@ -324,7 +366,7 @@ module Phronomy
|
|
|
324
366
|
expected_position: root.journal_position,
|
|
325
367
|
records: audit_records
|
|
326
368
|
)
|
|
327
|
-
failed =
|
|
369
|
+
failed = execution.with(
|
|
328
370
|
status: terminal_status,
|
|
329
371
|
phase: terminal_status,
|
|
330
372
|
working_records: [],
|
|
@@ -332,8 +374,8 @@ module Phronomy
|
|
|
332
374
|
terminal_reason: translated_error.class.name
|
|
333
375
|
)
|
|
334
376
|
tx.executions.save(
|
|
335
|
-
|
|
336
|
-
expected_revision:
|
|
377
|
+
execution.execution_id,
|
|
378
|
+
expected_revision: execution.execution_revision,
|
|
337
379
|
execution: failed
|
|
338
380
|
)
|
|
339
381
|
next_root = root.with(
|
|
@@ -341,10 +383,15 @@ module Phronomy
|
|
|
341
383
|
journal_position: root.journal_position + appended.length,
|
|
342
384
|
lifecycle_status: :idle
|
|
343
385
|
)
|
|
344
|
-
tx.agents.save(
|
|
345
|
-
|
|
386
|
+
tx.agents.save(
|
|
387
|
+
root.agent_id,
|
|
388
|
+
expected_revision: root.agent_revision,
|
|
389
|
+
root: next_root
|
|
390
|
+
)
|
|
346
391
|
end
|
|
347
|
-
@agent.
|
|
392
|
+
@agent.send(:_append_journal_records, appended)
|
|
393
|
+
@agent.__replace_root(next_root)
|
|
394
|
+
failed
|
|
348
395
|
end
|
|
349
396
|
|
|
350
397
|
def register(prepared, result_task, mode:, approval_policy:, approval_listener:, on_event:)
|
|
@@ -355,8 +402,8 @@ module Phronomy
|
|
|
355
402
|
coordinator: self,
|
|
356
403
|
application_listener: on_event
|
|
357
404
|
)
|
|
358
|
-
@agent.persistence.activations.register(activation)
|
|
359
405
|
runtime = Phronomy::Runtime.instance
|
|
406
|
+
runtime.__agent_activations.register(activation)
|
|
360
407
|
event_loop = runtime.event_loop
|
|
361
408
|
effective_config = prepared.config.merge(
|
|
362
409
|
phronomy_activation: activation,
|
|
@@ -388,8 +435,10 @@ module Phronomy
|
|
|
388
435
|
event_loop.register(session, completion: source_task)
|
|
389
436
|
rescue => error
|
|
390
437
|
activation ||= AgentExecutionActivation.new(
|
|
391
|
-
execution: prepared.execution,
|
|
392
|
-
|
|
438
|
+
execution: prepared.execution,
|
|
439
|
+
agent: @agent,
|
|
440
|
+
runtime_projection: prepared.runtime_projection,
|
|
441
|
+
coordinator: self,
|
|
393
442
|
application_listener: on_event
|
|
394
443
|
)
|
|
395
444
|
finish(activation, result_task, activation.invocation, error)
|
|
@@ -411,7 +460,11 @@ module Phronomy
|
|
|
411
460
|
payload: {command: command}
|
|
412
461
|
)
|
|
413
462
|
)
|
|
414
|
-
|
|
463
|
+
unless posted
|
|
464
|
+
settle_without_listener(
|
|
465
|
+
activation, result_task, outcome, commit_error
|
|
466
|
+
)
|
|
467
|
+
end
|
|
415
468
|
end
|
|
416
469
|
rescue => caught
|
|
417
470
|
fail_task(result_task, translated(caught))
|
|
@@ -431,8 +484,11 @@ module Phronomy
|
|
|
431
484
|
)
|
|
432
485
|
)
|
|
433
486
|
settle_after_terminal(
|
|
434
|
-
command.result_task,
|
|
435
|
-
|
|
487
|
+
command.result_task,
|
|
488
|
+
callback_error,
|
|
489
|
+
terminal_event_type(command.error),
|
|
490
|
+
nil,
|
|
491
|
+
command.error
|
|
436
492
|
)
|
|
437
493
|
when AgentTerminalCommand
|
|
438
494
|
deliver_execution_terminal(command)
|
|
@@ -445,7 +501,11 @@ module Phronomy
|
|
|
445
501
|
|
|
446
502
|
def compute_terminal(activation, invocation, error)
|
|
447
503
|
if (failure = activation.callback_failure)
|
|
448
|
-
terminal = commit_failed(
|
|
504
|
+
terminal = commit_failed(
|
|
505
|
+
activation,
|
|
506
|
+
invocation,
|
|
507
|
+
failure.to_stream_callback_error
|
|
508
|
+
)
|
|
449
509
|
return {type: :failed, error: terminal.fetch(:error)}
|
|
450
510
|
end
|
|
451
511
|
if error
|
|
@@ -453,10 +513,14 @@ module Phronomy
|
|
|
453
513
|
return {type: :failed, error: terminal.fetch(:error)}
|
|
454
514
|
end
|
|
455
515
|
if invocation&.phase == :suspended
|
|
456
|
-
return {
|
|
516
|
+
return {
|
|
517
|
+
type: :suspended,
|
|
518
|
+
result: commit_suspended(activation, invocation)
|
|
519
|
+
}
|
|
457
520
|
end
|
|
458
521
|
raise invocation.block_error if invocation.input_blocked? || invocation.output_blocked?
|
|
459
522
|
raise invocation.error if invocation.error
|
|
523
|
+
|
|
460
524
|
{type: :completed, result: commit_completed(activation, invocation)}
|
|
461
525
|
rescue => caught
|
|
462
526
|
terminal = commit_failed(activation, invocation, caught)
|
|
@@ -464,17 +528,20 @@ module Phronomy
|
|
|
464
528
|
end
|
|
465
529
|
|
|
466
530
|
def commit_suspended(activation, invocation)
|
|
467
|
-
|
|
531
|
+
current = activation.execution
|
|
532
|
+
root = @agent.agent_root
|
|
468
533
|
request = invocation.approval_request
|
|
469
534
|
runtime_snapshot = activation.runtime_snapshot
|
|
470
|
-
suspended = nil
|
|
471
|
-
|
|
535
|
+
suspended = next_root = nil
|
|
536
|
+
|
|
472
537
|
@agent.persistence.transaction do |tx|
|
|
473
538
|
encoded_records, call_records = encode_runtime_records(
|
|
474
|
-
activation,
|
|
539
|
+
activation,
|
|
540
|
+
tx: tx,
|
|
541
|
+
snapshot: runtime_snapshot,
|
|
542
|
+
context_candidate: true,
|
|
543
|
+
agent_root: root
|
|
475
544
|
)
|
|
476
|
-
root = tx.agents.load(@agent.agent_id)
|
|
477
|
-
current = tx.executions.load(execution.execution_id)
|
|
478
545
|
request_ref = tx.contents.put_json(json_value(request.to_h))
|
|
479
546
|
approval_record = JournalRecord.new(
|
|
480
547
|
agent_id: @agent.agent_id,
|
|
@@ -502,33 +569,37 @@ module Phronomy
|
|
|
502
569
|
agent_revision: root.agent_revision + 1,
|
|
503
570
|
lifecycle_status: :suspended
|
|
504
571
|
)
|
|
505
|
-
tx.agents.save(
|
|
506
|
-
|
|
572
|
+
tx.agents.save(
|
|
573
|
+
root.agent_id,
|
|
574
|
+
expected_revision: root.agent_revision,
|
|
575
|
+
root: next_root
|
|
576
|
+
)
|
|
507
577
|
end
|
|
508
578
|
activation.acknowledge_runtime_snapshot(runtime_snapshot)
|
|
509
579
|
activation.replace_execution(suspended)
|
|
510
|
-
@agent.__replace_root(
|
|
580
|
+
@agent.__replace_root(next_root)
|
|
511
581
|
dispatch_approval_listener(invocation, request)
|
|
512
|
-
result_base(suspended,
|
|
582
|
+
result_base(suspended, next_root).merge(
|
|
513
583
|
suspended: true,
|
|
514
584
|
approval_request: request
|
|
515
585
|
)
|
|
516
586
|
end
|
|
517
587
|
|
|
518
588
|
def commit_completed(activation, invocation)
|
|
519
|
-
|
|
589
|
+
current = activation.execution
|
|
590
|
+
root = @agent.agent_root
|
|
520
591
|
runtime_snapshot = activation.runtime_snapshot
|
|
521
|
-
completed = nil
|
|
522
|
-
root = nil
|
|
523
|
-
appended = nil
|
|
592
|
+
completed = next_root = appended = nil
|
|
524
593
|
|
|
525
594
|
@agent.persistence.transaction do |tx|
|
|
526
595
|
encoded_records, call_records = encode_runtime_records(
|
|
527
|
-
activation,
|
|
596
|
+
activation,
|
|
597
|
+
tx: tx,
|
|
598
|
+
snapshot: runtime_snapshot,
|
|
599
|
+
context_candidate: true,
|
|
600
|
+
agent_root: root
|
|
528
601
|
)
|
|
529
602
|
output_ref = tx.contents.put_text(invocation.output.to_s)
|
|
530
|
-
root = tx.agents.load(@agent.agent_id)
|
|
531
|
-
current = tx.executions.load(execution.execution_id)
|
|
532
603
|
final_output_record = JournalRecord.new(
|
|
533
604
|
agent_id: @agent.agent_id,
|
|
534
605
|
execution_id: current.execution_id,
|
|
@@ -576,38 +647,47 @@ module Phronomy
|
|
|
576
647
|
journal_position: root.journal_position + appended.length,
|
|
577
648
|
lifecycle_status: :idle
|
|
578
649
|
)
|
|
579
|
-
tx.agents.save(
|
|
580
|
-
|
|
650
|
+
tx.agents.save(
|
|
651
|
+
root.agent_id,
|
|
652
|
+
expected_revision: root.agent_revision,
|
|
653
|
+
root: next_root
|
|
654
|
+
)
|
|
581
655
|
end
|
|
582
656
|
activation.acknowledge_runtime_snapshot(runtime_snapshot)
|
|
583
657
|
activation.replace_execution(completed)
|
|
584
|
-
@agent.
|
|
585
|
-
|
|
658
|
+
@agent.send(:_append_journal_records, appended)
|
|
659
|
+
@agent.__replace_root(next_root)
|
|
660
|
+
result_base(completed, next_root).merge(
|
|
586
661
|
output: invocation.output,
|
|
587
662
|
rejected: invocation.rejected || nil,
|
|
588
663
|
usage: invocation.usage,
|
|
589
|
-
messages: transcript_messages(
|
|
664
|
+
messages: transcript_messages(next_root)
|
|
590
665
|
).compact
|
|
591
666
|
end
|
|
592
667
|
|
|
593
668
|
def commit_failed(activation, _invocation, error)
|
|
594
669
|
translated_error = translated(error)
|
|
595
|
-
|
|
670
|
+
current = activation.execution
|
|
671
|
+
root = @agent.agent_root
|
|
596
672
|
runtime_snapshot = activation.runtime_snapshot
|
|
597
|
-
failed = nil
|
|
598
|
-
|
|
673
|
+
failed = next_root = appended = nil
|
|
674
|
+
|
|
599
675
|
@agent.persistence.transaction do |tx|
|
|
600
676
|
encoded_records, call_records = encode_runtime_records(
|
|
601
|
-
activation,
|
|
677
|
+
activation,
|
|
678
|
+
tx: tx,
|
|
679
|
+
snapshot: runtime_snapshot,
|
|
680
|
+
context_candidate: false,
|
|
681
|
+
agent_root: root
|
|
602
682
|
)
|
|
603
683
|
error_ref = tx.contents.put_json(
|
|
604
684
|
"class" => translated_error.class.name,
|
|
605
685
|
"message" => translated_error.message
|
|
606
686
|
)
|
|
607
|
-
root = tx.agents.load(@agent.agent_id)
|
|
608
|
-
current = tx.executions.load(execution.execution_id)
|
|
609
687
|
audit_records = (current.working_records + encoded_records).map do |record|
|
|
610
|
-
JournalRecord.from_h(
|
|
688
|
+
JournalRecord.from_h(
|
|
689
|
+
record.to_h.merge("context_candidate" => false)
|
|
690
|
+
)
|
|
611
691
|
end
|
|
612
692
|
terminal_status = terminal_status_for(translated_error)
|
|
613
693
|
audit_records << JournalRecord.new(
|
|
@@ -642,18 +722,28 @@ module Phronomy
|
|
|
642
722
|
journal_position: root.journal_position + appended.length,
|
|
643
723
|
lifecycle_status: :idle
|
|
644
724
|
)
|
|
645
|
-
tx.agents.save(
|
|
646
|
-
|
|
725
|
+
tx.agents.save(
|
|
726
|
+
root.agent_id,
|
|
727
|
+
expected_revision: root.agent_revision,
|
|
728
|
+
root: next_root
|
|
729
|
+
)
|
|
647
730
|
end
|
|
648
731
|
activation.acknowledge_runtime_snapshot(runtime_snapshot)
|
|
649
732
|
activation.replace_execution(failed)
|
|
650
|
-
@agent.
|
|
651
|
-
|
|
733
|
+
@agent.send(:_append_journal_records, appended)
|
|
734
|
+
@agent.__replace_root(next_root)
|
|
735
|
+
{error: translated_error, execution: failed, root: next_root}
|
|
652
736
|
end
|
|
653
737
|
|
|
654
|
-
def encode_runtime_records(
|
|
738
|
+
def encode_runtime_records(
|
|
739
|
+
activation,
|
|
740
|
+
tx:,
|
|
741
|
+
snapshot:,
|
|
742
|
+
context_candidate:,
|
|
743
|
+
agent_root:
|
|
744
|
+
)
|
|
655
745
|
execution = activation.execution
|
|
656
|
-
root =
|
|
746
|
+
root = agent_root
|
|
657
747
|
records = []
|
|
658
748
|
calls = []
|
|
659
749
|
|
|
@@ -665,10 +755,12 @@ module Phronomy
|
|
|
665
755
|
call_error = intercepted ? nil : error
|
|
666
756
|
output_ref = assistant_output_ref(tx, outcome)
|
|
667
757
|
assistant_ref = assistant_message_ref(tx, outcome)
|
|
668
|
-
error_ref = call_error
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
758
|
+
error_ref = if call_error
|
|
759
|
+
tx.contents.put_json(
|
|
760
|
+
"class" => call_error.class.name,
|
|
761
|
+
"message" => call_error.message
|
|
762
|
+
)
|
|
763
|
+
end
|
|
672
764
|
usage_ref = if outcome && !outcome.usage.empty?
|
|
673
765
|
tx.contents.put_json(json_value(outcome.usage))
|
|
674
766
|
end
|
|
@@ -728,7 +820,9 @@ module Phronomy
|
|
|
728
820
|
manifest_ref: active.fetch(:manifest_ref),
|
|
729
821
|
started_at: active.fetch(:started_at),
|
|
730
822
|
completed_at: Time.now.utc.iso8601(6),
|
|
731
|
-
metadata: {
|
|
823
|
+
metadata: {
|
|
824
|
+
"reason" => "execution_terminalized_before_provider_settlement"
|
|
825
|
+
}
|
|
732
826
|
)
|
|
733
827
|
calls << abandoned
|
|
734
828
|
records << JournalRecord.new(
|
|
@@ -746,18 +840,12 @@ module Phronomy
|
|
|
746
840
|
snapshot.fetch(:runtime_events).each do |event|
|
|
747
841
|
case event.type
|
|
748
842
|
when :tool_call
|
|
749
|
-
# Tool Calls remain part of the captured assistant message. The
|
|
750
|
-
# StreamEvent is an application/runtime notification only.
|
|
751
843
|
next
|
|
752
844
|
when :tool_result
|
|
753
845
|
payload = event.payload
|
|
754
846
|
llm_call_id = payload[:llm_call_id] || payload["llm_call_id"]
|
|
755
847
|
tool_call_id = payload.fetch(:tool_call_id).to_s
|
|
756
848
|
tool_name = payload.fetch(:tool_name).to_s
|
|
757
|
-
|
|
758
|
-
# The raw Tool return value is an execution fact, not an LLM message.
|
|
759
|
-
# Keep it in the complete Journal, but never expose it directly as a
|
|
760
|
-
# Context candidate.
|
|
761
849
|
result_ref = put_runtime_content(tx, payload.fetch(:tool_result))
|
|
762
850
|
records << JournalRecord.new(
|
|
763
851
|
agent_id: @agent.agent_id,
|
|
@@ -775,8 +863,6 @@ module Phronomy
|
|
|
775
863
|
}
|
|
776
864
|
)
|
|
777
865
|
|
|
778
|
-
# Separately preserve the exact Tool message that was appended to
|
|
779
|
-
# RubyLLM Chat and therefore became eligible for a later Manifest.
|
|
780
866
|
message_payload = if payload.key?(:tool_message)
|
|
781
867
|
payload.fetch(:tool_message)
|
|
782
868
|
else
|
|
@@ -803,56 +889,22 @@ module Phronomy
|
|
|
803
889
|
[records, calls]
|
|
804
890
|
end
|
|
805
891
|
|
|
806
|
-
def
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
"
|
|
811
|
-
|
|
812
|
-
"tool_calls" => json_value(Array(outcome.tool_calls)),
|
|
813
|
-
"model_id" => outcome.metadata["model_id"]
|
|
814
|
-
}.compact
|
|
815
|
-
tx.contents.put_json(payload)
|
|
816
|
-
end
|
|
817
|
-
|
|
818
|
-
def assistant_message_metadata(outcome)
|
|
819
|
-
calls = Array(outcome&.tool_calls)
|
|
820
|
-
{
|
|
821
|
-
"tool_call_ids" => calls.map { |call| call.fetch("id").to_s },
|
|
822
|
-
"tool_names" => calls.map { |call| call.fetch("name").to_s }
|
|
823
|
-
}
|
|
824
|
-
end
|
|
825
|
-
|
|
826
|
-
def assistant_output_ref(tx, outcome)
|
|
827
|
-
return unless outcome&.content_present?
|
|
828
|
-
|
|
829
|
-
content = outcome.content
|
|
830
|
-
text = content.is_a?(String) ? content : Phronomy::CanonicalJSON.dump(content)
|
|
831
|
-
tx.contents.put_text(text)
|
|
832
|
-
end
|
|
892
|
+
def mark_resuming!(activation, approval_request_id:, approved:)
|
|
893
|
+
current = activation.execution
|
|
894
|
+
current_root = @agent.agent_root
|
|
895
|
+
unless current.agent_id == @agent.agent_id && current.status == :suspended
|
|
896
|
+
raise ArgumentError, "execution is not suspended: #{current.execution_id}"
|
|
897
|
+
end
|
|
833
898
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
899
|
+
request = current.approval_request || {}
|
|
900
|
+
request_id = request["id"] || request[:id]
|
|
901
|
+
unless request_id.to_s == approval_request_id.to_s
|
|
902
|
+
raise ArgumentError,
|
|
903
|
+
"approval request does not match execution #{current.execution_id}"
|
|
904
|
+
end
|
|
837
905
|
|
|
838
|
-
|
|
839
|
-
root = nil
|
|
840
|
-
updated = nil
|
|
906
|
+
updated = next_root = nil
|
|
841
907
|
@agent.persistence.transaction do |tx|
|
|
842
|
-
current = tx.executions.load(execution_id)
|
|
843
|
-
unless current.agent_id == @agent.agent_id && current.status == :suspended
|
|
844
|
-
raise ArgumentError,
|
|
845
|
-
"execution is not suspended: #{execution_id}"
|
|
846
|
-
end
|
|
847
|
-
|
|
848
|
-
request = current.approval_request || {}
|
|
849
|
-
request_id = request["id"] || request[:id]
|
|
850
|
-
unless request_id.to_s == approval_request_id.to_s
|
|
851
|
-
raise ArgumentError,
|
|
852
|
-
"approval request does not match execution #{execution_id}"
|
|
853
|
-
end
|
|
854
|
-
|
|
855
|
-
current_root = tx.agents.load(@agent.agent_id)
|
|
856
908
|
decision_ref = tx.contents.put_json(
|
|
857
909
|
"approval_request_id" => request_id.to_s,
|
|
858
910
|
"approved" => !!approved
|
|
@@ -887,13 +939,151 @@ module Phronomy
|
|
|
887
939
|
expected_revision: current_root.agent_revision,
|
|
888
940
|
root: next_root
|
|
889
941
|
)
|
|
890
|
-
root = next_root
|
|
891
942
|
end
|
|
892
|
-
|
|
893
|
-
@agent.__replace_root(
|
|
943
|
+
activation.replace_execution(updated)
|
|
944
|
+
@agent.__replace_root(next_root)
|
|
894
945
|
updated
|
|
895
946
|
end
|
|
896
947
|
|
|
948
|
+
def assert_local_durable_base!(tx, root)
|
|
949
|
+
tx.assert_agent_watermark!(
|
|
950
|
+
agent_id: root.agent_id,
|
|
951
|
+
agent_revision: root.agent_revision,
|
|
952
|
+
journal_position: root.journal_position
|
|
953
|
+
)
|
|
954
|
+
end
|
|
955
|
+
|
|
956
|
+
def transcript_messages(root)
|
|
957
|
+
materializer = RubyLLMMaterializer.new(
|
|
958
|
+
agent: @agent,
|
|
959
|
+
persistence: @agent.persistence
|
|
960
|
+
)
|
|
961
|
+
materializer.materialize_journal_records(
|
|
962
|
+
JournalProjection.new(
|
|
963
|
+
agent_root: root,
|
|
964
|
+
records: @agent.send(:_journal_records_snapshot)
|
|
965
|
+
).transcript_records
|
|
966
|
+
)
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
def deliver_execution_terminal(command)
|
|
970
|
+
activation = command.activation
|
|
971
|
+
registry = Phronomy::Runtime.instance.__agent_activations
|
|
972
|
+
if command.commit_error
|
|
973
|
+
callback_error = deliver_terminal(
|
|
974
|
+
activation,
|
|
975
|
+
:error,
|
|
976
|
+
error: command.commit_error
|
|
977
|
+
)
|
|
978
|
+
settle_after_terminal(
|
|
979
|
+
command.result_task,
|
|
980
|
+
callback_error,
|
|
981
|
+
:error,
|
|
982
|
+
nil,
|
|
983
|
+
command.commit_error
|
|
984
|
+
)
|
|
985
|
+
return
|
|
986
|
+
end
|
|
987
|
+
|
|
988
|
+
outcome = command.outcome
|
|
989
|
+
case outcome.fetch(:type)
|
|
990
|
+
when :suspended
|
|
991
|
+
result = outcome.fetch(:result)
|
|
992
|
+
callback_error = deliver_terminal(
|
|
993
|
+
activation,
|
|
994
|
+
:approval_required,
|
|
995
|
+
request: result.fetch(:approval_request)
|
|
996
|
+
)
|
|
997
|
+
settle_after_terminal(
|
|
998
|
+
command.result_task,
|
|
999
|
+
callback_error,
|
|
1000
|
+
:approval_required,
|
|
1001
|
+
result
|
|
1002
|
+
)
|
|
1003
|
+
when :completed
|
|
1004
|
+
registry.delete(activation.execution_id)
|
|
1005
|
+
result = outcome.fetch(:result)
|
|
1006
|
+
callback_error = deliver_terminal(activation, :done, result)
|
|
1007
|
+
settle_after_terminal(
|
|
1008
|
+
command.result_task,
|
|
1009
|
+
callback_error,
|
|
1010
|
+
:done,
|
|
1011
|
+
result
|
|
1012
|
+
)
|
|
1013
|
+
when :failed
|
|
1014
|
+
registry.delete(activation.execution_id)
|
|
1015
|
+
error = outcome.fetch(:error)
|
|
1016
|
+
type = terminal_event_type(error)
|
|
1017
|
+
callback_error = deliver_terminal(activation, type, error: error)
|
|
1018
|
+
settle_after_terminal(
|
|
1019
|
+
command.result_task,
|
|
1020
|
+
callback_error,
|
|
1021
|
+
type,
|
|
1022
|
+
nil,
|
|
1023
|
+
error
|
|
1024
|
+
)
|
|
1025
|
+
end
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
def settle_without_listener(activation, result_task, outcome, commit_error)
|
|
1029
|
+
registry = Phronomy::Runtime.instance.__agent_activations
|
|
1030
|
+
if commit_error
|
|
1031
|
+
return fail_task(result_task, commit_error)
|
|
1032
|
+
end
|
|
1033
|
+
|
|
1034
|
+
case outcome&.fetch(:type)
|
|
1035
|
+
when :suspended
|
|
1036
|
+
complete_task(result_task, outcome.fetch(:result))
|
|
1037
|
+
when :completed
|
|
1038
|
+
registry.delete(activation.execution_id)
|
|
1039
|
+
complete_task(result_task, outcome.fetch(:result))
|
|
1040
|
+
when :failed
|
|
1041
|
+
registry.delete(activation.execution_id)
|
|
1042
|
+
fail_task(result_task, outcome.fetch(:error))
|
|
1043
|
+
else
|
|
1044
|
+
registry.delete(activation.execution_id)
|
|
1045
|
+
fail_task(
|
|
1046
|
+
result_task,
|
|
1047
|
+
Phronomy::RuntimeShutdownError.new(
|
|
1048
|
+
"EventLoop is not accepting terminal delivery"
|
|
1049
|
+
)
|
|
1050
|
+
)
|
|
1051
|
+
end
|
|
1052
|
+
end
|
|
1053
|
+
|
|
1054
|
+
def assistant_message_ref(tx, outcome)
|
|
1055
|
+
return unless outcome
|
|
1056
|
+
|
|
1057
|
+
payload = {
|
|
1058
|
+
"role" => "assistant",
|
|
1059
|
+
"content" => json_value(outcome.content),
|
|
1060
|
+
"tool_calls" => json_value(Array(outcome.tool_calls)),
|
|
1061
|
+
"model_id" => outcome.metadata["model_id"]
|
|
1062
|
+
}.compact
|
|
1063
|
+
tx.contents.put_json(payload)
|
|
1064
|
+
end
|
|
1065
|
+
|
|
1066
|
+
def assistant_message_metadata(outcome)
|
|
1067
|
+
calls = Array(outcome&.tool_calls)
|
|
1068
|
+
{
|
|
1069
|
+
"tool_call_ids" => calls.map { |call| call.fetch("id").to_s },
|
|
1070
|
+
"tool_names" => calls.map { |call| call.fetch("name").to_s }
|
|
1071
|
+
}
|
|
1072
|
+
end
|
|
1073
|
+
|
|
1074
|
+
def assistant_output_ref(tx, outcome)
|
|
1075
|
+
return unless outcome&.content_present?
|
|
1076
|
+
|
|
1077
|
+
content = outcome.content
|
|
1078
|
+
text = content.is_a?(String) ? content : Phronomy::CanonicalJSON.dump(content)
|
|
1079
|
+
tx.contents.put_text(text)
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
def put_runtime_content(tx, value)
|
|
1083
|
+
value.is_a?(String) ?
|
|
1084
|
+
tx.contents.put_text(value) : tx.contents.put_json(json_value(value))
|
|
1085
|
+
end
|
|
1086
|
+
|
|
897
1087
|
def start_resume(activation, result_task, approved:, config:)
|
|
898
1088
|
invocation = activation.invocation
|
|
899
1089
|
invocation.merge_config!(config)
|
|
@@ -909,7 +1099,12 @@ module Phronomy
|
|
|
909
1099
|
activation.session = parent_session
|
|
910
1100
|
source_task = Phronomy::Task.deferred(name: "#{result_task.name}-source")
|
|
911
1101
|
source_task.on_complete do |completed, error|
|
|
912
|
-
finish(
|
|
1102
|
+
finish(
|
|
1103
|
+
activation,
|
|
1104
|
+
result_task,
|
|
1105
|
+
completed || parent_session.context,
|
|
1106
|
+
error
|
|
1107
|
+
)
|
|
913
1108
|
end
|
|
914
1109
|
event_loop.register(parent_session, completion: source_task)
|
|
915
1110
|
invocation.tool_invocations.each do |child|
|
|
@@ -936,6 +1131,7 @@ module Phronomy
|
|
|
936
1131
|
completion = Phronomy::Task.deferred(name: "tool-session:#{child.id}")
|
|
937
1132
|
completion.on_complete do |_result, error|
|
|
938
1133
|
next unless error
|
|
1134
|
+
|
|
939
1135
|
child.mark_framework_failed!(error)
|
|
940
1136
|
runtime.event_loop.post_to_session(
|
|
941
1137
|
Phronomy::Event.new(
|
|
@@ -966,23 +1162,15 @@ module Phronomy
|
|
|
966
1162
|
ContextPolicyRegistry.default.resolve(descriptor)
|
|
967
1163
|
end
|
|
968
1164
|
|
|
969
|
-
def transcript_messages(root)
|
|
970
|
-
materializer = RubyLLMMaterializer.new(
|
|
971
|
-
agent: @agent,
|
|
972
|
-
persistence: @agent.persistence
|
|
973
|
-
)
|
|
974
|
-
materializer.materialize_journal_records(
|
|
975
|
-
JournalProjection.new(
|
|
976
|
-
persistence: @agent.persistence,
|
|
977
|
-
agent_root: root
|
|
978
|
-
).transcript_records
|
|
979
|
-
)
|
|
980
|
-
end
|
|
981
|
-
|
|
982
1165
|
def deliver_terminal(activation, type, payload)
|
|
983
1166
|
listener = activation.application_listener
|
|
984
1167
|
return nil unless listener
|
|
985
|
-
|
|
1168
|
+
|
|
1169
|
+
@agent.send(
|
|
1170
|
+
:_deliver_stream_event,
|
|
1171
|
+
listener,
|
|
1172
|
+
StreamEvent.new(type: type, payload: payload)
|
|
1173
|
+
)
|
|
986
1174
|
end
|
|
987
1175
|
|
|
988
1176
|
def post_prep_failure(runtime, listener, result_task, error)
|
|
@@ -997,79 +1185,55 @@ module Phronomy
|
|
|
997
1185
|
fail_task(result_task, error) unless posted
|
|
998
1186
|
end
|
|
999
1187
|
|
|
1000
|
-
def
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
outcome = command.outcome
|
|
1008
|
-
case outcome.fetch(:type)
|
|
1009
|
-
when :suspended
|
|
1010
|
-
result = outcome.fetch(:result)
|
|
1011
|
-
callback_error = deliver_terminal(
|
|
1012
|
-
activation, :approval_required,
|
|
1013
|
-
request: result.fetch(:approval_request)
|
|
1014
|
-
)
|
|
1015
|
-
settle_after_terminal(command.result_task, callback_error, :approval_required, result)
|
|
1016
|
-
when :completed
|
|
1017
|
-
@agent.persistence.activations.delete(activation.execution_id)
|
|
1018
|
-
result = outcome.fetch(:result)
|
|
1019
|
-
callback_error = deliver_terminal(activation, :done, result)
|
|
1020
|
-
settle_after_terminal(command.result_task, callback_error, :done, result)
|
|
1021
|
-
when :failed
|
|
1022
|
-
@agent.persistence.activations.delete(activation.execution_id)
|
|
1023
|
-
error = outcome.fetch(:error)
|
|
1024
|
-
type = terminal_event_type(error)
|
|
1025
|
-
callback_error = deliver_terminal(activation, type, error: error)
|
|
1026
|
-
settle_after_terminal(command.result_task, callback_error, type, nil, error)
|
|
1027
|
-
end
|
|
1028
|
-
end
|
|
1029
|
-
|
|
1030
|
-
def settle_without_listener(activation, result_task, outcome, commit_error)
|
|
1031
|
-
return fail_task(result_task, commit_error) if commit_error
|
|
1032
|
-
case outcome&.fetch(:type)
|
|
1033
|
-
when :suspended then complete_task(result_task, outcome.fetch(:result))
|
|
1034
|
-
when :completed
|
|
1035
|
-
@agent.persistence.activations.delete(activation.execution_id)
|
|
1036
|
-
complete_task(result_task, outcome.fetch(:result))
|
|
1037
|
-
when :failed
|
|
1038
|
-
@agent.persistence.activations.delete(activation.execution_id)
|
|
1039
|
-
fail_task(result_task, outcome.fetch(:error))
|
|
1040
|
-
else
|
|
1041
|
-
fail_task(result_task, Phronomy::RuntimeShutdownError.new("EventLoop is not accepting terminal delivery"))
|
|
1042
|
-
end
|
|
1043
|
-
end
|
|
1044
|
-
|
|
1045
|
-
def settle_after_terminal(result_task, callback_error, event_type, result, execution_error = nil)
|
|
1188
|
+
def settle_after_terminal(
|
|
1189
|
+
result_task,
|
|
1190
|
+
callback_error,
|
|
1191
|
+
event_type,
|
|
1192
|
+
result,
|
|
1193
|
+
execution_error = nil
|
|
1194
|
+
)
|
|
1046
1195
|
if callback_error
|
|
1047
1196
|
policy = Phronomy.configuration.stream_callback_error_policy
|
|
1048
1197
|
@agent.send(
|
|
1049
|
-
:_report_stream_callback_error,
|
|
1050
|
-
|
|
1051
|
-
|
|
1198
|
+
:_report_stream_callback_error,
|
|
1199
|
+
callback_error,
|
|
1200
|
+
event: StreamEvent.new(
|
|
1201
|
+
type: event_type,
|
|
1202
|
+
payload: result || {error: execution_error}
|
|
1203
|
+
),
|
|
1204
|
+
invocation_id: nil,
|
|
1205
|
+
callback_error_policy: policy
|
|
1052
1206
|
)
|
|
1053
1207
|
if policy == :fail_task && execution_error.nil?
|
|
1054
|
-
return fail_task(
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1208
|
+
return fail_task(
|
|
1209
|
+
result_task,
|
|
1210
|
+
@agent.send(
|
|
1211
|
+
:_build_stream_callback_error,
|
|
1212
|
+
event_type: event_type,
|
|
1213
|
+
callback_error: callback_error,
|
|
1214
|
+
result: result
|
|
1215
|
+
)
|
|
1216
|
+
)
|
|
1058
1217
|
end
|
|
1059
1218
|
end
|
|
1060
|
-
execution_error ?
|
|
1219
|
+
execution_error ?
|
|
1220
|
+
fail_task(result_task, execution_error) : complete_task(result_task, result)
|
|
1061
1221
|
end
|
|
1062
1222
|
|
|
1063
1223
|
def terminal_event_type(error)
|
|
1064
1224
|
return :timeout if error.is_a?(Phronomy::TimeoutError)
|
|
1065
1225
|
return :cancelled if error.is_a?(Phronomy::CancellationError)
|
|
1226
|
+
|
|
1066
1227
|
:error
|
|
1067
1228
|
end
|
|
1068
1229
|
|
|
1069
1230
|
def dispatch_approval_listener(invocation, request)
|
|
1070
1231
|
listener = invocation.approval_listener
|
|
1071
1232
|
return unless listener
|
|
1072
|
-
|
|
1233
|
+
|
|
1234
|
+
Phronomy::Runtime.instance.offload.submit(on_full: :raise) do
|
|
1235
|
+
listener.call(request)
|
|
1236
|
+
end
|
|
1073
1237
|
end
|
|
1074
1238
|
|
|
1075
1239
|
def json_value(value)
|
|
@@ -1086,14 +1250,21 @@ module Phronomy
|
|
|
1086
1250
|
if value.respond_to?(:to_h)
|
|
1087
1251
|
json_value(value.to_h)
|
|
1088
1252
|
else
|
|
1089
|
-
raise ArgumentError,
|
|
1253
|
+
raise ArgumentError,
|
|
1254
|
+
"unsupported canonical runtime value: #{value.class}"
|
|
1090
1255
|
end
|
|
1091
1256
|
end
|
|
1092
1257
|
end
|
|
1093
1258
|
|
|
1094
1259
|
def terminal_status_for(error)
|
|
1095
|
-
|
|
1096
|
-
|
|
1260
|
+
if defined?(Phronomy::CancellationError) &&
|
|
1261
|
+
error.is_a?(Phronomy::CancellationError)
|
|
1262
|
+
return :cancelled
|
|
1263
|
+
end
|
|
1264
|
+
if defined?(Phronomy::FilterBlockError) &&
|
|
1265
|
+
error.is_a?(Phronomy::FilterBlockError)
|
|
1266
|
+
return :blocked
|
|
1267
|
+
end
|
|
1097
1268
|
|
|
1098
1269
|
:failed
|
|
1099
1270
|
end
|