phronomy 0.15.1 → 0.16.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 +105 -28
- data/README.md +300 -75
- data/benchmark/bench_agent_invoke.rb +3 -0
- data/benchmark/bench_regression.rb +2 -18
- data/benchmark/bench_tool_schema.rb +1 -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/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 +42 -10
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +50 -11
- data/lib/phronomy/agent/agent_root.rb +67 -0
- data/lib/phronomy/agent/async_event_api.rb +55 -475
- data/lib/phronomy/agent/base.rb +301 -285
- data/lib/phronomy/agent/concerns/before_llm_input.rb +66 -0
- data/lib/phronomy/agent/context_assembler.rb +321 -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 +188 -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 +167 -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 +1123 -0
- data/lib/phronomy/agent/fsm_runtime_adapter.rb +210 -0
- data/lib/phronomy/agent/immutable.rb +31 -0
- data/lib/phronomy/agent/journal_projection.rb +34 -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 +298 -0
- data/lib/phronomy/agent/token_budget_resolver.rb +69 -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.rb +14 -16
- data/lib/phronomy/agent_busy_error.rb +5 -0
- data/lib/phronomy/canonical_json.rb +136 -0
- data/lib/phronomy/configuration.rb +9 -4
- data/lib/phronomy/content_store/base.rb +51 -0
- data/lib/phronomy/context_budget_exceeded_error.rb +8 -0
- data/lib/phronomy/engine/event_loop.rb +3 -0
- 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/assembler.rb +8 -8
- data/lib/phronomy/multi_agent/orchestrator.rb +1 -0
- data/lib/phronomy/multi_agent/parallel_tool_chat.rb +7 -5
- data/lib/phronomy/multi_agent/team_coordinator.rb +6 -2
- 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/version.rb +1 -1
- data/lib/phronomy.rb +11 -0
- data/scripts/add_to_h_to_token_doubles.rb +33 -0
- data/scripts/add_to_h_unnamed_doubles.rb +27 -0
- 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 -7
- 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
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Migrates spec files to add agent_definition to Agent::Base subclasses.
|
|
5
|
+
# - Named classes: inserts after the class declaration line
|
|
6
|
+
# - Anonymous blocks (do...end): inserts after the opening `do`
|
|
7
|
+
# - Inline anonymous ({ ... }): expands to multi-line with agent_definition
|
|
8
|
+
# Safe: skips classes that already have agent_definition
|
|
9
|
+
|
|
10
|
+
require "fileutils"
|
|
11
|
+
|
|
12
|
+
SPEC_DIR = File.expand_path("../spec", __dir__)
|
|
13
|
+
AGENT_BASE_PATTERN = /Phronomy::Agent::Base/
|
|
14
|
+
|
|
15
|
+
def snake_case(class_name)
|
|
16
|
+
# Remove trailing Agent/Tool suffixes, snake_case the name
|
|
17
|
+
name = class_name.split("::").last
|
|
18
|
+
name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
19
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
20
|
+
.downcase
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
counter = 0
|
|
24
|
+
files_modified = 0
|
|
25
|
+
|
|
26
|
+
Dir.glob("#{SPEC_DIR}/**/*.rb").sort.each do |file|
|
|
27
|
+
lines = File.readlines(file, chomp: false)
|
|
28
|
+
new_lines = []
|
|
29
|
+
modified = false
|
|
30
|
+
i = 0
|
|
31
|
+
|
|
32
|
+
while i < lines.length
|
|
33
|
+
line = lines[i]
|
|
34
|
+
|
|
35
|
+
# ── Named class: class FooAgent < Phronomy::Agent::Base ──────────────────
|
|
36
|
+
if line =~ /^(\s*)class\s+(\w+)\s*<\s*(?:::)?Phronomy::Agent::Base\s*$/
|
|
37
|
+
indent = $1
|
|
38
|
+
class_name = $2
|
|
39
|
+
id = snake_case(class_name).sub(/_agent$/, "-agent").tr("_", "-")
|
|
40
|
+
id = "#{id}-agent" unless id.end_with?("-agent")
|
|
41
|
+
new_lines << line
|
|
42
|
+
i += 1
|
|
43
|
+
# Check if agent_definition already present in the class body
|
|
44
|
+
look_ahead = lines[i..]&.take(10) || []
|
|
45
|
+
unless look_ahead.any? { |l| l =~ /agent_definition/ }
|
|
46
|
+
new_lines << "#{indent} agent_definition id: #{id.inspect}, version: 1\n"
|
|
47
|
+
counter += 1
|
|
48
|
+
modified = true
|
|
49
|
+
end
|
|
50
|
+
next
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# ── Anonymous block (do...end): Class.new(Phronomy::Agent::Base) do ─────
|
|
54
|
+
if line =~ /^(\s*)([\w.]*\s*=\s*)?Class\.new\((?:::)?Phronomy::Agent::Base\)\s+do\s*(\|\w+\|)?\s*$/
|
|
55
|
+
indent = $1
|
|
56
|
+
# Check if agent_definition already present
|
|
57
|
+
look_ahead = lines[i + 1..]&.take(10) || []
|
|
58
|
+
new_lines << line
|
|
59
|
+
i += 1
|
|
60
|
+
unless look_ahead.any? { |l| l =~ /agent_definition/ }
|
|
61
|
+
# Derive indentation from the `do` block body (first non-blank line)
|
|
62
|
+
inner = lines[i..]&.take(5)&.find { |l| l.strip.length > 0 } || ""
|
|
63
|
+
inner_indent = inner.match(/^(\s*)/)[1]
|
|
64
|
+
inner_indent = "#{indent} " if inner_indent.strip.length > 0 && inner_indent.length < indent.length + 2
|
|
65
|
+
new_lines << "#{inner_indent}agent_definition id: \"test-agent-#{counter += 1}\", version: 1\n"
|
|
66
|
+
modified = true
|
|
67
|
+
end
|
|
68
|
+
next
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# ── Inline anonymous: Class.new(Phronomy::Agent::Base) { ... } ───────────
|
|
72
|
+
if line =~ /^(\s*)((?:[\w.]*\s*=\s*)?)Class\.new\((?:::)?Phronomy::Agent::Base\)\s*\{(.+)\}\s*$/
|
|
73
|
+
indent = $1
|
|
74
|
+
assignment = $2
|
|
75
|
+
body = $3
|
|
76
|
+
# Skip if agent_definition already present
|
|
77
|
+
if /agent_definition/.match?(body)
|
|
78
|
+
new_lines << line
|
|
79
|
+
else
|
|
80
|
+
id = counter += 1
|
|
81
|
+
definition_line = "agent_definition id: \"test-agent-#{id}\", version: 1;"
|
|
82
|
+
new_lines << "#{indent}#{assignment}Class.new(Phronomy::Agent::Base) do\n"
|
|
83
|
+
new_lines << "#{indent} #{definition_line}\n"
|
|
84
|
+
# Rewrite the original body lines, stripping leading spaces
|
|
85
|
+
body.strip.split(";").each do |stmt|
|
|
86
|
+
stmt = stmt.strip
|
|
87
|
+
next if stmt.empty?
|
|
88
|
+
new_lines << "#{indent} #{stmt}\n"
|
|
89
|
+
end
|
|
90
|
+
new_lines << "#{indent}end\n"
|
|
91
|
+
modified = true
|
|
92
|
+
end
|
|
93
|
+
i += 1
|
|
94
|
+
next
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
new_lines << line
|
|
98
|
+
i += 1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if modified
|
|
102
|
+
File.write(file, new_lines.join)
|
|
103
|
+
files_modified += 1
|
|
104
|
+
puts " patched: #{file.sub("#{SPEC_DIR}/", "spec/")}"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
puts "\nDone: #{counter} agent_definition insertions in #{files_modified} files."
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Second pass: handles patterns missed by the first script
|
|
5
|
+
# - Class.new(Phronomy::Agent::Base) { ... }.new (inline + .new suffix)
|
|
6
|
+
# - Class.new(Phronomy::Agent::Base).new (no block + .new)
|
|
7
|
+
# - { Class.new(Phronomy::Agent::Base) } (no block, inside outer { })
|
|
8
|
+
|
|
9
|
+
require "fileutils"
|
|
10
|
+
|
|
11
|
+
SPEC_DIR = File.expand_path("../spec", __dir__)
|
|
12
|
+
counter = 200
|
|
13
|
+
|
|
14
|
+
files_modified = 0
|
|
15
|
+
|
|
16
|
+
Dir.glob("#{SPEC_DIR}/**/*.rb").sort.each do |file|
|
|
17
|
+
content = File.read(file)
|
|
18
|
+
new_content = content.dup
|
|
19
|
+
|
|
20
|
+
# Pattern A: Class.new(Phronomy::Agent::Base) { body }.new
|
|
21
|
+
new_content = new_content.gsub(
|
|
22
|
+
/Class\.new\(Phronomy::Agent::Base\) \{([^}]*)\}\.new/
|
|
23
|
+
) do
|
|
24
|
+
body = $1
|
|
25
|
+
next $& if body.include?("agent_definition")
|
|
26
|
+
counter += 1
|
|
27
|
+
"Class.new(Phronomy::Agent::Base) { agent_definition id: \"test-agent-#{counter}\", version: 1;#{body}}.new"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Pattern B: Class.new(Phronomy::Agent::Base).new (no block, chained .new)
|
|
31
|
+
new_content = new_content.gsub(
|
|
32
|
+
/Class\.new\(Phronomy::Agent::Base\)\.new\b/
|
|
33
|
+
) do
|
|
34
|
+
counter += 1
|
|
35
|
+
"Class.new(Phronomy::Agent::Base) { agent_definition id: \"test-agent-#{counter}\", version: 1 }.new"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Pattern C: { Class.new(Phronomy::Agent::Base) } (no block, inside outer block)
|
|
39
|
+
new_content = new_content.gsub(
|
|
40
|
+
"{ Class.new(Phronomy::Agent::Base) }"
|
|
41
|
+
) do
|
|
42
|
+
counter += 1
|
|
43
|
+
"{ Class.new(Phronomy::Agent::Base) { agent_definition id: \"test-agent-#{counter}\", version: 1 } }"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if new_content != content
|
|
47
|
+
File.write(file, new_content)
|
|
48
|
+
files_modified += 1
|
|
49
|
+
puts " patched: #{file.sub("#{SPEC_DIR}/", "spec/")}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
puts "\nSecond pass done: #{files_modified} files modified."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Adds agent_definition to single-line inline anonymous blocks like:
|
|
5
|
+
# Class.new(Phronomy::Agent::Base) { model "test-model" }
|
|
6
|
+
|
|
7
|
+
counter = 300
|
|
8
|
+
files = Dir.glob("spec/**/*.rb").sort
|
|
9
|
+
|
|
10
|
+
files.each do |f|
|
|
11
|
+
content = File.read(f)
|
|
12
|
+
modified = content.gsub(
|
|
13
|
+
/Class\.new\(Phronomy::Agent::Base\) \{ ([^{}]+) \}/
|
|
14
|
+
) do
|
|
15
|
+
body = $1
|
|
16
|
+
next $& if body.include?("agent_definition")
|
|
17
|
+
counter += 1
|
|
18
|
+
"Class.new(Phronomy::Agent::Base) { agent_definition id: \"test-agent-#{counter}\", version: 1; #{body} }"
|
|
19
|
+
end
|
|
20
|
+
if modified != content
|
|
21
|
+
File.write(f, modified)
|
|
22
|
+
puts " patched: #{f}"
|
|
23
|
+
end
|
|
24
|
+
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.16.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-08-
|
|
11
|
+
date: 2026-08-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby_llm
|
|
@@ -16,7 +16,7 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1.
|
|
19
|
+
version: '1.15'
|
|
20
20
|
- - "<"
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
22
|
version: '2'
|
|
@@ -26,7 +26,7 @@ dependencies:
|
|
|
26
26
|
requirements:
|
|
27
27
|
- - ">="
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '1.
|
|
29
|
+
version: '1.15'
|
|
30
30
|
- - "<"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
32
|
version: '2'
|
|
@@ -116,20 +116,23 @@ files:
|
|
|
116
116
|
- docs/decisions/010-cooperative-first-concurrency.md
|
|
117
117
|
- docs/decisions/011-build-context-as-single-llm-input-authority.md
|
|
118
118
|
- docs/decisions/011-delegate-transport-policy-to-adapters.md
|
|
119
|
+
- docs/decisions/012-canonical-execution-log-and-context-policy.md
|
|
119
120
|
- docs/mcp-client.md
|
|
120
121
|
- examples/workflows/agent_event_mapping.rb
|
|
121
122
|
- examples/workflows/generic_task_event_mapping.rb
|
|
122
123
|
- gemfiles/mcp_1_0.gemfile
|
|
123
124
|
- lib/phronomy.rb
|
|
124
125
|
- lib/phronomy/agent.rb
|
|
126
|
+
- lib/phronomy/agent/activation_registry.rb
|
|
127
|
+
- lib/phronomy/agent/agent_execution.rb
|
|
128
|
+
- lib/phronomy/agent/agent_execution_activation.rb
|
|
125
129
|
- lib/phronomy/agent/agent_invocation.rb
|
|
126
|
-
- lib/phronomy/agent/agent_invocation_registry.rb
|
|
127
130
|
- lib/phronomy/agent/agent_invocation_session_builder.rb
|
|
131
|
+
- lib/phronomy/agent/agent_root.rb
|
|
128
132
|
- lib/phronomy/agent/approval_evaluation_request.rb
|
|
129
133
|
- lib/phronomy/agent/async_event_api.rb
|
|
130
134
|
- lib/phronomy/agent/base.rb
|
|
131
|
-
- lib/phronomy/agent/
|
|
132
|
-
- lib/phronomy/agent/concerns/before_completion.rb
|
|
135
|
+
- lib/phronomy/agent/concerns/before_llm_input.rb
|
|
133
136
|
- lib/phronomy/agent/concerns/error_translation.rb
|
|
134
137
|
- lib/phronomy/agent/concerns/filterable.rb
|
|
135
138
|
- lib/phronomy/agent/context/capability/base.rb
|
|
@@ -137,16 +140,51 @@ files:
|
|
|
137
140
|
- lib/phronomy/agent/context/knowledge/base.rb
|
|
138
141
|
- lib/phronomy/agent/context/knowledge/entity_knowledge.rb
|
|
139
142
|
- lib/phronomy/agent/context/knowledge/static_knowledge.rb
|
|
143
|
+
- lib/phronomy/agent/context_assembler.rb
|
|
144
|
+
- lib/phronomy/agent/context_candidate.rb
|
|
145
|
+
- lib/phronomy/agent/context_candidate_resolver.rb
|
|
146
|
+
- lib/phronomy/agent/context_importer.rb
|
|
147
|
+
- lib/phronomy/agent/context_parts/budget/token_budget_packer.rb
|
|
148
|
+
- lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb
|
|
149
|
+
- lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb
|
|
150
|
+
- lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb
|
|
151
|
+
- lib/phronomy/agent/context_parts/validators/final_budget_validator.rb
|
|
152
|
+
- lib/phronomy/agent/context_plan.rb
|
|
153
|
+
- lib/phronomy/agent/context_plan_validator.rb
|
|
154
|
+
- lib/phronomy/agent/context_policies/default.rb
|
|
155
|
+
- lib/phronomy/agent/context_policy.rb
|
|
156
|
+
- lib/phronomy/agent/context_policy_descriptor.rb
|
|
157
|
+
- lib/phronomy/agent/context_policy_registry.rb
|
|
158
|
+
- lib/phronomy/agent/context_request.rb
|
|
159
|
+
- lib/phronomy/agent/context_selection_unit.rb
|
|
160
|
+
- lib/phronomy/agent/derived_content_spec.rb
|
|
161
|
+
- lib/phronomy/agent/execution_coordinator.rb
|
|
162
|
+
- lib/phronomy/agent/fsm_runtime_adapter.rb
|
|
163
|
+
- lib/phronomy/agent/immutable.rb
|
|
164
|
+
- lib/phronomy/agent/journal_projection.rb
|
|
165
|
+
- lib/phronomy/agent/journal_record.rb
|
|
166
|
+
- lib/phronomy/agent/llm_call_record.rb
|
|
167
|
+
- lib/phronomy/agent/llm_input_build_context.rb
|
|
168
|
+
- lib/phronomy/agent/llm_input_manifest.rb
|
|
169
|
+
- lib/phronomy/agent/llm_input_patch.rb
|
|
140
170
|
- lib/phronomy/agent/llm_operation_result.rb
|
|
141
171
|
- lib/phronomy/agent/phase_machine_builder.rb
|
|
172
|
+
- lib/phronomy/agent/provider_call_outcome.rb
|
|
173
|
+
- lib/phronomy/agent/ruby_llm_materializer.rb
|
|
142
174
|
- lib/phronomy/agent/runner.rb
|
|
143
175
|
- lib/phronomy/agent/shared_state.rb
|
|
176
|
+
- lib/phronomy/agent/token_budget_resolver.rb
|
|
144
177
|
- lib/phronomy/agent/tool_approval_request.rb
|
|
145
178
|
- lib/phronomy/agent/tool_call_intercepted.rb
|
|
179
|
+
- lib/phronomy/agent/tool_definition_set.rb
|
|
146
180
|
- lib/phronomy/agent/tool_executor.rb
|
|
147
181
|
- lib/phronomy/agent/tool_invocation.rb
|
|
148
182
|
- lib/phronomy/agent/tool_invocation_session_builder.rb
|
|
183
|
+
- lib/phronomy/agent_busy_error.rb
|
|
184
|
+
- lib/phronomy/canonical_json.rb
|
|
149
185
|
- lib/phronomy/configuration.rb
|
|
186
|
+
- lib/phronomy/content_store/base.rb
|
|
187
|
+
- lib/phronomy/context_budget_exceeded_error.rb
|
|
150
188
|
- lib/phronomy/diagnostics.rb
|
|
151
189
|
- lib/phronomy/engine/concurrency/async_queue.rb
|
|
152
190
|
- lib/phronomy/engine/concurrency/blocking_adapter_pool.rb
|
|
@@ -188,6 +226,7 @@ files:
|
|
|
188
226
|
- lib/phronomy/eval/scorer/includes_scorer.rb
|
|
189
227
|
- lib/phronomy/eval/scorer/llm_judge.rb
|
|
190
228
|
- lib/phronomy/event.rb
|
|
229
|
+
- lib/phronomy/execution_rehydration_required_error.rb
|
|
191
230
|
- lib/phronomy/filter.rb
|
|
192
231
|
- lib/phronomy/filter/base.rb
|
|
193
232
|
- lib/phronomy/filter/prompt_injection_filter.rb
|
|
@@ -195,6 +234,7 @@ files:
|
|
|
195
234
|
- lib/phronomy/invalid_async_entry_action_error.rb
|
|
196
235
|
- lib/phronomy/invalid_async_transition_action_error.rb
|
|
197
236
|
- lib/phronomy/invalid_async_workflow_action_error.rb
|
|
237
|
+
- lib/phronomy/invalid_context_budget_configuration_error.rb
|
|
198
238
|
- lib/phronomy/invocation_context.rb
|
|
199
239
|
- lib/phronomy/knowledge_source.rb
|
|
200
240
|
- lib/phronomy/llm_adapter.rb
|
|
@@ -213,6 +253,8 @@ files:
|
|
|
213
253
|
- lib/phronomy/output_parser/base.rb
|
|
214
254
|
- lib/phronomy/output_parser/json_parser.rb
|
|
215
255
|
- lib/phronomy/output_parser/structured_parser.rb
|
|
256
|
+
- lib/phronomy/persistence.rb
|
|
257
|
+
- lib/phronomy/persistence/in_memory.rb
|
|
216
258
|
- lib/phronomy/ruby_llm_patches.rb
|
|
217
259
|
- lib/phronomy/runnable.rb
|
|
218
260
|
- lib/phronomy/state_store/base.rb
|
|
@@ -251,11 +293,16 @@ files:
|
|
|
251
293
|
- lib/phronomy/workflow/phase_machine_builder.rb
|
|
252
294
|
- lib/phronomy/workflow_context.rb
|
|
253
295
|
- lib/phronomy/workflow_runner.rb
|
|
296
|
+
- scripts/add_to_h_to_token_doubles.rb
|
|
297
|
+
- scripts/add_to_h_unnamed_doubles.rb
|
|
254
298
|
- scripts/api_snapshot.rb
|
|
255
299
|
- scripts/check_api_annotations.rb
|
|
256
300
|
- scripts/check_private_enforcement.rb
|
|
257
301
|
- scripts/check_readme_ruby.rb
|
|
258
302
|
- scripts/check_readme_runnable.rb
|
|
303
|
+
- scripts/migrate_spec_agent_definition.rb
|
|
304
|
+
- scripts/migrate_spec_agent_definition_pass2.rb
|
|
305
|
+
- scripts/migrate_spec_inline_pass3.rb
|
|
259
306
|
- scripts/run_mutation.sh
|
|
260
307
|
- sig/phronomy.rbs
|
|
261
308
|
homepage: https://github.com/Raizo-TCS/phronomy
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Phronomy
|
|
4
|
-
module Agent
|
|
5
|
-
# In-process registry for suspended AgentInvocation aggregates.
|
|
6
|
-
#
|
|
7
|
-
# The registry is the single in-process source for pending Human approval.
|
|
8
|
-
# Cross-process persistence remains outside the scope of this implementation.
|
|
9
|
-
#
|
|
10
|
-
# @api private
|
|
11
|
-
module AgentInvocationRegistry
|
|
12
|
-
Entry = Struct.new(:invocation, :approval_request)
|
|
13
|
-
|
|
14
|
-
@entries = {}
|
|
15
|
-
@approval_index = {}
|
|
16
|
-
@mutex = Mutex.new
|
|
17
|
-
|
|
18
|
-
def self.store_suspended(invocation, approval_request)
|
|
19
|
-
@mutex.synchronize do
|
|
20
|
-
invocation_id = invocation.id
|
|
21
|
-
request_id = approval_request.id
|
|
22
|
-
if @entries.key?(invocation_id) || @approval_index.key?(request_id)
|
|
23
|
-
raise Phronomy::Error,
|
|
24
|
-
"Suspended AgentInvocation is already registered: #{invocation_id}"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
@entries[invocation_id] = Entry.new(
|
|
28
|
-
invocation: invocation,
|
|
29
|
-
approval_request: approval_request
|
|
30
|
-
)
|
|
31
|
-
@approval_index[request_id] = invocation_id
|
|
32
|
-
end
|
|
33
|
-
approval_request
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Atomically removes and returns one pending approval aggregate.
|
|
37
|
-
# Duplicate approval commands therefore cannot execute the Tool twice.
|
|
38
|
-
def self.consume_approval(agent_invocation_id, approval_request_id)
|
|
39
|
-
@mutex.synchronize do
|
|
40
|
-
indexed_invocation_id = @approval_index[approval_request_id.to_s]
|
|
41
|
-
return nil unless indexed_invocation_id == agent_invocation_id.to_s
|
|
42
|
-
|
|
43
|
-
entry = @entries.delete(indexed_invocation_id)
|
|
44
|
-
return nil unless entry
|
|
45
|
-
|
|
46
|
-
@approval_index.delete(entry.approval_request.id)
|
|
47
|
-
entry
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def self.lookup(agent_invocation_id)
|
|
52
|
-
@mutex.synchronize { @entries[agent_invocation_id.to_s] }
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def self.exists?(agent_invocation_id)
|
|
56
|
-
@mutex.synchronize { @entries.key?(agent_invocation_id.to_s) }
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def self.remove_terminal(agent_invocation_id)
|
|
60
|
-
@mutex.synchronize do
|
|
61
|
-
entry = @entries.delete(agent_invocation_id.to_s)
|
|
62
|
-
@approval_index.delete(entry.approval_request.id) if entry
|
|
63
|
-
entry
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def self.clear!
|
|
68
|
-
@mutex.synchronize do
|
|
69
|
-
@entries.clear
|
|
70
|
-
@approval_index.clear
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Phronomy
|
|
4
|
-
module Agent
|
|
5
|
-
# Passed to every before_completion hook callable.
|
|
6
|
-
# Provides read access to the agent, assembled messages, and invocation config.
|
|
7
|
-
# Hooks may inspect these and return a Hash of params to merge into the LLM request.
|
|
8
|
-
#
|
|
9
|
-
# @example Reading context inside a hook
|
|
10
|
-
# Phronomy.configure do |cfg|
|
|
11
|
-
# cfg.before_completion = lambda do |ctx|
|
|
12
|
-
# Rails.logger.info "LLM request: model=#{ctx.params[:model]}"
|
|
13
|
-
# { temperature: 0.2 }
|
|
14
|
-
# end
|
|
15
|
-
# end
|
|
16
|
-
class BeforeCompletionContext
|
|
17
|
-
# The agent instance making the LLM call.
|
|
18
|
-
# @return [Phronomy::Agent::Base]
|
|
19
|
-
attr_reader :agent
|
|
20
|
-
|
|
21
|
-
# Messages currently assembled for this invocation (read-only snapshot).
|
|
22
|
-
# @return [Array]
|
|
23
|
-
attr_reader :messages
|
|
24
|
-
|
|
25
|
-
# Runtime config hash passed to invoke/stream (e.g. thread_id, memory).
|
|
26
|
-
# @return [Hash]
|
|
27
|
-
attr_reader :config
|
|
28
|
-
|
|
29
|
-
# Current LLM params being built (model, temperature, etc.).
|
|
30
|
-
# Read-only; return a Hash from the hook to merge overrides.
|
|
31
|
-
# @return [Hash]
|
|
32
|
-
attr_reader :params
|
|
33
|
-
|
|
34
|
-
# @param agent [Phronomy::Agent::Base]
|
|
35
|
-
# @param messages [Array]
|
|
36
|
-
# @param config [Hash]
|
|
37
|
-
# @param params [Hash] initial params (model, temperature already set on chat)
|
|
38
|
-
# @api public
|
|
39
|
-
def initialize(agent:, messages:, config:, params: {})
|
|
40
|
-
@agent = agent
|
|
41
|
-
@messages = messages.dup.freeze
|
|
42
|
-
@config = config
|
|
43
|
-
@params = params.dup.freeze
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Phronomy
|
|
4
|
-
module Agent
|
|
5
|
-
module Concerns
|
|
6
|
-
# Adds before_completion hook support to an agent.
|
|
7
|
-
#
|
|
8
|
-
# Included in {Phronomy::Agent::Base}. Hooks are executed just before every
|
|
9
|
-
# LLM call (global → class → instance order) and may inject or override
|
|
10
|
-
# LLM parameters such as temperature or model.
|
|
11
|
-
# @api private
|
|
12
|
-
module BeforeCompletion
|
|
13
|
-
def self.included(base)
|
|
14
|
-
base.extend(ClassMethods)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Class-level DSL methods mixed into the including agent class.
|
|
18
|
-
module ClassMethods
|
|
19
|
-
# Sets or reads the class-level before_completion hook.
|
|
20
|
-
# The hook is called before every LLM request for instances of this class.
|
|
21
|
-
# Receives a {Phronomy::Agent::BeforeCompletionContext}; must return a Hash
|
|
22
|
-
# of params to merge into the LLM call, or nil to pass through unchanged.
|
|
23
|
-
#
|
|
24
|
-
# @param callable [#call, nil] lambda/proc to register, or nil to clear
|
|
25
|
-
# @return [#call, nil]
|
|
26
|
-
# @example
|
|
27
|
-
# class MyAgent < Phronomy::Agent::Base
|
|
28
|
-
# before_completion ->(ctx) { { temperature: 0.2 } }
|
|
29
|
-
# end
|
|
30
|
-
# @api private
|
|
31
|
-
def before_completion(callable = nil)
|
|
32
|
-
if callable.nil? && !block_given?
|
|
33
|
-
@before_completion
|
|
34
|
-
else
|
|
35
|
-
@before_completion = callable
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# @return [#call, nil]
|
|
40
|
-
# @api private
|
|
41
|
-
def _before_completion
|
|
42
|
-
@before_completion
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Instance-level before_completion hook. When set, takes precedence over
|
|
47
|
-
# the class-level hook for this specific agent instance only.
|
|
48
|
-
# @return [#call, nil]
|
|
49
|
-
attr_accessor :before_completion
|
|
50
|
-
|
|
51
|
-
private
|
|
52
|
-
|
|
53
|
-
# Collects and runs all registered before_completion hooks in order
|
|
54
|
-
# (global → class → instance) and applies the merged params to the chat.
|
|
55
|
-
#
|
|
56
|
-
# @param chat [RubyLLM::Chat] the assembled chat object
|
|
57
|
-
# @param config [Hash] the invocation config hash
|
|
58
|
-
# @return [Hash] the merged params applied to the chat
|
|
59
|
-
# @api private
|
|
60
|
-
def run_before_completion_hooks!(chat, config)
|
|
61
|
-
hooks = [
|
|
62
|
-
Phronomy.configuration.before_completion,
|
|
63
|
-
self.class._before_completion,
|
|
64
|
-
@before_completion
|
|
65
|
-
].compact
|
|
66
|
-
|
|
67
|
-
return {} if hooks.empty?
|
|
68
|
-
|
|
69
|
-
ctx = BeforeCompletionContext.new(
|
|
70
|
-
agent: self,
|
|
71
|
-
messages: chat.messages,
|
|
72
|
-
config: config,
|
|
73
|
-
params: {}
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
merged = {}
|
|
77
|
-
hooks.each do |hook|
|
|
78
|
-
result = hook.call(ctx)
|
|
79
|
-
check_cancellation!(config, "invocation cancelled during before_completion hook")
|
|
80
|
-
merged.merge!(result) if result.is_a?(Hash)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
apply_before_completion_params!(chat, merged)
|
|
84
|
-
merged
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Applies a merged param hash returned by before_completion hooks to
|
|
88
|
-
# the chat object using the appropriate RubyLLM::Chat API methods.
|
|
89
|
-
# When overriding the model, reuses the agent's configured provider and
|
|
90
|
-
# assume_exists setting so that local/namespaced models continue to work.
|
|
91
|
-
#
|
|
92
|
-
# @param chat [RubyLLM::Chat]
|
|
93
|
-
# @param params [Hash]
|
|
94
|
-
# @api private
|
|
95
|
-
def apply_before_completion_params!(chat, params)
|
|
96
|
-
params.each do |key, value|
|
|
97
|
-
case key
|
|
98
|
-
when :model
|
|
99
|
-
prov = self.class.provider
|
|
100
|
-
chat.with_model(value, provider: prov, assume_exists: !prov.nil?)
|
|
101
|
-
when :temperature
|
|
102
|
-
chat.with_temperature(value)
|
|
103
|
-
else
|
|
104
|
-
chat.with_params(key => value)
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|