phronomy 0.31.0 → 0.33.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 +141 -0
- data/README.md +13 -1
- data/RELEASE_CHECKLIST.md +4 -0
- data/docs/architecture/structured-output-and-scope.md +5 -4
- data/docs/architecture/trust-acceptance-followup.md +15 -0
- data/docs/context-policy-budgeting.md +108 -0
- data/docs/examples/shared_state_custom_sharing.rb +145 -0
- data/docs/features.md +4 -0
- data/docs/mcp-client.md +4 -2
- data/docs/migrations/shared-state-multi-agent.md +5 -0
- data/docs/runtime-and-concurrency.md +195 -0
- data/docs/shared-state-sharing.md +223 -0
- data/lib/phronomy/agent/approval_wait.rb +8 -0
- data/lib/phronomy/agent/async_event_api.rb +60 -0
- data/lib/phronomy/agent/base.rb +54 -1
- data/lib/phronomy/agent/execution/exact_execution.rb +4 -0
- data/lib/phronomy/agent/execution/execution_coordinator.rb +100 -2
- data/lib/phronomy/agent/journal/journal_projection.rb +24 -1
- data/lib/phronomy/agent/lifecycle/hosted_invocation.rb +67 -0
- data/lib/phronomy/agent/lifecycle/lifetime_api.rb +30 -0
- data/lib/phronomy/agent/lifecycle/operation_lease.rb +7 -0
- data/lib/phronomy/agent/lifecycle/operation_result.rb +12 -0
- data/lib/phronomy/agent/lifecycle/ownership_registry.rb +86 -14
- data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +1 -1
- data/lib/phronomy/agent/store.rb +52 -0
- data/lib/phronomy/agent/tool_execution/tool_approval_request.rb +3 -2
- data/lib/phronomy/agent/tool_execution/tool_invocation.rb +12 -36
- data/lib/phronomy/context/context_policy.rb +11 -2
- data/lib/phronomy/context/context_policy_input.rb +26 -0
- data/lib/phronomy/context/context_policy_input_builder.rb +27 -19
- data/lib/phronomy/engine/fsm_session.rb +9 -3
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +87 -5
- data/lib/phronomy/multi_agent/orchestrator.rb +30 -18
- data/lib/phronomy/multi_agent/shared_state/binding.rb +44 -0
- data/lib/phronomy/multi_agent/shared_state/member_lifecycle_error.rb +32 -0
- data/lib/phronomy/multi_agent/shared_state.rb +147 -2
- data/lib/phronomy/tool/base.rb +69 -3
- data/lib/phronomy/tool/schema.rb +23 -11
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/execution/workflow.rb +9 -0
- data/lib/phronomy/workflow/execution/workflow_runner.rb +62 -14
- data/scripts/api_snapshot.rb +8 -3
- data/sig/phronomy/agent/execution_contract.rbs +13 -0
- data/sig/phronomy/agent/store.rbs +1 -0
- data/sig/phronomy/agent.rbs +2 -0
- data/sig/phronomy/context.rbs +3 -0
- data/sig/phronomy/multi_agent.rbs +28 -2
- data/sig/phronomy/tool.rbs +1 -0
- data/sig/phronomy/workflow.rbs +1 -0
- metadata +9 -2
data/lib/phronomy/tool/base.rb
CHANGED
|
@@ -363,10 +363,17 @@ module Phronomy
|
|
|
363
363
|
# @api public
|
|
364
364
|
def validate_arguments(args)
|
|
365
365
|
schema = parameters_schema
|
|
366
|
-
|
|
367
|
-
|
|
366
|
+
validator = @validation_schema
|
|
367
|
+
if !validator || validator.json_schema != schema
|
|
368
|
+
declaration = self.class.parameters_schema_definition
|
|
369
|
+
validator = if Schema === declaration && declaration.instance_of?(Schema) && declaration.json_schema.equal?(schema)
|
|
370
|
+
declaration.for_tool_instance
|
|
371
|
+
else
|
|
372
|
+
Schema.new(schema)
|
|
373
|
+
end
|
|
374
|
+
@validation_schema = validator
|
|
368
375
|
end
|
|
369
|
-
values, error =
|
|
376
|
+
values, error = validator.validate(args || {}, coerce: self.class.on_schema_error == :coerce)
|
|
370
377
|
if values && !self.class.parameters_schema_definition
|
|
371
378
|
# Legacy optional param nil means omission, preserving Ruby defaults.
|
|
372
379
|
self.class.declared_parameters.each do |name, parameter|
|
|
@@ -442,6 +449,24 @@ module Phronomy
|
|
|
442
449
|
{}
|
|
443
450
|
end
|
|
444
451
|
|
|
452
|
+
# Selects the arguments and policy facts that may appear in a human approval
|
|
453
|
+
# request. Arguments may be raw or validated. This does not change either input.
|
|
454
|
+
# @api public
|
|
455
|
+
def approval_display(arguments:, facts:)
|
|
456
|
+
sensitive = arguments.transform_keys(&:to_sym).slice(*self.class.redact_params).values
|
|
457
|
+
indexed = {}
|
|
458
|
+
sensitive.each do |value|
|
|
459
|
+
if Hash === value || Array === value
|
|
460
|
+
approval_comparison_key(value) { |key| indexed[key] = true }
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
display_facts = redact_approval_fact(facts) do |value|
|
|
464
|
+
sensitive_fact_value?(value, sensitive) || (!indexed.empty? && indexed.key?(approval_comparison_key(value)))
|
|
465
|
+
end
|
|
466
|
+
# If the entire facts Hash matches a sensitive argument, hide all of its keys.
|
|
467
|
+
{arguments: redacted_args(arguments), facts: display_facts.is_a?(Hash) ? display_facts : {}}
|
|
468
|
+
end
|
|
469
|
+
|
|
445
470
|
# @api public
|
|
446
471
|
def execute(**_args)
|
|
447
472
|
raise NotImplementedError, "#{self.class}#execute is not implemented"
|
|
@@ -536,6 +561,47 @@ module Phronomy
|
|
|
536
561
|
end
|
|
537
562
|
end
|
|
538
563
|
|
|
564
|
+
# Index each sensitive subtree and scalar once. Object member names describe
|
|
565
|
+
# structure; its member values and array elements can be copied into facts.
|
|
566
|
+
def approval_comparison_key(value, &index)
|
|
567
|
+
key = case value
|
|
568
|
+
when Hash then value.transform_values { |item| approval_comparison_key(item, &index) }
|
|
569
|
+
when Array then value.map { |item| approval_comparison_key(item, &index) }
|
|
570
|
+
when Symbol then value.to_s
|
|
571
|
+
when Float then (value.finite? && value == value.to_i) ? value.to_i : value
|
|
572
|
+
else value
|
|
573
|
+
end
|
|
574
|
+
index&.call(key)
|
|
575
|
+
key
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def sensitive_fact_value?(value, sensitive_values)
|
|
579
|
+
sensitive_values.any? do |sensitive|
|
|
580
|
+
sensitive == value ||
|
|
581
|
+
(Symbol === value && value.to_s == sensitive) ||
|
|
582
|
+
(Symbol === sensitive && sensitive.to_s == value)
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
def redact_approval_fact(value, &sensitive)
|
|
587
|
+
return "[REDACTED]" if sensitive.call(value)
|
|
588
|
+
|
|
589
|
+
case value
|
|
590
|
+
when Hash
|
|
591
|
+
value.each_with_object({}) do |(key, item), result|
|
|
592
|
+
next if sensitive.call(key)
|
|
593
|
+
|
|
594
|
+
result[key] = redact_approval_fact(item, &sensitive)
|
|
595
|
+
end.freeze
|
|
596
|
+
when Array
|
|
597
|
+
value.map { |item| redact_approval_fact(item, &sensitive) }.freeze
|
|
598
|
+
when String
|
|
599
|
+
"[REDACTED]"
|
|
600
|
+
else
|
|
601
|
+
value
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
|
|
539
605
|
def nested_schema_to_json_schema(nested)
|
|
540
606
|
nested.each_with_object({}) do |(prop_name, spec), result|
|
|
541
607
|
type = spec[:type].to_s
|
data/lib/phronomy/tool/schema.rb
CHANGED
|
@@ -43,19 +43,17 @@ module Phronomy
|
|
|
43
43
|
unless @json_schema.is_a?(Hash) && @json_schema["type"] == "object"
|
|
44
44
|
raise ArgumentError, "Tool schema must declare an object"
|
|
45
45
|
end
|
|
46
|
+
@json_schema = Phronomy::Values::Immutable.copy(@json_schema)
|
|
46
47
|
@references = []
|
|
47
48
|
validate_keywords!(@json_schema)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
freeze
|
|
57
|
-
rescue JSONSchemer::InvalidRefPointer, JSONSchemer::InvalidRefResolution => error
|
|
58
|
-
raise ArgumentError, "invalid local Tool schema reference: #{error.message}"
|
|
49
|
+
@references.freeze
|
|
50
|
+
compile_validator!
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Build an independent validator from this immutable Tool definition.
|
|
54
|
+
# @api private
|
|
55
|
+
def for_tool_instance
|
|
56
|
+
dup.compile_validator!
|
|
59
57
|
end
|
|
60
58
|
|
|
61
59
|
# @api public
|
|
@@ -99,6 +97,20 @@ module Phronomy
|
|
|
99
97
|
end
|
|
100
98
|
end
|
|
101
99
|
|
|
100
|
+
protected
|
|
101
|
+
|
|
102
|
+
def compile_validator!
|
|
103
|
+
options = {ref_resolver: ->(uri) { raise ArgumentError, "external Tool schema reference: #{uri}" }}
|
|
104
|
+
unless JSONSchemer.valid_schema?(@json_schema, **options)
|
|
105
|
+
raise ArgumentError, "invalid Tool JSON Schema"
|
|
106
|
+
end
|
|
107
|
+
@validator = JSONSchemer.schema(@json_schema, **options)
|
|
108
|
+
@references.each { |reference| @validator.ref(reference) }
|
|
109
|
+
freeze
|
|
110
|
+
rescue JSONSchemer::InvalidRefPointer, JSONSchemer::InvalidRefResolution => error
|
|
111
|
+
raise ArgumentError, "invalid local Tool schema reference: #{error.message}"
|
|
112
|
+
end
|
|
113
|
+
|
|
102
114
|
private
|
|
103
115
|
|
|
104
116
|
def error_message(error)
|
data/lib/phronomy/version.rb
CHANGED
|
@@ -45,6 +45,15 @@ module Phronomy
|
|
|
45
45
|
@runner.send_event(state: state, event: event, input: input)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
# Applies an explicitly named event to one revision of a saved wait.
|
|
49
|
+
# The host holds its exclusive claim for this Workflow across the call.
|
|
50
|
+
# @return [WorkflowContext] the next halted or completed context
|
|
51
|
+
# @api public
|
|
52
|
+
def send_saved_event(workflow_instance_id:, expected_revision:, event:, input: nil)
|
|
53
|
+
@runner.send_saved_event(workflow_instance_id: workflow_instance_id,
|
|
54
|
+
expected_revision: expected_revision, event: event, input: input)
|
|
55
|
+
end
|
|
56
|
+
|
|
48
57
|
# Sends an event to an active Workflow execution without blocking.
|
|
49
58
|
#
|
|
50
59
|
# +workflow_instance_id+ is the logical/durable Workflow identity. EventLoop resolves it
|
|
@@ -54,6 +54,7 @@ module Phronomy
|
|
|
54
54
|
:input,
|
|
55
55
|
:event_name,
|
|
56
56
|
:current_phase,
|
|
57
|
+
:expected_revision,
|
|
57
58
|
:result_task,
|
|
58
59
|
:workflow_instance_id,
|
|
59
60
|
:owner_token
|
|
@@ -141,13 +142,28 @@ module Phronomy
|
|
|
141
142
|
end
|
|
142
143
|
|
|
143
144
|
start_resume_execution(
|
|
144
|
-
|
|
145
|
+
workflow_instance_id,
|
|
146
|
+
state: state,
|
|
145
147
|
input: input,
|
|
146
148
|
event_name: event_name,
|
|
147
149
|
current_phase: current_phase
|
|
148
150
|
).wait_result
|
|
149
151
|
end
|
|
150
152
|
|
|
153
|
+
def send_saved_event(workflow_instance_id:, expected_revision:, event:, input: nil)
|
|
154
|
+
if Phronomy::WaitPolicy.blocking_forbidden?
|
|
155
|
+
raise Phronomy::EventLoopReentrancyError, "send_saved_event requires a host thread"
|
|
156
|
+
end
|
|
157
|
+
unless workflow_instance_id.is_a?(String) && !workflow_instance_id.empty?
|
|
158
|
+
raise ArgumentError, "workflow_instance_id must be a nonempty String"
|
|
159
|
+
end
|
|
160
|
+
unless expected_revision.is_a?(Integer) && expected_revision.positive?
|
|
161
|
+
raise ArgumentError, "expected_revision must be a positive Integer"
|
|
162
|
+
end
|
|
163
|
+
start_resume_execution(workflow_instance_id.dup, expected_revision: expected_revision,
|
|
164
|
+
input: input, event_name: event).wait_result
|
|
165
|
+
end
|
|
166
|
+
|
|
151
167
|
# Posts an application-defined event to the currently live Workflow owner.
|
|
152
168
|
# WorkflowExecutionRegistry resolves workflow_instance_id to the current concrete
|
|
153
169
|
# fsm_session_id. The opaque admission owner token is deliberately not a
|
|
@@ -256,8 +272,8 @@ module Phronomy
|
|
|
256
272
|
result_task || failed_task("workflow:preparation", error)
|
|
257
273
|
end
|
|
258
274
|
|
|
259
|
-
def start_resume_execution(
|
|
260
|
-
workflow_instance_id =
|
|
275
|
+
def start_resume_execution(workflow_instance_id, input:, event_name:, state: nil, current_phase: nil, expected_revision: nil)
|
|
276
|
+
workflow_instance_id = workflow_instance_id.to_s.freeze
|
|
261
277
|
result_task = Phronomy::TaskResult.deferred(name: "workflow-resume:#{workflow_instance_id}")
|
|
262
278
|
Phronomy::Tracing::Automatic.observe_task(
|
|
263
279
|
result_task,
|
|
@@ -272,7 +288,8 @@ module Phronomy
|
|
|
272
288
|
state: state,
|
|
273
289
|
input: input,
|
|
274
290
|
event_name: event_name.to_sym,
|
|
275
|
-
current_phase: current_phase
|
|
291
|
+
current_phase: current_phase,
|
|
292
|
+
expected_revision: expected_revision,
|
|
276
293
|
result_task: result_task,
|
|
277
294
|
workflow_instance_id: workflow_instance_id,
|
|
278
295
|
owner_token: Object.new.freeze
|
|
@@ -345,6 +362,7 @@ module Phronomy
|
|
|
345
362
|
|
|
346
363
|
repository = configured_repository
|
|
347
364
|
unless repository
|
|
365
|
+
raise Phronomy::ConfigurationError, "send_saved_event requires Workflow persistence" if request.expected_revision
|
|
348
366
|
context = request.input ? request.state.merge(request.input) : request.state
|
|
349
367
|
execution = Execution.new(
|
|
350
368
|
context: context,
|
|
@@ -445,12 +463,9 @@ module Phronomy
|
|
|
445
463
|
return
|
|
446
464
|
end
|
|
447
465
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
ready.result.revision
|
|
452
|
-
)
|
|
453
|
-
context = request.input ? request.state.merge(request.input) : request.state
|
|
466
|
+
state = request.state || restore_saved_wait!(request, ready.result)
|
|
467
|
+
expected_revision = validate_resume_snapshot!(state, ready.result.snapshot, ready.result.revision)
|
|
468
|
+
context = request.input ? state.merge(request.input) : state
|
|
454
469
|
execution = Execution.new(
|
|
455
470
|
context: context,
|
|
456
471
|
workflow_instance_id: request.workflow_instance_id,
|
|
@@ -465,12 +480,28 @@ module Phronomy
|
|
|
465
480
|
execution,
|
|
466
481
|
request.result_task,
|
|
467
482
|
resume_event: request.event_name,
|
|
468
|
-
resume_phase: request.current_phase
|
|
483
|
+
resume_phase: request.current_phase || state.phase,
|
|
484
|
+
require_resume_transition: !request.expected_revision.nil?
|
|
469
485
|
)
|
|
470
486
|
rescue => error
|
|
471
487
|
release_and_fail(request, error)
|
|
472
488
|
end
|
|
473
489
|
|
|
490
|
+
def restore_saved_wait!(request, loaded)
|
|
491
|
+
unless loaded.snapshot && loaded.revision == request.expected_revision
|
|
492
|
+
raise Phronomy::Persistence::StateConflictError,
|
|
493
|
+
"No saved Workflow at revision #{request.expected_revision} for #{request.workflow_instance_id.inspect}"
|
|
494
|
+
end
|
|
495
|
+
phase = record_value(loaded.snapshot, :phase)&.to_sym
|
|
496
|
+
unless @wait_state_names.include?(phase) && @external_events.fetch(request.event_name, []).any? { |edge| edge[:from] == phase }
|
|
497
|
+
raise ArgumentError, "No saved wait at #{phase.inspect} accepting #{request.event_name.inspect}"
|
|
498
|
+
end
|
|
499
|
+
fields = record_value(loaded.snapshot, :fields)
|
|
500
|
+
@state_class.new(**restore_context_fields(fields)).set_graph_metadata(
|
|
501
|
+
workflow_instance_id: request.workflow_instance_id, phase: phase
|
|
502
|
+
)
|
|
503
|
+
end
|
|
504
|
+
|
|
474
505
|
def build_new_execution(
|
|
475
506
|
input,
|
|
476
507
|
workflow_instance_id:,
|
|
@@ -487,9 +518,7 @@ module Phronomy
|
|
|
487
518
|
stored_fields = loaded_snapshot &&
|
|
488
519
|
(loaded_snapshot[:fields] || loaded_snapshot["fields"])
|
|
489
520
|
initial_fields = if stored_fields
|
|
490
|
-
stored_fields
|
|
491
|
-
.transform_keys(&:to_sym)
|
|
492
|
-
.merge(input.transform_keys(&:to_sym))
|
|
521
|
+
restore_context_fields(stored_fields).merge(input.transform_keys(&:to_sym))
|
|
493
522
|
else
|
|
494
523
|
input
|
|
495
524
|
end
|
|
@@ -509,6 +538,21 @@ module Phronomy
|
|
|
509
538
|
)
|
|
510
539
|
end
|
|
511
540
|
|
|
541
|
+
# The repository and the load message own immutable snapshots. Actions receive
|
|
542
|
+
# their own mutable field values, including strings nested in arrays or hashes.
|
|
543
|
+
def restore_context_fields(fields)
|
|
544
|
+
fields.to_h { |name, value| [name.to_sym, mutable_field_value(value)] }
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def mutable_field_value(value)
|
|
548
|
+
case value
|
|
549
|
+
when Hash then value.transform_values { |child| mutable_field_value(child) }
|
|
550
|
+
when Array then value.map { |child| mutable_field_value(child) }
|
|
551
|
+
when String then value.dup
|
|
552
|
+
else value
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
512
556
|
def validate_resume_snapshot!(state, durable_snapshot, durable_revision)
|
|
513
557
|
return nil unless durable_snapshot
|
|
514
558
|
|
|
@@ -576,6 +620,7 @@ module Phronomy
|
|
|
576
620
|
result_task,
|
|
577
621
|
resume_event: nil,
|
|
578
622
|
resume_phase: nil,
|
|
623
|
+
require_resume_transition: false,
|
|
579
624
|
stable_observer: nil
|
|
580
625
|
)
|
|
581
626
|
assert_execution_owner!
|
|
@@ -588,6 +633,7 @@ module Phronomy
|
|
|
588
633
|
execution: execution,
|
|
589
634
|
resume_event: resume_event,
|
|
590
635
|
resume_phase: resume_phase,
|
|
636
|
+
require_resume_transition: require_resume_transition,
|
|
591
637
|
stable_observer: stable_observer,
|
|
592
638
|
task_execution: task_execution
|
|
593
639
|
)
|
|
@@ -769,6 +815,7 @@ module Phronomy
|
|
|
769
815
|
execution:,
|
|
770
816
|
resume_event: nil,
|
|
771
817
|
resume_phase: nil,
|
|
818
|
+
require_resume_transition: false,
|
|
772
819
|
stable_observer: nil,
|
|
773
820
|
task_execution: nil
|
|
774
821
|
)
|
|
@@ -787,6 +834,7 @@ module Phronomy
|
|
|
787
834
|
recursion_limit: execution.recursion_limit,
|
|
788
835
|
resume_event: resume_event,
|
|
789
836
|
resume_phase: resume_phase,
|
|
837
|
+
require_resume_transition: require_resume_transition,
|
|
790
838
|
stable_observer: stable_observer,
|
|
791
839
|
persist: terminal_persistence_callback(execution),
|
|
792
840
|
task_execution: task_execution
|
data/scripts/api_snapshot.rb
CHANGED
|
@@ -30,6 +30,8 @@ PUBLIC_API_ENTRIES = [
|
|
|
30
30
|
Phronomy::Embeddings::AuthenticationError,
|
|
31
31
|
Phronomy::Embeddings::RateLimitError,
|
|
32
32
|
Phronomy::Agent::Base,
|
|
33
|
+
Phronomy::Agent::HostedInvocation,
|
|
34
|
+
Phronomy::Agent::ApprovalWait,
|
|
33
35
|
Phronomy::Agent::ReservedExecution,
|
|
34
36
|
Phronomy::Agent::ExecutionObservation,
|
|
35
37
|
Phronomy::Agent::KnowledgeItem,
|
|
@@ -104,9 +106,12 @@ def snapshot_entry(klass)
|
|
|
104
106
|
"public_class_methods" => (klass.public_methods(false) - BASELINE_CLASS_METHODS).sort
|
|
105
107
|
}
|
|
106
108
|
else
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
internal_methods = case klass.name
|
|
110
|
+
when "Phronomy::InvocationContext" then %i[__bind_execution __execution_scope]
|
|
111
|
+
when "Phronomy::Tool::Schema" then %i[for_tool_instance]
|
|
112
|
+
else []
|
|
113
|
+
end
|
|
114
|
+
instance_methods = (klass.public_instance_methods - BASELINE_INSTANCE_METHODS - internal_methods).sort
|
|
110
115
|
internal_class_methods = (klass == Phronomy::Storage::AsyncClient) ? %i[submit] : []
|
|
111
116
|
class_methods = (klass.public_methods(false) - BASELINE_CLASS_METHODS - internal_class_methods).sort
|
|
112
117
|
{
|
|
@@ -4,6 +4,19 @@ module Phronomy
|
|
|
4
4
|
type execution_wiring = { ?phronomy_execution_participant: _ExecutionParticipant, ?phronomy_control_bindings: Array[_ControlCapability], ?phronomy_transfer_context: TransferContext, ?phronomy_admission: _AdmissionOwner, ?phronomy_reservation: Hash[String, json_value] }
|
|
5
5
|
type execution_result = { agent_id: String, execution_id: String, status: Symbol, result: json_value, output: json_value, ?raw_output: String?, error: Hash[String, json_value]? }
|
|
6
6
|
|
|
7
|
+
class ApprovalWait < Data
|
|
8
|
+
attr_reader execution_id: String
|
|
9
|
+
attr_reader approval_request_id: String
|
|
10
|
+
attr_reader execution_revision: Integer
|
|
11
|
+
def self.new: (execution_id: String, approval_request_id: String, execution_revision: Integer) -> instance
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class HostedInvocation
|
|
15
|
+
def on_complete: () { (untyped, StandardError?) -> untyped } -> self
|
|
16
|
+
def detach!: (approval_request_id: String, ?execution_id: String) -> ApprovalWait
|
|
17
|
+
def wait_result: (?timeout: Numeric?) -> (execution_result | ApprovalWait)
|
|
18
|
+
end
|
|
19
|
+
|
|
7
20
|
class ReservedExecution < Data
|
|
8
21
|
attr_reader agent_id: String
|
|
9
22
|
attr_reader execution_id: String
|
|
@@ -54,6 +54,7 @@ module Phronomy
|
|
|
54
54
|
def authorized_operations: (::Phronomy::Persistence::Transaction scope, agent_id: String, execution_id: String, invocation_id: String, name: String, arguments: Hash[String, untyped], names: Array[String]) -> Array[AuthorizedOperation]
|
|
55
55
|
def result: (String execution_id) -> Hash[Symbol, untyped]
|
|
56
56
|
def runs: (String owner_id, ?after: String?, ?limit: Integer) -> Array[untyped]
|
|
57
|
+
def standalone_approval_wait: (agent_id: String, execution_id: String, ?scope: ::Phronomy::Persistence::Transaction?) -> ApprovalWait?
|
|
57
58
|
def observe_execution: (agent_id: String, execution_id: String, ?scope: ::Phronomy::Persistence::Transaction?) -> ExecutionObservation
|
|
58
59
|
def active_executions: (agent_id: String, ?scope: ::Phronomy::Persistence::Transaction?) -> Array[ExecutionObservation]
|
|
59
60
|
def definition: (agent_id: String, ?scope: ::Phronomy::Persistence::Transaction?) -> { id: String, version: Integer }
|
data/sig/phronomy/agent.rbs
CHANGED
|
@@ -83,6 +83,7 @@ module Phronomy
|
|
|
83
83
|
def self.instructions: (?untyped text) { (untyped) -> untyped } -> untyped
|
|
84
84
|
| (?untyped text) -> untyped
|
|
85
85
|
def self.tools: (?Hash[Class, String?] definitions) -> untyped
|
|
86
|
+
def self.with_tools: (Hash[Class, String?] definitions, id: String, version: Integer) -> singleton(Base)
|
|
86
87
|
def self.tool_aliases: () -> Hash[Class, String]
|
|
87
88
|
def self.provider: (?Symbol name) -> Symbol?
|
|
88
89
|
def self.temperature: (?Float value) -> Float?
|
|
@@ -108,6 +109,7 @@ module Phronomy
|
|
|
108
109
|
attr_reader persistence: Store
|
|
109
110
|
def knowledge_snapshot: () -> Array[KnowledgeItem]
|
|
110
111
|
def event_listener: () -> (^(Object) -> void)?
|
|
112
|
+
def start_hosted: (reservation: ReservedExecution, input: String, ?config: Hash[Symbol, Object], ?output_schema: Hash[String | Symbol, untyped]?, ?schema_delivery: (:native | :none)?) -> HostedInvocation
|
|
111
113
|
def start_reserved_async: (reservation: ReservedExecution, input: String, ?config: Hash[Symbol, Object], ?output_schema: Hash[String | Symbol, untyped]?, ?schema_delivery: (:native | :none)?) -> TaskResult[execution_result]
|
|
112
114
|
def resume_async: (String execution_id, ?config: Hash[Symbol, Object]) -> TaskResult[execution_result]
|
|
113
115
|
def resume: (String execution_id, ?config: Hash[Symbol, Object]) -> execution_result
|
data/sig/phronomy/context.rbs
CHANGED
|
@@ -110,6 +110,8 @@ module Phronomy
|
|
|
110
110
|
attr_reader previous_manifest: LLMInputManifest?
|
|
111
111
|
attr_reader metadata: Hash[untyped, untyped]
|
|
112
112
|
def initialize: (agent_id: String, ?execution_id: String?, call_sequence: Integer, call_mode: Symbol, ?instruction: Array[InstructionItem], ?knowledge: Array[KnowledgeItem], ?tools: Array[ToolItem], ?conversation: Array[Array[ConversationItem]], token_budget: untyped, ?model_config: Hash[untyped, untyped], previous_manifest: LLMInputManifest?, ?metadata: Hash[untyped, untyped]) -> void
|
|
113
|
+
def input_token_limit: () -> Integer?
|
|
114
|
+
def with_input_token_limit: (Integer limit) -> ContextPolicyInput
|
|
113
115
|
end
|
|
114
116
|
|
|
115
117
|
class ContextPlan
|
|
@@ -124,6 +126,7 @@ module Phronomy
|
|
|
124
126
|
class ContextPolicy
|
|
125
127
|
def call: (ContextPolicyInput input) -> ContextPlan
|
|
126
128
|
|
|
129
|
+
def estimate_tokens: (untyped content) -> Integer
|
|
127
130
|
def instruction_item: (content: untyped, ?role: Symbol, ?kind: Symbol, ?content_format: Symbol?, ?required: bool, ?metadata: Hash[untyped, untyped]) -> ContextPolicyInput::InstructionItem
|
|
128
131
|
def knowledge_item: (content: untyped, ?role: Symbol, ?kind: Symbol, ?content_format: Symbol?, ?required: bool, ?metadata: Hash[untyped, untyped]) -> ContextPolicyInput::KnowledgeItem
|
|
129
132
|
def conversation_item: (content: untyped, role: Symbol, ?kind: Symbol, ?content_format: Symbol?, ?sequence: Integer?, ?required: bool, ?tool_call_id: String?, ?tool_call_ids: Array[String], ?delivery: Symbol, ?metadata: Hash[untyped, untyped]) -> ContextPolicyInput::ConversationItem
|
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
module Phronomy
|
|
2
2
|
module MultiAgent
|
|
3
3
|
class SharedState
|
|
4
|
+
interface _SharingTools
|
|
5
|
+
def call: (member: singleton(::Phronomy::Agent::Base), cycle: Integer) -> Hash[singleton(::Phronomy::Tool::Base), untyped]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
interface _SharingProvider
|
|
9
|
+
def open: (input: untyped, config: Hash[Symbol, untyped]) { (Binding) -> untyped } -> untyped
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class Binding
|
|
13
|
+
def self.new: (store: untyped, tools: _SharingTools) -> instance
|
|
14
|
+
attr_reader store: untyped
|
|
15
|
+
def tool_definitions: (member: singleton(::Phronomy::Agent::Base), cycle: Integer) -> Hash[singleton(::Phronomy::Tool::Base), String?]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class MemberLifecycleError < ::Phronomy::Error
|
|
19
|
+
attr_reader agent: ::Phronomy::Agent::Base
|
|
20
|
+
attr_reader result: untyped
|
|
21
|
+
attr_reader reason: Symbol
|
|
22
|
+
attr_reader primary_error: Exception?
|
|
23
|
+
attr_reader release_error: Exception?
|
|
24
|
+
def self.new: (agent: ::Phronomy::Agent::Base, result: untyped, reason: Symbol, ?primary_error: Exception?, ?release_error: Exception?) -> instance
|
|
25
|
+
end
|
|
26
|
+
|
|
4
27
|
class KnowledgeStore
|
|
5
28
|
def self.new: () -> instance
|
|
6
29
|
def read_all: () -> Array[Hash[Symbol, untyped]]
|
|
@@ -13,8 +36,9 @@ module Phronomy
|
|
|
13
36
|
def self.coordination: (?untyped text) -> untyped
|
|
14
37
|
def self.max_cycles: (?untyped value) -> Integer?
|
|
15
38
|
def self.timeout: (?untyped value) -> Float?
|
|
16
|
-
def self.terminate_when: () ?{ (
|
|
17
|
-
def self.aggregate: () ?{ (
|
|
39
|
+
def self.terminate_when: () ?{ (untyped) -> untyped } -> untyped
|
|
40
|
+
def self.aggregate: () ?{ (untyped) -> untyped } -> untyped
|
|
41
|
+
def self.sharing: (_SharingProvider provider, id: String, version: Integer) -> Hash[Symbol, untyped]
|
|
18
42
|
def invoke: (untyped input, ?config: Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
|
|
19
43
|
end
|
|
20
44
|
|
|
@@ -23,6 +47,8 @@ module Phronomy
|
|
|
23
47
|
def self.create: (?coordination_store: Store?, **Object options) ?{ (Object) -> void } -> instance
|
|
24
48
|
def self.load: (String agent_id, persistence: Agent::Store, coordination_store: Store, **Object options) ?{ (Object) -> void } -> instance
|
|
25
49
|
def self.subagent: (Symbol | String name, singleton(::Phronomy::Agent::Base) agent_class, ?on_error: Symbol, ?inherit_knowledge: bool) -> untyped
|
|
50
|
+
def unload_with_completed_child!: (String parent_execution_id, execution_id: String) -> bool
|
|
51
|
+
def approve_hosted_child: (String parent_execution_id, execution_id: String, approval_request_id: String, ?approved: bool, ?config: Hash[Symbol, untyped]) -> ::Phronomy::Agent::HostedInvocation
|
|
26
52
|
def self.registered_subagents: () -> Hash[Symbol | String, untyped]
|
|
27
53
|
def resume: (String execution_id, ?config: Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
|
|
28
54
|
def dispatch_parallel: (*Hash[Symbol, untyped] tasks, ?max_concurrency: Integer?, ?on_error: Symbol, ?timeout: Numeric?, ?cancellation_token: untyped, ?invocation_context: untyped, ?inherit_knowledge: bool) -> Array[untyped]
|
data/sig/phronomy/tool.rbs
CHANGED
|
@@ -55,6 +55,7 @@ module Phronomy
|
|
|
55
55
|
def requires_approval?: () -> untyped
|
|
56
56
|
def tool_origin: () -> Symbol
|
|
57
57
|
def approval_metadata: () -> Hash[untyped, untyped]
|
|
58
|
+
def approval_display: (arguments: Hash[String | Symbol, untyped], facts: Hash[untyped, untyped]) -> { arguments: Hash[String | Symbol, untyped], facts: Hash[untyped, untyped] }
|
|
58
59
|
def execute: (**untyped args) -> untyped
|
|
59
60
|
|
|
60
61
|
private
|
data/sig/phronomy/workflow.rbs
CHANGED
|
@@ -18,6 +18,7 @@ module Phronomy
|
|
|
18
18
|
def stream: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped) { (untyped) -> void } -> untyped
|
|
19
19
|
def resume: (state: untyped, ?input: untyped) -> untyped
|
|
20
20
|
def send_event: (state: untyped, event: Symbol, ?input: untyped) -> untyped
|
|
21
|
+
def send_saved_event: (workflow_instance_id: String, expected_revision: Integer, event: Symbol, ?input: untyped) -> untyped
|
|
21
22
|
def signal: (workflow_instance_id: String, event: Symbol, ?payload: untyped) -> bool
|
|
22
23
|
|
|
23
24
|
class Builder
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: phronomy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.33.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Raizo T.C.S
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby_llm
|
|
@@ -257,6 +257,7 @@ files:
|
|
|
257
257
|
- docs/archive/design/historical/06_design_decisions.md
|
|
258
258
|
- docs/async-composition.md
|
|
259
259
|
- docs/changelog/0.14-and-earlier.md
|
|
260
|
+
- docs/context-policy-budgeting.md
|
|
260
261
|
- docs/decisions/001-rubyllm-as-provider-layer.md
|
|
261
262
|
- docs/decisions/002-workflow-context-immutability.md
|
|
262
263
|
- docs/decisions/003-event-loop-singleton.md
|
|
@@ -333,6 +334,7 @@ files:
|
|
|
333
334
|
- docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md
|
|
334
335
|
- docs/design/durable-semantic-coordination/RECOVERY_CONTRACT_CLARIFICATIONS.md
|
|
335
336
|
- docs/design/durable-semantic-coordination/RESPONSIBILITY_BOUNDARY_REVIEW.md
|
|
337
|
+
- docs/examples/shared_state_custom_sharing.rb
|
|
336
338
|
- docs/execution-boundaries-preview.md
|
|
337
339
|
- docs/failure-reason-codes.md
|
|
338
340
|
- docs/features.md
|
|
@@ -376,6 +378,7 @@ files:
|
|
|
376
378
|
- docs/ordered-event-delivery.md
|
|
377
379
|
- docs/persistence-backends.md
|
|
378
380
|
- docs/runtime-and-concurrency.md
|
|
381
|
+
- docs/shared-state-sharing.md
|
|
379
382
|
- docs/workflow-completion.md
|
|
380
383
|
- examples/README.md
|
|
381
384
|
- gemfiles/http_minimum.gemfile
|
|
@@ -389,6 +392,7 @@ files:
|
|
|
389
392
|
- lib/phronomy/agent/agent_unloaded_error.rb
|
|
390
393
|
- lib/phronomy/agent/api/agent.rb
|
|
391
394
|
- lib/phronomy/agent/api/execution_rehydration_required_error.rb
|
|
395
|
+
- lib/phronomy/agent/approval_wait.rb
|
|
392
396
|
- lib/phronomy/agent/async_event_api.rb
|
|
393
397
|
- lib/phronomy/agent/base.rb
|
|
394
398
|
- lib/phronomy/agent/before_llm_input.rb
|
|
@@ -429,6 +433,7 @@ files:
|
|
|
429
433
|
- lib/phronomy/agent/knowledge_item.rb
|
|
430
434
|
- lib/phronomy/agent/lifecycle/agent_root.rb
|
|
431
435
|
- lib/phronomy/agent/lifecycle/default_persistence.rb
|
|
436
|
+
- lib/phronomy/agent/lifecycle/hosted_invocation.rb
|
|
432
437
|
- lib/phronomy/agent/lifecycle/lifetime_api.rb
|
|
433
438
|
- lib/phronomy/agent/lifecycle/operation_lease.rb
|
|
434
439
|
- lib/phronomy/agent/lifecycle/operation_result.rb
|
|
@@ -614,6 +619,8 @@ files:
|
|
|
614
619
|
- lib/phronomy/multi_agent/reserved_child_admission.rb
|
|
615
620
|
- lib/phronomy/multi_agent/runtime_binding/engine_environment.rb
|
|
616
621
|
- lib/phronomy/multi_agent/shared_state.rb
|
|
622
|
+
- lib/phronomy/multi_agent/shared_state/binding.rb
|
|
623
|
+
- lib/phronomy/multi_agent/shared_state/member_lifecycle_error.rb
|
|
617
624
|
- lib/phronomy/multi_agent/store.rb
|
|
618
625
|
- lib/phronomy/multi_agent/team_busy_error.rb
|
|
619
626
|
- lib/phronomy/multi_agent/team_coordinator.rb
|