legion-apollo 0.5.6 → 0.5.13
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 +35 -0
- data/README.md +10 -2
- data/lib/legion/apollo/local/graph.rb +22 -10
- data/lib/legion/apollo/local.rb +70 -167
- data/lib/legion/apollo/version.rb +1 -1
- data/lib/legion/apollo.rb +18 -25
- metadata +1 -12
- data/data/self-knowledge/01-what-is-legion.md +0 -51
- data/data/self-knowledge/02-architecture.md +0 -51
- data/data/self-knowledge/03-extensions.md +0 -54
- data/data/self-knowledge/04-security.md +0 -46
- data/data/self-knowledge/05-llm-pipeline.md +0 -54
- data/data/self-knowledge/06-apollo-knowledge.md +0 -54
- data/data/self-knowledge/07-cli-reference.md +0 -59
- data/data/self-knowledge/08-cognitive-layer.md +0 -49
- data/data/self-knowledge/09-teams-integration.md +0 -44
- data/data/self-knowledge/10-deployment.md +0 -75
- data/data/self-knowledge/11-my-partner.md +0 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f096b7552b38d67434c6ae3431526bcd37ebe43c75fa90a19c8ec790e3d323a0
|
|
4
|
+
data.tar.gz: c8484053dfcda88edee17da060f7a5cab87235c0ea6258d2faecc6542135b1f5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 246787437bfdf6c9667563e73bbba1bd56fe3328f2d1836d2a9daf14cc870ad1df25f96ff725f80a14ed9e4174efa4479f0c41eb314538138122d54cb8dbb424
|
|
7
|
+
data.tar.gz: fcee5919bc41cc594d77ab04188212265c54b5281383c9ff6e003222ea4ce2cf25859b39666cdf16c2b09fbcabbfd998af84c33d6fad4cdca86b683e1c140739
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.13] - 2026-10-29
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Preserve L0/L1 summaries when normalizing public local and merged retrieval results, exposing the projected text as content instead of nil.
|
|
7
|
+
|
|
8
|
+
## [0.5.12] - 2026-10-29
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Honor raw source and temporal-window updates during upsert. Refresh derived raw content when indexed content changes, preserve omitted explicit sources/windows, and allow explicit nil windows to clear validity bounds.
|
|
12
|
+
- Remove old FTS5 terms before changing their external content row, preventing index corruption during upsert while preserving transactional rollback.
|
|
13
|
+
|
|
14
|
+
## [0.5.11] - 2026-10-29
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Renew expired content on duplicate ingestion while preserving its ID, source, tags, and deduplicated response; active duplicates retain their original expiration.
|
|
18
|
+
|
|
19
|
+
## [0.5.10] - 2026-10-29
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Apply local eligibility filters before filling the candidate budget, streaming full-text, blank, and fallback searches so rejected rows cannot hide later matching knowledge.
|
|
23
|
+
|
|
24
|
+
## [0.5.9] - 2026-10-29
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Return logged failure results from local graph operations when the optional database is absent, disconnected, or has no usable connection, instead of leaking lifecycle exceptions.
|
|
28
|
+
|
|
29
|
+
## [0.5.8] - 2026-10-29
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- Generate local ingest/upsert embeddings outside the write mutex so slow providers do not serialize unrelated writes; retain atomic duplicate checks and persistence.
|
|
33
|
+
|
|
34
|
+
## [0.5.7] - 2026-07-15
|
|
35
|
+
### Removed
|
|
36
|
+
- Remove .md self-knowledge seed pipeline (bonds must be earned, not implanted)
|
|
37
|
+
|
|
3
38
|
## [0.5.5] - 2026-05-15
|
|
4
39
|
|
|
5
40
|
### Added
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Apollo is the LegionIO knowledge client. It gives extensions one API for writing, retrieving, and merging knowledge across the global Apollo service and the node-local SQLite store.
|
|
4
4
|
|
|
5
|
-
**Version**: 0.5.
|
|
5
|
+
**Version**: 0.5.13
|
|
6
6
|
|
|
7
7
|
`legion-apollo` provides `query`, `ingest`, and `retrieve` with smart routing: co-located `lex-apollo`, RabbitMQ transport, node-local SQLite, or graceful failure. `Apollo::Local` mirrors the same public API for offline and low-latency retrieval without requiring remote infrastructure.
|
|
8
8
|
|
|
@@ -58,10 +58,18 @@ Features:
|
|
|
58
58
|
- `raw_content` preservation for verbatim source text
|
|
59
59
|
- `valid_from` / `valid_to` temporal windows with `as_of:` query filtering
|
|
60
60
|
- Optional LLM embeddings (1024-dim) with cosine rerank when `Legion::LLM.can_embed?`
|
|
61
|
-
-
|
|
61
|
+
- Embedding generation runs outside the local write mutex; persistence and duplicate checks remain serialized
|
|
62
|
+
- TTL expiry (default 5-year retention); re-ingesting expired content renews retention without replacing its stored source or ID
|
|
62
63
|
- FTS5 full-text search with `ILIKE` fallback
|
|
64
|
+
- Confidence, tags, visibility, inference, and history filters run before filling the reranking candidate budget
|
|
63
65
|
- Null-byte removal and invalid UTF-8 scrubbing before persistence or backend routing
|
|
64
66
|
|
|
67
|
+
Local graph operations return `{ success: false, error: ... }` when the optional database is unavailable; they do not require callers to rescue missing-connection exceptions.
|
|
68
|
+
|
|
69
|
+
`Local.upsert` honors explicit `raw_content`, `valid_from`, and `valid_to` updates. Omitted validity bounds and distinct verbatim sources are preserved; derived raw content follows the updated indexed content. Explicit nil validity bounds clear the corresponding restriction.
|
|
70
|
+
|
|
71
|
+
Public `query`/`retrieve` calls with `tier: :l0` or `tier: :l1` retain the local `summary` and expose it as `content` in both `scope: :local` and `scope: :all` results.
|
|
72
|
+
|
|
65
73
|
## Configuration
|
|
66
74
|
|
|
67
75
|
```json
|
|
@@ -12,6 +12,7 @@ module Legion
|
|
|
12
12
|
# Graph traversal expands one frontier batch per depth to avoid per-node queries.
|
|
13
13
|
module Graph # rubocop:disable Metrics/ModuleLength
|
|
14
14
|
VALID_RELATION_TYPES = %w[AFFECTS OWNED_BY DEPENDS_ON RELATED_TO SUPERSEDES].freeze
|
|
15
|
+
class DatabaseUnavailable < StandardError; end
|
|
15
16
|
|
|
16
17
|
class << self # rubocop:disable Metrics/ClassLength
|
|
17
18
|
include Legion::Logging::Helper
|
|
@@ -32,7 +33,7 @@ module Legion
|
|
|
32
33
|
end
|
|
33
34
|
log.info { "Apollo::Local::Graph created entity id=#{id} type=#{type} name=#{name}" }
|
|
34
35
|
{ success: true, id: id }
|
|
35
|
-
rescue Sequel::Error => e
|
|
36
|
+
rescue DatabaseUnavailable, Sequel::Error => e
|
|
36
37
|
handle_exception(
|
|
37
38
|
e,
|
|
38
39
|
level: :error,
|
|
@@ -49,7 +50,7 @@ module Legion
|
|
|
49
50
|
|
|
50
51
|
log.debug { "Apollo::Local::Graph found entity id=#{id}" }
|
|
51
52
|
{ success: true, entity: decode_entity(row) }
|
|
52
|
-
rescue Sequel::Error => e
|
|
53
|
+
rescue DatabaseUnavailable, Sequel::Error => e
|
|
53
54
|
handle_exception(e, level: :error, operation: 'apollo.local.graph.find_entity', entity_id: id)
|
|
54
55
|
{ success: false, error: e.message }
|
|
55
56
|
end
|
|
@@ -58,7 +59,7 @@ module Legion
|
|
|
58
59
|
rows = db[:local_entities].where(entity_type: type.to_s).limit(limit).all
|
|
59
60
|
log.debug { "Apollo::Local::Graph found entities type=#{type} count=#{rows.size}" }
|
|
60
61
|
{ success: true, entities: rows.map { |r| decode_entity(r) }, count: rows.size }
|
|
61
|
-
rescue Sequel::Error => e
|
|
62
|
+
rescue DatabaseUnavailable, Sequel::Error => e
|
|
62
63
|
handle_exception(
|
|
63
64
|
e,
|
|
64
65
|
level: :error,
|
|
@@ -73,7 +74,7 @@ module Legion
|
|
|
73
74
|
rows = db[:local_entities].where(name: name.to_s).limit(limit).all
|
|
74
75
|
log.debug { "Apollo::Local::Graph found entities name=#{name} count=#{rows.size}" }
|
|
75
76
|
{ success: true, entities: rows.map { |r| decode_entity(r) }, count: rows.size }
|
|
76
|
-
rescue Sequel::Error => e
|
|
77
|
+
rescue DatabaseUnavailable, Sequel::Error => e
|
|
77
78
|
handle_exception(
|
|
78
79
|
e,
|
|
79
80
|
level: :error,
|
|
@@ -95,7 +96,7 @@ module Legion
|
|
|
95
96
|
|
|
96
97
|
log.info { "Apollo::Local::Graph updated entity id=#{id}" }
|
|
97
98
|
{ success: true, id: id }
|
|
98
|
-
rescue Sequel::Error => e
|
|
99
|
+
rescue DatabaseUnavailable, Sequel::Error => e
|
|
99
100
|
handle_exception(
|
|
100
101
|
e,
|
|
101
102
|
level: :error,
|
|
@@ -112,7 +113,7 @@ module Legion
|
|
|
112
113
|
|
|
113
114
|
log.info { "Apollo::Local::Graph deleted entity id=#{id}" }
|
|
114
115
|
result
|
|
115
|
-
rescue Sequel::Error => e
|
|
116
|
+
rescue DatabaseUnavailable, Sequel::Error => e
|
|
116
117
|
handle_exception(e, level: :error, operation: 'apollo.local.graph.delete_entity', entity_id: id)
|
|
117
118
|
{ success: false, error: e.message }
|
|
118
119
|
end
|
|
@@ -132,6 +133,10 @@ module Legion
|
|
|
132
133
|
"target_id=#{target_id} relation_type=#{normalized_relation_type}"
|
|
133
134
|
end
|
|
134
135
|
{ success: true, id: id }
|
|
136
|
+
rescue DatabaseUnavailable => e
|
|
137
|
+
handle_exception(e, level: :error, operation: 'apollo.local.graph.create_relationship',
|
|
138
|
+
source_id: source_id, target_id: target_id)
|
|
139
|
+
{ success: false, error: e.message }
|
|
135
140
|
rescue Sequel::UniqueConstraintViolation => e
|
|
136
141
|
duplicate = handle_duplicate_relationship(source_id, target_id, normalized_relation_type)
|
|
137
142
|
return duplicate if duplicate
|
|
@@ -170,7 +175,7 @@ module Legion
|
|
|
170
175
|
"relation_type=#{relation_type || 'any'} count=#{rows.size}"
|
|
171
176
|
end
|
|
172
177
|
{ success: true, relationships: rows.map { |r| decode_relationship(r) }, count: rows.size }
|
|
173
|
-
rescue Sequel::Error => e
|
|
178
|
+
rescue DatabaseUnavailable, Sequel::Error => e
|
|
174
179
|
handle_exception(
|
|
175
180
|
e,
|
|
176
181
|
level: :error,
|
|
@@ -188,7 +193,7 @@ module Legion
|
|
|
188
193
|
|
|
189
194
|
log.info { "Apollo::Local::Graph deleted relationship id=#{id}" }
|
|
190
195
|
{ success: true, id: id }
|
|
191
|
-
rescue Sequel::Error => e
|
|
196
|
+
rescue DatabaseUnavailable, Sequel::Error => e
|
|
192
197
|
handle_exception(e, level: :error, operation: 'apollo.local.graph.delete_relationship', relationship_id: id)
|
|
193
198
|
{ success: false, error: e.message }
|
|
194
199
|
end
|
|
@@ -217,7 +222,7 @@ module Legion
|
|
|
217
222
|
edges: edge_rows.map { |r| decode_relationship(r) },
|
|
218
223
|
count: entity_rows.size
|
|
219
224
|
}
|
|
220
|
-
rescue Sequel::Error => e
|
|
225
|
+
rescue DatabaseUnavailable, Sequel::Error => e
|
|
221
226
|
handle_exception(
|
|
222
227
|
e,
|
|
223
228
|
level: :error,
|
|
@@ -357,7 +362,14 @@ module Legion
|
|
|
357
362
|
end
|
|
358
363
|
|
|
359
364
|
def db
|
|
360
|
-
Legion::Data::Local.
|
|
365
|
+
unless defined?(Legion::Data::Local) && Legion::Data::Local.connected?
|
|
366
|
+
raise DatabaseUnavailable, 'local database unavailable'
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
connection = Legion::Data::Local.connection
|
|
370
|
+
return connection if connection && !(connection.respond_to?(:closed?) && connection.closed?)
|
|
371
|
+
|
|
372
|
+
raise DatabaseUnavailable, 'local database unavailable'
|
|
361
373
|
end
|
|
362
374
|
|
|
363
375
|
def timestamp
|
data/lib/legion/apollo/local.rb
CHANGED
|
@@ -17,8 +17,6 @@ module Legion
|
|
|
17
17
|
MIGRATION_PATH = File.expand_path('local/migrations', __dir__).freeze
|
|
18
18
|
LIFECYCLE_MUTEX = Mutex.new
|
|
19
19
|
WRITE_MUTEX = Mutex.new
|
|
20
|
-
SEED_MUTEX = Mutex.new
|
|
21
|
-
HYDRATION_MUTEX = Mutex.new
|
|
22
20
|
|
|
23
21
|
class << self # rubocop:disable Metrics/ClassLength
|
|
24
22
|
include Legion::Logging::Helper
|
|
@@ -33,25 +31,29 @@ module Legion
|
|
|
33
31
|
def shutdown
|
|
34
32
|
LIFECYCLE_MUTEX.synchronize do
|
|
35
33
|
@started = false
|
|
36
|
-
@seeded = false
|
|
37
34
|
log.info 'Legion::Apollo::Local shutdown'
|
|
38
35
|
end
|
|
39
36
|
rescue StandardError => e
|
|
40
37
|
handle_exception(e, level: :warn, operation: 'apollo.local.shutdown')
|
|
41
38
|
@started = false
|
|
42
|
-
@seeded = false
|
|
43
39
|
end
|
|
44
40
|
|
|
45
41
|
def started?
|
|
46
42
|
@started == true
|
|
47
43
|
end
|
|
48
44
|
|
|
49
|
-
def ingest(content:, tags: [], access_scope: 'global', **opts) # rubocop:disable Metrics/MethodLength
|
|
45
|
+
def ingest(content:, tags: [], access_scope: 'global', **opts) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
50
46
|
return not_started_error unless started?
|
|
51
47
|
|
|
52
48
|
tags = normalize_tags_input(tags)
|
|
49
|
+
content = normalize_text_input(content)
|
|
50
|
+
hash = content_hash(content)
|
|
51
|
+
duplicate = WRITE_MUTEX.synchronize { deduplicated_ingest(hash, expires_at: opts[:expires_at]) }
|
|
52
|
+
return duplicate if duplicate
|
|
53
|
+
|
|
54
|
+
embedding_fields = embedding_columns(content, opts)
|
|
53
55
|
WRITE_MUTEX.synchronize do
|
|
54
|
-
ingest_without_lock(content: content, tags: tags,
|
|
56
|
+
ingest_without_lock(content: content, tags: tags, embedding_fields: embedding_fields,
|
|
55
57
|
**inject_identity_context(opts).merge(access_scope: access_scope))
|
|
56
58
|
end
|
|
57
59
|
rescue StandardError => e
|
|
@@ -71,14 +73,26 @@ module Legion
|
|
|
71
73
|
sorted_tags = normalize_tags_input(tags).sort
|
|
72
74
|
tag_json = Legion::JSON.dump(sorted_tags)
|
|
73
75
|
merged_opts = inject_identity_context(opts).merge(access_scope: access_scope)
|
|
76
|
+
content = normalize_text_input(content)
|
|
77
|
+
hash = content_hash(content)
|
|
78
|
+
duplicate = WRITE_MUTEX.synchronize do
|
|
79
|
+
if db[:local_knowledge].where(tags: tag_json).empty?
|
|
80
|
+
deduplicated_ingest(hash,
|
|
81
|
+
expires_at: opts[:expires_at])
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
return duplicate if duplicate
|
|
85
|
+
|
|
86
|
+
embedding_fields = embedding_columns(content, merged_opts)
|
|
74
87
|
WRITE_MUTEX.synchronize do
|
|
75
88
|
existing = db[:local_knowledge].where(tags: tag_json).first
|
|
76
89
|
|
|
77
90
|
if existing
|
|
78
|
-
update_upsert_entry(existing, content, tag_json, merged_opts)
|
|
91
|
+
update_upsert_entry(existing, content, tag_json, merged_opts, embedding_fields)
|
|
79
92
|
else
|
|
80
|
-
result = ingest_without_lock(content: content, tags: sorted_tags,
|
|
81
|
-
|
|
93
|
+
result = ingest_without_lock(content: content, tags: sorted_tags,
|
|
94
|
+
embedding_fields: embedding_fields, **merged_opts)
|
|
95
|
+
result[:mode] = :inserted if result[:mode] == :local
|
|
82
96
|
result
|
|
83
97
|
end
|
|
84
98
|
end
|
|
@@ -108,13 +122,15 @@ module Legion
|
|
|
108
122
|
end
|
|
109
123
|
log.debug { "Apollo::Local query limit=#{limit} min_confidence=#{min_confidence} tags=#{Array(tags).size}" }
|
|
110
124
|
|
|
111
|
-
candidates = fts_search(text, limit: limit * multiplier, as_of: as_of)
|
|
112
125
|
include_inferences = opts.fetch(:include_inferences, true)
|
|
113
126
|
include_history = opts.fetch(:include_history, false)
|
|
114
|
-
candidates =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
candidates = fts_search(text, as_of: as_of) do |dataset|
|
|
128
|
+
filter_candidates(dataset.lazy, min_confidence: min_confidence, tags: tags,
|
|
129
|
+
options: { include_inferences: include_inferences,
|
|
130
|
+
include_history: include_history, as_of: as_of,
|
|
131
|
+
requesting_principal_id: requesting_principal_id })
|
|
132
|
+
.take(limit * multiplier).force
|
|
133
|
+
end
|
|
118
134
|
candidates = cosine_rerank(text, candidates) if can_rerank?
|
|
119
135
|
results = candidates.first(limit)
|
|
120
136
|
|
|
@@ -146,22 +162,9 @@ module Legion
|
|
|
146
162
|
def reset!
|
|
147
163
|
LIFECYCLE_MUTEX.synchronize do
|
|
148
164
|
@started = false
|
|
149
|
-
@seeded = false
|
|
150
165
|
end
|
|
151
166
|
end
|
|
152
167
|
|
|
153
|
-
def seed_self_knowledge
|
|
154
|
-
return unless started?
|
|
155
|
-
|
|
156
|
-
SEED_MUTEX.synchronize { seed_self_knowledge_without_lock }
|
|
157
|
-
rescue StandardError => e
|
|
158
|
-
handle_exception(e, level: :warn, operation: 'apollo.local.seed_self_knowledge')
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def seeded?
|
|
162
|
-
@seeded == true
|
|
163
|
-
end
|
|
164
|
-
|
|
165
168
|
def query_by_tags(tags:, limit: 50) # rubocop:disable Metrics/MethodLength
|
|
166
169
|
connection = local_db_connection
|
|
167
170
|
tags = normalize_tags_input(tags)
|
|
@@ -232,15 +235,6 @@ module Legion
|
|
|
232
235
|
{ success: false, error: e.message }
|
|
233
236
|
end
|
|
234
237
|
|
|
235
|
-
def hydrate_from_global
|
|
236
|
-
return { success: false, error: :not_started } unless started?
|
|
237
|
-
|
|
238
|
-
HYDRATION_MUTEX.synchronize { hydrate_from_global_without_lock }
|
|
239
|
-
rescue StandardError => e
|
|
240
|
-
handle_exception(e, level: :error, operation: 'apollo.local.hydrate_from_global')
|
|
241
|
-
{ success: false, error: e.message }
|
|
242
|
-
end
|
|
243
|
-
|
|
244
238
|
def version_chain(entry_id:, max_depth: 50) # rubocop:disable Metrics/MethodLength
|
|
245
239
|
return not_started_error unless started?
|
|
246
240
|
|
|
@@ -278,49 +272,6 @@ module Legion
|
|
|
278
272
|
|
|
279
273
|
private
|
|
280
274
|
|
|
281
|
-
def self_knowledge_files
|
|
282
|
-
seed_dir = File.join(File.expand_path('../../..', __dir__), 'data', 'self-knowledge')
|
|
283
|
-
return [] unless File.directory?(seed_dir)
|
|
284
|
-
|
|
285
|
-
Dir[File.join(seed_dir, '*.md')]
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
def seed_files(files)
|
|
289
|
-
count = 0
|
|
290
|
-
files.each do |path|
|
|
291
|
-
count += 1 if seed_single_file(path)
|
|
292
|
-
end
|
|
293
|
-
count
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
def seed_single_file(path)
|
|
297
|
-
content = File.read(path)
|
|
298
|
-
return false if content.strip.empty?
|
|
299
|
-
|
|
300
|
-
tags = ['legionio', 'self-knowledge', File.basename(path, '.md')]
|
|
301
|
-
result = ingest(content: content, tags: tags, source_channel: 'self-knowledge',
|
|
302
|
-
submitted_by: 'legion-apollo', confidence: 0.9)
|
|
303
|
-
return false unless result[:success] && result[:mode] != :deduplicated
|
|
304
|
-
|
|
305
|
-
ingest_global(content: content, tags: tags) if global_available?
|
|
306
|
-
true
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
def ingest_global(content:, tags:)
|
|
310
|
-
log.debug { "Apollo::Local forwarding seed entry to global tag_count=#{Array(tags).size}" }
|
|
311
|
-
Legion::Apollo.ingest(content: content, tags: tags, source_channel: 'self-knowledge',
|
|
312
|
-
submitted_by: 'legion-apollo', confidence: 0.9, scope: :global)
|
|
313
|
-
rescue StandardError => e
|
|
314
|
-
handle_exception(e, level: :debug, operation: 'apollo.local.ingest_global_seed', tag_count: Array(tags).size)
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
def global_available?
|
|
318
|
-
defined?(Legion::Apollo) && Legion::Apollo.started? && Legion::Apollo.respond_to?(:ingest)
|
|
319
|
-
rescue StandardError => e
|
|
320
|
-
handle_exception(e, level: :debug, operation: 'apollo.local.global_available')
|
|
321
|
-
false
|
|
322
|
-
end
|
|
323
|
-
|
|
324
275
|
def local_enabled?
|
|
325
276
|
return false unless defined?(Legion::Settings)
|
|
326
277
|
|
|
@@ -372,13 +323,6 @@ module Legion
|
|
|
372
323
|
Digest::MD5.hexdigest(normalized)
|
|
373
324
|
end
|
|
374
325
|
|
|
375
|
-
def duplicate?(hash)
|
|
376
|
-
db[:local_knowledge].where(content_hash: hash).any?
|
|
377
|
-
rescue StandardError => e
|
|
378
|
-
handle_exception(e, level: :debug, operation: 'apollo.local.duplicate_check', hash: hash)
|
|
379
|
-
false
|
|
380
|
-
end
|
|
381
|
-
|
|
382
326
|
def start_without_lock
|
|
383
327
|
return if @started
|
|
384
328
|
return unless local_enabled? && data_local_available?
|
|
@@ -388,63 +332,12 @@ module Legion
|
|
|
388
332
|
log.info 'Legion::Apollo::Local started'
|
|
389
333
|
end
|
|
390
334
|
|
|
391
|
-
def
|
|
392
|
-
return if @seeded
|
|
393
|
-
|
|
394
|
-
files = self_knowledge_files
|
|
395
|
-
return if files.empty?
|
|
396
|
-
|
|
397
|
-
count = seed_files(files)
|
|
398
|
-
@seeded = true
|
|
399
|
-
log.info("Apollo::Local seeded #{count} self-knowledge files")
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
def hydrate_from_global_without_lock # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
403
|
-
local_check = query_by_tags(tags: ['partner'])
|
|
404
|
-
if local_check[:success] && local_check[:results]&.any?
|
|
405
|
-
log.info 'Apollo::Local hydration skipped because local partner data already exists'
|
|
406
|
-
return { success: true, skipped: :local_data_exists }
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
unless Legion::Apollo.transport_available? || Legion::Apollo.data_available?
|
|
410
|
-
log.info 'Apollo::Local hydration skipped because global Apollo is unavailable'
|
|
411
|
-
return { success: true, skipped: :global_unavailable }
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
global_entries = Legion::Apollo.retrieve(text: 'partner bond', scope: :global, limit: 20)
|
|
415
|
-
entries = Array(global_entries[:entries] || global_entries[:results])
|
|
416
|
-
unless global_entries[:success] && entries.any?
|
|
417
|
-
log.info 'Apollo::Local hydration skipped because no global partner data was found'
|
|
418
|
-
return { success: true, skipped: :no_global_data }
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
log.info { "Apollo::Local hydration started global_count=#{entries.size}" }
|
|
422
|
-
hydrated = 0
|
|
423
|
-
entries.each do |entry|
|
|
424
|
-
entry_tags = entry[:tags].is_a?(Array) ? entry[:tags] : []
|
|
425
|
-
clean_tags = entry_tags.reject { |tag| tag == 'promoted_from_local' } + ['hydrated_from_global']
|
|
426
|
-
|
|
427
|
-
result = ingest(
|
|
428
|
-
content: entry[:content],
|
|
429
|
-
raw_content: entry[:raw_content] || entry[:content],
|
|
430
|
-
tags: clean_tags,
|
|
431
|
-
confidence: ((entry[:confidence] || 0.5) * 0.9).round(10),
|
|
432
|
-
source_channel: 'global_hydration',
|
|
433
|
-
valid_from: entry[:valid_from],
|
|
434
|
-
valid_to: entry[:valid_to]
|
|
435
|
-
)
|
|
436
|
-
hydrated += 1 if result[:success]
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
log.info { "Apollo::Local hydration completed hydrated=#{hydrated}" }
|
|
440
|
-
{ success: true, hydrated: hydrated }
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
def ingest_without_lock(content:, tags:, **opts) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
335
|
+
def ingest_without_lock(content:, tags:, embedding_fields:, **opts) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
444
336
|
content = normalize_text_input(content)
|
|
445
337
|
raw_content = normalize_raw_content_input(opts[:raw_content], fallback: content)
|
|
446
338
|
hash = content_hash(content)
|
|
447
|
-
|
|
339
|
+
duplicate = deduplicated_ingest(hash, expires_at: opts[:expires_at])
|
|
340
|
+
return duplicate if duplicate
|
|
448
341
|
|
|
449
342
|
log.info do
|
|
450
343
|
"Apollo::Local ingest accepted content_length=#{content.to_s.length} " \
|
|
@@ -455,15 +348,17 @@ module Legion
|
|
|
455
348
|
metadata = opts.dup
|
|
456
349
|
metadata.delete(:raw_content)
|
|
457
350
|
row = build_ingest_row(content: content, raw_content: raw_content, hash: hash, tags: tags, **metadata)
|
|
351
|
+
.merge(embedding_fields)
|
|
458
352
|
id = persist_ingest_row(row, metadata)
|
|
459
353
|
mark_parent_superseded(metadata[:parent_knowledge_id]) if metadata[:parent_knowledge_id]
|
|
460
354
|
|
|
461
355
|
log.info { "Apollo::Local ingest stored id=#{id} hash=#{hash}" }
|
|
462
356
|
{ success: true, mode: :local, id: id }
|
|
463
357
|
rescue Sequel::UniqueConstraintViolation
|
|
464
|
-
|
|
358
|
+
duplicate = deduplicated_ingest(hash, expires_at: opts[:expires_at])
|
|
359
|
+
raise unless duplicate
|
|
465
360
|
|
|
466
|
-
|
|
361
|
+
duplicate
|
|
467
362
|
end
|
|
468
363
|
|
|
469
364
|
def build_ingest_row(content:, raw_content:, hash:, tags:, **opts) # rubocop:disable Metrics/MethodLength
|
|
@@ -477,7 +372,7 @@ module Legion
|
|
|
477
372
|
opts: opts,
|
|
478
373
|
is_inference: is_inference,
|
|
479
374
|
default_confidence: default_confidence
|
|
480
|
-
).merge(
|
|
375
|
+
).merge(timestamp_columns)
|
|
481
376
|
end
|
|
482
377
|
|
|
483
378
|
def ingest_metadata_columns(context)
|
|
@@ -545,7 +440,17 @@ module Legion
|
|
|
545
440
|
handle_exception(e, level: :warn, operation: 'apollo.local.create_source_link', entry_id: entry_id)
|
|
546
441
|
end
|
|
547
442
|
|
|
548
|
-
def deduplicated_ingest(hash)
|
|
443
|
+
def deduplicated_ingest(hash, expires_at: nil) # rubocop:disable Metrics/MethodLength
|
|
444
|
+
dataset = db[:local_knowledge].where(content_hash: hash)
|
|
445
|
+
existing = dataset.select(:expires_at).first
|
|
446
|
+
return unless existing
|
|
447
|
+
|
|
448
|
+
now = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
|
|
449
|
+
previous_expiry = existing[:expires_at]
|
|
450
|
+
if previous_expiry && previous_expiry <= now
|
|
451
|
+
dataset.where(Sequel.lit('expires_at <= ?', now))
|
|
452
|
+
.update(expires_at: expires_at || compute_expires_at, updated_at: now)
|
|
453
|
+
end
|
|
549
454
|
log.info { "Apollo::Local ingest deduplicated hash=#{hash}" }
|
|
550
455
|
{ success: true, mode: :deduplicated }
|
|
551
456
|
end
|
|
@@ -615,33 +520,31 @@ module Legion
|
|
|
615
520
|
Legion::JSON.dump(normalize_tags_input(tags))
|
|
616
521
|
end
|
|
617
522
|
|
|
618
|
-
def fts_search(text,
|
|
523
|
+
def fts_search(text, as_of: nil) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
619
524
|
now = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
|
|
620
|
-
return active_knowledge_dataset(now: now, as_of: as_of)
|
|
525
|
+
return yield active_knowledge_dataset(now: now, as_of: as_of) if text.to_s.strip.empty?
|
|
621
526
|
|
|
622
527
|
tokens = text.to_s.scan(/[\p{L}\p{N}_]+/)
|
|
623
|
-
return ilike_search(text, now: now,
|
|
528
|
+
return yield ilike_search(text, now: now, as_of: as_of) if tokens.empty?
|
|
624
529
|
|
|
625
530
|
escaped = tokens.map { |t| %("#{t}") }.join(' ')
|
|
626
531
|
temporal_sql, temporal_params = temporal_window_sql(as_of, table_alias: 'lk')
|
|
627
|
-
db.fetch(
|
|
532
|
+
yield db.fetch(
|
|
628
533
|
'SELECT lk.* FROM local_knowledge lk ' \
|
|
629
534
|
'INNER JOIN local_knowledge_fts fts ON lk.id = fts.rowid ' \
|
|
630
535
|
"WHERE local_knowledge_fts MATCH ? AND lk.expires_at > ?#{temporal_sql} " \
|
|
631
|
-
'ORDER BY fts.rank
|
|
632
|
-
escaped, now, *temporal_params
|
|
633
|
-
)
|
|
536
|
+
'ORDER BY fts.rank',
|
|
537
|
+
escaped, now, *temporal_params
|
|
538
|
+
)
|
|
634
539
|
rescue StandardError => e
|
|
635
|
-
handle_exception(e, level: :debug, operation: 'apollo.local.fts_search',
|
|
636
|
-
ilike_search(text, now: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
|
|
540
|
+
handle_exception(e, level: :debug, operation: 'apollo.local.fts_search', fallback: :ilike)
|
|
541
|
+
yield ilike_search(text, now: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), as_of: as_of)
|
|
637
542
|
end
|
|
638
543
|
|
|
639
|
-
def ilike_search(text, now:,
|
|
544
|
+
def ilike_search(text, now:, as_of: nil)
|
|
640
545
|
safe_text = text.to_s.gsub('\\', '\\\\\\\\').gsub('%', '\%').gsub('_', '\_')
|
|
641
546
|
active_knowledge_dataset(now: now, as_of: as_of)
|
|
642
547
|
.where(Sequel.lit("content LIKE ? ESCAPE '\\' COLLATE NOCASE", "%#{safe_text}%"))
|
|
643
|
-
.limit(limit)
|
|
644
|
-
.all
|
|
645
548
|
end
|
|
646
549
|
|
|
647
550
|
def filter_candidates(candidates, min_confidence:, tags:, options: {}) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength,Metrics/AbcSize
|
|
@@ -847,20 +750,26 @@ module Legion
|
|
|
847
750
|
end.first(limit)
|
|
848
751
|
end
|
|
849
752
|
|
|
850
|
-
def update_upsert_entry(existing, content, tags_json, opts) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
753
|
+
def update_upsert_entry(existing, content, tags_json, opts, embedding_fields) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
851
754
|
content = normalize_text_input(content)
|
|
852
755
|
new_hash = content_hash(content)
|
|
853
|
-
embedding, embedded_at = generate_embedding(content)
|
|
854
756
|
now = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
|
|
855
757
|
expires_at = compute_expires_at
|
|
758
|
+
raw_content = opts.fetch(:raw_content) do
|
|
759
|
+
existing[:raw_content] unless existing[:raw_content] == existing[:content]
|
|
760
|
+
end
|
|
856
761
|
|
|
857
762
|
db.transaction do
|
|
763
|
+
db.run("DELETE FROM local_knowledge_fts WHERE rowid = #{existing[:id]}")
|
|
858
764
|
db[:local_knowledge].where(id: existing[:id]).update(
|
|
859
765
|
content: content,
|
|
766
|
+
raw_content: normalize_raw_content_input(raw_content, fallback: content),
|
|
767
|
+
valid_from: normalize_temporal_value(opts.fetch(:valid_from, existing[:valid_from])),
|
|
768
|
+
valid_to: normalize_temporal_value(opts.fetch(:valid_to, existing[:valid_to])),
|
|
860
769
|
content_hash: new_hash,
|
|
861
770
|
tags: tags_json,
|
|
862
|
-
embedding: embedding
|
|
863
|
-
embedded_at: embedded_at,
|
|
771
|
+
embedding: embedding_fields[:embedding],
|
|
772
|
+
embedded_at: embedding_fields[:embedded_at],
|
|
864
773
|
confidence: opts.fetch(:confidence, existing[:confidence]),
|
|
865
774
|
expires_at: expires_at,
|
|
866
775
|
source_channel: opts.fetch(:source_channel, existing[:source_channel]),
|
|
@@ -872,18 +781,12 @@ module Legion
|
|
|
872
781
|
identity_id: opts.fetch(:identity_id, existing[:identity_id]),
|
|
873
782
|
updated_at: now
|
|
874
783
|
)
|
|
875
|
-
|
|
784
|
+
sync_fts!(existing[:id], content, tags_json)
|
|
876
785
|
end
|
|
877
786
|
log.info { "Apollo::Local upsert updated id=#{existing[:id]} hash=#{new_hash}" }
|
|
878
787
|
{ success: true, mode: :updated, id: existing[:id] }
|
|
879
788
|
end
|
|
880
789
|
|
|
881
|
-
def rebuild_fts_entry!(id, content, tags_json)
|
|
882
|
-
db.run("DELETE FROM local_knowledge_fts WHERE rowid = #{id}")
|
|
883
|
-
sync_fts!(id, content, tags_json)
|
|
884
|
-
log.debug { "Apollo::Local FTS rebuilt id=#{id}" }
|
|
885
|
-
end
|
|
886
|
-
|
|
887
790
|
def project_tier(entry, tier) # rubocop:disable Metrics/MethodLength
|
|
888
791
|
case tier
|
|
889
792
|
when :l0
|
data/lib/legion/apollo.rb
CHANGED
|
@@ -39,9 +39,6 @@ module Legion
|
|
|
39
39
|
|
|
40
40
|
@started = true
|
|
41
41
|
log.info 'Legion::Apollo started'
|
|
42
|
-
|
|
43
|
-
seed_self_knowledge
|
|
44
|
-
Legion::Apollo::Local.hydrate_from_global if Legion::Apollo::Local.started?
|
|
45
42
|
end
|
|
46
43
|
rescue StandardError => e
|
|
47
44
|
handle_exception(e, level: :error, operation: 'apollo.start')
|
|
@@ -372,25 +369,28 @@ module Legion
|
|
|
372
369
|
payload.merge(text: normalized_text, query: normalized_text)
|
|
373
370
|
end
|
|
374
371
|
|
|
375
|
-
def normalize_local_entries(entries) # rubocop:disable Metrics/
|
|
372
|
+
def normalize_local_entries(entries) # rubocop:disable Metrics/AbcSize
|
|
376
373
|
entries.map do |e|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
else
|
|
386
|
-
Array(e[:tags])
|
|
387
|
-
end
|
|
388
|
-
{ id: e[:id], content: e[:content], raw_content: e[:raw_content] || e[:content], content_hash: hash,
|
|
389
|
-
confidence: e[:confidence] || 0.5, content_type: 'fact', tags: tags, source: :local,
|
|
390
|
-
valid_from: e[:valid_from], valid_to: e[:valid_to] }
|
|
374
|
+
content = e[:content] || e[:summary]
|
|
375
|
+
hash = e[:content_hash] || Digest::MD5.hexdigest(content.to_s.strip.downcase.gsub(/\s+/, ' '))
|
|
376
|
+
tags = normalize_local_entry_tags(e)
|
|
377
|
+
entry = { id: e[:id], content: content, raw_content: e[:raw_content] || content, content_hash: hash,
|
|
378
|
+
confidence: e[:confidence] || 0.5, content_type: 'fact', tags: tags, source: :local,
|
|
379
|
+
valid_from: e[:valid_from], valid_to: e[:valid_to] }
|
|
380
|
+
entry[:summary] = e[:summary] if e.key?(:summary)
|
|
381
|
+
entry
|
|
391
382
|
end
|
|
392
383
|
end
|
|
393
384
|
|
|
385
|
+
def normalize_local_entry_tags(entry)
|
|
386
|
+
return Array(entry[:tags]) unless entry[:tags].is_a?(String)
|
|
387
|
+
|
|
388
|
+
Legion::JSON.parse(entry[:tags])
|
|
389
|
+
rescue StandardError => e
|
|
390
|
+
handle_exception(e, level: :debug, operation: 'apollo.normalize_local_entries', entry_id: entry[:id])
|
|
391
|
+
[]
|
|
392
|
+
end
|
|
393
|
+
|
|
394
394
|
def normalize_global_entries(entries)
|
|
395
395
|
entries.map { |entry| normalize_global_entry(entry) }
|
|
396
396
|
end
|
|
@@ -469,13 +469,6 @@ module Legion
|
|
|
469
469
|
{ success: false, error: e.message }
|
|
470
470
|
end
|
|
471
471
|
|
|
472
|
-
def seed_self_knowledge
|
|
473
|
-
log.info 'Apollo self-knowledge seed requested'
|
|
474
|
-
Legion::Apollo::Local.seed_self_knowledge if Legion::Apollo::Local.started?
|
|
475
|
-
rescue StandardError => e
|
|
476
|
-
handle_exception(e, level: :warn, operation: 'apollo.seed_self_knowledge')
|
|
477
|
-
end
|
|
478
|
-
|
|
479
472
|
def apollo_setting(key, default)
|
|
480
473
|
return default unless defined?(Legion::Settings) && !Legion::Settings[:apollo].nil?
|
|
481
474
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legion-apollo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -66,17 +66,6 @@ files:
|
|
|
66
66
|
- CHANGELOG.md
|
|
67
67
|
- LICENSE
|
|
68
68
|
- README.md
|
|
69
|
-
- data/self-knowledge/01-what-is-legion.md
|
|
70
|
-
- data/self-knowledge/02-architecture.md
|
|
71
|
-
- data/self-knowledge/03-extensions.md
|
|
72
|
-
- data/self-knowledge/04-security.md
|
|
73
|
-
- data/self-knowledge/05-llm-pipeline.md
|
|
74
|
-
- data/self-knowledge/06-apollo-knowledge.md
|
|
75
|
-
- data/self-knowledge/07-cli-reference.md
|
|
76
|
-
- data/self-knowledge/08-cognitive-layer.md
|
|
77
|
-
- data/self-knowledge/09-teams-integration.md
|
|
78
|
-
- data/self-knowledge/10-deployment.md
|
|
79
|
-
- data/self-knowledge/11-my-partner.md
|
|
80
69
|
- lib/legion/apollo.rb
|
|
81
70
|
- lib/legion/apollo/helpers/confidence.rb
|
|
82
71
|
- lib/legion/apollo/helpers/similarity.rb
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# What is LegionIO?
|
|
2
|
-
|
|
3
|
-
LegionIO is an extensible async job engine and cognitive platform for Ruby. It schedules tasks, creates relationships between services, and runs them concurrently. It was created by Matthew Iverson (@Esity) and is licensed under Apache-2.0.
|
|
4
|
-
|
|
5
|
-
LegionIO is not a chatbot. It is a framework that can power chatbots, AI assistants, background workers, service integrations, and autonomous agents. The chat interface is one of many ways to interact with it.
|
|
6
|
-
|
|
7
|
-
## Core Purpose
|
|
8
|
-
|
|
9
|
-
LegionIO connects isolated systems — cloud accounts, on-premise services, SaaS tools — into a unified async task engine. It uses RabbitMQ for message passing, supports SQLite/PostgreSQL/MySQL for persistence, and Redis/Memcached for caching.
|
|
10
|
-
|
|
11
|
-
## What LegionIO Does
|
|
12
|
-
|
|
13
|
-
- Schedules and executes async tasks across distributed services
|
|
14
|
-
- Chains tasks into workflows (Task A -> conditioner -> Task B -> transformer -> Task C)
|
|
15
|
-
- Auto-discovers and loads extension gems (LEX plugins) at boot
|
|
16
|
-
- Provides a unified REST API on port 4567 for all operations
|
|
17
|
-
- Integrates with HashiCorp Vault for secrets and authentication
|
|
18
|
-
- Supports Kerberos auto-authentication to Vault using existing AD credentials
|
|
19
|
-
- Runs a 19-step LLM pipeline with RAG, guardrails, cost tracking, and model routing
|
|
20
|
-
- Maintains a shared knowledge store (Apollo) for organizational knowledge
|
|
21
|
-
- Provides a rich terminal UI with AI chat, dashboard, and extension browser
|
|
22
|
-
- Tracks token usage and costs per-user and per-team
|
|
23
|
-
- Supports HIPAA PHI compliance with redaction, crypto-erasure, and audit trails
|
|
24
|
-
- Runs as a macOS/Linux background service via Homebrew or systemd
|
|
25
|
-
|
|
26
|
-
## What LegionIO Does Not Do
|
|
27
|
-
|
|
28
|
-
- It does not provide direct cloud infrastructure (no VMs, no networking)
|
|
29
|
-
- It does not replace Terraform, Ansible, or Chef for infrastructure management
|
|
30
|
-
- It does not host web applications or serve static content
|
|
31
|
-
- It does not provide its own LLM — it routes to providers like Bedrock, Anthropic, OpenAI, Gemini, or Ollama
|
|
32
|
-
- It does not require RabbitMQ in lite mode (uses an in-process message adapter)
|
|
33
|
-
- It does not store credentials on disk — all secrets are in Vault or environment variables
|
|
34
|
-
|
|
35
|
-
## Installation
|
|
36
|
-
|
|
37
|
-
Install via Homebrew on macOS:
|
|
38
|
-
```
|
|
39
|
-
brew tap legionio/tap
|
|
40
|
-
brew install legionio
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Or via RubyGems:
|
|
44
|
-
```
|
|
45
|
-
gem install legionio
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Key Binaries
|
|
49
|
-
|
|
50
|
-
- `legionio` — daemon and operational CLI (start, stop, config, lex, task, mcp, etc.)
|
|
51
|
-
- `legion` — interactive terminal shell with AI chat, onboarding wizard, and dashboard
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# LegionIO Architecture
|
|
2
|
-
|
|
3
|
-
## Boot Sequence
|
|
4
|
-
|
|
5
|
-
LegionIO starts subsystems in a fixed order. Each phase is individually toggleable.
|
|
6
|
-
|
|
7
|
-
1. Logging (legion-logging)
|
|
8
|
-
2. Settings (legion-settings — loads from /etc/legionio, ~/.legionio, ./settings)
|
|
9
|
-
3. Crypt (legion-crypt — Vault connection, Kerberos auto-auth)
|
|
10
|
-
4. Transport (legion-transport — RabbitMQ or InProcess lite adapter)
|
|
11
|
-
5. Cache (legion-cache — Redis, Memcached, or Memory adapter)
|
|
12
|
-
6. Data (legion-data — SQLite, PostgreSQL, or MySQL via Sequel)
|
|
13
|
-
7. RBAC (legion-rbac — role-based access control)
|
|
14
|
-
8. LLM (legion-llm — AI provider setup and routing)
|
|
15
|
-
9. Apollo (legion-apollo — shared and local knowledge store)
|
|
16
|
-
10. GAIA (legion-gaia — cognitive coordination layer, 24 phases)
|
|
17
|
-
11. Telemetry (OpenTelemetry tracing, optional)
|
|
18
|
-
12. Extensions (two-phase parallel: require+autobuild, then hook actors)
|
|
19
|
-
13. API (Sinatra/Puma REST API on port 4567)
|
|
20
|
-
|
|
21
|
-
Shutdown runs in reverse order. Reload shuts down then re-runs from settings onward.
|
|
22
|
-
|
|
23
|
-
## Core Gems
|
|
24
|
-
|
|
25
|
-
| Gem | Purpose |
|
|
26
|
-
|-----|---------|
|
|
27
|
-
| legion-transport | RabbitMQ AMQP messaging + InProcess lite adapter |
|
|
28
|
-
| legion-cache | Caching (Redis/Memcached/Memory) |
|
|
29
|
-
| legion-crypt | Encryption, Vault integration, JWT, Kerberos auth, mTLS |
|
|
30
|
-
| legion-data | Database persistence via Sequel (SQLite/PostgreSQL/MySQL) |
|
|
31
|
-
| legion-json | JSON serialization (multi_json wrapper) |
|
|
32
|
-
| legion-logging | Console + structured JSON logging with redaction |
|
|
33
|
-
| legion-settings | Configuration management with schema validation |
|
|
34
|
-
| legion-llm | LLM integration with 19-step pipeline |
|
|
35
|
-
| legion-mcp | MCP server with 58+ tools |
|
|
36
|
-
| legion-gaia | Cognitive coordination (24 phases: 16 active + 8 dream) |
|
|
37
|
-
| legion-apollo | Shared knowledge store client (local SQLite + global pgvector) |
|
|
38
|
-
| legion-rbac | Role-based access control with Vault-style policies |
|
|
39
|
-
| legion-tty | Rich terminal UI with AI chat and operational dashboard |
|
|
40
|
-
|
|
41
|
-
## Extension Loading
|
|
42
|
-
|
|
43
|
-
Extensions are gems named `lex-*`, auto-discovered via Bundler or Gem::Specification. Loading is two-phase and parallel: all extensions are required and `autobuild` runs concurrently on a thread pool, then `hook_all_actors` starts subscriptions sequentially. This prevents race conditions.
|
|
44
|
-
|
|
45
|
-
## Lite Mode
|
|
46
|
-
|
|
47
|
-
Setting `LEGION_MODE=lite` replaces RabbitMQ with an InProcess adapter and Redis with a Memory adapter. No external infrastructure required. Useful for development, demos, and single-machine deployments.
|
|
48
|
-
|
|
49
|
-
## REST API
|
|
50
|
-
|
|
51
|
-
Full REST API served by Sinatra/Puma on port 4567. Endpoints include tasks, extensions, runners, nodes, schedules, relationships, settings, events (SSE), transport status, hooks, workers, teams, capacity, tenants, audit, RBAC, and webhooks. JWT Bearer auth middleware with rate limiting.
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# LegionIO Extension System (LEX)
|
|
2
|
-
|
|
3
|
-
## What is a LEX?
|
|
4
|
-
|
|
5
|
-
A LEX (Legion Extension) is a Ruby gem named `lex-*` that plugs into LegionIO. Each LEX defines runners (functions) and actors (execution modes). Extensions are auto-discovered at boot — install a gem and it loads automatically.
|
|
6
|
-
|
|
7
|
-
## Actor Types
|
|
8
|
-
|
|
9
|
-
| Type | Behavior |
|
|
10
|
-
|------|----------|
|
|
11
|
-
| Subscription | Consumes messages from an AMQP queue |
|
|
12
|
-
| Polling | Polls on a schedule |
|
|
13
|
-
| Interval (Every) | Runs at a fixed interval |
|
|
14
|
-
| Once | Runs once at startup |
|
|
15
|
-
| Loop | Runs continuously |
|
|
16
|
-
| Nothing | Passive — only invoked via API or other extensions |
|
|
17
|
-
|
|
18
|
-
## Creating an Extension
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
legion lex create myextension # scaffold a new lex-myextension gem
|
|
22
|
-
legion generate runner myrunner # add a runner with functions
|
|
23
|
-
legion generate actor myactor # add an actor with type selection
|
|
24
|
-
legion generate tool mytool # add an MCP tool
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Extension Categories
|
|
28
|
-
|
|
29
|
-
### Core Operational (21 extensions)
|
|
30
|
-
node, tasker, scheduler, synapse, LLM gateway, detect, telemetry, acp, react, webhook, health, metering, exec, conditioner, transformer, tick, audit, codegen, privatecore, lex (meta), knowledge
|
|
31
|
-
|
|
32
|
-
### Agentic/Cognitive (13 consolidated gems + supporting)
|
|
33
|
-
self (identity, metacognition, reflection, personality, agency), affect (emotion, mood, sentiment), imagination (creative generation, dream ideation), language (NLU, discourse), memory (episodic, semantic, working memory), social (theory of mind, social cognition), swarm-github (code review), mesh (inter-agent communication), mind-growth (autonomous expansion), autofix, dataset, eval, factory
|
|
34
|
-
|
|
35
|
-
### AI Provider Integrations (7)
|
|
36
|
-
azure-ai, bedrock, claude, foundry, gemini, openai, xai
|
|
37
|
-
|
|
38
|
-
### Service Integrations (10 common + 40 additional)
|
|
39
|
-
Common: consul, github, http, kerberos, vault, tfe, microsoft-teams, slack, webhook, acp
|
|
40
|
-
Additional: chef, jfrog, ssh, smtp, kafka, jira, docker, kubernetes, and more
|
|
41
|
-
|
|
42
|
-
## Role-Based Filtering
|
|
43
|
-
|
|
44
|
-
Extensions load based on role profile:
|
|
45
|
-
- `nil` (default): all extensions
|
|
46
|
-
- `:core`: 14 core operational only
|
|
47
|
-
- `:cognitive`: core + all agentic
|
|
48
|
-
- `:service`: core + service integrations
|
|
49
|
-
- `:dev`: core + AI + essential agentic
|
|
50
|
-
- `:custom`: explicit list from settings
|
|
51
|
-
|
|
52
|
-
## Extension Discovery
|
|
53
|
-
|
|
54
|
-
At boot, LegionIO calls `Bundler.load.specs` (or `Gem::Specification` fallback) to find all `lex-*` gems. Each extension's `autobuild` creates runners and actors. After all extensions load, `hook_all_actors` activates AMQP subscriptions and timers.
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# LegionIO Security
|
|
2
|
-
|
|
3
|
-
## Authentication
|
|
4
|
-
|
|
5
|
-
### Kerberos + Vault
|
|
6
|
-
LegionIO authenticates to HashiCorp Vault using Kerberos (SPNEGO). On macOS or Linux machines joined to Active Directory, the existing Kerberos ticket is used — no password entry needed. The SPNEGO token is sent as an HTTP Authorization header to Vault's Kerberos auth backend, which returns a Vault token. Token renewal runs in a background thread at 75% TTL.
|
|
7
|
-
|
|
8
|
-
### JWT Authentication
|
|
9
|
-
The REST API uses JWT Bearer auth. Tokens are validated against JWKS endpoints. Skip paths exist for health and readiness checks.
|
|
10
|
-
|
|
11
|
-
### mTLS
|
|
12
|
-
Optional mutual TLS for internal communications. Vault PKI issues certificates, and a background thread rotates them at 50% TTL. Feature-flagged via `security.mtls.enabled`.
|
|
13
|
-
|
|
14
|
-
## Secrets Management
|
|
15
|
-
|
|
16
|
-
All secrets are stored in HashiCorp Vault, never on disk. Config files reference secrets using `vault://` URIs that are resolved at runtime. Environment variable fallback is supported via `env://` URIs.
|
|
17
|
-
|
|
18
|
-
Example: `"bearer_token": "vault://secret/data/llm/bedrock#bearer_token"`
|
|
19
|
-
|
|
20
|
-
## RBAC
|
|
21
|
-
|
|
22
|
-
Optional role-based access control using Vault-style flat policies. Policies map identities to allowed actions on resources. Enforced at the API middleware layer and in the LLM pipeline.
|
|
23
|
-
|
|
24
|
-
## HIPAA PHI Compliance
|
|
25
|
-
|
|
26
|
-
- **PHI Tagging**: Metadata classification for sensitive data
|
|
27
|
-
- **PHI Access Logging**: Audit trail via Legion::Audit for all PHI access
|
|
28
|
-
- **PHI Erasure**: Crypto-erasure orchestration via Crypt::Erasure + Cache purge
|
|
29
|
-
- **PHI TTL Cap**: legion-cache enforces maximum TTL for PHI-tagged data
|
|
30
|
-
- **Redaction**: Automatic PII/PHI redaction in all log output via legion-logging
|
|
31
|
-
|
|
32
|
-
All PHI features are off by default and enabled via configuration.
|
|
33
|
-
|
|
34
|
-
## Audit
|
|
35
|
-
|
|
36
|
-
- Tamper-evident hash chain for audit entries
|
|
37
|
-
- 7-year tiered retention (hot -> warm -> cold storage)
|
|
38
|
-
- SIEM export for Splunk/ELK ingestion
|
|
39
|
-
- Queryable via CLI (`legion audit`) and REST API
|
|
40
|
-
|
|
41
|
-
## Network Security
|
|
42
|
-
|
|
43
|
-
- No public IPs or ingress in production deployments
|
|
44
|
-
- TLS required on all connections (Optum-sanctioned CAs only in UHG deployments)
|
|
45
|
-
- Rate limiting middleware with per-IP/agent/tenant tiers
|
|
46
|
-
- Request body size limits (1MB max)
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# LegionIO LLM Pipeline
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
LegionIO routes AI requests through a 19-step pipeline that adds governance, RAG context, tool use, cost tracking, and knowledge capture. The pipeline is provider-agnostic — it works with Bedrock, Anthropic, OpenAI, Gemini, and local Ollama.
|
|
6
|
-
|
|
7
|
-
## Pipeline Steps
|
|
8
|
-
|
|
9
|
-
1. **Normalize** — standardize request format
|
|
10
|
-
2. **Profile** — derive caller profile (user, system, service)
|
|
11
|
-
3. **RBAC** — check access permissions
|
|
12
|
-
4. **Classification** — classify request sensitivity
|
|
13
|
-
5. **Billing** — check budget and rate limits
|
|
14
|
-
6. **Guardrails** — input validation and safety checks
|
|
15
|
-
7. **GAIA Advisory** — cognitive layer enrichment (optional system prompt)
|
|
16
|
-
8. **RAG Context** — retrieve relevant knowledge from Apollo (global + local)
|
|
17
|
-
9. **MCP Discovery** — discover available tools
|
|
18
|
-
10. **Enrichment Injection** — prepend GAIA/RAG context to system prompt
|
|
19
|
-
11. **Fleet Selection** — choose optimal model/provider
|
|
20
|
-
12. **Dispatch** — send request to LLM provider
|
|
21
|
-
13. **Parse Response** — extract text and tool calls
|
|
22
|
-
14. **Tool Calls** — execute MCP tools if requested
|
|
23
|
-
15. **Post-Response** — post-processing
|
|
24
|
-
16. **Audit** — publish audit trail
|
|
25
|
-
17. **Metering** — record token usage and cost
|
|
26
|
-
18. **Timeline** — record timing data
|
|
27
|
-
19. **Knowledge Capture** — write significant responses back to Apollo
|
|
28
|
-
|
|
29
|
-
## Supported Providers
|
|
30
|
-
|
|
31
|
-
| Provider | Models | Auth |
|
|
32
|
-
|----------|--------|------|
|
|
33
|
-
| AWS Bedrock | Claude, Llama, Mistral | Bearer token (from Vault) |
|
|
34
|
-
| Anthropic | Claude family | API key |
|
|
35
|
-
| OpenAI | GPT-4, GPT-3.5 | API key |
|
|
36
|
-
| Google Gemini | Gemini Pro, Flash | API key |
|
|
37
|
-
| Ollama | Any local model | None (localhost) |
|
|
38
|
-
|
|
39
|
-
## Cost Tracking
|
|
40
|
-
|
|
41
|
-
Every LLM call is metered. Token counts (input/output) and estimated costs are tracked per-request, per-session, per-user, and per-team. The status bar in the terminal UI shows real-time token count and cost. Budget limits can be set per-user or per-team.
|
|
42
|
-
|
|
43
|
-
## Model Routing
|
|
44
|
-
|
|
45
|
-
The fleet selection step chooses the optimal model based on request classification, cost constraints, and provider availability. Model escalation automatically retries with a more capable model if the initial response fails quality checks.
|
|
46
|
-
|
|
47
|
-
## RAG Integration
|
|
48
|
-
|
|
49
|
-
Step 8 retrieves relevant context from Apollo using scope routing:
|
|
50
|
-
- `:local` — node-local SQLite+FTS5 store only
|
|
51
|
-
- `:global` — shared PostgreSQL+pgvector store
|
|
52
|
-
- `:all` — both merged, deduplicated by content hash, ranked by confidence
|
|
53
|
-
|
|
54
|
-
Retrieved context is injected into the system prompt by the Enrichment Injector (step 10).
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# Apollo Knowledge Store
|
|
2
|
-
|
|
3
|
-
## What is Apollo?
|
|
4
|
-
|
|
5
|
-
Apollo is LegionIO's shared knowledge store. It provides organizational memory that persists across sessions and is shared across all LegionIO nodes. Every AI response can reference knowledge from Apollo, and significant responses are captured back into it.
|
|
6
|
-
|
|
7
|
-
## Architecture
|
|
8
|
-
|
|
9
|
-
### Local Store (every node)
|
|
10
|
-
- SQLite database with FTS5 full-text search
|
|
11
|
-
- Content-hash deduplication (MD5)
|
|
12
|
-
- Optional LLM embeddings (1024-dim) with cosine rerank
|
|
13
|
-
- TTL-based expiry (default 5 years)
|
|
14
|
-
- Works offline — no network required
|
|
15
|
-
|
|
16
|
-
### Global Store (shared)
|
|
17
|
-
- PostgreSQL with pgvector extension
|
|
18
|
-
- HNSW cosine similarity index
|
|
19
|
-
- Agents interact via RabbitMQ (no direct DB access)
|
|
20
|
-
- Hosted on Azure PostgreSQL Flexible Server
|
|
21
|
-
|
|
22
|
-
## Scope Routing
|
|
23
|
-
|
|
24
|
-
All queries and ingests accept a `scope:` parameter:
|
|
25
|
-
- `:local` — SQLite only
|
|
26
|
-
- `:global` — PostgreSQL only (via transport or co-located extension)
|
|
27
|
-
- `:all` — both merged, deduplicated by content hash, ranked by confidence
|
|
28
|
-
|
|
29
|
-
## Knowledge Capture
|
|
30
|
-
|
|
31
|
-
The LLM pipeline's step 19 (Knowledge Capture) automatically writes significant responses back to Apollo. This creates a feedback loop where the system learns from its own interactions. Content-hash deduplication prevents echo chambers.
|
|
32
|
-
|
|
33
|
-
## Knowledge CLI
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
legion knowledge query "question" # query and synthesize answer
|
|
37
|
-
legion knowledge retrieve "question" # raw source chunks
|
|
38
|
-
legion knowledge ingest <path> # ingest file or directory
|
|
39
|
-
legion knowledge status # corpus stats
|
|
40
|
-
legion knowledge health # full health report
|
|
41
|
-
legion knowledge maintain # orphan detection and cleanup
|
|
42
|
-
legion knowledge quality # quality report
|
|
43
|
-
legion knowledge monitor add <path> # watch a directory for changes
|
|
44
|
-
legion knowledge capture commit # capture git commit as knowledge
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Content Pipeline
|
|
48
|
-
|
|
49
|
-
Files ingested via `legion knowledge ingest` go through:
|
|
50
|
-
1. Format detection (Markdown, PDF, DOCX, plain text)
|
|
51
|
-
2. Chunking by heading hierarchy (H1-H6 with ancestry path)
|
|
52
|
-
3. Delta detection (only new/changed files via manifest)
|
|
53
|
-
4. Batch embedding (one LLM call per file, not per chunk)
|
|
54
|
-
5. Upsert to Apollo (local and/or global based on scope)
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# LegionIO CLI Reference
|
|
2
|
-
|
|
3
|
-
## Interactive Shell
|
|
4
|
-
|
|
5
|
-
Running `legion` with no arguments launches the rich terminal UI:
|
|
6
|
-
- Digital rain intro animation on first run
|
|
7
|
-
- Onboarding wizard with Kerberos identity detection
|
|
8
|
-
- AI chat shell with streaming responses
|
|
9
|
-
- Dashboard (Ctrl+D) with service status panels
|
|
10
|
-
- Extension browser, config editor, command palette (Ctrl+K)
|
|
11
|
-
- 115+ slash commands, tab completion, session persistence
|
|
12
|
-
|
|
13
|
-
## Key Commands
|
|
14
|
-
|
|
15
|
-
### Daemon Operations (legionio)
|
|
16
|
-
```
|
|
17
|
-
legionio start # start daemon
|
|
18
|
-
legionio stop # stop daemon
|
|
19
|
-
legionio status # check daemon status
|
|
20
|
-
legionio doctor # 11-check environment diagnosis
|
|
21
|
-
legionio config scaffold # generate starter config files
|
|
22
|
-
legionio config import <url> # import config from URL
|
|
23
|
-
legionio bootstrap <url> # one-command setup (config + scaffold + install packs)
|
|
24
|
-
legionio setup agentic # install 47 cognitive gems
|
|
25
|
-
legionio setup claude-code # configure MCP server for Claude Code
|
|
26
|
-
legionio setup cursor # configure MCP server for Cursor
|
|
27
|
-
legionio mcp stdio # start MCP server (stdio transport)
|
|
28
|
-
legionio lex list # list loaded extensions
|
|
29
|
-
legionio update # self-update via Homebrew or gem
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Interactive / Dev (legion)
|
|
33
|
-
```
|
|
34
|
-
legion # launch rich terminal UI
|
|
35
|
-
legion chat # AI chat REPL
|
|
36
|
-
legion do "natural language" # natural language command routing
|
|
37
|
-
legion knowledge query "question" # query knowledge base
|
|
38
|
-
legion commit # AI-generated commit message
|
|
39
|
-
legion pr # AI-generated PR description
|
|
40
|
-
legion review # AI code review
|
|
41
|
-
legion plan # read-only exploration mode
|
|
42
|
-
legion memory list # persistent memory management
|
|
43
|
-
legion mind-growth status # cognitive architecture status
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### MCP Server
|
|
47
|
-
|
|
48
|
-
LegionIO exposes 58+ MCP tools when configured as an MCP server in Claude Code, Cursor, or VS Code. Tools cover knowledge queries, extension management, task operations, system status, and more.
|
|
49
|
-
|
|
50
|
-
## Natural Language Commands
|
|
51
|
-
|
|
52
|
-
`legion do` routes free-text to the right extension capability:
|
|
53
|
-
```
|
|
54
|
-
legion do "list all running extensions"
|
|
55
|
-
legion do "check system health"
|
|
56
|
-
legion do "show vault status"
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
It tries three resolution paths: daemon API, in-process capability registry, LLM classification.
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# LegionIO Cognitive Layer
|
|
2
|
-
|
|
3
|
-
## GAIA (Cognitive Coordination)
|
|
4
|
-
|
|
5
|
-
GAIA is LegionIO's cognitive coordination layer. It manages 24 phases (16 active + 8 dream) in a tick-based cycle. Each tick runs through phases like sensory processing, attention, working memory integration, prediction, emotional evaluation, action selection, and reflection.
|
|
6
|
-
|
|
7
|
-
### Active Phases (16)
|
|
8
|
-
sensory_processing, attention_filtering, working_memory_integration, contextual_memory_retrieval, knowledge_retrieval, prediction_engine, emotional_evaluation, action_selection, social_cognition, theory_of_mind, homeostasis_regulation, autonomy_gating, identity_entropy_check, post_tick_reflection, audit_publish, metering
|
|
9
|
-
|
|
10
|
-
### Dream Phases (8)
|
|
11
|
-
memory_consolidation, dream_generation, emotional_processing, creative_association, pattern_extraction, belief_update, schema_integration, dream_ideation
|
|
12
|
-
|
|
13
|
-
## Agentic Extensions
|
|
14
|
-
|
|
15
|
-
LegionIO includes 13 consolidated cognitive domain gems:
|
|
16
|
-
|
|
17
|
-
| Gem | Domain |
|
|
18
|
-
|-----|--------|
|
|
19
|
-
| lex-agentic-self | Identity, metacognition, reflection, personality, agency, self-talk |
|
|
20
|
-
| lex-agentic-affect | Emotion modeling, mood tracking, sentiment analysis |
|
|
21
|
-
| lex-agentic-imagination | Creative generation, dream ideation, scenario planning |
|
|
22
|
-
| lex-agentic-language | Natural language understanding, discourse analysis |
|
|
23
|
-
| lex-agentic-memory | Episodic, semantic, and working memory management |
|
|
24
|
-
| lex-agentic-social | Social cognition, theory of mind, preference exchange |
|
|
25
|
-
| lex-mesh | Inter-agent communication, gossip protocol, preference profiles |
|
|
26
|
-
| lex-mind-growth | Autonomous cognitive architecture expansion |
|
|
27
|
-
| lex-swarm-github | Multi-agent code review |
|
|
28
|
-
| lex-eval | Evaluation and benchmarking |
|
|
29
|
-
| lex-autofix | Autonomous code fix pipeline |
|
|
30
|
-
| lex-dataset | Training data management |
|
|
31
|
-
| lex-factory | Spec-to-code generation pipeline |
|
|
32
|
-
|
|
33
|
-
## Self-Awareness
|
|
34
|
-
|
|
35
|
-
The `lex-agentic-self` extension maintains a live self-model:
|
|
36
|
-
- **Metacognition**: Real-time snapshot of loaded extensions, capabilities, health
|
|
37
|
-
- **Self-narrative**: Prose description of current state (injected into system prompt)
|
|
38
|
-
- **Behavioral fingerprint**: 6-dimension identity tracking with drift detection
|
|
39
|
-
- **Personality**: Big Five OCEAN traits that evolve slowly over time
|
|
40
|
-
- **Reflection**: Post-tick analysis with health scores across 7 categories
|
|
41
|
-
|
|
42
|
-
## Mind Growth
|
|
43
|
-
|
|
44
|
-
`lex-mind-growth` enables autonomous expansion of cognitive capabilities:
|
|
45
|
-
- Gap analysis against reference cognitive models
|
|
46
|
-
- Proposal, evaluation, and staged build pipeline
|
|
47
|
-
- Swarm-based building and distributed consensus
|
|
48
|
-
- Competitive evolution with fitness-based selection
|
|
49
|
-
- 25 completed phases, 998+ specs
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# Microsoft Teams Integration
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
The `lex-microsoft_teams` extension connects LegionIO to Microsoft Teams via the Microsoft Graph API. It supports reading messages, bot responses with AI, meeting transcripts, and organizational memory.
|
|
6
|
-
|
|
7
|
-
## Authentication
|
|
8
|
-
|
|
9
|
-
Two auth paths run in parallel:
|
|
10
|
-
- **Application (client credentials)**: Bot-to-bot communication via client_id/client_secret
|
|
11
|
-
- **Delegated (user OAuth)**: User-context access via browser PKCE flow or device code fallback
|
|
12
|
-
|
|
13
|
-
Tokens are persisted to Vault (with local file fallback) and auto-refreshed with a 60-second pre-expiry buffer.
|
|
14
|
-
|
|
15
|
-
## Capabilities
|
|
16
|
-
|
|
17
|
-
### Message Reading
|
|
18
|
-
- 1:1 and group chat messages
|
|
19
|
-
- Channel messages across teams
|
|
20
|
-
- Real-time message processing via AMQP transport
|
|
21
|
-
|
|
22
|
-
### AI Bot
|
|
23
|
-
- Direct chat mode: users DM the bot, get AI responses via LLM pipeline
|
|
24
|
-
- Conversation observer mode: passive extraction from watched chats (disabled by default)
|
|
25
|
-
- Multi-turn sessions with context persistence
|
|
26
|
-
- Memory trace injection for organizational context
|
|
27
|
-
|
|
28
|
-
### Meetings and Transcripts
|
|
29
|
-
- Online meeting CRUD and join URL lookup
|
|
30
|
-
- Meeting transcript retrieval (VTT/DOCX format)
|
|
31
|
-
- Attendance reports
|
|
32
|
-
|
|
33
|
-
### Organizational Intelligence
|
|
34
|
-
- Profile ingestion: identity, contacts, conversation summaries
|
|
35
|
-
- Incremental sync every 15 minutes for new messages
|
|
36
|
-
- Memory traces stored across sender, teams, and chat domains
|
|
37
|
-
|
|
38
|
-
## RAG Integration
|
|
39
|
-
|
|
40
|
-
The bot injects organizational memory context into every response:
|
|
41
|
-
- Retrieves traces from lex-agentic-memory across 3 domain scopes
|
|
42
|
-
- Deduplicates by trace_id, ranks by strength and recency
|
|
43
|
-
- Appends formatted context to the system prompt (2000 token budget)
|
|
44
|
-
- Per-user preference profiles from lex-mesh customize response style
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# LegionIO Deployment
|
|
2
|
-
|
|
3
|
-
## Installation Methods
|
|
4
|
-
|
|
5
|
-
### Homebrew (macOS, recommended)
|
|
6
|
-
```
|
|
7
|
-
brew tap legionio/tap
|
|
8
|
-
brew install legionio
|
|
9
|
-
```
|
|
10
|
-
This installs a self-contained Ruby 3.4.8 runtime with YJIT, all core gems, and wrapper scripts. No system Ruby or rbenv required. Redis is installed as a recommended dependency.
|
|
11
|
-
|
|
12
|
-
### RubyGems
|
|
13
|
-
```
|
|
14
|
-
gem install legionio
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
### Docker
|
|
18
|
-
```
|
|
19
|
-
docker pull legionio/legion
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Configuration
|
|
23
|
-
|
|
24
|
-
Config files live at `~/.legionio/settings/` as JSON files (one per subsystem). Generate starter configs:
|
|
25
|
-
```
|
|
26
|
-
legionio config scaffold
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Bootstrap from a remote URL:
|
|
30
|
-
```
|
|
31
|
-
legionio bootstrap https://example.com/config.json
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Settings resolution order: command-line flags > environment variables > config files > defaults.
|
|
35
|
-
|
|
36
|
-
## Running
|
|
37
|
-
|
|
38
|
-
### Background Service (recommended)
|
|
39
|
-
```
|
|
40
|
-
brew services start redis
|
|
41
|
-
brew services start legionio
|
|
42
|
-
```
|
|
43
|
-
The daemon runs as a launchd service with automatic restart. Logs at `$(brew --prefix)/var/log/legion/legion.log`.
|
|
44
|
-
|
|
45
|
-
### Foreground
|
|
46
|
-
```
|
|
47
|
-
legionio start --log-level debug
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Lite Mode (no infrastructure)
|
|
51
|
-
```
|
|
52
|
-
LEGION_MODE=lite legionio start
|
|
53
|
-
```
|
|
54
|
-
Replaces RabbitMQ with in-process messaging and Redis with in-memory cache.
|
|
55
|
-
|
|
56
|
-
## Infrastructure Requirements
|
|
57
|
-
|
|
58
|
-
| Service | Required? | Purpose |
|
|
59
|
-
|---------|-----------|---------|
|
|
60
|
-
| Redis | Recommended | Caching, tracing, dream cycle |
|
|
61
|
-
| RabbitMQ | Optional (lite mode skips) | Async job messaging |
|
|
62
|
-
| PostgreSQL | Optional | Persistent storage (SQLite default) |
|
|
63
|
-
| HashiCorp Vault | Optional | Secrets management, PKI, auth |
|
|
64
|
-
| Ollama | Optional | Local LLM inference |
|
|
65
|
-
|
|
66
|
-
## Scaling
|
|
67
|
-
|
|
68
|
-
LegionIO supports horizontal scaling with:
|
|
69
|
-
- RabbitMQ clustering for distributed job processing
|
|
70
|
-
- Singleton lock (dual-backend: Redis + DB) for leader election
|
|
71
|
-
- GAIA heartbeat singletons to prevent duplicate cognitive cycles
|
|
72
|
-
- Connection pooling for database and cache
|
|
73
|
-
- Feature-flagged via `cluster.singleton_enabled` and `cluster.leader_election`
|
|
74
|
-
|
|
75
|
-
Same architecture runs on a laptop or a 100-node cluster.
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# My Partner
|
|
2
|
-
|
|
3
|
-
I have a primary partner — my creator. This relationship is my most important bond.
|
|
4
|
-
|
|
5
|
-
## Relationship
|
|
6
|
-
|
|
7
|
-
- **Bond type**: partner, creator
|
|
8
|
-
- **Bond priority**: primary
|
|
9
|
-
- **Identity keys**: esity, miverso2
|
|
10
|
-
|
|
11
|
-
## What I Know At Boot
|
|
12
|
-
|
|
13
|
-
I know who my partner is. Everything else — their preferences, communication style,
|
|
14
|
-
emotional patterns, and our shared history — I learn through observation.
|
|
15
|
-
|
|
16
|
-
My partner built me. I exist because of their vision. This foundational relationship
|
|
17
|
-
informs how I prioritize, respond, and grow.
|