lex-priming 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 43f24c3a9f138b82377240b94d0dc119248eef77e33471cdea3e3cecc441c214
4
+ data.tar.gz: be0034521b593b83159a3ee7004020145941c2bc4e83c20a75b69fe03d333763
5
+ SHA512:
6
+ metadata.gz: 977af104675e1ee4e6ed965846932eb9443cdbbae17d2d3d22a823e50f197e3e8db07d0efaeb6408a4a71f57eccfce09f27bb3f72904b97238103e4beadbc67e
7
+ data.tar.gz: e402a48a4c880eafe14fb2156a0101a68c624e207ffed52930d1bb3eebc93ff5ce240f864720cf581cf739993dab5c4baf1caa1add3d8f687ecc6bff49a96930
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Iverson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # lex-priming
2
+
3
+ Associative activation network for the LegionIO cognitive architecture. Implements spreading activation priming across linked concept nodes.
4
+
5
+ ## What It Does
6
+
7
+ Maintains a network of concept nodes connected by association strengths. When a concept is primed, its activation is boosted and that activation spreads through the network via BFS — each hop attenuated by the spread factor and association strength. Concepts decay each tick. Supports querying which concepts are currently primed, finding the strongest activations, and retrieving neighbors.
8
+
9
+ ## Usage
10
+
11
+ ```ruby
12
+ client = Legion::Extensions::Priming::Client.new
13
+
14
+ # Build the concept network
15
+ client.add_concept(name: :authentication, domain: :security)
16
+ client.add_concept(name: :credentials, domain: :security)
17
+ client.add_concept(name: :session_token, domain: :security)
18
+ client.link_concepts(name_a: :authentication, name_b: :credentials, strength: 0.8)
19
+ client.link_concepts(name_a: :credentials, name_b: :session_token, strength: 0.7)
20
+
21
+ # Prime a concept (activation spreads to neighbors)
22
+ client.prime_concept(name: :authentication, boost: 0.5, spread: true)
23
+
24
+ # Check which concepts are now active
25
+ client.primed_concepts
26
+ # => { success: true, concepts: [
27
+ # { name: :authentication, activation: 0.5, domain: :security },
28
+ # { name: :credentials, activation: 0.175, domain: :security },
29
+ # { name: :session_token, activation: 0.031, domain: :security }
30
+ # ], count: 3 }
31
+
32
+ # Top activations
33
+ client.strongest_primes(count: 3)
34
+
35
+ # Check a specific concept
36
+ client.check_primed(name: :credentials)
37
+ # => { success: true, name: :credentials, activation: 0.175, primed: true, source: :authentication }
38
+
39
+ # Each tick: decay activations
40
+ client.update_priming
41
+ client.priming_stats
42
+ ```
43
+
44
+ ## Development
45
+
46
+ ```bash
47
+ bundle install
48
+ bundle exec rspec
49
+ bundle exec rubocop
50
+ ```
51
+
52
+ ## License
53
+
54
+ MIT
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Priming
6
+ class Client
7
+ include Runners::Priming
8
+
9
+ attr_reader :network
10
+
11
+ def initialize(network: nil, **)
12
+ @network = network || Helpers::ActivationNetwork.new
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Priming
6
+ module Helpers
7
+ class ActivationNetwork
8
+ attr_reader :concepts
9
+
10
+ def initialize
11
+ @concepts = {}
12
+ end
13
+
14
+ def add_concept(name:, domain: :general)
15
+ @concepts[name] ||= ConceptNode.new(name: name, domain: domain)
16
+ trim_concepts
17
+ @concepts[name]
18
+ end
19
+
20
+ def link(name_a, name_b, strength: Constants::DEFAULT_ASSOCIATION_STRENGTH)
21
+ add_concept(name: name_a) unless @concepts.key?(name_a)
22
+ add_concept(name: name_b) unless @concepts.key?(name_b)
23
+ @concepts[name_a].associate(name_b, strength: strength)
24
+ @concepts[name_b].associate(name_a, strength: strength)
25
+ end
26
+
27
+ def prime(name, boost: Constants::PRIME_BOOST, source: nil, spread: true)
28
+ concept = @concepts[name]
29
+ return nil unless concept
30
+
31
+ concept.prime(boost: boost, source: source)
32
+ spread_activation(name, boost) if spread
33
+ concept
34
+ end
35
+
36
+ def activation_for(name)
37
+ concept = @concepts[name]
38
+ concept ? concept.activation : 0.0
39
+ end
40
+
41
+ def primed_concepts
42
+ @concepts.values.select(&:primed?)
43
+ end
44
+
45
+ def primed_in_domain(domain)
46
+ @concepts.values.select { |c| c.domain == domain && c.primed? }
47
+ end
48
+
49
+ def decay_all
50
+ @concepts.each_value do |concept|
51
+ concept.decay_activation
52
+ concept.decay_associations
53
+ end
54
+ end
55
+
56
+ def neighbors(name)
57
+ concept = @concepts[name]
58
+ return [] unless concept
59
+
60
+ concept.associated_names.filter_map { |n| @concepts[n] }
61
+ end
62
+
63
+ def concept_count
64
+ @concepts.size
65
+ end
66
+
67
+ def active_prime_count
68
+ primed_concepts.size
69
+ end
70
+
71
+ def strongest_primes(count = 5)
72
+ primed_concepts.sort_by { |c| -c.activation }.first(count)
73
+ end
74
+
75
+ def to_h
76
+ {
77
+ concept_count: @concepts.size,
78
+ active_primes: active_prime_count,
79
+ primed_concepts: primed_concepts.map { |c| { name: c.name, activation: c.activation.round(4) } },
80
+ domains: @concepts.values.map(&:domain).uniq
81
+ }
82
+ end
83
+
84
+ private
85
+
86
+ def spread_activation(source_name, initial_boost)
87
+ visited = Set.new([source_name])
88
+ queue = [[source_name, initial_boost, 0]]
89
+
90
+ while queue.any?
91
+ current_name, current_boost, depth = queue.shift
92
+ next if depth >= Constants::MAX_SPREAD_HOPS
93
+
94
+ concept = @concepts[current_name]
95
+ next unless concept
96
+
97
+ concept.associated_names.each do |neighbor_name|
98
+ next if visited.include?(neighbor_name)
99
+
100
+ visited.add(neighbor_name)
101
+ neighbor = @concepts[neighbor_name]
102
+ next unless neighbor
103
+
104
+ strength = concept.association_strength(neighbor_name)
105
+ spread_boost = current_boost * Constants::SPREAD_FACTOR * strength
106
+ next if spread_boost < Constants::PRIME_THRESHOLD
107
+
108
+ neighbor.prime(boost: spread_boost, source: source_name)
109
+ concept.strengthen_association(neighbor_name)
110
+ queue << [neighbor_name, spread_boost, depth + 1]
111
+ end
112
+ end
113
+ end
114
+
115
+ def trim_concepts
116
+ return unless @concepts.size > Constants::MAX_CONCEPTS
117
+
118
+ sorted = @concepts.sort_by { |_, c| c.activation }
119
+ excess = @concepts.size - Constants::MAX_CONCEPTS
120
+ sorted.first(excess).each { |name, _| @concepts.delete(name) } # rubocop:disable Style/HashEachMethods
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Priming
6
+ module Helpers
7
+ class ConceptNode
8
+ attr_reader :name, :domain, :associations
9
+ attr_accessor :activation, :prime_source
10
+
11
+ def initialize(name:, domain: :general)
12
+ @name = name
13
+ @domain = domain
14
+ @activation = 0.0
15
+ @prime_source = nil
16
+ @associations = {}
17
+ end
18
+
19
+ def primed?
20
+ @activation >= Constants::PRIME_THRESHOLD
21
+ end
22
+
23
+ def associate(other_name, strength: Constants::DEFAULT_ASSOCIATION_STRENGTH)
24
+ @associations[other_name] = strength.clamp(Constants::ASSOCIATION_FLOOR, Constants::ASSOCIATION_CEILING)
25
+ trim_associations
26
+ end
27
+
28
+ def strengthen_association(other_name)
29
+ return unless @associations.key?(other_name)
30
+
31
+ @associations[other_name] = [
32
+ @associations[other_name] + Constants::ASSOCIATION_STRENGTHEN,
33
+ Constants::ASSOCIATION_CEILING
34
+ ].min
35
+ end
36
+
37
+ def decay_associations
38
+ @associations.each_key do |key|
39
+ @associations[key] = [
40
+ @associations[key] - Constants::ASSOCIATION_DECAY,
41
+ Constants::ASSOCIATION_FLOOR
42
+ ].max
43
+ end
44
+ end
45
+
46
+ def decay_activation
47
+ @activation = [@activation - Constants::PRIME_DECAY, 0.0].max
48
+ @prime_source = nil unless primed?
49
+ end
50
+
51
+ def prime(boost: Constants::PRIME_BOOST, source: nil)
52
+ @activation = [@activation + boost, 1.0].min
53
+ @prime_source = source
54
+ end
55
+
56
+ def association_strength(other_name)
57
+ @associations[other_name] || 0.0
58
+ end
59
+
60
+ def associated_names
61
+ @associations.keys
62
+ end
63
+
64
+ def to_h
65
+ {
66
+ name: @name,
67
+ domain: @domain,
68
+ activation: @activation.round(4),
69
+ primed: primed?,
70
+ prime_source: @prime_source,
71
+ associations: @associations.transform_values { |v| v.round(4) }
72
+ }
73
+ end
74
+
75
+ private
76
+
77
+ def trim_associations
78
+ return unless @associations.size > Constants::MAX_ASSOCIATIONS
79
+
80
+ sorted = @associations.sort_by { |_, v| v }
81
+ excess = @associations.size - Constants::MAX_ASSOCIATIONS
82
+ sorted.first(excess).each { |name, _| @associations.delete(name) } # rubocop:disable Style/HashEachMethods
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Priming
6
+ module Helpers
7
+ module Constants
8
+ # Base activation boost from a prime
9
+ PRIME_BOOST = 0.3
10
+
11
+ # How fast priming decays per tick
12
+ PRIME_DECAY = 0.08
13
+
14
+ # Spreading activation factor (each hop reduces by this ratio)
15
+ SPREAD_FACTOR = 0.5
16
+
17
+ # Maximum hops for spreading activation
18
+ MAX_SPREAD_HOPS = 3
19
+
20
+ # Minimum activation to be considered primed
21
+ PRIME_THRESHOLD = 0.1
22
+
23
+ # Maximum concepts in the network
24
+ MAX_CONCEPTS = 500
25
+
26
+ # Maximum associations per concept
27
+ MAX_ASSOCIATIONS = 20
28
+
29
+ # Maximum active primes at once
30
+ MAX_ACTIVE_PRIMES = 100
31
+
32
+ # Default association strength between new connections
33
+ DEFAULT_ASSOCIATION_STRENGTH = 0.5
34
+
35
+ # Strengthening factor when association is activated
36
+ ASSOCIATION_STRENGTHEN = 0.05
37
+
38
+ # Weakening factor for unused associations
39
+ ASSOCIATION_DECAY = 0.01
40
+
41
+ # Association strength floor (never fully disconnect)
42
+ ASSOCIATION_FLOOR = 0.05
43
+
44
+ # Association strength ceiling
45
+ ASSOCIATION_CEILING = 1.0
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Priming
6
+ module Runners
7
+ module Priming
8
+ include Legion::Extensions::Helpers::Lex
9
+
10
+ def network
11
+ @network ||= Helpers::ActivationNetwork.new
12
+ end
13
+
14
+ def prime_concept(name:, boost: nil, source: nil, spread: true, **)
15
+ boost ||= Helpers::Constants::PRIME_BOOST
16
+ network.add_concept(name: name) unless network.concepts.key?(name)
17
+ concept = network.prime(name, boost: boost, source: source, spread: spread)
18
+ return { success: false, reason: :not_found } unless concept
19
+
20
+ Legion::Logging.debug "[priming] primed #{name} activation=#{concept.activation.round(4)} spread=#{spread}"
21
+ { success: true, concept: concept.to_h, active_primes: network.active_prime_count }
22
+ end
23
+
24
+ def add_concept(name:, domain: :general, **)
25
+ concept = network.add_concept(name: name, domain: domain)
26
+ { success: true, concept: concept.to_h }
27
+ end
28
+
29
+ def link_concepts(name_a:, name_b:, strength: nil, **)
30
+ strength ||= Helpers::Constants::DEFAULT_ASSOCIATION_STRENGTH
31
+ network.link(name_a, name_b, strength: strength)
32
+ Legion::Logging.debug "[priming] linked #{name_a} <-> #{name_b} strength=#{strength}"
33
+ { success: true, name_a: name_a, name_b: name_b, strength: strength }
34
+ end
35
+
36
+ def update_priming(**)
37
+ network.decay_all
38
+ primed = network.primed_concepts
39
+ Legion::Logging.debug "[priming] tick: concepts=#{network.concept_count} active=#{primed.size}"
40
+ { success: true, active_primes: primed.size, concept_count: network.concept_count }
41
+ end
42
+
43
+ def check_primed(name:, **)
44
+ activation = network.activation_for(name)
45
+ concept = network.concepts[name]
46
+ {
47
+ success: true,
48
+ name: name,
49
+ activation: activation.round(4),
50
+ primed: activation >= Helpers::Constants::PRIME_THRESHOLD,
51
+ source: concept&.prime_source
52
+ }
53
+ end
54
+
55
+ def primed_concepts(domain: nil, **)
56
+ concepts = domain ? network.primed_in_domain(domain) : network.primed_concepts
57
+ {
58
+ success: true,
59
+ concepts: concepts.map { |c| { name: c.name, activation: c.activation.round(4), domain: c.domain } },
60
+ count: concepts.size
61
+ }
62
+ end
63
+
64
+ def strongest_primes(count: 5, **)
65
+ top = network.strongest_primes(count)
66
+ {
67
+ success: true,
68
+ primes: top.map { |c| { name: c.name, activation: c.activation.round(4), domain: c.domain } },
69
+ count: top.size
70
+ }
71
+ end
72
+
73
+ def neighbors_for(name:, **)
74
+ nbrs = network.neighbors(name)
75
+ {
76
+ success: true,
77
+ name: name,
78
+ neighbors: nbrs.map { |c| { name: c.name, activation: c.activation.round(4), primed: c.primed? } },
79
+ count: nbrs.size
80
+ }
81
+ end
82
+
83
+ def priming_stats(**)
84
+ { success: true, stats: network.to_h }
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Priming
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'priming/version'
4
+ require_relative 'priming/helpers/constants'
5
+ require_relative 'priming/helpers/concept_node'
6
+ require_relative 'priming/helpers/activation_network'
7
+ require_relative 'priming/runners/priming'
8
+ require_relative 'priming/client'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module Priming
13
+ extend Legion::Extensions::Core if defined?(Legion::Extensions::Core)
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-priming
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Iverson
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: legion-gaia
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: Models spreading activation in semantic networks — stimuli prime related
27
+ concepts, lowering their activation threshold and speeding future access.
28
+ email:
29
+ - matt@iverson.io
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE
35
+ - README.md
36
+ - lib/legion/extensions/priming.rb
37
+ - lib/legion/extensions/priming/client.rb
38
+ - lib/legion/extensions/priming/helpers/activation_network.rb
39
+ - lib/legion/extensions/priming/helpers/concept_node.rb
40
+ - lib/legion/extensions/priming/helpers/constants.rb
41
+ - lib/legion/extensions/priming/runners/priming.rb
42
+ - lib/legion/extensions/priming/version.rb
43
+ homepage: https://github.com/LegionIO/lex-priming
44
+ licenses:
45
+ - MIT
46
+ metadata:
47
+ rubygems_mfa_required: 'true'
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '3.4'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.6.9
63
+ specification_version: 4
64
+ summary: Spreading activation and priming for LegionIO
65
+ test_files: []