phronomy 0.16.0 → 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 +54 -0
- data/CONTRIBUTING.md +28 -16
- data/README.md +124 -92
- data/benchmark/baseline.json +2 -3
- data/benchmark/bench_agent_invoke.rb +4 -4
- data/benchmark/bench_context_assembler.rb +134 -34
- data/benchmark/bench_regression.rb +1 -1
- data/benchmark/bench_tool_schema.rb +2 -35
- 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 +2 -2
- data/docs/decisions/013-journal-backed-knowledge-as-context-candidates.md +122 -0
- data/lib/phronomy/agent/agent_invocation.rb +2 -36
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +156 -93
- data/lib/phronomy/agent/agent_root.rb +1 -2
- data/lib/phronomy/agent/base.rb +135 -314
- data/lib/phronomy/agent/context/capability/base.rb +166 -297
- data/lib/phronomy/agent/context_assembler.rb +65 -29
- data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +19 -89
- data/lib/phronomy/agent/context_plan_validator.rb +0 -33
- data/lib/phronomy/agent/execution_coordinator.rb +0 -1
- data/lib/phronomy/agent/journal_projection.rb +28 -2
- data/lib/phronomy/agent/ruby_llm_materializer.rb +2 -111
- data/lib/phronomy/agent/shared_state.rb +46 -138
- data/lib/phronomy/agent/token_budget_resolver.rb +5 -4
- data/lib/phronomy/agent/tool_invocation.rb +108 -314
- data/lib/phronomy/agent.rb +6 -10
- data/lib/phronomy/configuration.rb +15 -158
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +7 -80
- data/lib/phronomy/engine/runtime.rb +15 -230
- data/lib/phronomy/engine/task_group.rb +30 -102
- data/lib/phronomy/llm_context_window/token_budget.rb +8 -79
- data/lib/phronomy/multi_agent/orchestrator.rb +152 -204
- data/lib/phronomy/multi_agent/team_coordinator.rb +42 -133
- data/lib/phronomy/vector_store/in_memory.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy.rb +3 -120
- data/scripts/api_snapshot.rb +1 -12
- metadata +3 -9
- 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/agent/fsm_runtime_adapter.rb +0 -210
- 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,
|
|
@@ -181,41 +180,20 @@ module Phronomy
|
|
|
181
180
|
end
|
|
182
181
|
private_class_method :build_entry_actions
|
|
183
182
|
|
|
184
|
-
def self.filtering_input_action(
|
|
185
|
-
|
|
186
|
-
:check_cancellation!,
|
|
187
|
-
invocation.config,
|
|
188
|
-
"invocation cancelled before input filtering"
|
|
189
|
-
)
|
|
190
|
-
invocation.input = agent.send(
|
|
191
|
-
:run_input_filters!,
|
|
192
|
-
invocation.input
|
|
193
|
-
)
|
|
194
|
-
invocation
|
|
195
|
-
rescue Phronomy::FilterBlockError => error
|
|
196
|
-
invocation.input_blocked = true
|
|
197
|
-
invocation.block_error = error
|
|
183
|
+
def self.filtering_input_action(_agent, invocation)
|
|
184
|
+
invocation.input = invocation.config.fetch(:phronomy_filtered_input)
|
|
198
185
|
invocation
|
|
199
186
|
end
|
|
200
187
|
private_class_method :filtering_input_action
|
|
201
188
|
|
|
202
189
|
def self.building_context_action(agent, invocation)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
:build_context,
|
|
206
|
-
invocation.input,
|
|
207
|
-
messages: invocation.messages,
|
|
208
|
-
thread_id: invocation.thread_id,
|
|
209
|
-
config: invocation.config,
|
|
210
|
-
budget: agent.send(:build_token_budget),
|
|
211
|
-
instruction: agent.send(:build_instructions, invocation.input),
|
|
212
|
-
tools: agent.class.tools + agent.send(:_handoff_tools)
|
|
213
|
-
)
|
|
214
|
-
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)
|
|
215
192
|
agent.send(
|
|
216
|
-
:
|
|
193
|
+
:_apply_runtime_projection_to_chat,
|
|
217
194
|
invocation.chat,
|
|
218
|
-
|
|
195
|
+
projection,
|
|
196
|
+
invocation: invocation
|
|
219
197
|
)
|
|
220
198
|
install_tool_interceptors(invocation.chat, invocation)
|
|
221
199
|
invocation
|
|
@@ -270,23 +248,10 @@ module Phronomy
|
|
|
270
248
|
private_class_method :build_tool_interception
|
|
271
249
|
|
|
272
250
|
def self.calling_llm_action(agent, runtime, invocation)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
agent.send(
|
|
277
|
-
:check_cancellation!,
|
|
278
|
-
invocation.config,
|
|
279
|
-
"invocation cancelled before LLM call"
|
|
280
|
-
)
|
|
281
|
-
operation = Phronomy.configuration.llm_adapter.complete_async(
|
|
282
|
-
invocation.chat,
|
|
283
|
-
user_message,
|
|
284
|
-
config: invocation.config
|
|
285
|
-
)
|
|
286
|
-
observe_llm_operation(
|
|
287
|
-
operation,
|
|
251
|
+
prepare_and_start_llm_call(
|
|
252
|
+
agent,
|
|
253
|
+
runtime,
|
|
288
254
|
invocation,
|
|
289
|
-
runtime: runtime,
|
|
290
255
|
streaming: false
|
|
291
256
|
)
|
|
292
257
|
invocation
|
|
@@ -294,77 +259,175 @@ module Phronomy
|
|
|
294
259
|
private_class_method :calling_llm_action
|
|
295
260
|
|
|
296
261
|
def self.calling_llm_stream_action(agent, runtime, invocation)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
|
300
317
|
agent.send(
|
|
301
318
|
:check_cancellation!,
|
|
302
319
|
invocation.config,
|
|
303
320
|
"invocation cancelled before LLM call"
|
|
304
321
|
)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
invocation.chat,
|
|
308
|
-
user_message,
|
|
309
|
-
config: invocation.config
|
|
310
|
-
) do |chunk|
|
|
322
|
+
if replace_messages
|
|
323
|
+
invocation.chat = agent.send(:build_chat, model_config: projection.model_config)
|
|
311
324
|
agent.send(
|
|
312
|
-
:
|
|
313
|
-
invocation.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
post_to_invocation!(
|
|
317
|
-
runtime,
|
|
318
|
-
invocation.id,
|
|
319
|
-
:llm_stream_chunk,
|
|
320
|
-
{content: chunk.content}
|
|
325
|
+
:_apply_runtime_projection_to_chat,
|
|
326
|
+
invocation.chat,
|
|
327
|
+
projection,
|
|
328
|
+
invocation: invocation
|
|
321
329
|
)
|
|
330
|
+
install_tool_interceptors(invocation.chat, invocation)
|
|
331
|
+
invocation.config[:phronomy_runtime_projection] = projection
|
|
322
332
|
end
|
|
323
333
|
|
|
324
|
-
|
|
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(
|
|
325
365
|
operation,
|
|
366
|
+
activation,
|
|
326
367
|
invocation,
|
|
327
368
|
runtime: runtime,
|
|
328
|
-
streaming:
|
|
369
|
+
streaming: streaming
|
|
329
370
|
)
|
|
330
|
-
|
|
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)
|
|
331
380
|
end
|
|
332
|
-
private_class_method :
|
|
381
|
+
private_class_method :start_provider_call
|
|
333
382
|
|
|
334
|
-
def self.
|
|
335
|
-
operation,
|
|
336
|
-
invocation,
|
|
337
|
-
runtime:,
|
|
338
|
-
streaming:
|
|
339
|
-
)
|
|
383
|
+
def self.observe_manifest_call(operation, activation, invocation, runtime:, streaming:)
|
|
340
384
|
operation.on_complete do |response, error|
|
|
341
|
-
|
|
342
|
-
response: response,
|
|
385
|
+
activation.record_llm_result(
|
|
386
|
+
response: canonical_response_for(response, error),
|
|
343
387
|
error: error,
|
|
344
388
|
streaming: streaming
|
|
345
389
|
)
|
|
346
|
-
|
|
347
|
-
if error && !error.is_a?(ToolCallIntercepted)
|
|
348
|
-
:llm_failed
|
|
349
|
-
else
|
|
350
|
-
:llm_completed
|
|
351
|
-
end
|
|
352
|
-
post_to_invocation!(
|
|
390
|
+
post_llm_result(
|
|
353
391
|
runtime,
|
|
354
|
-
invocation
|
|
355
|
-
|
|
356
|
-
|
|
392
|
+
invocation,
|
|
393
|
+
response,
|
|
394
|
+
error,
|
|
395
|
+
streaming: streaming
|
|
357
396
|
)
|
|
358
397
|
end
|
|
359
398
|
end
|
|
360
|
-
private_class_method :
|
|
399
|
+
private_class_method :observe_manifest_call
|
|
361
400
|
|
|
362
|
-
def self.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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)
|
|
368
431
|
accepted = runtime.event_loop.post_to_session(
|
|
369
432
|
Phronomy::Event.new(
|
|
370
433
|
type: event_type,
|
|
@@ -9,7 +9,7 @@ module Phronomy
|
|
|
9
9
|
|
|
10
10
|
ATTRIBUTES = %i[
|
|
11
11
|
agent_id agent_definition_id definition_version agent_revision
|
|
12
|
-
context_revision journal_position lifecycle_status transcript_generation
|
|
12
|
+
context_revision journal_position lifecycle_status transcript_generation
|
|
13
13
|
created_at updated_at metadata
|
|
14
14
|
].freeze
|
|
15
15
|
|
|
@@ -26,7 +26,6 @@ module Phronomy
|
|
|
26
26
|
journal_position: 0,
|
|
27
27
|
lifecycle_status: :idle,
|
|
28
28
|
transcript_generation: 0,
|
|
29
|
-
memory_generation: 0,
|
|
30
29
|
created_at: now,
|
|
31
30
|
updated_at: now,
|
|
32
31
|
metadata: metadata
|