phronomy 0.17.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 +122 -1134
- data/README.md +70 -1280
- data/benchmark/bench_regression.rb +25 -5
- data/docs/changelog/0.14-and-earlier.md +1137 -0
- data/docs/decisions/008-orchestrator-uses-os-threads.md +46 -48
- data/docs/decisions/009-state-store-abstraction.md +1 -1
- data/docs/decisions/010-cooperative-first-concurrency.md +155 -235
- data/docs/decisions/014-unified-persistence-durable-state.md +268 -0
- data/docs/features.md +93 -0
- data/docs/getting-started.md +387 -0
- data/docs/migrations/0.15.md +35 -0
- data/docs/migrations/0.16.md +43 -0
- data/docs/migrations/0.19.md +148 -0
- data/docs/runtime-and-concurrency.md +349 -0
- data/examples/workflows/generic_task_event_mapping.rb +14 -6
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +2 -2
- data/lib/phronomy/agent/async_event_api.rb +3 -3
- data/lib/phronomy/agent/base.rb +116 -55
- data/lib/phronomy/agent/context/capability/base.rb +13 -3
- data/lib/phronomy/agent/context_assembler.rb +13 -3
- data/lib/phronomy/agent/execution_coordinator.rb +423 -252
- data/lib/phronomy/agent/journal_projection.rb +5 -1
- data/lib/phronomy/agent/shared_state.rb +2 -0
- data/lib/phronomy/agent/tool_executor.rb +29 -71
- data/lib/phronomy/agent/tool_invocation.rb +97 -47
- data/lib/phronomy/agent/tool_invocation_session_builder.rb +55 -161
- data/lib/phronomy/configuration.rb +7 -30
- data/lib/phronomy/diagnostics.rb +12 -41
- data/lib/phronomy/engine/concurrency/async_queue.rb +5 -188
- data/lib/phronomy/engine/concurrency/cancellation_scope.rb +6 -7
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +48 -3
- data/lib/phronomy/engine/concurrency/deadline.rb +2 -3
- data/lib/phronomy/engine/concurrency/offload_pool.rb +696 -0
- data/lib/phronomy/engine/concurrency/pool_registry.rb +5 -5
- data/lib/phronomy/engine/event_loop.rb +170 -193
- data/lib/phronomy/engine/fsm_session.rb +6 -4
- data/lib/phronomy/engine/runtime/timer_queue.rb +48 -71
- data/lib/phronomy/engine/runtime/timer_service.rb +13 -21
- data/lib/phronomy/engine/runtime.rb +52 -158
- data/lib/phronomy/engine/task.rb +136 -277
- data/lib/phronomy/llm_adapter/base.rb +14 -14
- data/lib/phronomy/llm_adapter/ruby_llm.rb +3 -4
- data/lib/phronomy/llm_adapter.rb +2 -2
- data/lib/phronomy/metrics.rb +15 -30
- data/lib/phronomy/multi_agent/fan_out_invocation.rb +146 -0
- data/lib/phronomy/multi_agent/fan_out_session_builder.rb +125 -0
- data/lib/phronomy/multi_agent/handoff.rb +1 -0
- data/lib/phronomy/multi_agent/orchestrator.rb +147 -99
- data/lib/phronomy/multi_agent/team_coordinator.rb +2 -0
- data/lib/phronomy/persistence/in_memory.rb +113 -8
- data/lib/phronomy/persistence.rb +12 -3
- data/lib/phronomy/testing/eval/comparison.rb +23 -0
- data/lib/phronomy/testing/eval/dataset.rb +27 -0
- data/lib/phronomy/testing/eval/eval_case.rb +13 -0
- data/lib/phronomy/testing/eval/eval_result.rb +16 -0
- data/lib/phronomy/testing/eval/metrics.rb +43 -0
- data/lib/phronomy/testing/eval/runner.rb +52 -0
- data/lib/phronomy/testing/eval/scorer/base.rb +15 -0
- data/lib/phronomy/testing/eval/scorer/exact_match.rb +25 -0
- data/lib/phronomy/testing/eval/scorer/includes_scorer.rb +25 -0
- data/lib/phronomy/testing/eval/scorer/llm_judge.rb +46 -0
- data/lib/phronomy/testing/eval/scorer.rb +10 -0
- data/lib/phronomy/testing/eval.rb +9 -0
- data/lib/phronomy/testing/fake_clock.rb +6 -53
- data/lib/phronomy/testing.rb +2 -6
- data/lib/phronomy/tools/agent.rb +141 -6
- data/lib/phronomy/vector_store/async_backend.rb +21 -17
- data/lib/phronomy/vector_store/base.rb +2 -2
- data/lib/phronomy/vector_store/embeddings/base.rb +6 -5
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow.rb +10 -9
- data/lib/phronomy/workflow_runner.rb +363 -99
- data/lib/phronomy.rb +10 -4
- data/scripts/api_snapshot.rb +4 -4
- metadata +25 -33
- data/lib/phronomy/engine/concurrency/blocking_adapter_pool.rb +0 -561
- data/lib/phronomy/engine/runtime/deterministic_scheduler.rb +0 -439
- data/lib/phronomy/engine/runtime/fake_scheduler.rb +0 -165
- data/lib/phronomy/engine/runtime/runtime_metrics.rb +0 -116
- data/lib/phronomy/engine/runtime/scheduler.rb +0 -98
- data/lib/phronomy/engine/runtime/scheduler_timer_adapter.rb +0 -79
- data/lib/phronomy/engine/runtime/task_registry.rb +0 -95
- data/lib/phronomy/engine/runtime/thread_scheduler.rb +0 -30
- data/lib/phronomy/engine/task/backend.rb +0 -80
- data/lib/phronomy/engine/task/deferred_backend.rb +0 -73
- data/lib/phronomy/engine/task/fiber_backend.rb +0 -157
- data/lib/phronomy/engine/task/immediate_backend.rb +0 -89
- data/lib/phronomy/engine/task/mapped_backend.rb +0 -90
- data/lib/phronomy/engine/task/thread_backend.rb +0 -84
- data/lib/phronomy/engine/task_group.rb +0 -193
- data/lib/phronomy/eval/comparison.rb +0 -47
- data/lib/phronomy/eval/dataset.rb +0 -45
- data/lib/phronomy/eval/eval_case.rb +0 -17
- data/lib/phronomy/eval/eval_result.rb +0 -29
- data/lib/phronomy/eval/metrics.rb +0 -66
- data/lib/phronomy/eval/runner.rb +0 -94
- data/lib/phronomy/eval/scorer/base.rb +0 -22
- data/lib/phronomy/eval/scorer/exact_match.rb +0 -31
- data/lib/phronomy/eval/scorer/includes_scorer.rb +0 -32
- data/lib/phronomy/eval/scorer/llm_judge.rb +0 -72
- data/lib/phronomy/eval/scorer.rb +0 -9
- data/lib/phronomy/eval.rb +0 -7
- data/lib/phronomy/state_store/base.rb +0 -48
- data/lib/phronomy/state_store/in_memory.rb +0 -62
- data/lib/phronomy/testing/fake_scheduler.rb +0 -104
- data/lib/phronomy/testing/scheduler_helpers.rb +0 -68
data/lib/phronomy/agent/base.rb
CHANGED
|
@@ -252,24 +252,22 @@ module Phronomy
|
|
|
252
252
|
new(agent_id: agent_id, persistence: persistence, load_existing: true)
|
|
253
253
|
end
|
|
254
254
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
config: config
|
|
272
|
-
)
|
|
255
|
+
# Resolves the live Agent instance that currently owns execution_id in
|
|
256
|
+
# this process. This is a Runtime-local lookup, not durable rehydration.
|
|
257
|
+
def live_for_execution(execution_id)
|
|
258
|
+
activation = Phronomy::Runtime.instance.__agent_activations.fetch(execution_id)
|
|
259
|
+
unless activation
|
|
260
|
+
raise Phronomy::ExecutionRehydrationRequiredError,
|
|
261
|
+
"no live activation for #{execution_id}; durable rehydration is required"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
agent = activation.agent
|
|
265
|
+
unless agent.is_a?(self)
|
|
266
|
+
raise ArgumentError,
|
|
267
|
+
"live activation #{execution_id} belongs to #{agent.class}, not #{self}"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
agent
|
|
273
271
|
end
|
|
274
272
|
end
|
|
275
273
|
|
|
@@ -283,21 +281,33 @@ module Phronomy
|
|
|
283
281
|
metadata: {},
|
|
284
282
|
load_existing: false
|
|
285
283
|
)
|
|
286
|
-
@persistence = persistence ||
|
|
284
|
+
@persistence = persistence ||
|
|
285
|
+
Phronomy.configuration.persistence ||
|
|
286
|
+
Phronomy::Persistence::InMemory.new
|
|
287
287
|
@agent_id = agent_id.to_s.freeze
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
288
|
+
|
|
289
|
+
if load_existing
|
|
290
|
+
root = records = nil
|
|
291
|
+
@persistence.transaction do |tx|
|
|
292
|
+
root = tx.agents.load(@agent_id)
|
|
293
|
+
records = tx.journals.read(
|
|
294
|
+
@agent_id,
|
|
295
|
+
limit: root.journal_position
|
|
296
|
+
)
|
|
297
297
|
end
|
|
298
|
-
|
|
298
|
+
validate_loaded_definition!(root)
|
|
299
|
+
@root = root
|
|
300
|
+
@_phronomy_journal_records = Array(records).dup.freeze
|
|
299
301
|
else
|
|
300
|
-
create_agent_root!(
|
|
302
|
+
@root = create_agent_root!(
|
|
303
|
+
context: context,
|
|
304
|
+
knowledge: knowledge,
|
|
305
|
+
metadata: metadata
|
|
306
|
+
)
|
|
307
|
+
@_phronomy_journal_records = @persistence.journals.read(
|
|
308
|
+
@agent_id,
|
|
309
|
+
limit: @root.journal_position
|
|
310
|
+
).dup.freeze
|
|
301
311
|
end
|
|
302
312
|
end
|
|
303
313
|
|
|
@@ -306,7 +316,10 @@ module Phronomy
|
|
|
306
316
|
end
|
|
307
317
|
|
|
308
318
|
def journal_projection
|
|
309
|
-
Agent::JournalProjection.new(
|
|
319
|
+
Agent::JournalProjection.new(
|
|
320
|
+
agent_root: @root,
|
|
321
|
+
records: _journal_records_snapshot
|
|
322
|
+
)
|
|
310
323
|
end
|
|
311
324
|
|
|
312
325
|
def transcript
|
|
@@ -334,13 +347,14 @@ module Phronomy
|
|
|
334
347
|
end
|
|
335
348
|
end
|
|
336
349
|
|
|
337
|
-
# Appends persistent Knowledge to the Agent Journal.
|
|
338
|
-
#
|
|
350
|
+
# Appends persistent Knowledge to the Agent Journal. The live Agent owns the
|
|
351
|
+
# current logical root/Journal view; Persistence is advanced optimistically.
|
|
339
352
|
def add_knowledge(content, metadata: {})
|
|
353
|
+
current = agent_root
|
|
340
354
|
next_root = nil
|
|
355
|
+
appended = nil
|
|
341
356
|
persistence.transaction do |tx|
|
|
342
357
|
tx.executions.assert_idle!(agent_id)
|
|
343
|
-
current = tx.agents.load(agent_id)
|
|
344
358
|
record = build_knowledge_record(
|
|
345
359
|
tx: tx,
|
|
346
360
|
root: current,
|
|
@@ -363,6 +377,7 @@ module Phronomy
|
|
|
363
377
|
root: next_root
|
|
364
378
|
)
|
|
365
379
|
end
|
|
380
|
+
_append_journal_records(appended)
|
|
366
381
|
@root = next_root
|
|
367
382
|
self
|
|
368
383
|
end
|
|
@@ -394,6 +409,7 @@ module Phronomy
|
|
|
394
409
|
tx.agents.delete(agent_id)
|
|
395
410
|
end
|
|
396
411
|
@root = nil
|
|
412
|
+
@_phronomy_journal_records = [].freeze
|
|
397
413
|
true
|
|
398
414
|
end
|
|
399
415
|
|
|
@@ -404,6 +420,17 @@ module Phronomy
|
|
|
404
420
|
|
|
405
421
|
private
|
|
406
422
|
|
|
423
|
+
def validate_loaded_definition!(loaded)
|
|
424
|
+
definition = self.class.agent_definition
|
|
425
|
+
return if loaded.agent_definition_id == definition.fetch(:id) &&
|
|
426
|
+
loaded.definition_version == definition.fetch(:version)
|
|
427
|
+
|
|
428
|
+
raise Phronomy::ConfigurationError,
|
|
429
|
+
"Agent definition mismatch for #{@agent_id}: stored " \
|
|
430
|
+
"#{loaded.agent_definition_id}@#{loaded.definition_version}, runtime " \
|
|
431
|
+
"#{definition.fetch(:id)}@#{definition.fetch(:version)}"
|
|
432
|
+
end
|
|
433
|
+
|
|
407
434
|
def create_agent_root!(context:, knowledge:, metadata:)
|
|
408
435
|
definition = self.class.agent_definition
|
|
409
436
|
root = Agent::AgentRoot.create(
|
|
@@ -480,10 +507,11 @@ module Phronomy
|
|
|
480
507
|
end
|
|
481
508
|
|
|
482
509
|
def mutate_context!(kind, context_affecting: true)
|
|
510
|
+
current = agent_root
|
|
483
511
|
next_root = nil
|
|
512
|
+
appended = nil
|
|
484
513
|
persistence.transaction do |tx|
|
|
485
514
|
tx.executions.assert_idle!(agent_id)
|
|
486
|
-
current = tx.agents.load(agent_id)
|
|
487
515
|
record = Agent::JournalRecord.new(
|
|
488
516
|
agent_id: agent_id,
|
|
489
517
|
kind: kind,
|
|
@@ -502,8 +530,13 @@ module Phronomy
|
|
|
502
530
|
context_revision: context_affecting ?
|
|
503
531
|
yield_context_revision(current, proposed) : current.context_revision
|
|
504
532
|
)
|
|
505
|
-
tx.agents.save(
|
|
533
|
+
tx.agents.save(
|
|
534
|
+
agent_id,
|
|
535
|
+
expected_revision: current.agent_revision,
|
|
536
|
+
root: next_root
|
|
537
|
+
)
|
|
506
538
|
end
|
|
539
|
+
_append_journal_records(appended)
|
|
507
540
|
@root = next_root
|
|
508
541
|
end
|
|
509
542
|
|
|
@@ -511,6 +544,18 @@ module Phronomy
|
|
|
511
544
|
(proposed.context_revision == current.context_revision) ? current.context_revision + 1 : proposed.context_revision
|
|
512
545
|
end
|
|
513
546
|
|
|
547
|
+
def _journal_records_snapshot
|
|
548
|
+
@_phronomy_journal_records || [].freeze
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def _append_journal_records(records)
|
|
552
|
+
incoming = Array(records)
|
|
553
|
+
return _journal_records_snapshot if incoming.empty?
|
|
554
|
+
|
|
555
|
+
@_phronomy_journal_records =
|
|
556
|
+
(_journal_records_snapshot + incoming).freeze
|
|
557
|
+
end
|
|
558
|
+
|
|
514
559
|
public
|
|
515
560
|
|
|
516
561
|
def _add_handoff_tool(tool_class)
|
|
@@ -553,35 +598,20 @@ module Phronomy
|
|
|
553
598
|
[effective_thread_id, effective_config]
|
|
554
599
|
end
|
|
555
600
|
|
|
556
|
-
def
|
|
601
|
+
def _check_event_loop_reentrancy(sync_method, async_method)
|
|
557
602
|
if Phronomy::Runtime.instance.event_loop.current?
|
|
558
|
-
raise Phronomy::
|
|
603
|
+
raise Phronomy::EventLoopReentrancyError,
|
|
559
604
|
"#{self.class.name}##{sync_method} cannot run on the EventLoop thread. " \
|
|
560
605
|
"Use #{async_method} and return immediately."
|
|
561
606
|
end
|
|
562
|
-
|
|
563
|
-
return unless Phronomy::Task.current
|
|
564
|
-
|
|
565
|
-
msg = "#{self.class.name}##{sync_method} called from inside a scheduler task. " \
|
|
566
|
-
"This blocks the scheduler until the inner invocation completes, preventing " \
|
|
567
|
-
"other tasks from making progress. Use #{async_method} + await instead."
|
|
568
|
-
if Phronomy.configuration.strict_runtime_guards
|
|
569
|
-
raise Phronomy::SchedulerReentrancyError, msg
|
|
570
|
-
elsif Phronomy.configuration.logger
|
|
571
|
-
Phronomy.configuration.logger.warn(msg)
|
|
572
|
-
else
|
|
573
|
-
Kernel.warn("[phronomy] WARNING: #{msg}")
|
|
574
|
-
end
|
|
575
607
|
end
|
|
576
608
|
|
|
577
609
|
def _complete_result_task(task, result)
|
|
578
|
-
task.
|
|
579
|
-
task.transition!(:completed, value: result)
|
|
610
|
+
task.complete(result)
|
|
580
611
|
end
|
|
581
612
|
|
|
582
613
|
def _fail_result_task(task, error)
|
|
583
|
-
task.
|
|
584
|
-
task.transition!(:failed, error: error)
|
|
614
|
+
task.fail(error)
|
|
585
615
|
end
|
|
586
616
|
|
|
587
617
|
def _translated_error(error)
|
|
@@ -782,6 +812,10 @@ module Phronomy
|
|
|
782
812
|
return resolved if result_filters.empty?
|
|
783
813
|
|
|
784
814
|
effective_name = resolved.new.name
|
|
815
|
+
custom_async_call =
|
|
816
|
+
resolved.instance_method(:call_async).owner !=
|
|
817
|
+
Phronomy::Agent::Context::Capability::Base
|
|
818
|
+
|
|
785
819
|
Class.new(resolved) do
|
|
786
820
|
tool_name effective_name
|
|
787
821
|
define_method(:call) do |args, **kwargs|
|
|
@@ -790,6 +824,33 @@ module Phronomy
|
|
|
790
824
|
filter.call(val, tool_name: name, args: args)
|
|
791
825
|
}
|
|
792
826
|
end
|
|
827
|
+
|
|
828
|
+
# Base#call_async ultimately executes #call, so the synchronous wrapper
|
|
829
|
+
# above already applies filters for ordinary Tools. Agent-backed Tools
|
|
830
|
+
# override #call_async and bypass #call; only those custom async Tools
|
|
831
|
+
# need an asynchronous result-filter wrapper here.
|
|
832
|
+
if custom_async_call
|
|
833
|
+
define_method(:call_async) do |args, **kwargs|
|
|
834
|
+
source = super(args, **kwargs)
|
|
835
|
+
filtered = Phronomy::Task.deferred(name: "tool-filter-#{name}")
|
|
836
|
+
source.on_complete do |value, error|
|
|
837
|
+
if error
|
|
838
|
+
filtered.fail(error)
|
|
839
|
+
next
|
|
840
|
+
end
|
|
841
|
+
|
|
842
|
+
begin
|
|
843
|
+
result = result_filters.inject(value) { |val, filter|
|
|
844
|
+
filter.call(val, tool_name: name, args: args)
|
|
845
|
+
}
|
|
846
|
+
filtered.complete(result)
|
|
847
|
+
rescue => filter_error
|
|
848
|
+
filtered.fail(filter_error)
|
|
849
|
+
end
|
|
850
|
+
end
|
|
851
|
+
filtered
|
|
852
|
+
end
|
|
853
|
+
end
|
|
793
854
|
end
|
|
794
855
|
end
|
|
795
856
|
end
|
|
@@ -113,16 +113,22 @@ module Phronomy
|
|
|
113
113
|
|
|
114
114
|
public
|
|
115
115
|
|
|
116
|
+
# Declares whether Tool work is safe to run cooperatively on the EventLoop
|
|
117
|
+
# or must be offloaded to a bounded worker pool.
|
|
118
|
+
#
|
|
119
|
+
# Phronomy does not classify the reason for offloading. Blocking I/O,
|
|
120
|
+
# CPU-bound synchronous work, and other long synchronous calls all use
|
|
121
|
+
# +:offloaded+. The application owns that workload classification.
|
|
116
122
|
# @api public
|
|
117
123
|
def execution_mode(value = nil)
|
|
118
124
|
if value.nil?
|
|
119
125
|
return @execution_mode if instance_variable_defined?(:@execution_mode)
|
|
120
126
|
return superclass.execution_mode if superclass.respond_to?(:execution_mode)
|
|
121
127
|
|
|
122
|
-
return :
|
|
128
|
+
return :offloaded
|
|
123
129
|
end
|
|
124
130
|
|
|
125
|
-
valid = %i[cooperative
|
|
131
|
+
valid = %i[cooperative offloaded]
|
|
126
132
|
unless valid.include?(value)
|
|
127
133
|
raise ArgumentError,
|
|
128
134
|
"execution_mode must be one of #{valid.inspect}, got #{value.inspect}"
|
|
@@ -296,7 +302,11 @@ module Phronomy
|
|
|
296
302
|
end
|
|
297
303
|
|
|
298
304
|
# @api public
|
|
299
|
-
def call_async(
|
|
305
|
+
def call_async(
|
|
306
|
+
args,
|
|
307
|
+
cancellation_token: nil,
|
|
308
|
+
config: {}
|
|
309
|
+
)
|
|
300
310
|
Phronomy::Agent::ToolExecutor.call_async(
|
|
301
311
|
tool: self,
|
|
302
312
|
args: args,
|
|
@@ -14,18 +14,20 @@ module Phronomy
|
|
|
14
14
|
agent:,
|
|
15
15
|
persistence:,
|
|
16
16
|
policy: ContextPolicies::Default.new,
|
|
17
|
-
candidate_resolver: nil
|
|
17
|
+
candidate_resolver: nil,
|
|
18
|
+
journal_records: nil
|
|
18
19
|
)
|
|
19
20
|
@agent = agent
|
|
20
21
|
@persistence = persistence
|
|
21
22
|
@policy = policy
|
|
23
|
+
@journal_records = journal_records
|
|
22
24
|
@candidate_resolver = candidate_resolver || ContextCandidateResolver.new(
|
|
23
25
|
content_loader: method(:fetch_content)
|
|
24
26
|
)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def build_initial(input:, agent_root:, execution:, config: {}, patch: LLMInputPatch.empty)
|
|
28
|
-
projection =
|
|
30
|
+
projection = journal_projection(agent_root)
|
|
29
31
|
model_cfg = effective_model_config(config, patch)
|
|
30
32
|
tool_set = ToolDefinitionSet.build(@agent)
|
|
31
33
|
system_text = build_system_text(input)
|
|
@@ -65,7 +67,7 @@ module Phronomy
|
|
|
65
67
|
config: {},
|
|
66
68
|
patch: LLMInputPatch.empty
|
|
67
69
|
)
|
|
68
|
-
projection =
|
|
70
|
+
projection = journal_projection(agent_root)
|
|
69
71
|
model_cfg = effective_model_config(config, patch)
|
|
70
72
|
hook_candidates = normalize_candidates(patch.segment_candidates)
|
|
71
73
|
system_segments = base_manifest.segments.select do |segment|
|
|
@@ -96,6 +98,14 @@ module Phronomy
|
|
|
96
98
|
|
|
97
99
|
private
|
|
98
100
|
|
|
101
|
+
def journal_projection(agent_root)
|
|
102
|
+
if @journal_records
|
|
103
|
+
JournalProjection.new(agent_root: agent_root, records: @journal_records)
|
|
104
|
+
else
|
|
105
|
+
JournalProjection.new(persistence: @persistence, agent_root: agent_root)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
99
109
|
def assemble(
|
|
100
110
|
agent_root:,
|
|
101
111
|
execution:,
|