lex-agentic-executive 0.2.2 → 0.2.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ac4e5f2b4c752913d15a4d25240de021a5e7821b2b50be4dd474b1a51ab482f
4
- data.tar.gz: 9c407b92da789b2f6a11c340b921d772f32fdc5617b968ed5a3dad01c680ce53
3
+ metadata.gz: 96780b1afcbb05fed19728d4ff7ce759d554c3f546341ae529fc858207c9d7d8
4
+ data.tar.gz: 240e134b5429af9de0ca9783f8632418b6968742b4921f9100acf857762d9909
5
5
  SHA512:
6
- metadata.gz: 558348baa035852ac306d07d992a3fb8c65263e87bad2d359dfa3c6ab611a25c5c6477ab02cd5d6776f81cd5b5e648f6bcd5583a4984705ca52f587e41304f4d
7
- data.tar.gz: 9c0eb7766904360b3c679c3357fb94006f24841f3eb392e9dcf7690e6282a4d65f67150b4cc204f0c67d7bafd67f1299ce5dcb2eccaed0e5ed50697d8b634fa8
6
+ metadata.gz: 32967c12a79d49227bc65e8ad400ab36aafde6f52b306792df60d5c1655c895bb42c3cb135e9903cd6a4d993538f9ba5bc916763c097e17dd7407730ccec371a
7
+ data.tar.gz: 5c3c763991e6145292134255689f0defb9ee4b565be8098bd1a1d223b5f726d55c3774be7696b6591d8ca1bdf839ca420e7def0db7d22bccdd2717f883a425a7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.3] - 2026-10-29
4
+ ### Fixed
5
+ - Reject unattributed goal feedback and preserve terminal goals and ancestor state when delayed task outcomes arrive.
6
+ - Reclaim completed intention history at capacity without discarding active or suspended commitments.
7
+
3
8
  ## [0.2.2] - 2026-05-08
4
9
  ### Fixed
5
10
  - `Decomposer#parse_sub_goals` now accesses JSON sub-goal hashes using string keys (with symbol fallback), preventing LLM-decomposed goals from getting empty content/default domain/0.5 priority.
data/README.md CHANGED
@@ -5,7 +5,7 @@ Domain consolidation gem for executive function, goal management, planning, and
5
5
  ## Overview
6
6
 
7
7
  **Gem**: `lex-agentic-executive`
8
- **Version**: 0.1.12
8
+ **Version**: 0.2.3
9
9
  **Namespace**: `Legion::Extensions::Agentic::Executive`
10
10
 
11
11
  ## Sub-Modules
@@ -213,9 +213,15 @@ module Legion
213
213
  end
214
214
 
215
215
  def update_from_task_event(task_id:, status:, result: nil)
216
+ return { found: false } if task_id.nil?
217
+
216
218
  @mutex.synchronize do
217
219
  goal = @goals.values.find { |g| g.task_id == task_id }
218
220
  return { found: false } unless goal
221
+ if %i[completed abandoned].include?(goal.status)
222
+ return { found: true, goal_id: goal.id, new_status: goal.status, progress: goal.progress,
223
+ ignored: :terminal_goal }
224
+ end
219
225
 
220
226
  case status
221
227
  when 'task.completed'
@@ -228,8 +234,8 @@ module Legion
228
234
  when 'task.exception', 'task.failed'
229
235
  goal.block!
230
236
  persist_goal(goal)
231
- log.info "[goal_engine] goal status change goal=#{goal.id} transition=blocked"
232
- { found: true, goal_id: goal.id, new_status: :blocked, error: result }
237
+ log.info "[goal_engine] goal status change goal=#{goal.id} transition=#{goal.status}"
238
+ { found: true, goal_id: goal.id, new_status: goal.status, error: result }
233
239
  else
234
240
  { found: true, goal_id: goal.id, unhandled_status: status }
235
241
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Executive
7
- VERSION = '0.2.2'
7
+ VERSION = '0.2.3'
8
8
  end
9
9
  end
10
10
  end
@@ -15,6 +15,12 @@ module Legion
15
15
 
16
16
  def push(intention)
17
17
  return :duplicate if duplicate?(intention)
18
+
19
+ if @intentions.size >= Constants::MAX_INTENTIONS
20
+ completed_index = @intentions.index { |entry| entry[:state] == :completed }
21
+ @intentions.delete_at(completed_index) if completed_index
22
+ end
23
+
18
24
  return :capacity_full if @intentions.size >= Constants::MAX_INTENTIONS
19
25
 
20
26
  @intentions << intention
@@ -40,6 +40,41 @@ RSpec.describe Legion::Extensions::Agentic::Executive::GoalManagement::Helpers::
40
40
  result = listener.handle_task_event(task_id: 'task-123', status: 'task.failed', result: 'timeout')
41
41
  expect(result[:error]).to eq('timeout')
42
42
  end
43
+
44
+ it 'does not attribute an event without a task ID to an undispatched goal' do
45
+ undispatched = engine.add_goal(content: 'awaiting authorized dispatch')[:goal][:id]
46
+ engine.activate_goal(goal_id: undispatched)
47
+
48
+ result = listener.handle_task_event(task_id: nil, status: 'task.completed')
49
+
50
+ expect(result).to eq(found: false)
51
+ expect(engine.goal_tree(goal_id: undispatched)[:tree]).to include(status: :active, progress: 0.0)
52
+ expect(engine.goal_tree(goal_id: @goal_id)[:tree]).to include(status: :active, progress: 0.0)
53
+ end
54
+
55
+ it 'preserves completion when a delayed failure arrives for the same task' do
56
+ listener.handle_task_event(task_id: 'task-123', status: 'task.completed')
57
+
58
+ result = listener.handle_task_event(task_id: 'task-123', status: 'task.failed', result: 'late failure')
59
+
60
+ expect(result).to include(new_status: :completed, ignored: :terminal_goal)
61
+ expect(engine.goal_tree(goal_id: @goal_id)[:tree]).to include(status: :completed, progress: 1.0)
62
+ end
63
+
64
+ it 'does not credit an abandoned child or its parent for a late completion' do
65
+ parent = engine.add_goal(content: 'parent goal')[:goal][:id]
66
+ engine.activate_goal(goal_id: parent)
67
+ child = engine.add_goal(content: 'cancelled work', parent_id: parent)[:goal][:id]
68
+ engine.activate_goal(goal_id: child)
69
+ engine.assign_task_to_goal(goal_id: child, task_id: 'cancelled-task', runner_mapping: 'TestRunner')
70
+ engine.abandon_goal(goal_id: child)
71
+ before = engine.goal_tree(goal_id: parent)[:tree]
72
+
73
+ result = listener.handle_task_event(task_id: 'cancelled-task', status: 'task.completed')
74
+
75
+ expect(result).to include(new_status: :abandoned, ignored: :terminal_goal)
76
+ expect(engine.goal_tree(goal_id: parent)[:tree]).to eq(before)
77
+ end
43
78
  end
44
79
 
45
80
  describe '#start_listening' do
@@ -27,6 +27,22 @@ RSpec.describe Legion::Extensions::Agentic::Executive::Volition::Helpers::Intent
27
27
  expect(stack.push(make_intention(goal: 'overflow'))).to eq(:capacity_full)
28
28
  end
29
29
 
30
+ it 'reclaims completed history at capacity without losing pending commitments' do
31
+ limit = Legion::Extensions::Agentic::Executive::Volition::Helpers::Constants::MAX_INTENTIONS
32
+ intentions = Array.new(limit) { |i| make_intention(goal: "goal #{i}") }
33
+ intentions.each { |intention| stack.push(intention) }
34
+ stack.complete(intentions.first[:intention_id])
35
+ stack.suspend(intentions.last[:intention_id])
36
+ replacement = make_intention(goal: 'next commitment')
37
+
38
+ expect(stack.push(replacement)).to eq(:pushed)
39
+ expect(stack.size).to eq(limit)
40
+ expect(stack.active.map { |intention| intention[:goal] })
41
+ .to contain_exactly(*intentions[1...-1].map { |intention| intention[:goal] }, 'next commitment')
42
+ expect(stack.resume(intentions.last[:intention_id])).to eq(:resumed)
43
+ expect(stack.push(make_intention(goal: 'overflow'))).to eq(:capacity_full)
44
+ end
45
+
30
46
  it 'sorts by salience descending' do
31
47
  stack.push(make_intention(goal: 'low', salience: 0.3))
32
48
  stack.push(make_intention(goal: 'high', salience: 0.9))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-executive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity