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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c2e14e59941d157f3934d2defda8273c0d33e4d2ccc7c1cda305cdfd0c4a56d
4
- data.tar.gz: f238dcc116658b5c35d4990531472f700b0504170af2ac80dff194cd388c0c9b
3
+ metadata.gz: 836093ff32350c705da43ab5a36cf9b0e5e135e24556bf5b2c10c50df421ee11
4
+ data.tar.gz: ac682d87908c360cc90409e9fa773a5a8937245104394dbcd940fea67ca10141
5
5
  SHA512:
6
- metadata.gz: 3c10304ab55f3ffd752971ea184fb7917ab499adda804dcae7f92e9bcffd0c8b7860a36a79ba1110d27e02b72783660e1b4f77fb929b98d08b93ceff5cc20894
7
- data.tar.gz: dc5f13d4b000a5d3949b65885af43fba4a041a7df9de37fc13ae9fb5e282b07a5e0cac7583ef4c77f44f7c1734308e20949704c2d7e4fa6e3df21ac3666edf67
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>One Ruby gem that is a distributed async job engine, an AI coding assistant, an MCP server,
5
- and a cognitive-computing platform and runs with zero required infrastructure.</b>
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
- legionio check # verify subsystem connections
84
- legionio start # start the daemon
85
- ```
86
-
87
- For the AI features:
88
-
89
- ```bash
90
- legion # launch the interactive TTY shell
91
- legion chat # interactive AI REPL with 40 built-in tools
92
- legion commit # AI-generated commit message from staged changes
93
- legion review # AI code review of your code
94
- ```
95
-
96
- ### Two Binaries
97
-
98
- | Binary | Purpose |
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
- ### Knowledge
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
- Query and manage the Apollo shared knowledge store and local knowledge index:
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
- ```bash
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
- ### Mind Growth
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
- Autonomous cognitive architecture expansion system. Analyzes gaps, proposes new cognitive extensions, and builds them via a staged pipeline:
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
- ```bash
261
- legion mind-growth status # current growth cycle state
262
- legion mind-growth analyze # gap analysis against 5 reference models
263
- legion mind-growth propose # propose a new concept
264
- legion mind-growth evaluate <id> # evaluate a proposal
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
- Requires `lex-mind-growth`. Also exposes 6 MCP tools in the `legion.mind_growth_*` namespace.
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
- ### Code Generation
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
- Run inside a `lex-*` directory:
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
- ```bash
292
- legion generate runner <name> # add runner + spec
293
- legion generate actor <name> # add actor + spec
294
- legion g exchange <name> # 'g' is an alias
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
- ### Scheduling
85
+ ## The job engine
298
86
 
299
- Requires `lex-scheduler`:
87
+ The original core, in production since before any of the AI layers existed:
300
88
 
301
- ```bash
302
- legion schedule add alerts "*/5 * * * *" http.request.get
303
- legion schedule add daily "every day at noon" report.generate.summary
304
- legion schedule list
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
- ### Configuration
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
- ```bash
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
- `config scaffold` auto-detects environment variables (`ANTHROPIC_API_KEY`, `AWS_BEARER_TOKEN_BEDROCK`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `VAULT_TOKEN`, `RABBITMQ_USER`/`PASSWORD`) and a running Ollama instance, enabling providers and setting `env://` references automatically.
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
- Settings load from the first directory found: `/etc/legionio/` `~/.legionio/settings/` `~/legionio/` `./settings/`
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
- ### Observability
119
+ ## Interfaces
320
120
 
321
121
  ```bash
322
- legion dashboard # TUI operational dashboard with auto-refresh
323
- legion cost summary # cost overview (today/week/month)
324
- legion cost worker <id> # per-worker cost breakdown
325
- legion trace search "failed tasks last hour" # natural language trace search
326
- legion graph show --format mermaid # task relationship graph
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
- ### Audit & RBAC
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
- ```bash
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
- ### Diagnostics
134
+ Every number above is reproducible from public source. A few one-liners:
339
135
 
340
136
  ```bash
341
- legion doctor # diagnose environment, suggest fixes
342
- legion doctor --fix # auto-remediate fixable issues (stale PIDs, missing gems)
343
- legion doctor --json # machine-readable output
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
- Checks Ruby version, bundle status, config files, RabbitMQ, database, cache, Vault, extensions, PID files, and permissions. Exits 1 if any check fails.
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
- ### Updating
349
-
350
- ```bash
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
- Uses the same Ruby that `legion` is running from — safe for Homebrew installs (updates go into the bundled gem directory, not your system Ruby).
356
-
357
- All commands support `--json` for structured output and `--no-color` to strip ANSI codes.
358
-
359
- ## REST API
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
- ### AI / LLM
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
- Always run `bundle exec rspec` and `bundle exec rubocop -A` and fix all errors before committing.
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
- ### Project Structure
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
- | Path | Purpose |
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
- ### Contributing
169
+ ## Requirements
612
170
 
613
- 1. Fork the repo and create a feature branch
614
- 2. Write specs for new functionality
615
- 3. Ensure `bundle exec rspec` passes with 0 failures
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 = 'The primary gem to run the LegionIO Framework'
14
- spec.description = 'LegionIO is an extensible framework for running, scheduling and building relationships of tasks in a concurrent matter'
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
- # Store token via TokenCache if available
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
- SKIP_PATHS.any? { |p| path.start_with?(p) }
87
+ SKIP_PATTERNS.any? { |re| re.match?(path) }
85
88
  end
86
89
 
87
90
  def extract_negotiate_token(env)
@@ -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 version. Skips auth middleware.',
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
- json_response({ status: 'ok', version: Legion::VERSION, uptime_seconds: uptime_seconds, uptime: uptime_seconds })
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
@@ -4,7 +4,7 @@ require 'securerandom'
4
4
  require 'legion/cli/chat_command'
5
5
 
6
6
  begin
7
- require 'legion/llm/daemon_client'
7
+ require 'legion/llm/call/daemon_client'
8
8
  rescue LoadError
9
9
  # legion-llm not yet loaded; DaemonClient must be defined before DaemonChat#ask is called.
10
10
  end
@@ -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
- Legion::CLI::Auth.start(['teams'] + ARGV.select { |a| a.start_with?('--') })
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
- manager = Legion::Auth::TokenManager.new(provider: provider.to_sym)
35
- if manager.token_valid?
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
@@ -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!
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.9.43'
4
+ VERSION = '1.9.50'
5
5
  end
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.43
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 is an extensible framework for running, scheduling and building
391
- relationships of tasks in a concurrent matter
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: The primary gem to run the LegionIO Framework
976
+ summary: 'Modular Ruby framework: distributed async job engine with optional LLM gateway,
977
+ MCP server, and RBAC'
973
978
  test_files: []