Module: Legion::Extensions::Agentic::Executive::GoalManagement::Helpers::Decomposer

Extended by:
JSON::Helper
Defined in:
lib/legion/extensions/agentic/executive/goal_management/helpers/decomposer.rb

Constant Summary collapse

DOMAIN_TEMPLATES =
{
  safety:     ->(c) { ["diagnose: #{c}", "implement fix for: #{c}", "verify health after: #{c}"] },
  cognition:  ->(c) { ["analyze gaps in: #{c}", "design approach for: #{c}", "validate: #{c}"] },
  perception: ->(c) { ["observe current state of: #{c}", "identify patterns in: #{c}", "calibrate: #{c}"] }
}.freeze

Class Method Summary collapse

Class Method Details

.build_decomposition_prompt(goal) ⇒ Object



75
76
77
78
79
80
81
82
83
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/decomposer.rb', line 75

def build_decomposition_prompt(goal)
  <<~PROMPT
    Decompose this goal into 2-5 concrete sub-goals. Return JSON array.
    Goal: #{goal[:content]}
    Domain: #{goal[:domain]}
    Each sub-goal: {"content": "action description", "domain": "#{goal[:domain]}", "priority": 0.0-1.0}
    Return ONLY the JSON array, no other text.
  PROMPT
end

.decompose(goal:, strategy: :heuristic) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/decomposer.rb', line 24

def decompose(goal:, strategy: :heuristic)
  sub_goals, strategy_used = case strategy
                             when :llm
                               result = decompose_with_llm(goal)
                               result ? [result, :llm] : [decompose_heuristic(goal), :heuristic]
                             else
                               [decompose_heuristic(goal), :heuristic]
                             end
  log.info "[decomposer] decomposed goal=#{goal[:id]} strategy=#{strategy_used}"
  { success: true, sub_goals: sub_goals, strategy_used: strategy_used }
rescue StandardError => e
  log.error "Decomposer: #{e.message}"
  { success: false, error: e.message }
end

.decompose_by_domain(content, domain) ⇒ Object



59
60
61
62
63
64
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/decomposer.rb', line 59

def decompose_by_domain(content, domain)
  template = DOMAIN_TEMPLATES[domain]
  return nil unless template

  template.call(content)
end

.decompose_heuristic(goal) ⇒ Object



39
40
41
42
43
44
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/decomposer.rb', line 39

def decompose_heuristic(goal)
  content = goal[:content].to_s.downcase
  domain  = (goal[:domain] || :general).to_sym
  steps   = decompose_by_domain(content, domain) || default_steps(content)
  steps.map { |step| { content: step, domain: domain, priority: goal[:priority] || 0.5 } }
end

.decompose_with_llm(goal) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/decomposer.rb', line 46

def decompose_with_llm(goal)
  return nil unless defined?(Legion::LLM) && Legion::LLM.respond_to?(:chat)

  prompt   = build_decomposition_prompt(goal)
  response = Legion::LLM.chat(
    caller: { extension: 'lex-agentic-executive', operation: 'decompose' }
  ).ask(prompt)
  parse_sub_goals(response.content, goal[:domain])
rescue StandardError => e
  log.error "Decomposer#decompose_with_llm: #{e.message}"
  nil
end

.default_steps(content) ⇒ Object



66
67
68
69
70
71
72
73
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/decomposer.rb', line 66

def default_steps(content)
  [
    "analyze current state of: #{content}",
    "plan approach for: #{content}",
    "execute: #{content}",
    "verify result of: #{content}"
  ]
end

.logObject



14
15
16
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/decomposer.rb', line 14

def log
  Legion::Logging
end

.parse_sub_goals(content, domain) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/decomposer.rb', line 85

def parse_sub_goals(content, domain)
  cleaned = content.gsub(/```(?:json)?\s*\n?/, '').strip
  data = json_load(cleaned)
  return nil unless data.is_a?(Array) && !data.empty?

  data.map do |sg|
    {
      content:  sg[:content].to_s,
      domain:   (sg[:domain] || domain).to_sym,
      priority: (sg[:priority] || 0.5).to_f.clamp(0.0, 1.0)
    }
  end
rescue StandardError => e
  log.error "Decomposer#parse_sub_goals: #{e.message}"
  nil
end