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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -9
  3. data/AGENTS.md +48 -6
  4. data/CHANGELOG.md +6 -0
  5. data/CLAUDE.md +20 -1
  6. data/Gemfile +5 -3
  7. data/README.md +48 -13
  8. data/exe/legion +0 -11
  9. data/exe/replay_ledger +2 -2
  10. data/lib/legion/api/auth.rb +1 -1
  11. data/lib/legion/api/auth_human.rb +1 -1
  12. data/lib/legion/api/auth_worker.rb +1 -1
  13. data/lib/legion/api/chains.rb +1 -1
  14. data/lib/legion/api/codegen.rb +1 -1
  15. data/lib/legion/api/lex_dispatch.rb +1 -1
  16. data/lib/legion/api/marketplace.rb +1 -1
  17. data/lib/legion/api/rbac.rb +1 -1
  18. data/lib/legion/api/tbi_patterns.rb +1 -1
  19. data/lib/legion/api/workers.rb +3 -3
  20. data/lib/legion/api.rb +3 -2
  21. data/lib/legion/cli/admin/purge_topology.rb +1 -1
  22. data/lib/legion/cli/audit_command.rb +1 -1
  23. data/lib/legion/cli/broker_command.rb +1 -1
  24. data/lib/legion/cli/chat/tools/consolidate_memory.rb +16 -4
  25. data/lib/legion/cli/chat/tools/reflect.rb +14 -4
  26. data/lib/legion/cli/chat/tools/search_content.rb +1 -1
  27. data/lib/legion/cli/chat_command.rb +5 -7
  28. data/lib/legion/cli/commit_command.rb +1 -1
  29. data/lib/legion/cli/config_command.rb +2 -2
  30. data/lib/legion/cli/config_scaffold.rb +2 -2
  31. data/lib/legion/cli/eval_command.rb +1 -1
  32. data/lib/legion/cli/setup_command.rb +4 -4
  33. data/lib/legion/cli/task_command.rb +1 -1
  34. data/lib/legion/cli/trigger.rb +1 -1
  35. data/lib/legion/cli/worker_command.rb +1 -1
  36. data/lib/legion/cli.rb +1 -1
  37. data/lib/legion/extensions/actors/subscription.rb +3 -3
  38. data/lib/legion/extensions.rb +26 -17
  39. data/lib/legion/ingress.rb +1 -1
  40. data/lib/legion/memory/consolidator.rb +16 -4
  41. data/lib/legion/runner.rb +2 -2
  42. data/lib/legion/service.rb +4 -10
  43. data/lib/legion/version.rb +1 -1
  44. metadata +1 -2
  45. data/lib/legion/api/llm.rb +0 -460
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b24faa2a8eb6db1841c7e6e181b9f1470dc2c3167efd58008d923cdfa25c41f8
4
- data.tar.gz: f0af7a1459f4edb91751fb71161a02675f4b49c91c194d64100d7d33f7cbccbf
3
+ metadata.gz: 052a20d232d946844926289c16dfd82854ab154f50228d4634145e4464340160
4
+ data.tar.gz: f9b14bd14bc1e069025bbde580f59e443d325b2acb8324cad3e356c8322824c5
5
5
  SHA512:
6
- metadata.gz: a8a3eb0fdf4fc17f105f282b8b1f4cd8ffface0429f3e74622f8d321b7c29488a0515fe67e8b50dbcc28cee70d151a4d03aa103bc0c78cb6405efdbecc3be6bc
7
- data.tar.gz: ad3250e699dce50fc0edc7786dae7f2fa9371ab6639fe4d14f113a9c3aa47a04249d7cd7ef6928de0cb922456c82eb8157a09b792608c62972cf9941dbe52329
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: 50
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: 52
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: 62
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: 15
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: 17
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
- # AGENTS.md
1
+ # LegionIO — Agent Notes
2
2
 
3
- Instructions for AI agents working in this repository.
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
- ## Pre-Commit Requirements
8
+ ## Fast Start
6
9
 
7
- Always run a full `bundle exec rspec` and `bundle exec rubocop -A` and fix all errors before committing.
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
- ## Repository Context
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
- This is the primary gem (`legionio`) of the LegionIO framework. See `CLAUDE.md` for full architecture, file map, and conventions.
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
- `exe/legion` applies: YJIT, GC tuning (600k heap slots), bootsnap cache.
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
- # gem 'lex-lex', path: '../extensions/lex-lex' if File.exist?(File.expand_path('../extensions/lex-lex', __dir__))
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
- # LegionIO
1
+ <h1 align="center">LegionIO</h1>
2
2
 
3
- **An extensible async job engine, AI coding assistant, and cognitive computing platform for Ruby.**
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
- Schedule tasks, chain services into dependency graphs, run them concurrently via RabbitMQ, and orchestrate AI-powered workflows — from a single `legion` command.
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
- 280+ extensions · 60 MCP tools
12
- AI chat CLI · REST API · HA
13
- cognitive architecture · Vault
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
- [![Gem Version](https://img.shields.io/gem/v/legionio.svg)](https://rubygems.org/gems/legionio)
18
- [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.4-red.svg)](https://www.ruby-lang.org/)
19
- [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
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
- **Ruby >= 3.4** | **v1.9.18** | **Apache-2.0** | [@Esity](https://github.com/Esity)
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 three performance optimizations:
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** — caches YARV bytecodes and `$LOAD_PATH` resolution at `~/.legionio/cache/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/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/ParameterLists
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/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/ParameterLists
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
 
@@ -8,7 +8,7 @@ module Legion
8
8
  register_token_exchange(app)
9
9
  end
10
10
 
11
- def self.register_token_exchange(app) # rubocop:disable Metrics/MethodLength
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) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
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) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
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
@@ -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, Metrics/MethodLength
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)
@@ -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) # rubocop:disable Metrics/MethodLength
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,Metrics/AbcSize,Metrics/CyclomaticComplexity
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, Metrics/MethodLength
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])
@@ -62,7 +62,7 @@ module Legion
62
62
  end
63
63
  end
64
64
 
65
- def self.register_assignments(app) # rubocop:disable Metrics/AbcSize
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) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
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
@@ -12,7 +12,7 @@ module Legion
12
12
  register_teams(app)
13
13
  end
14
14
 
15
- def self.register_collection(app) # rubocop:disable Metrics/AbcSize
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, Metrics/MethodLength
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, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
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
@@ -54,7 +54,7 @@ module Legion
54
54
  exit(1)
55
55
  end
56
56
 
57
- no_commands do # rubocop:disable Metrics/BlockLength
57
+ no_commands do
58
58
  def formatter
59
59
  @formatter ||= Output::Formatter.new(
60
60
  json: options[:json],
@@ -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 # rubocop:disable Metrics/AbcSize
20
+ def list
21
21
  Connection.ensure_settings
22
22
  Connection.ensure_data
23
23
 
@@ -109,7 +109,7 @@ module Legion
109
109
  exit(1)
110
110
  end
111
111
 
112
- no_commands do # rubocop:disable Metrics/BlockLength
112
+ no_commands do
113
113
  def formatter
114
114
  @formatter ||= Output::Formatter.new(
115
115
  json: options[:json],
@@ -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?(:chat_direct)
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
- session = Legion::LLM.chat_direct(model: nil, provider: nil)
74
- response = session.ask("#{CONSOLIDATION_PROMPT}\n\nCurrent entries:\n#{numbered}")
75
- response.content
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.chat_direct(
63
+ response = Legion::LLM.chat(
64
64
  message: "#{EXTRACTION_PROMPT}\n\nText:\n#{text}",
65
- model: nil, provider: nil
65
+ caller: { requested_by: { type: :system, identity: 'legion:internal:cli:reflect' } }
66
66
  )
67
- parse_entries(response.content)
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?(:chat_direct)
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) # rubocop:disable Metrics/CyclomaticComplexity
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?(:chat_direct)
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 = if Legion::LLM.respond_to?(:ask_direct)
260
- Legion::LLM.ask_direct(message: prompt, model: nil, provider: nil)
261
- else
262
- session = Legion::LLM.chat_direct(model: nil, provider: nil)
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]
@@ -75,7 +75,7 @@ module Legion
75
75
  end
76
76
  default_task :generate
77
77
 
78
- no_commands do # rubocop:disable Metrics/BlockLength
78
+ no_commands do
79
79
  def formatter
80
80
  @formatter ||= Output::Formatter.new(
81
81
  json: options[:json],
@@ -101,7 +101,7 @@ module Legion
101
101
  end
102
102
 
103
103
  desc 'validate', 'Validate current configuration'
104
- def validate # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
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 # rubocop:disable Metrics/BlockLength
251
+ no_commands do
252
252
  def formatter
253
253
  @formatter ||= Output::Formatter.new(
254
254
  json: options[:json],