Module: Legion::MCP::ContextCompiler

Defined in:
lib/legion/mcp/context_compiler.rb

Constant Summary collapse

CATEGORIES =
{
  tasks:         {
    tools:   %w[legion.run_task legion.list_tasks legion.get_task legion.delete_task legion.get_task_logs],
    summary: 'Create, list, query, and delete tasks. Run functions via dot-notation task identifiers.'
  },
  chains:        {
    tools:   %w[legion.list_chains legion.create_chain legion.update_chain legion.delete_chain],
    summary: 'Manage task chains - ordered sequences of tasks that execute in series.'
  },
  relationships: {
    tools:   %w[legion.list_relationships legion.create_relationship legion.update_relationship
                legion.delete_relationship],
    summary: 'Manage trigger-action relationships between functions.'
  },
  extensions:    {
    tools:   %w[legion.list_extensions legion.get_extension legion.enable_extension
                legion.disable_extension],
    summary: 'Manage LEX extensions - list installed, inspect details, enable/disable.'
  },
  schedules:     {
    tools:   %w[legion.list_schedules legion.create_schedule legion.update_schedule legion.delete_schedule],
    summary: 'Manage scheduled tasks - cron-style recurring task execution.'
  },
  workers:       {
    tools:   %w[legion.list_workers legion.show_worker legion.worker_lifecycle legion.worker_costs],
    summary: 'Manage digital workers - list, inspect, lifecycle transitions, cost tracking.'
  },
  rbac:          {
    tools:   %w[legion.rbac_check legion.rbac_assignments legion.rbac_grants],
    summary: 'Role-based access control - check permissions, view assignments and grants.'
  },
  status:        {
    tools:   %w[legion.get_status legion.get_config legion.team_summary legion.routing_stats],
    summary: 'System status, configuration, team overview, and routing statistics.'
  },
  describe:      {
    tools:   %w[legion.describe_runner],
    summary: 'Inspect a specific runner function - parameters, return type, metadata.'
  },
  knowledge:     {
    tools:   %w[legion.query_knowledge legion.knowledge_health legion.knowledge_context legion.absorb],
    summary: 'Knowledge base operations - query, health, context retrieval, content absorption.'
  },
  mesh:          {
    tools:   %w[legion.ask_peer legion.list_peers legion.notify_peer legion.broadcast_peers
                legion.mesh_status],
    summary: 'Agent mesh communication - peer queries, notifications, broadcasts, and topology.'
  },
  mind_growth:   {
    tools:   %w[legion.mind_growth_status legion.mind_growth_propose legion.mind_growth_approve
                legion.mind_growth_build_queue legion.mind_growth_cognitive_profile
                legion.mind_growth_health],
    summary: 'Cognitive growth - proposals, approvals, build queue, profiling, fitness scores.'
  },
  prompts:       {
    tools:   %w[legion.prompt_list legion.prompt_show legion.prompt_run],
    summary: 'Prompt template management - list, view, and render prompt templates.'
  },
  datasets:      {
    tools:   %w[legion.dataset_list legion.dataset_show legion.experiment_results],
    summary: 'Dataset and experiment browsing - list datasets, view rows, compare results.'
  },
  evals:         {
    tools:   %w[legion.eval_list legion.eval_run legion.eval_results],
    summary: 'Evaluation management - list evaluators, run evaluations, view results.'
  },
  skills:        {
    tools:   %w[legion.skill.list legion.skill.describe legion.skill.invoke legion.skill.cancel],
    summary: 'Skill management - list, describe, invoke, and cancel LLM skills.'
  },
  meta:          {
    tools:   %w[legion.do legion.tools legion.plan_action legion.structural_index],
    summary: 'Meta-tools - natural language routing, tool discovery, planning, structural index.'
  }
}.freeze

Class Method Summary collapse

Class Method Details

.build_tool_indexObject



181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/legion/mcp/context_compiler.rb', line 181

def build_tool_index
  Server.tool_registry.each_with_object({}) do |klass, idx|
    raw_schema = klass.input_schema
    schema = raw_schema.is_a?(Hash) ? raw_schema : raw_schema.to_h
    properties = schema[:properties] || {}
    idx[klass.tool_name] = {
      name:        klass.tool_name,
      description: klass.description,
      params:      properties.keys.map(&:to_s)
    }
  end
end

.category_tools(category_sym) ⇒ Hash?

Returns tools for a specific category, filtered to only those present in Server.tool_registry. Checks CATEGORIES (hardcoded fallback) as well as definition-declared mcp_category on tool classes.

Parameters:

  • category_sym (Symbol)

    one of the CATEGORIES keys (or a definition-declared category)

Returns:

  • (Hash, nil)

    { category:, summary:, tools: [{ name:, description:, params: }] } or nil



107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/legion/mcp/context_compiler.rb', line 107

def category_tools(category_sym)
  config = merged_categories[category_sym]
  return nil unless config

  index = tool_index
  tools = config[:tools].filter_map { |name| index[name] }
  return nil if tools.empty?

  {
    category: category_sym,
    summary:  config[:summary],
    tools:    tools
  }
end

.compressed_catalogArray<Hash>

Returns a compressed summary of all categories with tool counts and tool name lists. Merges CATEGORIES (hardcoded fallback) with any categories declared via the definition DSL (mcp_category: on dynamically discovered tool classes).

Returns:

  • (Array<Hash>)

    array of { category:, summary:, tool_count:, tools: }



90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/legion/mcp/context_compiler.rb', line 90

def compressed_catalog
  merged = merged_categories
  merged.map do |category, config|
    tool_names = config[:tools]
    {
      category:   category,
      summary:    config[:summary],
      tool_count: tool_names.length,
      tools:      tool_names
    }
  end
end

.keyword_score_map(keywords) ⇒ Object



217
218
219
220
221
222
223
224
225
# File 'lib/legion/mcp/context_compiler.rb', line 217

def keyword_score_map(keywords)
  tool_index.values.to_h do |entry|
    haystack = "#{entry[:name].downcase} #{entry[:description].downcase}"
    score = keywords.count { |kw| haystack.include?(kw) }
    name_terms = entry[:name].downcase.tr('._-', ' ').split
    score += (keywords & name_terms).length * 3
    [entry[:name], score]
  end
end

.match_tool(intent_string) ⇒ Class?

Keyword-match intent against tool names and descriptions.

Parameters:

  • intent_string (String)

    natural language intent

Returns:

  • (Class, nil)

    best matching tool CLASS from Server.tool_registry or nil



147
148
149
150
151
152
153
154
155
# File 'lib/legion/mcp/context_compiler.rb', line 147

def match_tool(intent_string)
  scored = scored_tools(intent_string)
  return nil if scored.empty?

  best = scored.max_by { |entry| entry[:score] }
  return nil if best[:score].zero?

  Server.tool_registry.find { |klass| klass.tool_name == best[:name] }
end

.match_tools(intent_string, limit: 5) ⇒ Array<Hash>

Returns top N keyword-matched tools ranked by score.

Parameters:

  • intent_string (String)

    natural language intent

  • limit (Integer) (defaults to: 5)

    max results (default 5)

Returns:

  • (Array<Hash>)

    array of { name:, description:, score: }



161
162
163
164
165
166
# File 'lib/legion/mcp/context_compiler.rb', line 161

def match_tools(intent_string, limit: 5)
  scored = scored_tools(intent_string)
           .select { |entry| entry[:score].positive? }
           .sort_by { |entry| -entry[:score] }
  scored.first(limit)
end

.merged_categoriesHash<Symbol, Hash>

Builds a merged category map: CATEGORIES constant as fallback, augmented by tool classes that declare mcp_category: via the definition DSL.

Returns:

  • (Hash<Symbol, Hash>)

    { category_sym => { tools: […], summary: ‘…’ } }



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/legion/mcp/context_compiler.rb', line 125

def merged_categories
  result = CATEGORIES.transform_values do |config|
    { tools: config[:tools].dup, summary: config[:summary] }
  end

  Server.tool_registry.each do |klass|
    next unless klass.respond_to?(:mcp_category) && klass.mcp_category

    cat = klass.mcp_category.to_sym
    if result.key?(cat)
      result[cat][:tools] |= [klass.tool_name]
    else
      result[cat] = { tools: [klass.tool_name], summary: cat.to_s.tr('_', ' ').capitalize }
    end
  end

  result
end

.reset!Object

Clears the memoized tool_index.



176
177
178
179
# File 'lib/legion/mcp/context_compiler.rb', line 176

def reset!
  @tool_index = nil
  Legion::MCP::EmbeddingIndex.reset! if defined?(Legion::MCP::EmbeddingIndex)
end

.scored_tools(intent_string) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/legion/mcp/context_compiler.rb', line 194

def scored_tools(intent_string)
  keywords = intent_string.downcase.split
  return [] if keywords.empty?

  kw_scores = keyword_score_map(keywords)
  sem_scores = semantic_score_map(intent_string)
  use_semantic = !sem_scores.empty?

  tool_index.values.map do |entry|
    kw_raw = kw_scores[entry[:name]] || 0
    if use_semantic
      max_kw = kw_scores.values.max || 1
      normalized_kw = max_kw.positive? ? kw_raw.to_f / max_kw : 0.0
      sem = sem_scores[entry[:name]] || 0.0
      blended = (normalized_kw * 0.4) + (sem * 0.6)
    else
      blended = kw_raw.to_f
    end

    { name: entry[:name], description: entry[:description], score: blended }
  end
end

.semantic_score_map(intent_string) ⇒ Object



227
228
229
230
231
232
233
# File 'lib/legion/mcp/context_compiler.rb', line 227

def semantic_score_map(intent_string)
  return {} unless defined?(Legion::MCP::EmbeddingIndex) && Legion::MCP::EmbeddingIndex.populated?

  Legion::MCP::EmbeddingIndex.semantic_match(intent_string, limit: tool_index.size).to_h do |result|
    [result[:name], result[:score]]
  end
end

.tool_indexHash<String, Hash>

Returns a hash keyed by tool_name with compressed param info. Memoized — call reset! to clear.

Returns:

  • (Hash<String, Hash>)

    { name:, description:, params: [String] }



171
172
173
# File 'lib/legion/mcp/context_compiler.rb', line 171

def tool_index
  @tool_index ||= build_tool_index
end