phronomy 0.15.0 → 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 +107 -18
- 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 -393
- data/lib/phronomy/agent/base.rb +301 -641
- 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 -47
- 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'
|
|
@@ -78,46 +78,6 @@ dependencies:
|
|
|
78
78
|
- - "~>"
|
|
79
79
|
- !ruby/object:Gem::Version
|
|
80
80
|
version: '1.0'
|
|
81
|
-
- !ruby/object:Gem::Dependency
|
|
82
|
-
name: faraday
|
|
83
|
-
requirement: !ruby/object:Gem::Requirement
|
|
84
|
-
requirements:
|
|
85
|
-
- - ">="
|
|
86
|
-
- !ruby/object:Gem::Version
|
|
87
|
-
version: '2'
|
|
88
|
-
- - "<"
|
|
89
|
-
- !ruby/object:Gem::Version
|
|
90
|
-
version: '3'
|
|
91
|
-
type: :runtime
|
|
92
|
-
prerelease: false
|
|
93
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
94
|
-
requirements:
|
|
95
|
-
- - ">="
|
|
96
|
-
- !ruby/object:Gem::Version
|
|
97
|
-
version: '2'
|
|
98
|
-
- - "<"
|
|
99
|
-
- !ruby/object:Gem::Version
|
|
100
|
-
version: '3'
|
|
101
|
-
- !ruby/object:Gem::Dependency
|
|
102
|
-
name: event_stream_parser
|
|
103
|
-
requirement: !ruby/object:Gem::Requirement
|
|
104
|
-
requirements:
|
|
105
|
-
- - ">="
|
|
106
|
-
- !ruby/object:Gem::Version
|
|
107
|
-
version: '1'
|
|
108
|
-
- - "<"
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: '2'
|
|
111
|
-
type: :runtime
|
|
112
|
-
prerelease: false
|
|
113
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
114
|
-
requirements:
|
|
115
|
-
- - ">="
|
|
116
|
-
- !ruby/object:Gem::Version
|
|
117
|
-
version: '1'
|
|
118
|
-
- - "<"
|
|
119
|
-
- !ruby/object:Gem::Version
|
|
120
|
-
version: '2'
|
|
121
81
|
description: Phronomy is a Ruby AI agent framework that provides composable building
|
|
122
82
|
blocks — Agents, Workflows, Tools, Filters, and Tracing — for building AI agents
|
|
123
83
|
in Ruby. Powered by RubyLLM for LLM abstraction.
|
|
@@ -156,20 +116,23 @@ files:
|
|
|
156
116
|
- docs/decisions/010-cooperative-first-concurrency.md
|
|
157
117
|
- docs/decisions/011-build-context-as-single-llm-input-authority.md
|
|
158
118
|
- docs/decisions/011-delegate-transport-policy-to-adapters.md
|
|
119
|
+
- docs/decisions/012-canonical-execution-log-and-context-policy.md
|
|
159
120
|
- docs/mcp-client.md
|
|
160
121
|
- examples/workflows/agent_event_mapping.rb
|
|
161
122
|
- examples/workflows/generic_task_event_mapping.rb
|
|
162
123
|
- gemfiles/mcp_1_0.gemfile
|
|
163
124
|
- lib/phronomy.rb
|
|
164
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
|
|
165
129
|
- lib/phronomy/agent/agent_invocation.rb
|
|
166
|
-
- lib/phronomy/agent/agent_invocation_registry.rb
|
|
167
130
|
- lib/phronomy/agent/agent_invocation_session_builder.rb
|
|
131
|
+
- lib/phronomy/agent/agent_root.rb
|
|
168
132
|
- lib/phronomy/agent/approval_evaluation_request.rb
|
|
169
133
|
- lib/phronomy/agent/async_event_api.rb
|
|
170
134
|
- lib/phronomy/agent/base.rb
|
|
171
|
-
- lib/phronomy/agent/
|
|
172
|
-
- lib/phronomy/agent/concerns/before_completion.rb
|
|
135
|
+
- lib/phronomy/agent/concerns/before_llm_input.rb
|
|
173
136
|
- lib/phronomy/agent/concerns/error_translation.rb
|
|
174
137
|
- lib/phronomy/agent/concerns/filterable.rb
|
|
175
138
|
- lib/phronomy/agent/context/capability/base.rb
|
|
@@ -177,16 +140,51 @@ files:
|
|
|
177
140
|
- lib/phronomy/agent/context/knowledge/base.rb
|
|
178
141
|
- lib/phronomy/agent/context/knowledge/entity_knowledge.rb
|
|
179
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
|
|
180
170
|
- lib/phronomy/agent/llm_operation_result.rb
|
|
181
171
|
- lib/phronomy/agent/phase_machine_builder.rb
|
|
172
|
+
- lib/phronomy/agent/provider_call_outcome.rb
|
|
173
|
+
- lib/phronomy/agent/ruby_llm_materializer.rb
|
|
182
174
|
- lib/phronomy/agent/runner.rb
|
|
183
175
|
- lib/phronomy/agent/shared_state.rb
|
|
176
|
+
- lib/phronomy/agent/token_budget_resolver.rb
|
|
184
177
|
- lib/phronomy/agent/tool_approval_request.rb
|
|
185
178
|
- lib/phronomy/agent/tool_call_intercepted.rb
|
|
179
|
+
- lib/phronomy/agent/tool_definition_set.rb
|
|
186
180
|
- lib/phronomy/agent/tool_executor.rb
|
|
187
181
|
- lib/phronomy/agent/tool_invocation.rb
|
|
188
182
|
- lib/phronomy/agent/tool_invocation_session_builder.rb
|
|
183
|
+
- lib/phronomy/agent_busy_error.rb
|
|
184
|
+
- lib/phronomy/canonical_json.rb
|
|
189
185
|
- lib/phronomy/configuration.rb
|
|
186
|
+
- lib/phronomy/content_store/base.rb
|
|
187
|
+
- lib/phronomy/context_budget_exceeded_error.rb
|
|
190
188
|
- lib/phronomy/diagnostics.rb
|
|
191
189
|
- lib/phronomy/engine/concurrency/async_queue.rb
|
|
192
190
|
- lib/phronomy/engine/concurrency/blocking_adapter_pool.rb
|
|
@@ -228,6 +226,7 @@ files:
|
|
|
228
226
|
- lib/phronomy/eval/scorer/includes_scorer.rb
|
|
229
227
|
- lib/phronomy/eval/scorer/llm_judge.rb
|
|
230
228
|
- lib/phronomy/event.rb
|
|
229
|
+
- lib/phronomy/execution_rehydration_required_error.rb
|
|
231
230
|
- lib/phronomy/filter.rb
|
|
232
231
|
- lib/phronomy/filter/base.rb
|
|
233
232
|
- lib/phronomy/filter/prompt_injection_filter.rb
|
|
@@ -235,6 +234,7 @@ files:
|
|
|
235
234
|
- lib/phronomy/invalid_async_entry_action_error.rb
|
|
236
235
|
- lib/phronomy/invalid_async_transition_action_error.rb
|
|
237
236
|
- lib/phronomy/invalid_async_workflow_action_error.rb
|
|
237
|
+
- lib/phronomy/invalid_context_budget_configuration_error.rb
|
|
238
238
|
- lib/phronomy/invocation_context.rb
|
|
239
239
|
- lib/phronomy/knowledge_source.rb
|
|
240
240
|
- lib/phronomy/llm_adapter.rb
|
|
@@ -253,6 +253,8 @@ files:
|
|
|
253
253
|
- lib/phronomy/output_parser/base.rb
|
|
254
254
|
- lib/phronomy/output_parser/json_parser.rb
|
|
255
255
|
- lib/phronomy/output_parser/structured_parser.rb
|
|
256
|
+
- lib/phronomy/persistence.rb
|
|
257
|
+
- lib/phronomy/persistence/in_memory.rb
|
|
256
258
|
- lib/phronomy/ruby_llm_patches.rb
|
|
257
259
|
- lib/phronomy/runnable.rb
|
|
258
260
|
- lib/phronomy/state_store/base.rb
|
|
@@ -291,11 +293,16 @@ files:
|
|
|
291
293
|
- lib/phronomy/workflow/phase_machine_builder.rb
|
|
292
294
|
- lib/phronomy/workflow_context.rb
|
|
293
295
|
- lib/phronomy/workflow_runner.rb
|
|
296
|
+
- scripts/add_to_h_to_token_doubles.rb
|
|
297
|
+
- scripts/add_to_h_unnamed_doubles.rb
|
|
294
298
|
- scripts/api_snapshot.rb
|
|
295
299
|
- scripts/check_api_annotations.rb
|
|
296
300
|
- scripts/check_private_enforcement.rb
|
|
297
301
|
- scripts/check_readme_ruby.rb
|
|
298
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
|
|
299
306
|
- scripts/run_mutation.sh
|
|
300
307
|
- sig/phronomy.rbs
|
|
301
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
|