lex-apollo 0.4.3 → 0.4.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37b431f37cdc8eb57e8a6ebdd1961a73e8259c2dff9f3cbb8c62f00d468b7355
4
- data.tar.gz: ebc63f21a7076df86cc10b6e68bda68fa0bbfa661fa242eb09dddb2f7502f431
3
+ metadata.gz: 692cd508a98259d83ceaf2c6b752c7c87ddeb219145291bd568fa76430e41577
4
+ data.tar.gz: b02962423b3dff2950af8b51b0cf5035a8db72f6c2ef80c4f1e54f9f0989e25c
5
5
  SHA512:
6
- metadata.gz: 3945caccdf6763705e4f0bd9064b638a8c10920a5c802b2dfe3fde93822569af6fd70bd32097987a60a81f209ad844bc0248f92fbc096f68a30857eeee999cf6
7
- data.tar.gz: cc44d3e686c933d4d662ce2c03e2ccb2a1b10a58992e354d94bd5f805d99f676abafdcca047539b22505cf35ccb85306b785e56c65f9aca19e8548a8492b8b87
6
+ metadata.gz: 7c4989d252540ae09177a73f389a1f4d1a09c51aee09acdf88bdfbb9ffb3e5d7c6522e8cf0953117518011f199f97f1e76dacb6d88aa2f10a48ecfe1da064497
7
+ data.tar.gz: a806f1485d4f3ef587f2fbd015180c027934ac87b080c4e0264ccb12a478708d89d0aa313ac3c9b2f20ed3c64ec779e2468ec7b457967c7cb6c1ce780324a884
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.5] - 2026-03-25
4
+
5
+ ### Added
6
+ - `handle_traverse` method in Knowledge runner: executes recursive CTE graph traversal SQL, returns formatted entries with depth and activation scores (closes #3)
7
+ - `Runners::Request` client-tier runner with `data_required? false`: routes `query`, `retrieve`, `ingest`, `traverse` locally when DB is available or via transport when it isn't (closes #1)
8
+ - REST API (`Api` Sinatra app) with 8 endpoints: health, query, ingest, traverse, retrieve, deprecate, expertise, stats (closes #5)
9
+ - Conditional `require 'legion/extensions/apollo/api'` when Sinatra is available
10
+
11
+ ### Fixed
12
+ - Contradiction detection now orders by embedding distance (`<=>` pgvector operator) before LIMIT, scanning the most semantically similar entries instead of arbitrary rows (closes #4)
13
+ - Whitelisted `relation_types` in `handle_traverse` against `RELATION_TYPES` constant to prevent SQL injection
14
+
15
+ ### Changed
16
+ - GAIA phase wiring updated: `knowledge_retrieval` now targets `Request#retrieve` instead of `Knowledge#retrieve_relevant` (in legion-gaia)
17
+
18
+ ## [0.4.4] - 2026-03-24
19
+
20
+ ### Changed
21
+ - All ~35 hardcoded numeric thresholds, limits, intervals, and confidence values are now configurable via `Legion::Settings[:apollo]`
22
+ - `Helpers::Confidence` gains `apollo_setting(*keys, default:)` lookup helper and accessor methods for every constant (initial_confidence, corroboration_boost, retrieval_boost, power_law_alpha, decay_threshold, write gates, stale_days)
23
+ - `Helpers::GraphQuery` gains `spread_factor`, `default_depth`, `min_activation`, `default_query_limit`, `default_query_min_confidence` settings-aware accessors
24
+ - `Runners::Gas` gains `relate_confidence_gate`, `synthesis_confidence_cap`, `max_anticipations`, `similar_entries_limit`, `fallback_confidence` settings-aware accessors
25
+ - `Runners::Knowledge` method defaults (query limits, min_confidence, contradiction/corroboration params) now read from Settings
26
+ - `Runners::Maintenance` removed duplicate private helpers, delegates to `Helpers::Confidence` methods
27
+ - `Runners::Expertise` proficiency cap and min_agents_at_risk configurable
28
+ - Actor intervals (Decay, ExpertiseAggregator, CorroborationChecker, EntityWatchdog) configurable via `apollo.actors.*_interval`
29
+ - EntityWatchdog lookback_seconds, log_limit, exists_min_confidence configurable
30
+ - EntityExtractor min_confidence configurable via `apollo.entity_extractor.min_confidence`
31
+ - Helpers::EntityWatchdog detect_confidence configurable
32
+
3
33
  ## [0.4.3] - 2026-03-24
4
34
 
5
35
  ### Fixed
@@ -10,7 +10,7 @@ module Legion
10
10
  class CorroborationChecker < Legion::Extensions::Actors::Every
11
11
  def runner_class = Legion::Extensions::Apollo::Runners::Maintenance
12
12
  def runner_function = 'check_corroboration'
13
- def time = 900
13
+ def time = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :actors, :corroboration_interval)) || 900
14
14
  def run_now? = false
15
15
  def use_runner? = false
16
16
  def check_subtask? = false
@@ -10,7 +10,7 @@ module Legion
10
10
  class Decay < Legion::Extensions::Actors::Every
11
11
  def runner_class = Legion::Extensions::Apollo::Runners::Maintenance
12
12
  def runner_function = 'run_decay_cycle'
13
- def time = 3600
13
+ def time = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :actors, :decay_interval)) || 3600
14
14
  def run_now? = false
15
15
  def use_runner? = false
16
16
  def check_subtask? = false
@@ -18,7 +18,7 @@ module Legion
18
18
 
19
19
  def runner_class = self.class
20
20
  def runner_function = 'scan_and_ingest'
21
- def time = 120
21
+ def time = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :actors, :entity_watchdog_interval)) || 120
22
22
  def run_now? = false
23
23
  def use_runner? = false
24
24
  def check_subtask? = false
@@ -62,11 +62,13 @@ module Legion
62
62
  def recent_task_log_texts
63
63
  return [] unless defined?(Legion::Data) && defined?(Legion::Data::Model::TaskLog)
64
64
 
65
- cutoff = Time.now - TASK_LOG_LOOKBACK_SECONDS
65
+ lookback = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :entity_watchdog, :lookback_seconds)) || TASK_LOG_LOOKBACK_SECONDS
66
+ log_limit = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :entity_watchdog, :log_limit)) || TASK_LOG_LIMIT
67
+ cutoff = Time.now - lookback
66
68
  logs = Legion::Data::Model::TaskLog
67
69
  .where { created_at >= cutoff }
68
70
  .order(Sequel.desc(:created_at))
69
- .limit(TASK_LOG_LIMIT)
71
+ .limit(log_limit)
70
72
  .select_map(:message)
71
73
  logs.map(&:to_s).reject(&:empty?).uniq
72
74
  rescue StandardError
@@ -77,7 +79,7 @@ module Legion
77
79
  result = retrieve_relevant(
78
80
  query: entity[:name].to_s,
79
81
  limit: 1,
80
- min_confidence: 0.1,
82
+ min_confidence: Helpers::Confidence.apollo_setting(:entity_watchdog, :exists_min_confidence, default: 0.1),
81
83
  tags: [entity[:type].to_s]
82
84
  )
83
85
  return false unless result[:success] && result[:count].positive?
@@ -10,7 +10,7 @@ module Legion
10
10
  class ExpertiseAggregator < Legion::Extensions::Actors::Every
11
11
  def runner_class = Legion::Extensions::Apollo::Runners::Expertise
12
12
  def runner_function = 'aggregate'
13
- def time = 1800
13
+ def time = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :actors, :expertise_interval)) || 1800
14
14
  def run_now? = false
15
15
  def use_runner? = false
16
16
  def check_subtask? = false
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra/base' unless defined?(Sinatra)
4
+ require 'json'
5
+
6
+ module Legion
7
+ module Extensions
8
+ module Apollo
9
+ class Api < Sinatra::Base
10
+ set :host_authorization, permitted: :any
11
+
12
+ before do
13
+ content_type :json
14
+ end
15
+
16
+ helpers do
17
+ def json_body
18
+ body = request.body.read
19
+ return {} if body.empty?
20
+
21
+ ::JSON.parse(body, symbolize_names: true)
22
+ rescue ::JSON::ParserError
23
+ halt 400, { error: 'invalid JSON' }.to_json
24
+ end
25
+
26
+ def runner
27
+ @runner ||= begin
28
+ obj = Object.new
29
+ obj.extend(Runners::Knowledge)
30
+ obj
31
+ end
32
+ end
33
+
34
+ def expertise_runner
35
+ @expertise_runner ||= begin
36
+ obj = Object.new
37
+ obj.extend(Runners::Expertise)
38
+ obj
39
+ end
40
+ end
41
+ end
42
+
43
+ # Health check
44
+ get '/api/apollo/health' do
45
+ available = defined?(Legion::Data::Model::ApolloEntry) ? true : false
46
+ { status: available ? 'ok' : 'degraded', data_available: available }.to_json
47
+ end
48
+
49
+ # Query knowledge (semantic search)
50
+ post '/api/apollo/query' do
51
+ req = json_body
52
+ halt 400, { error: 'query is required' }.to_json unless req[:query]
53
+
54
+ result = runner.handle_query(
55
+ query: req[:query],
56
+ limit: req[:limit] || 10,
57
+ min_confidence: req[:min_confidence] || 0.3,
58
+ status: req[:status] || [:confirmed],
59
+ tags: req[:tags],
60
+ domain: req[:domain],
61
+ agent_id: req[:agent_id] || 'api'
62
+ )
63
+ status result[:success] ? 200 : 500
64
+ result.to_json
65
+ end
66
+
67
+ # Ingest knowledge
68
+ post '/api/apollo/ingest' do
69
+ req = json_body
70
+ halt 400, { error: 'content is required' }.to_json unless req[:content]
71
+ halt 400, { error: 'content_type is required' }.to_json unless req[:content_type]
72
+
73
+ result = runner.handle_ingest(
74
+ content: req[:content],
75
+ content_type: req[:content_type],
76
+ tags: req[:tags] || [],
77
+ source_agent: req[:source_agent] || 'api',
78
+ source_provider: req[:source_provider],
79
+ source_channel: req[:source_channel],
80
+ knowledge_domain: req[:knowledge_domain],
81
+ context: req[:context] || {}
82
+ )
83
+ status result[:success] ? 201 : 500
84
+ result.to_json
85
+ end
86
+
87
+ # Graph traversal
88
+ post '/api/apollo/traverse' do
89
+ req = json_body
90
+ halt 400, { error: 'entry_id is required' }.to_json unless req[:entry_id]
91
+
92
+ result = runner.handle_traverse(
93
+ entry_id: req[:entry_id],
94
+ depth: req[:depth] || 2,
95
+ relation_types: req[:relation_types],
96
+ agent_id: req[:agent_id] || 'api'
97
+ )
98
+ status result[:success] ? 200 : 500
99
+ result.to_json
100
+ end
101
+
102
+ # Retrieve relevant (GAIA-compatible)
103
+ post '/api/apollo/retrieve' do
104
+ req = json_body
105
+ halt 400, { error: 'query is required' }.to_json unless req[:query]
106
+
107
+ result = runner.retrieve_relevant(
108
+ query: req[:query],
109
+ limit: req[:limit] || 5,
110
+ min_confidence: req[:min_confidence] || 0.3,
111
+ tags: req[:tags],
112
+ domain: req[:domain]
113
+ )
114
+ status result[:success] ? 200 : 500
115
+ result.to_json
116
+ end
117
+
118
+ # Deprecate entry
119
+ post '/api/apollo/entries/:id/deprecate' do
120
+ result = runner.deprecate_entry(
121
+ entry_id: params[:id],
122
+ reason: json_body[:reason] || 'deprecated via API'
123
+ )
124
+ result.to_json
125
+ end
126
+
127
+ # Domains at risk — must be declared before /:agent_id to avoid routing conflict
128
+ get '/api/apollo/expertise/at-risk' do
129
+ result = expertise_runner.domains_at_risk
130
+ result.to_json
131
+ end
132
+
133
+ # Expertise for an agent
134
+ get '/api/apollo/expertise/:agent_id' do
135
+ result = expertise_runner.agent_profile(agent_id: params[:agent_id])
136
+ result.to_json
137
+ end
138
+
139
+ # Statistics
140
+ get '/api/apollo/stats' do
141
+ stats = {}
142
+ if defined?(Legion::Data::Model::ApolloEntry)
143
+ stats[:total_entries] = Legion::Data::Model::ApolloEntry.count
144
+ stats[:by_status] = Legion::Data::Model::ApolloEntry.group_and_count(:status).all
145
+ .to_h { |r| [r[:status], r[:count]] }
146
+ stats[:by_content_type] = Legion::Data::Model::ApolloEntry.group_and_count(:content_type).all
147
+ .to_h { |r| [r[:content_type], r[:count]] }
148
+ stats[:total_relations] = Legion::Data::Model::ApolloRelation.count if defined?(Legion::Data::Model::ApolloRelation)
149
+ else
150
+ stats[:error] = 'apollo_data_not_available'
151
+ end
152
+ stats.to_json
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -20,7 +20,28 @@ module Legion
20
20
 
21
21
  module_function
22
22
 
23
- def apply_decay(confidence:, age_hours: nil, alpha: POWER_LAW_ALPHA, **)
23
+ def apollo_setting(*keys, default:)
24
+ return default unless defined?(Legion::Settings) && !Legion::Settings[:apollo].nil?
25
+
26
+ Legion::Settings[:apollo].dig(*keys) || default
27
+ rescue StandardError
28
+ default
29
+ end
30
+
31
+ def initial_confidence = apollo_setting(:confidence, :initial, default: INITIAL_CONFIDENCE)
32
+ def corroboration_boost = apollo_setting(:confidence, :corroboration_boost, default: CORROBORATION_BOOST)
33
+ def retrieval_boost = apollo_setting(:confidence, :retrieval_boost, default: RETRIEVAL_BOOST)
34
+ def power_law_alpha = apollo_setting(:power_law_alpha, default: POWER_LAW_ALPHA)
35
+ def decay_threshold = apollo_setting(:decay_threshold, default: DECAY_THRESHOLD)
36
+ def write_confidence_gate = apollo_setting(:confidence, :write_gate, default: WRITE_CONFIDENCE_GATE)
37
+ def write_novelty_gate = apollo_setting(:confidence, :novelty_gate, default: WRITE_NOVELTY_GATE)
38
+ def stale_days = apollo_setting(:stale_days, default: STALE_DAYS)
39
+
40
+ def corroboration_similarity_threshold
41
+ apollo_setting(:confidence, :corroboration_similarity, default: CORROBORATION_SIMILARITY_THRESHOLD)
42
+ end
43
+
44
+ def apply_decay(confidence:, age_hours: nil, alpha: power_law_alpha, **)
24
45
  if age_hours
25
46
  [confidence * ((age_hours.clamp(0, Float::INFINITY) + 2.0)**(-alpha)) / ((age_hours.clamp(0, Float::INFINITY) + 1.0)**(-alpha)), 0.0].max
26
47
  else
@@ -30,19 +51,19 @@ module Legion
30
51
  end
31
52
 
32
53
  def apply_retrieval_boost(confidence:, **)
33
- [confidence + RETRIEVAL_BOOST, 1.0].min
54
+ [confidence + retrieval_boost, 1.0].min
34
55
  end
35
56
 
36
57
  def apply_corroboration_boost(confidence:, weight: 1.0, **)
37
- [confidence + (CORROBORATION_BOOST * weight), 1.0].min
58
+ [confidence + (corroboration_boost * weight), 1.0].min
38
59
  end
39
60
 
40
61
  def decayed?(confidence:, **)
41
- confidence < DECAY_THRESHOLD
62
+ confidence < decay_threshold
42
63
  end
43
64
 
44
65
  def meets_write_gate?(confidence:, novelty:, **)
45
- confidence > WRITE_CONFIDENCE_GATE && novelty > WRITE_NOVELTY_GATE
66
+ confidence > write_confidence_gate && novelty > write_novelty_gate
46
67
  end
47
68
  end
48
69
  end
@@ -23,7 +23,7 @@ module Legion
23
23
  next unless pattern
24
24
 
25
25
  text.scan(pattern).each do |match|
26
- entities << { type: type_sym, value: match.strip, confidence: 0.5 }
26
+ entities << { type: type_sym, value: match.strip, confidence: Confidence.apollo_setting(:entity_watchdog, :detect_confidence, default: 0.5) }
27
27
  end
28
28
  end
29
29
 
@@ -11,7 +11,15 @@ module Legion
11
11
 
12
12
  module_function
13
13
 
14
- def build_traversal_sql(depth: DEFAULT_DEPTH, relation_types: nil, min_activation: MIN_ACTIVATION, **)
14
+ def spread_factor = Confidence.apollo_setting(:graph, :spread_factor, default: SPREAD_FACTOR)
15
+ def default_depth = Confidence.apollo_setting(:graph, :default_depth, default: DEFAULT_DEPTH)
16
+ def min_activation = Confidence.apollo_setting(:graph, :min_activation, default: MIN_ACTIVATION)
17
+
18
+ def default_query_limit = Confidence.apollo_setting(:query, :default_limit, default: 10)
19
+ def default_query_min_confidence = Confidence.apollo_setting(:query, :default_min_confidence, default: 0.3)
20
+
21
+ def build_traversal_sql(depth: default_depth, relation_types: nil, min_activation: self.min_activation, **)
22
+ sf = spread_factor
15
23
  type_filter = if relation_types&.any?
16
24
  types = relation_types.map { |t| "'#{t}'" }.join(', ')
17
25
  "AND r.relation_type IN (#{types})"
@@ -30,12 +38,12 @@ module Legion
30
38
 
31
39
  SELECT e.id, e.content, e.content_type, e.confidence, e.tags, e.source_agent,
32
40
  g.depth + 1,
33
- (g.activation * #{SPREAD_FACTOR} * r.weight)::float
41
+ (g.activation * #{sf} * r.weight)::float
34
42
  FROM graph g
35
43
  JOIN apollo_relations r ON r.from_entry_id = g.id #{type_filter}
36
44
  JOIN apollo_entries e ON e.id = r.to_entry_id
37
45
  WHERE g.depth < #{depth}
38
- AND g.activation * #{SPREAD_FACTOR} * r.weight > #{min_activation}
46
+ AND g.activation * #{sf} * r.weight > #{min_activation}
39
47
  )
40
48
  SELECT DISTINCT ON (id) id, content, content_type, confidence, tags, source_agent,
41
49
  depth, activation
@@ -44,7 +52,7 @@ module Legion
44
52
  SQL
45
53
  end
46
54
 
47
- def build_semantic_search_sql(limit: 10, min_confidence: 0.3, statuses: nil, tags: nil, domain: nil, **)
55
+ def build_semantic_search_sql(limit: default_query_limit, min_confidence: default_query_min_confidence, statuses: nil, tags: nil, domain: nil, **)
48
56
  conditions = ["e.confidence >= #{min_confidence}"]
49
57
 
50
58
  if statuses&.any?
@@ -8,7 +8,7 @@ module Legion
8
8
  DEFAULT_ENTITY_TYPES = %w[person service repository concept].freeze
9
9
  DEFAULT_MIN_CONFIDENCE = 0.7
10
10
 
11
- def extract_entities(text:, entity_types: nil, min_confidence: DEFAULT_MIN_CONFIDENCE, **)
11
+ def extract_entities(text:, entity_types: nil, min_confidence: Helpers::Confidence.apollo_setting(:entity_extractor, :min_confidence, default: DEFAULT_MIN_CONFIDENCE), **) # rubocop:disable Layout/LineLength
12
12
  return { success: true, entities: [], source: :empty } if text.to_s.strip.empty?
13
13
 
14
14
  return { success: true, entities: [], source: :unavailable } unless defined?(Legion::LLM) && Legion::LLM.started?
@@ -5,11 +5,11 @@ module Legion
5
5
  module Apollo
6
6
  module Runners
7
7
  module Expertise
8
- def get_expertise(domain:, min_proficiency: 0.0, **)
8
+ def get_expertise(domain:, min_proficiency: Helpers::Confidence.apollo_setting(:expertise, :initial_proficiency, default: 0.0), **)
9
9
  { action: :expertise_query, domain: domain, min_proficiency: min_proficiency }
10
10
  end
11
11
 
12
- def domains_at_risk(min_agents: 2, **)
12
+ def domains_at_risk(min_agents: Helpers::Confidence.apollo_setting(:expertise, :min_agents_at_risk, default: 2), **)
13
13
  { action: :domains_at_risk, min_agents: min_agents }
14
14
  end
15
15
 
@@ -40,7 +40,8 @@ module Legion
40
40
  groups.each_value do |group|
41
41
  avg = group[:confidences].sum / group[:confidences].size
42
42
  count = group[:confidences].size
43
- proficiency = [avg * Math.log2(count + 1), 1.0].min
43
+ cap = Helpers::Confidence.apollo_setting(:expertise, :proficiency_cap, default: 1.0)
44
+ proficiency = [avg * Math.log2(count + 1), cap].min
44
45
 
45
46
  existing = Legion::Data::Model::ApolloExpertise
46
47
  .where(agent_id: group[:agent_id], domain: group[:domain]).first
@@ -5,8 +5,23 @@ module Legion
5
5
  module Apollo
6
6
  module Runners
7
7
  module Gas
8
+ RELATION_TYPES = %w[
9
+ similar_to contradicts depends_on causes
10
+ part_of supersedes supports_by extends
11
+ ].freeze
12
+
13
+ RELATE_CONFIDENCE_GATE = 0.7
14
+ SYNTHESIS_CONFIDENCE_CAP = 0.7
15
+ MAX_ANTICIPATIONS = 3
16
+
8
17
  module_function
9
18
 
19
+ def relate_confidence_gate = Helpers::Confidence.apollo_setting(:gas, :relate_confidence_gate, default: RELATE_CONFIDENCE_GATE)
20
+ def synthesis_confidence_cap = Helpers::Confidence.apollo_setting(:gas, :synthesis_confidence_cap, default: SYNTHESIS_CONFIDENCE_CAP)
21
+ def max_anticipations = Helpers::Confidence.apollo_setting(:gas, :max_anticipations, default: MAX_ANTICIPATIONS)
22
+ def similar_entries_limit = Helpers::Confidence.apollo_setting(:gas, :similar_entries_limit, default: 3)
23
+ def fallback_confidence = Helpers::Confidence.apollo_setting(:gas, :fallback_confidence, default: 0.5)
24
+
10
25
  def process(audit_event)
11
26
  return { phases_completed: 0, reason: 'no content' } unless processable?(audit_event)
12
27
 
@@ -57,13 +72,6 @@ module Legion
57
72
  []
58
73
  end
59
74
 
60
- RELATION_TYPES = %w[
61
- similar_to contradicts depends_on causes
62
- part_of supersedes supports_by extends
63
- ].freeze
64
-
65
- RELATE_CONFIDENCE_GATE = 0.7
66
-
67
75
  # Phase 3: Relate - classify relationships between new and existing entries
68
76
  def phase_relate(facts, _entities)
69
77
  return [] unless defined?(Runners::Knowledge)
@@ -81,8 +89,6 @@ module Legion
81
89
  relations
82
90
  end
83
91
 
84
- SYNTHESIS_CONFIDENCE_CAP = 0.7
85
-
86
92
  # Phase 4: Synthesize - generate derivative knowledge
87
93
  def phase_synthesize(facts, _relations)
88
94
  return [] if facts.length < 2
@@ -115,8 +121,6 @@ module Legion
115
121
  { deposited: deposited }
116
122
  end
117
123
 
118
- MAX_ANTICIPATIONS = 3
119
-
120
124
  # Phase 6: Anticipate - pre-cache likely follow-up questions
121
125
  def phase_anticipate(facts, _synthesis)
122
126
  return [] if facts.empty?
@@ -128,9 +132,11 @@ module Legion
128
132
  end
129
133
 
130
134
  def fetch_similar_entries(facts)
135
+ lim = similar_entries_limit
136
+ min_conf = Helpers::GraphQuery.default_query_min_confidence
131
137
  entries = []
132
138
  facts.each do |fact|
133
- result = Runners::Knowledge.retrieve_relevant(query: fact[:content], limit: 3, min_confidence: 0.3)
139
+ result = Runners::Knowledge.retrieve_relevant(query: fact[:content], limit: lim, min_confidence: min_conf)
134
140
  entries.concat(result[:entries]) if result[:success] && result[:entries]&.any?
135
141
  rescue StandardError
136
142
  next
@@ -139,13 +145,14 @@ module Legion
139
145
  end
140
146
 
141
147
  def classify_relation(fact, entry)
148
+ fb_conf = fallback_confidence
142
149
  if llm_available?
143
150
  llm_classify_relation(fact, entry)
144
151
  else
145
- { from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence: 0.5 }
152
+ { from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence: fb_conf }
146
153
  end
147
154
  rescue StandardError
148
- { from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence: 0.5 }
155
+ { from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence: fb_conf }
149
156
  end
150
157
 
151
158
  def llm_classify_relation(fact, entry)
@@ -190,7 +197,7 @@ module Legion
190
197
 
191
198
  conf = best[:confidence] || best['confidence'] || 0
192
199
  rtype = best[:relation_type] || best['relation_type']
193
- return fallback_relation(fact, entry) if conf < RELATE_CONFIDENCE_GATE || !RELATION_TYPES.include?(rtype)
200
+ return fallback_relation(fact, entry) if conf < relate_confidence_gate || !RELATION_TYPES.include?(rtype)
194
201
 
195
202
  { from_content: fact[:content], to_id: entry[:id], relation_type: rtype, confidence: conf }
196
203
  rescue StandardError
@@ -198,7 +205,7 @@ module Legion
198
205
  end
199
206
 
200
207
  def fallback_relation(fact, entry)
201
- { from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence: 0.5 }
208
+ { from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence: fallback_confidence }
202
209
  end
203
210
 
204
211
  def llm_synthesize(facts)
@@ -249,13 +256,14 @@ module Legion
249
256
  def build_synthesis_entry(item, facts)
250
257
  source_indices = item[:source_indices] || item['source_indices'] || []
251
258
  source_confs = source_indices.filter_map { |i| facts[i]&.dig(:confidence) }
252
- geo_mean = source_confs.empty? ? 0.5 : geometric_mean(source_confs)
259
+ fb = fallback_confidence
260
+ geo_mean = source_confs.empty? ? fb : geometric_mean(source_confs)
253
261
 
254
262
  {
255
263
  content: item[:content] || item['content'],
256
264
  content_type: (item[:content_type] || item['content_type'] || 'inference').to_sym,
257
265
  status: :candidate,
258
- confidence: [geo_mean, SYNTHESIS_CONFIDENCE_CAP].min,
266
+ confidence: [geo_mean, synthesis_confidence_cap].min,
259
267
  source_indices: source_indices
260
268
  }
261
269
  end
@@ -294,7 +302,7 @@ module Legion
294
302
  content = result.respond_to?(:message) ? result.message[:content] : result.to_s
295
303
  parsed = Legion::JSON.load(content)
296
304
  questions = parsed.is_a?(Hash) ? (parsed[:questions] || parsed['questions'] || []) : []
297
- questions = questions.first(MAX_ANTICIPATIONS)
305
+ questions = questions.first(max_anticipations)
298
306
 
299
307
  questions.map do |q|
300
308
  promote_to_pattern_store(question: q, facts: facts)
@@ -310,7 +318,7 @@ module Legion
310
318
  Legion::Extensions::Agentic::TBI::PatternStore.promote_candidate(
311
319
  intent: question,
312
320
  resolution: { source: 'gas_anticipate', facts: facts.map { |f| f[:content] } },
313
- confidence: 0.5
321
+ confidence: fallback_confidence
314
322
  )
315
323
  rescue StandardError
316
324
  nil