phronomy 0.28.0 → 0.30.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 +96 -1
- data/README.md +32 -6
- data/docs/application-recipes.md +229 -0
- data/docs/architecture/agent-configuration-and-tool-binding.md +8 -0
- data/docs/architecture/agent-iteration-budget.md +81 -0
- data/docs/architecture/dx05-agent-scope.md +126 -0
- data/docs/architecture/dx05-workflow-task-state.md +134 -0
- data/docs/architecture/error-api-review.md +245 -0
- data/docs/architecture/generator-verifier-lifecycle-result.md +126 -0
- data/docs/architecture/generator-verifier-ownership.md +18 -109
- data/docs/architecture/llm-continuation-metadata.md +88 -0
- data/docs/architecture/lm-studio-output-failures.md +111 -0
- data/docs/architecture/r8-unit15.md +3 -0
- data/docs/architecture/r8-unit7.md +2 -1
- data/docs/architecture/structured-output-and-scope.md +183 -0
- data/docs/architecture/tracing.md +22 -4
- data/docs/architecture/transport-error-boundaries.md +103 -0
- data/docs/architecture/trust-acceptance-followup.md +87 -0
- data/docs/failure-reason-codes.md +45 -0
- data/docs/features.md +14 -2
- data/docs/getting-started.md +44 -43
- data/docs/migrations/r8-unit15.md +3 -0
- data/docs/ordered-event-delivery.md +54 -0
- data/docs/workflow-completion.md +92 -0
- data/gemfiles/http_minimum.gemfile +10 -0
- data/lib/phronomy/agent/admission.rb +1 -1
- data/lib/phronomy/agent/agent_not_found_error.rb +16 -0
- data/lib/phronomy/agent/agent_unloaded_error.rb +14 -0
- data/lib/phronomy/agent/api/agent.rb +13 -0
- data/lib/phronomy/agent/async_event_api.rb +121 -88
- data/lib/phronomy/agent/base.rb +82 -44
- data/lib/phronomy/agent/before_llm_input.rb +7 -1
- data/lib/phronomy/agent/context_importer.rb +4 -0
- data/lib/phronomy/agent/execution/agent_invocation.rb +6 -2
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +6 -1
- data/lib/phronomy/agent/execution/exact_execution.rb +20 -9
- data/lib/phronomy/agent/execution/execution_cancellation.rb +1 -1
- data/lib/phronomy/agent/execution/execution_coordinator.rb +79 -18
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +24 -4
- data/lib/phronomy/agent/execution/execution_registry.rb +5 -1
- data/lib/phronomy/agent/execution/initial_preparation.rb +11 -5
- data/lib/phronomy/agent/execution/invocation_actions.rb +1 -1
- data/lib/phronomy/agent/execution/invocation_transitions.rb +4 -4
- data/lib/phronomy/agent/execution/iteration_budget.rb +35 -0
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +4 -4
- data/lib/phronomy/agent/execution_observation.rb +5 -5
- data/lib/phronomy/agent/filterable.rb +21 -15
- data/lib/phronomy/agent/lifecycle/lifetime_api.rb +76 -0
- data/lib/phronomy/agent/lifecycle/operation_lease.rb +33 -0
- data/lib/phronomy/agent/lifecycle/operation_result.rb +53 -0
- data/lib/phronomy/agent/lifecycle/ownership_registry.rb +99 -8
- data/lib/phronomy/agent/lifecycle/scope.rb +208 -0
- data/lib/phronomy/agent/lifecycle/scope_cleanup_error.rb +21 -0
- data/lib/phronomy/agent/lifecycle/unload_transition.rb +78 -0
- data/lib/phronomy/agent/output_contract.rb +64 -0
- data/lib/phronomy/agent/output_error.rb +53 -0
- data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +6 -6
- data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +8 -7
- data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +14 -18
- data/lib/phronomy/agent/recovery/recovery_coordinator.rb +6 -10
- data/lib/phronomy/agent/recovery/recovery_support.rb +6 -8
- data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +2 -1
- data/lib/phronomy/agent/runtime_input.rb +15 -4
- data/lib/phronomy/agent/saved_context_reader.rb +2 -1
- data/lib/phronomy/agent/store.rb +10 -2
- data/lib/phronomy/agent/stream_callback_error.rb +2 -2
- data/lib/phronomy/common/error.rb +65 -1
- data/lib/phronomy/common/values/immutable.rb +33 -0
- data/lib/phronomy/content_store/base.rb +7 -2
- data/lib/phronomy/context/assembly.rb +18 -22
- data/lib/phronomy/embeddings/authentication_error.rb +11 -0
- data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +8 -2
- data/lib/phronomy/embeddings/rate_limit_error.rb +11 -0
- data/lib/phronomy/engine/fsm_session.rb +16 -1
- data/lib/phronomy/execution/execution_cancellation_error.rb +2 -2
- data/lib/phronomy/execution/execution_timeout_error.rb +2 -2
- data/lib/phronomy/execution/task_result.rb +7 -2
- data/lib/phronomy/filter/filter_block_error.rb +2 -2
- data/lib/phronomy/generation/generator_verifier/agent_operation.rb +28 -5
- data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +14 -27
- data/lib/phronomy/generation/generator_verifier/approval.rb +30 -0
- data/lib/phronomy/generation/generator_verifier/cleanup_error.rb +15 -0
- data/lib/phronomy/generation/generator_verifier/invocation.rb +147 -0
- data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +16 -0
- data/lib/phronomy/generation/generator_verifier/protocol.rb +55 -0
- data/lib/phronomy/generation/generator_verifier/result.rb +121 -0
- data/lib/phronomy/generation/generator_verifier/unaccepted_result_error.rb +14 -0
- data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +13 -11
- data/lib/phronomy/generation/generator_verifier.rb +170 -85
- data/lib/phronomy/integrations/ordered_event_delivery.rb +208 -0
- data/lib/phronomy/llm_adapter/backends/lm_studio.rb +139 -0
- data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +134 -12
- data/lib/phronomy/llm_adapter/backends/structured_response_capture.rb +41 -0
- data/lib/phronomy/llm_adapter/message.rb +4 -0
- data/lib/phronomy/llm_adapter/request.rb +17 -2
- data/lib/phronomy/llm_adapter/response.rb +11 -4
- data/lib/phronomy/llm_adapter/structured_output_unsupported_error.rb +14 -0
- data/lib/phronomy/llm_adapter/transport_error.rb +4 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +5 -5
- data/lib/phronomy/multi_agent/shared_state.rb +1 -0
- data/lib/phronomy/multi_agent/team_busy_error.rb +18 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +20 -7
- data/lib/phronomy/multi_agent/team_not_found_error.rb +18 -0
- data/lib/phronomy/output_parser/json_schema_parser.rb +113 -0
- data/lib/phronomy/output_parser/parse_error.rb +10 -1
- data/lib/phronomy/runtime_composition/agent_defaults.rb +4 -0
- data/lib/phronomy/storage/condition_failed_error.rb +2 -2
- data/lib/phronomy/storage/unique_constraint_error.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +1 -1
- data/lib/phronomy/tool/base.rb +25 -23
- data/lib/phronomy/tracing/automatic.rb +14 -8
- data/lib/phronomy/tracing/base.rb +37 -7
- data/lib/phronomy/tracing/langfuse_tracer.rb +3 -3
- data/lib/phronomy/tracing/observation.rb +2 -1
- data/lib/phronomy/tracing/open_telemetry_tracer.rb +7 -32
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/execution/workflow.rb +30 -2
- data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +1 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +74 -3
- data/lib/phronomy/workflow/execution/workflow_runner.rb +48 -14
- data/lib/phronomy/workflow/execution/workflow_task_execution.rb +153 -0
- data/lib/phronomy/workflow/execution/workflow_task_reception.rb +72 -0
- data/lib/phronomy/workflow/execution/workflow_task_resume_error.rb +14 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +26 -3
- data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +3 -1
- data/lib/phronomy/workflow/runtime_binding/workflow_task_session.rb +76 -0
- data/lib/phronomy.rb +7 -2
- data/scripts/api_snapshot.rb +15 -0
- data/scripts/check_readme_ruby.rb +24 -29
- data/scripts/check_readme_runnable.rb +92 -81
- data/scripts/migrate_generator_verifier_result.rb +18 -0
- data/sig/phronomy/agent/agent_not_found_error.rbs +6 -0
- data/sig/phronomy/agent/agent_unloaded_error.rbs +6 -0
- data/sig/phronomy/agent/execution_contract.rbs +4 -3
- data/sig/phronomy/agent/stream_callback_error.rbs +8 -0
- data/sig/phronomy/agent.rbs +36 -5
- data/sig/phronomy/embeddings/base.rbs +6 -0
- data/sig/phronomy/execution/execution.rbs +2 -2
- data/sig/phronomy/extensions.rbs +8 -2
- data/sig/phronomy/filter/filter_block_error.rbs +6 -0
- data/sig/phronomy/generation/generator_verifier.rbs +70 -0
- data/sig/phronomy/integrations.rbs +14 -0
- data/sig/phronomy/llm_adapter/backends/lm_studio.rbs +8 -0
- data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +1 -0
- data/sig/phronomy/llm_adapter/values.rbs +8 -2
- data/sig/phronomy/multi_agent/team_busy_error.rbs +8 -0
- data/sig/phronomy/multi_agent/team_not_found_error.rbs +8 -0
- data/sig/phronomy/storage/contracts.rbs +2 -2
- data/sig/phronomy/tracing/observation.rbs +1 -1
- data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +1 -0
- data/sig/phronomy/workflow.rbs +4 -0
- data/sig/phronomy.rbs +6 -0
- metadata +103 -5
- data/lib/phronomy/generation/generator_verifier/default_parser.rb +0 -27
- data/lib/phronomy/generation/low_confidence_error.rb +0 -14
- data/lib/phronomy/runtime_composition/generation_defaults.rb +0 -6
|
@@ -18,7 +18,7 @@ module Phronomy
|
|
|
18
18
|
@agent, @id, @input, @config = agent, execution_id.to_s.freeze, input, config.freeze
|
|
19
19
|
@resume_only = resume_only
|
|
20
20
|
@environment = @agent.__execution_environment
|
|
21
|
-
@completion =
|
|
21
|
+
@completion = OperationResult.new(agent: @agent, name: "exact-execution:#{@id}")
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def start
|
|
@@ -43,13 +43,14 @@ module Phronomy
|
|
|
43
43
|
[:absent, nil]
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
|
+
@completion.track(preparation)
|
|
46
47
|
preparation.on_complete do |prepared, error|
|
|
47
48
|
if error
|
|
48
|
-
@completion.
|
|
49
|
+
@completion.settle(error: error)
|
|
49
50
|
else
|
|
50
51
|
disposition, result = prepared
|
|
51
52
|
case disposition
|
|
52
|
-
when :terminal then @completion.
|
|
53
|
+
when :terminal then @completion.settle(result)
|
|
53
54
|
when :absent
|
|
54
55
|
source = @agent.send(:_start_agent_operation, @input,
|
|
55
56
|
config: @config.merge(phronomy_reserved_execution_id: @id, phronomy_exact_observers: [@completion].freeze),
|
|
@@ -58,15 +59,15 @@ module Phronomy
|
|
|
58
59
|
when :active
|
|
59
60
|
command = Wait.new(coordinator: self, agent: @agent, execution_id: @id)
|
|
60
61
|
posted = @environment.registry.post(command, completion: @completion)
|
|
61
|
-
@completion.
|
|
62
|
+
@completion.settle(error: Phronomy::RuntimeShutdownError.new("Exact execution observer rejected")) unless posted
|
|
62
63
|
end
|
|
63
64
|
end
|
|
64
65
|
rescue => failure
|
|
65
|
-
@completion.
|
|
66
|
+
@completion.settle(error: failure)
|
|
66
67
|
end
|
|
67
68
|
@completion
|
|
68
69
|
rescue => failure
|
|
69
|
-
@completion.
|
|
70
|
+
@completion.settle(error: failure)
|
|
70
71
|
@completion
|
|
71
72
|
end
|
|
72
73
|
|
|
@@ -101,7 +102,7 @@ module Phronomy
|
|
|
101
102
|
waiter.on_complete { |_result, failure| reconcile(failure) }
|
|
102
103
|
@environment.registry.register_agent_completion_waiter(@id, waiter)
|
|
103
104
|
rescue => failure
|
|
104
|
-
@completion.
|
|
105
|
+
@completion.settle(error: failure)
|
|
105
106
|
end
|
|
106
107
|
|
|
107
108
|
private
|
|
@@ -120,6 +121,12 @@ module Phronomy
|
|
|
120
121
|
if expected && stored != expected
|
|
121
122
|
raise Phronomy::Persistence::StateConflictError, "Reserved execution #{@id} correlation mismatch"
|
|
122
123
|
end
|
|
124
|
+
unless @resume_only
|
|
125
|
+
expected_digest = @config[:phronomy_output_contract]&.digest
|
|
126
|
+
if expected_digest != execution.metadata.dig("output_contract", "schema_digest")
|
|
127
|
+
raise Phronomy::Persistence::StateConflictError, "Reserved execution output schema mismatch"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
123
130
|
ref = execution.metadata["current_input_ref"]
|
|
124
131
|
@input = @agent.persistence.contents.fetch_text(ref) if @resume_only && ref
|
|
125
132
|
if ref && @agent.persistence.contents.fetch_text(ref) != @input
|
|
@@ -157,15 +164,19 @@ module Phronomy
|
|
|
157
164
|
))
|
|
158
165
|
end
|
|
159
166
|
end
|
|
167
|
+
@completion.track(task)
|
|
160
168
|
task.on_complete do |result, error|
|
|
161
|
-
error ? @completion.
|
|
169
|
+
error ? @completion.settle(error: error) : @completion.settle(result)
|
|
162
170
|
end
|
|
163
171
|
rescue => error
|
|
164
|
-
@completion.
|
|
172
|
+
@completion.settle(error: error)
|
|
165
173
|
end
|
|
166
174
|
|
|
167
175
|
def materialize(execution)
|
|
168
176
|
result = @agent.persistence.result(execution.execution_id)
|
|
177
|
+
if result[:error].is_a?(Hash) && result[:error]["class"] == "Phronomy::Agent::OutputError"
|
|
178
|
+
raise RecoverySupport.error_from_failure(result[:error])
|
|
179
|
+
end
|
|
169
180
|
result.merge(output: result[:result]).freeze
|
|
170
181
|
end
|
|
171
182
|
end
|
|
@@ -7,7 +7,6 @@ module Phronomy
|
|
|
7
7
|
# @api private
|
|
8
8
|
class ExecutionCancellation
|
|
9
9
|
Command = Data.define(:coordinator, :execution_id, :agent_id)
|
|
10
|
-
private_constant :Command
|
|
11
10
|
|
|
12
11
|
def self.signal(execution_id, agent_id, environment:)
|
|
13
12
|
command = Command.new(coordinator: new(environment), execution_id: execution_id, agent_id: agent_id)
|
|
@@ -22,6 +21,7 @@ module Phronomy
|
|
|
22
21
|
state = @environment.registry.agent_execution_state(command.execution_id)
|
|
23
22
|
return unless state && state.agent.agent_id == command.agent_id
|
|
24
23
|
state.invocation&.config&.fetch(:cancellation_token, nil)&.cancel!
|
|
24
|
+
state.coordinator.deliver_on_event_loop(command)
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -74,6 +74,8 @@ module Phronomy
|
|
|
74
74
|
TerminalCommitReady = Data.define(
|
|
75
75
|
:coordinator, :operation, :delivery, :outcome, :error
|
|
76
76
|
)
|
|
77
|
+
SuspensionCancelledCommand = Data.define(:coordinator, :execution_id)
|
|
78
|
+
|
|
77
79
|
DeferredTerminalCommand = Data.define(
|
|
78
80
|
:coordinator, :execution_id, :result_task, :invocation,
|
|
79
81
|
:source_error, :fsm_session_id
|
|
@@ -93,7 +95,7 @@ module Phronomy
|
|
|
93
95
|
)
|
|
94
96
|
@agent.send(:__assert_live_agent!)
|
|
95
97
|
@agent.send(:_reject_removed_generic_identity_keys!, config)
|
|
96
|
-
result_task =
|
|
98
|
+
result_task = OperationResult.new(agent: @agent, name: "agent-#{@agent.agent_id}-#{mode}")
|
|
97
99
|
controls = Phronomy::InvocationControls.attach(result_task,
|
|
98
100
|
invocation_context: config[:invocation_context],
|
|
99
101
|
cancellation_token: config[:cancellation_token])
|
|
@@ -127,9 +129,8 @@ module Phronomy
|
|
|
127
129
|
)
|
|
128
130
|
@agent.send(:__assert_live_agent!)
|
|
129
131
|
@agent.send(:_reject_removed_generic_identity_keys!, config)
|
|
130
|
-
result_task =
|
|
131
|
-
name: "agent-approval-resume:#{execution_id}"
|
|
132
|
-
)
|
|
132
|
+
result_task = OperationResult.new(agent: @agent,
|
|
133
|
+
name: "agent-approval-resume:#{execution_id}")
|
|
133
134
|
command = ResumeCommand.new(
|
|
134
135
|
coordinator: self,
|
|
135
136
|
execution_id: execution_id.to_s.freeze,
|
|
@@ -255,6 +256,8 @@ module Phronomy
|
|
|
255
256
|
apply_terminal_commit_on_event_loop(command)
|
|
256
257
|
when DeferredTerminalCommand
|
|
257
258
|
resume_deferred_terminal_on_event_loop(command)
|
|
259
|
+
when ExecutionCancellation::Command, SuspensionCancelledCommand
|
|
260
|
+
cancel_suspended_on_event_loop(command.execution_id)
|
|
258
261
|
else
|
|
259
262
|
raise Phronomy::Error, "unknown Agent control command: #{command.class}"
|
|
260
263
|
end
|
|
@@ -269,7 +272,7 @@ module Phronomy
|
|
|
269
272
|
def begin_start_on_event_loop(request)
|
|
270
273
|
environment = @agent.__execution_environment
|
|
271
274
|
request.controls.check_start!
|
|
272
|
-
root = @agent.
|
|
275
|
+
root = @agent.send(:__root_for_operation)
|
|
273
276
|
if root.lifecycle_status == :closed
|
|
274
277
|
deliver_start_failure_on_event_loop(
|
|
275
278
|
request,
|
|
@@ -298,6 +301,7 @@ module Phronomy
|
|
|
298
301
|
@initial_preparation.prepare(operation)
|
|
299
302
|
end
|
|
300
303
|
submitted = true
|
|
304
|
+
request.result_task.track(task) if request.result_task.respond_to?(:track)
|
|
301
305
|
task.on_complete do |result, error|
|
|
302
306
|
ready = InitialPreparationReady.new(
|
|
303
307
|
coordinator: self,
|
|
@@ -428,6 +432,7 @@ module Phronomy
|
|
|
428
432
|
Phronomy::Tracing::Automatic.observe_task(
|
|
429
433
|
request.result_task,
|
|
430
434
|
"agent.execution",
|
|
435
|
+
usage: ->(value) { value.is_a?(Hash) ? value[:usage]&.to_h&.freeze : nil },
|
|
431
436
|
input: request.input,
|
|
432
437
|
agent_id: @agent.agent_id,
|
|
433
438
|
execution_id: prepared.execution.execution_id,
|
|
@@ -519,7 +524,7 @@ module Phronomy
|
|
|
519
524
|
|
|
520
525
|
operation = InitialPreparation::RecoveryCommand.new(
|
|
521
526
|
execution: execution,
|
|
522
|
-
root: @agent.
|
|
527
|
+
root: @agent.send(:__root_for_operation),
|
|
523
528
|
journal_records: @agent.send(:_journal_records_snapshot)
|
|
524
529
|
)
|
|
525
530
|
task = environment.submit(on_full: :raise) do
|
|
@@ -717,7 +722,7 @@ module Phronomy
|
|
|
717
722
|
execution_id: state.execution_id,
|
|
718
723
|
fsm_session_id: fsm_session_id.to_s.freeze,
|
|
719
724
|
expected_execution_revision: state.execution.execution_revision,
|
|
720
|
-
root: @agent.
|
|
725
|
+
root: @agent.send(:__root_for_operation),
|
|
721
726
|
journal_records: @agent.send(:_journal_records_snapshot),
|
|
722
727
|
execution: state.execution,
|
|
723
728
|
base_manifest: state.base_manifest,
|
|
@@ -739,7 +744,7 @@ module Phronomy
|
|
|
739
744
|
execution_id: state.execution_id,
|
|
740
745
|
fsm_session_id: fsm_session_id.to_s.freeze,
|
|
741
746
|
expected_execution_revision: state.execution.execution_revision,
|
|
742
|
-
root: @agent.
|
|
747
|
+
root: @agent.send(:__root_for_operation),
|
|
743
748
|
execution: state.execution,
|
|
744
749
|
runtime_snapshot: invocation.runtime_snapshot,
|
|
745
750
|
tool_batch_snapshot: tool_batch_snapshot,
|
|
@@ -911,16 +916,23 @@ module Phronomy
|
|
|
911
916
|
operation = capture_approval_resume(state, request)
|
|
912
917
|
resume_transition_started = false
|
|
913
918
|
submitted = false
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
+
begin
|
|
920
|
+
environment.registry.mark_agent_execution_admission(
|
|
921
|
+
@agent.agent_id,
|
|
922
|
+
execution_id: state.execution_id,
|
|
923
|
+
state: :resuming,
|
|
924
|
+
expected_state: :suspended
|
|
925
|
+
)
|
|
926
|
+
rescue Phronomy::AgentBusyError => error
|
|
927
|
+
return fail_task(request.result_task, error)
|
|
928
|
+
end
|
|
929
|
+
suspended_cancellation = clear_suspended_cancellation
|
|
919
930
|
resume_transition_started = true
|
|
920
931
|
task = environment.submit(on_full: :raise) do
|
|
921
932
|
@approval_resume_commit.commit(operation)
|
|
922
933
|
end
|
|
923
934
|
submitted = true
|
|
935
|
+
request.result_task.track(task) if request.result_task.respond_to?(:track)
|
|
924
936
|
task.on_complete do |result, error|
|
|
925
937
|
ready = ResumeCommitReady.new(
|
|
926
938
|
coordinator: self,
|
|
@@ -939,6 +951,9 @@ module Phronomy
|
|
|
939
951
|
execution_id: state.execution_id,
|
|
940
952
|
state: submitted ? :recovery_required : :suspended
|
|
941
953
|
)
|
|
954
|
+
if !submitted && suspended_cancellation
|
|
955
|
+
bind_suspended_cancellation(*suspended_cancellation.first(3))
|
|
956
|
+
end
|
|
942
957
|
end
|
|
943
958
|
fail_task(request.result_task, error)
|
|
944
959
|
raise unless resume_transition_started
|
|
@@ -953,7 +968,7 @@ module Phronomy
|
|
|
953
968
|
ResumeCommitCommand.new(
|
|
954
969
|
execution_id: state.execution_id,
|
|
955
970
|
expected_execution_revision: state.execution.execution_revision,
|
|
956
|
-
root: @agent.
|
|
971
|
+
root: @agent.send(:__root_for_operation),
|
|
957
972
|
execution: state.execution,
|
|
958
973
|
approval_request_id: request.approval_request_id,
|
|
959
974
|
approved: request.approved,
|
|
@@ -1042,6 +1057,7 @@ module Phronomy
|
|
|
1042
1057
|
Phronomy::Tracing::Automatic.observe_task(
|
|
1043
1058
|
request.result_task,
|
|
1044
1059
|
"agent.execution",
|
|
1060
|
+
usage: ->(value) { value.is_a?(Hash) ? value[:usage]&.to_h&.freeze : nil },
|
|
1045
1061
|
agent_id: @agent.agent_id,
|
|
1046
1062
|
execution_id: operation.execution_id,
|
|
1047
1063
|
mode: trace_mode,
|
|
@@ -1254,7 +1270,7 @@ module Phronomy
|
|
|
1254
1270
|
|
|
1255
1271
|
operation = build_terminal_operation(
|
|
1256
1272
|
execution: state.execution,
|
|
1257
|
-
root: @agent.
|
|
1273
|
+
root: @agent.send(:__root_for_operation),
|
|
1258
1274
|
journal_records: @agent.send(:_journal_records_snapshot),
|
|
1259
1275
|
invocation: invocation,
|
|
1260
1276
|
source_error: source_error,
|
|
@@ -1338,6 +1354,7 @@ module Phronomy
|
|
|
1338
1354
|
# worker boundary. TaskResult/listener delivery state stays outside it.
|
|
1339
1355
|
@outcome_committer.commit_outcome(operation)
|
|
1340
1356
|
end
|
|
1357
|
+
delivery.result_task.track(task) if delivery.result_task.respond_to?(:track)
|
|
1341
1358
|
task.on_complete do |outcome, error|
|
|
1342
1359
|
ready = TerminalCommitReady.new(
|
|
1343
1360
|
coordinator: self,
|
|
@@ -1371,6 +1388,7 @@ module Phronomy
|
|
|
1371
1388
|
TerminalView.new(
|
|
1372
1389
|
phase: invocation&.phase,
|
|
1373
1390
|
output: invocation&.output,
|
|
1391
|
+
response: invocation&.final_response,
|
|
1374
1392
|
usage: invocation&.usage,
|
|
1375
1393
|
approval_request: invocation&.approval_request,
|
|
1376
1394
|
rejected: invocation ? !!invocation.rejected : false,
|
|
@@ -1440,7 +1458,7 @@ module Phronomy
|
|
|
1440
1458
|
if operation.execution.metadata["reservation"] || operation.execution.metadata["execution_extension"]
|
|
1441
1459
|
release_terminal_ownership(environment, operation.execution_id, state)
|
|
1442
1460
|
environment.registry.take_agent_completion_waiters(operation.execution_id, fallback: delivery.result_task).each do |task|
|
|
1443
|
-
task
|
|
1461
|
+
fail_task(task, ready.error)
|
|
1444
1462
|
end
|
|
1445
1463
|
return
|
|
1446
1464
|
end
|
|
@@ -1477,7 +1495,7 @@ module Phronomy
|
|
|
1477
1495
|
delivery = ready.delivery
|
|
1478
1496
|
outcome = ready.outcome
|
|
1479
1497
|
release_terminal_ownership(environment, operation.execution_id, state)
|
|
1480
|
-
environment.registry.take_agent_completion_waiters(operation.execution_id, fallback: delivery.result_task).each { |task| task
|
|
1498
|
+
environment.registry.take_agent_completion_waiters(operation.execution_id, fallback: delivery.result_task).each { |task| fail_task(task, outcome.error) }
|
|
1481
1499
|
end
|
|
1482
1500
|
|
|
1483
1501
|
def suspend_execution_on_event_loop(ready, state, environment)
|
|
@@ -1489,6 +1507,7 @@ module Phronomy
|
|
|
1489
1507
|
execution_id: operation.execution_id,
|
|
1490
1508
|
state: :suspended
|
|
1491
1509
|
)
|
|
1510
|
+
bind_suspended_cancellation(operation.execution_id, state.invocation, delivery.result_task)
|
|
1492
1511
|
dispatch_approval_listener(delivery.approval_listener, outcome.approval_request)
|
|
1493
1512
|
callback_error = deliver_terminal(
|
|
1494
1513
|
delivery.application_listener,
|
|
@@ -1501,10 +1520,50 @@ module Phronomy
|
|
|
1501
1520
|
outcome.result
|
|
1502
1521
|
)
|
|
1503
1522
|
Array(state&.invocation&.config&.fetch(:phronomy_exact_observers, [])).each do |task|
|
|
1504
|
-
task
|
|
1523
|
+
fail_task(task, Phronomy::ExecutionRehydrationRequiredError.new("Execution #{operation.execution_id} requires approval"))
|
|
1524
|
+
end
|
|
1525
|
+
end
|
|
1526
|
+
|
|
1527
|
+
# Suspension closes the FSM session, but not the logical execution.
|
|
1528
|
+
# Keep cancellation connected until approval resumes or the owner terminates.
|
|
1529
|
+
def bind_suspended_cancellation(execution_id, invocation, result_task)
|
|
1530
|
+
clear_suspended_cancellation
|
|
1531
|
+
subscriptions = Phronomy::Concurrency::Subscriptions.new
|
|
1532
|
+
@suspended_cancellation = [execution_id, invocation, result_task, subscriptions]
|
|
1533
|
+
subscriptions.result(result_task) { subscriptions.close }
|
|
1534
|
+
environment = @agent.__execution_environment
|
|
1535
|
+
subscriptions.cancellation(invocation.config[:cancellation_token]) do
|
|
1536
|
+
post_control(environment, SuspensionCancelledCommand.new(coordinator: self, execution_id: execution_id))
|
|
1505
1537
|
end
|
|
1506
1538
|
end
|
|
1507
1539
|
|
|
1540
|
+
def clear_suspended_cancellation
|
|
1541
|
+
record = @suspended_cancellation
|
|
1542
|
+
@suspended_cancellation = nil
|
|
1543
|
+
record&.last&.close
|
|
1544
|
+
record
|
|
1545
|
+
end
|
|
1546
|
+
|
|
1547
|
+
def cancel_suspended_on_event_loop(execution_id)
|
|
1548
|
+
record = @suspended_cancellation
|
|
1549
|
+
return unless record && record.first == execution_id
|
|
1550
|
+
state = @agent.__execution_environment.registry.agent_execution_state(execution_id)
|
|
1551
|
+
return unless state && state.execution.status == :suspended &&
|
|
1552
|
+
state.invocation.equal?(record[1]) && state.fsm_session_id.nil?
|
|
1553
|
+
|
|
1554
|
+
@agent.__execution_environment.registry.mark_agent_execution_admission(
|
|
1555
|
+
@agent.agent_id, execution_id: execution_id, state: :cancelling,
|
|
1556
|
+
expected_state: :suspended
|
|
1557
|
+
)
|
|
1558
|
+
clear_suspended_cancellation
|
|
1559
|
+
finish_on_event_loop(execution_id, record[2], state.invocation,
|
|
1560
|
+
Phronomy::CancellationError.new("Agent execution cancelled during approval"),
|
|
1561
|
+
fsm_session_id: nil)
|
|
1562
|
+
rescue Phronomy::AgentBusyError
|
|
1563
|
+
# A resume/terminal transition already owns this suspended boundary.
|
|
1564
|
+
nil
|
|
1565
|
+
end
|
|
1566
|
+
|
|
1508
1567
|
def deliver_completed_execution_on_event_loop(ready, state, environment)
|
|
1509
1568
|
operation = ready.operation
|
|
1510
1569
|
delivery = ready.delivery
|
|
@@ -1552,6 +1611,7 @@ module Phronomy
|
|
|
1552
1611
|
|
|
1553
1612
|
def release_terminal_ownership(environment, execution_id, state)
|
|
1554
1613
|
registry = environment.registry
|
|
1614
|
+
clear_suspended_cancellation if @suspended_cancellation&.first == execution_id
|
|
1555
1615
|
registry.release_agent_execution(execution_id) if state
|
|
1556
1616
|
registry.release_agent_execution_admission(@agent.agent_id, execution_id: execution_id)
|
|
1557
1617
|
end
|
|
@@ -1695,6 +1755,7 @@ module Phronomy
|
|
|
1695
1755
|
end
|
|
1696
1756
|
if execution_error
|
|
1697
1757
|
if execution_error.is_a?(Phronomy::CancellationError)
|
|
1758
|
+
result_task.finish_operation! if result_task.respond_to?(:finish_operation!)
|
|
1698
1759
|
result_task.cancel!(execution_error)
|
|
1699
1760
|
else
|
|
1700
1761
|
fail_task(result_task, execution_error)
|
|
@@ -13,8 +13,12 @@ module Phronomy
|
|
|
13
13
|
TerminalView = Data.define(
|
|
14
14
|
:phase, :output, :usage, :approval_request, :rejected,
|
|
15
15
|
:input_blocked, :output_blocked, :block_error,
|
|
16
|
-
:invocation_error, :handoff, :callback_failure, :source_error, :cancel_requested
|
|
17
|
-
)
|
|
16
|
+
:invocation_error, :handoff, :callback_failure, :source_error, :cancel_requested, :response
|
|
17
|
+
) do
|
|
18
|
+
def initialize(response: nil, **values)
|
|
19
|
+
super(**values, response: response)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
18
22
|
Command = Data.define(
|
|
19
23
|
:execution_id, :fsm_session_id, :expected_execution_revision,
|
|
20
24
|
:root, :journal_records, :execution, :runtime_snapshot,
|
|
@@ -36,6 +40,18 @@ module Phronomy
|
|
|
36
40
|
view = operation.terminal_view
|
|
37
41
|
error = view.callback_failure&.to_stream_callback_error || view.source_error ||
|
|
38
42
|
view.block_error || view.invocation_error
|
|
43
|
+
if !error && !view.rejected && !view.handoff && view.phase != :suspended
|
|
44
|
+
begin
|
|
45
|
+
contract = OutputContract.restore(operation.execution, @persistence.contents)
|
|
46
|
+
if contract
|
|
47
|
+
output = contract.validate(view.output, response: view.response, execution_id: operation.execution_id)
|
|
48
|
+
view = view.with(output: output)
|
|
49
|
+
operation = operation.with(terminal_view: view)
|
|
50
|
+
end
|
|
51
|
+
rescue => validation_error
|
|
52
|
+
error = validation_error
|
|
53
|
+
end
|
|
54
|
+
end
|
|
39
55
|
change = ExecutionChange.new(agent: @agent, current: operation.execution,
|
|
40
56
|
root: operation.root, kind: :terminal, terminal: view, config: operation.wiring) do |tx, state, receipt, pending|
|
|
41
57
|
if pending
|
|
@@ -147,7 +163,9 @@ module Phronomy
|
|
|
147
163
|
current = operation.execution
|
|
148
164
|
view = operation.terminal_view
|
|
149
165
|
encoded_records, call_records = encode_runtime_records(tx, operation, context_candidate: true)
|
|
150
|
-
|
|
166
|
+
structured = current.metadata.key?("output_contract") && !view.rejected
|
|
167
|
+
output_ref = tx.contents.put_text(structured ? JSON.generate(view.output) : view.output.to_s)
|
|
168
|
+
output_metadata = structured ? {"result_format" => "json", "raw_output_ref" => tx.contents.put_text(view.response.content)} : {}
|
|
151
169
|
records = current.working_records + encoded_records + completion_records(operation, output_ref)
|
|
152
170
|
completed = current.with(
|
|
153
171
|
status: view.rejected ? :rejected : :completed,
|
|
@@ -156,6 +174,7 @@ module Phronomy
|
|
|
156
174
|
llm_calls: current.llm_calls + call_records,
|
|
157
175
|
approval_request: nil,
|
|
158
176
|
result_ref: output_ref,
|
|
177
|
+
metadata: current.metadata.merge(output_metadata),
|
|
159
178
|
terminal_reason: view.rejected ? "rejected" : "completed"
|
|
160
179
|
)
|
|
161
180
|
[records, completed]
|
|
@@ -187,7 +206,7 @@ module Phronomy
|
|
|
187
206
|
def encode_failure(tx, operation, error)
|
|
188
207
|
current = operation.execution
|
|
189
208
|
encoded_records, call_records = encode_runtime_records(tx, operation, context_candidate: false)
|
|
190
|
-
error_ref = tx.contents.put_json(
|
|
209
|
+
error_ref = tx.contents.put_json(error.is_a?(OutputError) ? error.diagnostic : Phronomy::Error.diagnostic(error))
|
|
191
210
|
terminal_status = ExecutionFailure.status_for(error)
|
|
192
211
|
records = failure_records(operation, encoded_records, terminal_status, error_ref)
|
|
193
212
|
failed = current.with(
|
|
@@ -252,6 +271,7 @@ module Phronomy
|
|
|
252
271
|
def completed_outcome(view, execution, root, appended, messages)
|
|
253
272
|
result = result_base(execution, root).merge(
|
|
254
273
|
output: view.output,
|
|
274
|
+
raw_output: (execution.metadata["result_format"] == "json") ? view.response.content : nil,
|
|
255
275
|
rejected: view.rejected || nil,
|
|
256
276
|
usage: view.usage,
|
|
257
277
|
messages: messages
|
|
@@ -145,7 +145,7 @@ module Phronomy
|
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
# @api private
|
|
148
|
-
def mark_agent_execution_admission(agent_id, execution_id:, state:)
|
|
148
|
+
def mark_agent_execution_admission(agent_id, execution_id:, state:, expected_state: nil)
|
|
149
149
|
assert_event_loop_thread!
|
|
150
150
|
key = agent_id.to_s
|
|
151
151
|
execution_key = execution_id.to_s
|
|
@@ -161,6 +161,10 @@ module Phronomy
|
|
|
161
161
|
unless current.execution_id.to_s == execution_key
|
|
162
162
|
raise Phronomy::Error, "stale Agent admission state update for #{key.inspect}"
|
|
163
163
|
end
|
|
164
|
+
if expected_state && current.state != expected_state
|
|
165
|
+
raise Phronomy::AgentBusyError,
|
|
166
|
+
"Agent #{key.inspect} is #{current.state}, expected #{expected_state}"
|
|
167
|
+
end
|
|
164
168
|
@agent_admissions[key] = AgentAdmission.new(
|
|
165
169
|
agent_id: current.agent_id,
|
|
166
170
|
owner_token: current.owner_token,
|
|
@@ -105,6 +105,13 @@ module Phronomy
|
|
|
105
105
|
def prepare_admitted(input:, config:, execution:, active_root:, journal_records:)
|
|
106
106
|
current_execution = execution
|
|
107
107
|
begin
|
|
108
|
+
# Legacy executions acquire a limit at their first resumed preparation.
|
|
109
|
+
current_execution = execution.with(
|
|
110
|
+
execution_revision: execution.execution_revision,
|
|
111
|
+
metadata: execution.metadata.merge(IterationBudget::LIMIT_KEY =>
|
|
112
|
+
IterationBudget.limit_for(execution, default: @agent.class.max_iterations))
|
|
113
|
+
)
|
|
114
|
+
IterationBudget.check!(current_execution, default: @agent.class.max_iterations)
|
|
108
115
|
filtered_input = filter_input(input, config)
|
|
109
116
|
staged = stage_filtered_input(current_execution, active_root, filtered_input)
|
|
110
117
|
assembler, prepared = prepare_context(staged, active_root, filtered_input, config, journal_records)
|
|
@@ -184,7 +191,8 @@ module Phronomy
|
|
|
184
191
|
active = manifest = manifest_ref = nil
|
|
185
192
|
@persistence.transaction do |tx|
|
|
186
193
|
assert_local_durable_base!(tx, root)
|
|
187
|
-
manifest, manifest_ref = Phronomy::Context::Assembly.new.store(prepared, contents: tx.contents
|
|
194
|
+
manifest, manifest_ref = Phronomy::Context::Assembly.new.store(prepared, contents: tx.contents,
|
|
195
|
+
response_schema_ref: staged.metadata.dig("output_contract", "schema_ref"))
|
|
188
196
|
active = execution_with_initial_manifest(staged, manifest_ref)
|
|
189
197
|
tx.executions.save(
|
|
190
198
|
staged.execution_id,
|
|
@@ -222,6 +230,7 @@ module Phronomy
|
|
|
222
230
|
def initial_preparation_metadata(mode, replayable)
|
|
223
231
|
{
|
|
224
232
|
"preparation_replayable" => !!replayable,
|
|
233
|
+
IterationBudget::LIMIT_KEY => IterationBudget.validate_limit(@agent.class.max_iterations),
|
|
225
234
|
ExecutionMetadata::CONTRACT_VERSION_KEY => ExecutionMetadata::CONTRACT_VERSION,
|
|
226
235
|
ExecutionMetadata::INVOCATION_MODE_KEY => mode.to_s,
|
|
227
236
|
ExecutionMetadata::PENDING_LLM_ID_KEY => SecureRandom.uuid.to_s.freeze,
|
|
@@ -262,10 +271,7 @@ module Phronomy
|
|
|
262
271
|
translated_error = error
|
|
263
272
|
failed = next_root = appended = nil
|
|
264
273
|
@persistence.transaction do |tx|
|
|
265
|
-
error_ref = tx.contents.put_json(
|
|
266
|
-
"class" => translated_error.class.name,
|
|
267
|
-
"message" => translated_error.message
|
|
268
|
-
)
|
|
274
|
+
error_ref = tx.contents.put_json(Phronomy::Error.diagnostic(translated_error))
|
|
269
275
|
status = ExecutionFailure.status_for(translated_error)
|
|
270
276
|
records = preparation_failure_records(execution, root, status, error_ref)
|
|
271
277
|
appended = tx.journals.append(
|
|
@@ -197,7 +197,7 @@ module Phronomy
|
|
|
197
197
|
|
|
198
198
|
def self.finish_provider_trace(trace_handle, response, error)
|
|
199
199
|
Phronomy::Tracing::Automatic.finish(trace_handle,
|
|
200
|
-
output: response, usage: response&.usage, error: error)
|
|
200
|
+
output: response, usage: response&.usage&.to_h&.freeze, error: error)
|
|
201
201
|
end
|
|
202
202
|
private_class_method :finish_provider_trace
|
|
203
203
|
|
|
@@ -25,6 +25,10 @@ module Phronomy
|
|
|
25
25
|
recording_tool_results suspended output_filtering handed_off completed blocked failed
|
|
26
26
|
].freeze
|
|
27
27
|
|
|
28
|
+
# An automatic chain cannot visit more phases than an acyclic traversal.
|
|
29
|
+
# Logical LLM turns are bounded independently by IterationBudget.
|
|
30
|
+
AUTOMATIC_RECURSION_LIMIT = DECLARED_STATES.length
|
|
31
|
+
|
|
28
32
|
WAIT_STATES = %i[suspended].freeze
|
|
29
33
|
|
|
30
34
|
TOOL_EVENTS = %i[
|
|
@@ -115,10 +119,6 @@ module Phronomy
|
|
|
115
119
|
guard = definition[:guard]
|
|
116
120
|
!guard || !!(context && guard.call(context))
|
|
117
121
|
end
|
|
118
|
-
|
|
119
|
-
def self.recursion_limit(max_iterations)
|
|
120
|
-
12 + ((max_iterations || 10) * 8)
|
|
121
|
-
end
|
|
122
122
|
end
|
|
123
123
|
end
|
|
124
124
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Agent
|
|
5
|
+
# Agent policy over existing execution facts. No Engine steps, provider
|
|
6
|
+
# retries, Tool events or context-selected messages contribute to this count.
|
|
7
|
+
# @api private
|
|
8
|
+
module IterationBudget
|
|
9
|
+
LIMIT_KEY = "agent_max_iterations"
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def validate_limit(value)
|
|
14
|
+
unless value.is_a?(Integer) && value >= 0
|
|
15
|
+
raise ArgumentError, "Agent max_iterations must be a non-negative Integer"
|
|
16
|
+
end
|
|
17
|
+
value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def limit_for(execution, default:)
|
|
21
|
+
validate_limit(execution.metadata.fetch(LIMIT_KEY) { default })
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check!(execution, default:)
|
|
25
|
+
limit = limit_for(execution, default: default)
|
|
26
|
+
completed = execution.llm_calls.length
|
|
27
|
+
if completed >= limit
|
|
28
|
+
raise Phronomy::RecursionLimitError,
|
|
29
|
+
"Agent max_iterations (#{limit}) exceeded after #{completed} LLM calls"
|
|
30
|
+
end
|
|
31
|
+
limit
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -29,10 +29,7 @@ module Phronomy
|
|
|
29
29
|
output_ref = assistant_output_ref(tx, outcome)
|
|
30
30
|
assistant_ref = assistant_message_ref(tx, outcome)
|
|
31
31
|
error_ref = if call_error
|
|
32
|
-
tx.contents.put_json(
|
|
33
|
-
"class" => call_error.class.name,
|
|
34
|
-
"message" => call_error.message
|
|
35
|
-
)
|
|
32
|
+
tx.contents.put_json(Phronomy::Error.diagnostic(call_error))
|
|
36
33
|
end
|
|
37
34
|
usage_ref = if outcome && outcome.usage.to_h.values.any? { |value| !value.nil? }
|
|
38
35
|
tx.contents.put_json(json_value(outcome.usage.to_h))
|
|
@@ -172,6 +169,9 @@ module Phronomy
|
|
|
172
169
|
"role" => "assistant",
|
|
173
170
|
"content" => json_value(outcome.content),
|
|
174
171
|
"tool_calls" => json_value(Array(outcome.tool_calls)),
|
|
172
|
+
"finish_reason" => outcome.finish_reason.to_s,
|
|
173
|
+
"refusal" => outcome.refusal,
|
|
174
|
+
"metadata" => outcome.metadata,
|
|
175
175
|
"model_id" => outcome.metadata["model_id"]
|
|
176
176
|
}.compact
|
|
177
177
|
tx.contents.put_json(payload)
|
|
@@ -5,14 +5,14 @@ module Phronomy
|
|
|
5
5
|
# Stable domain observation; does not expose repositories or FSM phases.
|
|
6
6
|
# @api public
|
|
7
7
|
ExecutionObservation = Data.define(:agent_id, :execution_id, :status, :result,
|
|
8
|
-
:error, :reservation, :transfer_receipt, :cancellation_requested) do
|
|
9
|
-
def initialize(**values)
|
|
10
|
-
super(**values.transform_values { |value| Phronomy::Values::Immutable.copy(value) })
|
|
8
|
+
:error, :reservation, :transfer_receipt, :cancellation_requested, :raw_output) do
|
|
9
|
+
def initialize(raw_output: nil, **values)
|
|
10
|
+
super(raw_output: Phronomy::Values::Immutable.copy(raw_output), **values.transform_values { |value| Phronomy::Values::Immutable.copy(value) })
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def self.from_result(value)
|
|
14
14
|
new(agent_id: value.fetch(:agent_id), execution_id: value.fetch(:execution_id), status: value.fetch(:status),
|
|
15
|
-
result: value[:result], error: value[:error], reservation: nil, transfer_receipt: nil, cancellation_requested: false)
|
|
15
|
+
result: value[:result], raw_output: value[:raw_output], error: value[:error], reservation: nil, transfer_receipt: nil, cancellation_requested: false)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def absent? = status == :absent
|
|
@@ -28,7 +28,7 @@ module Phronomy
|
|
|
28
28
|
|
|
29
29
|
def to_result
|
|
30
30
|
Phronomy::Values::Immutable.copy(execution_id: execution_id, agent_id: agent_id,
|
|
31
|
-
status: status, result: result, output: result, error: error)
|
|
31
|
+
status: status, result: result, output: result, raw_output: raw_output, error: error)
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
end
|