lex-apollo 0.4.3 → 0.4.4

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: 98c5141f161198efe7dbdd37179e46bf36ec1c58404efa645897bc256975d3f8
4
+ data.tar.gz: c2ac6728af5ada2ab45eb8fff0a0e80d6bceac5464305bca18bd836c81fc0ffb
5
5
  SHA512:
6
- metadata.gz: 3945caccdf6763705e4f0bd9064b638a8c10920a5c802b2dfe3fde93822569af6fd70bd32097987a60a81f209ad844bc0248f92fbc096f68a30857eeee999cf6
7
- data.tar.gz: cc44d3e686c933d4d662ce2c03e2ccb2a1b10a58992e354d94bd5f805d99f676abafdcca047539b22505cf35ccb85306b785e56c65f9aca19e8548a8492b8b87
6
+ metadata.gz: 45328d181484cabb6a17aceb9d6d1a816a623fbe1dc8f5c80972b9151a2250f244d88ba88551cf0cecac70229732546a35abe32189a2ff75a4026010279a810e
7
+ data.tar.gz: 1eab2286abce601fe8a95b75c0da2edb0017bebb39e09abe05a1c7dbf72658e7955147b2ab1e1b6ed743788ed61a86c4871919e7ab53762ef3e2e40906eea00f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.4] - 2026-03-24
4
+
5
+ ### Changed
6
+ - All ~35 hardcoded numeric thresholds, limits, intervals, and confidence values are now configurable via `Legion::Settings[:apollo]`
7
+ - `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)
8
+ - `Helpers::GraphQuery` gains `spread_factor`, `default_depth`, `min_activation`, `default_query_limit`, `default_query_min_confidence` settings-aware accessors
9
+ - `Runners::Gas` gains `relate_confidence_gate`, `synthesis_confidence_cap`, `max_anticipations`, `similar_entries_limit`, `fallback_confidence` settings-aware accessors
10
+ - `Runners::Knowledge` method defaults (query limits, min_confidence, contradiction/corroboration params) now read from Settings
11
+ - `Runners::Maintenance` removed duplicate private helpers, delegates to `Helpers::Confidence` methods
12
+ - `Runners::Expertise` proficiency cap and min_agents_at_risk configurable
13
+ - Actor intervals (Decay, ExpertiseAggregator, CorroborationChecker, EntityWatchdog) configurable via `apollo.actors.*_interval`
14
+ - EntityWatchdog lookback_seconds, log_limit, exists_min_confidence configurable
15
+ - EntityExtractor min_confidence configurable via `apollo.entity_extractor.min_confidence`
16
+ - Helpers::EntityWatchdog detect_confidence configurable
17
+
3
18
  ## [0.4.3] - 2026-03-24
4
19
 
5
20
  ### 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
@@ -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
@@ -31,7 +31,7 @@ module Legion
31
31
  }
32
32
  end
33
33
 
34
- def query_knowledge(query:, limit: 10, min_confidence: 0.3, status: [:confirmed], tags: nil, **)
34
+ def query_knowledge(query:, limit: Helpers::GraphQuery.default_query_limit, min_confidence: Helpers::GraphQuery.default_query_min_confidence, status: [:confirmed], tags: nil, **) # rubocop:disable Layout/LineLength
35
35
  {
36
36
  action: :query,
37
37
  query: query,
@@ -42,7 +42,7 @@ module Legion
42
42
  }
43
43
  end
44
44
 
45
- def related_entries(entry_id:, relation_types: nil, depth: 2, **)
45
+ def related_entries(entry_id:, relation_types: nil, depth: Helpers::GraphQuery.default_depth, **)
46
46
  {
47
47
  action: :traverse,
48
48
  entry_id: entry_id,
@@ -73,7 +73,7 @@ module Legion
73
73
  new_entry = Legion::Data::Model::ApolloEntry.create(
74
74
  content: content,
75
75
  content_type: content_type_sym,
76
- confidence: Helpers::Confidence::INITIAL_CONFIDENCE,
76
+ confidence: Helpers::Confidence.initial_confidence,
77
77
  source_agent: source_agent,
78
78
  source_provider: source_provider || derive_provider_from_agent(source_agent),
79
79
  source_channel: source_channel,
@@ -100,7 +100,7 @@ module Legion
100
100
  { success: false, error: e.message }
101
101
  end
102
102
 
103
- def handle_query(query:, limit: 10, min_confidence: 0.3, status: [:confirmed], tags: nil, domain: nil, agent_id: 'unknown', **) # rubocop:disable Metrics/ParameterLists
103
+ def handle_query(query:, limit: Helpers::GraphQuery.default_query_limit, min_confidence: Helpers::GraphQuery.default_query_min_confidence, status: [:confirmed], tags: nil, domain: nil, agent_id: 'unknown', **) # rubocop:disable Metrics/ParameterLists, Layout/LineLength
104
104
  return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
105
105
 
106
106
  embedding = Helpers::Embedding.generate(text: query)
@@ -140,7 +140,7 @@ module Legion
140
140
  { success: false, error: e.message }
141
141
  end
142
142
 
143
- def redistribute_knowledge(agent_id:, min_confidence: 0.5, **)
143
+ def redistribute_knowledge(agent_id:, min_confidence: Helpers::Confidence.apollo_setting(:query, :redistribute_min_confidence, default: 0.5), **)
144
144
  return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
145
145
 
146
146
  entries = Legion::Data::Model::ApolloEntry
@@ -173,7 +173,7 @@ module Legion
173
173
  { success: false, error: e.message }
174
174
  end
175
175
 
176
- def retrieve_relevant(query: nil, limit: 5, min_confidence: 0.3, tags: nil, domain: nil, skip: false, **) # rubocop:disable Metrics/ParameterLists
176
+ def retrieve_relevant(query: nil, limit: Helpers::Confidence.apollo_setting(:query, :retrieval_limit, default: 5), min_confidence: Helpers::GraphQuery.default_query_min_confidence, tags: nil, domain: nil, skip: false, **) # rubocop:disable Metrics/ParameterLists, Layout/LineLength
177
177
  return { status: :skipped } if skip
178
178
 
179
179
  return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
@@ -208,7 +208,7 @@ module Legion
208
208
  { success: false, error: e.message }
209
209
  end
210
210
 
211
- def prepare_mesh_export(target_domain:, min_confidence: 0.5, limit: 100, **)
211
+ def prepare_mesh_export(target_domain:, min_confidence: Helpers::Confidence.apollo_setting(:query, :mesh_export_min_confidence, default: 0.5), limit: Helpers::Confidence.apollo_setting(:query, :mesh_export_limit, default: 100), **) # rubocop:disable Layout/LineLength
212
212
  unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && Legion::Data.connection
213
213
  return { success: false, error: 'apollo_data_not_available' }
214
214
  end
@@ -277,21 +277,25 @@ module Legion
277
277
  def detect_contradictions(entry_id, embedding, content)
278
278
  return [] unless embedding && defined?(Legion::Data::Model::ApolloEntry)
279
279
 
280
+ sim_limit = Helpers::Confidence.apollo_setting(:contradiction, :similar_limit, default: 10)
281
+ sim_threshold = Helpers::Confidence.apollo_setting(:contradiction, :similarity_threshold, default: 0.7)
282
+ rel_weight = Helpers::Confidence.apollo_setting(:contradiction, :relation_weight, default: 0.8)
283
+
280
284
  similar = Legion::Data::Model::ApolloEntry
281
285
  .exclude(id: entry_id)
282
286
  .exclude(embedding: nil)
283
- .limit(10).all
287
+ .limit(sim_limit).all
284
288
 
285
289
  contradictions = []
286
290
  similar.each do |existing|
287
291
  sim = Helpers::Similarity.cosine_similarity(vec_a: embedding, vec_b: existing.embedding)
288
- next unless sim > 0.7
292
+ next unless sim > sim_threshold
289
293
  next unless llm_detects_conflict?(content, existing.content)
290
294
 
291
295
  Legion::Data::Model::ApolloRelation.create(
292
296
  from_entry_id: entry_id, to_entry_id: existing.id,
293
297
  relation_type: 'contradicts', source_agent: 'system:contradiction',
294
- weight: 0.8
298
+ weight: rel_weight
295
299
  )
296
300
 
297
301
  Legion::Data::Model::ApolloEntry.where(id: [entry_id, existing.id]).update(status: 'disputed')
@@ -319,10 +323,11 @@ module Legion
319
323
  end
320
324
 
321
325
  def find_corroboration(embedding, content_type_sym, source_agent, source_channel = nil)
326
+ scan_limit = Helpers::Confidence.apollo_setting(:corroboration, :scan_limit, default: 50)
322
327
  existing = Legion::Data::Model::ApolloEntry
323
328
  .where(content_type: content_type_sym)
324
329
  .exclude(embedding: nil)
325
- .limit(50)
330
+ .limit(scan_limit)
326
331
 
327
332
  existing.each do |entry|
328
333
  next unless entry.embedding
@@ -330,7 +335,8 @@ module Legion
330
335
  sim = Helpers::Similarity.cosine_similarity(vec_a: embedding, vec_b: entry.embedding)
331
336
  next unless Helpers::Similarity.above_corroboration_threshold?(similarity: sim)
332
337
 
333
- weight = same_source_provider?(source_agent, entry) ? 0.5 : 1.0
338
+ same_provider_wt = Helpers::Confidence.apollo_setting(:corroboration, :same_provider_weight, default: 0.5)
339
+ weight = same_source_provider?(source_agent, entry) ? same_provider_wt : 1.0
334
340
 
335
341
  # Reject corroboration entirely if same channel (same data source)
336
342
  if source_channel && entry.respond_to?(:source_channel)
@@ -377,7 +383,8 @@ module Legion
377
383
  expertise.update(entry_count: expertise.entry_count + 1, last_active_at: Time.now)
378
384
  else
379
385
  Legion::Data::Model::ApolloExpertise.create(
380
- agent_id: source_agent, domain: domain, proficiency: 0.0,
386
+ agent_id: source_agent, domain: domain,
387
+ proficiency: Helpers::Confidence.apollo_setting(:expertise, :initial_proficiency, default: 0.0),
381
388
  entry_count: 1, last_active_at: Time.now
382
389
  )
383
390
  end
@@ -9,11 +9,11 @@ module Legion
9
9
  module Apollo
10
10
  module Runners
11
11
  module Maintenance
12
- def force_decay(factor: 0.5, **)
12
+ def force_decay(factor: Helpers::Confidence.apollo_setting(:maintenance, :force_decay_factor, default: 0.5), **)
13
13
  { action: :force_decay, factor: factor }
14
14
  end
15
15
 
16
- def archive_stale(days: 90, **)
16
+ def archive_stale(days: Helpers::Confidence.stale_days, **)
17
17
  { action: :archive_stale, days: days }
18
18
  end
19
19
 
@@ -22,17 +22,13 @@ module Legion
22
22
  end
23
23
 
24
24
  def run_decay_cycle(alpha: nil, min_confidence: nil, **)
25
- alpha ||= decay_alpha
26
- min_confidence ||= decay_threshold
25
+ alpha ||= Helpers::Confidence.power_law_alpha
26
+ min_confidence ||= Helpers::Confidence.decay_threshold
27
27
 
28
28
  return { decayed: 0, archived: 0 } unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && Legion::Data.connection
29
29
 
30
30
  conn = Legion::Data.connection
31
31
 
32
- # Power-law: per-cycle decay factor decreases as entries age
33
- # Factor = (hours_old / (hours_old + 1)) ^ alpha
34
- # Recent entries (small hours_old) get a factor closer to 0 (more decay)
35
- # Old entries (large hours_old) get a factor closer to 1 (less decay)
36
32
  hours_expr = Sequel.lit(
37
33
  'GREATEST(EXTRACT(EPOCH FROM (NOW() - COALESCE(updated_at, created_at))) / 3600.0, 1.0)'
38
34
  )
@@ -79,7 +75,6 @@ module Legion
79
75
  both_known = known_provider?(candidate_provider) && known_provider?(match_provider)
80
76
  next if both_known && candidate_provider == match_provider
81
77
 
82
- # Also reject if same source_channel (same data pipeline)
83
78
  candidate_channel = candidate.respond_to?(:source_channel) ? candidate.source_channel : nil
84
79
  match_channel = match.respond_to?(:source_channel) ? match.source_channel : nil
85
80
  next if candidate_channel && match_channel && candidate_channel == match_channel
@@ -96,7 +91,7 @@ module Legion
96
91
  to_entry_id: match.id,
97
92
  relation_type: 'similar_to',
98
93
  source_agent: 'system:corroboration',
99
- weight: 1.0
94
+ weight: Helpers::Confidence.apollo_setting(:corroboration, :relation_weight, default: 1.0)
100
95
  )
101
96
 
102
97
  promoted += 1
@@ -109,19 +104,10 @@ module Legion
109
104
 
110
105
  private
111
106
 
112
- def decay_alpha
113
- (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :power_law_alpha)) ||
114
- Helpers::Confidence::POWER_LAW_ALPHA
115
- end
116
-
117
107
  def known_provider?(provider)
118
108
  !provider.nil? && !provider.to_s.empty? && provider.to_s != 'unknown'
119
109
  end
120
110
 
121
- def decay_threshold
122
- (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :decay_threshold)) || 0.1
123
- end
124
-
125
111
  include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
126
112
  end
127
113
  end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Apollo
6
- VERSION = '0.4.3'
6
+ VERSION = '0.4.4'
7
7
  end
8
8
  end
9
9
  end
@@ -12,15 +12,13 @@ RSpec.describe 'Apollo Decay Cycle' do
12
12
  end
13
13
  end
14
14
 
15
- describe '#decay_alpha' do
16
- it 'returns POWER_LAW_ALPHA when settings unavailable' do
17
- expect(maintenance.send(:decay_alpha)).to eq(0.5)
15
+ describe 'configurable decay parameters' do
16
+ it 'returns POWER_LAW_ALPHA as default' do
17
+ expect(Legion::Extensions::Apollo::Helpers::Confidence.power_law_alpha).to eq(0.5)
18
18
  end
19
- end
20
19
 
21
- describe '#decay_threshold' do
22
- it 'returns default threshold when settings unavailable' do
23
- expect(maintenance.send(:decay_threshold)).to eq(0.1)
20
+ it 'returns default decay threshold' do
21
+ expect(Legion::Extensions::Apollo::Helpers::Confidence.decay_threshold).to eq(0.1)
24
22
  end
25
23
  end
26
24
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-apollo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity