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
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
module Agent
|
|
5
5
|
StreamEvent = Data.define(:type, :payload)
|
|
6
|
+
|
|
7
|
+
# Own Agents acquired by the block, then attempt every release. A returned
|
|
8
|
+
# TaskResult is awaited and unwrapped; a plain value is returned unchanged.
|
|
9
|
+
# @yield [scope] the Runtime-bound owner of newly materialized Agents
|
|
10
|
+
# @yieldparam scope [Phronomy::Agent::Scope]
|
|
11
|
+
# @return [Object] the block value or resolved TaskResult value
|
|
12
|
+
# @raise [ScopeCleanupError] successful work whose Agent release failed
|
|
13
|
+
# @api public
|
|
14
|
+
def self.open_scope(&block)
|
|
15
|
+
Scope.send(:open, &block)
|
|
16
|
+
end
|
|
6
17
|
end
|
|
7
18
|
end
|
|
8
19
|
|
|
@@ -15,3 +26,5 @@ end
|
|
|
15
26
|
# Durable Agent Recovery and Agent-incarnation event binding.
|
|
16
27
|
require_relative "../recovery/recovery_support"
|
|
17
28
|
require_relative "../recovery/recovery_coordinator"
|
|
29
|
+
|
|
30
|
+
Phronomy::Agent::Base.prepend(Phronomy::Agent::LifetimeApi)
|
|
@@ -12,32 +12,40 @@ module Phronomy
|
|
|
12
12
|
# - approval notifications share the Agent listener as :approval_required;
|
|
13
13
|
# - Recovery resolution uses resolve/resolve_async.
|
|
14
14
|
module AsyncEventApi
|
|
15
|
-
def start_reserved_async(reservation:, input:, config: {})
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
def start_reserved_async(reservation:, input:, config: {}, output_schema: nil)
|
|
16
|
+
__with_operation(async: true) do
|
|
17
|
+
unless reservation.is_a?(ReservedExecution) && reservation.agent_id == agent_id
|
|
18
|
+
raise ArgumentError, "reservation must belong to this Agent"
|
|
19
|
+
end
|
|
20
|
+
config = __invocation_config(_prepare_output_config(config, nil, output_schema))
|
|
21
|
+
ExactExecution.start(agent: self, execution_id: reservation.execution_id, input: input,
|
|
22
|
+
config: config.merge(phronomy_reservation: reservation.correlation))
|
|
18
23
|
end
|
|
19
|
-
config = __invocation_config(_prepare_invocation_config(config, nil))
|
|
20
|
-
ExactExecution.start(agent: self, execution_id: reservation.execution_id, input: input,
|
|
21
|
-
config: config.merge(phronomy_reservation: reservation.correlation))
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def resume_async(execution_id, config: {})
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
__with_operation(async: true) do
|
|
28
|
+
config = __invocation_config(_prepare_invocation_config(config, nil))
|
|
29
|
+
ExactExecution.start(agent: self, execution_id: execution_id, input: nil, config: config, resume_only: true)
|
|
30
|
+
end
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def resume(execution_id, config: {})
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
__with_operation do
|
|
35
|
+
_check_event_loop_reentrancy(:resume, :resume_async)
|
|
36
|
+
resume_async(execution_id, config: config).wait_result
|
|
37
|
+
end
|
|
32
38
|
end
|
|
33
39
|
|
|
34
40
|
def cancel_async(execution_id)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
__with_operation(async: true) do
|
|
42
|
+
__assert_live_agent!
|
|
43
|
+
environment = __execution_environment
|
|
44
|
+
environment.submit(on_full: :raise) do
|
|
45
|
+
requested = persistence.request_cancellation(agent_id: agent_id, execution_id: execution_id)
|
|
46
|
+
ExecutionCancellation.signal(execution_id, agent_id, environment: environment) if requested
|
|
47
|
+
persistence.observe_execution(agent_id: agent_id, execution_id: execution_id)
|
|
48
|
+
end
|
|
41
49
|
end
|
|
42
50
|
end
|
|
43
51
|
|
|
@@ -45,98 +53,114 @@ module Phronomy
|
|
|
45
53
|
input,
|
|
46
54
|
config: {},
|
|
47
55
|
invocation_context: nil,
|
|
56
|
+
output_schema: nil,
|
|
48
57
|
**removed_options,
|
|
49
58
|
&removed_block
|
|
50
59
|
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
__with_operation do
|
|
61
|
+
_reject_removed_invocation_listener_arguments!(removed_options, removed_block)
|
|
62
|
+
config = _prepare_output_config(config, invocation_context, output_schema)
|
|
63
|
+
_check_event_loop_reentrancy(:invoke, :invoke_async)
|
|
64
|
+
_start_agent_operation(
|
|
65
|
+
input,
|
|
66
|
+
config: config,
|
|
67
|
+
mode: :invoke,
|
|
68
|
+
listener: _phronomy_event_listener
|
|
69
|
+
).wait_result
|
|
70
|
+
end
|
|
60
71
|
end
|
|
61
72
|
|
|
62
73
|
def invoke_async(
|
|
63
74
|
input,
|
|
64
75
|
config: {},
|
|
65
76
|
invocation_context: nil,
|
|
77
|
+
output_schema: nil,
|
|
66
78
|
**removed_options,
|
|
67
79
|
&removed_block
|
|
68
80
|
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
__with_operation(async: true) do
|
|
82
|
+
_reject_removed_invocation_listener_arguments!(removed_options, removed_block)
|
|
83
|
+
config = _prepare_output_config(config, invocation_context, output_schema)
|
|
84
|
+
_start_agent_operation(
|
|
85
|
+
input,
|
|
86
|
+
config: config,
|
|
87
|
+
mode: :invoke,
|
|
88
|
+
listener: _phronomy_event_listener
|
|
89
|
+
)
|
|
90
|
+
end
|
|
77
91
|
end
|
|
78
92
|
|
|
79
93
|
def stream(
|
|
80
94
|
input,
|
|
81
95
|
config: {},
|
|
82
96
|
invocation_context: nil,
|
|
97
|
+
output_schema: nil,
|
|
83
98
|
**removed_options,
|
|
84
99
|
&removed_block
|
|
85
100
|
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
__with_operation do
|
|
102
|
+
_reject_removed_invocation_listener_arguments!(removed_options, removed_block)
|
|
103
|
+
listener = _required_stream_listener!(:stream)
|
|
104
|
+
config = _prepare_output_config(config, invocation_context, output_schema)
|
|
105
|
+
_check_event_loop_reentrancy(:stream, :stream_async)
|
|
106
|
+
_start_agent_operation(
|
|
107
|
+
input,
|
|
108
|
+
config: config,
|
|
109
|
+
mode: :stream,
|
|
110
|
+
listener: listener
|
|
111
|
+
).wait_result
|
|
112
|
+
end
|
|
96
113
|
end
|
|
97
114
|
|
|
98
115
|
def stream_async(
|
|
99
116
|
input,
|
|
100
117
|
config: {},
|
|
101
118
|
invocation_context: nil,
|
|
119
|
+
output_schema: nil,
|
|
102
120
|
**removed_options,
|
|
103
121
|
&removed_block
|
|
104
122
|
)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
123
|
+
__with_operation(async: true) do
|
|
124
|
+
_reject_removed_invocation_listener_arguments!(removed_options, removed_block)
|
|
125
|
+
listener = _required_stream_listener!(:stream_async)
|
|
126
|
+
config = _prepare_output_config(config, invocation_context, output_schema)
|
|
127
|
+
_start_agent_operation(
|
|
128
|
+
input,
|
|
129
|
+
config: config,
|
|
130
|
+
mode: :stream,
|
|
131
|
+
listener: listener
|
|
132
|
+
)
|
|
133
|
+
end
|
|
114
134
|
end
|
|
115
135
|
|
|
116
136
|
def approve(execution_id, approval_request_id:, approved: true, config: {})
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
__with_operation do
|
|
138
|
+
_check_event_loop_reentrancy(:approve, :approve_async)
|
|
139
|
+
approve_async(
|
|
140
|
+
execution_id,
|
|
141
|
+
approval_request_id: approval_request_id,
|
|
142
|
+
approved: approved,
|
|
143
|
+
config: config
|
|
144
|
+
).wait_result
|
|
145
|
+
end
|
|
124
146
|
end
|
|
125
147
|
|
|
126
148
|
def approve_async(execution_id, approval_request_id:, approved: true, config: {})
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
owner
|
|
131
|
-
|
|
132
|
-
|
|
149
|
+
__with_operation(async: true) do
|
|
150
|
+
config = _prepare_invocation_config(config, nil)
|
|
151
|
+
owner = __execution_environment.existing_registry&.agent_execution_owner(execution_id)
|
|
152
|
+
coordinator = if owner&.agent&.equal?(self)
|
|
153
|
+
owner.coordinator
|
|
154
|
+
else
|
|
155
|
+
execution_coordinator_for(config)
|
|
156
|
+
end
|
|
157
|
+
coordinator.resume(
|
|
158
|
+
execution_id,
|
|
159
|
+
approval_request_id: approval_request_id,
|
|
160
|
+
approved: approved,
|
|
161
|
+
config: config
|
|
162
|
+
)
|
|
133
163
|
end
|
|
134
|
-
coordinator.resume(
|
|
135
|
-
execution_id,
|
|
136
|
-
approval_request_id: approval_request_id,
|
|
137
|
-
approved: approved,
|
|
138
|
-
config: config
|
|
139
|
-
)
|
|
140
164
|
end
|
|
141
165
|
|
|
142
166
|
def resolve(
|
|
@@ -147,15 +171,17 @@ module Phronomy
|
|
|
147
171
|
result: Phronomy::Agent::RecoveryRules::MISSING,
|
|
148
172
|
error: Phronomy::Agent::RecoveryRules::MISSING
|
|
149
173
|
)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
174
|
+
__with_operation do
|
|
175
|
+
_check_event_loop_reentrancy(:resolve, :resolve_async)
|
|
176
|
+
resolve_async(
|
|
177
|
+
execution_id,
|
|
178
|
+
expected_execution_revision: expected_execution_revision,
|
|
179
|
+
subject: subject,
|
|
180
|
+
outcome: outcome,
|
|
181
|
+
result: result,
|
|
182
|
+
error: error
|
|
183
|
+
).wait_result
|
|
184
|
+
end
|
|
159
185
|
end
|
|
160
186
|
|
|
161
187
|
def resolve_async(
|
|
@@ -166,18 +192,25 @@ module Phronomy
|
|
|
166
192
|
result: Phronomy::Agent::RecoveryRules::MISSING,
|
|
167
193
|
error: Phronomy::Agent::RecoveryRules::MISSING
|
|
168
194
|
)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
195
|
+
__with_operation(async: true) do
|
|
196
|
+
Phronomy::Agent::RecoveryCoordinator.new(self).resolve(
|
|
197
|
+
execution_id,
|
|
198
|
+
expected_execution_revision: expected_execution_revision,
|
|
199
|
+
subject: subject,
|
|
200
|
+
outcome: outcome,
|
|
201
|
+
result: result,
|
|
202
|
+
error: error
|
|
203
|
+
)
|
|
204
|
+
end
|
|
177
205
|
end
|
|
178
206
|
|
|
179
207
|
private
|
|
180
208
|
|
|
209
|
+
def _prepare_output_config(config, invocation_context, schema)
|
|
210
|
+
config = _prepare_invocation_config(config, invocation_context)
|
|
211
|
+
schema.nil? ? config : config.merge(phronomy_output_contract: OutputContract.new(schema))
|
|
212
|
+
end
|
|
213
|
+
|
|
181
214
|
def _start_agent_operation(input, config:, mode:, listener:)
|
|
182
215
|
config = __invocation_config(_snapshot_durable_context(config))
|
|
183
216
|
@_phronomy_execution_wiring = config.dup.freeze
|
data/lib/phronomy/agent/base.rb
CHANGED
|
@@ -101,6 +101,9 @@ module Phronomy
|
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# Maximum logical LLM calls per execution, including the first call.
|
|
105
|
+
# Tool calls and SDK transport retries do not consume extra iterations.
|
|
106
|
+
# The limit is captured at admission; zero admits no LLM call.
|
|
104
107
|
def max_iterations(val = nil)
|
|
105
108
|
if val
|
|
106
109
|
@max_iterations = val
|
|
@@ -292,10 +295,14 @@ module Phronomy
|
|
|
292
295
|
# @api private
|
|
293
296
|
def __invocation_config(config) = config
|
|
294
297
|
|
|
295
|
-
def knowledge_snapshot
|
|
298
|
+
def knowledge_snapshot
|
|
299
|
+
__with_operation { persistence.knowledge_snapshot(agent_id: agent_id) }
|
|
300
|
+
end
|
|
296
301
|
|
|
297
302
|
# Listener inheritance is an explicit orchestration choice, not a private getter.
|
|
298
|
-
def event_listener
|
|
303
|
+
def event_listener
|
|
304
|
+
__with_operation(read_only: true) { _phronomy_event_listener }
|
|
305
|
+
end
|
|
299
306
|
|
|
300
307
|
attr_reader :agent_id, :persistence
|
|
301
308
|
|
|
@@ -371,65 +378,81 @@ module Phronomy
|
|
|
371
378
|
end
|
|
372
379
|
|
|
373
380
|
def agent_root
|
|
374
|
-
|
|
375
|
-
|
|
381
|
+
__with_operation(read_only: true) do
|
|
382
|
+
__assert_agent_accessible!
|
|
383
|
+
@root
|
|
384
|
+
end
|
|
376
385
|
end
|
|
377
386
|
|
|
378
387
|
def journal_projection
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
388
|
+
__with_operation(read_only: true) do
|
|
389
|
+
__assert_agent_accessible!
|
|
390
|
+
Agent::JournalProjection.new(
|
|
391
|
+
agent_root: @root,
|
|
392
|
+
records: _journal_records_snapshot
|
|
393
|
+
)
|
|
394
|
+
end
|
|
384
395
|
end
|
|
385
396
|
|
|
386
397
|
def transcript
|
|
387
|
-
|
|
398
|
+
__with_operation(read_only: true) do
|
|
399
|
+
journal_projection.transcript_records
|
|
400
|
+
end
|
|
388
401
|
end
|
|
389
402
|
|
|
390
403
|
def clear_transcript!
|
|
391
|
-
|
|
392
|
-
root
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
404
|
+
__with_operation do
|
|
405
|
+
mutate_context!(:transcript_cleared) do |root|
|
|
406
|
+
root.with(
|
|
407
|
+
agent_revision: root.agent_revision + 1,
|
|
408
|
+
context_revision: root.context_revision + 1,
|
|
409
|
+
transcript_generation: root.transcript_generation + 1
|
|
410
|
+
)
|
|
411
|
+
end
|
|
397
412
|
end
|
|
398
413
|
end
|
|
399
414
|
|
|
400
415
|
def clear_knowledge!
|
|
401
|
-
|
|
402
|
-
root
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
416
|
+
__with_operation do
|
|
417
|
+
mutate_context!(:knowledge_cleared) do |root|
|
|
418
|
+
root.with(
|
|
419
|
+
agent_revision: root.agent_revision + 1,
|
|
420
|
+
context_revision: root.context_revision + 1
|
|
421
|
+
)
|
|
422
|
+
end
|
|
406
423
|
end
|
|
407
424
|
end
|
|
408
425
|
|
|
409
426
|
def add_knowledge(content, metadata: {})
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
427
|
+
__with_operation do
|
|
428
|
+
__assert_live_agent!
|
|
429
|
+
current = agent_root
|
|
430
|
+
update = state_writer.add_knowledge(root: current, content: content, metadata: metadata)
|
|
431
|
+
apply_state_update(update)
|
|
432
|
+
self
|
|
433
|
+
end
|
|
415
434
|
end
|
|
416
435
|
|
|
417
436
|
def reset_context!
|
|
418
|
-
|
|
419
|
-
root
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
437
|
+
__with_operation do
|
|
438
|
+
mutate_context!(:context_reset) do |root|
|
|
439
|
+
root.with(
|
|
440
|
+
agent_revision: root.agent_revision + 1,
|
|
441
|
+
context_revision: root.context_revision + 1,
|
|
442
|
+
transcript_generation: root.transcript_generation + 1
|
|
443
|
+
)
|
|
444
|
+
end
|
|
424
445
|
end
|
|
425
446
|
end
|
|
426
447
|
|
|
427
448
|
def close!
|
|
428
|
-
|
|
429
|
-
root
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
449
|
+
__with_operation do
|
|
450
|
+
mutate_context!(:agent_closed, context_affecting: false) do |root|
|
|
451
|
+
root.with(
|
|
452
|
+
agent_revision: root.agent_revision + 1,
|
|
453
|
+
lifecycle_status: :closed
|
|
454
|
+
)
|
|
455
|
+
end
|
|
433
456
|
end
|
|
434
457
|
end
|
|
435
458
|
|
|
@@ -544,7 +567,7 @@ module Phronomy
|
|
|
544
567
|
|
|
545
568
|
# @api private
|
|
546
569
|
def __release_runtime_owner!(runtime)
|
|
547
|
-
return self if @_phronomy_runtime_owner_state
|
|
570
|
+
return self if %i[purged unloaded].include?(@_phronomy_runtime_owner_state)
|
|
548
571
|
return self unless @_phronomy_runtime_owner.equal?(runtime)
|
|
549
572
|
|
|
550
573
|
@_phronomy_runtime_owner_state = :released
|
|
@@ -579,7 +602,12 @@ module Phronomy
|
|
|
579
602
|
if load_existing
|
|
580
603
|
root = records = nil
|
|
581
604
|
@persistence.transaction do |tx|
|
|
582
|
-
|
|
605
|
+
begin
|
|
606
|
+
root = tx.agents.load(@agent_id)
|
|
607
|
+
rescue Phronomy::Persistence::NotFoundError => error
|
|
608
|
+
raise if error.is_a?(Phronomy::AgentNotFoundError)
|
|
609
|
+
raise Phronomy::AgentNotFoundError.new(agent_id: @agent_id), cause: error
|
|
610
|
+
end
|
|
583
611
|
records = tx.journals.read(
|
|
584
612
|
@agent_id,
|
|
585
613
|
limit: root.journal_position
|
|
@@ -656,11 +684,13 @@ module Phronomy
|
|
|
656
684
|
public
|
|
657
685
|
|
|
658
686
|
def tool_approval_policy(&block)
|
|
659
|
-
|
|
660
|
-
|
|
687
|
+
__with_operation do
|
|
688
|
+
__assert_live_agent!
|
|
689
|
+
raise ArgumentError, "tool_approval_policy requires a block" unless block
|
|
661
690
|
|
|
662
|
-
|
|
663
|
-
|
|
691
|
+
_approval_configuration_mutex.synchronize { @tool_approval_policy = block }
|
|
692
|
+
self
|
|
693
|
+
end
|
|
664
694
|
end
|
|
665
695
|
|
|
666
696
|
private
|
|
@@ -671,6 +701,8 @@ module Phronomy
|
|
|
671
701
|
true
|
|
672
702
|
when :purged
|
|
673
703
|
raise Phronomy::AgentPurgedError, "Agent #{agent_id.inspect} has been purged"
|
|
704
|
+
when :unloaded
|
|
705
|
+
raise Phronomy::AgentUnloadedError.new(agent_id: agent_id)
|
|
674
706
|
when :purging
|
|
675
707
|
raise Phronomy::Error, "Agent #{agent_id.inspect} is being purged"
|
|
676
708
|
when :recovery_required
|
|
@@ -698,6 +730,9 @@ module Phronomy
|
|
|
698
730
|
def _prepare_invocation_config(config, invocation_context)
|
|
699
731
|
__assert_live_agent!
|
|
700
732
|
_reject_removed_generic_identity_keys!(config)
|
|
733
|
+
if config.keys.any? { |key| %w[output_schema response_schema phronomy_output_contract].include?(key.to_s) }
|
|
734
|
+
raise ArgumentError, "Use the output_schema keyword on a new Agent execution"
|
|
735
|
+
end
|
|
701
736
|
invocation_context ?
|
|
702
737
|
config.merge(invocation_context: invocation_context) : config
|
|
703
738
|
|
|
@@ -728,10 +763,12 @@ module Phronomy
|
|
|
728
763
|
end
|
|
729
764
|
|
|
730
765
|
def _complete_result_task(task, result)
|
|
766
|
+
task.finish_operation! if task.respond_to?(:finish_operation!)
|
|
731
767
|
task.complete(result)
|
|
732
768
|
end
|
|
733
769
|
|
|
734
770
|
def _fail_result_task(task, error)
|
|
771
|
+
task.finish_operation! if task.respond_to?(:finish_operation!)
|
|
735
772
|
task.fail(error)
|
|
736
773
|
end
|
|
737
774
|
|
|
@@ -840,7 +877,8 @@ module Phronomy
|
|
|
840
877
|
definitions = Phronomy::Tool::DefinitionSet.build(tools: invocation.tools.values).definitions
|
|
841
878
|
Phronomy::LLMAdapter::Request.new(
|
|
842
879
|
model_config: projection.model_config, system: projection.system,
|
|
843
|
-
messages: invocation.messages, tools: definitions, message: projection.ask_message
|
|
880
|
+
messages: invocation.messages, tools: definitions, message: projection.ask_message,
|
|
881
|
+
response_schema: projection.response_schema
|
|
844
882
|
)
|
|
845
883
|
end
|
|
846
884
|
|
|
@@ -19,7 +19,13 @@ module Phronomy
|
|
|
19
19
|
def _before_llm_input = @before_llm_input
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
def before_llm_input
|
|
23
|
+
__with_operation(read_only: true) { @before_llm_input }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def before_llm_input=(value)
|
|
27
|
+
__with_operation { @before_llm_input = value }
|
|
28
|
+
end
|
|
23
29
|
|
|
24
30
|
private
|
|
25
31
|
|
|
@@ -116,6 +116,10 @@ module Phronomy
|
|
|
116
116
|
"content" => normalize(content),
|
|
117
117
|
"tool_calls" => calls
|
|
118
118
|
}
|
|
119
|
+
metadata = read_optional(message, :metadata) || {}
|
|
120
|
+
raise ArgumentError, "imported assistant metadata must be a Hash" unless metadata.is_a?(Hash)
|
|
121
|
+
Phronomy::Values::Immutable.validate_canonical_json!(metadata, label: "imported assistant metadata")
|
|
122
|
+
payload["metadata"] = Phronomy::Values::Immutable.copy(metadata) unless metadata.empty?
|
|
119
123
|
model_id = read_optional(message, :model_id)
|
|
120
124
|
payload["model_id"] = model_id.to_s if model_id
|
|
121
125
|
|
|
@@ -46,7 +46,8 @@ module Phronomy
|
|
|
46
46
|
:rejected,
|
|
47
47
|
:error
|
|
48
48
|
|
|
49
|
-
attr_reader :
|
|
49
|
+
attr_reader :final_response,
|
|
50
|
+
:execution_id,
|
|
50
51
|
:agent,
|
|
51
52
|
:config,
|
|
52
53
|
:approval_policy,
|
|
@@ -73,7 +74,9 @@ module Phronomy
|
|
|
73
74
|
@agent = agent
|
|
74
75
|
@input = input
|
|
75
76
|
# The caller's Hash must not become a shared mutable Runtime object.
|
|
76
|
-
|
|
77
|
+
# OutputContract is admission-only; the durable Manifest owns subsequent
|
|
78
|
+
# schema projection. Never forward the parent promise to an Agent Tool.
|
|
79
|
+
@config = config.except(:phronomy_output_contract)
|
|
77
80
|
resolved_execution_id = execution_id || @config[:execution_id]
|
|
78
81
|
@execution_id = resolved_execution_id&.to_s&.freeze
|
|
79
82
|
invocation_context = @config[:invocation_context]
|
|
@@ -269,6 +272,7 @@ module Phronomy
|
|
|
269
272
|
raise Phronomy::Error, "LLM operation completed without a response" unless response
|
|
270
273
|
|
|
271
274
|
@user_message_sent = true
|
|
275
|
+
@final_response = response
|
|
272
276
|
@output = response.content
|
|
273
277
|
@usage = response.usage
|
|
274
278
|
@pending_tool_calls = []
|
|
@@ -34,6 +34,7 @@ module Phronomy
|
|
|
34
34
|
def prepare_provider(operation)
|
|
35
35
|
staged = stage_provider_call(operation)
|
|
36
36
|
recorded = encode_provider_records(operation, staged)
|
|
37
|
+
IterationBudget.check!(recorded, default: @agent.class.max_iterations)
|
|
37
38
|
assembler, prepared = prepare_provider_context(operation, recorded)
|
|
38
39
|
committed, manifest, manifest_ref = commit_provider_preparation(
|
|
39
40
|
operation, recorded, assembler, prepared
|
|
@@ -56,6 +57,9 @@ module Phronomy
|
|
|
56
57
|
|
|
57
58
|
def stage_provider_call(operation)
|
|
58
59
|
metadata = operation.execution.metadata.dup
|
|
60
|
+
metadata[IterationBudget::LIMIT_KEY] = IterationBudget.limit_for(
|
|
61
|
+
operation.execution, default: @agent.class.max_iterations
|
|
62
|
+
)
|
|
59
63
|
metadata.delete(ExecutionMetadata::TOOL_BATCH_METADATA_KEY)
|
|
60
64
|
metadata.delete(ExecutionMetadata::RECOVERY_METADATA_KEY)
|
|
61
65
|
metadata[ExecutionMetadata::PENDING_LLM_ID_KEY] = operation.pending_llm_call_id
|
|
@@ -118,7 +122,8 @@ module Phronomy
|
|
|
118
122
|
def commit_provider_preparation(operation, recorded, assembler, prepared)
|
|
119
123
|
manifest, manifest_ref = @persistence.transaction do |tx|
|
|
120
124
|
assert_local_durable_base!(tx, operation.root)
|
|
121
|
-
Phronomy::Context::Assembly.new.store(prepared, contents: tx.contents
|
|
125
|
+
Phronomy::Context::Assembly.new.store(prepared, contents: tx.contents,
|
|
126
|
+
response_schema_ref: recorded.metadata.dig("output_contract", "schema_ref"))
|
|
122
127
|
end
|
|
123
128
|
updated = execution_with_provider_manifest(recorded, manifest_ref)
|
|
124
129
|
change = ExecutionChange.new(agent: @agent, current: operation.execution,
|