legionio 1.9.41 → 1.9.42
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/.rubocop.yml +5 -9
- data/AGENTS.md +48 -6
- data/CHANGELOG.md +6 -0
- data/CLAUDE.md +20 -1
- data/Gemfile +5 -3
- data/README.md +48 -13
- data/exe/legion +0 -11
- data/exe/replay_ledger +2 -2
- data/lib/legion/api/auth.rb +1 -1
- data/lib/legion/api/auth_human.rb +1 -1
- data/lib/legion/api/auth_worker.rb +1 -1
- data/lib/legion/api/chains.rb +1 -1
- data/lib/legion/api/codegen.rb +1 -1
- data/lib/legion/api/lex_dispatch.rb +1 -1
- data/lib/legion/api/marketplace.rb +1 -1
- data/lib/legion/api/rbac.rb +1 -1
- data/lib/legion/api/tbi_patterns.rb +1 -1
- data/lib/legion/api/workers.rb +3 -3
- data/lib/legion/api.rb +3 -2
- data/lib/legion/cli/admin/purge_topology.rb +1 -1
- data/lib/legion/cli/audit_command.rb +1 -1
- data/lib/legion/cli/broker_command.rb +1 -1
- data/lib/legion/cli/chat/tools/consolidate_memory.rb +16 -4
- data/lib/legion/cli/chat/tools/reflect.rb +14 -4
- data/lib/legion/cli/chat/tools/search_content.rb +1 -1
- data/lib/legion/cli/chat_command.rb +5 -7
- data/lib/legion/cli/commit_command.rb +1 -1
- data/lib/legion/cli/config_command.rb +2 -2
- data/lib/legion/cli/config_scaffold.rb +2 -2
- data/lib/legion/cli/eval_command.rb +1 -1
- data/lib/legion/cli/setup_command.rb +4 -4
- data/lib/legion/cli/task_command.rb +1 -1
- data/lib/legion/cli/trigger.rb +1 -1
- data/lib/legion/cli/worker_command.rb +1 -1
- data/lib/legion/cli.rb +1 -1
- data/lib/legion/extensions/actors/subscription.rb +3 -3
- data/lib/legion/extensions.rb +26 -17
- data/lib/legion/ingress.rb +1 -1
- data/lib/legion/memory/consolidator.rb +16 -4
- data/lib/legion/runner.rb +2 -2
- data/lib/legion/service.rb +4 -10
- data/lib/legion/version.rb +1 -1
- metadata +1 -2
- data/lib/legion/api/llm.rb +0 -460
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 052a20d232d946844926289c16dfd82854ab154f50228d4634145e4464340160
|
|
4
|
+
data.tar.gz: f9b14bd14bc1e069025bbde580f59e443d325b2acb8324cad3e356c8322824c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 933985d444612a564932a97f8ab3fb31c34aadedb25c351458a939d88f31883d990b78678f971614ee8b9c6f4f84a41bd609270612e2f8beee8fe487a04bab7b
|
|
7
|
+
data.tar.gz: eaa8b1c18278099a67010efe351aef28cb8f78fbe8008d6ecaa265796dcb70f33776b599017c5df0272fedf0448a22b212dcd13c3aa4535ef5d78440644dc3ed
|
data/.rubocop.yml
CHANGED
|
@@ -19,7 +19,7 @@ Layout/HashAlignment:
|
|
|
19
19
|
EnforcedColonStyle: table
|
|
20
20
|
|
|
21
21
|
Metrics/MethodLength:
|
|
22
|
-
Max:
|
|
22
|
+
Max: 80
|
|
23
23
|
Exclude:
|
|
24
24
|
- 'lib/legion/cli/chat_command.rb'
|
|
25
25
|
- 'lib/legion/api/openapi.rb'
|
|
@@ -35,7 +35,7 @@ Metrics/ModuleLength:
|
|
|
35
35
|
- 'lib/legion/api/openapi.rb'
|
|
36
36
|
|
|
37
37
|
Metrics/BlockLength:
|
|
38
|
-
Max:
|
|
38
|
+
Max: 80
|
|
39
39
|
Exclude:
|
|
40
40
|
- 'spec/**/*'
|
|
41
41
|
- 'integration/**/*'
|
|
@@ -70,14 +70,14 @@ Metrics/BlockLength:
|
|
|
70
70
|
- 'lib/legion/cli/mode_command.rb'
|
|
71
71
|
|
|
72
72
|
Metrics/AbcSize:
|
|
73
|
-
Max:
|
|
73
|
+
Max: 80
|
|
74
74
|
Exclude:
|
|
75
75
|
- 'lib/legion/cli/chat_command.rb'
|
|
76
76
|
- 'lib/legion/api/llm.rb'
|
|
77
77
|
- 'lib/legion/digital_worker/lifecycle.rb'
|
|
78
78
|
|
|
79
79
|
Metrics/CyclomaticComplexity:
|
|
80
|
-
Max:
|
|
80
|
+
Max: 25
|
|
81
81
|
Exclude:
|
|
82
82
|
- 'lib/legion/cli/chat_command.rb'
|
|
83
83
|
- 'lib/legion/api/auth_human.rb'
|
|
@@ -85,7 +85,7 @@ Metrics/CyclomaticComplexity:
|
|
|
85
85
|
- 'lib/legion/digital_worker/lifecycle.rb'
|
|
86
86
|
|
|
87
87
|
Metrics/PerceivedComplexity:
|
|
88
|
-
Max:
|
|
88
|
+
Max: 25
|
|
89
89
|
Exclude:
|
|
90
90
|
- 'lib/legion/api/auth_human.rb'
|
|
91
91
|
- 'lib/legion/api/llm.rb'
|
|
@@ -93,16 +93,12 @@ Metrics/PerceivedComplexity:
|
|
|
93
93
|
|
|
94
94
|
Style/Documentation:
|
|
95
95
|
Enabled: false
|
|
96
|
-
|
|
97
96
|
Style/SymbolArray:
|
|
98
97
|
Enabled: true
|
|
99
|
-
|
|
100
98
|
Style/FrozenStringLiteralComment:
|
|
101
99
|
Enabled: true
|
|
102
100
|
EnforcedStyle: always
|
|
103
|
-
|
|
104
101
|
Naming/FileName:
|
|
105
102
|
Enabled: false
|
|
106
|
-
|
|
107
103
|
Naming/PredicateMethod:
|
|
108
104
|
Enabled: false
|
data/AGENTS.md
CHANGED
|
@@ -1,11 +1,53 @@
|
|
|
1
|
-
#
|
|
1
|
+
# LegionIO — Agent Notes
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`legionio` is the **primary gem** of the LegionIO framework: it orchestrates all `legion-*` gems and
|
|
4
|
+
loads `lex-*` extensions. It's an async job engine, an AI coding assistant, an MCP server, and a
|
|
5
|
+
cognitive platform in one. See `CLAUDE.md` for the full boot sequence, module map, and conventions;
|
|
6
|
+
`README.md` for the user-facing tour.
|
|
4
7
|
|
|
5
|
-
##
|
|
8
|
+
## Fast Start
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
```bash
|
|
11
|
+
bundle install
|
|
12
|
+
bundle exec rspec # ~3500+ examples — 0 failures required before commit
|
|
13
|
+
bundle exec rubocop # 0 offenses required
|
|
14
|
+
```
|
|
8
15
|
|
|
9
|
-
|
|
16
|
+
Run **both** in full and fix everything before committing. No exceptions — the PR CI gate is green
|
|
17
|
+
and must stay green.
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
## Primary Entry Points
|
|
20
|
+
|
|
21
|
+
- `lib/legion.rb` — `Legion.start`, `.shutdown`, `.reload`
|
|
22
|
+
- `lib/legion/service.rb` — the 15-phase startup orchestrator (logging → settings → crypt →
|
|
23
|
+
transport → cache → data → rbac → llm → apollo → gaia → telemetry → supervision → extensions →
|
|
24
|
+
cluster secret → api)
|
|
25
|
+
- `lib/legion/cli.rb` + `lib/legion/cli/` — Thor CLI across the two binaries (`legion`, `legionio`)
|
|
26
|
+
- `lib/legion/cli/chat/` — the interactive AI REPL
|
|
27
|
+
- `lib/legion/api.rb` + `lib/legion/api/` — Sinatra REST API (port 4567) + middleware
|
|
28
|
+
- `lib/legion/extensions/` — LEX discovery/loading/actors/builders
|
|
29
|
+
- `lib/legion/tools/` — canonical tool layer (Registry, Discovery, EmbeddingCache)
|
|
30
|
+
- `exe/legion`, `exe/legionio` — the binaries; perf opts applied before any code loads
|
|
31
|
+
|
|
32
|
+
## Guardrails / Gotchas (these prevent real bugs)
|
|
33
|
+
|
|
34
|
+
- **`Legion::JSON` only** — `Legion::JSON.load` returns **symbol keys**; `.dump` takes exactly one
|
|
35
|
+
positional arg (wrap kwargs in `{}`). Inside the `Legion::` namespace, **`::JSON` and `::Process`
|
|
36
|
+
must be explicit** (they resolve to `Legion::JSON` / `Legion::Process` otherwise).
|
|
37
|
+
- **Thor 1.5+ reserves `run`** — use `map 'run' => :trigger` in the Task subcommand.
|
|
38
|
+
- **Sinatra 4** — `set :host_authorization, permitted: :any`. API response shape is
|
|
39
|
+
`{ data:, meta: { timestamp:, node: } }`; errors `{ error: { code:, message: }, meta: }`.
|
|
40
|
+
- **LLM routes are owned by `legion-llm`** and mounted from it — do not re-add in-app LLM routes or a
|
|
41
|
+
provider gateway fallback (that migration is intentional).
|
|
42
|
+
- **Bootsnap is opt-in** (`LEGION_BOOTSNAP=true`), not always-on.
|
|
43
|
+
- **Never swallow exceptions** — every `rescue` re-raises or `handle_exception`s; use `log.*`, never
|
|
44
|
+
`puts`. **No personal/company identifiers in VCS**; never force-push.
|
|
45
|
+
- Extensions declare `data_required?` / `cache_required?` / `crypt_required?` / `vault_required?` /
|
|
46
|
+
`llm_required?` and are skipped when the dependency is absent — keep that contract intact.
|
|
47
|
+
- `LEGION_MODE=lite` must keep working end-to-end (in-process transport + in-memory cache, no
|
|
48
|
+
RabbitMQ/Redis).
|
|
49
|
+
|
|
50
|
+
## Validation
|
|
51
|
+
|
|
52
|
+
Run targeted specs for the area you touched, then full `rspec` + `rubocop` before handoff. Specs use
|
|
53
|
+
`rack-test`; the suite runs without external infrastructure.
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.9.42] - 2026-06-07
|
|
4
|
+
|
|
5
|
+
### Performance
|
|
6
|
+
- Extensions: batched extension registration into a single `LexRegister` publish after all extensions load, eliminating N individual queue messages and DB transactions during boot
|
|
7
|
+
- Removed redundant `flush_pending_registrations!` call from `setup_identity` ensure block, consolidating to a single flush point in `reload!`
|
|
8
|
+
|
|
3
9
|
## [1.9.41] - 2026-06-02
|
|
4
10
|
|
|
5
11
|
### Fixed
|
data/CLAUDE.md
CHANGED
|
@@ -14,7 +14,8 @@ Primary gem. Orchestrates all `legion-*` gems and loads LEX extensions.
|
|
|
14
14
|
|
|
15
15
|
## Boot Sequence
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Executables enable YJIT + GC tuning (600k heap slots). Bootsnap is **opt-in** —
|
|
18
|
+
set `LEGION_BOOTSNAP=true` (it is no longer applied unconditionally).
|
|
18
19
|
|
|
19
20
|
```
|
|
20
21
|
Legion.start → Legion::Service.new
|
|
@@ -80,6 +81,24 @@ Legion
|
|
|
80
81
|
└── Graph # Task relationship visualization (Mermaid/DOT)
|
|
81
82
|
```
|
|
82
83
|
|
|
84
|
+
## Where Things Live (most-touched)
|
|
85
|
+
|
|
86
|
+
| Path | Purpose |
|
|
87
|
+
|------|---------|
|
|
88
|
+
| `lib/legion.rb` | Entry: `Legion.start`, `.shutdown`, `.reload` |
|
|
89
|
+
| `lib/legion/service.rb` | 15-phase startup orchestrator |
|
|
90
|
+
| `lib/legion/cli.rb` + `lib/legion/cli/` | Thor CLI — two binaries, 40+ subcommands |
|
|
91
|
+
| `lib/legion/cli/chat/` | Interactive AI REPL (sessions, tools, agents, memory, skills) |
|
|
92
|
+
| `lib/legion/api.rb` + `lib/legion/api/` | Sinatra REST API + middleware (Auth, Tenant, RateLimit, BodyLimit) |
|
|
93
|
+
| `lib/legion/extensions/` | LEX discovery, loading, actors, builders |
|
|
94
|
+
| `lib/legion/tools/` | Canonical tool layer (Registry, Discovery, EmbeddingCache) |
|
|
95
|
+
| `lib/legion/digital_worker/` | AI-as-labor governance (Lifecycle, RiskTier, ValueMetrics) |
|
|
96
|
+
| `exe/legion`, `exe/legionio` | The two binaries; perf opts (YJIT/GC, opt-in bootsnap) applied here |
|
|
97
|
+
| `spec/` | RSpec suite (~3500+ examples) |
|
|
98
|
+
|
|
99
|
+
LLM HTTP routes are **owned by `legion-llm`** and mounted from it — LegionIO no longer
|
|
100
|
+
defines its own LLM routes or a provider gateway fallback.
|
|
101
|
+
|
|
83
102
|
## Lite Mode
|
|
84
103
|
|
|
85
104
|
`LEGION_MODE=lite` — `InProcess` transport adapter + `Memory` cache adapter. No RabbitMQ/Redis needed.
|
data/Gemfile
CHANGED
|
@@ -19,11 +19,13 @@ gem 'legion-mcp', path: '../legion-mcp' if File.exist?(File.expand_path('../legi
|
|
|
19
19
|
gem 'legion-tty', path: '../legion-tty' if File.exist?(File.expand_path('../legion-tty', __dir__))
|
|
20
20
|
|
|
21
21
|
gem 'lex-apollo', path: '../extensions/lex-apollo' if File.exist?(File.expand_path('../extensions/lex-apollo', __dir__))
|
|
22
|
-
gem 'lex-lex', path: '../extensions/lex-lex' if File.exist?(File.expand_path('../extensions/lex-lex', __dir__))
|
|
23
22
|
gem 'lex-llm', path: '../extensions-ai/lex-llm' if File.exist?(File.expand_path('../extensions-ai/lex-llm', __dir__))
|
|
24
|
-
gem 'lex-llm-ledger', path: '../extensions-ai/lex-llm-ledger' if File.exist?(File.expand_path('../extensions-ai/lex-llm-ledger', __dir__))
|
|
25
23
|
# gem 'lex-microsoft_teams', path: '../extensions/lex-microsoft_teams' if File.exist?(File.expand_path('../extensions/lex-microsoft_teams', __dir__))
|
|
26
|
-
|
|
24
|
+
|
|
25
|
+
gem 'lex-lex', path: '../extensions/lex-lex' if File.exist?(File.expand_path('../extensions/lex-lex', __dir__))
|
|
26
|
+
gem 'lex-llm-ledger', path: '../extensions-ai/lex-llm-ledger' if File.exist?(File.expand_path('../extensions-ai/lex-llm-ledger', __dir__))
|
|
27
|
+
gem 'lex-scheduler', path: '../extensions/lex-scheduler' if File.exist?(File.expand_path('../extensions/lex-scheduler', __dir__))
|
|
28
|
+
gem 'lex-tasker', path: '../extensions/lex-tasker' if File.exist?(File.expand_path('../extensions/lex-tasker', __dir__))
|
|
27
29
|
|
|
28
30
|
if File.exist?(File.expand_path('../extensions-identity/lex-identity-entra', __dir__))
|
|
29
31
|
gem 'lex-identity-entra', path: '../extensions-identity/lex-identity-entra'
|
data/README.md
CHANGED
|
@@ -1,26 +1,61 @@
|
|
|
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>
|
|
6
|
+
</p>
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://rubygems.org/gems/legionio"><img alt="Gem Version" src="https://img.shields.io/gem/v/legionio.svg"></a>
|
|
10
|
+
<img alt="Ruby" src="https://img.shields.io/badge/ruby-%3E%3D%203.4-CC342D.svg">
|
|
11
|
+
<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
|
+
</p>
|
|
6
14
|
|
|
7
15
|
```
|
|
8
16
|
╭──────────────────────────────────────╮
|
|
9
17
|
│ L E G I O N I O │
|
|
10
18
|
│ │
|
|
11
|
-
│
|
|
12
|
-
│
|
|
13
|
-
│
|
|
19
|
+
│ async jobs · AI chat · MCP │
|
|
20
|
+
│ REST API · HA · cognitive AI │
|
|
21
|
+
│ zero-infra lite mode · Vault │
|
|
14
22
|
╰──────────────────────────────────────╯
|
|
15
23
|
```
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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.
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
## Why LegionIO
|
|
22
30
|
|
|
23
|
-
|
|
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.
|
|
24
59
|
|
|
25
60
|
## What Does It Do?
|
|
26
61
|
|
|
@@ -507,11 +542,11 @@ CMD ruby --yjit $(which legion) start
|
|
|
507
542
|
|
|
508
543
|
## Architecture
|
|
509
544
|
|
|
510
|
-
Before any Legion code loads, the executable applies
|
|
545
|
+
Before any Legion code loads, the executable applies performance optimizations:
|
|
511
546
|
|
|
512
547
|
- **YJIT** — `RubyVM::YJIT.enable` for 15-30% runtime throughput (Ruby 3.1+ builds)
|
|
513
548
|
- **GC tuning** — pre-allocates 600k heap slots and raises malloc limits (ENV overrides respected)
|
|
514
|
-
- **bootsnap** —
|
|
549
|
+
- **bootsnap** *(opt-in)* — set `LEGION_BOOTSNAP=true` to cache YARV bytecode and `$LOAD_PATH` resolution at `~/.legionio/cache/bootsnap/`
|
|
515
550
|
|
|
516
551
|
```
|
|
517
552
|
legion start
|
data/exe/legion
CHANGED
|
@@ -9,17 +9,6 @@ ENV['RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO'] ||= '0.40'
|
|
|
9
9
|
ENV['RUBY_GC_MALLOC_LIMIT'] ||= '64000000'
|
|
10
10
|
ENV['RUBY_GC_MALLOC_LIMIT_MAX'] ||= '128000000'
|
|
11
11
|
|
|
12
|
-
if ENV['LEGION_BOOTSNAP'] == 'true' && Dir.exist?(File.expand_path('~/.legionio'))
|
|
13
|
-
require 'bootsnap'
|
|
14
|
-
Bootsnap.setup(
|
|
15
|
-
cache_dir: File.expand_path('~/.legionio/cache/bootsnap'),
|
|
16
|
-
development_mode: false,
|
|
17
|
-
load_path_cache: true,
|
|
18
|
-
compile_cache_iseq: true,
|
|
19
|
-
compile_cache_yaml: true
|
|
20
|
-
)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
12
|
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
|
24
13
|
|
|
25
14
|
# Bare `legion` (no args, interactive terminal) launches the TTY shell
|
data/exe/replay_ledger
CHANGED
|
@@ -163,7 +163,7 @@ end
|
|
|
163
163
|
|
|
164
164
|
MANIFEST_DIR = '/tmp/legion_migration_manifests'
|
|
165
165
|
|
|
166
|
-
# rubocop:disable Metrics/AbcSize, Metrics/
|
|
166
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
|
|
167
167
|
def migrate_table(local_db, prod_db, table, id_map_out, log:, dry_run:, fk_maps: {}, identity_maps: {})
|
|
168
168
|
count = local_db[table].count
|
|
169
169
|
if count.zero?
|
|
@@ -274,7 +274,7 @@ def migrate_table(local_db, prod_db, table, id_map_out, log:, dry_run:, fk_maps:
|
|
|
274
274
|
log.info " #{table}: manifest → #{manifest_path}"
|
|
275
275
|
log.info " #{table}: IDs → #{ids_path}"
|
|
276
276
|
end
|
|
277
|
-
# rubocop:enable Metrics/AbcSize, Metrics/
|
|
277
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
|
|
278
278
|
|
|
279
279
|
identity_maps = { principals: principal_id_map, identities: identity_id_map }
|
|
280
280
|
|
data/lib/legion/api/auth.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Legion
|
|
|
8
8
|
register_token_exchange(app)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def self.register_token_exchange(app)
|
|
11
|
+
def self.register_token_exchange(app)
|
|
12
12
|
app.post '/api/auth/token' do
|
|
13
13
|
Legion::Logging.debug "API: POST /api/auth/token params=#{params.keys}"
|
|
14
14
|
body = parse_request_body
|
|
@@ -68,7 +68,7 @@ module Legion
|
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
def self.register_callback(app)
|
|
71
|
+
def self.register_callback(app)
|
|
72
72
|
app.get '/api/auth/callback' do
|
|
73
73
|
entra = Routes::AuthHuman.resolve_entra_settings
|
|
74
74
|
unless entra[:tenant_id] && entra[:client_id]
|
|
@@ -8,7 +8,7 @@ module Legion
|
|
|
8
8
|
register_worker_token_exchange(app)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def self.register_worker_token_exchange(app)
|
|
11
|
+
def self.register_worker_token_exchange(app)
|
|
12
12
|
app.post '/api/auth/worker-token' do
|
|
13
13
|
Legion::Logging.debug "API: POST /api/auth/worker-token params=#{params.keys}"
|
|
14
14
|
body = parse_request_body
|
data/lib/legion/api/chains.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Legion
|
|
|
4
4
|
class API < Sinatra::Base
|
|
5
5
|
module Routes
|
|
6
6
|
module Chains
|
|
7
|
-
def self.registered(app) # rubocop:disable Metrics/AbcSize
|
|
7
|
+
def self.registered(app) # rubocop:disable Metrics/AbcSize
|
|
8
8
|
app.get '/api/chains' do
|
|
9
9
|
require_data!
|
|
10
10
|
halt 501, json_error('not_implemented', 'chain data model is not yet available', status_code: 501) unless Legion::Data::Model.const_defined?(:Chain)
|
data/lib/legion/api/codegen.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Legion
|
|
|
4
4
|
class API < Sinatra::Base
|
|
5
5
|
module Routes
|
|
6
6
|
module Codegen
|
|
7
|
-
def self.registered(app)
|
|
7
|
+
def self.registered(app)
|
|
8
8
|
app.get '/api/codegen/status' do
|
|
9
9
|
halt 503, json_error('codegen_unavailable', 'codegen not available', status_code: 503) unless defined?(Legion::MCP::SelfGenerate)
|
|
10
10
|
|
|
@@ -64,7 +64,7 @@ module Legion
|
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
def self.dispatch_request(context, request, params) # rubocop:disable Metrics/MethodLength
|
|
67
|
+
def self.dispatch_request(context, request, params) # rubocop:disable Metrics/MethodLength
|
|
68
68
|
content_type = 'application/json'
|
|
69
69
|
context.content_type content_type
|
|
70
70
|
|
|
@@ -55,7 +55,7 @@ module Legion
|
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
def self.register_review_actions(app) # rubocop:disable Metrics/AbcSize
|
|
58
|
+
def self.register_review_actions(app) # rubocop:disable Metrics/AbcSize
|
|
59
59
|
app.post '/api/marketplace/:name/submit' do
|
|
60
60
|
begin
|
|
61
61
|
Legion::Registry.submit_for_review(params[:name])
|
data/lib/legion/api/rbac.rb
CHANGED
|
@@ -62,7 +62,7 @@ module Legion
|
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
def self.register_assignments(app)
|
|
65
|
+
def self.register_assignments(app)
|
|
66
66
|
app.get '/api/rbac/assignments' do
|
|
67
67
|
return json_error('rbac_unavailable', 'legion-rbac not installed', status_code: 501) unless defined?(Legion::Rbac)
|
|
68
68
|
return json_error('db_unavailable', 'legion-data not connected', status_code: 503) unless Legion::Rbac::Store.db_available?
|
|
@@ -19,7 +19,7 @@ module Legion
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# POST /api/tbi/patterns/export — anonymously export a learned behavioral pattern
|
|
22
|
-
def self.register_export(app)
|
|
22
|
+
def self.register_export(app)
|
|
23
23
|
app.post '/api/tbi/patterns/export' do
|
|
24
24
|
require_data!
|
|
25
25
|
body = parse_request_body
|
data/lib/legion/api/workers.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Legion
|
|
|
12
12
|
register_teams(app)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def self.register_collection(app)
|
|
15
|
+
def self.register_collection(app)
|
|
16
16
|
app.get '/api/workers' do
|
|
17
17
|
require_data!
|
|
18
18
|
dataset = Legion::Data::Model::DigitalWorker.order(:id)
|
|
@@ -53,7 +53,7 @@ module Legion
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
def self.register_member(app) # rubocop:disable Metrics/AbcSize
|
|
56
|
+
def self.register_member(app) # rubocop:disable Metrics/AbcSize
|
|
57
57
|
app.get '/api/workers/:id' do
|
|
58
58
|
require_data!
|
|
59
59
|
worker = Legion::Data::Model::DigitalWorker.first(worker_id: params[:id])
|
|
@@ -120,7 +120,7 @@ module Legion
|
|
|
120
120
|
end
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
-
def self.register_sub_resources(app) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
123
|
+
def self.register_sub_resources(app) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
124
124
|
app.get '/api/workers/:id/health' do
|
|
125
125
|
require_data!
|
|
126
126
|
worker = Legion::Data::Model::DigitalWorker.first(worker_id: params[:id])
|
data/lib/legion/api.rb
CHANGED
|
@@ -34,7 +34,6 @@ require_relative 'api/auth_saml'
|
|
|
34
34
|
require_relative 'api/capacity'
|
|
35
35
|
require_relative 'api/audit'
|
|
36
36
|
require_relative 'api/metrics'
|
|
37
|
-
require_relative 'api/llm'
|
|
38
37
|
require_relative 'api/skills'
|
|
39
38
|
require_relative 'api/catalog'
|
|
40
39
|
require_relative 'api/org_chart'
|
|
@@ -201,7 +200,6 @@ module Legion
|
|
|
201
200
|
register Routes::Capacity
|
|
202
201
|
register Routes::Audit
|
|
203
202
|
register Routes::Metrics
|
|
204
|
-
mount_library_routes('llm', Routes::Llm, 'Legion::LLM::Routes')
|
|
205
203
|
register Routes::Skills
|
|
206
204
|
register Routes::ExtensionCatalog
|
|
207
205
|
register Routes::OrgChart
|
|
@@ -209,6 +207,9 @@ module Legion
|
|
|
209
207
|
register Routes::Acp
|
|
210
208
|
register Routes::Prompts
|
|
211
209
|
register Routes::Marketplace
|
|
210
|
+
# Legion::LLM routes are registered directly from legion-llm.
|
|
211
|
+
# setup_llm runs before setup_api so Legion::LLM is always defined when this loads.
|
|
212
|
+
mount_library_routes('llm', Legion::LLM::API, 'Legion::LLM::Routes') if defined?(Legion::LLM::API)
|
|
212
213
|
mount_library_routes('apollo', Routes::Apollo, 'Legion::Apollo::Routes')
|
|
213
214
|
register Routes::Costs
|
|
214
215
|
register Routes::Traces
|
|
@@ -17,7 +17,7 @@ module Legion
|
|
|
17
17
|
option :until, type: :string, desc: 'Records before this ISO8601 timestamp'
|
|
18
18
|
option :limit, type: :numeric, default: 20, desc: 'Number of records'
|
|
19
19
|
option :json, type: :boolean, default: false, desc: 'Output as JSON'
|
|
20
|
-
def list
|
|
20
|
+
def list
|
|
21
21
|
Connection.ensure_settings
|
|
22
22
|
Connection.ensure_data
|
|
23
23
|
|
|
@@ -66,13 +66,25 @@ module Legion
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def self.consolidate_entries(entries)
|
|
69
|
-
return nil unless defined?(Legion::LLM) && Legion::LLM.respond_to?(:
|
|
69
|
+
return nil unless defined?(Legion::LLM) && Legion::LLM.respond_to?(:chat)
|
|
70
70
|
|
|
71
71
|
numbered = entries.map.with_index(1) { |e, i| "#{i}. #{e}" }.join("\n")
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
response = Legion::LLM.chat(
|
|
74
|
+
message: "#{CONSOLIDATION_PROMPT}\n\nCurrent entries:\n#{numbered}",
|
|
75
|
+
caller: { requested_by: { type: :system, identity: 'legion:internal:cli:consolidate_memory' } }
|
|
76
|
+
)
|
|
77
|
+
extract_response_content(response)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.extract_response_content(response)
|
|
81
|
+
if response.is_a?(Hash)
|
|
82
|
+
(response[:response] || response[:content] || response['response'] || response['content']).to_s
|
|
83
|
+
elsif response.respond_to?(:content)
|
|
84
|
+
response.content.to_s
|
|
85
|
+
else
|
|
86
|
+
response.to_s
|
|
87
|
+
end
|
|
76
88
|
end
|
|
77
89
|
|
|
78
90
|
def self.parse_consolidated(text)
|
|
@@ -60,15 +60,25 @@ module Legion
|
|
|
60
60
|
def self.extract_entries(text)
|
|
61
61
|
return [text] unless llm_available?
|
|
62
62
|
|
|
63
|
-
response = Legion::LLM.
|
|
63
|
+
response = Legion::LLM.chat(
|
|
64
64
|
message: "#{EXTRACTION_PROMPT}\n\nText:\n#{text}",
|
|
65
|
-
|
|
65
|
+
caller: { requested_by: { type: :system, identity: 'legion:internal:cli:reflect' } }
|
|
66
66
|
)
|
|
67
|
-
parse_entries(response
|
|
67
|
+
parse_entries(extract_response_content(response))
|
|
68
68
|
rescue StandardError
|
|
69
69
|
[text]
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
def self.extract_response_content(response)
|
|
73
|
+
if response.is_a?(Hash)
|
|
74
|
+
(response[:response] || response[:content] || response['response'] || response['content']).to_s
|
|
75
|
+
elsif response.respond_to?(:content)
|
|
76
|
+
response.content.to_s
|
|
77
|
+
else
|
|
78
|
+
response.to_s
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
72
82
|
def self.parse_entries(content)
|
|
73
83
|
content.lines
|
|
74
84
|
.map(&:strip)
|
|
@@ -124,7 +134,7 @@ module Legion
|
|
|
124
134
|
end
|
|
125
135
|
|
|
126
136
|
def self.llm_available?
|
|
127
|
-
defined?(Legion::LLM) && Legion::LLM.respond_to?(:
|
|
137
|
+
defined?(Legion::LLM) && Legion::LLM.respond_to?(:chat)
|
|
128
138
|
end
|
|
129
139
|
|
|
130
140
|
def self.api_port
|
|
@@ -19,7 +19,7 @@ module Legion
|
|
|
19
19
|
required: ['pattern']
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
def self.call(pattern:, directory: nil, glob: nil)
|
|
22
|
+
def self.call(pattern:, directory: nil, glob: nil)
|
|
23
23
|
dir = File.expand_path(directory || Dir.pwd)
|
|
24
24
|
return "Error: directory not found: #{dir}" unless Dir.exist?(dir)
|
|
25
25
|
|
|
@@ -243,7 +243,7 @@ module Legion
|
|
|
243
243
|
end
|
|
244
244
|
|
|
245
245
|
def show_away_summary(out)
|
|
246
|
-
return unless defined?(Legion::LLM) && Legion::LLM.respond_to?(:
|
|
246
|
+
return unless defined?(Legion::LLM) && Legion::LLM.respond_to?(:chat)
|
|
247
247
|
|
|
248
248
|
messages = @session.chat.messages.last(30).select { |m| m.respond_to?(:role) }
|
|
249
249
|
return if messages.length < 2
|
|
@@ -256,12 +256,10 @@ module Legion
|
|
|
256
256
|
'Focus on: what task was in progress, what was accomplished, what needs attention next. ' \
|
|
257
257
|
"Skip status reports and commit recaps.\n\nRecent conversation:\n#{summary_input}"
|
|
258
258
|
|
|
259
|
-
response =
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
session.ask(prompt)
|
|
264
|
-
end
|
|
259
|
+
response = Legion::LLM.chat(
|
|
260
|
+
message: prompt,
|
|
261
|
+
caller: { requested_by: { type: :system, identity: 'legion:internal:cli:away_summary' } }
|
|
262
|
+
)
|
|
265
263
|
|
|
266
264
|
text = if response.is_a?(Hash)
|
|
267
265
|
response[:response] || response[:content]
|
|
@@ -101,7 +101,7 @@ module Legion
|
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
desc 'validate', 'Validate current configuration'
|
|
104
|
-
def validate
|
|
104
|
+
def validate
|
|
105
105
|
out = formatter
|
|
106
106
|
Connection.config_dir = options[:config_dir] if options[:config_dir]
|
|
107
107
|
|
|
@@ -248,7 +248,7 @@ module Legion
|
|
|
248
248
|
raise SystemExit, 1
|
|
249
249
|
end
|
|
250
250
|
|
|
251
|
-
no_commands do
|
|
251
|
+
no_commands do
|
|
252
252
|
def formatter
|
|
253
253
|
@formatter ||= Output::Formatter.new(
|
|
254
254
|
json: options[:json],
|