phronomy 0.15.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.mutant.yml +8 -9
- data/CHANGELOG.md +159 -28
- data/CONTRIBUTING.md +28 -16
- data/README.md +400 -143
- data/benchmark/baseline.json +2 -3
- data/benchmark/bench_agent_invoke.rb +7 -4
- data/benchmark/bench_context_assembler.rb +134 -34
- data/benchmark/bench_regression.rb +3 -19
- data/benchmark/bench_tool_schema.rb +2 -34
- data/docs/decisions/005-static-knowledge-class-level-cache.md +12 -1
- data/docs/decisions/010-cooperative-first-concurrency.md +7 -0
- data/docs/decisions/011-build-context-as-single-llm-input-authority.md +40 -1
- data/docs/decisions/012-canonical-execution-log-and-context-policy.md +69 -0
- data/docs/decisions/013-journal-backed-knowledge-as-context-candidates.md +122 -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 +44 -46
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +206 -104
- data/lib/phronomy/agent/agent_root.rb +66 -0
- data/lib/phronomy/agent/async_event_api.rb +55 -475
- data/lib/phronomy/agent/base.rb +351 -514
- data/lib/phronomy/agent/concerns/before_llm_input.rb +66 -0
- data/lib/phronomy/agent/context/capability/base.rb +166 -297
- data/lib/phronomy/agent/context_assembler.rb +357 -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 +118 -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 +134 -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 +1122 -0
- data/lib/phronomy/agent/immutable.rb +31 -0
- data/lib/phronomy/agent/journal_projection.rb +60 -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 +189 -0
- data/lib/phronomy/agent/shared_state.rb +46 -138
- data/lib/phronomy/agent/token_budget_resolver.rb +70 -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/tool_invocation.rb +108 -314
- data/lib/phronomy/agent.rb +10 -16
- data/lib/phronomy/agent_busy_error.rb +5 -0
- data/lib/phronomy/canonical_json.rb +136 -0
- data/lib/phronomy/configuration.rb +17 -155
- data/lib/phronomy/content_store/base.rb +51 -0
- data/lib/phronomy/context_budget_exceeded_error.rb +8 -0
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +7 -80
- data/lib/phronomy/engine/event_loop.rb +3 -0
- data/lib/phronomy/engine/runtime.rb +15 -230
- data/lib/phronomy/engine/task_group.rb +30 -102
- 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/token_budget.rb +8 -79
- data/lib/phronomy/multi_agent/orchestrator.rb +153 -204
- data/lib/phronomy/multi_agent/parallel_tool_chat.rb +7 -5
- data/lib/phronomy/multi_agent/team_coordinator.rb +46 -133
- 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/vector_store/in_memory.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy.rb +9 -115
- data/scripts/add_to_h_to_token_doubles.rb +33 -0
- data/scripts/add_to_h_unnamed_doubles.rb +27 -0
- data/scripts/api_snapshot.rb +1 -12
- 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 -13
- 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
- data/lib/phronomy/agent/context/knowledge/base.rb +0 -58
- data/lib/phronomy/agent/context/knowledge/entity_knowledge.rb +0 -102
- data/lib/phronomy/agent/context/knowledge/static_knowledge.rb +0 -58
- data/lib/phronomy/knowledge_source.rb +0 -12
- data/lib/phronomy/llm_context_window/assembler.rb +0 -191
- data/lib/phronomy/llm_context_window/context_version_cache.rb +0 -52
|
@@ -4,8 +4,8 @@ module Phronomy
|
|
|
4
4
|
module VectorStore
|
|
5
5
|
# Pure-Ruby in-memory vector store using cosine similarity.
|
|
6
6
|
#
|
|
7
|
-
# Intended for tests, short-lived agents, and
|
|
8
|
-
# the
|
|
7
|
+
# Intended for tests, short-lived agents, and small retrieval workloads where
|
|
8
|
+
# the document count is small enough that a linear scan is fast enough.
|
|
9
9
|
#
|
|
10
10
|
# @example
|
|
11
11
|
# store = Phronomy::VectorStore::InMemory.new
|
data/lib/phronomy/version.rb
CHANGED
data/lib/phronomy.rb
CHANGED
|
@@ -5,25 +5,18 @@ require "ruby_llm"
|
|
|
5
5
|
require_relative "phronomy/ruby_llm_patches"
|
|
6
6
|
|
|
7
7
|
loader = Zeitwerk::Loader.for_gem
|
|
8
|
-
# Teach Zeitwerk that "llm" maps to "LLM" so that file names such as
|
|
9
|
-
# ruby_llm_embeddings.rb resolve to RubyLLMEmbeddings (not RubyLlmEmbeddings).
|
|
10
8
|
loader.inflector.inflect("ruby_llm_embeddings" => "RubyLLMEmbeddings")
|
|
11
|
-
# RAG: Zeitwerk would infer "Rag" — override to "RAG".
|
|
12
9
|
loader.inflector.inflect("rag" => "RAG")
|
|
13
|
-
# FSMSession: Zeitwerk would infer "FsmSession" — override to "FSMSession".
|
|
14
|
-
# Phronomy::FSMSession is the top-level cooperative execution engine shared by
|
|
15
|
-
# WorkflowRunner, AgentInvocationSessionBuilder, and ToolInvocationSessionBuilder.
|
|
16
10
|
loader.inflector.inflect("fsm_session" => "FSMSession")
|
|
17
|
-
# LLMAdapter: Zeitwerk would infer "LlmAdapter" — override to "LLMAdapter".
|
|
18
11
|
loader.inflector.inflect("llm_adapter" => "LLMAdapter")
|
|
19
|
-
# LLMOperationResult: preserve the LLM acronym for the Agent result carrier.
|
|
20
12
|
loader.inflector.inflect("llm_operation_result" => "LLMOperationResult")
|
|
21
|
-
# LLMAdapter::RubyLLM: "ruby_llm" maps to "RubyLLM" (not "RubyLlm").
|
|
22
13
|
loader.inflector.inflect("ruby_llm" => "RubyLLM")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
loader.inflector.inflect("canonical_json" => "CanonicalJSON")
|
|
15
|
+
loader.inflector.inflect("ruby_llm_materializer" => "RubyLLMMaterializer")
|
|
16
|
+
loader.inflector.inflect("llm_call_record" => "LLMCallRecord")
|
|
17
|
+
loader.inflector.inflect("llm_input_manifest" => "LLMInputManifest")
|
|
18
|
+
loader.inflector.inflect("llm_input_build_context" => "LLMInputBuildContext")
|
|
19
|
+
loader.inflector.inflect("llm_input_patch" => "LLMInputPatch")
|
|
27
20
|
loader.collapse("#{__dir__}/phronomy/engine")
|
|
28
21
|
loader.setup
|
|
29
22
|
|
|
@@ -31,70 +24,25 @@ require_relative "phronomy/version"
|
|
|
31
24
|
require_relative "phronomy/token_usage"
|
|
32
25
|
|
|
33
26
|
module Phronomy
|
|
34
|
-
# Exception hierarchy
|
|
35
27
|
class Error < StandardError; end
|
|
36
28
|
class ParseError < Error; end
|
|
37
29
|
class RecursionLimitError < Error; end
|
|
38
30
|
class ToolError < Error; end
|
|
39
|
-
# Base error for Phronomy-owned timed boundaries and generic timeout primitives.
|
|
40
31
|
class TimeoutError < Error; end
|
|
41
|
-
|
|
42
32
|
class ConfigurationError < Error; end
|
|
43
|
-
|
|
44
33
|
class HandoffError < Error; end
|
|
45
34
|
|
|
46
|
-
# Raised when a network or transport layer call fails (e.g. LLM API unreachable,
|
|
47
|
-
# MCP server connection refused). Distinguishable from application-level errors
|
|
48
|
-
# so callers can apply network-specific retry logic.
|
|
49
35
|
class TransportError < Error; end
|
|
50
|
-
|
|
51
|
-
# Raised when the LLM API returns a rate-limit response (HTTP 429 or equivalent).
|
|
52
|
-
# Callers should back off and retry after the indicated delay.
|
|
53
36
|
class RateLimitError < TransportError; end
|
|
54
|
-
|
|
55
|
-
# Raised when the LLM API rejects the request due to an invalid or revoked API key.
|
|
56
|
-
# Callers should not retry without fixing the credentials.
|
|
57
37
|
class AuthenticationError < TransportError; end
|
|
58
|
-
|
|
59
|
-
# Raised when the prompt exceeds the model's context window limit.
|
|
60
38
|
class ContextLengthError < Error; end
|
|
61
|
-
|
|
62
|
-
# Raised when a workflow or agent execution is explicitly cancelled.
|
|
63
|
-
# Separate from TimeoutError (deadline exceeded) — this is an intentional stop.
|
|
64
39
|
class CancellationError < Error; end
|
|
65
40
|
|
|
66
|
-
# Raised when {Agent#invoke} (a synchronous, blocking call) is attempted from
|
|
67
|
-
# inside an active scheduler task and +strict_runtime_guards+ is enabled.
|
|
68
|
-
#
|
|
69
|
-
# Calling a blocking invocation from within a scheduler task stalls the
|
|
70
|
-
# scheduler until the inner invocation completes, preventing other tasks from
|
|
71
|
-
# making progress (hidden deadlock risk). Use {Agent#invoke_async} followed by
|
|
72
|
-
# +#await+ inside scheduler tasks instead.
|
|
73
|
-
#
|
|
74
|
-
# This error is only raised when:
|
|
75
|
-
# Phronomy.configure { |c| c.strict_runtime_guards = true }
|
|
76
|
-
#
|
|
77
|
-
# By default a warning is logged and execution continues.
|
|
78
|
-
#
|
|
79
|
-
# @see Phronomy::Runtime.in_scheduler_context?
|
|
80
41
|
class SchedulerReentrancyError < Error; end
|
|
81
|
-
|
|
82
|
-
# Raised when work is submitted to a Runtime whose shutdown has begun, or
|
|
83
|
-
# when a Runtime cannot be reset because owned resources are still alive.
|
|
84
42
|
class RuntimeShutdownError < Error; end
|
|
85
|
-
|
|
86
|
-
# Raised when Runtime#shutdown is invoked from inside a Phronomy::Task.
|
|
87
43
|
class RuntimeShutdownReentrancyError < RuntimeShutdownError; end
|
|
88
44
|
|
|
89
|
-
# Raised by {Phronomy::GeneratorVerifier#invoke} when +raise_if_untrusted: true+
|
|
90
|
-
# and the pipeline's combined confidence score falls below the configured threshold.
|
|
91
|
-
#
|
|
92
|
-
# @example
|
|
93
|
-
# rescue Phronomy::LowConfidenceError => e
|
|
94
|
-
# puts e.result.confidence # => e.g. 0.45
|
|
95
|
-
# puts e.result.output # best-effort answer despite low confidence
|
|
96
45
|
class LowConfidenceError < Error
|
|
97
|
-
# @return [Phronomy::GeneratorVerifier::Result] the untrusted result
|
|
98
46
|
attr_reader :result
|
|
99
47
|
|
|
100
48
|
def initialize(result)
|
|
@@ -103,9 +51,6 @@ module Phronomy
|
|
|
103
51
|
end
|
|
104
52
|
end
|
|
105
53
|
|
|
106
|
-
# Raised by a {Phronomy::Filter::Base} subclass when the filter rejects a
|
|
107
|
-
# value without transforming it (blocking the pipeline).
|
|
108
|
-
# @api public
|
|
109
54
|
class FilterBlockError < Error
|
|
110
55
|
attr_reader :filter
|
|
111
56
|
|
|
@@ -115,27 +60,12 @@ module Phronomy
|
|
|
115
60
|
end
|
|
116
61
|
end
|
|
117
62
|
|
|
118
|
-
# Raised when an operation is submitted to a {BlockingAdapterPool} that has
|
|
119
|
-
# already been shut down via {BlockingAdapterPool#shutdown}.
|
|
120
63
|
class PoolShutdownError < Error; end
|
|
121
|
-
|
|
122
|
-
# Raised when a concurrency limit is exceeded and the configured backpressure
|
|
123
|
-
# strategy is +:raise+. The caller should back off and retry.
|
|
124
64
|
class BackpressureError < Error; end
|
|
125
65
|
|
|
126
|
-
# Raised
|
|
127
|
-
#
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
# Deprecated compatibility constant. Workflow entry/exit actions are
|
|
131
|
-
# synchronous and the Workflow DSL no longer accepts +action_timeout:+.
|
|
132
|
-
class ActionTimeoutError < TimeoutError; end
|
|
133
|
-
|
|
134
|
-
# Raised when a {Phronomy::WorkflowContext} field is mutated from a thread
|
|
135
|
-
# that does not own the context (i.e. not the EventLoop dispatch thread).
|
|
136
|
-
# Only raised in EventLoop mode. Use +context.merge(...)+ to produce a new
|
|
137
|
-
# context, or deliver updates as +:action_completed+ event payloads
|
|
138
|
-
# via {Agent::Base#invoke_async} + {Task#map}.
|
|
66
|
+
# Raised when a WorkflowContext field is mutated from a thread that does not
|
|
67
|
+
# own the context. Deliver asynchronous updates back to the Workflow as later
|
|
68
|
+
# events via Workflow#signal instead of mutating context from worker callbacks.
|
|
139
69
|
class WorkflowContextOwnershipError < Error; end
|
|
140
70
|
|
|
141
71
|
class << self
|
|
@@ -147,36 +77,10 @@ module Phronomy
|
|
|
147
77
|
yield configuration
|
|
148
78
|
end
|
|
149
79
|
|
|
150
|
-
# Resets the global Phronomy configuration to defaults.
|
|
151
|
-
#
|
|
152
|
-
# **Intended for test suites only.** Calling this in a production process
|
|
153
|
-
# will drop all runtime configuration (tracer, model, tokenizer, etc.)
|
|
154
|
-
# globally and immediately affect all subsequent agent and workflow calls.
|
|
155
|
-
#
|
|
156
|
-
# **Parallel test suites warning:** When tests run in parallel (e.g.
|
|
157
|
-
# `parallel_tests` or `parallel_rspec`), +reset_configuration!+ in one
|
|
158
|
-
# worker will clear configuration shared with other workers in the same
|
|
159
|
-
# process. Prefer process-isolation strategies (forked workers) over
|
|
160
|
-
# thread-based parallelism when using this method.
|
|
161
|
-
#
|
|
162
|
-
# Typical usage in a sequential test suite:
|
|
163
|
-
# after { Phronomy.reset_configuration! }
|
|
164
80
|
def reset_configuration!
|
|
165
81
|
@configuration = Configuration.new
|
|
166
82
|
end
|
|
167
83
|
|
|
168
|
-
# Yields the current {Configuration} object, then restores the original
|
|
169
|
-
# configuration on exit (even if the block raises).
|
|
170
|
-
#
|
|
171
|
-
# Intended for test helpers that need to temporarily override settings
|
|
172
|
-
# without permanently mutating the global configuration.
|
|
173
|
-
#
|
|
174
|
-
# @yield [config] the current {Configuration} instance (mutable)
|
|
175
|
-
# @example
|
|
176
|
-
# Phronomy.with_configuration do |c|
|
|
177
|
-
# c.logger = Logger.new($stdout)
|
|
178
|
-
# end
|
|
179
|
-
# @api public
|
|
180
84
|
def with_configuration
|
|
181
85
|
original = @configuration&.dup
|
|
182
86
|
yield configuration
|
|
@@ -184,16 +88,6 @@ module Phronomy
|
|
|
184
88
|
@configuration = original
|
|
185
89
|
end
|
|
186
90
|
|
|
187
|
-
# Shuts down and clears the process-wide default Runtime, then resets
|
|
188
|
-
# global configuration. Intended for test suites only.
|
|
189
|
-
#
|
|
190
|
-
# Runtime execution failure and resource cleanup are separate. The
|
|
191
|
-
# singleton is cleared when cleanup completed, even if execution failed.
|
|
192
|
-
#
|
|
193
|
-
# @param timeout [Numeric] maximum graceful wait for Runtime tasks and
|
|
194
|
-
# EventLoop shutdown
|
|
195
|
-
# @return [Phronomy::Runtime::ShutdownResult]
|
|
196
|
-
# @api public
|
|
197
91
|
def reset_runtime!(timeout: configuration.event_loop_stop_grace_seconds)
|
|
198
92
|
previous_grace = @configuration&.event_loop_stop_grace_seconds
|
|
199
93
|
result = Runtime.reset_default!(timeout: timeout)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Adds to_h to all Tokens-like doubles so ExecutionCoordinator can serialize usage.
|
|
5
|
+
|
|
6
|
+
files = Dir.glob("spec/**/*.rb").sort
|
|
7
|
+
|
|
8
|
+
total = 0
|
|
9
|
+
files.each do |f|
|
|
10
|
+
content = File.read(f)
|
|
11
|
+
modified = content.gsub(
|
|
12
|
+
/double\(("[^"]+"),\s*(input: \d+, output: \d+, cached: \d+, cache_creation: \d+)\)/
|
|
13
|
+
) do
|
|
14
|
+
name = $1
|
|
15
|
+
attrs = $2
|
|
16
|
+
next $& if $&.include?("to_h")
|
|
17
|
+
|
|
18
|
+
h_entries = attrs.split(",").map(&:strip).map do |pair|
|
|
19
|
+
k, v = pair.split(":").map(&:strip)
|
|
20
|
+
"\"#{k}\" => #{v}"
|
|
21
|
+
end.join(", ")
|
|
22
|
+
|
|
23
|
+
total += 1
|
|
24
|
+
"double(#{name}, #{attrs}, to_h: {#{h_entries}})"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if modified != content
|
|
28
|
+
File.write(f, modified)
|
|
29
|
+
puts " patched: #{f}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
puts "\nAdded to_h to #{total} Tokens doubles."
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Adds to_h to unnamed (no-string-name) Tokens doubles like double(input: 5, ...)
|
|
5
|
+
|
|
6
|
+
files = Dir.glob("spec/**/*.rb").sort
|
|
7
|
+
|
|
8
|
+
total = 0
|
|
9
|
+
files.each do |f|
|
|
10
|
+
content = File.read(f)
|
|
11
|
+
modified = content.gsub(
|
|
12
|
+
/double\((input: (\d+), output: (\d+), cached: (\d+), cache_creation: (\d+))\)/
|
|
13
|
+
) do
|
|
14
|
+
attrs, a, b, c, d = $1, $2, $3, $4, $5
|
|
15
|
+
next $& if $&.include?("to_h")
|
|
16
|
+
|
|
17
|
+
total += 1
|
|
18
|
+
"double(#{attrs}, to_h: {\"input\" => #{a}, \"output\" => #{b}, \"cached\" => #{c}, \"cache_creation\" => #{d}})"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if modified != content
|
|
22
|
+
File.write(f, modified)
|
|
23
|
+
puts " patched: #{f}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
puts "\nAdded to_h to #{total} unnamed Tokens doubles."
|
data/scripts/api_snapshot.rb
CHANGED
|
@@ -4,23 +4,17 @@
|
|
|
4
4
|
# scripts/api_snapshot.rb
|
|
5
5
|
#
|
|
6
6
|
# Dumps the public instance methods of all Stable/Beta public API classes to
|
|
7
|
-
# JSON.
|
|
7
|
+
# JSON. The snapshot is stored in spec/fixtures/api_snapshot.json and is used
|
|
8
8
|
# by spec/phronomy/api_compatibility_spec.rb to detect unintended API removals.
|
|
9
9
|
#
|
|
10
10
|
# Usage:
|
|
11
|
-
# # Regenerate spec/fixtures/api_snapshot.json (run when intentionally adding
|
|
12
|
-
# # or removing public API methods after updating the stability table):
|
|
13
11
|
# ruby scripts/api_snapshot.rb --write
|
|
14
|
-
#
|
|
15
|
-
# # Print snapshot to stdout (useful for manual inspection):
|
|
16
12
|
# ruby scripts/api_snapshot.rb
|
|
17
13
|
|
|
18
14
|
require "json"
|
|
19
15
|
require "fileutils"
|
|
20
16
|
require_relative "../lib/phronomy"
|
|
21
17
|
|
|
22
|
-
# Classes and modules whose public API is tracked.
|
|
23
|
-
# Add an entry whenever a new class/module is promoted to Stable or Beta in README.md.
|
|
24
18
|
PUBLIC_API_ENTRIES = [
|
|
25
19
|
# Stable
|
|
26
20
|
Phronomy::Agent::Base,
|
|
@@ -37,8 +31,6 @@ PUBLIC_API_ENTRIES = [
|
|
|
37
31
|
Phronomy::VectorStore::Base,
|
|
38
32
|
Phronomy::VectorStore::InMemory,
|
|
39
33
|
Phronomy::VectorStore::Embeddings::Base,
|
|
40
|
-
Phronomy::Agent::Context::Knowledge::Base,
|
|
41
|
-
Phronomy::Agent::Context::Knowledge::StaticKnowledge,
|
|
42
34
|
Phronomy::Tracing::Base,
|
|
43
35
|
Phronomy::Tracing::NullTracer,
|
|
44
36
|
Phronomy::Eval::Runner,
|
|
@@ -47,7 +39,6 @@ PUBLIC_API_ENTRIES = [
|
|
|
47
39
|
Phronomy::Tools::VectorSearch
|
|
48
40
|
].freeze
|
|
49
41
|
|
|
50
|
-
# Baseline methods common to all Ruby objects — excluded from the snapshot.
|
|
51
42
|
BASELINE_INSTANCE_METHODS = (
|
|
52
43
|
Object.public_instance_methods |
|
|
53
44
|
Kernel.public_instance_methods
|
|
@@ -60,7 +51,6 @@ BASELINE_CLASS_METHODS = (
|
|
|
60
51
|
|
|
61
52
|
def snapshot_entry(klass)
|
|
62
53
|
if klass.instance_of?(Module)
|
|
63
|
-
# Module — capture instance methods defined in this module only
|
|
64
54
|
own_methods = klass.public_instance_methods(false).sort
|
|
65
55
|
{
|
|
66
56
|
"name" => klass.name,
|
|
@@ -68,7 +58,6 @@ def snapshot_entry(klass)
|
|
|
68
58
|
"public_instance_methods" => own_methods
|
|
69
59
|
}
|
|
70
60
|
else
|
|
71
|
-
# Class — capture public instance methods minus universal baseline
|
|
72
61
|
instance_methods = (klass.public_instance_methods - BASELINE_INSTANCE_METHODS).sort
|
|
73
62
|
class_methods = (klass.public_methods(false) - BASELINE_CLASS_METHODS).sort
|
|
74
63
|
{
|
|
@@ -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
|