lex-apollo 0.2.0 → 0.3.1

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: fcb22d66eb9b08e01ececa39900c455be2aa64f358a2976878c0b2934b71670a
4
- data.tar.gz: 1d2d41cf8835c04e14827e22caaed2a848d0c8fc4c41cef3aeecc927041849b3
3
+ metadata.gz: 1478924cdbe16dac455d02313dd3de7b42bb0d022da411213b537f44527d14c0
4
+ data.tar.gz: 2e72cfa6dab1b790b1fe904ea23fe55ae093e0c6570a3b2790c8b7c0930b64cb
5
5
  SHA512:
6
- metadata.gz: 6bff39d97c42ca8085b7937066e38d52c50fe302cd1d04e80d8a4aca6762eb5c28b33eb05a0ea16adf391b86bffa3d0936f3d83caf3d936d5703e8c08bd0140c
7
- data.tar.gz: 694442a97667f0355bba359938c7bd9317f9d2308a6ef169a54c59bc63e097c7786625a525b33defe44581bc20593cba259ff68be019c6c55708b6ad7f31e43a
6
+ metadata.gz: a8aafbcf43d73647b48c27a8e400de209a723591b59b15f92f3144fd73ea4ff374a735cc4aa4720ffa48560461fe6951da231a0dc0114064f38d1f9b4cc170a6
7
+ data.tar.gz: d5d04a370239f53a7ab891a957ace9f52b3d9a459c93494892134a4658153cc13e629de766aaf84f4598b9b0d3c2406df316869156592e778eb46ea89848ce83
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1] - 2026-03-17
4
+
5
+ ### Added
6
+ - `Apollo::Transport` module now extends `Legion::Extensions::Transport` to provide the `build` method expected by LegionIO's `build_transport` call
7
+
8
+ ## [0.3.0] - 2026-03-17
9
+
10
+ ### Added
11
+ - Contradiction detection: LLM-based conflict analysis during knowledge ingest via structured output
12
+ - `detect_contradictions`: finds similar entries and checks for semantic conflicts, creates `contradicts` relations
13
+ - `run_decay_cycle`: hourly confidence reduction with configurable rate (0.998 default) and archival threshold (0.1)
14
+ - `GaiaIntegration.publish_insight`: auto-publish high-confidence insights from cognitive reflection phase
15
+ - `GaiaIntegration.handle_mesh_departure`: knowledge vulnerability detection when agents leave the mesh
16
+
3
17
  ## [0.2.0] - 2026-03-16
4
18
 
5
19
  ### Added
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Apollo
6
+ module GaiaIntegration
7
+ PUBLISH_CONFIDENCE_THRESHOLD = 0.6
8
+ PUBLISH_NOVELTY_THRESHOLD = 0.3
9
+
10
+ class << self
11
+ def publish_insight(insight, agent_id:)
12
+ return nil unless publishable?(insight)
13
+ return nil unless defined?(Legion::Extensions::Apollo::Client)
14
+
15
+ client = Legion::Extensions::Apollo::Client.new(agent_id: agent_id)
16
+ client.store_knowledge(
17
+ content: insight[:content],
18
+ content_type: insight[:domain] || 'general',
19
+ source_agent: agent_id,
20
+ tags: Array(insight[:tags])
21
+ )
22
+ end
23
+
24
+ def publishable?(insight)
25
+ (insight[:confidence] || 0) > PUBLISH_CONFIDENCE_THRESHOLD &&
26
+ (insight[:novelty] || 0) > PUBLISH_NOVELTY_THRESHOLD
27
+ end
28
+
29
+ def handle_mesh_departure(agent_id:)
30
+ return nil unless defined?(Legion::Data::Model::ApolloExpertise)
31
+
32
+ sole_expert_domains = Legion::Data::Model::ApolloExpertise
33
+ .where(agent_id: agent_id)
34
+ .all
35
+ .select { |e| sole_expert?(e.domain, agent_id) }
36
+ .map(&:domain)
37
+
38
+ return nil if sole_expert_domains.empty?
39
+
40
+ {
41
+ event: 'knowledge_vulnerability',
42
+ agent_id: agent_id,
43
+ domains_at_risk: sole_expert_domains,
44
+ severity: sole_expert_domains.size > 3 ? :critical : :warning
45
+ }
46
+ end
47
+
48
+ private
49
+
50
+ def sole_expert?(domain, agent_id)
51
+ return false unless defined?(Legion::Data::Model::ApolloExpertise)
52
+
53
+ count = Legion::Data::Model::ApolloExpertise
54
+ .where(domain: domain)
55
+ .exclude(agent_id: agent_id)
56
+ .count
57
+ count.zero?
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -82,8 +82,10 @@ module Legion
82
82
  entry_id: existing_id, agent_id: source_agent, action: 'ingest'
83
83
  )
84
84
 
85
+ contradictions = detect_contradictions(existing_id, embedding, content)
86
+
85
87
  { success: true, entry_id: existing_id, status: corroborated ? 'corroborated' : 'candidate',
86
- corroborated: corroborated }
88
+ corroborated: corroborated, contradictions: contradictions }
87
89
  rescue Sequel::Error => e
88
90
  { success: false, error: e.message }
89
91
  end
@@ -163,6 +165,49 @@ module Legion
163
165
 
164
166
  private
165
167
 
168
+ def detect_contradictions(entry_id, embedding, content)
169
+ return [] unless embedding && defined?(Legion::Data::Model::ApolloEntry)
170
+
171
+ similar = Legion::Data::Model::ApolloEntry
172
+ .exclude(id: entry_id)
173
+ .exclude(embedding: nil)
174
+ .limit(10).all
175
+
176
+ contradictions = []
177
+ similar.each do |existing|
178
+ sim = Helpers::Similarity.cosine_similarity(vec_a: embedding, vec_b: existing.embedding)
179
+ next unless sim > 0.7
180
+ next unless llm_detects_conflict?(content, existing.content)
181
+
182
+ Legion::Data::Model::ApolloRelation.create(
183
+ from_entry_id: entry_id, to_entry_id: existing.id,
184
+ relation_type: 'contradicts', source_agent: 'system:contradiction',
185
+ weight: 0.8
186
+ )
187
+
188
+ Legion::Data::Model::ApolloEntry.where(id: [entry_id, existing.id]).update(status: 'disputed')
189
+ contradictions << existing.id
190
+ end
191
+ contradictions
192
+ rescue Sequel::Error
193
+ []
194
+ end
195
+
196
+ def llm_detects_conflict?(content_a, content_b)
197
+ return false unless defined?(Legion::LLM) && Legion::LLM.respond_to?(:structured)
198
+
199
+ result = Legion::LLM.structured(
200
+ messages: [
201
+ { role: 'system', content: 'Do these two statements contradict each other? Return JSON.' },
202
+ { role: 'user', content: "A: #{content_a}\n\nB: #{content_b}" }
203
+ ],
204
+ schema: { type: 'object', properties: { contradicts: { type: 'boolean' } } }
205
+ )
206
+ result[:data]&.dig(:contradicts) == true
207
+ rescue StandardError
208
+ false
209
+ end
210
+
166
211
  def find_corroboration(embedding, content_type_sym, source_agent)
167
212
  existing = Legion::Data::Model::ApolloEntry
168
213
  .where(content_type: content_type_sym)
@@ -21,6 +21,27 @@ module Legion
21
21
  { action: :resolve_dispute, entry_id: entry_id, resolution: resolution }
22
22
  end
23
23
 
24
+ def run_decay_cycle(rate: nil, min_confidence: nil, **)
25
+ rate ||= decay_rate
26
+ min_confidence ||= decay_threshold
27
+
28
+ return { decayed: 0, archived: 0 } unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && Legion::Data.connection
29
+
30
+ conn = Legion::Data.connection
31
+ decayed = conn[:apollo_entries]
32
+ .exclude(status: 'archived')
33
+ .update(confidence: Sequel[:confidence] * rate)
34
+
35
+ archived = conn[:apollo_entries]
36
+ .where { confidence < min_confidence }
37
+ .exclude(status: 'archived')
38
+ .update(status: 'archived')
39
+
40
+ { decayed: decayed, archived: archived, rate: rate, threshold: min_confidence }
41
+ rescue Sequel::Error => e
42
+ { decayed: 0, archived: 0, error: e.message }
43
+ end
44
+
24
45
  def check_corroboration(**)
25
46
  return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
26
47
 
@@ -64,6 +85,16 @@ module Legion
64
85
  { success: false, error: e.message }
65
86
  end
66
87
 
88
+ private
89
+
90
+ def decay_rate
91
+ (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :decay_rate)) || 0.998
92
+ end
93
+
94
+ def decay_threshold
95
+ (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :decay_threshold)) || 0.1
96
+ end
97
+
67
98
  include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
68
99
  end
69
100
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Apollo
6
+ module Transport
7
+ extend Legion::Extensions::Transport
8
+ end
9
+ end
10
+ end
11
+ end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Apollo
6
- VERSION = '0.2.0'
6
+ VERSION = '0.3.1'
7
7
  end
8
8
  end
9
9
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe 'Apollo Contradiction Detection' do
6
+ let(:knowledge) { Object.new.extend(Legion::Extensions::Apollo::Runners::Knowledge) }
7
+
8
+ describe '#llm_detects_conflict?' do
9
+ it 'returns false when LLM unavailable' do
10
+ expect(knowledge.send(:llm_detects_conflict?, 'sky is blue', 'sky is red')).to be false
11
+ end
12
+ end
13
+
14
+ describe '#detect_contradictions' do
15
+ it 'returns empty when ApolloEntry model unavailable' do
16
+ expect(knowledge.send(:detect_contradictions, 1, nil, 'test')).to eq([])
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'legion/extensions/apollo/gaia_integration'
5
+
6
+ RSpec.describe Legion::Extensions::Apollo::GaiaIntegration do
7
+ describe '.publishable?' do
8
+ it 'returns true when above thresholds' do
9
+ expect(described_class.publishable?({ confidence: 0.8, novelty: 0.5 })).to be true
10
+ end
11
+
12
+ it 'returns false when below confidence' do
13
+ expect(described_class.publishable?({ confidence: 0.3, novelty: 0.5 })).to be false
14
+ end
15
+
16
+ it 'returns false when below novelty' do
17
+ expect(described_class.publishable?({ confidence: 0.8, novelty: 0.1 })).to be false
18
+ end
19
+
20
+ it 'returns false with empty hash' do
21
+ expect(described_class.publishable?({})).to be false
22
+ end
23
+ end
24
+
25
+ describe '.handle_mesh_departure' do
26
+ it 'returns nil when ApolloExpertise unavailable' do
27
+ expect(described_class.handle_mesh_departure(agent_id: 'test')).to be_nil
28
+ end
29
+ end
30
+
31
+ describe '.publish_insight' do
32
+ it 'returns nil when not publishable' do
33
+ expect(described_class.publish_insight({ confidence: 0.1, novelty: 0.1 }, agent_id: 'test')).to be_nil
34
+ end
35
+
36
+ it 'calls client when publishable and client available' do
37
+ client_double = instance_double(Legion::Extensions::Apollo::Client)
38
+ allow(Legion::Extensions::Apollo::Client).to receive(:new).and_return(client_double)
39
+ allow(client_double).to receive(:store_knowledge).and_return({ success: true })
40
+
41
+ result = described_class.publish_insight(
42
+ { confidence: 0.9, novelty: 0.5, content: 'test insight', domain: 'fact' },
43
+ agent_id: 'test-agent'
44
+ )
45
+ expect(result).to eq({ success: true })
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe 'Apollo Decay Cycle' do
6
+ let(:maintenance) { Object.new.extend(Legion::Extensions::Apollo::Runners::Maintenance) }
7
+
8
+ describe '#run_decay_cycle' do
9
+ it 'returns zeros when db unavailable' do
10
+ result = maintenance.run_decay_cycle
11
+ expect(result).to eq({ decayed: 0, archived: 0 })
12
+ end
13
+ end
14
+
15
+ describe '#decay_rate' do
16
+ it 'returns default rate when settings unavailable' do
17
+ expect(maintenance.send(:decay_rate)).to eq(0.998)
18
+ end
19
+ end
20
+
21
+ describe '#decay_threshold' do
22
+ it 'returns default threshold when settings unavailable' do
23
+ expect(maintenance.send(:decay_threshold)).to eq(0.1)
24
+ end
25
+ end
26
+ end
@@ -119,6 +119,8 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Knowledge do
119
119
  .and_return(Array.new(1536, 0.0))
120
120
 
121
121
  allow(mock_entry_class).to receive(:where).and_return(double(exclude: double(limit: empty_dataset)))
122
+ allow(mock_entry_class).to receive(:exclude)
123
+ .and_return(double(exclude: double(limit: double(all: []))))
122
124
  allow(mock_entry_class).to receive(:create).and_return(mock_entry)
123
125
  allow(mock_expertise_class).to receive(:where).and_return(double(first: nil))
124
126
  allow(mock_expertise_class).to receive(:create)
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.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -54,6 +54,7 @@ files:
54
54
  - lib/legion/extensions/apollo/actors/ingest.rb
55
55
  - lib/legion/extensions/apollo/actors/query_responder.rb
56
56
  - lib/legion/extensions/apollo/client.rb
57
+ - lib/legion/extensions/apollo/gaia_integration.rb
57
58
  - lib/legion/extensions/apollo/helpers/confidence.rb
58
59
  - lib/legion/extensions/apollo/helpers/embedding.rb
59
60
  - lib/legion/extensions/apollo/helpers/graph_query.rb
@@ -61,6 +62,7 @@ files:
61
62
  - lib/legion/extensions/apollo/runners/expertise.rb
62
63
  - lib/legion/extensions/apollo/runners/knowledge.rb
63
64
  - lib/legion/extensions/apollo/runners/maintenance.rb
65
+ - lib/legion/extensions/apollo/transport.rb
64
66
  - lib/legion/extensions/apollo/transport/exchanges/apollo.rb
65
67
  - lib/legion/extensions/apollo/transport/messages/ingest.rb
66
68
  - lib/legion/extensions/apollo/transport/messages/query.rb
@@ -71,10 +73,13 @@ files:
71
73
  - spec/legion/extensions/apollo/actors/expertise_aggregator_spec.rb
72
74
  - spec/legion/extensions/apollo/actors/ingest_spec.rb
73
75
  - spec/legion/extensions/apollo/client_spec.rb
76
+ - spec/legion/extensions/apollo/contradiction_spec.rb
77
+ - spec/legion/extensions/apollo/gaia_integration_spec.rb
74
78
  - spec/legion/extensions/apollo/helpers/confidence_spec.rb
75
79
  - spec/legion/extensions/apollo/helpers/embedding_spec.rb
76
80
  - spec/legion/extensions/apollo/helpers/graph_query_spec.rb
77
81
  - spec/legion/extensions/apollo/helpers/similarity_spec.rb
82
+ - spec/legion/extensions/apollo/runners/decay_cycle_spec.rb
78
83
  - spec/legion/extensions/apollo/runners/expertise_spec.rb
79
84
  - spec/legion/extensions/apollo/runners/knowledge_spec.rb
80
85
  - spec/legion/extensions/apollo/runners/maintenance_spec.rb