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 +4 -4
- data/CHANGELOG.md +14 -0
- data/lib/legion/extensions/apollo/gaia_integration.rb +63 -0
- data/lib/legion/extensions/apollo/runners/knowledge.rb +46 -1
- data/lib/legion/extensions/apollo/runners/maintenance.rb +31 -0
- data/lib/legion/extensions/apollo/transport.rb +11 -0
- data/lib/legion/extensions/apollo/version.rb +1 -1
- data/spec/legion/extensions/apollo/contradiction_spec.rb +19 -0
- data/spec/legion/extensions/apollo/gaia_integration_spec.rb +48 -0
- data/spec/legion/extensions/apollo/runners/decay_cycle_spec.rb +26 -0
- data/spec/legion/extensions/apollo/runners/knowledge_spec.rb +2 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1478924cdbe16dac455d02313dd3de7b42bb0d022da411213b537f44527d14c0
|
|
4
|
+
data.tar.gz: 2e72cfa6dab1b790b1fe904ea23fe55ae093e0c6570a3b2790c8b7c0930b64cb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,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.
|
|
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
|