legionio 1.9.43 → 1.9.50
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 +30 -0
- data/README.md +126 -569
- data/legionio.gemspec +5 -2
- data/lib/legion/api/auth_teams.rb +7 -4
- data/lib/legion/api/extensions.rb +2 -2
- data/lib/legion/api/health.rb +73 -0
- data/lib/legion/api/middleware/auth.rb +4 -1
- data/lib/legion/api/openapi.rb +8 -4
- data/lib/legion/api.rb +6 -1
- data/lib/legion/cli/chat/daemon_chat.rb +1 -1
- data/lib/legion/cli/chat/tools/model_comparison.rb +1 -1
- data/lib/legion/cli/chat_command.rb +1 -1
- data/lib/legion/cli/connect_command.rb +34 -9
- data/lib/legion/extensions/builders/routes.rb +1 -1
- data/lib/legion/service.rb +29 -0
- data/lib/legion/version.rb +1 -1
- metadata +9 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 836093ff32350c705da43ab5a36cf9b0e5e135e24556bf5b2c10c50df421ee11
|
|
4
|
+
data.tar.gz: ac682d87908c360cc90409e9fa773a5a8937245104394dbcd940fea67ca10141
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f350c4a919b2746bd5fcc72dfa22e03b4e30217a5d934a6fb50411903708473d394e8652b7bb09b27672ee1c93ed222c586a241d93f5aa40eaddae690521a312
|
|
7
|
+
data.tar.gz: e80c66310e5e90fc93c19cc3c672e4075dac237b27105a0ea3cc5c8cad5d295841bfeb0a893edaf34a1615b44b7fe843c6c060bdc69ef03086ccc200ced7e609
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.9.50] - 2026-07-15
|
|
4
|
+
### Fixed
|
|
5
|
+
- Backfill extension routes with API router after boot to fix /api/extensions/* 404s
|
|
6
|
+
- Fix CLI daemon_client require path after legion-llm restructure
|
|
7
|
+
## [1.9.48] - 2026-07-02
|
|
8
|
+
|
|
9
|
+
### Security
|
|
10
|
+
- API: fixed an authentication bypass in the auth middleware's `skip_path?` (CWE-284). It used bare prefix matching (`start_with?`), so any path sharing a prefix with an unauthenticated route bypassed auth — e.g. `/api/healthily_fake`, `/api/health/admin/export`, `/api/auth/tokens_dump` all matched `/api/health` / `/api/auth/token`. Now uses segment-bounded matching (exact path or `/`-delimited sub-path) via precompiled `SKIP_PATTERNS`, so only the intended paths and their legitimate sub-paths skip auth (fixes #209)
|
|
11
|
+
|
|
12
|
+
## [1.9.47] - 2026-07-02
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- API: `GET /api/health` now reflects subsystem health instead of always returning `200 ok`. It reports `degraded` with HTTP `503` when an **enabled and previously-healthy** subsystem (transport, cache, data) has broken — e.g. transport `session_open: false` after a connection recovery failure — and includes a per-component `components` breakdown (`enabled`/`healthy`/`detail`). A subsystem only degrades health if it was marked ready at boot (via `Legion::Readiness`); disabled or still-booting subsystems never fail the check. This makes the endpoint usable for load balancers and monitoring (fixes #194)
|
|
16
|
+
|
|
17
|
+
## [1.9.46] - 2026-07-02
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- API: `GET /api/extensions/tools` no longer 500s with `undefined method 'filter_tool_entries'`. The route block runs in the Sinatra instance context but `filter_tool_entries`/`serialize_tool_entry` are class methods on `Routes::Extensions`; they are now called on the explicit receiver, matching the pattern used by the other catalog routes. Added request specs for `/api/extensions/tools` (fixes #227)
|
|
21
|
+
|
|
22
|
+
## [1.9.45] - 2026-07-02
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- API: Teams delegated OAuth callback now forwards `tenant_id`/`client_id` to `Entra::Helpers::TokenManager.save_token(:delegated, …)` so browser-login tokens can be refreshed (previously omitted, forcing refresh to fall back to settings and silently fail for delegated-only logins). The unshipped `TokenCache` `require` that 500'd the callback was already removed in 1.9.43 (fixes #212)
|
|
26
|
+
|
|
27
|
+
## [1.9.44] - 2026-07-01
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- CLI: `connect status` now reads the Entra `TokenManager` (`token_data(:delegated)` + `expired?`) for the `microsoft` provider instead of the legacy `Legion::Auth::TokenManager` secret store, which the delegated/Teams login never writes to — previously always reported `microsoft: not connected` after a successful login (fixes #213)
|
|
31
|
+
- CLI: `connect microsoft` now forwards `--tenant_id`/`--client_id`/`--scope` (as `--scopes`) explicitly to the Teams auth flow instead of dropping flag values via `ARGV.select`
|
|
32
|
+
|
|
3
33
|
## [1.9.43] - 2026-06-25
|
|
4
34
|
|
|
5
35
|
### Fixed
|
data/README.md
CHANGED
|
@@ -1,621 +1,178 @@
|
|
|
1
1
|
<h1 align="center">LegionIO</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<b>
|
|
5
|
-
|
|
4
|
+
<b>A modular Ruby framework for async jobs and AI infrastructure.</b><br>
|
|
5
|
+
Start with a task engine that runs on zero infrastructure. Add LLM routing with
|
|
6
|
+
measured context curation, an MCP server, RBAC, a knowledge store, or an experimental
|
|
7
|
+
cognitive layer. Each is an independent gem. All of it is open source. Nothing is gated.
|
|
6
8
|
</p>
|
|
7
9
|
|
|
8
10
|
<p align="center">
|
|
9
11
|
<a href="https://rubygems.org/gems/legionio"><img alt="Gem Version" src="https://img.shields.io/gem/v/legionio.svg"></a>
|
|
10
12
|
<img alt="Ruby" src="https://img.shields.io/badge/ruby-%3E%3D%203.4-CC342D.svg">
|
|
11
13
|
<img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue.svg">
|
|
12
|
-
<img alt="HA" src="https://img.shields.io/badge/HA-no%20paid%20tiers%20·%20no%20feature%20gates-success.svg">
|
|
13
14
|
</p>
|
|
14
15
|
|
|
15
|
-
```
|
|
16
|
-
╭──────────────────────────────────────╮
|
|
17
|
-
│ L E G I O N I O │
|
|
18
|
-
│ │
|
|
19
|
-
│ async jobs · AI chat · MCP │
|
|
20
|
-
│ REST API · HA · cognitive AI │
|
|
21
|
-
│ zero-infra lite mode · Vault │
|
|
22
|
-
╰──────────────────────────────────────╯
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
> Schedule tasks, chain services into dependency graphs, run them concurrently across a RabbitMQ
|
|
26
|
-
> fleet, and orchestrate AI-powered workflows — from a single `legion` command. Then run the whole
|
|
27
|
-
> thing with **no RabbitMQ, no Redis, nothing** via lite mode.
|
|
28
|
-
|
|
29
|
-
## Why LegionIO
|
|
30
|
-
|
|
31
|
-
- 🧩 **Four products in one gem.** A RabbitMQ-backed async **job engine**, an **AI coding assistant** (chat, commit, review, PR, multi-agent), an **MCP server** that exposes your infrastructure to any agent, and a brain-modeled **cognitive platform** — all in one `gem install`.
|
|
32
|
-
- 🪶 **Zero-infrastructure lite mode.** `LEGION_MODE=lite` swaps RabbitMQ for in-process pub/sub and Redis/Memcached for an in-memory cache. Every feature still works — `gem install` to a running daemon in seconds, no services to stand up.
|
|
33
|
-
- 🔗 **Dependency-graph orchestration.** Chain tasks with JSON conditions and ERB transformations, fan out in parallel, and scale by simply launching more processes — RabbitMQ distributes the work automatically (tested to 100+ workers).
|
|
34
|
-
- 🤖 **AI workflows built in.** `legion chat`, `commit`, `review`, `pr`, multi-agent `swarm`, persistent cross-session memory, and a shared knowledge store — powered by [legion-llm](https://github.com/LegionIO/legion-llm)'s any-client → any-provider routing.
|
|
35
|
-
- 🧠 **Cognitive architecture.** 240+ brain-modeled extensions across 18 domains (emotion, reasoning, social, metacognition…), coordinated by a tick-cycle scheduler ([legion-gaia](https://github.com/LegionIO/legion-gaia)).
|
|
36
|
-
- 🔌 **MCP-native.** Exposes itself as an MCP server (stdio or streamable HTTP), so Claude Desktop or any agent SDK can run tasks, manage extensions, and query your infrastructure directly.
|
|
37
|
-
- 🛡️ **Operational from day one.** Vault secrets, AES-256 message encryption, RBAC, JWT / API-key auth, sliding-window rate limiting, Prometheus metrics, an OpenAPI 3.1 spec, and live `SIGHUP` reload. **No paid tiers, no feature gates, full HA out of the box.**
|
|
38
|
-
|
|
39
|
-
## The Legion Ecosystem
|
|
40
|
-
|
|
41
|
-
LegionIO is the orchestrator; the heavy lifting lives in a family of focused, independently-versioned gems. Here's the one-line version — follow a link to dig in:
|
|
42
|
-
|
|
43
|
-
| Gem | What it is |
|
|
44
|
-
|-----|-----------|
|
|
45
|
-
| [legion-llm](https://github.com/LegionIO/legion-llm) | Universal LLM proxy — any client dialect → any provider, with routing, escalation, and metering |
|
|
46
|
-
| [legion-gaia](https://github.com/LegionIO/legion-gaia) | Cognitive coordination layer — tick-cycle scheduler + weighted routing across cognitive modules |
|
|
47
|
-
| [legion-apollo](https://github.com/LegionIO/legion-apollo) | Shared + local knowledge store — RAG retrieval, embeddings, and a knowledge graph |
|
|
48
|
-
| [legion-data](https://github.com/LegionIO/legion-data) | Persistence — task history, scheduling, and chains over SQLite / PostgreSQL / MySQL |
|
|
49
|
-
| [legion-transport](https://github.com/LegionIO/legion-transport) | Messaging abstraction — RabbitMQ AMQP plus the in-process lite adapter |
|
|
50
|
-
| [legion-cache](https://github.com/LegionIO/legion-cache) | Caching abstraction — Redis / Memcached plus the in-memory lite adapter |
|
|
51
|
-
| [legion-crypt](https://github.com/LegionIO/legion-crypt) | Secrets & encryption — Vault integration, AES-256, JWT auth |
|
|
52
|
-
| [legion-rbac](https://github.com/LegionIO/legion-rbac) | Role-based access control with Vault-style flat policies |
|
|
53
|
-
| [legion-mcp](https://github.com/LegionIO/legion-mcp) | Model Context Protocol server/client implementation |
|
|
54
|
-
| [legion-settings](https://github.com/LegionIO/legion-settings) | Layered configuration + secret resolution (`vault://`, `env://`) |
|
|
55
|
-
| [legion-logging](https://github.com/LegionIO/legion-logging) | Structured logging used across every gem |
|
|
56
|
-
| [legion-tty](https://github.com/LegionIO/legion-tty) | Terminal UI components — spinners, tables, prompts |
|
|
57
|
-
|
|
58
|
-
Capabilities (`lex-*` extensions) are a separate, much larger catalog — see [Extensions](#extensions) below.
|
|
59
|
-
|
|
60
|
-
## What Does It Do?
|
|
61
|
-
|
|
62
|
-
LegionIO routes work between services asynchronously. Tasks chain into dependency graphs with conditions and transformations controlling data flow:
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
Task A ──→ [condition] ──→ Task B ──→ [transform] ──→ Task C
|
|
66
|
-
└──→ Task D (parallel)
|
|
67
|
-
└──→ Task E ──→ Task F
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
When A completes, B runs. B triggers C, D, and E in parallel. Conditions gate execution. Transformations reshape payloads between steps. Add more workers by running more processes — RabbitMQ handles distribution automatically.
|
|
71
|
-
|
|
72
|
-
But that's just the foundation. LegionIO is also:
|
|
73
|
-
|
|
74
|
-
- **An AI coding assistant** — interactive chat with tools, code review, commit messages, PR generation, and multi-agent workflows
|
|
75
|
-
- **An MCP server** — 60 tools that let any AI agent run tasks, manage extensions, and query your infrastructure
|
|
76
|
-
- **A cognitive computing platform** — 242 brain-modeled extensions across 18 cognitive domains
|
|
77
|
-
- **A digital worker platform** — AI-as-labor with governance, risk tiers, and cost tracking
|
|
78
|
-
|
|
79
|
-
## Quick Start
|
|
80
|
-
|
|
81
16
|
```bash
|
|
82
17
|
gem install legionio
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
|
99
|
-
|
|
100
|
-
| `legion` | Interactive TTY shell + dev-workflow commands (`chat`, `commit`, `review`, `plan`, `memory`, `init`) |
|
|
101
|
-
| `legionio` | Daemon lifecycle + all operational commands (`start`, `stop`, `lex`, `task`, `config`, `mcp`, and 40+ more) |
|
|
102
|
-
|
|
103
|
-
`legion` with no args drops into the interactive TTY shell. `legionio` is the full operational CLI.
|
|
104
|
-
|
|
105
|
-
## Installation
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
gem install legionio
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Or add to your Gemfile:
|
|
112
|
-
|
|
113
|
-
```ruby
|
|
114
|
-
gem 'legionio'
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Optional Capabilities
|
|
118
|
-
|
|
119
|
-
| Gem | What It Unlocks |
|
|
120
|
-
|-----|-----------------|
|
|
121
|
-
| `legion-data` | Task history, scheduling, chains (SQLite/PostgreSQL/MySQL) |
|
|
122
|
-
| `legion-llm` | AI chat, commit, review, agents, multi-provider LLM routing |
|
|
123
|
-
| `legion-cache` | Redis/Memcached caching for extensions |
|
|
124
|
-
| `legion-crypt` | Vault integration, encryption, JWT auth |
|
|
125
|
-
| `legion-tty` | TTY UI components (spinners, tables, prompts) |
|
|
126
|
-
|
|
127
|
-
## Zero-Infrastructure Mode (Lite)
|
|
128
|
-
|
|
129
|
-
Run LegionIO without RabbitMQ, Redis, or Memcached:
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
LEGION_MODE=lite legion start # environment variable
|
|
133
|
-
legion start --lite # CLI flag
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
In lite mode, `legion-transport` uses an in-process pub/sub adapter (no RabbitMQ required) and `legion-cache` uses a pure in-memory store with TTL (no Redis/Memcached required). All extensions and features work normally. Useful for single-machine development, CI, and trying LegionIO with no infrastructure.
|
|
137
|
-
|
|
138
|
-
## Natural Language Intent Router
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
legion do "list all running tasks"
|
|
142
|
-
legion do "start the email extension"
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
`legion do` routes free-text to the Capability Registry. Routes through the running daemon (MCP Tier 0 fast path) when available, or runs in-process otherwise.
|
|
146
|
-
|
|
147
|
-
## Infrastructure
|
|
148
|
-
|
|
149
|
-
| Component | Role | Required? |
|
|
150
|
-
|-----------|------|-----------|
|
|
151
|
-
| **RabbitMQ** | Task distribution (AMQP 0.9.1) | No (lite mode replaces with InProcess adapter) |
|
|
152
|
-
| **SQLite/PostgreSQL/MySQL** | Persistence (tasks, scheduling, chains) | Optional |
|
|
153
|
-
| **Redis/Memcached** | Extension caching | No (lite mode replaces with Memory adapter) |
|
|
154
|
-
| **HashiCorp Vault** | Secrets, PKI, encrypted settings | Optional |
|
|
155
|
-
|
|
156
|
-
## The CLI
|
|
157
|
-
|
|
158
|
-
Operational commands run through `legionio`. Dev-workflow and AI commands run through `legion`.
|
|
159
|
-
|
|
160
|
-
### Daemon & Health
|
|
161
|
-
|
|
162
|
-
```bash
|
|
163
|
-
legionio start # foreground
|
|
164
|
-
legionio start -d # daemonize
|
|
165
|
-
legionio start --http-port 8080 # custom API port
|
|
166
|
-
legionio status # service status
|
|
167
|
-
legionio stop # graceful shutdown
|
|
168
|
-
legionio check # smoke-test all connections
|
|
169
|
-
legionio check --extensions # also verify extensions
|
|
170
|
-
legionio check --full # full boot including API
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Extensions (LEX)
|
|
174
|
-
|
|
175
|
-
Extensions are gems named `lex-*`, auto-discovered at startup:
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
legion lex list # installed extensions
|
|
179
|
-
legion lex info <name> # runners, actors, dependencies
|
|
180
|
-
legion lex create <name> # scaffold a new extension
|
|
181
|
-
legion lex enable <name> # enable / disable
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Tasks
|
|
185
|
-
|
|
186
|
-
```bash
|
|
187
|
-
legion task run http.request.get url:https://example.com # dot notation
|
|
188
|
-
legion task run -e http -r request -f get # explicit flags
|
|
189
|
-
legion task run # interactive picker
|
|
190
|
-
legion task list # recent tasks
|
|
191
|
-
legion task show <id> # detail + logs
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### AI Chat
|
|
195
|
-
|
|
196
|
-
An interactive AI coding assistant with project awareness, persistent memory, tool use, and multi-agent coordination. Requires `legion-llm`.
|
|
197
|
-
|
|
198
|
-
```bash
|
|
199
|
-
legion chat # interactive REPL
|
|
200
|
-
legion chat prompt "explain main.rb" # single-prompt mode
|
|
201
|
-
echo "fix the bug" | legion chat prompt - # pipe from stdin
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
**40 built-in tools**: read_file, write_file, edit_file, search_files, search_content, run_command, save_memory, search_memory, web_search, spawn_agent, search_traces, query_knowledge, ingest_knowledge, consolidate_memory, relate_knowledge, knowledge_maintenance, knowledge_stats, summarize_traces, list_extensions, manage_tasks, system_status, view_events, cost_summary, reflect, manage_schedules, worker_status, detect_anomalies, view_trends, trigger_dream, generate_insights, budget_status, provider_health, model_comparison, shadow_eval_status, entity_extract, arbitrage_status, escalation_status, graph_explore, scheduling_status, memory_status
|
|
205
|
-
|
|
206
|
-
**Slash commands**: `/help` `/quit` `/cost` `/status` `/clear` `/new` `/save` `/load` `/sessions` `/compact` `/fetch URL` `/search QUERY` `/diff` `/copy` `/rewind` `/memory` `/agent` `/agents` `/plan` `/swarm` `/review` `/permissions` `/personality` `/model` `/edit` `/commit` `/workers` `/dream`
|
|
207
|
-
|
|
208
|
-
**Bang commands**: `!ls -la` — run shell commands with output injected into context
|
|
209
|
-
|
|
210
|
-
**At-mentions**: `@reviewer check main.rb` — delegate to custom agents in `.legion/agents/`
|
|
211
|
-
|
|
212
|
-
### AI Workflows
|
|
213
|
-
|
|
214
|
-
```bash
|
|
215
|
-
legion commit # AI-generated commit message
|
|
216
|
-
legion pr # AI-generated PR title + description
|
|
217
|
-
legion pr --base develop --draft # target branch, draft mode
|
|
218
|
-
legion review # AI code review of staged changes
|
|
219
|
-
legion review src/main.rb # review specific files
|
|
220
|
-
legion review --diff # review uncommitted diff
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### Multi-Agent Orchestration
|
|
224
|
-
|
|
225
|
-
```bash
|
|
226
|
-
legion plan # read-only exploration mode (AI reasons, no writes)
|
|
227
|
-
legion swarm start deploy-pipeline # run multi-agent workflow
|
|
228
|
-
legion swarm list # available workflows
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Memory
|
|
232
|
-
|
|
233
|
-
Persistent project and global memory that survives across sessions:
|
|
234
|
-
|
|
235
|
-
```bash
|
|
236
|
-
legion memory list # project memories
|
|
237
|
-
legion memory add "always use rspec"
|
|
238
|
-
legion memory search "testing"
|
|
239
|
-
legion memory forget 3
|
|
240
|
-
```
|
|
18
|
+
LEGION_MODE=lite legion start # no RabbitMQ, no Redis, no database required
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## What this is
|
|
22
|
+
|
|
23
|
+
LegionIO is a distributed async job engine (RabbitMQ-backed, in the Sidekiq/Celery
|
|
24
|
+
family) that chains tasks into dependency graphs, plus a set of optional layers that
|
|
25
|
+
install as separate gems:
|
|
26
|
+
|
|
27
|
+
| Layer | Gem | What it adds | Requires |
|
|
28
|
+
|-------|-----|--------------|----------|
|
|
29
|
+
| Task engine | [legionio](https://github.com/LegionIO/LegionIO) | Task chains, scheduling, 8 actor types, CLI, REST API | nothing (lite mode) |
|
|
30
|
+
| LLM gateway | [legion-llm](https://github.com/LegionIO/legion-llm) | Any-client-to-any-provider routing, tiered escalation, mid-stream failover, context curation, fleet dispatch to pooled workstation GPUs | usable standalone |
|
|
31
|
+
| MCP server | [legion-mcp](https://github.com/LegionIO/legion-mcp) | Exposes tasks and extensions as MCP tools (stdio or HTTP) | legion-llm |
|
|
32
|
+
| Knowledge store | [legion-apollo](https://github.com/LegionIO/legion-apollo) | RAG retrieval, embeddings, confidence-decayed knowledge | activated by lex-apollo / lex-knowledge |
|
|
33
|
+
| Access control | [legion-rbac](https://github.com/LegionIO/legion-rbac) | Vault-style flat policies | usable standalone |
|
|
34
|
+
| Cognitive layer | [legion-gaia](https://github.com/LegionIO/legion-gaia) | Experimental: tick-cycle scheduling over agentic extensions | see "The experimental part" below |
|
|
241
35
|
|
|
242
|
-
|
|
36
|
+
The composition rules are simple and enforced by gemspecs, not documentation: every
|
|
37
|
+
layer is optional, dependencies between layers are declared where they exist
|
|
38
|
+
(legion-mcp depends on legion-llm; Apollo does little until a lex-* extension activates
|
|
39
|
+
it), and installing a core gem you don't use adds a contract, not overhead. You can run
|
|
40
|
+
the LLM gateway without the task engine, the task engine without any AI, or the whole
|
|
41
|
+
stack together.
|
|
243
42
|
|
|
244
|
-
|
|
43
|
+
There are no paid tiers, no enterprise editions, and no feature gates. RBAC, the audit
|
|
44
|
+
ledger, identity integration, and every operational control ship in the open-source
|
|
45
|
+
gems, because there is no commercial version for them to be held back for.
|
|
245
46
|
|
|
246
|
-
|
|
247
|
-
legion knowledge query "how does transport routing work?"
|
|
248
|
-
legion knowledge retrieve "embedding cosine similarity" --scope global
|
|
249
|
-
legion knowledge ingest /path/to/docs/
|
|
250
|
-
legion knowledge status # index stats, embedding coverage
|
|
251
|
-
legion knowledge health # detect orphans, quality metrics
|
|
252
|
-
legion knowledge maintain # cleanup orphans, reindex
|
|
253
|
-
legion knowledge quality # quality report
|
|
254
|
-
```
|
|
47
|
+
## The part most worth your skepticism budget: legion-llm
|
|
255
48
|
|
|
256
|
-
|
|
49
|
+
legion-llm is a proxy/gateway between AI clients and model backends, in the same
|
|
50
|
+
category as LiteLLM or OpenRouter, with one addition neither has: automatic context
|
|
51
|
+
curation for long agent sessions.
|
|
257
52
|
|
|
258
|
-
|
|
53
|
+
**Routing.** Every model is classified into a tier:
|
|
54
|
+
[`local(0) → direct(1) → fleet(2) → cloud(3) → frontier(4)`](https://github.com/LegionIO/legion-llm/blob/main/lib/legion/llm/router.rb).
|
|
55
|
+
Requests try the cheapest capable tier first and escalate on failure or capability
|
|
56
|
+
mismatch. A [health tracker](https://github.com/LegionIO/legion-llm/blob/main/lib/legion/llm/router/health_tracker.rb)
|
|
57
|
+
(300s window, 3 failures trips the circuit, 60s cooldown) keys availability per
|
|
58
|
+
provider instance, and a provider dying mid-stream fails over and continues the stream
|
|
59
|
+
rather than erroring the client.
|
|
259
60
|
|
|
260
|
-
|
|
261
|
-
legion
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
legion mind-growth build <id> # run staged build pipeline
|
|
266
|
-
legion mind-growth list # list proposals
|
|
267
|
-
legion mind-growth approve <id> # manually approve
|
|
268
|
-
legion mind-growth reject <id> # manually reject
|
|
269
|
-
legion mind-growth profile # cognitive profile across all models
|
|
270
|
-
legion mind-growth health # extension fitness validation
|
|
271
|
-
```
|
|
61
|
+
**Curation.** After each turn (async, off the request path), the
|
|
62
|
+
[Curator](https://github.com/LegionIO/legion-llm/blob/main/lib/legion/llm/context/curator.rb)
|
|
63
|
+
shrinks accumulated history with six deterministic strategies: `strip_thinking`,
|
|
64
|
+
`distill_tool_result`, `fold_resolved_exchanges`, `evict_superseded`, `dedup_similar`,
|
|
65
|
+
and `drop_and_archive` (overflow goes to the knowledge store, not the trash).
|
|
272
66
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
### Digital Workers
|
|
276
|
-
|
|
277
|
-
AI-as-labor with governance, risk tiers, and cost tracking:
|
|
278
|
-
|
|
279
|
-
```bash
|
|
280
|
-
legion worker list # list workers
|
|
281
|
-
legion worker show <id> # worker detail
|
|
282
|
-
legion worker create <name> # register new worker (bootstrap state)
|
|
283
|
-
legion worker pause <id> # pause / activate / retire
|
|
284
|
-
legion worker costs --days 30 # cost report
|
|
285
|
-
```
|
|
67
|
+
**Measured, including where it does nothing.** Production ledger aggregates, all
|
|
68
|
+
requests, by conversation length:
|
|
286
69
|
|
|
287
|
-
|
|
70
|
+
| Turns | 1 | 2–3 | 4–5 | 6–9 | 10–19 | 20–49 | 50+ |
|
|
71
|
+
|-------|---|-----|-----|-----|-------|-------|-----|
|
|
72
|
+
| Reduction vs. naive full-history resend | -0.1% | 9.6% | 13.3% | 23.6% | 54.3% | 72.8% | **97.7%** |
|
|
288
73
|
|
|
289
|
-
|
|
74
|
+
Single-turn workloads gain nothing; long agent sessions go from an average 1.13M
|
|
75
|
+
naive tokens per turn to ~26K. The asymmetry is the point: curation doesn't make cheap
|
|
76
|
+
traffic cheaper, it bounds the runaway sessions where cost concentrates. Methodology,
|
|
77
|
+
baseline definition, raw numbers, and caveats:
|
|
78
|
+
[curation-production-metrics.md](https://github.com/LegionIO/legion-llm/blob/main/docs/curation-production-metrics.md).
|
|
290
79
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
legion
|
|
295
|
-
```
|
|
80
|
+
Nine provider adapters exist today (Anthropic, OpenAI, Bedrock, Gemini, Vertex,
|
|
81
|
+
Azure Foundry, Ollama, vLLM, MLX), each its own gem built on the
|
|
82
|
+
[lex-llm](https://github.com/LegionIO/lex-llm) contract layer. lex-llm defines what a
|
|
83
|
+
provider is; legion-llm decides where traffic goes. Install only the adapters you use.
|
|
296
84
|
|
|
297
|
-
|
|
85
|
+
## The job engine
|
|
298
86
|
|
|
299
|
-
|
|
87
|
+
The original core, in production since before any of the AI layers existed:
|
|
300
88
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
89
|
+
- Task chains with conditions and transformations:
|
|
90
|
+
`Task A → [condition] → Task B → [transform] → Task C`, with parallel fan-out.
|
|
91
|
+
- Eight actor types (subscription, poll, every, once, loop, singleton, nothing,
|
|
92
|
+
absorber_dispatch) — see
|
|
93
|
+
[lib/legion/extensions/actors/](https://github.com/LegionIO/LegionIO/tree/main/lib/legion/extensions/actors).
|
|
94
|
+
- Distributed cron scheduling with interval locking.
|
|
95
|
+
- A JSONL disk spool that buffers messages through AMQP outages.
|
|
96
|
+
- Scale by starting more processes; RabbitMQ distributes the work.
|
|
306
97
|
|
|
307
|
-
|
|
98
|
+
`LEGION_MODE=lite` swaps RabbitMQ for in-process pub/sub and Redis for an in-memory
|
|
99
|
+
cache. Every feature works; nothing external is required. This is the recommended way
|
|
100
|
+
to evaluate the framework, and it takes about five minutes.
|
|
308
101
|
|
|
309
|
-
|
|
310
|
-
legion config show # resolved config (redacted)
|
|
311
|
-
legion config validate # verify settings + subsystem health
|
|
312
|
-
legion config scaffold # generate starter config files (auto-detects env vars)
|
|
313
|
-
```
|
|
102
|
+
## The experimental part, labeled as such
|
|
314
103
|
|
|
315
|
-
|
|
104
|
+
The `lex-agentic-*` gems are a research layer: 16 gems containing 369 actor and runner
|
|
105
|
+
modules that model cognition-inspired behaviors on top of the job engine. The honest
|
|
106
|
+
mechanical description is that each is a scheduled job or subscription that adjusts
|
|
107
|
+
persistent state. The interesting research idea is what that state does: task-routing
|
|
108
|
+
connections strengthen when chains succeed and
|
|
109
|
+
[decay](https://github.com/LegionIO/lex-synapse) when unused, so frequently-successful
|
|
110
|
+
paths get cheaper to select. A
|
|
111
|
+
[16-phase tick cycle](https://github.com/LegionIO/lex-tick/blob/main/lib/legion/extensions/tick/helpers/constants.rb)
|
|
112
|
+
schedules this work in budgeted modes (dormant 0.2s, sentinel 0.5s, full 5.0s), and a
|
|
113
|
+
10-phase idle-time cycle consolidates memory and feeds what it learns about your usage
|
|
114
|
+
back into RAG retrieval for the LLM layer.
|
|
316
115
|
|
|
317
|
-
|
|
116
|
+
Whether this framing earns its keep is an open question we're running in production to
|
|
117
|
+
answer. If you don't install these gems, none of this exists in your deployment.
|
|
318
118
|
|
|
319
|
-
|
|
119
|
+
## Interfaces
|
|
320
120
|
|
|
321
121
|
```bash
|
|
322
|
-
legion
|
|
323
|
-
legion
|
|
324
|
-
legion
|
|
325
|
-
legion
|
|
326
|
-
|
|
122
|
+
legion start # the engine
|
|
123
|
+
legion chat # agentic REPL with tools, memory, subagents
|
|
124
|
+
legion task run http.request.get url:https://example.com
|
|
125
|
+
legion mcp # MCP server over stdio or streamable HTTP
|
|
126
|
+
curl http://localhost:4567/api/v1/tasks # REST API (OpenAPI 3.1 spec in legionio-spec)
|
|
327
127
|
```
|
|
328
128
|
|
|
329
|
-
|
|
129
|
+
Every CLI command supports `--json`. MCP tools are discovered at runtime from
|
|
130
|
+
installed extensions, so the tool list reflects what your deployment can actually do.
|
|
330
131
|
|
|
331
|
-
|
|
332
|
-
legion audit list # query audit log
|
|
333
|
-
legion audit export --format csv
|
|
334
|
-
legion rbac roles # list roles
|
|
335
|
-
legion rbac check <identity> <resource> <action>
|
|
336
|
-
```
|
|
132
|
+
## Verify the claims yourself
|
|
337
133
|
|
|
338
|
-
|
|
134
|
+
Every number above is reproducible from public source. A few one-liners:
|
|
339
135
|
|
|
340
136
|
```bash
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
```
|
|
137
|
+
# tick and dream phase counts (16 and 10)
|
|
138
|
+
git clone https://github.com/LegionIO/lex-tick && \
|
|
139
|
+
grep -A20 'PHASES = ' lex-tick/lib/legion/extensions/tick/helpers/constants.rb
|
|
345
140
|
|
|
346
|
-
|
|
141
|
+
# legion-llm test surface (269 spec files, ~3,050 examples)
|
|
142
|
+
git clone https://github.com/LegionIO/legion-llm && \
|
|
143
|
+
find legion-llm/spec -name '*_spec.rb' | wc -l
|
|
347
144
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
legion update # update all legion gems in-place
|
|
352
|
-
legion update --dry-run # check what's available without installing
|
|
145
|
+
# router tiers and circuit-breaker defaults
|
|
146
|
+
grep -n 'TIER_RANK\|failure_threshold' legion-llm/lib/legion/llm/router.rb \
|
|
147
|
+
legion-llm/lib/legion/llm/router/health_tracker.rb
|
|
353
148
|
```
|
|
354
149
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
The daemon exposes a REST API on port 4567 (configurable):
|
|
362
|
-
|
|
363
|
-
| Route | Description |
|
|
364
|
-
|-------|-------------|
|
|
365
|
-
| `GET /api/health` | Health check |
|
|
366
|
-
| `GET /api/ready` | Readiness + component status |
|
|
367
|
-
| `GET/POST /api/tasks` | List / create tasks |
|
|
368
|
-
| `GET /api/extensions` | Installed extensions + runners |
|
|
369
|
-
| `GET /api/nodes` | Cluster nodes |
|
|
370
|
-
| `GET/POST/PUT/DELETE /api/schedules` | Cron / interval scheduling |
|
|
371
|
-
| `GET /api/settings` | Config (sensitive values redacted) |
|
|
372
|
-
| `GET /api/transport` | RabbitMQ connection status |
|
|
373
|
-
| `GET /api/events` | SSE event stream |
|
|
374
|
-
| `GET/POST/PUT/DELETE /api/workers` | Digital worker lifecycle |
|
|
375
|
-
| `GET /api/capacity` | Workforce capacity and forecasting |
|
|
376
|
-
| `GET /api/tenants` | Multi-tenant management |
|
|
377
|
-
| `GET /api/audit` | Audit log query and export |
|
|
378
|
-
| `GET /api/rbac/*` | Role-based access control |
|
|
379
|
-
| `GET /api/webhooks` | Webhook subscription management |
|
|
380
|
-
| `GET /api/openapi.json` | OpenAPI 3.1.0 specification |
|
|
381
|
-
| `GET /metrics` | Prometheus metrics |
|
|
382
|
-
| `POST /api/coldstart/ingest` | Context ingestion |
|
|
383
|
-
|
|
384
|
-
```json
|
|
385
|
-
{
|
|
386
|
-
"data": { "..." },
|
|
387
|
-
"meta": { "timestamp": "2026-03-15T12:00:00Z", "node": "legion-01" }
|
|
388
|
-
}
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
## MCP Server
|
|
392
|
-
|
|
393
|
-
LegionIO exposes itself as an [MCP](https://modelcontextprotocol.io/) server, letting any AI agent run tasks, manage extensions, and query infrastructure directly.
|
|
394
|
-
|
|
395
|
-
```bash
|
|
396
|
-
legion mcp # stdio transport (Claude Desktop, agent SDKs)
|
|
397
|
-
legion mcp http # streamable HTTP on localhost:9393
|
|
398
|
-
legion mcp http --port 8080 --host 0.0.0.0
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
**60 tools** in the `legion.*` namespace:
|
|
402
|
-
|
|
403
|
-
| Category | Tools |
|
|
404
|
-
|----------|-------|
|
|
405
|
-
| **Agentic** | `run_task`, `describe_runner` |
|
|
406
|
-
| **Tasks** | `list_tasks`, `get_task`, `delete_task`, `get_task_logs` |
|
|
407
|
-
| **Extensions** | `list_extensions`, `get_extension`, `enable_extension`, `disable_extension` |
|
|
408
|
-
| **Chains** | `list_chains`, `create_chain`, `update_chain`, `delete_chain` |
|
|
409
|
-
| **Relationships** | `list_relationships`, `create_relationship`, `update_relationship`, `delete_relationship` |
|
|
410
|
-
| **Schedules** | `list_schedules`, `create_schedule`, `update_schedule`, `delete_schedule` |
|
|
411
|
-
| **System** | `get_status`, `get_config` |
|
|
412
|
-
| **Workers** | `list_workers`, `show_worker`, `worker_lifecycle`, `worker_costs`, `team_summary` |
|
|
413
|
-
| **RBAC** | `rbac_assignments`, `rbac_check`, `rbac_grants` |
|
|
414
|
-
| **Analytics** | `routing_stats` |
|
|
415
|
-
| **Knowledge** | `query_knowledge`, `knowledge_health` |
|
|
416
|
-
| **Mind Growth** | `mind_growth_status`, `mind_growth_analyze`, `mind_growth_propose`, `mind_growth_evaluate`, `mind_growth_build`, `mind_growth_profile` |
|
|
417
|
-
|
|
418
|
-
**Resources**: `legion://runners` (full runner catalog), `legion://extensions/{name}` (extension detail)
|
|
419
|
-
|
|
420
|
-
## Task Relationships
|
|
421
|
-
|
|
422
|
-
### Conditions
|
|
423
|
-
|
|
424
|
-
JSON rule engine via `lex-conditioner`. Supports nested `all`/`any` with operators:
|
|
425
|
-
|
|
426
|
-
```json
|
|
427
|
-
{
|
|
428
|
-
"all": [
|
|
429
|
-
{"fact": "pet.type", "value": "dog", "operator": "equal"},
|
|
430
|
-
{"fact": "pet.hungry", "operator": "is_true"}
|
|
431
|
-
]
|
|
432
|
-
}
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
### Transformations
|
|
436
|
-
|
|
437
|
-
ERB templates via `lex-transformer`. Map data between services:
|
|
438
|
-
|
|
439
|
-
```json
|
|
440
|
-
{"message": "Incident assigned to <%= assignee %> with priority <%= severity %>"}
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
Access Vault secrets inline: `<%= Legion::Crypt.read('pushover/token') %>`
|
|
444
|
-
|
|
445
|
-
## Extensions
|
|
446
|
-
|
|
447
|
-
Browse: [LegionIO GitHub](https://github.com/LegionIO) | [legionio topic](https://github.com/topics/legionio?l=ruby)
|
|
448
|
-
|
|
449
|
-
### Core (14 operational extensions)
|
|
450
|
-
|
|
451
|
-
`lex-node` `lex-tasker` `lex-conditioner` `lex-transformer` `lex-scheduler` `lex-health` `lex-log` `lex-ping` `lex-exec` `lex-lex` `lex-codegen` `lex-metering` `lex-telemetry` `lex-task_pruner`
|
|
452
|
-
|
|
453
|
-
### Agentic (242 cognitive extensions)
|
|
454
|
-
|
|
455
|
-
Brain-modeled cognitive architecture. 20 core orchestration extensions plus 222 expanded modules across 18 domains:
|
|
456
|
-
|
|
457
|
-
| Domain | Examples |
|
|
458
|
-
|--------|----------|
|
|
459
|
-
| **Orchestration** | `lex-tick`, `lex-cortex`, `lex-dream`, `lex-memory`, `lex-identity` |
|
|
460
|
-
| **Emotion** | `lex-emotion`, `lex-mood`, `lex-empathy` |
|
|
461
|
-
| **Reasoning** | `lex-prediction`, `lex-planning`, `lex-logic` |
|
|
462
|
-
| **Social** | `lex-trust`, `lex-consent`, `lex-governance` |
|
|
463
|
-
| **Metacognition** | `lex-reflection`, `lex-awareness`, `lex-curiosity` |
|
|
464
|
-
|
|
465
|
-
Coordinated by [legion-gaia](https://github.com/LegionIO/legion-gaia), the cognitive coordination layer with tick-cycle scheduling, channel abstraction, and weighted routing across cognitive modules.
|
|
150
|
+
Engineering docs are public in each repo, including the
|
|
151
|
+
[router design doc](https://github.com/LegionIO/legion-llm/tree/main/docs/work/planning)
|
|
152
|
+
and the [debugging methodology](https://github.com/LegionIO/legion-llm/blob/main/docs/work/planning/nxn-debugging-method.md)
|
|
153
|
+
the routing layer is held to. They are the most accurate picture of how this project
|
|
154
|
+
is actually built.
|
|
466
155
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
`legion-llm` `lex-llm` `lex-llm-anthropic` `lex-llm-azure-foundry` `lex-llm-bedrock` `lex-llm-gemini` `lex-llm-ledger` `lex-llm-mlx` `lex-llm-ollama` `lex-llm-openai` `lex-llm-vertex` `lex-llm-vllm`
|
|
470
|
-
|
|
471
|
-
Powered by [legion-llm](https://github.com/LegionIO/legion-llm) with provider-neutral model offerings, local and fleet routing, hosted cloud providers, health tracking, metering, and automatic model discovery.
|
|
472
|
-
|
|
473
|
-
LLM API routes are mounted from `legion-llm` when available; LegionIO only hosts those route modules and does not provide a provider gateway fallback.
|
|
474
|
-
|
|
475
|
-
### Service Integrations (8 common + 15 additional)
|
|
476
|
-
|
|
477
|
-
**Common**: `lex-http` `lex-redis` `lex-s3` `lex-github` `lex-consul` `lex-tfe` `lex-vault` `lex-kerberos` `lex-microsoft_teams`
|
|
478
|
-
|
|
479
|
-
**Additional**: `lex-ssh` `lex-slack` `lex-smtp` `lex-influxdb` `lex-pagerduty` `lex-elasticsearch` `lex-chef` `lex-pushover` `lex-twilio` `lex-todoist` `lex-pushbullet` `lex-sleepiq` `lex-elastic_app_search` `lex-memcached` `lex-sonos`
|
|
480
|
-
|
|
481
|
-
### Build Your Own
|
|
482
|
-
|
|
483
|
-
```bash
|
|
484
|
-
legion lex create myextension
|
|
485
|
-
cd lex-myextension
|
|
486
|
-
legion generate runner myrunner
|
|
487
|
-
legion generate actor myactor
|
|
488
|
-
bundle exec rspec
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
## Role Profiles
|
|
492
|
-
|
|
493
|
-
Control which extensions load at startup via `settings/legion.json`:
|
|
494
|
-
|
|
495
|
-
```json
|
|
496
|
-
{"role": {"profile": "dev"}}
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
| Profile | What loads |
|
|
500
|
-
|---------|-----------|
|
|
501
|
-
| *(default)* | Everything — no filtering |
|
|
502
|
-
| `core` | 14 core operational extensions only |
|
|
503
|
-
| `cognitive` | core + all agentic extensions |
|
|
504
|
-
| `service` | core + service + other integrations |
|
|
505
|
-
| `dev` | core + native LLM providers + essential agentic (~20 extensions) |
|
|
506
|
-
| `custom` | only what's listed in `role.extensions` |
|
|
507
|
-
|
|
508
|
-
Faster boot and lower memory footprint for dedicated worker roles.
|
|
509
|
-
|
|
510
|
-
## Scaling
|
|
511
|
-
|
|
512
|
-
Task distribution uses RabbitMQ FIFO queues. Add workers by running more Legion processes — each subscribes to the same queues and picks up work automatically. Tested to 100+ workers.
|
|
513
|
-
|
|
514
|
-
Run different LEX combinations per worker: 10 pods focused on `lex-ssh`, a separate pod for `lex-pagerduty` + `lex-log` notifications.
|
|
515
|
-
|
|
516
|
-
No paid tiers. No feature gates. Full HA out of the box.
|
|
517
|
-
|
|
518
|
-
## Security
|
|
519
|
-
|
|
520
|
-
- **Message encryption**: AES-256-CBC via `legion-crypt`
|
|
521
|
-
- **Vault integration**: Secrets, PKI, encrypted settings
|
|
522
|
-
- **Node identity**: Each worker generates a keypair for inter-node communication
|
|
523
|
-
- **Cluster secret**: Generated at first startup, distributed via Vault or in-memory
|
|
524
|
-
- **JWT auth**: Bearer token authentication on the REST API
|
|
525
|
-
- **API key support**: `X-API-Key` header authentication
|
|
526
|
-
- **RBAC**: Role-based access control with Vault-style flat policies
|
|
527
|
-
- **Rate limiting**: Sliding-window per-IP/agent/tenant rate limiting
|
|
528
|
-
- **API versioning**: `/api/v1/` prefix with deprecation headers
|
|
529
|
-
- **Kerberos**: SPNEGO/GSSAPI authentication with LDAP group resolution
|
|
530
|
-
|
|
531
|
-
## Docker
|
|
532
|
-
|
|
533
|
-
```bash
|
|
534
|
-
docker pull legionio/legion
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
```dockerfile
|
|
538
|
-
FROM ruby:3-alpine
|
|
539
|
-
RUN gem install legionio
|
|
540
|
-
CMD ruby --yjit $(which legion) start
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
## Architecture
|
|
544
|
-
|
|
545
|
-
Before any Legion code loads, the executable applies performance optimizations:
|
|
546
|
-
|
|
547
|
-
- **YJIT** — `RubyVM::YJIT.enable` for 15-30% runtime throughput (Ruby 3.1+ builds)
|
|
548
|
-
- **GC tuning** — pre-allocates 600k heap slots and raises malloc limits (ENV overrides respected)
|
|
549
|
-
- **bootsnap** *(opt-in)* — set `LEGION_BOOTSNAP=true` to cache YARV bytecode and `$LOAD_PATH` resolution at `~/.legionio/cache/bootsnap/`
|
|
550
|
-
|
|
551
|
-
```
|
|
552
|
-
legion start
|
|
553
|
-
└── Legion::Service
|
|
554
|
-
├── 1. Logging (legion-logging)
|
|
555
|
-
├── 2. Settings (legion-settings — /etc/legionio, ~/legionio, ./settings)
|
|
556
|
-
├── 3. Crypt (legion-crypt — Vault connection)
|
|
557
|
-
├── 4. Transport (legion-transport — RabbitMQ)
|
|
558
|
-
├── 5. Cache (legion-cache — Redis/Memcached)
|
|
559
|
-
├── 6. Data (legion-data — database + migrations)
|
|
560
|
-
├── 7. RBAC (legion-rbac — optional role-based access control)
|
|
561
|
-
├── 8. LLM (legion-llm — AI provider setup + routing)
|
|
562
|
-
├── 9. Apollo (legion-apollo — shared/local knowledge store)
|
|
563
|
-
├── 10. GAIA (legion-gaia — cognitive coordination layer)
|
|
564
|
-
├── 11. Telemetry (OpenTelemetry — optional)
|
|
565
|
-
├── 12. Supervision (process supervision)
|
|
566
|
-
├── 13. Extensions (two-phase parallel load: require+autobuild, then hook_all_actors)
|
|
567
|
-
├── 14. Cluster Secret (distribute via Vault or memory)
|
|
568
|
-
└── 15. API (Sinatra/Puma on port 4567)
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
Each phase registers with `Legion::Readiness`. All phases are individually toggleable.
|
|
572
|
-
|
|
573
|
-
`SIGHUP` triggers a live reload (`Legion.reload`) — subsystems shut down in reverse order and restart fresh without killing the process. Useful for rolling restarts and config changes.
|
|
574
|
-
|
|
575
|
-
## Similar Projects
|
|
576
|
-
|
|
577
|
-
| Project | Language | HA | AI | Cognitive |
|
|
578
|
-
|---------|----------|----|----|-----------|
|
|
579
|
-
| **LegionIO** | Ruby | Yes | Chat, MCP, agents, LLM routing | 242 extensions |
|
|
580
|
-
| [Node-RED](https://nodered.org/) | JS | No | No | No |
|
|
581
|
-
| [n8n.io](https://n8n.io/) | TS | Limited | Limited | No |
|
|
582
|
-
| [StackStorm](https://stackstorm.com/) | Python | Yes | No | No |
|
|
583
|
-
| [Huginn](https://github.com/huginn/huginn) | Ruby | No | No | No |
|
|
584
|
-
|
|
585
|
-
## Development
|
|
586
|
-
|
|
587
|
-
```bash
|
|
588
|
-
git clone https://github.com/LegionIO/LegionIO.git
|
|
589
|
-
cd LegionIO
|
|
590
|
-
bundle install
|
|
591
|
-
bundle exec rspec # ~3500+ examples, 0 failures
|
|
592
|
-
bundle exec rubocop # 0 offenses
|
|
593
|
-
```
|
|
156
|
+
## Project status, honestly
|
|
594
157
|
|
|
595
|
-
|
|
158
|
+
LegionIO is built primarily by one engineer, with a disciplined process: PR-based flow,
|
|
159
|
+
CI on every repo, RSpec and RuboCop green before merge, conventional commits. The
|
|
160
|
+
GitHub org dates to 2018 (the job engine's first life); the AI platform is a 2025–2026
|
|
161
|
+
rebuild, which is why most public repos are young. It runs production workloads daily.
|
|
162
|
+
It is early, it is small, and the code is real. Read the source before betting on it —
|
|
163
|
+
that's what it's there for.
|
|
596
164
|
|
|
597
|
-
|
|
165
|
+
The commit history shows the shape plainly: a 2018-era org, early gems from 2019, and an intense 2025–2026 rebuild sprint. That rebuild was heavily AI-assisted — and the LLM traffic behind it was routed through LegionIO itself, which means the production metrics published here are largely the workload of building the thing they describe. The 1:1 test-to-code ratio, PR-gated flow, and conventional commits are what make that velocity safe; the audit ledger is its receipt trail.
|
|
598
166
|
|
|
599
|
-
|
|
600
|
-
|------|---------|
|
|
601
|
-
| `lib/legion.rb` | Entry point: `Legion.start`, `.shutdown`, `.reload` |
|
|
602
|
-
| `lib/legion/service.rb` | 15-phase startup orchestrator |
|
|
603
|
-
| `lib/legion/cli.rb` | Thor CLI: 40+ subcommands across two binaries |
|
|
604
|
-
| `lib/legion/api.rb` | Sinatra REST API with middleware stack |
|
|
605
|
-
| `lib/legion/extensions/` | LEX discovery, loading, actors, builders |
|
|
606
|
-
| `lib/legion/tools/` | Canonical tool layer (Registry, Discovery, EmbeddingCache) |
|
|
607
|
-
| `lib/legion/digital_worker/` | AI-as-labor governance platform |
|
|
608
|
-
| `lib/legion/cli/chat/` | Interactive AI REPL with 40 tools |
|
|
609
|
-
| `spec/` | RSpec suite (~3500+ examples) |
|
|
167
|
+
LegionIO is also published through [Optum Open Source](https://github.com/Optum/LegionIO), where it first shipped publicly; this org is the active development home, and updates are periodically merged back upstream. The enterprise provenance is why a framework this young ships RBAC, an audit ledger, and identity integration — a real production deployment required them.
|
|
610
168
|
|
|
611
|
-
|
|
169
|
+
## Requirements
|
|
612
170
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
4. Ensure `bundle exec rubocop` passes with 0 offenses
|
|
617
|
-
5. Open a PR targeting `main`
|
|
171
|
+
- Ruby >= 3.4
|
|
172
|
+
- Nothing else in lite mode. Full mode: RabbitMQ; optional PostgreSQL/MySQL/SQLite,
|
|
173
|
+
Redis/Memcached, HashiCorp Vault.
|
|
618
174
|
|
|
619
175
|
## License
|
|
620
176
|
|
|
621
|
-
Apache-2.0
|
|
177
|
+
Core framework: [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
|
178
|
+
Extensions: [MIT](https://opensource.org/licenses/MIT).
|
data/legionio.gemspec
CHANGED
|
@@ -10,8 +10,11 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
spec.authors = ['Esity']
|
|
11
11
|
spec.email = ['matthewdiverson@gmail.com']
|
|
12
12
|
|
|
13
|
-
spec.summary = '
|
|
14
|
-
spec.description = 'LegionIO
|
|
13
|
+
spec.summary = 'Modular Ruby framework: distributed async job engine with optional LLM gateway, MCP server, and RBAC'
|
|
14
|
+
spec.description = 'LegionIO chains tasks into dependency graphs across a RabbitMQ fleet, or fully in-process via ' \
|
|
15
|
+
'lite mode with zero infrastructure. Optional layers install as independent gems: legion-llm adds ' \
|
|
16
|
+
'tiered LLM routing with mid-stream failover and context curation, legion-mcp adds an MCP server, ' \
|
|
17
|
+
'legion-rbac adds access control. All open source, no feature gates.'
|
|
15
18
|
spec.homepage = 'https://github.com/LegionIO/LegionIO'
|
|
16
19
|
spec.license = 'Apache-2.0'
|
|
17
20
|
spec.require_paths = ['lib']
|
|
@@ -107,8 +107,9 @@ module Legion
|
|
|
107
107
|
token_body = Legion::JSON.load(token_response.body)
|
|
108
108
|
|
|
109
109
|
if token_body[:access_token]
|
|
110
|
-
#
|
|
111
|
-
store_teams_token(token_body, entry[:scopes]
|
|
110
|
+
# Persist via the Entra TokenManager (the store the read path consults)
|
|
111
|
+
store_teams_token(token_body, entry[:scopes],
|
|
112
|
+
tenant_id: entry[:tenant_id], client_id: entry[:client_id])
|
|
112
113
|
AuthTeams.mutex.synchronize { entry[:result] = { authenticated: true } }
|
|
113
114
|
content_type :html
|
|
114
115
|
'<html><body><h2>Authentication successful!</h2><p>You can close this tab.</p></body></html>'
|
|
@@ -128,14 +129,16 @@ module Legion
|
|
|
128
129
|
end
|
|
129
130
|
|
|
130
131
|
module TeamsTokenHelper
|
|
131
|
-
def store_teams_token(token_body, scopes)
|
|
132
|
+
def store_teams_token(token_body, scopes, tenant_id: nil, client_id: nil)
|
|
132
133
|
require 'legion/extensions/identity/entra/helpers/token_manager'
|
|
133
134
|
Legion::Extensions::Identity::Entra::Helpers::TokenManager.save_token(
|
|
134
135
|
:delegated,
|
|
135
136
|
access_token: token_body[:access_token],
|
|
136
137
|
refresh_token: token_body[:refresh_token],
|
|
137
138
|
expires_in: token_body[:expires_in] || 3600,
|
|
138
|
-
scopes: scopes
|
|
139
|
+
scopes: scopes,
|
|
140
|
+
tenant_id: tenant_id,
|
|
141
|
+
client_id: client_id
|
|
139
142
|
)
|
|
140
143
|
Legion::Logging.info 'Teams delegated token stored' if defined?(Legion::Logging)
|
|
141
144
|
rescue StandardError => e
|
|
@@ -32,8 +32,8 @@ module Legion
|
|
|
32
32
|
|
|
33
33
|
def self.register_tools_route(app)
|
|
34
34
|
app.get '/api/extensions/tools' do
|
|
35
|
-
entries = filter_tool_entries(Array(Legion::Settings::Extensions.tools), params)
|
|
36
|
-
tools = entries.map { |e| serialize_tool_entry(e) }
|
|
35
|
+
entries = Routes::Extensions.filter_tool_entries(Array(Legion::Settings::Extensions.tools), params)
|
|
36
|
+
tools = entries.map { |e| Routes::Extensions.serialize_tool_entry(e) }
|
|
37
37
|
json_response({ total: tools.size, tools: tools })
|
|
38
38
|
end
|
|
39
39
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
class API < Sinatra::Base
|
|
5
|
+
# Subsystem health assessment for GET /api/health.
|
|
6
|
+
#
|
|
7
|
+
# A component degrades health ONLY if it is both:
|
|
8
|
+
# 1. enabled — Legion::Readiness.status[c] == true (Service marks a
|
|
9
|
+
# component ready only when its config flag is on AND setup succeeded).
|
|
10
|
+
# :skipped (disabled) and false/nil (never booted / still booting) are
|
|
11
|
+
# NOT enabled-and-previously-healthy, so they never fail health.
|
|
12
|
+
# 2. currently unhealthy — its live liveness check now returns false.
|
|
13
|
+
#
|
|
14
|
+
# This prevents both false negatives (200 while transport can't process
|
|
15
|
+
# messages) and false positives (503 for a subsystem that was never
|
|
16
|
+
# configured or hasn't finished booting).
|
|
17
|
+
module Health
|
|
18
|
+
COMPONENTS = %i[transport cache data].freeze
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
# Returns { status:, components: { name => { enabled:, healthy:, detail? } } }
|
|
22
|
+
def assess
|
|
23
|
+
components = COMPONENTS.to_h { |name| [name, component_health(name)] }
|
|
24
|
+
degraded = components.any? { |_name, c| c[:enabled] && c[:healthy] == false }
|
|
25
|
+
{ status: degraded ? 'degraded' : 'ok', components: components }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def component_health(name)
|
|
29
|
+
enabled = Legion::Readiness.status[name] == true
|
|
30
|
+
return { enabled: false, healthy: nil } unless enabled
|
|
31
|
+
|
|
32
|
+
healthy, detail = send("#{name}_liveness")
|
|
33
|
+
info = { enabled: true, healthy: healthy }
|
|
34
|
+
info[:detail] = detail if detail && !healthy
|
|
35
|
+
info
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# --- liveness checks: [healthy_boolean, detail_string_or_nil] ---
|
|
39
|
+
|
|
40
|
+
def transport_liveness
|
|
41
|
+
conn = Legion::Transport::Connection
|
|
42
|
+
return [true, nil] if conn.respond_to?(:lite_mode?) && conn.lite_mode?
|
|
43
|
+
|
|
44
|
+
session = conn.session
|
|
45
|
+
open = session.respond_to?(:open?) && session.open?
|
|
46
|
+
[open, open ? nil : 'session_open: false']
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
[false, e.message]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def cache_liveness
|
|
52
|
+
return [true, nil] unless defined?(Legion::Cache)
|
|
53
|
+
|
|
54
|
+
connected = Legion::Cache.connected?
|
|
55
|
+
[connected, connected ? nil : 'cache not connected']
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
[false, e.message]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def data_liveness
|
|
61
|
+
connected = begin
|
|
62
|
+
Legion::Settings[:data][:connected] == true
|
|
63
|
+
rescue StandardError
|
|
64
|
+
false
|
|
65
|
+
end
|
|
66
|
+
[connected, connected ? nil : 'data not connected']
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
[false, e.message]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -6,6 +6,9 @@ module Legion
|
|
|
6
6
|
class Auth
|
|
7
7
|
SKIP_PATHS = %w[/api/health /api/ready /api/openapi.json /metrics /api/auth/token /api/auth/worker-token
|
|
8
8
|
/api/auth/authorize /api/auth/callback /api/auth/negotiate].freeze
|
|
9
|
+
# Match a skip path exactly or as a bounded sub-path (segment boundary),
|
|
10
|
+
# NOT as a bare prefix — `/api/healthily_fake` must not match `/api/health`.
|
|
11
|
+
SKIP_PATTERNS = SKIP_PATHS.map { |p| %r{\A#{Regexp.escape(p)}(?:/|\z)} }.freeze
|
|
9
12
|
AUTH_HEADER = 'HTTP_AUTHORIZATION'
|
|
10
13
|
BEARER_PATTERN = /\ABearer\s+(.+)\z/i
|
|
11
14
|
NEGOTIATE_PATTERN = /\ANegotiate\s+(.+)\z/i
|
|
@@ -81,7 +84,7 @@ module Legion
|
|
|
81
84
|
end
|
|
82
85
|
|
|
83
86
|
def skip_path?(path)
|
|
84
|
-
|
|
87
|
+
SKIP_PATTERNS.any? { |re| re.match?(path) }
|
|
85
88
|
end
|
|
86
89
|
|
|
87
90
|
def extract_negotiate_token(env)
|
data/lib/legion/api/openapi.rb
CHANGED
|
@@ -92,7 +92,7 @@ module Legion
|
|
|
92
92
|
}
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
-
def self.to_json
|
|
95
|
+
def self.to_json(*_args)
|
|
96
96
|
require 'json'
|
|
97
97
|
::JSON.generate(spec)
|
|
98
98
|
end
|
|
@@ -432,7 +432,9 @@ module Legion
|
|
|
432
432
|
get: {
|
|
433
433
|
tags: ['Health'],
|
|
434
434
|
summary: 'Health check',
|
|
435
|
-
description: 'Returns ok status and
|
|
435
|
+
description: 'Returns ok/degraded status, version, and per-subsystem component health. ' \
|
|
436
|
+
'Returns 503 when an enabled, previously-healthy subsystem (transport, cache, data) ' \
|
|
437
|
+
'has degraded. Skips auth middleware.',
|
|
436
438
|
operationId: 'getHealth',
|
|
437
439
|
security: [],
|
|
438
440
|
responses: {
|
|
@@ -440,11 +442,13 @@ module Legion
|
|
|
440
442
|
properties: {
|
|
441
443
|
data: {
|
|
442
444
|
type: 'object',
|
|
443
|
-
properties: { status: { type: 'string', example: 'ok' }, version: { type: 'string' }
|
|
445
|
+
properties: { status: { type: 'string', example: 'ok' }, version: { type: 'string' },
|
|
446
|
+
components: { type: 'object' } }
|
|
444
447
|
},
|
|
445
448
|
meta: { '$ref' => '#/components/schemas/Meta' }
|
|
446
449
|
}
|
|
447
|
-
))
|
|
450
|
+
)),
|
|
451
|
+
'503' => { description: 'Degraded — an enabled subsystem has broken' }
|
|
448
452
|
}
|
|
449
453
|
}
|
|
450
454
|
},
|
data/lib/legion/api.rb
CHANGED
|
@@ -5,6 +5,7 @@ require 'legion/json'
|
|
|
5
5
|
require_relative 'events'
|
|
6
6
|
require_relative 'readiness'
|
|
7
7
|
require_relative 'api/default_settings'
|
|
8
|
+
require_relative 'api/health'
|
|
8
9
|
|
|
9
10
|
require_relative 'api/middleware/auth'
|
|
10
11
|
require_relative 'api/middleware/body_limit'
|
|
@@ -107,7 +108,11 @@ module Legion
|
|
|
107
108
|
# Health and readiness
|
|
108
109
|
get '/api/health' do
|
|
109
110
|
uptime_seconds = (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - START_TIME).to_i
|
|
110
|
-
|
|
111
|
+
assessment = Legion::API::Health.assess
|
|
112
|
+
json_response({ status: assessment[:status], version: Legion::VERSION,
|
|
113
|
+
uptime_seconds: uptime_seconds, uptime: uptime_seconds,
|
|
114
|
+
components: assessment[:components] },
|
|
115
|
+
status_code: assessment[:status] == 'ok' ? 200 : 503)
|
|
111
116
|
end
|
|
112
117
|
|
|
113
118
|
get '/api/ready' do
|
|
@@ -56,7 +56,7 @@ module Legion
|
|
|
56
56
|
return pricing if models_str.nil? || models_str.strip.empty?
|
|
57
57
|
|
|
58
58
|
names = models_str.split(',').map(&:strip).map(&:downcase)
|
|
59
|
-
pricing.select { |k, _| names.any? { |n| k.downcase.include?(n) } }
|
|
59
|
+
pricing.select { |k, _| names.any? { |n| k.downcase.include?(n) } } # rubocop:disable Style/ArrayIntersect
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def self.format_comparison(selected, tokens)
|
|
@@ -184,7 +184,7 @@ module Legion
|
|
|
184
184
|
Connection.log_level = options[:verbose] ? 'debug' : 'error'
|
|
185
185
|
Connection.ensure_settings
|
|
186
186
|
|
|
187
|
-
require 'legion/llm/daemon_client'
|
|
187
|
+
require 'legion/llm/call/daemon_client'
|
|
188
188
|
return if Legion::LLM::DaemonClient.available?
|
|
189
189
|
|
|
190
190
|
raise CLI::Error,
|
|
@@ -8,6 +8,7 @@ module Legion
|
|
|
8
8
|
namespace :connect
|
|
9
9
|
|
|
10
10
|
PROVIDERS = %w[microsoft github google].freeze
|
|
11
|
+
STATE_COLORS = { 'connected' => :green, 'revoked' => :red, 'not connected' => :yellow }.freeze
|
|
11
12
|
|
|
12
13
|
desc 'microsoft', 'Connect a Microsoft account (OAuth2 delegated auth)'
|
|
13
14
|
method_option :tenant_id, type: :string, desc: 'Azure tenant ID'
|
|
@@ -17,7 +18,11 @@ module Legion
|
|
|
17
18
|
method_option :no_browser, type: :boolean, default: false, desc: 'Print URL instead of launching browser'
|
|
18
19
|
def microsoft
|
|
19
20
|
say 'Delegating to Teams OAuth2 browser auth...', :blue
|
|
20
|
-
|
|
21
|
+
forwarded = ['teams']
|
|
22
|
+
forwarded += ['--tenant_id', options[:tenant_id]] if options[:tenant_id]
|
|
23
|
+
forwarded += ['--client_id', options[:client_id]] if options[:client_id]
|
|
24
|
+
forwarded += ['--scopes', options[:scope]] if options[:scope]
|
|
25
|
+
Legion::CLI::Auth.start(forwarded)
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
desc 'github', 'Connect a GitHub account (OAuth2 device flow)'
|
|
@@ -31,14 +36,8 @@ module Legion
|
|
|
31
36
|
require 'legion/auth/token_manager'
|
|
32
37
|
|
|
33
38
|
PROVIDERS.each do |provider|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
say " #{provider}: connected", :green
|
|
37
|
-
elsif manager.revoked?
|
|
38
|
-
say " #{provider}: revoked", :red
|
|
39
|
-
else
|
|
40
|
-
say " #{provider}: not connected", :yellow
|
|
41
|
-
end
|
|
39
|
+
state = provider_state(provider)
|
|
40
|
+
say " #{provider}: #{state}", STATE_COLORS.fetch(state, :yellow)
|
|
42
41
|
end
|
|
43
42
|
end
|
|
44
43
|
|
|
@@ -51,6 +50,32 @@ module Legion
|
|
|
51
50
|
|
|
52
51
|
say "Disconnected #{provider} account.", :green
|
|
53
52
|
end
|
|
53
|
+
|
|
54
|
+
no_commands do
|
|
55
|
+
# Microsoft delegated login writes tokens via the Entra TokenManager
|
|
56
|
+
# (vault/local/memory), not the legacy Legion::Auth secret store — so
|
|
57
|
+
# status for :microsoft must consult the Entra store to avoid always
|
|
58
|
+
# reporting 'not connected' after a successful Teams/delegated login.
|
|
59
|
+
def provider_state(provider)
|
|
60
|
+
return microsoft_state if provider == 'microsoft'
|
|
61
|
+
|
|
62
|
+
manager = Legion::Auth::TokenManager.new(provider: provider.to_sym)
|
|
63
|
+
return 'connected' if manager.token_valid?
|
|
64
|
+
return 'revoked' if manager.revoked?
|
|
65
|
+
|
|
66
|
+
'not connected'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def microsoft_state
|
|
70
|
+
return 'not connected' unless defined?(Legion::Extensions::Identity::Entra::Helpers::TokenManager)
|
|
71
|
+
|
|
72
|
+
tm = Legion::Extensions::Identity::Entra::Helpers::TokenManager
|
|
73
|
+
data = tm.token_data(:delegated, refresh: false)
|
|
74
|
+
data && !tm.expired?(data) ? 'connected' : 'not connected'
|
|
75
|
+
rescue StandardError
|
|
76
|
+
'not connected'
|
|
77
|
+
end
|
|
78
|
+
end
|
|
54
79
|
end
|
|
55
80
|
end
|
|
56
81
|
end
|
|
@@ -29,7 +29,6 @@ module Legion
|
|
|
29
29
|
methods.each do |function|
|
|
30
30
|
route_path = "#{extension_name}/#{runner_name}/#{function}"
|
|
31
31
|
defn = runner_module.respond_to?(:definition_for) ? runner_module.definition_for(function) : nil
|
|
32
|
-
log.info "[Routes] auto-route registered: POST /api/extensions/#{extension_name}/runners/#{runner_name}/#{function}"
|
|
33
32
|
@routes[route_path] = {
|
|
34
33
|
lex_name: extension_name,
|
|
35
34
|
runner_name: runner_name,
|
|
@@ -51,6 +50,7 @@ module Legion
|
|
|
51
50
|
runner_class: runner_class,
|
|
52
51
|
definition: defn
|
|
53
52
|
)
|
|
53
|
+
log.info "[Routes] registered: POST /api/extensions/#{extension_name}/runners/#{runner_name}/#{function}"
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
end
|
data/lib/legion/service.rb
CHANGED
|
@@ -418,6 +418,8 @@ module Legion
|
|
|
418
418
|
Legion::API.use Legion::Rbac::Middleware
|
|
419
419
|
end
|
|
420
420
|
|
|
421
|
+
register_extension_routes_with_api_router
|
|
422
|
+
|
|
421
423
|
@api_thread = Thread.new do
|
|
422
424
|
retries = 0
|
|
423
425
|
max_retries = api_settings[:bind_retries]
|
|
@@ -448,6 +450,32 @@ module Legion
|
|
|
448
450
|
handle_exception(e, level: :warn, operation: 'service.setup_api')
|
|
449
451
|
end
|
|
450
452
|
|
|
453
|
+
def register_extension_routes_with_api_router
|
|
454
|
+
return unless defined?(Legion::API) && Legion::API.respond_to?(:router)
|
|
455
|
+
return unless defined?(Legion::Extensions)
|
|
456
|
+
|
|
457
|
+
count = 0
|
|
458
|
+
Legion::Extensions.loaded_extension_modules.each do |ext_mod|
|
|
459
|
+
next unless ext_mod.respond_to?(:routes) && ext_mod.routes.is_a?(Hash)
|
|
460
|
+
|
|
461
|
+
ext_mod.routes.each_value do |route_entry|
|
|
462
|
+
Legion::API.router.register_extension_route(
|
|
463
|
+
lex_name: route_entry[:lex_name],
|
|
464
|
+
amqp_prefix: ext_mod.respond_to?(:amqp_prefix) ? ext_mod.amqp_prefix : "lex.#{route_entry[:lex_name].to_s.tr('_', '.')}",
|
|
465
|
+
component_type: route_entry[:component_type],
|
|
466
|
+
component_name: route_entry[:runner_name],
|
|
467
|
+
method_name: route_entry[:function].to_s,
|
|
468
|
+
runner_class: route_entry[:runner_class],
|
|
469
|
+
definition: route_entry[:definition]
|
|
470
|
+
)
|
|
471
|
+
count += 1
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
log.info "[Routes] backfilled #{count} extension routes with API router" if count.positive?
|
|
475
|
+
rescue StandardError => e
|
|
476
|
+
handle_exception(e, level: :warn, operation: 'service.register_extension_routes_with_api_router')
|
|
477
|
+
end
|
|
478
|
+
|
|
451
479
|
def setup_llm
|
|
452
480
|
log.info 'Setting up Legion::LLM'
|
|
453
481
|
require 'legion/llm'
|
|
@@ -960,6 +988,7 @@ module Legion
|
|
|
960
988
|
Legion::Crypt.cs if defined?(Legion::Crypt)
|
|
961
989
|
setup_apm if @api_enabled
|
|
962
990
|
setup_api if @api_enabled
|
|
991
|
+
register_extension_routes_with_api_router if @api_enabled
|
|
963
992
|
|
|
964
993
|
if defined?(Legion::MCP)
|
|
965
994
|
Legion::MCP.reset!
|
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.9.
|
|
4
|
+
version: 1.9.50
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -387,8 +387,11 @@ dependencies:
|
|
|
387
387
|
- - ">="
|
|
388
388
|
- !ruby/object:Gem::Version
|
|
389
389
|
version: '0'
|
|
390
|
-
description: LegionIO
|
|
391
|
-
|
|
390
|
+
description: 'LegionIO chains tasks into dependency graphs across a RabbitMQ fleet,
|
|
391
|
+
or fully in-process via lite mode with zero infrastructure. Optional layers install
|
|
392
|
+
as independent gems: legion-llm adds tiered LLM routing with mid-stream failover
|
|
393
|
+
and context curation, legion-mcp adds an MCP server, legion-rbac adds access control.
|
|
394
|
+
All open source, no feature gates.'
|
|
392
395
|
email:
|
|
393
396
|
- matthewdiverson@gmail.com
|
|
394
397
|
executables:
|
|
@@ -490,6 +493,7 @@ files:
|
|
|
490
493
|
- lib/legion/api/graphql/types/query_type.rb
|
|
491
494
|
- lib/legion/api/graphql/types/task_type.rb
|
|
492
495
|
- lib/legion/api/graphql/types/worker_type.rb
|
|
496
|
+
- lib/legion/api/health.rb
|
|
493
497
|
- lib/legion/api/helpers.rb
|
|
494
498
|
- lib/legion/api/identity_audit.rb
|
|
495
499
|
- lib/legion/api/inbound_webhooks.rb
|
|
@@ -969,5 +973,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
969
973
|
requirements: []
|
|
970
974
|
rubygems_version: 3.6.9
|
|
971
975
|
specification_version: 4
|
|
972
|
-
summary:
|
|
976
|
+
summary: 'Modular Ruby framework: distributed async job engine with optional LLM gateway,
|
|
977
|
+
MCP server, and RBAC'
|
|
973
978
|
test_files: []
|