Class: Legion::Extensions::Agentic::Executive::GoalManagement::Helpers::GoalEngine

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb

Constant Summary collapse

URGENCY_BOOST =
{ critical: 0.3, high: 0.2, moderate: 0.1, low: 0.05 }.freeze

Constants included from Constants

Constants::CONFLICT_LABELS, Constants::CONFLICT_THRESHOLD, Constants::DEFAULT_PRIORITY, Constants::GOAL_STATUSES, Constants::MAX_DEPTH, Constants::MAX_GOALS, Constants::PRIORITY_BOOST, Constants::PRIORITY_DECAY, Constants::PRIORITY_LABELS, Constants::PROGRESS_LABELS, Constants::PROGRESS_THRESHOLD

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeGoalEngine

Returns a new instance of GoalEngine.



16
17
18
19
20
21
22
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 16

def initialize
  @goals         = {}
  @root_goal_ids = []
  @mutex         = Mutex.new
  @persistence   = GoalPersistence.new
  rehydrate_from_cache
end

Instance Attribute Details

#goalsObject (readonly)

Returns the value of attribute goals.



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

def goals
  @goals
end

#root_goal_idsObject (readonly)

Returns the value of attribute root_goal_ids.



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

def root_goal_ids
  @root_goal_ids
end

Instance Method Details

#abandon_goal(goal_id:) ⇒ Object



88
89
90
91
92
93
94
95
96
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 88

def abandon_goal(goal_id:)
  goal = @goals[goal_id]
  return { success: false, error: "goal #{goal_id} not found" } unless goal

  abandoned = goal.abandon!
  persist_goal(goal) if abandoned
  log.debug "[goal_management] abandon goal=#{goal_id} result=#{abandoned}"
  { success: abandoned, goal_id: goal_id, status: goal.status }
end

#activate_goal(goal_id:) ⇒ Object



68
69
70
71
72
73
74
75
76
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 68

def activate_goal(goal_id:)
  goal = @goals[goal_id]
  return { success: false, error: "goal #{goal_id} not found" } unless goal

  activated = goal.activate!
  persist_goal(goal) if activated
  log.debug "[goal_management] activate goal=#{goal_id} result=#{activated}"
  { success: activated, goal_id: goal_id, status: goal.status }
end

#active_goalsObject



150
151
152
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 150

def active_goals
  @goals.values.select(&:active?)
end

#add_goal(content:, parent_id: nil, domain: :general, priority: DEFAULT_PRIORITY, deadline: nil) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 24

def add_goal(content:, parent_id: nil, domain: :general, priority: DEFAULT_PRIORITY, deadline: nil)
  return { success: false, error: 'goal limit reached' } if @goals.size >= MAX_GOALS

  if parent_id
    parent = @goals[parent_id]
    return { success: false, error: "parent goal #{parent_id} not found" } unless parent
    return { success: false, error: 'max tree depth exceeded' } if depth_of(parent_id) >= MAX_DEPTH
  end

  prune_if_needed
  goal = Goal.new(content: content, parent_id: parent_id, domain: domain,
                  priority: priority, deadline: deadline)
  @goals[goal.id] = goal

  if parent_id
    @goals[parent_id].add_sub_goal(goal.id)
  else
    @root_goal_ids << goal.id
  end

  persist_goal(goal)
  log.debug "[goal_management] add_goal id=#{goal.id} domain=#{domain} priority=#{priority.round(2)}"
  { success: true, goal: goal.to_h }
end

#advance_progress(goal_id:, amount:) ⇒ Object



118
119
120
121
122
123
124
125
126
127
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 118

def advance_progress(goal_id:, amount:)
  goal = @goals[goal_id]
  return { success: false, error: "goal #{goal_id} not found" } unless goal

  goal.advance_progress!(amount)
  propagate_progress_to_parent(goal_id)
  persist_goal(goal)
  log.debug "[goal_management] advance_progress goal=#{goal_id} progress=#{goal.progress.round(2)}"
  { success: true, goal_id: goal_id, progress: goal.progress }
end

#assign_task_to_goal(goal_id:, task_id:, runner_mapping:) ⇒ Object



198
199
200
201
202
203
204
205
206
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 198

def assign_task_to_goal(goal_id:, task_id:, runner_mapping:)
  goal = @goals[goal_id]
  return { success: false, error: "goal #{goal_id} not found" } unless goal

  goal.assign_task!(task_id: task_id, runner_mapping: runner_mapping)
  persist_goal(goal)
  log.debug "[goal_management] assign_task goal=#{goal_id} task_id=#{task_id}"
  { success: true, goal_id: goal_id, task_id: task_id }
end

#block_goal(goal_id:) ⇒ Object



98
99
100
101
102
103
104
105
106
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 98

def block_goal(goal_id:)
  goal = @goals[goal_id]
  return { success: false, error: "goal #{goal_id} not found" } unless goal

  blocked = goal.block!
  persist_goal(goal) if blocked
  log.debug "[goal_management] block goal=#{goal_id} result=#{blocked}"
  { success: blocked, goal_id: goal_id, status: goal.status }
end

#blocked_goalsObject



154
155
156
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 154

def blocked_goals
  @goals.values.select(&:blocked?)
end

#complete_goal(goal_id:) ⇒ Object



78
79
80
81
82
83
84
85
86
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 78

def complete_goal(goal_id:)
  goal = @goals[goal_id]
  return { success: false, error: "goal #{goal_id} not found" } unless goal

  completed = goal.complete!
  persist_goal(goal) if completed
  log.debug "[goal_management] complete goal=#{goal_id} result=#{completed}"
  { success: completed, goal_id: goal_id, status: goal.status }
end

#completed_goalsObject



162
163
164
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 162

def completed_goals
  @goals.values.select(&:completed?)
end

#decay_all_priorities!Object



208
209
210
211
212
213
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 208

def decay_all_priorities!
  inactive = @goals.values.reject { |g| g.status == :active }
  inactive.each(&:decay_priority!)
  log.debug "[goal_management] decay_all inactive=#{inactive.size}"
  { decayed: inactive.size }
end

#decompose(goal_id:, sub_goals:) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 49

def decompose(goal_id:, sub_goals:)
  parent = @goals[goal_id]
  return { success: false, error: "goal #{goal_id} not found" } unless parent

  created = sub_goals.map do |sg|
    content  = sg.fetch(:content, sg.fetch('content', ''))
    domain   = sg.fetch(:domain, sg.fetch('domain', parent.domain))
    priority = sg.fetch(:priority, sg.fetch('priority', parent.priority))
    deadline = sg.fetch(:deadline, sg.fetch('deadline', nil))

    add_goal(content: content, parent_id: goal_id, domain: domain,
             priority: priority, deadline: deadline)
  end

  failures = created.reject { |r| r[:success] }
  log.debug "[goal_management] decompose parent=#{goal_id} created=#{created.size - failures.size} failed=#{failures.size}"
  { success: true, parent_id: goal_id, created: created, failures: failures.size }
end

#detect_conflicts(goal_id:) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 129

def detect_conflicts(goal_id:)
  goal = @goals[goal_id]
  return { success: false, error: "goal #{goal_id} not found" } unless goal

  competing = @goals.values.select do |g|
    g.id != goal_id &&
      g.domain == goal.domain &&
      %i[proposed active blocked].include?(g.status)
  end

  raw = competing.map do |g|
    score = conflict_score(goal, g)
    { goal_id: g.id, content: g.content, conflict_score: score, label: conflict_label(score) }
  end
  conflicts = raw.select { |c| c[:conflict_score] > 0.0 }
                 .sort_by { |c| -c[:conflict_score] }

  log.debug "[goal_management] detect_conflicts goal=#{goal_id} conflicts=#{conflicts.size}"
  { success: true, goal_id: goal_id, conflicts: conflicts, count: conflicts.size }
end

#goal_reportObject



239
240
241
242
243
244
245
246
247
248
249
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 239

def goal_report
  statuses = GOAL_STATUSES.to_h { |s| [s, 0] }
  @goals.each_value { |g| statuses[g.status] += 1 }
  {
    total:         @goals.size,
    root_goals:    @root_goal_ids.size,
    statuses:      statuses,
    overdue:       overdue_goals.size,
    high_priority: @goals.values.count { |g| g.priority >= 0.6 }
  }
end

#goal_tree(goal_id:) ⇒ Object



166
167
168
169
170
171
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 166

def goal_tree(goal_id:)
  goal = @goals[goal_id]
  return { success: false, error: "goal #{goal_id} not found" } unless goal

  { success: true, tree: build_tree(goal_id) }
end

#highest_priority(limit: 5) ⇒ Object



173
174
175
176
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 173

def highest_priority(limit: 5)
  active = @goals.values.select { |g| %i[proposed active blocked].include?(g.status) }
  active.sort_by { |g| -g.priority }.first(limit)
end

#overdue_goalsObject



158
159
160
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 158

def overdue_goals
  @goals.values.select(&:overdue?)
end

#reprioritize!(signal:) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 180

def reprioritize!(signal:)
  domain = signal[:domain]&.to_sym
  boost = URGENCY_BOOST[signal[:urgency]&.to_sym] || 0.05
  adjusted = 0

  @goals.each_value do |goal|
    next unless goal.domain == domain && goal.active?

    times = (boost / Constants::PRIORITY_BOOST).ceil
    times.times { goal.boost_priority! }
    persist_goal(goal)
    adjusted += 1
  end

  log.info "[goal_engine] reprioritize! domain=#{domain} boost=#{boost} adjusted=#{adjusted}"
  { adjusted: adjusted, domain: domain, boost: boost }
end

#to_hObject



251
252
253
254
255
256
257
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 251

def to_h
  {
    goals:         @goals.transform_values(&:to_h),
    root_goal_ids: @root_goal_ids.dup,
    report:        goal_report
  }
end

#unblock_goal(goal_id:) ⇒ Object



108
109
110
111
112
113
114
115
116
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 108

def unblock_goal(goal_id:)
  goal = @goals[goal_id]
  return { success: false, error: "goal #{goal_id} not found" } unless goal

  unblocked = goal.unblock!
  persist_goal(goal) if unblocked
  log.debug "[goal_management] unblock goal=#{goal_id} result=#{unblocked}"
  { success: unblocked, goal_id: goal_id, status: goal.status }
end

#update_from_task_event(task_id:, status:, result: nil) ⇒ Object



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/legion/extensions/agentic/executive/goal_management/helpers/goal_engine.rb', line 215

def update_from_task_event(task_id:, status:, result: nil)
  @mutex.synchronize do
    goal = @goals.values.find { |g| g.task_id == task_id }
    return { found: false } unless goal

    case status
    when 'task.completed'
      goal.advance_progress!(1.0 - goal.progress)
      goal.complete! if goal.progress >= Constants::PROGRESS_THRESHOLD
      propagate_progress_to_parent(goal.id) if goal.parent_id
      persist_goal(goal)
      log.info "[goal_engine] goal status change goal=#{goal.id} transition=completed"
      { found: true, goal_id: goal.id, new_status: goal.status, progress: goal.progress }
    when 'task.exception', 'task.failed'
      goal.block!
      persist_goal(goal)
      log.info "[goal_engine] goal status change goal=#{goal.id} transition=blocked"
      { found: true, goal_id: goal.id, new_status: :blocked, error: result }
    else
      { found: true, goal_id: goal.id, unhandled_status: status }
    end
  end
end