lex-expectation-violation 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: 36d95c00c9874f88a704e09c66272d6c53d15f0ef0dba263d96bdb4c89aca09d
4
+ data.tar.gz: fe5b4796fb94a4f90b01cbb2568f6819ddf49f5e0cc0f1841473cf9f340c6c0d
5
+ SHA512:
6
+ metadata.gz: 24e930f5b5beec88c7e8c6356f897c0444fc121dac4f84d6d714606e48de5869901222ca1b6125ec45a69206cd15c31e519a20615e4cde12cf3c3515a68bc758
7
+ data.tar.gz: 060b4374e7d6d3df5089bd01c6fd3fa0972c7d65edb26895d0040138a8e6a1b11fd4e854da1ad9049c8efaef85a7c2806d6da619116cf45379f44d68da5277de
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # lex-expectation-violation
2
+
3
+ Expectation violation modeling for the LegionIO brain-modeled cognitive architecture.
4
+
5
+ ## What It Does
6
+
7
+ Detects and processes mismatches between what the agent predicts and what actually happens. Computes violation magnitude, classifies violations as positive surprise, negative surprise, schema mismatch, or neutral, and selects adaptive responses: belief updating, heightened attention, or curiosity-driven exploration. The attention boost from significant violations ensures unexpected events receive more processing.
8
+
9
+ Based on predictive processing theory (Clark, Friston) and violation of expectation research.
10
+
11
+ ## Usage
12
+
13
+ ```ruby
14
+ client = Legion::Extensions::ExpectationViolation::Client.new
15
+
16
+ # Register an expectation before acting
17
+ client.register_expectation(
18
+ domain: :networking,
19
+ predicted: :success,
20
+ confidence: 0.85
21
+ )
22
+ # => { success: true, expectation_id: "...", domain: :networking }
23
+
24
+ # Check the expectation after observing the outcome
25
+ client.check_expectation(expectation_id: '...', actual: :timeout)
26
+ # => { success: true, violation_detected: true, magnitude: 0.85,
27
+ # violation_type: :negative_surprise, response_mode: :update_belief,
28
+ # surprise_label: :surprising }
29
+
30
+ # Process the violation (executes the response mode)
31
+ client.process_violation(violation_id: '...')
32
+ # => { processed: true, belief_updated: true, attention_boost: 0.25, exploration_triggered: false }
33
+
34
+ # Check current attention level
35
+ client.attention_level
36
+ # => { attention: 0.7, attention_label: :alert, heightened: true }
37
+
38
+ # Expectation accuracy over a domain
39
+ client.expectation_accuracy(domain: :networking)
40
+ # => { accuracy_rate: 0.72, total_checked: 25, violations_count: 7 }
41
+
42
+ # Periodic maintenance
43
+ client.update_expectation_violation
44
+ ```
45
+
46
+ ## Development
47
+
48
+ ```bash
49
+ bundle install
50
+ bundle exec rspec
51
+ bundle exec rubocop
52
+ ```
53
+
54
+ ## License
55
+
56
+ MIT
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module ExpectationViolation
6
+ module Helpers
7
+ class Client
8
+ include Runners::ExpectationViolation
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module ExpectationViolation
6
+ module Helpers
7
+ module Constants
8
+ VIOLATION_TYPES = %i[positive negative neutral].freeze
9
+
10
+ VIOLATION_LABELS = {
11
+ (0.8..) => :extreme_positive,
12
+ (0.3...0.8) => :positive,
13
+ (-0.3...0.3) => :neutral,
14
+ (-0.8...-0.3) => :negative,
15
+ (..-0.8) => :extreme_negative
16
+ }.freeze
17
+
18
+ AROUSAL_LABELS = {
19
+ (0.8..) => :highly_aroused,
20
+ (0.6...0.8) => :aroused,
21
+ (0.4...0.6) => :moderate,
22
+ (0.2...0.4) => :calm,
23
+ (..0.2) => :unaffected
24
+ }.freeze
25
+
26
+ MAX_EXPECTATIONS = 200
27
+ MAX_VIOLATIONS = 500
28
+ MAX_HISTORY = 500
29
+
30
+ DEFAULT_EXPECTATION = 0.5
31
+ EXPECTATION_FLOOR = 0.0
32
+ EXPECTATION_CEILING = 1.0
33
+
34
+ AROUSAL_BASE = 0.3
35
+ AROUSAL_MULTIPLIER = 0.7
36
+
37
+ ADAPTATION_RATE = 0.1
38
+ DECAY_RATE = 0.02
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module ExpectationViolation
8
+ module Helpers
9
+ class Expectation
10
+ include Constants
11
+
12
+ attr_reader :id, :context, :domain, :expected_value, :tolerance,
13
+ :violation_count, :adaptation_count, :created_at
14
+
15
+ def initialize(context:, domain:, expected_value: DEFAULT_EXPECTATION, tolerance: 0.2)
16
+ @id = SecureRandom.uuid
17
+ @context = context
18
+ @domain = domain
19
+ @expected_value = expected_value.clamp(EXPECTATION_FLOOR, EXPECTATION_CEILING)
20
+ @tolerance = tolerance.clamp(0.01, 0.5)
21
+ @violation_count = 0
22
+ @adaptation_count = 0
23
+ @created_at = Time.now.utc
24
+ end
25
+
26
+ def evaluate(actual_value:)
27
+ deviation = actual_value - @expected_value
28
+ violated = deviation.abs > @tolerance
29
+
30
+ if violated
31
+ @violation_count += 1
32
+ arousal = compute_arousal(deviation)
33
+ violation_type = deviation.positive? ? :positive : :negative
34
+ { violated: true, deviation: deviation.round(3), type: violation_type,
35
+ arousal: arousal.round(3), arousal_label: arousal_label(arousal) }
36
+ else
37
+ { violated: false, deviation: deviation.round(3), type: :neutral,
38
+ arousal: 0.0, arousal_label: :unaffected }
39
+ end
40
+ end
41
+
42
+ def adapt!(actual_value:)
43
+ @expected_value += (actual_value - @expected_value) * ADAPTATION_RATE
44
+ @expected_value = @expected_value.clamp(EXPECTATION_FLOOR, EXPECTATION_CEILING)
45
+ @adaptation_count += 1
46
+ end
47
+
48
+ def violation_label(deviation)
49
+ VIOLATION_LABELS.find { |range, _| range.cover?(deviation) }&.last || :neutral
50
+ end
51
+
52
+ def to_h
53
+ {
54
+ id: @id,
55
+ context: @context,
56
+ domain: @domain,
57
+ expected_value: @expected_value.round(3),
58
+ tolerance: @tolerance,
59
+ violation_count: @violation_count,
60
+ adaptation_count: @adaptation_count,
61
+ created_at: @created_at
62
+ }
63
+ end
64
+
65
+ private
66
+
67
+ def compute_arousal(deviation)
68
+ (AROUSAL_BASE + (deviation.abs * AROUSAL_MULTIPLIER)).clamp(0.0, 1.0)
69
+ end
70
+
71
+ def arousal_label(arousal)
72
+ AROUSAL_LABELS.find { |range, _| range.cover?(arousal) }&.last || :moderate
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module ExpectationViolation
6
+ module Helpers
7
+ class ViolationEngine
8
+ include Constants
9
+
10
+ attr_reader :history
11
+
12
+ def initialize
13
+ @expectations = {}
14
+ @violations = []
15
+ @history = []
16
+ end
17
+
18
+ def create_expectation(context:, domain:, expected_value: DEFAULT_EXPECTATION, tolerance: 0.2)
19
+ evict_oldest if @expectations.size >= MAX_EXPECTATIONS
20
+
21
+ expectation = Expectation.new(
22
+ context: context,
23
+ domain: domain,
24
+ expected_value: expected_value,
25
+ tolerance: tolerance
26
+ )
27
+ @expectations[expectation.id] = expectation
28
+ record_history(:created, expectation.id)
29
+ expectation
30
+ end
31
+
32
+ def evaluate_against(expectation_id:, actual_value:)
33
+ expectation = @expectations[expectation_id]
34
+ return { success: false, reason: :not_found } unless expectation
35
+
36
+ result = expectation.evaluate(actual_value: actual_value)
37
+
38
+ if result[:violated]
39
+ @violations << {
40
+ expectation_id: expectation_id,
41
+ type: result[:type],
42
+ deviation: result[:deviation],
43
+ arousal: result[:arousal],
44
+ at: Time.now.utc
45
+ }
46
+ trim_violations
47
+ end
48
+
49
+ record_history(:evaluated, expectation_id)
50
+ { success: true }.merge(result)
51
+ end
52
+
53
+ def adapt_expectation(expectation_id:, actual_value:)
54
+ expectation = @expectations[expectation_id]
55
+ return { success: false, reason: :not_found } unless expectation
56
+
57
+ expectation.adapt!(actual_value: actual_value)
58
+ record_history(:adapted, expectation_id)
59
+ { success: true, new_expected: expectation.expected_value.round(3) }
60
+ end
61
+
62
+ def recent_violations(limit: 10)
63
+ @violations.last(limit)
64
+ end
65
+
66
+ def violations_by_type(type:)
67
+ @violations.select { |vio| vio[:type] == type }
68
+ end
69
+
70
+ def expectations_by_domain(domain:)
71
+ @expectations.values.select { |exp| exp.domain == domain }
72
+ end
73
+
74
+ def most_violated(limit: 5)
75
+ @expectations.values.sort_by { |exp| -exp.violation_count }.first(limit)
76
+ end
77
+
78
+ def violation_rate
79
+ total = @expectations.values.sum(&:violation_count)
80
+ evals = @history.count { |entry| entry[:event] == :evaluated }
81
+ return 0.0 if evals.zero?
82
+
83
+ (total.to_f / evals).round(3)
84
+ end
85
+
86
+ def positive_violation_ratio
87
+ return 0.0 if @violations.empty?
88
+
89
+ positive = @violations.count { |vio| vio[:type] == :positive }
90
+ (positive.to_f / @violations.size).round(3)
91
+ end
92
+
93
+ def to_h
94
+ {
95
+ total_expectations: @expectations.size,
96
+ total_violations: @violations.size,
97
+ violation_rate: violation_rate,
98
+ positive_ratio: positive_violation_ratio,
99
+ history_count: @history.size
100
+ }
101
+ end
102
+
103
+ private
104
+
105
+ def evict_oldest
106
+ oldest_id = @expectations.min_by { |_id, exp| exp.created_at }&.first
107
+ @expectations.delete(oldest_id) if oldest_id
108
+ end
109
+
110
+ def trim_violations
111
+ @violations.shift while @violations.size > MAX_VIOLATIONS
112
+ end
113
+
114
+ def record_history(event, expectation_id)
115
+ @history << { event: event, expectation_id: expectation_id, at: Time.now.utc }
116
+ @history.shift while @history.size > MAX_HISTORY
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module ExpectationViolation
6
+ module Runners
7
+ module ExpectationViolation
8
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
9
+ Legion::Extensions::Helpers.const_defined?(:Lex)
10
+
11
+ def create_expectation(context:, domain:, expected_value: nil, tolerance: 0.2, **)
12
+ exp = engine.create_expectation(
13
+ context: context,
14
+ domain: domain.to_sym,
15
+ expected_value: expected_value || Helpers::Constants::DEFAULT_EXPECTATION,
16
+ tolerance: tolerance
17
+ )
18
+ Legion::Logging.debug "[expectation_violation] create id=#{exp.id[0..7]} " \
19
+ "ctx=#{context} expected=#{exp.expected_value}"
20
+ { success: true, expectation: exp.to_h }
21
+ end
22
+
23
+ def evaluate_expectation(expectation_id:, actual_value:, **)
24
+ result = engine.evaluate_against(expectation_id: expectation_id, actual_value: actual_value)
25
+ Legion::Logging.debug "[expectation_violation] evaluate id=#{expectation_id[0..7]} " \
26
+ "violated=#{result[:violated]} type=#{result[:type]}"
27
+ result
28
+ end
29
+
30
+ def adapt_expectation_value(expectation_id:, actual_value:, **)
31
+ result = engine.adapt_expectation(expectation_id: expectation_id, actual_value: actual_value)
32
+ Legion::Logging.debug "[expectation_violation] adapt id=#{expectation_id[0..7]} " \
33
+ "new=#{result[:new_expected]}"
34
+ result
35
+ end
36
+
37
+ def recent_violations_report(limit: 10, **)
38
+ violations = engine.recent_violations(limit: limit)
39
+ Legion::Logging.debug "[expectation_violation] recent count=#{violations.size}"
40
+ { success: true, violations: violations, count: violations.size }
41
+ end
42
+
43
+ def violations_by_type_report(type:, **)
44
+ violations = engine.violations_by_type(type: type.to_sym)
45
+ Legion::Logging.debug '[expectation_violation] by_type ' \
46
+ "type=#{type} count=#{violations.size}"
47
+ { success: true, violations: violations, count: violations.size }
48
+ end
49
+
50
+ def most_violated_expectations(limit: 5, **)
51
+ exps = engine.most_violated(limit: limit)
52
+ Legion::Logging.debug "[expectation_violation] most_violated count=#{exps.size}"
53
+ { success: true, expectations: exps.map(&:to_h), count: exps.size }
54
+ end
55
+
56
+ def expectation_violation_stats(**)
57
+ stats = engine.to_h
58
+ Legion::Logging.debug '[expectation_violation] stats ' \
59
+ "total=#{stats[:total_expectations]}"
60
+ { success: true }.merge(stats)
61
+ end
62
+
63
+ private
64
+
65
+ def engine
66
+ @engine ||= Helpers::ViolationEngine.new
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module ExpectationViolation
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'expectation_violation/version'
4
+ require_relative 'expectation_violation/helpers/constants'
5
+ require_relative 'expectation_violation/helpers/expectation'
6
+ require_relative 'expectation_violation/helpers/violation_engine'
7
+ require_relative 'expectation_violation/runners/expectation_violation'
8
+ require_relative 'expectation_violation/helpers/client'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module ExpectationViolation
13
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined?(:Core)
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-expectation-violation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Esity
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 expectations with tolerance bands and detects violations. Positive
27
+ violations strengthen trust, negative violations damage it. Based on Burgoon (1978)
28
+ Expectancy Violations Theory.
29
+ email:
30
+ - matthewdiverson@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - README.md
36
+ - lib/legion/extensions/expectation_violation.rb
37
+ - lib/legion/extensions/expectation_violation/helpers/client.rb
38
+ - lib/legion/extensions/expectation_violation/helpers/constants.rb
39
+ - lib/legion/extensions/expectation_violation/helpers/expectation.rb
40
+ - lib/legion/extensions/expectation_violation/helpers/violation_engine.rb
41
+ - lib/legion/extensions/expectation_violation/runners/expectation_violation.rb
42
+ - lib/legion/extensions/expectation_violation/version.rb
43
+ homepage: https://github.com/LegionIO/lex-expectation-violation
44
+ licenses:
45
+ - MIT
46
+ metadata:
47
+ homepage_uri: https://github.com/LegionIO/lex-expectation-violation
48
+ source_code_uri: https://github.com/LegionIO/lex-expectation-violation
49
+ documentation_uri: https://github.com/LegionIO/lex-expectation-violation
50
+ changelog_uri: https://github.com/LegionIO/lex-expectation-violation/blob/master/CHANGELOG.md
51
+ bug_tracker_uri: https://github.com/LegionIO/lex-expectation-violation/issues
52
+ rubygems_mfa_required: 'true'
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '3.4'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.6.9
68
+ specification_version: 4
69
+ summary: Expectancy violation theory for LegionIO
70
+ test_files: []