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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +8 -9
  3. data/CHANGELOG.md +159 -28
  4. data/CONTRIBUTING.md +28 -16
  5. data/README.md +400 -143
  6. data/benchmark/baseline.json +2 -3
  7. data/benchmark/bench_agent_invoke.rb +7 -4
  8. data/benchmark/bench_context_assembler.rb +134 -34
  9. data/benchmark/bench_regression.rb +3 -19
  10. data/benchmark/bench_tool_schema.rb +2 -34
  11. data/docs/decisions/005-static-knowledge-class-level-cache.md +12 -1
  12. data/docs/decisions/010-cooperative-first-concurrency.md +7 -0
  13. data/docs/decisions/011-build-context-as-single-llm-input-authority.md +40 -1
  14. data/docs/decisions/012-canonical-execution-log-and-context-policy.md +69 -0
  15. data/docs/decisions/013-journal-backed-knowledge-as-context-candidates.md +122 -0
  16. data/lib/phronomy/agent/activation_registry.rb +28 -0
  17. data/lib/phronomy/agent/agent_execution.rb +97 -0
  18. data/lib/phronomy/agent/agent_execution_activation.rb +172 -0
  19. data/lib/phronomy/agent/agent_invocation.rb +44 -46
  20. data/lib/phronomy/agent/agent_invocation_session_builder.rb +206 -104
  21. data/lib/phronomy/agent/agent_root.rb +66 -0
  22. data/lib/phronomy/agent/async_event_api.rb +55 -475
  23. data/lib/phronomy/agent/base.rb +351 -514
  24. data/lib/phronomy/agent/concerns/before_llm_input.rb +66 -0
  25. data/lib/phronomy/agent/context/capability/base.rb +166 -297
  26. data/lib/phronomy/agent/context_assembler.rb +357 -0
  27. data/lib/phronomy/agent/context_candidate.rb +47 -0
  28. data/lib/phronomy/agent/context_candidate_resolver.rb +65 -0
  29. data/lib/phronomy/agent/context_importer.rb +217 -0
  30. data/lib/phronomy/agent/context_parts/budget/token_budget_packer.rb +53 -0
  31. data/lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb +56 -0
  32. data/lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb +30 -0
  33. data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +118 -0
  34. data/lib/phronomy/agent/context_parts/validators/final_budget_validator.rb +37 -0
  35. data/lib/phronomy/agent/context_plan.rb +25 -0
  36. data/lib/phronomy/agent/context_plan_validator.rb +134 -0
  37. data/lib/phronomy/agent/context_policies/default.rb +53 -0
  38. data/lib/phronomy/agent/context_policy.rb +15 -0
  39. data/lib/phronomy/agent/context_policy_descriptor.rb +49 -0
  40. data/lib/phronomy/agent/context_policy_registry.rb +46 -0
  41. data/lib/phronomy/agent/context_request.rb +35 -0
  42. data/lib/phronomy/agent/context_selection_unit.rb +38 -0
  43. data/lib/phronomy/agent/derived_content_spec.rb +34 -0
  44. data/lib/phronomy/agent/execution_coordinator.rb +1122 -0
  45. data/lib/phronomy/agent/immutable.rb +31 -0
  46. data/lib/phronomy/agent/journal_projection.rb +60 -0
  47. data/lib/phronomy/agent/journal_record.rb +67 -0
  48. data/lib/phronomy/agent/llm_call_record.rb +51 -0
  49. data/lib/phronomy/agent/llm_input_build_context.rb +17 -0
  50. data/lib/phronomy/agent/llm_input_manifest.rb +103 -0
  51. data/lib/phronomy/agent/llm_input_patch.rb +21 -0
  52. data/lib/phronomy/agent/phase_machine_builder.rb +12 -0
  53. data/lib/phronomy/agent/provider_call_outcome.rb +90 -0
  54. data/lib/phronomy/agent/ruby_llm_materializer.rb +189 -0
  55. data/lib/phronomy/agent/shared_state.rb +46 -138
  56. data/lib/phronomy/agent/token_budget_resolver.rb +70 -0
  57. data/lib/phronomy/agent/tool_call_intercepted.rb +11 -4
  58. data/lib/phronomy/agent/tool_definition_set.rb +55 -0
  59. data/lib/phronomy/agent/tool_invocation.rb +108 -314
  60. data/lib/phronomy/agent.rb +10 -16
  61. data/lib/phronomy/agent_busy_error.rb +5 -0
  62. data/lib/phronomy/canonical_json.rb +136 -0
  63. data/lib/phronomy/configuration.rb +17 -155
  64. data/lib/phronomy/content_store/base.rb +51 -0
  65. data/lib/phronomy/context_budget_exceeded_error.rb +8 -0
  66. data/lib/phronomy/engine/concurrency/cancellation_token.rb +7 -80
  67. data/lib/phronomy/engine/event_loop.rb +3 -0
  68. data/lib/phronomy/engine/runtime.rb +15 -230
  69. data/lib/phronomy/engine/task_group.rb +30 -102
  70. data/lib/phronomy/execution_rehydration_required_error.rb +5 -0
  71. data/lib/phronomy/invalid_context_budget_configuration_error.rb +8 -0
  72. data/lib/phronomy/llm_context_window/token_budget.rb +8 -79
  73. data/lib/phronomy/multi_agent/orchestrator.rb +153 -204
  74. data/lib/phronomy/multi_agent/parallel_tool_chat.rb +7 -5
  75. data/lib/phronomy/multi_agent/team_coordinator.rb +46 -133
  76. data/lib/phronomy/persistence/in_memory.rb +247 -0
  77. data/lib/phronomy/persistence.rb +39 -0
  78. data/lib/phronomy/tools/agent.rb +14 -36
  79. data/lib/phronomy/vector_store/in_memory.rb +2 -2
  80. data/lib/phronomy/version.rb +1 -1
  81. data/lib/phronomy.rb +9 -115
  82. data/scripts/add_to_h_to_token_doubles.rb +33 -0
  83. data/scripts/add_to_h_unnamed_doubles.rb +27 -0
  84. data/scripts/api_snapshot.rb +1 -12
  85. data/scripts/migrate_spec_agent_definition.rb +108 -0
  86. data/scripts/migrate_spec_agent_definition_pass2.rb +53 -0
  87. data/scripts/migrate_spec_inline_pass3.rb +24 -0
  88. metadata +54 -13
  89. data/lib/phronomy/agent/agent_invocation_registry.rb +0 -75
  90. data/lib/phronomy/agent/before_completion_context.rb +0 -47
  91. data/lib/phronomy/agent/concerns/before_completion.rb +0 -111
  92. data/lib/phronomy/agent/context/knowledge/base.rb +0 -58
  93. data/lib/phronomy/agent/context/knowledge/entity_knowledge.rb +0 -102
  94. data/lib/phronomy/agent/context/knowledge/static_knowledge.rb +0 -58
  95. data/lib/phronomy/knowledge_source.rb +0 -12
  96. data/lib/phronomy/llm_context_window/assembler.rb +0 -191
  97. data/lib/phronomy/llm_context_window/context_version_cache.rb +0 -52
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.15.1
4
+ version: 0.17.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-03 00:00:00.000000000 Z
11
+ date: 2026-08-09 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.3'
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.3'
29
+ version: '1.15'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2'
@@ -116,37 +116,72 @@ 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
120
+ - docs/decisions/013-journal-backed-knowledge-as-context-candidates.md
119
121
  - docs/mcp-client.md
120
122
  - examples/workflows/agent_event_mapping.rb
121
123
  - examples/workflows/generic_task_event_mapping.rb
122
124
  - gemfiles/mcp_1_0.gemfile
123
125
  - lib/phronomy.rb
124
126
  - lib/phronomy/agent.rb
127
+ - lib/phronomy/agent/activation_registry.rb
128
+ - lib/phronomy/agent/agent_execution.rb
129
+ - lib/phronomy/agent/agent_execution_activation.rb
125
130
  - lib/phronomy/agent/agent_invocation.rb
126
- - lib/phronomy/agent/agent_invocation_registry.rb
127
131
  - lib/phronomy/agent/agent_invocation_session_builder.rb
132
+ - lib/phronomy/agent/agent_root.rb
128
133
  - lib/phronomy/agent/approval_evaluation_request.rb
129
134
  - lib/phronomy/agent/async_event_api.rb
130
135
  - lib/phronomy/agent/base.rb
131
- - lib/phronomy/agent/before_completion_context.rb
132
- - lib/phronomy/agent/concerns/before_completion.rb
136
+ - lib/phronomy/agent/concerns/before_llm_input.rb
133
137
  - lib/phronomy/agent/concerns/error_translation.rb
134
138
  - lib/phronomy/agent/concerns/filterable.rb
135
139
  - lib/phronomy/agent/context/capability/base.rb
136
140
  - lib/phronomy/agent/context/instruction/prompt_template.rb
137
- - lib/phronomy/agent/context/knowledge/base.rb
138
- - lib/phronomy/agent/context/knowledge/entity_knowledge.rb
139
- - lib/phronomy/agent/context/knowledge/static_knowledge.rb
141
+ - lib/phronomy/agent/context_assembler.rb
142
+ - lib/phronomy/agent/context_candidate.rb
143
+ - lib/phronomy/agent/context_candidate_resolver.rb
144
+ - lib/phronomy/agent/context_importer.rb
145
+ - lib/phronomy/agent/context_parts/budget/token_budget_packer.rb
146
+ - lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb
147
+ - lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb
148
+ - lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb
149
+ - lib/phronomy/agent/context_parts/validators/final_budget_validator.rb
150
+ - lib/phronomy/agent/context_plan.rb
151
+ - lib/phronomy/agent/context_plan_validator.rb
152
+ - lib/phronomy/agent/context_policies/default.rb
153
+ - lib/phronomy/agent/context_policy.rb
154
+ - lib/phronomy/agent/context_policy_descriptor.rb
155
+ - lib/phronomy/agent/context_policy_registry.rb
156
+ - lib/phronomy/agent/context_request.rb
157
+ - lib/phronomy/agent/context_selection_unit.rb
158
+ - lib/phronomy/agent/derived_content_spec.rb
159
+ - lib/phronomy/agent/execution_coordinator.rb
160
+ - lib/phronomy/agent/immutable.rb
161
+ - lib/phronomy/agent/journal_projection.rb
162
+ - lib/phronomy/agent/journal_record.rb
163
+ - lib/phronomy/agent/llm_call_record.rb
164
+ - lib/phronomy/agent/llm_input_build_context.rb
165
+ - lib/phronomy/agent/llm_input_manifest.rb
166
+ - lib/phronomy/agent/llm_input_patch.rb
140
167
  - lib/phronomy/agent/llm_operation_result.rb
141
168
  - lib/phronomy/agent/phase_machine_builder.rb
169
+ - lib/phronomy/agent/provider_call_outcome.rb
170
+ - lib/phronomy/agent/ruby_llm_materializer.rb
142
171
  - lib/phronomy/agent/runner.rb
143
172
  - lib/phronomy/agent/shared_state.rb
173
+ - lib/phronomy/agent/token_budget_resolver.rb
144
174
  - lib/phronomy/agent/tool_approval_request.rb
145
175
  - lib/phronomy/agent/tool_call_intercepted.rb
176
+ - lib/phronomy/agent/tool_definition_set.rb
146
177
  - lib/phronomy/agent/tool_executor.rb
147
178
  - lib/phronomy/agent/tool_invocation.rb
148
179
  - lib/phronomy/agent/tool_invocation_session_builder.rb
180
+ - lib/phronomy/agent_busy_error.rb
181
+ - lib/phronomy/canonical_json.rb
149
182
  - lib/phronomy/configuration.rb
183
+ - lib/phronomy/content_store/base.rb
184
+ - lib/phronomy/context_budget_exceeded_error.rb
150
185
  - lib/phronomy/diagnostics.rb
151
186
  - lib/phronomy/engine/concurrency/async_queue.rb
152
187
  - lib/phronomy/engine/concurrency/blocking_adapter_pool.rb
@@ -188,6 +223,7 @@ files:
188
223
  - lib/phronomy/eval/scorer/includes_scorer.rb
189
224
  - lib/phronomy/eval/scorer/llm_judge.rb
190
225
  - lib/phronomy/event.rb
226
+ - lib/phronomy/execution_rehydration_required_error.rb
191
227
  - lib/phronomy/filter.rb
192
228
  - lib/phronomy/filter/base.rb
193
229
  - lib/phronomy/filter/prompt_injection_filter.rb
@@ -195,13 +231,11 @@ files:
195
231
  - lib/phronomy/invalid_async_entry_action_error.rb
196
232
  - lib/phronomy/invalid_async_transition_action_error.rb
197
233
  - lib/phronomy/invalid_async_workflow_action_error.rb
234
+ - lib/phronomy/invalid_context_budget_configuration_error.rb
198
235
  - lib/phronomy/invocation_context.rb
199
- - lib/phronomy/knowledge_source.rb
200
236
  - lib/phronomy/llm_adapter.rb
201
237
  - lib/phronomy/llm_adapter/base.rb
202
238
  - lib/phronomy/llm_adapter/ruby_llm.rb
203
- - lib/phronomy/llm_context_window/assembler.rb
204
- - lib/phronomy/llm_context_window/context_version_cache.rb
205
239
  - lib/phronomy/llm_context_window/token_budget.rb
206
240
  - lib/phronomy/llm_context_window/token_estimator.rb
207
241
  - lib/phronomy/metrics.rb
@@ -213,6 +247,8 @@ files:
213
247
  - lib/phronomy/output_parser/base.rb
214
248
  - lib/phronomy/output_parser/json_parser.rb
215
249
  - lib/phronomy/output_parser/structured_parser.rb
250
+ - lib/phronomy/persistence.rb
251
+ - lib/phronomy/persistence/in_memory.rb
216
252
  - lib/phronomy/ruby_llm_patches.rb
217
253
  - lib/phronomy/runnable.rb
218
254
  - lib/phronomy/state_store/base.rb
@@ -251,11 +287,16 @@ files:
251
287
  - lib/phronomy/workflow/phase_machine_builder.rb
252
288
  - lib/phronomy/workflow_context.rb
253
289
  - lib/phronomy/workflow_runner.rb
290
+ - scripts/add_to_h_to_token_doubles.rb
291
+ - scripts/add_to_h_unnamed_doubles.rb
254
292
  - scripts/api_snapshot.rb
255
293
  - scripts/check_api_annotations.rb
256
294
  - scripts/check_private_enforcement.rb
257
295
  - scripts/check_readme_ruby.rb
258
296
  - scripts/check_readme_runnable.rb
297
+ - scripts/migrate_spec_agent_definition.rb
298
+ - scripts/migrate_spec_agent_definition_pass2.rb
299
+ - scripts/migrate_spec_inline_pass3.rb
259
300
  - scripts/run_mutation.sh
260
301
  - sig/phronomy.rbs
261
302
  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
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module Context
6
- module Knowledge
7
- # Abstract base class for all KnowledgeSource implementations.
8
- #
9
- # Subclasses must implement #fetch(query:) and return an Array of chunk Hashes.
10
- # Each chunk Hash must contain:
11
- # :content [String] the text to inject into the context
12
- # :type [Symbol] semantic tag (e.g. :static, :rag, :entity)
13
- class Base
14
- # Retrieve knowledge chunks relevant to the given query.
15
- #
16
- # @param query [String, nil] the current user input used to select relevant chunks
17
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] optional token; raises CancellationError when cancelled
18
- # @return [Array<Hash>] array of { content: String, type: Symbol }
19
- # @api public
20
- def fetch(query: nil, cancellation_token: nil)
21
- cancellation_token&.raise_if_cancelled!
22
- raise NotImplementedError, "#{self.class}#fetch is not implemented"
23
- end
24
-
25
- # Submits a {#fetch} call to {BlockingAdapterPool} and returns a
26
- # {BlockingAdapterPool::PendingOperation}.
27
- # Callers can fan out multiple fetches in parallel and await them all.
28
- #
29
- # @param query [String, nil]
30
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
31
- # @param timeout [Numeric, nil] seconds before the operation is abandoned
32
- # @return [BlockingAdapterPool::PendingOperation]
33
- # @api public
34
- def fetch_async(query: nil, cancellation_token: nil, timeout: nil)
35
- Phronomy::Runtime.instance.blocking_io.submit(
36
- timeout: timeout,
37
- cancellation_token: cancellation_token
38
- ) do
39
- fetch(query: query, cancellation_token: cancellation_token)
40
- end
41
- end
42
-
43
- # Returns true when this source's content is considered static (i.e. does
44
- # not change between agent invocations). Static sources are eligible for
45
- # fingerprint-based caching in ContextVersionCache.
46
- #
47
- # Override in subclasses that return fixed content.
48
- #
49
- # @return [Boolean]
50
- # @api public
51
- def static?
52
- false
53
- end
54
- end
55
- end
56
- end
57
- end
58
- end
@@ -1,102 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module Context
6
- module Knowledge
7
- # A KnowledgeSource that extracts named-entity facts from conversation history.
8
- #
9
- # This is the knowledge-injection counterpart of the old EntityMemory.
10
- # It scans saved user messages with a regex heuristic (no LLM call) and
11
- # returns the discovered facts as a single knowledge chunk tagged :entity.
12
- #
13
- # EntityKnowledge is stateful: it accumulates extracted facts via #update(messages:)
14
- # which should be called each time new messages are saved.
15
- #
16
- # Supported extraction patterns (case-insensitive):
17
- # "my name is Alice" → { name: "Alice" }
18
- # "I am Alice" → { identity: "Alice" }
19
- # "I'm a software engineer" → { occupation: "software engineer" }
20
- # "I work at / for Acme" → { workplace: "Acme" }
21
- # "I live in Tokyo" → { location: "Tokyo" }
22
- # "I'm from Tokyo" → { location: "Tokyo" }
23
- # "I like / love Ruby" → { preference: "Ruby" }
24
- #
25
- # @example
26
- # ks = Phronomy::Agent::Context::Knowledge::EntityKnowledge.new
27
- # ks.update(messages: chat_messages)
28
- # agent = MyAgent.new
29
- # agent.add_knowledge_source(ks)
30
- # agent.invoke("What is my name?")
31
- class EntityKnowledge < Base
32
- PATTERNS = [
33
- [:name, /\bmy name is\s+([A-Za-z][A-Za-z0-9 \-']*)/i],
34
- [:identity, /\bI\s+am\s+([A-Z][A-Za-z0-9 \-']+)/],
35
- [:occupation, /\bI(?:'m| am) a(?:n)?\s+([A-Za-z][A-Za-z0-9 \-']*)/i],
36
- [:workplace, /\bI (?:work|worked) (?:at|for|in)\s+([A-Za-z0-9][A-Za-z0-9 \-'.&,]*)/i],
37
- [:location, /\bI live in\s+([A-Za-z][A-Za-z0-9 \-']*)/i],
38
- [:location, /\bI(?:'m| am) from\s+([A-Za-z][A-Za-z0-9 \-']*)/i],
39
- [:preference, /\bI (?:like|love|enjoy)\s+([A-Za-z][A-Za-z0-9 \-']*)/i]
40
- ].freeze
41
-
42
- def initialize
43
- @entities = {}
44
- end
45
-
46
- # Scan messages and accumulate entity facts.
47
- # Call this after saving a new set of messages (e.g. from a ConversationManager save hook).
48
- #
49
- # @param messages [Array] message objects responding to #role and #content
50
- # @api public
51
- def update(messages:)
52
- messages.each do |msg|
53
- next unless msg.role.to_sym == :user
54
-
55
- extract(msg.content.to_s).each { |key, value| @entities[key] = value }
56
- end
57
- end
58
-
59
- # Returns a single chunk containing all known entity facts in XML context format.
60
- # Returns an empty array when no entities have been discovered.
61
- #
62
- # @param query [String, nil] unused — entity knowledge is always fully injected
63
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] optional; raises CancellationError when cancelled
64
- # @return [Array<Hash>]
65
- # @api public
66
- def fetch(query: nil, cancellation_token: nil)
67
- cancellation_token&.raise_if_cancelled!
68
- return [] if @entities.empty?
69
-
70
- lines = @entities.map { |key, value| "- #{key}: #{value}" }.join("\n")
71
- content = <<~CONTENT.chomp
72
- Known facts about the user:
73
- #{lines}
74
- CONTENT
75
- [{content: content, type: :entity}]
76
- end
77
-
78
- # Returns the current entity store (primarily for testing).
79
- #
80
- # @return [Hash]
81
- # @api public
82
- def entities
83
- @entities.dup
84
- end
85
-
86
- private
87
-
88
- def extract(text)
89
- found = {}
90
- PATTERNS.each do |key, pattern|
91
- if (match = text.match(pattern))
92
- value = match[1].strip.sub(/[.!?]\s+.*$/, "").gsub(/[.,;!?]+$/, "")
93
- found[key] = value unless value.empty?
94
- end
95
- end
96
- found
97
- end
98
- end
99
- end
100
- end
101
- end
102
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module Context
6
- module Knowledge
7
- # A KnowledgeSource backed by fixed text provided at construction time.
8
- #
9
- # Useful for injecting static documents, policy files, or configuration
10
- # knowledge that does not change per request.
11
- #
12
- # @example
13
- # ks = Phronomy::Agent::Context::Knowledge::StaticKnowledge.new(
14
- # "Our refund policy: ...",
15
- # type: :policy
16
- # )
17
- # agent = MyAgent.new
18
- # agent.add_knowledge_source(ks)
19
- # agent.invoke("What is the refund policy?")
20
- class StaticKnowledge < Base
21
- # @param text [String] the static knowledge text to inject
22
- # @param type [Symbol] semantic tag for the chunk (default :static)
23
- # @param source [String, nil] label identifying where this knowledge came from
24
- # (e.g. a filename). Included in the context XML tag and exposed to the LLM
25
- # so that agents can produce grounded citations.
26
- # @api public
27
- def initialize(text, type: :static, source: nil)
28
- @text = text.to_s
29
- @type = type
30
- @source = source
31
- end
32
-
33
- # Returns the fixed text as a single chunk, regardless of query.
34
- #
35
- # @param query [String, nil] ignored for static knowledge
36
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] optional; raises CancellationError when cancelled
37
- # @return [Array<Hash>]
38
- # @api public
39
- def fetch(query: nil, cancellation_token: nil)
40
- cancellation_token&.raise_if_cancelled!
41
- return [] if @text.empty?
42
-
43
- chunk = {content: @text, type: @type}
44
- chunk[:source] = @source if @source
45
- [chunk]
46
- end
47
-
48
- # Static knowledge content never changes between invocations.
49
- # @return [true]
50
- # @api public
51
- def static?
52
- true
53
- end
54
- end
55
- end
56
- end
57
- end
58
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- # KnowledgeSource provides the interface for supplying context region 3 (Knowledge)
5
- # to the Context::Assembler.
6
- #
7
- # Each implementation returns an array of knowledge chunks via #fetch(query:).
8
- # Each chunk is a Hash with :content (String) and :type (Symbol) keys.
9
- # The Assembler wraps each chunk in an XML context tag before injecting it.
10
- module KnowledgeSource
11
- end
12
- end