phronomy 0.30.1 → 0.31.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 +65 -0
- data/README.md +7 -2
- data/docs/architecture/agent-tool-child-lifetime.md +141 -0
- data/docs/architecture/dx05-agent-scope.md +4 -2
- data/docs/architecture/structured-output-and-scope.md +73 -13
- data/docs/decisions/044-agent-default-and-one-shot-composition.md +5 -0
- data/docs/getting-started.md +26 -0
- data/docs/migrations/0.31.md +72 -0
- data/lib/phronomy/agent/async_event_api.rb +17 -8
- data/lib/phronomy/agent/base.rb +27 -2
- data/lib/phronomy/agent/composition/run_once.rb +11 -10
- data/lib/phronomy/agent/context_preparation.rb +1 -0
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +1 -1
- data/lib/phronomy/agent/execution/exact_execution.rb +5 -3
- data/lib/phronomy/agent/execution/execution_coordinator.rb +6 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +1 -6
- data/lib/phronomy/agent/execution/initial_preparation.rb +1 -1
- data/lib/phronomy/agent/execution/invocation_actions.rb +17 -15
- data/lib/phronomy/agent/execution_change.rb +4 -0
- data/lib/phronomy/agent/output_contract.rb +47 -5
- data/lib/phronomy/agent/persistence/codec.rb +16 -2
- data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +1 -0
- data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +2 -2
- data/lib/phronomy/agent/runtime_input.rb +7 -0
- data/lib/phronomy/engine/fsm_session.rb +7 -0
- data/lib/phronomy/execution/async_operation.rb +16 -0
- data/lib/phronomy/execution/concurrency/result_finalization.rb +106 -0
- data/lib/phronomy/generation/generator_verifier/agent_operation.rb +3 -3
- data/lib/phronomy/generation/generator_verifier.rb +6 -3
- data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +1 -1
- data/lib/phronomy/llm_adapter/backends/structured_response_capture.rb +3 -1
- data/lib/phronomy/tools/agent.rb +58 -22
- data/lib/phronomy/version.rb +1 -1
- data/sig/phronomy/agent.rbs +7 -5
- data/sig/phronomy/execution/async_operation.rbs +1 -0
- data/sig/phronomy/generation/generator_verifier.rbs +1 -1
- data/sig/phronomy/tools/agent.rbs +14 -0
- metadata +6 -2
data/lib/phronomy/tools/agent.rb
CHANGED
|
@@ -7,13 +7,30 @@ module Phronomy
|
|
|
7
7
|
# Agent-backed Tools are logically asynchronous rather than offloaded
|
|
8
8
|
# synchronous operations. Their ToolInvocation starts the child Agent and then
|
|
9
9
|
# returns to EventLoop immediately; the Tool completion handle settles when the
|
|
10
|
-
# child Agent
|
|
11
|
-
# consumed merely to wait for another Agent.
|
|
10
|
+
# child Agent's physical work and release finish. An OffloadPool worker is
|
|
11
|
+
# therefore never consumed merely to wait for another Agent.
|
|
12
12
|
class Agent < Phronomy::Tool::Base
|
|
13
13
|
execution_mode :cooperative
|
|
14
14
|
description "Wraps a Phronomy::Agent as a Tool"
|
|
15
15
|
param :input, type: :string, desc: "The input to forward to the wrapped Agent"
|
|
16
16
|
|
|
17
|
+
# A private child's operation ended, but its safe release failed. The live
|
|
18
|
+
# reference supports retrying unload_async without repeating business work.
|
|
19
|
+
# This error and its child reference are process-local, not durable state.
|
|
20
|
+
# @api public
|
|
21
|
+
class CleanupError < Phronomy::ToolError
|
|
22
|
+
attr_reader :child_agent, :agent_id, :result, :primary_error, :cleanup_error
|
|
23
|
+
|
|
24
|
+
def initialize(child_agent:, result:, primary_error:, cleanup_error:)
|
|
25
|
+
@child_agent = child_agent
|
|
26
|
+
@agent_id = child_agent.agent_id
|
|
27
|
+
@result = result
|
|
28
|
+
@primary_error = primary_error
|
|
29
|
+
@cleanup_error = cleanup_error
|
|
30
|
+
super("Agent Tool cleanup failed for #{@agent_id.inspect}", code: "tool.agent_cleanup_failed")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
17
34
|
class << self
|
|
18
35
|
def from_agent(agent_class, tool_name: nil, description: nil)
|
|
19
36
|
raise ArgumentError, "agent_class must be a Class" unless agent_class.is_a?(Class)
|
|
@@ -35,36 +52,34 @@ module Phronomy
|
|
|
35
52
|
# Preserve the synchronous Tool API for top-level callers. ToolInvocation
|
|
36
53
|
# never uses this path for Agent-backed Tools; it calls #call_async.
|
|
37
54
|
klass.define_method(:execute) do |input:, cancellation_token: nil|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
if Phronomy::WaitPolicy.blocking_forbidden?
|
|
56
|
+
raise Phronomy::EventLoopReentrancyError,
|
|
57
|
+
"Agent Tool execute cannot block EventLoop; use call_async"
|
|
41
58
|
end
|
|
42
|
-
|
|
43
|
-
# Preserve the creation arguments previously supplied by run_once.
|
|
44
|
-
persistence = Phronomy::Agent::DefaultPersistence.build
|
|
45
|
-
agent = agent_class.create(
|
|
46
|
-
context: nil,
|
|
47
|
-
knowledge: [],
|
|
48
|
-
persistence: persistence,
|
|
49
|
-
on_event: nil
|
|
50
|
-
)
|
|
51
|
-
result = agent.invoke(input, **invoke_options)
|
|
52
|
-
result[:output].to_s
|
|
59
|
+
execute_async(input: input, cancellation_token: cancellation_token).wait_result
|
|
53
60
|
end
|
|
54
61
|
|
|
55
62
|
# Internal asynchronous execution protocol used by Agent#call_async.
|
|
56
|
-
#
|
|
57
|
-
#
|
|
63
|
+
# C04 decides whether release is safe; C13 composes completion. The
|
|
64
|
+
# concrete Tool supplies only creation, output and cleanup policies.
|
|
58
65
|
klass.define_method(:execute_async) do |input:, cancellation_token: nil, config: {}|
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
unless config.nil? || config.is_a?(Hash)
|
|
67
|
+
raise ArgumentError, "Agent Tool config must be a Hash"
|
|
68
|
+
end
|
|
61
69
|
task_config = (config || {}).dup
|
|
62
70
|
if cancellation_token && !task_config[:cancellation_token]
|
|
63
71
|
task_config[:cancellation_token] = cancellation_token
|
|
64
72
|
end
|
|
65
73
|
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
persistence = Phronomy::Agent::DefaultPersistence.build
|
|
75
|
+
agent = agent_class.create(persistence: persistence)
|
|
76
|
+
Phronomy::AsyncOperation.finalize(name: "agent-tool-#{name}-release",
|
|
77
|
+
cleanup: -> { agent.unload_async },
|
|
78
|
+
on_cleanup_error: ->(value, primary, cleanup) { child_cleanup_failed(agent, value, primary, cleanup) }) do
|
|
79
|
+
Phronomy::AsyncOperation.call(name: "agent-tool-#{name}-invoke",
|
|
80
|
+
transform: ->(result) { result[:output].to_s }) do
|
|
81
|
+
agent.invoke_async(input, config: task_config)
|
|
82
|
+
end
|
|
68
83
|
end
|
|
69
84
|
end
|
|
70
85
|
klass.send(:private, :execute_async)
|
|
@@ -102,6 +117,27 @@ module Phronomy
|
|
|
102
117
|
|
|
103
118
|
private
|
|
104
119
|
|
|
120
|
+
def child_cleanup_failed(agent, result, primary_error, cleanup_error)
|
|
121
|
+
failure = CleanupError.new(child_agent: agent, result: result,
|
|
122
|
+
primary_error: primary_error, cleanup_error: cleanup_error)
|
|
123
|
+
# Always report cleanup failures: an outer observer may already have
|
|
124
|
+
# cancelled its handle, so returning an error alone is not sufficient.
|
|
125
|
+
report_child_cleanup_failure(failure)
|
|
126
|
+
raise failure, cause: primary_error || cleanup_error
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def report_child_cleanup_failure(failure)
|
|
130
|
+
details = {"agent_id" => failure.agent_id, "code" => failure.code,
|
|
131
|
+
"cleanup_error" => Phronomy::Error.diagnostic(failure.cleanup_error)}
|
|
132
|
+
details["primary_error"] = Phronomy::Error.diagnostic(failure.primary_error) if failure.primary_error
|
|
133
|
+
message = "[Phronomy] Agent Tool cleanup failed: #{details.inspect}"
|
|
134
|
+
logger = Phronomy::RuntimeSettings.current.logger
|
|
135
|
+
logger ? logger.error(message) : warn(message)
|
|
136
|
+
rescue
|
|
137
|
+
# Diagnostics cannot change the combined error or repeat the operation.
|
|
138
|
+
nil
|
|
139
|
+
end
|
|
140
|
+
|
|
105
141
|
# Subclasses created by .from_agent override this method.
|
|
106
142
|
# It deliberately remains private so it is not part of the public Tool API.
|
|
107
143
|
def execute_async(input:, cancellation_token: nil, config: {})
|
data/lib/phronomy/version.rb
CHANGED
data/sig/phronomy/agent.rbs
CHANGED
|
@@ -108,7 +108,7 @@ module Phronomy
|
|
|
108
108
|
attr_reader persistence: Store
|
|
109
109
|
def knowledge_snapshot: () -> Array[KnowledgeItem]
|
|
110
110
|
def event_listener: () -> (^(Object) -> void)?
|
|
111
|
-
def start_reserved_async: (reservation: ReservedExecution, input: String, ?config: Hash[Symbol, Object], ?output_schema: Hash[String | Symbol, untyped]?) -> TaskResult[execution_result]
|
|
111
|
+
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
112
|
def resume_async: (String execution_id, ?config: Hash[Symbol, Object]) -> TaskResult[execution_result]
|
|
113
113
|
def resume: (String execution_id, ?config: Hash[Symbol, Object]) -> execution_result
|
|
114
114
|
def cancel_async: (String execution_id) -> TaskResult[ExecutionObservation]
|
|
@@ -116,10 +116,10 @@ module Phronomy
|
|
|
116
116
|
|
|
117
117
|
# Public operation methods no longer accept per-invocation event listeners
|
|
118
118
|
# or event-listener blocks.
|
|
119
|
-
def invoke: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped, ?output_schema: Hash[String | Symbol, untyped]?) -> Hash[Symbol, untyped]
|
|
120
|
-
def invoke_async: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped, ?output_schema: Hash[String | Symbol, untyped]?) -> TaskResult[Hash[Symbol, untyped]]
|
|
121
|
-
def stream: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped, ?output_schema: Hash[String | Symbol, untyped]?) -> Hash[Symbol, untyped]
|
|
122
|
-
def stream_async: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped, ?output_schema: Hash[String | Symbol, untyped]?) -> TaskResult[Hash[Symbol, untyped]]
|
|
119
|
+
def invoke: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped, ?output_schema: Hash[String | Symbol, untyped]?, ?schema_delivery: (:native | :none)?) -> Hash[Symbol, untyped]
|
|
120
|
+
def invoke_async: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped, ?output_schema: Hash[String | Symbol, untyped]?, ?schema_delivery: (:native | :none)?) -> TaskResult[Hash[Symbol, untyped]]
|
|
121
|
+
def stream: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped, ?output_schema: Hash[String | Symbol, untyped]?, ?schema_delivery: (:native | :none)?) -> Hash[Symbol, untyped]
|
|
122
|
+
def stream_async: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped, ?output_schema: Hash[String | Symbol, untyped]?, ?schema_delivery: (:native | :none)?) -> TaskResult[Hash[Symbol, untyped]]
|
|
123
123
|
|
|
124
124
|
def approve: (String execution_id, approval_request_id: String, ?approved: bool, ?config: Hash[untyped, untyped]) -> untyped
|
|
125
125
|
def approve_async: (String execution_id, approval_request_id: String, ?approved: bool, ?config: Hash[untyped, untyped]) -> TaskResult[untyped]
|
|
@@ -131,6 +131,8 @@ module Phronomy
|
|
|
131
131
|
def resolve_async: (String execution_id, expected_execution_revision: Integer, subject: Hash[Symbol, untyped], outcome: Symbol, ?result: untyped, ?error: Exception) -> TaskResult[untyped]
|
|
132
132
|
|
|
133
133
|
def transcript: () -> Array[untyped]
|
|
134
|
+
def transcript_messages: () -> Array[LLMAdapter::Message]
|
|
135
|
+
def transcript_messages_async: () -> TaskResult[Array[LLMAdapter::Message]]
|
|
134
136
|
def add_knowledge: (untyped content, ?metadata: Hash[untyped, untyped]) -> self
|
|
135
137
|
def clear_transcript!: () -> untyped
|
|
136
138
|
def clear_knowledge!: () -> untyped
|
|
@@ -3,5 +3,6 @@ module Phronomy
|
|
|
3
3
|
def self.map: [A, B] (_CompletionHandle[A] source, name: String, ?on_error: (^(Exception) -> B)?) { (A) -> B } -> TaskResult[B]
|
|
4
4
|
def self.call: [A, B] (name: String, ?on_error: (^(Exception) -> B)?, ?transform: ^(A) -> B) { () -> _CompletionHandle[A] } -> TaskResult[B]
|
|
5
5
|
def self.capture: [V] (name: String, ?on_error: (^(Exception) -> V)?) { () -> V } -> TaskResult[V]
|
|
6
|
+
def self.finalize: [V] (name: String, cleanup: ^() -> TaskResult[untyped], on_cleanup_error: ^(V?, Exception?, Exception) -> Exception) { () -> TaskResult[V] } -> TaskResult[V]
|
|
6
7
|
end
|
|
7
8
|
end
|
|
@@ -10,7 +10,7 @@ module Phronomy
|
|
|
10
10
|
def self.open: [R] (**untyped options) { (GeneratorVerifier pipeline) -> (R | TaskResult[R]) } -> R
|
|
11
11
|
def initialize: (draft_agent: singleton(Agent::Base), review_agent: singleton(Agent::Base),
|
|
12
12
|
draft_prompt_builder: untyped, review_prompt_builder: untyped,
|
|
13
|
-
?citation_schema: Hash[String | Symbol, untyped],
|
|
13
|
+
?citation_schema: Hash[String | Symbol, untyped], ?schema_delivery: (:native | :none),
|
|
14
14
|
?confidence_threshold: Numeric, ?max_iterations: Integer,
|
|
15
15
|
?raise_if_unaccepted: bool, ?on_approval: _ApprovalListener?) -> void
|
|
16
16
|
def invoke: (untyped input, ?config: Hash[Symbol, untyped]) -> Result
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Phronomy
|
|
2
|
+
module Tools
|
|
3
|
+
class Agent < Phronomy::Tool::Base
|
|
4
|
+
class CleanupError < Phronomy::ToolError
|
|
5
|
+
attr_reader child_agent: Phronomy::Agent::Base
|
|
6
|
+
attr_reader agent_id: String
|
|
7
|
+
attr_reader result: untyped
|
|
8
|
+
attr_reader primary_error: Exception?
|
|
9
|
+
attr_reader cleanup_error: Exception
|
|
10
|
+
def initialize: (child_agent: Phronomy::Agent::Base, result: untyped, primary_error: Exception?, cleanup_error: Exception) -> void
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
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.31.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-10-
|
|
11
|
+
date: 2026-10-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby_llm
|
|
@@ -193,6 +193,7 @@ files:
|
|
|
193
193
|
- docs/architecture/agent-configuration-and-tool-binding.md
|
|
194
194
|
- docs/architecture/agent-context.md
|
|
195
195
|
- docs/architecture/agent-iteration-budget.md
|
|
196
|
+
- docs/architecture/agent-tool-child-lifetime.md
|
|
196
197
|
- docs/architecture/agent-transition-ownership.md
|
|
197
198
|
- docs/architecture/backend-groups.md
|
|
198
199
|
- docs/architecture/before-llm-input.md
|
|
@@ -341,6 +342,7 @@ files:
|
|
|
341
342
|
- docs/migrations/0.16.md
|
|
342
343
|
- docs/migrations/0.19.md
|
|
343
344
|
- docs/migrations/0.22.md
|
|
345
|
+
- docs/migrations/0.31.md
|
|
344
346
|
- docs/migrations/durable-semantic-coordination-v2.md
|
|
345
347
|
- docs/migrations/handoff-runner-multi-agent.md
|
|
346
348
|
- docs/migrations/neutral-storage-spi.md
|
|
@@ -534,6 +536,7 @@ files:
|
|
|
534
536
|
- lib/phronomy/execution/concurrency/physical_completion_task.rb
|
|
535
537
|
- lib/phronomy/execution/concurrency/result_collector.rb
|
|
536
538
|
- lib/phronomy/execution/concurrency/result_composition.rb
|
|
539
|
+
- lib/phronomy/execution/concurrency/result_finalization.rb
|
|
537
540
|
- lib/phronomy/execution/concurrency/result_subscriptions.rb
|
|
538
541
|
- lib/phronomy/execution/concurrency/subscriptions.rb
|
|
539
542
|
- lib/phronomy/execution/concurrency/worker_input_restricted.rb
|
|
@@ -818,6 +821,7 @@ files:
|
|
|
818
821
|
- sig/phronomy/storage/backends/in_memory.rbs
|
|
819
822
|
- sig/phronomy/storage/contracts.rbs
|
|
820
823
|
- sig/phronomy/tool.rbs
|
|
824
|
+
- sig/phronomy/tools/agent.rbs
|
|
821
825
|
- sig/phronomy/tracing/observation.rbs
|
|
822
826
|
- sig/phronomy/vector_store/async/async_client.rbs
|
|
823
827
|
- sig/phronomy/vector_store/base.rbs
|