lex-apollo 0.4.17 → 0.4.18
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 +13 -0
- data/lib/legion/extensions/apollo/helpers/confidence.rb +8 -3
- data/lib/legion/extensions/apollo/runners/knowledge.rb +14 -2
- data/lib/legion/extensions/apollo/runners/maintenance.rb +13 -4
- data/lib/legion/extensions/apollo/version.rb +1 -1
- data/spec/legion/extensions/apollo/helpers/confidence_spec.rb +15 -6
- data/spec/legion/extensions/apollo/runners/decay_cycle_spec.rb +6 -2
- data/spec/legion/extensions/apollo/runners/maintenance_spec.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 87724ea9011349f5f7c008a360728975567903827b5cdcf9a42e2678ca84b134
|
|
4
|
+
data.tar.gz: ae10f48baf9522bd7087a20e1886fc26170c1f5e0a0c36a3ccac26c836f11885
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b21c5413da8dfe9f60d9845a578bcfe8337982d128a5041df214004e144b9913dd34cd2da1514a30ea67e21d52604a11218c362a46f9c990c472ac4ec14a0fa9
|
|
7
|
+
data.tar.gz: ae07a4aba39ce05f20b6a495310b6a20c1c468e0d627393ab931af76d9cd57da5b52075a33706a8b94fadae01c32b65d818d4f5c1018f9eac08a8ffdc10c7ace
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.18] - 2026-04-24
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- `store_knowledge`, `query_knowledge`, `related_entries` now execute directly when PostgreSQL is available instead of returning unexecuted command hashes — MCP tool calls were returning dispatch payloads (`{action: :query, ...}`) as their result, making the knowledge base unsearchable via LLM tools
|
|
7
|
+
- `query_knowledge` and `retrieve_relevant` now include `candidate` status in default search filters so newly stored entries are immediately retrievable (previously only `confirmed` entries were returned)
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Reduce `POWER_LAW_ALPHA` from 0.5 to 0.05 — decay was compounding hourly and crushing entry confidence within a day
|
|
11
|
+
- Reduce `DECAY_THRESHOLD` from 0.1 to 0.05 — entries were being archived too eagerly
|
|
12
|
+
- Decay cycle now operates on age in **days** instead of hours, preventing aggressive per-cycle compounding
|
|
13
|
+
- Add `DECAY_MIN_AGE_HOURS` (168h / 7 days) — entries younger than this are completely exempt from decay and archival
|
|
14
|
+
- `apply_decay` Ruby helper matches the new SQL behavior (days-based, minimum age guard)
|
|
15
|
+
|
|
3
16
|
## [0.4.17] - 2026-04-03
|
|
4
17
|
|
|
5
18
|
### Changed
|
|
@@ -8,12 +8,13 @@ module Legion
|
|
|
8
8
|
INITIAL_CONFIDENCE = 0.5
|
|
9
9
|
CORROBORATION_BOOST = 0.3
|
|
10
10
|
RETRIEVAL_BOOST = 0.02
|
|
11
|
-
POWER_LAW_ALPHA = 0.
|
|
12
|
-
DECAY_THRESHOLD = 0.
|
|
11
|
+
POWER_LAW_ALPHA = 0.05
|
|
12
|
+
DECAY_THRESHOLD = 0.05
|
|
13
13
|
CORROBORATION_SIMILARITY_THRESHOLD = 0.9
|
|
14
14
|
WRITE_CONFIDENCE_GATE = 0.6
|
|
15
15
|
WRITE_NOVELTY_GATE = 0.3
|
|
16
16
|
STALE_DAYS = 90
|
|
17
|
+
DECAY_MIN_AGE_HOURS = 168
|
|
17
18
|
CONTENT_TYPES = %i[fact concept procedure association observation].freeze
|
|
18
19
|
STATUSES = %w[candidate confirmed disputed decayed archived].freeze
|
|
19
20
|
RELATION_TYPES = %w[is_a has_a part_of causes similar_to contradicts supersedes depends_on].freeze
|
|
@@ -41,14 +42,18 @@ module Legion
|
|
|
41
42
|
def write_confidence_gate = apollo_setting(:confidence, :write_gate, default: WRITE_CONFIDENCE_GATE)
|
|
42
43
|
def write_novelty_gate = apollo_setting(:confidence, :novelty_gate, default: WRITE_NOVELTY_GATE)
|
|
43
44
|
def stale_days = apollo_setting(:stale_days, default: STALE_DAYS)
|
|
45
|
+
def decay_min_age_hours = apollo_setting(:decay_min_age_hours, default: DECAY_MIN_AGE_HOURS)
|
|
44
46
|
|
|
45
47
|
def corroboration_similarity_threshold
|
|
46
48
|
apollo_setting(:confidence, :corroboration_similarity, default: CORROBORATION_SIMILARITY_THRESHOLD)
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
def apply_decay(confidence:, age_hours: nil, alpha: power_law_alpha, **)
|
|
52
|
+
return confidence if age_hours && age_hours < decay_min_age_hours
|
|
53
|
+
|
|
50
54
|
if age_hours
|
|
51
|
-
|
|
55
|
+
age_days = age_hours / 24.0
|
|
56
|
+
[confidence * ((age_days.clamp(1, Float::INFINITY) + 1.0)**(-alpha)) / (age_days.clamp(1, Float::INFINITY)**(-alpha)), 0.0].max
|
|
52
57
|
else
|
|
53
58
|
factor = 1.0 / (1.0 + alpha)
|
|
54
59
|
[confidence * factor, 0.0].max
|
|
@@ -20,6 +20,11 @@ module Legion
|
|
|
20
20
|
raise ArgumentError, "invalid content_type: #{content_type}. Must be one of #{Helpers::Confidence::CONTENT_TYPES}"
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
if defined?(Legion::Data::Model::ApolloEntry)
|
|
24
|
+
return handle_ingest(content: content, content_type: content_type,
|
|
25
|
+
tags: Array(tags), source_agent: source_agent, context: context, **)
|
|
26
|
+
end
|
|
27
|
+
|
|
23
28
|
{
|
|
24
29
|
action: :store,
|
|
25
30
|
content: content,
|
|
@@ -30,7 +35,12 @@ module Legion
|
|
|
30
35
|
}
|
|
31
36
|
end
|
|
32
37
|
|
|
33
|
-
def query_knowledge(query:, limit: Helpers::GraphQuery.default_query_limit, min_confidence: Helpers::GraphQuery.default_query_min_confidence, status: [
|
|
38
|
+
def query_knowledge(query:, limit: Helpers::GraphQuery.default_query_limit, min_confidence: Helpers::GraphQuery.default_query_min_confidence, status: %i[confirmed candidate], tags: nil, **) # rubocop:disable Layout/LineLength
|
|
39
|
+
if defined?(Legion::Data::Model::ApolloEntry)
|
|
40
|
+
return handle_query(query: query, limit: limit, min_confidence: min_confidence,
|
|
41
|
+
status: status, tags: tags, **)
|
|
42
|
+
end
|
|
43
|
+
|
|
34
44
|
{
|
|
35
45
|
action: :query,
|
|
36
46
|
query: query,
|
|
@@ -42,6 +52,8 @@ module Legion
|
|
|
42
52
|
end
|
|
43
53
|
|
|
44
54
|
def related_entries(entry_id:, relation_types: nil, depth: Helpers::GraphQuery.default_depth, **)
|
|
55
|
+
return handle_traverse(entry_id: entry_id, depth: depth, relation_types: relation_types, **) if defined?(Legion::Data::Model::ApolloEntry)
|
|
56
|
+
|
|
45
57
|
{
|
|
46
58
|
action: :traverse,
|
|
47
59
|
entry_id: entry_id,
|
|
@@ -235,7 +247,7 @@ module Legion
|
|
|
235
247
|
embedding = embed_text(query)
|
|
236
248
|
sql = Helpers::GraphQuery.build_semantic_search_sql(
|
|
237
249
|
limit: limit, min_confidence: min_confidence,
|
|
238
|
-
statuses: [
|
|
250
|
+
statuses: %w[confirmed candidate], tags: tags, domain: domain
|
|
239
251
|
)
|
|
240
252
|
|
|
241
253
|
db = Legion::Data::Model::ApolloEntry.db
|
|
@@ -23,28 +23,37 @@ module Legion
|
|
|
23
23
|
def run_decay_cycle(alpha: nil, min_confidence: nil, **)
|
|
24
24
|
alpha ||= Helpers::Confidence.power_law_alpha
|
|
25
25
|
min_confidence ||= Helpers::Confidence.decay_threshold
|
|
26
|
+
min_age_hours = Helpers::Confidence.decay_min_age_hours
|
|
26
27
|
|
|
27
28
|
return { decayed: 0, archived: 0 } unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && Legion::Data.connection
|
|
28
29
|
|
|
29
30
|
conn = Legion::Data.connection
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
'GREATEST(EXTRACT(EPOCH FROM (NOW() - COALESCE(updated_at, created_at))) /
|
|
32
|
+
age_days_expr = Sequel.lit(
|
|
33
|
+
'GREATEST(EXTRACT(EPOCH FROM (NOW() - COALESCE(updated_at, created_at))) / 86400.0, 1.0)'
|
|
33
34
|
)
|
|
34
35
|
decay_factor = Sequel.lit(
|
|
35
|
-
'POWER(CAST(? AS double precision) / (CAST(? AS double precision) + 1.0), ?)',
|
|
36
|
+
'POWER(CAST(? AS double precision) / (CAST(? AS double precision) + 1.0), ?)',
|
|
37
|
+
age_days_expr, age_days_expr, alpha
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
min_age_filter = Sequel.lit(
|
|
41
|
+
"COALESCE(updated_at, created_at) < NOW() - INTERVAL '? hours'", min_age_hours
|
|
36
42
|
)
|
|
37
43
|
|
|
38
44
|
decayed = conn[:apollo_entries]
|
|
39
45
|
.exclude(status: 'archived')
|
|
46
|
+
.where(min_age_filter)
|
|
40
47
|
.update(confidence: Sequel[:confidence] * decay_factor)
|
|
41
48
|
|
|
42
49
|
archived = conn[:apollo_entries]
|
|
43
50
|
.where { confidence < min_confidence }
|
|
51
|
+
.where(min_age_filter)
|
|
44
52
|
.exclude(status: 'archived')
|
|
45
53
|
.update(status: 'archived')
|
|
46
54
|
|
|
47
|
-
{ decayed: decayed, archived: archived, alpha: alpha, threshold: min_confidence
|
|
55
|
+
{ decayed: decayed, archived: archived, alpha: alpha, threshold: min_confidence,
|
|
56
|
+
min_age_hours: min_age_hours }
|
|
48
57
|
rescue Sequel::Error => e
|
|
49
58
|
{ decayed: 0, archived: 0, error: e.message }
|
|
50
59
|
end
|
|
@@ -18,11 +18,15 @@ RSpec.describe Legion::Extensions::Apollo::Helpers::Confidence do
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
it 'defines POWER_LAW_ALPHA' do
|
|
21
|
-
expect(described_class::POWER_LAW_ALPHA).to eq(0.
|
|
21
|
+
expect(described_class::POWER_LAW_ALPHA).to eq(0.05)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
it 'defines DECAY_THRESHOLD' do
|
|
25
|
-
expect(described_class::DECAY_THRESHOLD).to eq(0.
|
|
25
|
+
expect(described_class::DECAY_THRESHOLD).to eq(0.05)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'defines DECAY_MIN_AGE_HOURS' do
|
|
29
|
+
expect(described_class::DECAY_MIN_AGE_HOURS).to eq(168)
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
it 'defines CORROBORATION_SIMILARITY_THRESHOLD' do
|
|
@@ -45,12 +49,17 @@ RSpec.describe Legion::Extensions::Apollo::Helpers::Confidence do
|
|
|
45
49
|
describe '.apply_decay' do
|
|
46
50
|
it 'applies power-law decay with default alpha when no age given' do
|
|
47
51
|
result = described_class.apply_decay(confidence: 1.0)
|
|
48
|
-
expected = 1.0 / (1.0 + 0.
|
|
52
|
+
expected = 1.0 / (1.0 + 0.05) # ~0.9524
|
|
49
53
|
expect(result).to be_within(0.0001).of(expected)
|
|
50
54
|
end
|
|
51
55
|
|
|
52
|
-
it '
|
|
56
|
+
it 'skips decay when age_hours is below minimum age' do
|
|
53
57
|
result = described_class.apply_decay(confidence: 1.0, age_hours: 10)
|
|
58
|
+
expect(result).to eq(1.0)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'applies age-based power-law decay when age_hours exceeds minimum' do
|
|
62
|
+
result = described_class.apply_decay(confidence: 1.0, age_hours: 500)
|
|
54
63
|
expect(result).to be > 0.0
|
|
55
64
|
expect(result).to be < 1.0
|
|
56
65
|
end
|
|
@@ -98,11 +107,11 @@ RSpec.describe Legion::Extensions::Apollo::Helpers::Confidence do
|
|
|
98
107
|
|
|
99
108
|
describe '.decayed?' do
|
|
100
109
|
it 'returns true when confidence below threshold' do
|
|
101
|
-
expect(described_class.decayed?(confidence: 0.
|
|
110
|
+
expect(described_class.decayed?(confidence: 0.01)).to be true
|
|
102
111
|
end
|
|
103
112
|
|
|
104
113
|
it 'returns false when confidence at or above threshold' do
|
|
105
|
-
expect(described_class.decayed?(confidence: 0.
|
|
114
|
+
expect(described_class.decayed?(confidence: 0.05)).to be false
|
|
106
115
|
end
|
|
107
116
|
end
|
|
108
117
|
|
|
@@ -14,11 +14,15 @@ RSpec.describe 'Apollo Decay Cycle' do
|
|
|
14
14
|
|
|
15
15
|
describe 'configurable decay parameters' do
|
|
16
16
|
it 'returns POWER_LAW_ALPHA as default' do
|
|
17
|
-
expect(Legion::Extensions::Apollo::Helpers::Confidence.power_law_alpha).to eq(0.
|
|
17
|
+
expect(Legion::Extensions::Apollo::Helpers::Confidence.power_law_alpha).to eq(0.05)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
it 'returns default decay threshold' do
|
|
21
|
-
expect(Legion::Extensions::Apollo::Helpers::Confidence.decay_threshold).to eq(0.
|
|
21
|
+
expect(Legion::Extensions::Apollo::Helpers::Confidence.decay_threshold).to eq(0.05)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'returns default decay minimum age hours' do
|
|
25
|
+
expect(Legion::Extensions::Apollo::Helpers::Confidence.decay_min_age_hours).to eq(168)
|
|
22
26
|
end
|
|
23
27
|
end
|
|
24
28
|
end
|
|
@@ -71,7 +71,7 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Maintenance do
|
|
|
71
71
|
|
|
72
72
|
it 'returns alpha in result hash' do
|
|
73
73
|
result = host.run_decay_cycle
|
|
74
|
-
expect(result[:alpha]).to eq(0.
|
|
74
|
+
expect(result[:alpha]).to eq(0.05)
|
|
75
75
|
expect(result).not_to have_key(:rate)
|
|
76
76
|
end
|
|
77
77
|
|