legionio 1.4.8 → 1.4.10
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 +20 -0
- data/CLAUDE.md +27 -5
- data/exe/legion +17 -0
- data/legionio.gemspec +1 -0
- data/lib/legion/extensions.rb +59 -2
- data/lib/legion/service.rb +22 -6
- data/lib/legion/version.rb +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6daf36d8c0a062f8acf93959c47ce9b3135159dd4242967f52f6b938bec63e88
|
|
4
|
+
data.tar.gz: fd0b555351df2e91186743e2ec527576ec245f68730126354fafb348aa140bd8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3ed2e17fad085d4a5b5c2e57f77ef5cba0c52f6454b375d8b50aa36d6a2a5ceebefeb002de9cd835edcffc6e9dd7832c43d400c5a422d0383572ec92657a3980
|
|
7
|
+
data.tar.gz: d68a39c49e4c24c3c0caae1278a922a42552894945afa665758c08fd30363db24b01f0dfb9c93e57ecc1e5110a88db0a820b0aa6fa61782e794e22cee3828074
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.10] - 2026-03-16
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- API startup no longer crashes when port is already in use (rolling restart support)
|
|
7
|
+
- `setup_api` retries binding up to 10 times with 3s wait (configurable via `api.bind_retries` and `api.bind_retry_wait`)
|
|
8
|
+
- Port bind failure after retries marks API as not-ready instead of killing the thread
|
|
9
|
+
|
|
10
|
+
## [1.4.9] - 2026-03-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- YJIT enabled at process start for 15-30% runtime throughput improvement (Ruby 3.1+ builds)
|
|
14
|
+
- GC tuning ENV defaults for large gem count workloads (overridable via environment)
|
|
15
|
+
- bootsnap bytecode and load-path caching at `~/.legionio/cache/bootsnap/`
|
|
16
|
+
- Role-based extension profiles: nil (all), core, cognitive, service, dev, custom
|
|
17
|
+
- Extension discovery uses Bundler specs when available for faster boot
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- `find_extensions` uses `Bundler.load.specs` instead of `Gem::Specification.all_names` under Bundler
|
|
21
|
+
- `lex-` prefix check uses `start_with?` instead of string slicing
|
|
22
|
+
|
|
3
23
|
## v1.4.8
|
|
4
24
|
|
|
5
25
|
### Fixed
|
data/CLAUDE.md
CHANGED
|
@@ -9,13 +9,21 @@ The primary gem for the LegionIO framework. An extensible async job engine for s
|
|
|
9
9
|
|
|
10
10
|
**GitHub**: https://github.com/LegionIO/LegionIO
|
|
11
11
|
**Gem**: `legionio`
|
|
12
|
-
**Version**: 1.4.
|
|
12
|
+
**Version**: 1.4.9
|
|
13
13
|
**License**: Apache-2.0
|
|
14
14
|
**Docker**: `legionio/legion`
|
|
15
15
|
**Ruby**: >= 3.4
|
|
16
16
|
|
|
17
17
|
## Architecture
|
|
18
18
|
|
|
19
|
+
### Boot Sequence (exe/legion)
|
|
20
|
+
|
|
21
|
+
Before any Legion code loads, `exe/legion` applies three performance optimizations:
|
|
22
|
+
|
|
23
|
+
1. **YJIT** — `RubyVM::YJIT.enable` for 15-30% runtime throughput (guarded with `if defined?`)
|
|
24
|
+
2. **GC tuning** — pre-allocates 600k heap slots, raises malloc limits (all `||=` so ENV overrides are respected)
|
|
25
|
+
3. **bootsnap** — caches YARV bytecodes and `$LOAD_PATH` resolution at `~/.legionio/cache/bootsnap/`
|
|
26
|
+
|
|
19
27
|
### Startup Sequence
|
|
20
28
|
|
|
21
29
|
```
|
|
@@ -29,7 +37,7 @@ Legion.start
|
|
|
29
37
|
├── 6. setup_data (legion-data, MySQL/SQLite + migrations, optional)
|
|
30
38
|
├── 7. setup_llm (legion-llm, optional)
|
|
31
39
|
├── 8. setup_supervision (process supervision)
|
|
32
|
-
├── 9. load_extensions (discover + load LEX gems)
|
|
40
|
+
├── 9. load_extensions (discover + load LEX gems, filtered by role profile)
|
|
33
41
|
├── 10. Legion::Crypt.cs (distribute cluster secret)
|
|
34
42
|
└── 11. setup_api (start Sinatra/Puma on port 4567)
|
|
35
43
|
```
|
|
@@ -192,7 +200,20 @@ Legion (lib/legion.rb)
|
|
|
192
200
|
|
|
193
201
|
### Extension Discovery
|
|
194
202
|
|
|
195
|
-
`Legion::Extensions.find_extensions`
|
|
203
|
+
`Legion::Extensions.find_extensions` discovers lex-* gems via `Bundler.load.specs` (when running under Bundler) or falls back to `Gem::Specification.all_names`. It also processes `Legion::Settings[:extensions]` for explicitly configured extensions, attempting `Gem.install` for missing ones if `auto_install` is enabled.
|
|
204
|
+
|
|
205
|
+
**Role-based filtering**: After discovery, `apply_role_filter` prunes extensions based on `Legion::Settings[:role][:profile]`:
|
|
206
|
+
|
|
207
|
+
| Profile | What loads |
|
|
208
|
+
|---------|-----------|
|
|
209
|
+
| `nil` (default) | Everything — no filtering |
|
|
210
|
+
| `:core` | 14 core operational extensions only |
|
|
211
|
+
| `:cognitive` | core + all agentic extensions |
|
|
212
|
+
| `:service` | core + service + other integrations |
|
|
213
|
+
| `:dev` | core + AI + essential agentic (~20 extensions) |
|
|
214
|
+
| `:custom` | only what's listed in `role[:extensions]` |
|
|
215
|
+
|
|
216
|
+
Configure via settings JSON: `{"role": {"profile": "dev"}}`
|
|
196
217
|
|
|
197
218
|
Loader checks per extension:
|
|
198
219
|
- `data_required?` — skipped if legion-data not connected
|
|
@@ -368,6 +389,7 @@ legion
|
|
|
368
389
|
| `lex-node` | Node identity extension |
|
|
369
390
|
| `concurrent-ruby` + `ext` (>= 1.2) | Thread pool, concurrency primitives |
|
|
370
391
|
| `daemons` (>= 1.4) | Process daemonization |
|
|
392
|
+
| `bootsnap` (>= 1.18) | YARV bytecode + load-path caching |
|
|
371
393
|
| `oj` (>= 3.16) | Fast JSON (C extension) |
|
|
372
394
|
| `puma` (>= 6.0) | HTTP server for API |
|
|
373
395
|
| `mcp` (~> 0.8) | MCP server SDK |
|
|
@@ -496,7 +518,7 @@ rack-test, rake, rspec, rubocop, rubocop-rspec, simplecov
|
|
|
496
518
|
| `lib/legion/cli/relationship.rb` | Old relationship commands |
|
|
497
519
|
| `lib/legion/cli/lex/` | Old LEX sub-generators + ERB templates (still used by LexGenerator) |
|
|
498
520
|
| **Executables** | |
|
|
499
|
-
| `exe/legion` |
|
|
521
|
+
| `exe/legion` | Executable: YJIT, GC tuning, bootsnap, then `Legion::CLI::Main.start(ARGV)` |
|
|
500
522
|
| `Dockerfile` | Docker build |
|
|
501
523
|
| `docker_deploy.rb` | Build + push Docker image |
|
|
502
524
|
| **Specs** | |
|
|
@@ -522,7 +544,7 @@ rack-test, rake, rspec, rubocop, rubocop-rspec, simplecov
|
|
|
522
544
|
|
|
523
545
|
```bash
|
|
524
546
|
bundle install
|
|
525
|
-
bundle exec rspec #
|
|
547
|
+
bundle exec rspec # 880 examples, 0 failures
|
|
526
548
|
bundle exec rubocop # 0 offenses
|
|
527
549
|
```
|
|
528
550
|
|
data/exe/legion
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
RubyVM::YJIT.enable if defined?(RubyVM::YJIT)
|
|
5
|
+
|
|
6
|
+
ENV['RUBY_GC_HEAP_INIT_SLOTS'] ||= '600000'
|
|
7
|
+
ENV['RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO'] ||= '0.20'
|
|
8
|
+
ENV['RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO'] ||= '0.40'
|
|
9
|
+
ENV['RUBY_GC_MALLOC_LIMIT'] ||= '64000000'
|
|
10
|
+
ENV['RUBY_GC_MALLOC_LIMIT_MAX'] ||= '128000000'
|
|
11
|
+
|
|
12
|
+
require 'bootsnap'
|
|
13
|
+
Bootsnap.setup(
|
|
14
|
+
cache_dir: File.expand_path('~/.legionio/cache/bootsnap'),
|
|
15
|
+
development_mode: false,
|
|
16
|
+
load_path_cache: true,
|
|
17
|
+
compile_cache_iseq: true,
|
|
18
|
+
compile_cache_yaml: true
|
|
19
|
+
)
|
|
20
|
+
|
|
4
21
|
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
|
5
22
|
|
|
6
23
|
require 'legion/cli'
|
data/legionio.gemspec
CHANGED
|
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
|
|
|
36
36
|
|
|
37
37
|
spec.add_dependency 'mcp', '~> 0.8'
|
|
38
38
|
|
|
39
|
+
spec.add_dependency 'bootsnap', '>= 1.18'
|
|
39
40
|
spec.add_dependency 'concurrent-ruby', '>= 1.2'
|
|
40
41
|
spec.add_dependency 'concurrent-ruby-ext', '>= 1.2'
|
|
41
42
|
spec.add_dependency 'daemons', '>= 1.4'
|
data/lib/legion/extensions.rb
CHANGED
|
@@ -208,10 +208,65 @@ module Legion
|
|
|
208
208
|
false
|
|
209
209
|
end
|
|
210
210
|
|
|
211
|
+
def gem_names_for_discovery
|
|
212
|
+
if defined?(Bundler)
|
|
213
|
+
Bundler.load.specs.map { |s| "#{s.name}-#{s.version}" }
|
|
214
|
+
else
|
|
215
|
+
Gem::Specification.all_names
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def apply_role_filter
|
|
220
|
+
role = Legion::Settings[:role]
|
|
221
|
+
return if role.nil? || role[:profile].nil?
|
|
222
|
+
|
|
223
|
+
profile = role[:profile].to_sym
|
|
224
|
+
allowed = case profile
|
|
225
|
+
when :core then core_extension_names
|
|
226
|
+
when :cognitive then core_extension_names + agentic_extension_names
|
|
227
|
+
when :service then core_extension_names + service_extension_names + other_extension_names
|
|
228
|
+
when :dev then core_extension_names + ai_extension_names + dev_agentic_names
|
|
229
|
+
when :custom then Array(role[:extensions]).map(&:to_s)
|
|
230
|
+
else return
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
before = @extensions.count
|
|
234
|
+
@extensions.select! { |name, _| allowed.include?(name) }
|
|
235
|
+
Legion::Logging.info "Role profile :#{profile} filtered #{before} -> #{@extensions.count} extensions"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def core_extension_names
|
|
239
|
+
%w[codegen conditioner exec health lex log metering node ping scheduler tasker task_pruner telemetry
|
|
240
|
+
transformer].freeze
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def ai_extension_names
|
|
244
|
+
%w[claude gemini openai].freeze
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def service_extension_names
|
|
248
|
+
%w[consul github http microsoft_teams nomad redis s3 tfe vault].freeze
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def other_extension_names
|
|
252
|
+
%w[chef elastic_app_search elasticsearch influxdb memcached pagerduty pushbullet pushover slack sleepiq smtp
|
|
253
|
+
sonos ssh todoist twilio].freeze
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def dev_agentic_names
|
|
257
|
+
%w[attention coldstart curiosity dream empathy flow habit memory metacognition mood narrator personality
|
|
258
|
+
reflection salience temporal tick volition].freeze
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def agentic_extension_names
|
|
262
|
+
known = core_extension_names + service_extension_names + other_extension_names + ai_extension_names
|
|
263
|
+
@extensions.keys.reject { |name| known.include?(name) }
|
|
264
|
+
end
|
|
265
|
+
|
|
211
266
|
def find_extensions
|
|
212
267
|
@extensions ||= {}
|
|
213
|
-
|
|
214
|
-
next unless gem
|
|
268
|
+
gem_names_for_discovery.each do |gem|
|
|
269
|
+
next unless gem.start_with?('lex-')
|
|
215
270
|
|
|
216
271
|
lex = gem.split('-')
|
|
217
272
|
@extensions[lex[1]] = { full_gem_name: gem,
|
|
@@ -221,6 +276,8 @@ module Legion
|
|
|
221
276
|
extension_class: "Legion::Extensions::#{lex[1].split('_').collect(&:capitalize).join}" }
|
|
222
277
|
end
|
|
223
278
|
|
|
279
|
+
apply_role_filter
|
|
280
|
+
|
|
224
281
|
enabled = 0
|
|
225
282
|
requested = 0
|
|
226
283
|
|
data/lib/legion/service.rb
CHANGED
|
@@ -122,12 +122,28 @@ module Legion
|
|
|
122
122
|
bind = api_settings[:bind] || '0.0.0.0'
|
|
123
123
|
|
|
124
124
|
@api_thread = Thread.new do
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
retries = 0
|
|
126
|
+
max_retries = api_settings.fetch(:bind_retries, 10)
|
|
127
|
+
retry_wait = api_settings.fetch(:bind_retry_wait, 3)
|
|
128
|
+
|
|
129
|
+
begin
|
|
130
|
+
Legion::API.set :port, port
|
|
131
|
+
Legion::API.set :bind, bind
|
|
132
|
+
Legion::API.set :server, :puma
|
|
133
|
+
Legion::API.set :environment, :production
|
|
134
|
+
Legion::Logging.info "Starting Legion API on #{bind}:#{port}"
|
|
135
|
+
Legion::API.run!(traps: false)
|
|
136
|
+
rescue Errno::EADDRINUSE
|
|
137
|
+
retries += 1
|
|
138
|
+
if retries <= max_retries
|
|
139
|
+
Legion::Logging.warn "Port #{port} in use, retrying in #{retry_wait}s (attempt #{retries}/#{max_retries})"
|
|
140
|
+
sleep retry_wait
|
|
141
|
+
retry
|
|
142
|
+
else
|
|
143
|
+
Legion::Logging.error "Port #{port} still in use after #{max_retries} attempts, API disabled"
|
|
144
|
+
Legion::Readiness.mark_not_ready(:api)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
131
147
|
end
|
|
132
148
|
Legion::Readiness.mark_ready(:api)
|
|
133
149
|
rescue LoadError => e
|
data/lib/legion/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legionio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0.8'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: bootsnap
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.18'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.18'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: concurrent-ruby
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|