Skip to content

feat: unified naming convention phases 1-3 (v3.0 AMQP + API)#1

Closed
Esity wants to merge 651 commits intoOptum:mainfrom
LegionIO:feature/unified-naming-convention
Closed

feat: unified naming convention phases 1-3 (v3.0 AMQP + API)#1
Esity wants to merge 651 commits intoOptum:mainfrom
LegionIO:feature/unified-naming-convention

Conversation

@Esity
Copy link
Copy Markdown
Member

@Esity Esity commented Mar 28, 2026

Summary

Implements Phases 1-3 of the unified naming convention across the LegionIO framework. This is the breaking-change boundary for v3.0 AMQP topology and API routing.

Phase 1: Definition DSL + Component DSL Standardization (no breaking changes)

  • Legion::Extensions::Definitions mixin: definition DSL for method contracts (desc, inputs, outputs, remote_invocable, mcp_exposed, idempotent, risk_tier, tags, requires). Defaults: remote_invocable: true, mcp_exposed: true, idempotent: false, risk_tier: :standard
  • Legion::Extensions::Actors::Dsl mixin: define_dsl_accessor for inheritable class-level DSL on all actor types
  • Builders::Runners auto-extends Definitions onto every discovered runner module
  • Absorbers::Base#absorb as canonical entry point (alias handle absorb for backward compat)

Phase 2: API Router Standardization

  • Three-tier Legion::API::Router with register_library_routes and register_extension_routes
  • /api/extensions/{lex}/{runner}/{function} dispatch via LexDispatch.build_envelope (propagates X-Legion-Task-Id, X-Legion-Conversation-Id, X-Legion-Parent-Id, X-Legion-Master-Id, X-Legion-Chain-Id, X-Legion-Debug headers)

Phase 3: AMQP Topology Rename (breaking — v2.0 → v3.0)

  • amqp_prefix changed from legion.{lex_name} to lex.{lex_name} for all extension exchanges and queues
  • Queue routing keys now include component type segment: runners.{runner_name} (short-form) and lex.{lex_name}.runners.{runner_name}.# (fully-qualified)
  • generate_messages_from_definitions in Core#autobuild: auto-creates Legion::Transport::Message subclasses for each runner method with a definition, using lex.{lex_name}.runners.{runner_name}.{method} routing key
  • legion admin purge-topology CLI: enumerates/deletes legacy legion.{lex}.* exchanges and queues via RabbitMQ management API; dry-run by default, --execute to delete

Test Plan

  • bundle exec rspec — 3853 examples, 0 failures (verified)
  • bundle exec rubocop — 706 files, 0 offenses (verified)
  • Verify legion admin purge-topology --help lists all options
  • Verify amqp_prefix returns lex.{lex_name} for an extension
  • Verify queue routing keys include runners.{runner_name} segment
  • Verify legacy topology is correctly identified by pattern matching in specs

Migration

Existing RabbitMQ deployments will have stale legion.{lex_name}.* exchanges and queues after this upgrade. Run:

legion admin purge-topology --execute

See docs/protocol/LEGION_WIRE_PROTOCOL.md (updated in companion PR) for the full migration table.

Esity added 30 commits March 22, 2026 21:48
Esity and others added 21 commits March 27, 2026 11:47
#46)

* add absorber matcher system with url pattern matching

* add absorber base class with pattern DSL and knowledge helpers

* add pattern matcher for absorber dispatch resolution

* add absorber builder for auto-discovery during extension boot

* register absorbers as capabilities during extension boot

* add absorber dispatch module for pattern resolution and execution

* add legion generate absorber command for scaffolding new absorbers

* add legion absorb CLI command with url, list, and resolve subcommands

* fix absorb_command_spec test pollution from permanent module monkey-patch

The spec was reopening PatternMatcher and AbsorberDispatch at file load
time, permanently replacing .resolve, .list, and .dispatch with stubs
that return nil. This broke pattern_matcher_spec and absorber_dispatch_spec
when the full suite ran in random order.

Replace the module-level overwrite with proper RSpec allow/receive stubs
in a before block so the originals are restored after each example.

* bump version to 1.6.13

* apply copilot review suggestions (#46)

* fix config validate transport host check and doctor settings loading

* fix secret resolution in cli commands and check credential validation

* apply copilot re-review suggestions (#46)

- absorber_dispatch: switch publish_event to Messages::Dynamic with session open? guard
- extensions: add per-absorber error rescue in register_absorber_capabilities
- builders/absorbers: key absorbers hash by snake_case filename, not CamelCase
- absorbers/base: guard Apollo availability in absorb_to_knowledge; extract helpers to reduce complexity
- CHANGELOG: correct generator invocation to legionio dev generate absorber

* fix image analyze llm call and trace search llm boot

* fix Open3 thread leak in RunCommand and SimpleCov exit code (#46)

- replace Timeout.timeout + Open3.capture3 with Open3.popen3 and
  manual process kill on timeout to prevent dangling reader threads
- set SimpleCov.external_at_exit to prevent exit code 1 from
  SystemExit in Ruby 3.4 + RSpec

* apply copilot re-review suggestions (#46)

- absorb_raw: return structured { success: false, error: :apollo_not_available } instead of nil
- apollo_available?: also gate on Legion::Apollo.started? when available
- check_transport: redact vault/lease URI in error message (emit scheme only)
- check_data: extract raise_if_unresolved_data_creds helper, report unresolved fields + scheme hints without leaking paths
- trace_command: setup_connection returns false on CLI::Error (prints error); search/summarize gate on return value and run Connection.shutdown in ensure

* remove permanently-pending cross-gem integration specs (#46)

MCP, codegen, eval specs belong in their own gem suites.
Keeps the function metadata DSL spec that runs locally.

* fix SimpleCov exit code 1 on CI by overriding previous_error? (#46)

* temporarily disable detect_lex spec that leaks SystemExit (#46)

* fix CI exit code 1: update image_command_spec for chat.ask() interface, fix trace_command Connection resolution (#46)

image_command_spec stubs returned plain hash for Legion::LLM.chat but
code changed to call chat.ask() on the result. NoMethodError was caught
by rescue StandardError which raised SystemExit(1), killing the RSpec
process mid-suite (1944 of 3767 specs ran, 0 recorded failures, exit 1).

trace_command.rb used bare Connection constant which couldn't resolve
inside Thor subclass — now uses fully qualified Legion::CLI::Connection
and requires the connection module. Spec stubs Connection methods.

* add explicit requires for absorber constants in absorb_command (#46)

* remove duplicate test job from ci-cd.yml (#46)

the test job (rspec + rubocop with rabbitmq/postgres) duplicates what
the shared ci.yml workflow already runs. keep only helm-lint which is
unique to this workflow.

* trigger CI with updated shared workflow

* trigger ci with rabbitmq cookie fix

* trigger ci with init container cookie fix
#46)

* add absorber matcher system with url pattern matching

* add absorber base class with pattern DSL and knowledge helpers

* add pattern matcher for absorber dispatch resolution

* add absorber builder for auto-discovery during extension boot

* register absorbers as capabilities during extension boot

* add absorber dispatch module for pattern resolution and execution

* add legion generate absorber command for scaffolding new absorbers

* add legion absorb CLI command with url, list, and resolve subcommands

* fix absorb_command_spec test pollution from permanent module monkey-patch

The spec was reopening PatternMatcher and AbsorberDispatch at file load
time, permanently replacing .resolve, .list, and .dispatch with stubs
that return nil. This broke pattern_matcher_spec and absorber_dispatch_spec
when the full suite ran in random order.

Replace the module-level overwrite with proper RSpec allow/receive stubs
in a before block so the originals are restored after each example.

* bump version to 1.6.13

* apply copilot review suggestions (#46)

* fix config validate transport host check and doctor settings loading

* fix secret resolution in cli commands and check credential validation

* apply copilot re-review suggestions (#46)

- absorber_dispatch: switch publish_event to Messages::Dynamic with session open? guard
- extensions: add per-absorber error rescue in register_absorber_capabilities
- builders/absorbers: key absorbers hash by snake_case filename, not CamelCase
- absorbers/base: guard Apollo availability in absorb_to_knowledge; extract helpers to reduce complexity
- CHANGELOG: correct generator invocation to legionio dev generate absorber

* fix image analyze llm call and trace search llm boot

* fix Open3 thread leak in RunCommand and SimpleCov exit code (#46)

- replace Timeout.timeout + Open3.capture3 with Open3.popen3 and
  manual process kill on timeout to prevent dangling reader threads
- set SimpleCov.external_at_exit to prevent exit code 1 from
  SystemExit in Ruby 3.4 + RSpec

* apply copilot re-review suggestions (#46)

- absorb_raw: return structured { success: false, error: :apollo_not_available } instead of nil
- apollo_available?: also gate on Legion::Apollo.started? when available
- check_transport: redact vault/lease URI in error message (emit scheme only)
- check_data: extract raise_if_unresolved_data_creds helper, report unresolved fields + scheme hints without leaking paths
- trace_command: setup_connection returns false on CLI::Error (prints error); search/summarize gate on return value and run Connection.shutdown in ensure

* remove permanently-pending cross-gem integration specs (#46)

MCP, codegen, eval specs belong in their own gem suites.
Keeps the function metadata DSL spec that runs locally.

* fix SimpleCov exit code 1 on CI by overriding previous_error? (#46)

* temporarily disable detect_lex spec that leaks SystemExit (#46)

* fix CI exit code 1: update image_command_spec for chat.ask() interface, fix trace_command Connection resolution (#46)

image_command_spec stubs returned plain hash for Legion::LLM.chat but
code changed to call chat.ask() on the result. NoMethodError was caught
by rescue StandardError which raised SystemExit(1), killing the RSpec
process mid-suite (1944 of 3767 specs ran, 0 recorded failures, exit 1).

trace_command.rb used bare Connection constant which couldn't resolve
inside Thor subclass — now uses fully qualified Legion::CLI::Connection
and requires the connection module. Spec stubs Connection methods.

* add explicit requires for absorber constants in absorb_command (#46)

* remove duplicate test job from ci-cd.yml (#46)

the test job (rspec + rubocop with rabbitmq/postgres) duplicates what
the shared ci.yml workflow already runs. keep only helm-lint which is
unique to this workflow.

* trigger CI with updated shared workflow

* trigger ci with rabbitmq cookie fix

* trigger ci with init container cookie fix
…ging

structured exception logging: service wiring, handle_exception, call site sweep
…pollo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
)

- add POST /api/llm/inference endpoint: accepts messages array + tool schemas,
  runs a single LLM completion pass, returns content/tool_calls/stop_reason/tokens
- add DaemonChat adapter (lib/legion/cli/chat/daemon_chat.rb): drop-in replacement
  for RubyLLM::Chat that routes inference through daemon and executes tool calls locally
- rewrite chat setup_connection: hard fail with descriptive error if daemon unavailable
- rewrite chat create_chat: returns DaemonChat instead of direct RubyLLM::Chat
- add 37 new specs covering endpoint and adapter (0 failures)
- bump version to 1.6.22
route legion chat LLM requests through daemon (#48)
Phase 1 of unified naming convention:
- Add Legion::Extensions::Definitions mixin with definition/definitions/definition_for DSL
- Auto-extend Definitions onto every runner module at boot via builder
- Wire Definitions into Hooks::Base and Absorbers::Base
- Add Legion::Extensions::Actors::Dsl with define_dsl_accessor for class-level actor config
- Wire Dsl into Every, Poll, Subscription, Once, and Base actors
- Rename absorber entry point from handle to absorb (alias for compat)
- Remove mount DSL from Hooks::Base (paths derived from naming)
- Mark function_* helpers and expose_as_mcp_tool deprecated
Phase 2 of unified naming convention:
- Add Legion::API::Router for tier-aware route registration (infrastructure/library/extension)
- Add /api/extensions/:lex/:component_type/:name/:method dispatch (POST) and contract discovery (GET)
- Add /api/extensions/index listing endpoint
- Add /api/discovery root endpoint listing all tiers
- Wire router into builder: register_extension_route on every runner and hook at autobuild
- Standardize error envelope to include task_id, conversation_id, status fields
- Remove mount_path from hooks builder (paths fully derived from naming)
- Bump version to 1.6.23
@Esity Esity requested a review from Copilot March 28, 2026 06:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a broad set of v3.0 framework updates, including new CLI chat tooling, expanded REST/GraphQL API surface, infrastructure/deployment assets, and dependency/runtime upgrades aligned with the “unified naming convention” milestone.

Changes:

  • Adds a large set of CLI Chat “tools” (RubyLLM-based), registries/loaders, and supporting utilities (permissions, checkpointing, session storage, subagents).
  • Adds multiple new API routes/middleware (auth token exchange, governance, costs, traces, capacity, catalog, hooks, transport, settings, webhooks, SSE events) plus GraphQL schema/types/resolvers.
  • Updates packaging/runtime/deployment: Ruby 3.4+, gemspec dependencies, new Dockerfile, Helm chart, TLS config assets, and GitHub workflows.

Reviewed changes

Copilot reviewed 167 out of 771 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
lib/legion/cli/chat/tools/list_extensions.rb Adds chat tool to query extensions via HTTP API.
lib/legion/cli/chat/tools/knowledge_stats.rb Adds chat tool to fetch Apollo stats via HTTP API.
lib/legion/cli/chat/tools/knowledge_maintenance.rb Adds chat tool to run Apollo maintenance via HTTP POST.
lib/legion/cli/chat/tools/ingest_knowledge.rb Adds chat tool to ingest knowledge into Apollo.
lib/legion/cli/chat/tools/escalation_status.rb Adds chat tool to report LLM escalation tracking.
lib/legion/cli/chat/tools/entity_extract.rb Adds chat tool for Apollo entity extraction runner.
lib/legion/cli/chat/tools/edit_file.rb Adds file editing tool with checkpointing.
lib/legion/cli/chat/tools/detect_anomalies.rb Adds chat tool to call anomaly detection API endpoint.
lib/legion/cli/chat/tools/consolidate_memory.rb Adds memory consolidation tool using LLM summarization.
lib/legion/cli/chat/tools/budget_status.rb Adds local budget/cost-status tool (no daemon required).
lib/legion/cli/chat/tools/arbitrage_status.rb Adds tool to report arbitrage/pricing selection status.
lib/legion/cli/chat/tool_registry.rb Registers built-in tools and dynamically discovers extension tools.
lib/legion/cli/chat/team.rb Adds per-thread “current user” context helper.
lib/legion/cli/chat/subagent.rb Adds concurrent headless subagent runner using threads + Open3.
lib/legion/cli/chat/status_indicator.rb Adds spinner UI driven by session events.
lib/legion/cli/chat/session_store.rb Adds on-disk save/load/list/restore for chat sessions.
lib/legion/cli/chat/session.rb Adds session wrapper with event callbacks + budget guard.
lib/legion/cli/chat/progress_bar.rb Adds CLI progress bar utility.
lib/legion/cli/chat/permissions.rb Adds interactive/auto gating for tool execution by permission tier.
lib/legion/cli/chat/memory_store.rb Adds simple markdown-backed memory store (project/global).
lib/legion/cli/chat/markdown_renderer.rb Adds ANSI markdown renderer w/ Rouge highlighting fallback.
lib/legion/cli/chat/extension_tool_loader.rb Loads RubyLLM tools shipped inside extensions with tier overrides.
lib/legion/cli/chat/extension_tool.rb Adds mixin for extension tools to declare permission tiers.
lib/legion/cli/chat/checkpoint.rb Adds rollback/checkpoint support for file modifications.
lib/legion/cli/chat/chat_logger.rb Adds rotating file logger for chat.
lib/legion/cli/chat/agent_registry.rb Adds agent config discovery (JSON/YAML) from .legion/agents.
lib/legion/cli/chat/agent_delegator.rb Routes @agent and /agent invocations to subagents.
lib/legion/cli/chain_command.rb Adds Thor-based chain CLI for listing/creating/deleting chains.
lib/legion/cli/chain.rb Adds frozen string literal magic comment.
lib/legion/cli/acp_command.rb Adds CLI entry for ACP agent (stdio).
lib/legion/cli/absorb_command.rb Adds CLI entry for absorber dispatch and pattern listing/resolve.
lib/legion/chat/skills.rb Adds discovery/parser for skills markdown with YAML frontmatter.
lib/legion/chat/notification_queue.rb Adds thread-safe priority queue for notifications.
lib/legion/chat/notification_bridge.rb Bridges Legion events → notification queue w/ pattern priority mapping.
lib/legion/catalog.rb Adds client to register tools/workers to external catalog via HTTP.
lib/legion/capacity/model.rb Adds capacity aggregation/forecast model for workers.
lib/legion/audit/siem_export.rb Adds export helpers for SIEM-friendly payloads.
lib/legion/audit/hash_chain.rb Adds hash chain compute/verify helpers for audit integrity.
lib/legion/audit/cold_storage.rb Adds cold storage abstraction (local / S3).
lib/legion/audit/archiver_actor.rb Adds scheduled archival actor wrapper with cron field parsing.
lib/legion/audit.rb Adds audit event recording + query helpers.
lib/legion/api/workflow.rb Adds relationship graph route backed by graph builder.
lib/legion/api/webhooks.rb Adds basic CRUD routes for webhook registration.
lib/legion/api/validators.rb Adds request validation helper methods (required, enum, uuid, int bounds).
lib/legion/api/transport.rb Adds transport status/discovery/publish endpoints.
lib/legion/api/traces.rb Adds trace search/summary/anomalies/trend routes.
lib/legion/api/token.rb Adds JWT issuing helpers for worker/human tokens.
lib/legion/api/tenants.rb Adds tenant management/quota endpoints.
lib/legion/api/tasks.rb Adds task collection/member/log endpoints plus API-based task creation.
lib/legion/api/settings.rb Adds API access to settings with redaction and write restrictions.
lib/legion/api/schedules.rb Adds scheduler CRUD endpoints and logs endpoint.
lib/legion/api/router.rb Adds route registry/router abstraction for infra/library/extension routes.
lib/legion/api/relationships.rb Adds CRUD endpoints for relationships.
lib/legion/api/prompts.rb Adds prompt list/show/run endpoints via lex-prompt client + LLM.
lib/legion/api/org_chart.rb Adds org-chart endpoint combining extensions/functions/workers.
lib/legion/api/nodes.rb Adds node list/show endpoints.
lib/legion/api/middleware/tenant.rb Adds middleware to resolve/attach tenant context to requests.
lib/legion/api/middleware/request_logger.rb Adds API request timing logger middleware.
lib/legion/api/middleware/body_limit.rb Adds request size limit middleware.
lib/legion/api/middleware/api_version.rb Adds v1 rewrite + deprecation headers for unversioned APIs.
lib/legion/api/metrics.rb Adds Prometheus metrics endpoint.
lib/legion/api/lex.rb Adds LEX route listing + dispatch endpoint.
lib/legion/api/hooks.rb Adds hooks registry listing + verification + dispatch handler endpoints.
lib/legion/api/graphql/types/worker_type.rb Adds GraphQL Worker type.
lib/legion/api/graphql/types/task_type.rb Adds GraphQL Task type.
lib/legion/api/graphql/types/query_type.rb Adds GraphQL root query and resolver mapping.
lib/legion/api/graphql/types/node_type.rb Adds GraphQL Node type.
lib/legion/api/graphql/types/extension_type.rb Adds GraphQL Extension type.
lib/legion/api/graphql/types/base_object.rb Adds GraphQL BaseObject.
lib/legion/api/graphql/schema.rb Adds GraphQL schema config (depth/complexity).
lib/legion/api/graphql/resolvers/workers.rb Adds worker resolvers from data or registry fallback.
lib/legion/api/graphql/resolvers/tasks.rb Adds task resolvers from data.
lib/legion/api/graphql/resolvers/node.rb Adds node resolver.
lib/legion/api/graphql/resolvers/extensions.rb Adds extension resolvers from data or registry fallback.
lib/legion/api/graphql.rb Adds GraphQL endpoint + GraphiQL HTML.
lib/legion/api/governance.rb Adds governance approvals endpoints backed by lex-audit runner.
lib/legion/api/gaia.rb Adds Gaia status/channels/buffer/sessions and Teams webhook ingest.
lib/legion/api/extensions.rb Adds extension/runner/function listing and invoke route.
lib/legion/api/events.rb Adds SSE streaming + recent events buffer endpoint.
lib/legion/api/costs.rb Adds metering cost summary and breakdown endpoints.
lib/legion/api/coldstart.rb Adds coldstart ingest endpoint for file/dir ingestion.
lib/legion/api/codegen.rb Adds codegen status/generated/gaps/cycle endpoints.
lib/legion/api/chains.rb Adds chain CRUD endpoints.
lib/legion/api/catalog.rb Adds extension catalog manifest endpoints.
lib/legion/api/capacity.rb Adds capacity aggregate/forecast/per-worker endpoints.
lib/legion/api/auth.rb Adds OAuth token-exchange style endpoint for Entra → Legion tokens.
lib/legion/api/audit.rb Adds audit listing and verify endpoints.
lib/legion/api/acp.rb Adds ACP well-known agent card + task create/status endpoints.
lib/legion.rb Updates boot requires and adds autoloads for Region/Lock/Leader.
legionio.gemspec Updates metadata, required Ruby, executables, and runtime dependencies.
exe/lex_gen Removes legacy lex generator executable.
exe/legionio Replaces legacy daemon/optparse launcher with CLI entry + performance tuning.
exe/legion Adds interactive TTY default behavior and headless chat for piped stdin.
docs/README.md Adds “Moved” doc stub referencing local-only docs path.
docker_deploy.rb Adds frozen string literal comment.
deploy/helm/legion/values.yaml Adds Helm chart values for api/worker and dependencies.
deploy/helm/legion/templates/serviceaccount.yaml Adds ServiceAccount template.
deploy/helm/legion/templates/service-api.yaml Adds API Service template.
deploy/helm/legion/templates/pdb.yaml Adds PodDisruptionBudget template.
deploy/helm/legion/templates/hpa-worker.yaml Adds worker HPA template.
deploy/helm/legion/templates/deployment-worker.yaml Adds worker Deployment template.
deploy/helm/legion/templates/deployment-api.yaml Adds API Deployment template.
deploy/helm/legion/templates/_helpers.tpl Adds Helm helper templates for naming/labels.
deploy/helm/legion/Chart.yaml Adds Helm chart metadata.
config/tls/settings-tls.json Adds TLS settings example for transport/data/cache/api.
config/tls/generate-certs.sh Adds local dev certificate generation script.
config/tls/README.md Adds TLS quick-start docs.
attribution.txt Removes placeholder attribution file.
SECURITY.md Removes placeholder security policy file.
NOTICE.txt Removes notice file with prior attribution.
LICENSE Updates copyright line.
INDIVIDUAL_CONTRIBUTOR_LICENSE.md Removes prior CLA text.
Gemfile Updates dependencies and local path overrides for workspace development.
Dockerfile Replaces Alpine image with multi-stage Debian slim build/runtime + healthcheck.
CONTRIBUTING.md Removes prior Optum contribution guidelines.
CODE_OF_CONDUCT.md Removes prior code of conduct.
CODEOWNERS Adds CODEOWNERS config.
.rubocop.yml Updates RuboCop config for Ruby 3.4 and new excludes.
.github/workflows/sourcehawk-scan.yml Removes Sourcehawk workflow.
.github/workflows/rubocop.yml Removes legacy RuboCop workflow.
.github/workflows/rspec.yml Removes legacy RSpec workflow.
.github/workflows/publish-homebrew.yml Adds release-triggered Homebrew dispatch workflow.
.github/workflows/ci.yml Adds reusable-workflow-based CI, release, docker publish, etc.
.github/workflows/ci-cd.yml Adds Helm lint workflow.
.github/workflow-templates/eval-gate.yml Adds eval-gate workflow template.
.github/dependabot.yml Adds Dependabot for bundler and GitHub Actions.
.github/CODEOWNERS Adds org/team-based CODEOWNERS for repo paths.
.dockerignore Adds docker context excludes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Esity Esity requested a review from Copilot March 28, 2026 13:24
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 168 out of 773 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

replace busy-wait loop in SyncDispatch.dispatch with Mutex +
ConditionVariable; extract perform_dispatch, subscribe_reply, and
wait_for_response helpers to keep method lengths within rubocop limits
@Esity Esity requested a review from Copilot March 28, 2026 13:33
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 168 out of 773 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +15 to +24
app.post '/api/tenants' do
params = parsed_body
result = Legion::Tenants.create(
tenant_id: params['tenant_id'],
name: params['name'],
max_workers: params['max_workers'] || 10
)
status result[:error] ? 409 : 201
json_response(data: result)
end
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This route uses parsed_body, while the rest of the API routes introduced in this PR use parse_request_body. If parsed_body isn’t defined on this Sinatra app, this will raise at runtime; even if it is defined, it’s inconsistent in key type expectations (string keys here vs symbol keys elsewhere). Use the same request parsing helper as the other routes for consistency and to avoid runtime failures.

Copilot uses AI. Check for mistakes.
if extension
node_ids = nodes.select { |n| n[:extension] == extension }.map { |n| n[:id] }
nodes = nodes.select { |n| node_ids.include?(n[:id]) }
edges = edges.select { |e| node_ids.include?(e[:source]) || node_ids.include?(e[:target]) }
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When filtering by extension, edges are kept if either endpoint matches, but nodes is filtered to only the matching set. This can yield edges referencing nodes that are no longer present in the nodes list, producing an inconsistent graph response. Either (a) filter edges to those whose source and target are in node_ids, or (b) include the neighboring nodes needed to satisfy the retained edges.

Suggested change
edges = edges.select { |e| node_ids.include?(e[:source]) || node_ids.include?(e[:target]) }
edges = edges.select { |e| node_ids.include?(e[:source]) && node_ids.include?(e[:target]) }

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +72
Legion::Events.on('*') do |event|
push_event(event.transform_keys(&:to_s))
end
@listener_installed = true
end

def registered(app)
install_listener if defined?(Legion::Events)

app.get '/api/events' do
content_type 'text/event-stream'
headers 'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'X-Accel-Buffering' => 'no'

queue = Queue.new
listener = Legion::Events.on('*') do |event|
queue.push(event)
end

stream do |out|
Thread.new do
loop do
event = queue.pop
data = Legion::JSON.dump(event.transform_keys(&:to_s))
out << "event: #{event[:event]}\ndata: #{data}\n\n"
rescue IOError, Errno::EPIPE => e
Legion::Logging.debug "Events SSE stream broken for #{event[:event]}: #{e.message}" if defined?(Legion::Logging)
break
end
ensure
Legion::Events.off('*', listener)
end

out.callback { Legion::Events.off('*', listener) }
out.errback { Legion::Events.off('*', listener) }
end
end
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two concrete issues in this SSE implementation: (1) the background thread blocks indefinitely on queue.pop even after the client disconnects—out.callback/errback removes the listener but does not stop the thread—leading to leaked threads per connection; and (2) this file treats the Legion::Events.on('*') callback payload as a single event hash, while other new code in this PR (e.g., NotificationBridge) uses (event_name, payload), so one of these is likely incompatible with the actual Events API. Consider: stopping the thread on disconnect (e.g., a closed flag + pushing a sentinel/using timed pop) and aligning the Events.on callback signature/format so event: name and data: payload are reliably produced.

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +85
def spawn(task:, model: nil, provider: nil, on_complete: nil)
return { error: "Max concurrency reached (#{@max_concurrency}). Wait for a subagent to finish." } if at_capacity?

agent_id = "agent-#{Time.now.strftime('%H%M%S')}-#{rand(1000)}"

thread = Thread.new do
result = run_headless(task: task, model: model, provider: provider)
@mutex.synchronize { @running.delete_if { |a| a[:id] == agent_id } }
on_complete&.call(agent_id, result)
rescue StandardError => e
Legion::Logging.error("Subagent#spawn thread error for #{agent_id}: #{e.message}") if defined?(Legion::Logging)
@mutex.synchronize { @running.delete_if { |a| a[:id] == agent_id } }
on_complete&.call(agent_id, { error: e.message })
end

entry = { id: agent_id, task: task, thread: thread, started_at: Time.now }
@mutex.synchronize { @running << entry }

{ id: agent_id, status: 'running', task: task }
end

def running
@mutex.synchronize { @running.map { |a| { id: a[:id], task: a[:task], elapsed: Time.now - a[:started_at] } } }
end

def running_count
@mutex.synchronize { @running.length }
end

def at_capacity?
@mutex.synchronize { @running.length >= @max_concurrency }
end

def wait_all(timeout: @timeout || TIMEOUT)
deadline = Time.now + timeout
@running.each do |agent|
remaining = deadline - Time.now
break if remaining <= 0

agent[:thread]&.join(remaining)
end
end
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concurrency limits are not enforced atomically: spawn checks at_capacity? under the mutex, but then adds to @running later—multiple callers can pass the check concurrently and exceed @max_concurrency. Also, wait_all iterates @running without synchronization while worker threads remove entries, which can lead to inconsistent behavior. Wrap the capacity check + insertion into a single @mutex.synchronize block, and have wait_all iterate over a snapshot captured under the mutex.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +33
extensions = Legion::Data::Model::Extension.all
workers = Legion::Data::Model::DigitalWorker.all

extensions.map do |ext|
functions = Legion::Data::Model::Function.where(extension_id: ext.id).all
{
name: ext.name,
roles: functions.map do |func|
ext_workers = workers.select { |w| w.extension_name == ext.name }
{
name: func.name,
workers: ext_workers.map { |w| { id: w.id, name: w.name, status: w.lifecycle_state } }
}
end
}
end
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation does an N+1 query for Function.where(extension_id: ext.id) inside the extensions loop, and also re-filters workers for the same extension on every function iteration. To avoid the extra DB round-trips and repeated O(n) scans, fetch/group functions by extension_id once, and pre-group workers by extension_name once, then reuse those groups while building the response.

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +109
def persist_entry(entry)
return unless entry.existed

safe_name = entry.path.gsub('/', '_').gsub('\\', '_')
backup_path = File.join(storage_dir, "#{@entries.length}_#{safe_name}")
File.write(backup_path, entry.content, encoding: 'utf-8')
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using @entries.length in the backup filename can cause collisions/overwrites when older entries are shifted off due to @max_depth (the length will shrink and previously used prefixes can repeat). Use a monotonically increasing sequence (stored separately), a timestamp, or a UUID for backup filenames to ensure uniqueness.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +13
module Routes
module GraphQL
def self.registered(app)
app.post '/api/graphql' do
content_type :json
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR title/description focuses on unified naming convention Phases 1–3 (AMQP topology + API routing), but this change set also introduces large new surfaces like GraphQL endpoints (and additionally Helm/Docker/TLS scaffolding in other files). Either update the PR description to reflect these additional deliverables or split the unrelated features into separate PRs to keep the breaking-change boundary and migration scope clear.

Copilot uses AI. Check for mistakes.
@Esity Esity closed this Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants