phronomy 0.15.1 → 0.17.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/.mutant.yml +8 -9
- data/CHANGELOG.md +159 -28
- data/CONTRIBUTING.md +28 -16
- data/README.md +400 -143
- data/benchmark/baseline.json +2 -3
- data/benchmark/bench_agent_invoke.rb +7 -4
- data/benchmark/bench_context_assembler.rb +134 -34
- data/benchmark/bench_regression.rb +3 -19
- data/benchmark/bench_tool_schema.rb +2 -34
- data/docs/decisions/005-static-knowledge-class-level-cache.md +12 -1
- data/docs/decisions/010-cooperative-first-concurrency.md +7 -0
- data/docs/decisions/011-build-context-as-single-llm-input-authority.md +40 -1
- data/docs/decisions/012-canonical-execution-log-and-context-policy.md +69 -0
- data/docs/decisions/013-journal-backed-knowledge-as-context-candidates.md +122 -0
- data/lib/phronomy/agent/activation_registry.rb +28 -0
- data/lib/phronomy/agent/agent_execution.rb +97 -0
- data/lib/phronomy/agent/agent_execution_activation.rb +172 -0
- data/lib/phronomy/agent/agent_invocation.rb +44 -46
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +206 -104
- data/lib/phronomy/agent/agent_root.rb +66 -0
- data/lib/phronomy/agent/async_event_api.rb +55 -475
- data/lib/phronomy/agent/base.rb +351 -514
- data/lib/phronomy/agent/concerns/before_llm_input.rb +66 -0
- data/lib/phronomy/agent/context/capability/base.rb +166 -297
- data/lib/phronomy/agent/context_assembler.rb +357 -0
- data/lib/phronomy/agent/context_candidate.rb +47 -0
- data/lib/phronomy/agent/context_candidate_resolver.rb +65 -0
- data/lib/phronomy/agent/context_importer.rb +217 -0
- data/lib/phronomy/agent/context_parts/budget/token_budget_packer.rb +53 -0
- data/lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb +56 -0
- data/lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb +30 -0
- data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +118 -0
- data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +37 -0
- data/lib/phronomy/agent/context_plan.rb +25 -0
- data/lib/phronomy/agent/context_plan_validator.rb +134 -0
- data/lib/phronomy/agent/context_policies/default.rb +53 -0
- data/lib/phronomy/agent/context_policy.rb +15 -0
- data/lib/phronomy/agent/context_policy_descriptor.rb +49 -0
- data/lib/phronomy/agent/context_policy_registry.rb +46 -0
- data/lib/phronomy/agent/context_request.rb +35 -0
- data/lib/phronomy/agent/context_selection_unit.rb +38 -0
- data/lib/phronomy/agent/derived_content_spec.rb +34 -0
- data/lib/phronomy/agent/execution_coordinator.rb +1122 -0
- data/lib/phronomy/agent/immutable.rb +31 -0
- data/lib/phronomy/agent/journal_projection.rb +60 -0
- data/lib/phronomy/agent/journal_record.rb +67 -0
- data/lib/phronomy/agent/llm_call_record.rb +51 -0
- data/lib/phronomy/agent/llm_input_build_context.rb +17 -0
- data/lib/phronomy/agent/llm_input_manifest.rb +103 -0
- data/lib/phronomy/agent/llm_input_patch.rb +21 -0
- data/lib/phronomy/agent/phase_machine_builder.rb +12 -0
- data/lib/phronomy/agent/provider_call_outcome.rb +90 -0
- data/lib/phronomy/agent/ruby_llm_materializer.rb +189 -0
- data/lib/phronomy/agent/shared_state.rb +46 -138
- data/lib/phronomy/agent/token_budget_resolver.rb +70 -0
- data/lib/phronomy/agent/tool_call_intercepted.rb +11 -4
- data/lib/phronomy/agent/tool_definition_set.rb +55 -0
- data/lib/phronomy/agent/tool_invocation.rb +108 -314
- data/lib/phronomy/agent.rb +10 -16
- data/lib/phronomy/agent_busy_error.rb +5 -0
- data/lib/phronomy/canonical_json.rb +136 -0
- data/lib/phronomy/configuration.rb +17 -155
- data/lib/phronomy/content_store/base.rb +51 -0
- data/lib/phronomy/context_budget_exceeded_error.rb +8 -0
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +7 -80
- data/lib/phronomy/engine/event_loop.rb +3 -0
- data/lib/phronomy/engine/runtime.rb +15 -230
- data/lib/phronomy/engine/task_group.rb +30 -102
- data/lib/phronomy/execution_rehydration_required_error.rb +5 -0
- data/lib/phronomy/invalid_context_budget_configuration_error.rb +8 -0
- data/lib/phronomy/llm_context_window/token_budget.rb +8 -79
- data/lib/phronomy/multi_agent/orchestrator.rb +153 -204
- data/lib/phronomy/multi_agent/parallel_tool_chat.rb +7 -5
- data/lib/phronomy/multi_agent/team_coordinator.rb +46 -133
- data/lib/phronomy/persistence/in_memory.rb +247 -0
- data/lib/phronomy/persistence.rb +39 -0
- data/lib/phronomy/tools/agent.rb +14 -36
- data/lib/phronomy/vector_store/in_memory.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy.rb +9 -115
- data/scripts/add_to_h_to_token_doubles.rb +33 -0
- data/scripts/add_to_h_unnamed_doubles.rb +27 -0
- data/scripts/api_snapshot.rb +1 -12
- data/scripts/migrate_spec_agent_definition.rb +108 -0
- data/scripts/migrate_spec_agent_definition_pass2.rb +53 -0
- data/scripts/migrate_spec_inline_pass3.rb +24 -0
- metadata +54 -13
- data/lib/phronomy/agent/agent_invocation_registry.rb +0 -75
- data/lib/phronomy/agent/before_completion_context.rb +0 -47
- data/lib/phronomy/agent/concerns/before_completion.rb +0 -111
- data/lib/phronomy/agent/context/knowledge/base.rb +0 -58
- data/lib/phronomy/agent/context/knowledge/entity_knowledge.rb +0 -102
- data/lib/phronomy/agent/context/knowledge/static_knowledge.rb +0 -58
- data/lib/phronomy/knowledge_source.rb +0 -12
- data/lib/phronomy/llm_context_window/assembler.rb +0 -191
- data/lib/phronomy/llm_context_window/context_version_cache.rb +0 -52
|
@@ -6,6 +6,7 @@ module Phronomy
|
|
|
6
6
|
#
|
|
7
7
|
# Blocking/provider work returns through explicit Agent-internal events.
|
|
8
8
|
# Entry actions start operations and return synchronously.
|
|
9
|
+
# Every LLM Call is prepared from a canonical Manifest and RuntimeProjection.
|
|
9
10
|
#
|
|
10
11
|
# @api private
|
|
11
12
|
class AgentInvocationSessionBuilder
|
|
@@ -40,7 +41,6 @@ module Phronomy
|
|
|
40
41
|
def self.build(
|
|
41
42
|
agent:,
|
|
42
43
|
input:,
|
|
43
|
-
messages:,
|
|
44
44
|
config:,
|
|
45
45
|
approval_policy: nil,
|
|
46
46
|
approval_listener: nil,
|
|
@@ -51,7 +51,6 @@ module Phronomy
|
|
|
51
51
|
invocation = AgentInvocation.new(
|
|
52
52
|
agent: agent,
|
|
53
53
|
input: input,
|
|
54
|
-
messages: messages,
|
|
55
54
|
config: config,
|
|
56
55
|
approval_policy: approval_policy,
|
|
57
56
|
approval_listener: approval_listener,
|
|
@@ -132,6 +131,17 @@ module Phronomy
|
|
|
132
131
|
],
|
|
133
132
|
resume: [
|
|
134
133
|
{from: :suspended, to: :waiting_for_tools, guard: nil}
|
|
134
|
+
],
|
|
135
|
+
application_callback_failed: [
|
|
136
|
+
{from: :filtering_input, to: :failed, guard: nil},
|
|
137
|
+
{from: :building_context, to: :failed, guard: nil},
|
|
138
|
+
{from: :calling_llm, to: :failed, guard: nil},
|
|
139
|
+
{from: :starting_tools, to: :failed, guard: nil},
|
|
140
|
+
{from: :evaluating_tools, to: :failed, guard: nil},
|
|
141
|
+
{from: :waiting_for_tools, to: :failed, guard: nil},
|
|
142
|
+
{from: :dispatching_tools, to: :failed, guard: nil},
|
|
143
|
+
{from: :recording_tool_results, to: :failed, guard: nil},
|
|
144
|
+
{from: :output_filtering, to: :failed, guard: nil}
|
|
135
145
|
]
|
|
136
146
|
)
|
|
137
147
|
end
|
|
@@ -170,84 +180,78 @@ module Phronomy
|
|
|
170
180
|
end
|
|
171
181
|
private_class_method :build_entry_actions
|
|
172
182
|
|
|
173
|
-
def self.filtering_input_action(
|
|
174
|
-
|
|
175
|
-
:check_cancellation!,
|
|
176
|
-
invocation.config,
|
|
177
|
-
"invocation cancelled before input filtering"
|
|
178
|
-
)
|
|
179
|
-
invocation.input = agent.send(
|
|
180
|
-
:run_input_filters!,
|
|
181
|
-
invocation.input
|
|
182
|
-
)
|
|
183
|
-
invocation
|
|
184
|
-
rescue Phronomy::FilterBlockError => error
|
|
185
|
-
invocation.input_blocked = true
|
|
186
|
-
invocation.block_error = error
|
|
183
|
+
def self.filtering_input_action(_agent, invocation)
|
|
184
|
+
invocation.input = invocation.config.fetch(:phronomy_filtered_input)
|
|
187
185
|
invocation
|
|
188
186
|
end
|
|
189
187
|
private_class_method :filtering_input_action
|
|
190
188
|
|
|
191
189
|
def self.building_context_action(agent, invocation)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
:build_context,
|
|
195
|
-
invocation.input,
|
|
196
|
-
messages: invocation.messages,
|
|
197
|
-
thread_id: invocation.thread_id,
|
|
198
|
-
config: invocation.config,
|
|
199
|
-
budget: agent.send(:build_token_budget),
|
|
200
|
-
instruction: agent.send(:build_instructions, invocation.input),
|
|
201
|
-
tools: agent.class.tools + agent.send(:_handoff_tools)
|
|
202
|
-
)
|
|
203
|
-
agent.send(:_apply_context_to_chat, invocation.chat, context)
|
|
190
|
+
projection = invocation.config.fetch(:phronomy_runtime_projection)
|
|
191
|
+
invocation.chat = agent.send(:build_chat, model_config: projection.model_config)
|
|
204
192
|
agent.send(
|
|
205
|
-
:
|
|
193
|
+
:_apply_runtime_projection_to_chat,
|
|
206
194
|
invocation.chat,
|
|
207
|
-
|
|
195
|
+
projection,
|
|
196
|
+
invocation: invocation
|
|
208
197
|
)
|
|
209
|
-
install_tool_interceptors(invocation.chat)
|
|
198
|
+
install_tool_interceptors(invocation.chat, invocation)
|
|
210
199
|
invocation
|
|
211
200
|
end
|
|
212
201
|
private_class_method :building_context_action
|
|
213
202
|
|
|
214
|
-
|
|
203
|
+
# RubyLLM >= 1.15 adds the complete assistant Message to chat.messages
|
|
204
|
+
# before before_tool_call is fired. Capture that Message at the control
|
|
205
|
+
# boundary instead of trying to recover it after the exception unwinds.
|
|
206
|
+
def self.install_tool_interceptors(chat, invocation = nil)
|
|
207
|
+
unless chat.respond_to?(:before_tool_call)
|
|
208
|
+
raise Phronomy::ConfigurationError,
|
|
209
|
+
"Agent-owned Tool execution requires RubyLLM >= 1.15 (before_tool_call callback)"
|
|
210
|
+
end
|
|
211
|
+
|
|
215
212
|
if chat.respond_to?(:on_tool_call_batch)
|
|
216
213
|
chat.on_tool_call_batch do |tool_calls|
|
|
217
|
-
raise
|
|
214
|
+
raise build_tool_interception(chat, tool_calls, invocation)
|
|
218
215
|
end
|
|
219
216
|
end
|
|
220
217
|
|
|
221
|
-
|
|
222
|
-
chat
|
|
223
|
-
raise Phronomy::Agent::ToolCallIntercepted.new(tool_call)
|
|
224
|
-
end
|
|
225
|
-
else
|
|
226
|
-
chat.on_tool_call do |tool_call|
|
|
227
|
-
raise Phronomy::Agent::ToolCallIntercepted.new(tool_call)
|
|
228
|
-
end
|
|
218
|
+
chat.before_tool_call do |tool_call|
|
|
219
|
+
raise build_tool_interception(chat, [tool_call], invocation)
|
|
229
220
|
end
|
|
230
221
|
end
|
|
231
222
|
private_class_method :install_tool_interceptors
|
|
232
223
|
|
|
233
|
-
def self.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
224
|
+
def self.build_tool_interception(chat, fallback_tool_calls, invocation)
|
|
225
|
+
assistant_message = chat.messages.last
|
|
226
|
+
unless assistant_message&.respond_to?(:role) &&
|
|
227
|
+
assistant_message.role.to_sym == :assistant &&
|
|
228
|
+
assistant_message.respond_to?(:tool_calls)
|
|
229
|
+
raise Phronomy::Error,
|
|
230
|
+
"RubyLLM Tool callback fired before the complete assistant message was observable"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
message_tool_calls = assistant_message.tool_calls
|
|
234
|
+
tool_calls = if message_tool_calls.respond_to?(:values)
|
|
235
|
+
message_tool_calls.values
|
|
236
|
+
else
|
|
237
|
+
Array(message_tool_calls)
|
|
238
|
+
end
|
|
239
|
+
tool_calls = Array(fallback_tool_calls) if tool_calls.empty?
|
|
240
|
+
|
|
241
|
+
ToolCallIntercepted.new(
|
|
242
|
+
tool_calls,
|
|
243
|
+
assistant_message: assistant_message,
|
|
244
|
+
assistant_outcome: ProviderCallOutcome.capture(assistant_message),
|
|
245
|
+
llm_call_id: invocation&.current_llm_call_id
|
|
246
246
|
)
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
end
|
|
248
|
+
private_class_method :build_tool_interception
|
|
249
|
+
|
|
250
|
+
def self.calling_llm_action(agent, runtime, invocation)
|
|
251
|
+
prepare_and_start_llm_call(
|
|
252
|
+
agent,
|
|
253
|
+
runtime,
|
|
249
254
|
invocation,
|
|
250
|
-
runtime: runtime,
|
|
251
255
|
streaming: false
|
|
252
256
|
)
|
|
253
257
|
invocation
|
|
@@ -255,77 +259,175 @@ module Phronomy
|
|
|
255
259
|
private_class_method :calling_llm_action
|
|
256
260
|
|
|
257
261
|
def self.calling_llm_stream_action(agent, runtime, invocation)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
262
|
+
prepare_and_start_llm_call(
|
|
263
|
+
agent,
|
|
264
|
+
runtime,
|
|
265
|
+
invocation,
|
|
266
|
+
streaming: true
|
|
267
|
+
)
|
|
268
|
+
invocation
|
|
269
|
+
end
|
|
270
|
+
private_class_method :calling_llm_stream_action
|
|
271
|
+
|
|
272
|
+
def self.prepare_and_start_llm_call(agent, runtime, invocation, streaming:)
|
|
273
|
+
activation = invocation.config.fetch(:phronomy_activation)
|
|
274
|
+
if invocation.user_message_sent
|
|
275
|
+
preparation = runtime.blocking_io.submit do
|
|
276
|
+
activation.coordinator.prepare_next_llm_call(activation)
|
|
277
|
+
end
|
|
278
|
+
preparation.on_complete do |projection, error|
|
|
279
|
+
if error
|
|
280
|
+
post_preparation_failure(runtime, invocation, error, streaming: streaming)
|
|
281
|
+
else
|
|
282
|
+
start_provider_call(
|
|
283
|
+
agent,
|
|
284
|
+
runtime,
|
|
285
|
+
invocation,
|
|
286
|
+
activation,
|
|
287
|
+
projection,
|
|
288
|
+
streaming: streaming,
|
|
289
|
+
replace_messages: true
|
|
290
|
+
)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
else
|
|
294
|
+
start_provider_call(
|
|
295
|
+
agent,
|
|
296
|
+
runtime,
|
|
297
|
+
invocation,
|
|
298
|
+
activation,
|
|
299
|
+
activation.runtime_projection,
|
|
300
|
+
streaming: streaming,
|
|
301
|
+
replace_messages: false
|
|
302
|
+
)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
private_class_method :prepare_and_start_llm_call
|
|
306
|
+
|
|
307
|
+
def self.start_provider_call(
|
|
308
|
+
agent,
|
|
309
|
+
runtime,
|
|
310
|
+
invocation,
|
|
311
|
+
activation,
|
|
312
|
+
projection,
|
|
313
|
+
streaming:,
|
|
314
|
+
replace_messages:
|
|
315
|
+
)
|
|
316
|
+
call_started = false
|
|
261
317
|
agent.send(
|
|
262
318
|
:check_cancellation!,
|
|
263
319
|
invocation.config,
|
|
264
320
|
"invocation cancelled before LLM call"
|
|
265
321
|
)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
invocation.chat,
|
|
269
|
-
user_message,
|
|
270
|
-
config: invocation.config
|
|
271
|
-
) do |chunk|
|
|
322
|
+
if replace_messages
|
|
323
|
+
invocation.chat = agent.send(:build_chat, model_config: projection.model_config)
|
|
272
324
|
agent.send(
|
|
273
|
-
:
|
|
274
|
-
invocation.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
post_to_invocation!(
|
|
278
|
-
runtime,
|
|
279
|
-
invocation.id,
|
|
280
|
-
:llm_stream_chunk,
|
|
281
|
-
{content: chunk.content}
|
|
325
|
+
:_apply_runtime_projection_to_chat,
|
|
326
|
+
invocation.chat,
|
|
327
|
+
projection,
|
|
328
|
+
invocation: invocation
|
|
282
329
|
)
|
|
330
|
+
install_tool_interceptors(invocation.chat, invocation)
|
|
331
|
+
invocation.config[:phronomy_runtime_projection] = projection
|
|
283
332
|
end
|
|
284
333
|
|
|
285
|
-
|
|
334
|
+
call_context = activation.begin_llm_call(projection)
|
|
335
|
+
call_started = true
|
|
336
|
+
invocation.begin_llm_call!(call_context.fetch(:llm_call_id))
|
|
337
|
+
message = projection.ask_message
|
|
338
|
+
|
|
339
|
+
operation = if streaming
|
|
340
|
+
Phronomy.configuration.llm_adapter.stream_async(
|
|
341
|
+
invocation.chat,
|
|
342
|
+
message,
|
|
343
|
+
config: invocation.config
|
|
344
|
+
) do |chunk|
|
|
345
|
+
agent.send(
|
|
346
|
+
:check_cancellation!,
|
|
347
|
+
invocation.config,
|
|
348
|
+
"invocation cancelled during streaming"
|
|
349
|
+
)
|
|
350
|
+
post_to_invocation!(
|
|
351
|
+
runtime,
|
|
352
|
+
invocation.id,
|
|
353
|
+
:llm_stream_chunk,
|
|
354
|
+
{content: chunk.content}
|
|
355
|
+
)
|
|
356
|
+
end
|
|
357
|
+
else
|
|
358
|
+
Phronomy.configuration.llm_adapter.complete_async(
|
|
359
|
+
invocation.chat,
|
|
360
|
+
message,
|
|
361
|
+
config: invocation.config
|
|
362
|
+
)
|
|
363
|
+
end
|
|
364
|
+
observe_manifest_call(
|
|
286
365
|
operation,
|
|
366
|
+
activation,
|
|
287
367
|
invocation,
|
|
288
368
|
runtime: runtime,
|
|
289
|
-
streaming:
|
|
369
|
+
streaming: streaming
|
|
290
370
|
)
|
|
291
|
-
|
|
371
|
+
rescue => error
|
|
372
|
+
if call_started
|
|
373
|
+
activation.record_llm_result(
|
|
374
|
+
response: canonical_response_for(nil, error),
|
|
375
|
+
error: error,
|
|
376
|
+
streaming: streaming
|
|
377
|
+
)
|
|
378
|
+
end
|
|
379
|
+
post_llm_result(runtime, invocation, nil, error, streaming: streaming)
|
|
292
380
|
end
|
|
293
|
-
private_class_method :
|
|
381
|
+
private_class_method :start_provider_call
|
|
294
382
|
|
|
295
|
-
def self.
|
|
296
|
-
operation,
|
|
297
|
-
invocation,
|
|
298
|
-
runtime:,
|
|
299
|
-
streaming:
|
|
300
|
-
)
|
|
383
|
+
def self.observe_manifest_call(operation, activation, invocation, runtime:, streaming:)
|
|
301
384
|
operation.on_complete do |response, error|
|
|
302
|
-
|
|
303
|
-
response: response,
|
|
385
|
+
activation.record_llm_result(
|
|
386
|
+
response: canonical_response_for(response, error),
|
|
304
387
|
error: error,
|
|
305
388
|
streaming: streaming
|
|
306
389
|
)
|
|
307
|
-
|
|
308
|
-
if error && !error.is_a?(ToolCallIntercepted)
|
|
309
|
-
:llm_failed
|
|
310
|
-
else
|
|
311
|
-
:llm_completed
|
|
312
|
-
end
|
|
313
|
-
post_to_invocation!(
|
|
390
|
+
post_llm_result(
|
|
314
391
|
runtime,
|
|
315
|
-
invocation
|
|
316
|
-
|
|
317
|
-
|
|
392
|
+
invocation,
|
|
393
|
+
response,
|
|
394
|
+
error,
|
|
395
|
+
streaming: streaming
|
|
318
396
|
)
|
|
319
397
|
end
|
|
320
398
|
end
|
|
321
|
-
private_class_method :
|
|
399
|
+
private_class_method :observe_manifest_call
|
|
322
400
|
|
|
323
|
-
def self.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
401
|
+
def self.canonical_response_for(response, error)
|
|
402
|
+
if error.is_a?(ToolCallIntercepted)
|
|
403
|
+
error.assistant_outcome || ProviderCallOutcome.capture(response)
|
|
404
|
+
else
|
|
405
|
+
ProviderCallOutcome.capture(response)
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
private_class_method :canonical_response_for
|
|
409
|
+
|
|
410
|
+
def self.post_preparation_failure(runtime, invocation, error, streaming:)
|
|
411
|
+
post_llm_result(runtime, invocation, nil, error, streaming: streaming)
|
|
412
|
+
end
|
|
413
|
+
private_class_method :post_preparation_failure
|
|
414
|
+
|
|
415
|
+
def self.post_llm_result(runtime, invocation, response, error, streaming:)
|
|
416
|
+
result = LLMOperationResult.new(
|
|
417
|
+
response: response,
|
|
418
|
+
error: error,
|
|
419
|
+
streaming: streaming
|
|
420
|
+
)
|
|
421
|
+
event_type = if error && !error.is_a?(ToolCallIntercepted)
|
|
422
|
+
:llm_failed
|
|
423
|
+
else
|
|
424
|
+
:llm_completed
|
|
425
|
+
end
|
|
426
|
+
post_to_invocation!(runtime, invocation.id, event_type, result)
|
|
427
|
+
end
|
|
428
|
+
private_class_method :post_llm_result
|
|
429
|
+
|
|
430
|
+
def self.post_to_invocation!(runtime, invocation_id, event_type, payload)
|
|
329
431
|
accepted = runtime.event_loop.post_to_session(
|
|
330
432
|
Phronomy::Event.new(
|
|
331
433
|
type: event_type,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module Phronomy
|
|
6
|
+
module Agent
|
|
7
|
+
class AgentRoot
|
|
8
|
+
LIFECYCLE_STATUSES = %i[idle active suspended closed invalidated].freeze
|
|
9
|
+
|
|
10
|
+
ATTRIBUTES = %i[
|
|
11
|
+
agent_id agent_definition_id definition_version agent_revision
|
|
12
|
+
context_revision journal_position lifecycle_status transcript_generation
|
|
13
|
+
created_at updated_at metadata
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
attr_reader(*ATTRIBUTES)
|
|
17
|
+
|
|
18
|
+
def self.create(agent_id:, agent_definition_id:, definition_version:, metadata: {})
|
|
19
|
+
now = Time.now.utc.iso8601(6)
|
|
20
|
+
new(
|
|
21
|
+
agent_id: agent_id,
|
|
22
|
+
agent_definition_id: agent_definition_id,
|
|
23
|
+
definition_version: definition_version,
|
|
24
|
+
agent_revision: 0,
|
|
25
|
+
context_revision: 0,
|
|
26
|
+
journal_position: 0,
|
|
27
|
+
lifecycle_status: :idle,
|
|
28
|
+
transcript_generation: 0,
|
|
29
|
+
created_at: now,
|
|
30
|
+
updated_at: now,
|
|
31
|
+
metadata: metadata
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize(**attributes)
|
|
36
|
+
ATTRIBUTES.each do |name|
|
|
37
|
+
value = attributes.fetch(name)
|
|
38
|
+
value = value.to_sym if name == :lifecycle_status
|
|
39
|
+
instance_variable_set("@#{name}", Immutable.copy(value))
|
|
40
|
+
end
|
|
41
|
+
unless LIFECYCLE_STATUSES.include?(lifecycle_status)
|
|
42
|
+
raise ArgumentError, "unknown Agent lifecycle status: #{lifecycle_status.inspect}"
|
|
43
|
+
end
|
|
44
|
+
raise ArgumentError, "agent_revision must be non-negative" if agent_revision.negative?
|
|
45
|
+
raise ArgumentError, "context_revision must be non-negative" if context_revision.negative?
|
|
46
|
+
raise ArgumentError, "journal_position must be non-negative" if journal_position.negative?
|
|
47
|
+
Immutable.validate_canonical_json!(metadata, label: "Agent metadata")
|
|
48
|
+
freeze
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def with(**changes)
|
|
52
|
+
values = to_h.transform_keys(&:to_sym).merge(changes)
|
|
53
|
+
values[:updated_at] = Time.now.utc.iso8601(6) unless changes.key?(:updated_at)
|
|
54
|
+
self.class.new(**values)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_h
|
|
58
|
+
ATTRIBUTES.to_h { |name| [name.to_s, public_send(name)] }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.from_h(hash)
|
|
62
|
+
new(**ATTRIBUTES.to_h { |name| [name, hash.fetch(name.to_s)] })
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|