lex-cognitive-synthesis 0.1.0
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 +7 -0
- data/Gemfile +11 -0
- data/lex-cognitive-synthesis.gemspec +31 -0
- data/lib/legion/extensions/cognitive_synthesis/client.rb +25 -0
- data/lib/legion/extensions/cognitive_synthesis/helpers/constants.rb +37 -0
- data/lib/legion/extensions/cognitive_synthesis/helpers/synthesis.rb +59 -0
- data/lib/legion/extensions/cognitive_synthesis/helpers/synthesis_engine.rb +209 -0
- data/lib/legion/extensions/cognitive_synthesis/helpers/synthesis_stream.rb +63 -0
- data/lib/legion/extensions/cognitive_synthesis/runners/cognitive_synthesis.rb +78 -0
- data/lib/legion/extensions/cognitive_synthesis/version.rb +9 -0
- data/lib/legion/extensions/cognitive_synthesis.rb +16 -0
- data/spec/legion/extensions/cognitive_synthesis/client_spec.rb +26 -0
- data/spec/legion/extensions/cognitive_synthesis/helpers/constants_spec.rb +87 -0
- data/spec/legion/extensions/cognitive_synthesis/helpers/synthesis_engine_spec.rb +233 -0
- data/spec/legion/extensions/cognitive_synthesis/helpers/synthesis_spec.rb +101 -0
- data/spec/legion/extensions/cognitive_synthesis/helpers/synthesis_stream_spec.rb +116 -0
- data/spec/legion/extensions/cognitive_synthesis/runners/cognitive_synthesis_spec.rb +181 -0
- data/spec/spec_helper.rb +20 -0
- metadata +79 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 794a2635b1df3494d23c85016118e87cabd698c1f11da3c360a2985d398af0e2
|
|
4
|
+
data.tar.gz: b61b06852ae00e857131c9413bb347ff146d28809ebac36f9c1e62ebfae1a214
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ef493128ca5d01bb34dff75ec3a4f71991deae44d3f2bcf6fef7846902e57d54f038cf24c1f0e7c3defcce7d31e6f89cff302a97849ff47d5b0c8bf207ef179a
|
|
7
|
+
data.tar.gz: e369eca98a77a7d8be01ad95c2eb60c33a0cd603d30514e7bc7013e8d8da6575c9458466ba645c7db59830a81648976598972c0d2ed128ce320a363f5ea69958
|
data/Gemfile
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/cognitive_synthesis/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-cognitive-synthesis'
|
|
7
|
+
spec.version = Legion::Extensions::CognitiveSynthesis::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Cognitive Synthesis'
|
|
12
|
+
spec.description = 'Cognitive binding engine — combines disparate cognitive streams ' \
|
|
13
|
+
'(emotional, perceptual, memorial, predictive) into unified coherent ' \
|
|
14
|
+
'representations for brain-modeled agentic AI'
|
|
15
|
+
spec.homepage = 'https://github.com/LegionIO/lex-cognitive-synthesis'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
spec.required_ruby_version = '>= 3.4'
|
|
18
|
+
|
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
20
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-synthesis'
|
|
21
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-synthesis'
|
|
22
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-synthesis'
|
|
23
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-synthesis/issues'
|
|
24
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
25
|
+
|
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
27
|
+
Dir.glob('{lib,spec}/**/*') + %w[lex-cognitive-synthesis.gemspec Gemfile]
|
|
28
|
+
end
|
|
29
|
+
spec.require_paths = ['lib']
|
|
30
|
+
spec.add_development_dependency 'legion-gaia'
|
|
31
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/cognitive_synthesis/helpers/constants'
|
|
4
|
+
require 'legion/extensions/cognitive_synthesis/helpers/synthesis_stream'
|
|
5
|
+
require 'legion/extensions/cognitive_synthesis/helpers/synthesis'
|
|
6
|
+
require 'legion/extensions/cognitive_synthesis/helpers/synthesis_engine'
|
|
7
|
+
require 'legion/extensions/cognitive_synthesis/runners/cognitive_synthesis'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module CognitiveSynthesis
|
|
12
|
+
class Client
|
|
13
|
+
include Runners::CognitiveSynthesis
|
|
14
|
+
|
|
15
|
+
def initialize(engine: nil, **)
|
|
16
|
+
@synthesis_engine = engine || Helpers::SynthesisEngine.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :synthesis_engine
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveSynthesis
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
MAX_STREAMS = 50
|
|
9
|
+
MAX_SYNTHESES = 200
|
|
10
|
+
DEFAULT_WEIGHT = 0.5
|
|
11
|
+
COHERENCE_THRESHOLD = 0.6
|
|
12
|
+
NOVELTY_THRESHOLD = 0.7
|
|
13
|
+
FRESHNESS_DECAY = 0.02
|
|
14
|
+
MIN_STREAMS_FOR_SYNTHESIS = 2
|
|
15
|
+
|
|
16
|
+
STREAM_TYPES = %i[emotional perceptual memorial predictive reasoning social identity motor].freeze
|
|
17
|
+
|
|
18
|
+
COHERENCE_LABELS = {
|
|
19
|
+
(0.8..) => :unified,
|
|
20
|
+
(0.6...0.8) => :coherent,
|
|
21
|
+
(0.4...0.6) => :fragmented,
|
|
22
|
+
(0.2...0.4) => :dissonant,
|
|
23
|
+
(..0.2) => :chaotic
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
CONFIDENCE_LABELS = {
|
|
27
|
+
(0.8..) => :certain,
|
|
28
|
+
(0.6...0.8) => :confident,
|
|
29
|
+
(0.4...0.6) => :uncertain,
|
|
30
|
+
(0.2...0.4) => :doubtful,
|
|
31
|
+
(..0.2) => :guessing
|
|
32
|
+
}.freeze
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module CognitiveSynthesis
|
|
8
|
+
module Helpers
|
|
9
|
+
class Synthesis
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :streams, :coherence, :novelty, :confidence, :content, :created_at
|
|
13
|
+
|
|
14
|
+
def initialize(streams:, coherence:, novelty:, confidence:, content:)
|
|
15
|
+
@id = SecureRandom.uuid
|
|
16
|
+
@streams = streams
|
|
17
|
+
@coherence = coherence.clamp(0.0, 1.0)
|
|
18
|
+
@novelty = novelty.clamp(0.0, 1.0)
|
|
19
|
+
@confidence = confidence.clamp(0.0, 1.0)
|
|
20
|
+
@content = content
|
|
21
|
+
@created_at = Time.now.utc
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def fragmented?
|
|
25
|
+
@coherence < COHERENCE_THRESHOLD
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def novel?
|
|
29
|
+
@novelty > NOVELTY_THRESHOLD
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def coherence_label
|
|
33
|
+
COHERENCE_LABELS.find { |range, _| range.cover?(@coherence) }&.last || :chaotic
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def confidence_label
|
|
37
|
+
CONFIDENCE_LABELS.find { |range, _| range.cover?(@confidence) }&.last || :guessing
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_h
|
|
41
|
+
{
|
|
42
|
+
id: @id,
|
|
43
|
+
streams: @streams,
|
|
44
|
+
coherence: @coherence,
|
|
45
|
+
novelty: @novelty,
|
|
46
|
+
confidence: @confidence,
|
|
47
|
+
content: @content,
|
|
48
|
+
fragmented: fragmented?,
|
|
49
|
+
novel: novel?,
|
|
50
|
+
coherence_label: coherence_label,
|
|
51
|
+
confidence_label: confidence_label,
|
|
52
|
+
created_at: @created_at
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveSynthesis
|
|
6
|
+
module Helpers
|
|
7
|
+
class SynthesisEngine
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
attr_reader :streams, :syntheses
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@streams = {}
|
|
14
|
+
@syntheses = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add_stream(stream_type:, content:, weight: DEFAULT_WEIGHT, confidence: DEFAULT_WEIGHT, **)
|
|
18
|
+
return { success: false, error: :invalid_stream_type, valid_types: STREAM_TYPES } unless STREAM_TYPES.include?(stream_type)
|
|
19
|
+
|
|
20
|
+
stream = SynthesisStream.new(
|
|
21
|
+
stream_type: stream_type,
|
|
22
|
+
content: content,
|
|
23
|
+
weight: weight,
|
|
24
|
+
confidence: confidence
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@streams[stream.id] = stream
|
|
28
|
+
prune_streams! if @streams.size > MAX_STREAMS
|
|
29
|
+
|
|
30
|
+
Legion::Logging.debug "[cognitive_synthesis] stream added id=#{stream.id[0..7]} " \
|
|
31
|
+
"type=#{stream_type} weight=#{weight.round(2)}"
|
|
32
|
+
|
|
33
|
+
{ success: true, stream_id: stream.id, stream_type: stream_type }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def remove_stream(stream_id:, **)
|
|
37
|
+
removed = @streams.delete(stream_id)
|
|
38
|
+
if removed
|
|
39
|
+
Legion::Logging.debug "[cognitive_synthesis] stream removed id=#{stream_id[0..7]}"
|
|
40
|
+
{ success: true, stream_id: stream_id }
|
|
41
|
+
else
|
|
42
|
+
{ success: false, error: :not_found }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def synthesize!(**)
|
|
47
|
+
active = @streams.values.reject(&:stale?)
|
|
48
|
+
|
|
49
|
+
if active.size < MIN_STREAMS_FOR_SYNTHESIS
|
|
50
|
+
Legion::Logging.debug "[cognitive_synthesis] synthesize! skipped: only #{active.size} active streams"
|
|
51
|
+
return { success: false, error: :insufficient_streams, active_count: active.size, required: MIN_STREAMS_FOR_SYNTHESIS }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
coherence = compute_coherence(active)
|
|
55
|
+
novelty = compute_novelty(active)
|
|
56
|
+
confidence = compute_weighted_confidence(active)
|
|
57
|
+
content = merge_content(active)
|
|
58
|
+
|
|
59
|
+
synthesis = Synthesis.new(
|
|
60
|
+
streams: active.map(&:id),
|
|
61
|
+
coherence: coherence,
|
|
62
|
+
novelty: novelty,
|
|
63
|
+
confidence: confidence,
|
|
64
|
+
content: content
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@syntheses << synthesis
|
|
68
|
+
@syntheses.shift while @syntheses.size > MAX_SYNTHESES
|
|
69
|
+
|
|
70
|
+
Legion::Logging.info "[cognitive_synthesis] synthesis id=#{synthesis.id[0..7]} " \
|
|
71
|
+
"coherence=#{coherence.round(2)} novelty=#{novelty.round(2)} " \
|
|
72
|
+
"streams=#{active.size} label=#{synthesis.coherence_label}"
|
|
73
|
+
|
|
74
|
+
{ success: true, synthesis: synthesis.to_h }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def decay_all!(**)
|
|
78
|
+
before = @streams.size
|
|
79
|
+
@streams.each_value(&:decay_freshness!)
|
|
80
|
+
@streams.reject! { |_, s| s.stale? }
|
|
81
|
+
removed = before - @streams.size
|
|
82
|
+
|
|
83
|
+
Legion::Logging.debug "[cognitive_synthesis] decay_all! removed=#{removed} remaining=#{@streams.size}"
|
|
84
|
+
{ success: true, streams_removed: removed, streams_remaining: @streams.size }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def stream_conflict?(stream_id_a:, stream_id_b:, **)
|
|
88
|
+
a = @streams[stream_id_a]
|
|
89
|
+
b = @streams[stream_id_b]
|
|
90
|
+
|
|
91
|
+
return { success: false, error: :stream_not_found } unless a && b
|
|
92
|
+
|
|
93
|
+
weight_opposition = opposing_weights?(a, b)
|
|
94
|
+
content_conflict = conflicting_content?(a, b)
|
|
95
|
+
conflict = weight_opposition || content_conflict
|
|
96
|
+
|
|
97
|
+
Legion::Logging.debug "[cognitive_synthesis] conflict check #{stream_id_a[0..7]}<>#{stream_id_b[0..7]} " \
|
|
98
|
+
"result=#{conflict}"
|
|
99
|
+
|
|
100
|
+
{
|
|
101
|
+
success: true,
|
|
102
|
+
conflict: conflict,
|
|
103
|
+
weight_opposition: weight_opposition,
|
|
104
|
+
content_conflict: content_conflict
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def dominant_stream(**)
|
|
109
|
+
return { success: false, error: :no_streams } if @streams.empty?
|
|
110
|
+
|
|
111
|
+
stream = @streams.values.max_by(&:effective_weight)
|
|
112
|
+
Legion::Logging.debug "[cognitive_synthesis] dominant stream id=#{stream.id[0..7]} " \
|
|
113
|
+
"effective_weight=#{stream.effective_weight.round(4)}"
|
|
114
|
+
{ success: true, stream: stream.to_h }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def synthesis_history(limit: 10, **)
|
|
118
|
+
recent = @syntheses.last(limit)
|
|
119
|
+
{ success: true, syntheses: recent.map(&:to_h), count: recent.size }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def average_coherence(window: 10, **)
|
|
123
|
+
recent = @syntheses.last(window)
|
|
124
|
+
return { success: true, average_coherence: 0.0, sample_size: 0 } if recent.empty?
|
|
125
|
+
|
|
126
|
+
avg = recent.sum(&:coherence).round(10) / recent.size
|
|
127
|
+
{ success: true, average_coherence: avg.round(10), sample_size: recent.size }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def to_h
|
|
131
|
+
{
|
|
132
|
+
stream_count: @streams.size,
|
|
133
|
+
synthesis_count: @syntheses.size,
|
|
134
|
+
active_streams: @streams.values.reject(&:stale?).size,
|
|
135
|
+
stale_streams: @streams.values.count(&:stale?),
|
|
136
|
+
average_coherence: @syntheses.empty? ? 0.0 : (@syntheses.sum(&:coherence).round(10) / @syntheses.size).round(6)
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
def prune_streams!
|
|
143
|
+
overflow = @streams.size - MAX_STREAMS
|
|
144
|
+
return if overflow <= 0
|
|
145
|
+
|
|
146
|
+
ids_to_prune = @streams.min_by(overflow) { |_, s| s.effective_weight }.map(&:first)
|
|
147
|
+
ids_to_prune.each { |id| @streams.delete(id) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def compute_coherence(active)
|
|
151
|
+
return 1.0 if active.size == 1
|
|
152
|
+
|
|
153
|
+
weights = active.map(&:effective_weight)
|
|
154
|
+
mean = weights.sum.round(10) / weights.size
|
|
155
|
+
variance = weights.sum { |w| ((w - mean)**2).round(10) }.round(10) / weights.size
|
|
156
|
+
std_dev = Math.sqrt(variance)
|
|
157
|
+
|
|
158
|
+
(1.0 - [std_dev, 1.0].min).round(10)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def compute_novelty(active)
|
|
162
|
+
return 1.0 if @syntheses.empty?
|
|
163
|
+
|
|
164
|
+
last_syn = @syntheses.last
|
|
165
|
+
last_ids = Set.new(last_syn.streams)
|
|
166
|
+
current_ids = Set.new(active.map(&:id))
|
|
167
|
+
|
|
168
|
+
overlap = (last_ids & current_ids).size.to_f
|
|
169
|
+
union = (last_ids | current_ids).size.to_f
|
|
170
|
+
|
|
171
|
+
return 1.0 if union.zero?
|
|
172
|
+
|
|
173
|
+
jaccard_similarity = overlap / union
|
|
174
|
+
(1.0 - jaccard_similarity).round(10)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def compute_weighted_confidence(active)
|
|
178
|
+
total_weight = active.sum(&:effective_weight).round(10)
|
|
179
|
+
return 0.0 if total_weight.zero?
|
|
180
|
+
|
|
181
|
+
weighted_sum = active.sum { |s| (s.confidence * s.effective_weight).round(10) }.round(10)
|
|
182
|
+
(weighted_sum / total_weight).round(10)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def merge_content(active)
|
|
186
|
+
dominant = active.max_by(&:effective_weight)
|
|
187
|
+
{
|
|
188
|
+
dominant_type: dominant.stream_type,
|
|
189
|
+
dominant_weight: dominant.effective_weight.round(6),
|
|
190
|
+
stream_types: active.map(&:stream_type).uniq,
|
|
191
|
+
stream_count: active.size,
|
|
192
|
+
payload_snapshot: dominant.content
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def opposing_weights?(str_a, str_b)
|
|
197
|
+
(str_a.weight - str_b.weight).abs > 0.5
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def conflicting_content?(str_a, str_b)
|
|
201
|
+
keys_a = str_a.content.is_a?(Hash) ? str_a.content.keys : []
|
|
202
|
+
keys_b = str_b.content.is_a?(Hash) ? str_b.content.keys : []
|
|
203
|
+
(keys_a & keys_b).any? { |k| str_a.content[k] != str_b.content[k] }
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module CognitiveSynthesis
|
|
8
|
+
module Helpers
|
|
9
|
+
class SynthesisStream
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :stream_type, :content, :weight, :confidence, :freshness, :created_at
|
|
13
|
+
|
|
14
|
+
def initialize(stream_type:, content:, weight: DEFAULT_WEIGHT, confidence: DEFAULT_WEIGHT)
|
|
15
|
+
@id = SecureRandom.uuid
|
|
16
|
+
@stream_type = stream_type
|
|
17
|
+
@content = content
|
|
18
|
+
@weight = weight.clamp(0.0, 1.0)
|
|
19
|
+
@confidence = confidence.clamp(0.0, 1.0)
|
|
20
|
+
@freshness = 1.0
|
|
21
|
+
@created_at = Time.now.utc
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def decay_freshness!
|
|
25
|
+
@freshness = (@freshness - FRESHNESS_DECAY).clamp(0.0, 1.0)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def stale?
|
|
29
|
+
@freshness < 0.1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def effective_weight
|
|
33
|
+
(@weight * @freshness * @confidence).round(10)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def coherence_label
|
|
37
|
+
COHERENCE_LABELS.find { |range, _| range.cover?(@weight) }&.last || :chaotic
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def confidence_label
|
|
41
|
+
CONFIDENCE_LABELS.find { |range, _| range.cover?(@confidence) }&.last || :guessing
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_h
|
|
45
|
+
{
|
|
46
|
+
id: @id,
|
|
47
|
+
stream_type: @stream_type,
|
|
48
|
+
content: @content,
|
|
49
|
+
weight: @weight,
|
|
50
|
+
confidence: @confidence,
|
|
51
|
+
freshness: @freshness,
|
|
52
|
+
effective_weight: effective_weight,
|
|
53
|
+
stale: stale?,
|
|
54
|
+
coherence_label: coherence_label,
|
|
55
|
+
confidence_label: confidence_label,
|
|
56
|
+
created_at: @created_at
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveSynthesis
|
|
6
|
+
module Runners
|
|
7
|
+
module CognitiveSynthesis
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def add_stream(stream_type:, content:, weight: Helpers::Constants::DEFAULT_WEIGHT,
|
|
12
|
+
confidence: Helpers::Constants::DEFAULT_WEIGHT, engine: nil, **)
|
|
13
|
+
target = engine || synthesis_engine
|
|
14
|
+
result = target.add_stream(stream_type: stream_type, content: content,
|
|
15
|
+
weight: weight, confidence: confidence)
|
|
16
|
+
Legion::Logging.debug "[cognitive_synthesis] runner add_stream type=#{stream_type}"
|
|
17
|
+
result
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def remove_stream(stream_id:, engine: nil, **)
|
|
21
|
+
target = engine || synthesis_engine
|
|
22
|
+
Legion::Logging.debug "[cognitive_synthesis] runner remove_stream id=#{stream_id[0..7]}"
|
|
23
|
+
target.remove_stream(stream_id: stream_id)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def synthesize(engine: nil, **)
|
|
27
|
+
target = engine || synthesis_engine
|
|
28
|
+
Legion::Logging.debug '[cognitive_synthesis] runner synthesize'
|
|
29
|
+
target.synthesize!
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def decay_streams(engine: nil, **)
|
|
33
|
+
target = engine || synthesis_engine
|
|
34
|
+
Legion::Logging.debug '[cognitive_synthesis] runner decay_streams'
|
|
35
|
+
target.decay_all!
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def check_conflict(stream_id_a:, stream_id_b:, engine: nil, **)
|
|
39
|
+
target = engine || synthesis_engine
|
|
40
|
+
Legion::Logging.debug "[cognitive_synthesis] runner check_conflict #{stream_id_a[0..7]}<>#{stream_id_b[0..7]}"
|
|
41
|
+
target.stream_conflict?(stream_id_a: stream_id_a, stream_id_b: stream_id_b)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def dominant_stream(engine: nil, **)
|
|
45
|
+
target = engine || synthesis_engine
|
|
46
|
+
Legion::Logging.debug '[cognitive_synthesis] runner dominant_stream'
|
|
47
|
+
target.dominant_stream
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def synthesis_history(limit: 10, engine: nil, **)
|
|
51
|
+
target = engine || synthesis_engine
|
|
52
|
+
Legion::Logging.debug "[cognitive_synthesis] runner synthesis_history limit=#{limit}"
|
|
53
|
+
target.synthesis_history(limit: limit)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def average_coherence(window: 10, engine: nil, **)
|
|
57
|
+
target = engine || synthesis_engine
|
|
58
|
+
Legion::Logging.debug "[cognitive_synthesis] runner average_coherence window=#{window}"
|
|
59
|
+
target.average_coherence(window: window)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def status(engine: nil, **)
|
|
63
|
+
target = engine || synthesis_engine
|
|
64
|
+
stats = target.to_h
|
|
65
|
+
Legion::Logging.debug "[cognitive_synthesis] runner status streams=#{stats[:stream_count]}"
|
|
66
|
+
{ success: true }.merge(stats)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def synthesis_engine
|
|
72
|
+
@synthesis_engine ||= Helpers::SynthesisEngine.new
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/cognitive_synthesis/version'
|
|
4
|
+
require 'legion/extensions/cognitive_synthesis/helpers/constants'
|
|
5
|
+
require 'legion/extensions/cognitive_synthesis/helpers/synthesis_stream'
|
|
6
|
+
require 'legion/extensions/cognitive_synthesis/helpers/synthesis'
|
|
7
|
+
require 'legion/extensions/cognitive_synthesis/helpers/synthesis_engine'
|
|
8
|
+
require 'legion/extensions/cognitive_synthesis/runners/cognitive_synthesis'
|
|
9
|
+
|
|
10
|
+
module Legion
|
|
11
|
+
module Extensions
|
|
12
|
+
module CognitiveSynthesis
|
|
13
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/cognitive_synthesis/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::CognitiveSynthesis::Client do
|
|
6
|
+
it 'responds to all runner methods' do
|
|
7
|
+
client = described_class.new
|
|
8
|
+
%i[add_stream remove_stream synthesize decay_streams check_conflict
|
|
9
|
+
dominant_stream synthesis_history average_coherence status].each do |method|
|
|
10
|
+
expect(client).to respond_to(method)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'accepts an injected engine' do
|
|
15
|
+
engine = Legion::Extensions::CognitiveSynthesis::Helpers::SynthesisEngine.new
|
|
16
|
+
client = described_class.new(engine: engine)
|
|
17
|
+
client.add_stream(stream_type: :emotional, content: {})
|
|
18
|
+
expect(engine.streams.size).to eq(1)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'creates its own engine when none injected' do
|
|
22
|
+
client = described_class.new
|
|
23
|
+
result = client.add_stream(stream_type: :emotional, content: {})
|
|
24
|
+
expect(result[:success]).to be true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitiveSynthesis::Helpers::Constants do
|
|
4
|
+
subject(:mod) { described_class }
|
|
5
|
+
|
|
6
|
+
describe 'MAX_STREAMS' do
|
|
7
|
+
it 'is 50' do
|
|
8
|
+
expect(mod::MAX_STREAMS).to eq(50)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe 'MAX_SYNTHESES' do
|
|
13
|
+
it 'is 200' do
|
|
14
|
+
expect(mod::MAX_SYNTHESES).to eq(200)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe 'DEFAULT_WEIGHT' do
|
|
19
|
+
it 'is 0.5' do
|
|
20
|
+
expect(mod::DEFAULT_WEIGHT).to eq(0.5)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe 'COHERENCE_THRESHOLD' do
|
|
25
|
+
it 'is 0.6' do
|
|
26
|
+
expect(mod::COHERENCE_THRESHOLD).to eq(0.6)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe 'NOVELTY_THRESHOLD' do
|
|
31
|
+
it 'is 0.7' do
|
|
32
|
+
expect(mod::NOVELTY_THRESHOLD).to eq(0.7)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe 'FRESHNESS_DECAY' do
|
|
37
|
+
it 'is 0.02' do
|
|
38
|
+
expect(mod::FRESHNESS_DECAY).to eq(0.02)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe 'MIN_STREAMS_FOR_SYNTHESIS' do
|
|
43
|
+
it 'is 2' do
|
|
44
|
+
expect(mod::MIN_STREAMS_FOR_SYNTHESIS).to eq(2)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe 'STREAM_TYPES' do
|
|
49
|
+
it 'includes all expected types' do
|
|
50
|
+
expect(mod::STREAM_TYPES).to include(:emotional, :perceptual, :memorial, :predictive,
|
|
51
|
+
:reasoning, :social, :identity, :motor)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'is frozen' do
|
|
55
|
+
expect(mod::STREAM_TYPES).to be_frozen
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe 'COHERENCE_LABELS' do
|
|
60
|
+
it 'maps high values to :unified' do
|
|
61
|
+
label = mod::COHERENCE_LABELS.find { |range, _| range.cover?(0.9) }&.last
|
|
62
|
+
expect(label).to eq(:unified)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'maps low values to :chaotic' do
|
|
66
|
+
label = mod::COHERENCE_LABELS.find { |range, _| range.cover?(0.1) }&.last
|
|
67
|
+
expect(label).to eq(:chaotic)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'maps mid values to :coherent' do
|
|
71
|
+
label = mod::COHERENCE_LABELS.find { |range, _| range.cover?(0.7) }&.last
|
|
72
|
+
expect(label).to eq(:coherent)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe 'CONFIDENCE_LABELS' do
|
|
77
|
+
it 'maps high values to :certain' do
|
|
78
|
+
label = mod::CONFIDENCE_LABELS.find { |range, _| range.cover?(0.95) }&.last
|
|
79
|
+
expect(label).to eq(:certain)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'maps low values to :guessing' do
|
|
83
|
+
label = mod::CONFIDENCE_LABELS.find { |range, _| range.cover?(0.05) }&.last
|
|
84
|
+
expect(label).to eq(:guessing)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|