lex-agentic-memory 0.1.19 → 0.1.20

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: 440d04008d2af919b940d155b035cf4d1cac9d8e67361792008b166504ed9a2a
4
- data.tar.gz: 0f0d8fade5d070d635b707e792501d9b88624205940b32b9babf194c33970620
3
+ metadata.gz: c4e20449f626874ab90b96c4452b4b35e4eae22e61df8fb6c9e5a51e4b0b8cd3
4
+ data.tar.gz: eff572db0f462ada591dcfc6e6efa2d8d3092cc43b1be6f907ddcfe9003b0cd9
5
5
  SHA512:
6
- metadata.gz: d6695c59a99700fb47490bafbf29e9749fca9f9b523d38ec67ee31f3a522f633fc0c5a36d0cdbd6f362e6bbb78ac35a896a2bcf9023f16a3c3d6d9cb25ea6279
7
- data.tar.gz: 7e79c8329c3de95c22a832144d729c5430586048727a88762698df887fb8b9a06689391f9539ac081375082b19e76b8805dcb807554d7f30df342cd3b1eb00f1
6
+ metadata.gz: e0e21635a796f9d4c9fbb2503446657d727bb01de9bb82a5b6323270611ff13c72a18bb0c520f2fb0ab3a987d5a869ca92ebf7e1e718dce0d20cd0b3712f1722
7
+ data.tar.gz: 0ea2e0a708e2cb8f853bc17595935a596de7e98c03955c9ce97582a7edcdc5546a22e645f9e929b05a08a43e9b52a4e4a1a45329129b1d797cea58d42f833b76
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.20] - 2026-03-31
4
+
5
+ ### Added
6
+ - CommunicationPattern sub-module for Phase C relational intelligence
7
+ - PatternAnalyzer: time-of-day/day-of-week histograms, channel preference, topic clustering, consistency scoring
8
+ - CommunicationPattern runner: update_patterns, analyze_patterns, pattern_stats
9
+ - Apollo Local persistence for communication pattern state
10
+
3
11
  ## [0.1.19] - 2026-03-31
4
12
 
5
13
  ### Fixed
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Memory
7
+ module CommunicationPattern
8
+ class Client
9
+ include Runners::CommunicationPattern
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Memory
7
+ module CommunicationPattern
8
+ module Helpers
9
+ module Constants
10
+ HOURS_IN_DAY = 24
11
+ DAYS_IN_WEEK = 7
12
+
13
+ MESSAGE_LENGTH_BUCKETS = %i[short medium long].freeze
14
+ MESSAGE_LENGTH_THRESHOLDS = { short: 50, medium: 200 }.freeze
15
+
16
+ SLIDING_WINDOW_SIZE = 100
17
+ MIN_TRACES_FOR_PATTERN = 10
18
+
19
+ TAG_PREFIX = %w[bond communication_pattern].freeze
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Memory
7
+ module CommunicationPattern
8
+ module Helpers
9
+ class PatternAnalyzer
10
+ attr_reader :agent_id, :trace_count
11
+
12
+ def initialize(agent_id:)
13
+ @agent_id = agent_id
14
+ @tod_histogram = Array.new(Constants::HOURS_IN_DAY, 0)
15
+ @dow_histogram = Array.new(Constants::DAYS_IN_WEEK, 0)
16
+ @channel_counts = Hash.new(0)
17
+ @direct_count = 0
18
+ @topic_counts = Hash.new(0)
19
+ @trace_count = 0
20
+ @dirty = false
21
+ end
22
+
23
+ def update_from_traces(traces)
24
+ traces.each { |t| process_trace(t) }
25
+ @dirty = true
26
+ end
27
+
28
+ def time_of_day_distribution
29
+ @tod_histogram.dup
30
+ end
31
+
32
+ def day_of_week_distribution
33
+ @dow_histogram.dup
34
+ end
35
+
36
+ def channel_preference
37
+ @channel_counts.sort_by { |_k, v| -v }.map(&:first)
38
+ end
39
+
40
+ def direct_address_frequency
41
+ return 0.0 if @trace_count.zero?
42
+
43
+ @direct_count.to_f / @trace_count
44
+ end
45
+
46
+ def topic_clustering
47
+ @topic_counts.dup
48
+ end
49
+
50
+ def consistency
51
+ return 0.0 if @trace_count < Constants::MIN_TRACES_FOR_PATTERN
52
+
53
+ channel_entropy = normalized_entropy(@channel_counts.values)
54
+ tod_entropy = normalized_entropy(@tod_histogram)
55
+ (1.0 - ((channel_entropy + tod_entropy) / 2.0)).clamp(0.0, 1.0)
56
+ end
57
+
58
+ def dirty?
59
+ @dirty
60
+ end
61
+
62
+ def mark_clean!
63
+ @dirty = false
64
+ self
65
+ end
66
+
67
+ def to_apollo_entries
68
+ tags = Constants::TAG_PREFIX.dup + [@agent_id]
69
+ [{ content: serialize(state_hash), tags: tags }]
70
+ end
71
+
72
+ def from_apollo(store:)
73
+ result = store.query(text: 'communication_pattern',
74
+ tags: Constants::TAG_PREFIX + [@agent_id])
75
+ return false unless result[:success] && result[:results]&.any?
76
+
77
+ parsed = deserialize(result[:results].first[:content])
78
+ return false unless parsed
79
+
80
+ restore_state(parsed)
81
+ true
82
+ rescue StandardError => e
83
+ warn "[pattern_analyzer] from_apollo error: #{e.message}"
84
+ false
85
+ end
86
+
87
+ private
88
+
89
+ def process_trace(trace)
90
+ ts = trace[:created_at]
91
+ ts = Time.parse(ts.to_s) unless ts.is_a?(Time)
92
+
93
+ @tod_histogram[ts.hour] += 1
94
+ @dow_histogram[ts.wday] += 1
95
+
96
+ payload = trace[:content_payload] || {}
97
+ payload = payload.transform_keys(&:to_sym) if payload.is_a?(Hash)
98
+
99
+ channel = payload[:channel]&.to_s
100
+ @channel_counts[channel] += 1 if channel
101
+
102
+ @direct_count += 1 if payload[:direct_address]
103
+
104
+ (trace[:domain_tags] || []).each { |tag| @topic_counts[tag.to_s] += 1 }
105
+
106
+ @trace_count += 1
107
+ rescue StandardError => e
108
+ Legion::Logging.warn("[pattern_analyzer] process_trace error: #{e.message}")
109
+ nil
110
+ end
111
+
112
+ def normalized_entropy(counts)
113
+ total = counts.sum.to_f
114
+ return 0.0 if total.zero?
115
+
116
+ probs = counts.select(&:positive?).map { |c| c / total }
117
+ max_entropy = Math.log(probs.size)
118
+ return 0.0 if max_entropy.zero?
119
+
120
+ entropy = -probs.sum { |p| p * Math.log(p) }
121
+ entropy / max_entropy
122
+ end
123
+
124
+ def state_hash
125
+ { agent_id: @agent_id, trace_count: @trace_count,
126
+ tod_histogram: @tod_histogram, dow_histogram: @dow_histogram,
127
+ channel_counts: @channel_counts, direct_count: @direct_count,
128
+ topic_counts: @topic_counts, consistency: consistency }
129
+ end
130
+
131
+ def restore_state(parsed)
132
+ @trace_count = parsed[:trace_count].to_i
133
+ @tod_histogram = parsed[:tod_histogram] || Array.new(24, 0)
134
+ @dow_histogram = parsed[:dow_histogram] || Array.new(7, 0)
135
+ @channel_counts = Hash.new(0).merge(parsed[:channel_counts] || {})
136
+ @direct_count = parsed[:direct_count].to_i
137
+ @topic_counts = Hash.new(0).merge(parsed[:topic_counts] || {})
138
+ end
139
+
140
+ def serialize(hash)
141
+ defined?(Legion::JSON) ? Legion::JSON.dump(hash) : ::JSON.dump(hash)
142
+ end
143
+
144
+ def deserialize(content)
145
+ parsed = defined?(Legion::JSON) ? Legion::JSON.parse(content) : ::JSON.parse(content, symbolize_names: true)
146
+ parsed.is_a?(Hash) ? parsed.transform_keys(&:to_sym) : nil
147
+ rescue StandardError => e
148
+ Legion::Logging.warn("[pattern_analyzer] deserialize error: #{e.message}")
149
+ nil
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Memory
7
+ module CommunicationPattern
8
+ module Runners
9
+ module CommunicationPattern
10
+ include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers) &&
11
+ Legion::Extensions::Helpers.const_defined?(:Lex, false)
12
+
13
+ def update_patterns(agent_id:, traces: [], **)
14
+ analyzer = analyzer_for(agent_id)
15
+ analyzer.update_from_traces(traces)
16
+
17
+ { success: true, trace_count: analyzer.trace_count,
18
+ channel_preference: analyzer.channel_preference,
19
+ direct_address_frequency: analyzer.direct_address_frequency,
20
+ consistency: analyzer.consistency }
21
+ rescue StandardError => e
22
+ { success: false, error: e.message }
23
+ end
24
+
25
+ def analyze_patterns(agent_id:, **)
26
+ analyzer = analyzer_for(agent_id)
27
+ { time_of_day_distribution: analyzer.time_of_day_distribution,
28
+ day_of_week_distribution: analyzer.day_of_week_distribution,
29
+ channel_preference: analyzer.channel_preference,
30
+ direct_address_frequency: analyzer.direct_address_frequency,
31
+ topic_clustering: analyzer.topic_clustering,
32
+ consistency: analyzer.consistency,
33
+ trace_count: analyzer.trace_count }
34
+ end
35
+
36
+ def pattern_stats(agent_id:, **)
37
+ analyzer = analyzer_for(agent_id)
38
+ { trace_count: analyzer.trace_count,
39
+ channel_preference: analyzer.channel_preference,
40
+ consistency: analyzer.consistency }
41
+ end
42
+
43
+ private
44
+
45
+ def analyzer_for(agent_id)
46
+ @analyzers ||= {}
47
+ @analyzers[agent_id.to_s] ||= Helpers::PatternAnalyzer.new(agent_id: agent_id.to_s)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Memory
7
+ module CommunicationPattern
8
+ VERSION = '0.1.0'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'communication_pattern/version'
4
+ require_relative 'communication_pattern/helpers/constants'
5
+ require_relative 'communication_pattern/helpers/pattern_analyzer'
6
+ require_relative 'communication_pattern/runners/communication_pattern'
7
+ require_relative 'communication_pattern/client'
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Memory
7
- VERSION = '0.1.19'
7
+ VERSION = '0.1.20'
8
8
  end
9
9
  end
10
10
  end
@@ -19,6 +19,7 @@ require_relative 'memory/semantic_priming'
19
19
  require_relative 'memory/semantic_satiation'
20
20
  require_relative 'memory/source_monitoring'
21
21
  require_relative 'memory/transfer'
22
+ require_relative 'memory/communication_pattern'
22
23
 
23
24
  module Legion
24
25
  module Extensions
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'legion/extensions/agentic/memory/communication_pattern/helpers/constants'
5
+
6
+ RSpec.describe Legion::Extensions::Agentic::Memory::CommunicationPattern::Helpers::Constants do
7
+ describe 'HOURS_IN_DAY' do
8
+ it 'is 24' do
9
+ expect(described_class::HOURS_IN_DAY).to eq(24)
10
+ end
11
+ end
12
+
13
+ describe 'DAYS_IN_WEEK' do
14
+ it 'is 7' do
15
+ expect(described_class::DAYS_IN_WEEK).to eq(7)
16
+ end
17
+ end
18
+
19
+ describe 'MESSAGE_LENGTH_BUCKETS' do
20
+ it 'defines 3 buckets' do
21
+ expect(described_class::MESSAGE_LENGTH_BUCKETS).to eq(%i[short medium long])
22
+ end
23
+ end
24
+
25
+ describe 'SLIDING_WINDOW_SIZE' do
26
+ it 'is 100' do
27
+ expect(described_class::SLIDING_WINDOW_SIZE).to eq(100)
28
+ end
29
+ end
30
+
31
+ describe 'MIN_TRACES_FOR_PATTERN' do
32
+ it 'is 10' do
33
+ expect(described_class::MIN_TRACES_FOR_PATTERN).to eq(10)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'legion/extensions/agentic/memory/communication_pattern/helpers/constants'
5
+ require 'legion/extensions/agentic/memory/communication_pattern/helpers/pattern_analyzer'
6
+
7
+ RSpec.describe Legion::Extensions::Agentic::Memory::CommunicationPattern::Helpers::PatternAnalyzer do
8
+ subject(:analyzer) { described_class.new(agent_id: 'partner-1') }
9
+
10
+ let(:trace_9am) do
11
+ { trace_id: 'tr1', trace_type: :episodic, created_at: Time.utc(2026, 3, 31, 9, 0),
12
+ content_payload: { channel: 'teams', direct_address: true, agent_id: 'partner-1' },
13
+ domain_tags: %w[partner observation], source_agent_id: 'partner-1' }
14
+ end
15
+
16
+ let(:trace_2pm) do
17
+ { trace_id: 'tr2', trace_type: :episodic, created_at: Time.utc(2026, 3, 31, 14, 0),
18
+ content_payload: { channel: 'cli', direct_address: false, agent_id: 'partner-1' },
19
+ domain_tags: %w[partner observation work], source_agent_id: 'partner-1' }
20
+ end
21
+
22
+ describe '#update_from_traces' do
23
+ it 'processes an array of traces' do
24
+ analyzer.update_from_traces([trace_9am, trace_2pm])
25
+ expect(analyzer.trace_count).to eq(2)
26
+ end
27
+
28
+ it 'marks dirty' do
29
+ analyzer.update_from_traces([trace_9am])
30
+ expect(analyzer).to be_dirty
31
+ end
32
+ end
33
+
34
+ describe '#time_of_day_distribution' do
35
+ before { analyzer.update_from_traces([trace_9am, trace_2pm]) }
36
+
37
+ it 'returns 24-bucket histogram' do
38
+ dist = analyzer.time_of_day_distribution
39
+ expect(dist.size).to eq(24)
40
+ end
41
+
42
+ it 'has counts in the right buckets' do
43
+ dist = analyzer.time_of_day_distribution
44
+ expect(dist[9]).to eq(1)
45
+ expect(dist[14]).to eq(1)
46
+ expect(dist[0]).to eq(0)
47
+ end
48
+ end
49
+
50
+ describe '#day_of_week_distribution' do
51
+ before { analyzer.update_from_traces([trace_9am]) }
52
+
53
+ it 'returns 7-bucket histogram' do
54
+ dist = analyzer.day_of_week_distribution
55
+ expect(dist.size).to eq(7)
56
+ end
57
+ end
58
+
59
+ describe '#channel_preference' do
60
+ before { analyzer.update_from_traces([trace_9am, trace_2pm, trace_9am.merge(trace_id: 'tr3')]) }
61
+
62
+ it 'ranks channels by frequency' do
63
+ prefs = analyzer.channel_preference
64
+ expect(prefs.first).to eq('teams')
65
+ end
66
+ end
67
+
68
+ describe '#direct_address_frequency' do
69
+ before { analyzer.update_from_traces([trace_9am, trace_2pm]) }
70
+
71
+ it 'computes ratio of direct address traces' do
72
+ ratio = analyzer.direct_address_frequency
73
+ expect(ratio).to be_within(0.01).of(0.5)
74
+ end
75
+ end
76
+
77
+ describe '#topic_clustering' do
78
+ before { analyzer.update_from_traces([trace_9am, trace_2pm]) }
79
+
80
+ it 'returns domain tag frequencies' do
81
+ topics = analyzer.topic_clustering
82
+ expect(topics).to have_key('partner')
83
+ expect(topics['partner']).to eq(2)
84
+ end
85
+ end
86
+
87
+ describe '#consistency' do
88
+ it 'returns 0.0 with no data' do
89
+ expect(analyzer.consistency).to eq(0.0)
90
+ end
91
+
92
+ it 'returns positive value with data' do
93
+ analyzer.update_from_traces([trace_9am, trace_2pm])
94
+ expect(analyzer.consistency).to eq(0.0)
95
+ end
96
+ end
97
+
98
+ describe 'Apollo persistence' do
99
+ describe '#to_apollo_entries' do
100
+ before { analyzer.update_from_traces([trace_9am]) }
101
+
102
+ it 'returns entries with correct tags' do
103
+ entries = analyzer.to_apollo_entries
104
+ expect(entries.first[:tags]).to include('bond', 'communication_pattern', 'partner-1')
105
+ end
106
+ end
107
+
108
+ describe '#from_apollo' do
109
+ let(:mock_store) { double('apollo_local') }
110
+
111
+ it 'restores state' do
112
+ analyzer.update_from_traces([trace_9am, trace_2pm])
113
+ content = analyzer.send(:serialize, analyzer.send(:state_hash))
114
+
115
+ new_analyzer = described_class.new(agent_id: 'partner-1')
116
+ allow(mock_store).to receive(:query)
117
+ .and_return({ success: true, results: [{ content: content }] })
118
+
119
+ expect(new_analyzer.from_apollo(store: mock_store)).to be true
120
+ expect(new_analyzer.trace_count).to eq(2)
121
+ end
122
+ end
123
+
124
+ describe '#mark_clean!' do
125
+ it 'clears dirty flag' do
126
+ analyzer.update_from_traces([trace_9am])
127
+ analyzer.mark_clean!
128
+ expect(analyzer).not_to be_dirty
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'legion/extensions/agentic/memory/communication_pattern/helpers/constants'
5
+ require 'legion/extensions/agentic/memory/communication_pattern/helpers/pattern_analyzer'
6
+ require 'legion/extensions/agentic/memory/communication_pattern/runners/communication_pattern'
7
+
8
+ RSpec.describe Legion::Extensions::Agentic::Memory::CommunicationPattern::Runners::CommunicationPattern do
9
+ let(:host) { Object.new.extend(described_module) }
10
+ let(:described_module) { described_class }
11
+
12
+ before { host.instance_variable_set(:@analyzers, nil) }
13
+
14
+ describe '#update_patterns' do
15
+ let(:traces) do
16
+ [
17
+ { trace_id: 'tr1', trace_type: :episodic, created_at: Time.utc(2026, 3, 31, 9, 0),
18
+ content_payload: { channel: 'teams', direct_address: true }, domain_tags: %w[partner],
19
+ source_agent_id: 'partner-1' },
20
+ { trace_id: 'tr2', trace_type: :episodic, created_at: Time.utc(2026, 3, 31, 14, 0),
21
+ content_payload: { channel: 'cli', direct_address: false }, domain_tags: %w[partner],
22
+ source_agent_id: 'partner-1' }
23
+ ]
24
+ end
25
+
26
+ it 'processes traces and returns result' do
27
+ result = host.update_patterns(agent_id: 'partner-1', traces: traces)
28
+ expect(result[:success]).to be true
29
+ expect(result[:trace_count]).to eq(2)
30
+ end
31
+
32
+ it 'returns channel preference' do
33
+ result = host.update_patterns(agent_id: 'partner-1', traces: traces)
34
+ expect(result[:channel_preference]).to be_an(Array)
35
+ end
36
+
37
+ it 'returns direct address frequency' do
38
+ result = host.update_patterns(agent_id: 'partner-1', traces: traces)
39
+ expect(result[:direct_address_frequency]).to be_within(0.01).of(0.5)
40
+ end
41
+ end
42
+
43
+ describe '#analyze_patterns' do
44
+ it 'returns current patterns for an agent' do
45
+ host.update_patterns(agent_id: 'p1', traces: [
46
+ { trace_id: 't1', trace_type: :episodic, created_at: Time.now.utc,
47
+ content_payload: { channel: 'teams' }, domain_tags: [], source_agent_id: 'p1' }
48
+ ])
49
+ result = host.analyze_patterns(agent_id: 'p1')
50
+ expect(result).to have_key(:time_of_day_distribution)
51
+ expect(result).to have_key(:channel_preference)
52
+ end
53
+
54
+ it 'returns empty result for unknown agent' do
55
+ result = host.analyze_patterns(agent_id: 'unknown')
56
+ expect(result[:trace_count]).to eq(0)
57
+ end
58
+ end
59
+
60
+ describe '#pattern_stats' do
61
+ it 'returns stats hash' do
62
+ result = host.pattern_stats(agent_id: 'p1')
63
+ expect(result).to have_key(:trace_count)
64
+ end
65
+ end
66
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-memory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.19
4
+ version: 0.1.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -199,6 +199,12 @@ files:
199
199
  - lib/legion/extensions/agentic/memory/archaeology/helpers/excavation_site.rb
200
200
  - lib/legion/extensions/agentic/memory/archaeology/runners/cognitive_archaeology.rb
201
201
  - lib/legion/extensions/agentic/memory/archaeology/version.rb
202
+ - lib/legion/extensions/agentic/memory/communication_pattern.rb
203
+ - lib/legion/extensions/agentic/memory/communication_pattern/client.rb
204
+ - lib/legion/extensions/agentic/memory/communication_pattern/helpers/constants.rb
205
+ - lib/legion/extensions/agentic/memory/communication_pattern/helpers/pattern_analyzer.rb
206
+ - lib/legion/extensions/agentic/memory/communication_pattern/runners/communication_pattern.rb
207
+ - lib/legion/extensions/agentic/memory/communication_pattern/version.rb
202
208
  - lib/legion/extensions/agentic/memory/compression.rb
203
209
  - lib/legion/extensions/agentic/memory/compression/actors/maintenance.rb
204
210
  - lib/legion/extensions/agentic/memory/compression/client.rb
@@ -363,6 +369,9 @@ files:
363
369
  - spec/legion/extensions/agentic/memory/archaeology/helpers/constants_spec.rb
364
370
  - spec/legion/extensions/agentic/memory/archaeology/helpers/excavation_site_spec.rb
365
371
  - spec/legion/extensions/agentic/memory/archaeology/runners/cognitive_archaeology_spec.rb
372
+ - spec/legion/extensions/agentic/memory/communication_pattern/helpers/constants_spec.rb
373
+ - spec/legion/extensions/agentic/memory/communication_pattern/helpers/pattern_analyzer_spec.rb
374
+ - spec/legion/extensions/agentic/memory/communication_pattern/runners/communication_pattern_spec.rb
366
375
  - spec/legion/extensions/agentic/memory/compression/actors/maintenance_spec.rb
367
376
  - spec/legion/extensions/agentic/memory/compression/helpers/compression_engine_spec.rb
368
377
  - spec/legion/extensions/agentic/memory/compression/helpers/constants_spec.rb