lex-cognitive-anchor 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/.rspec +3 -0
- data/CLAUDE.md +88 -0
- data/README.md +70 -0
- data/lex-cognitive-anchor.gemspec +35 -0
- data/lib/legion/extensions/cognitive_anchor/client.rb +11 -0
- data/lib/legion/extensions/cognitive_anchor/helpers/anchor.rb +88 -0
- data/lib/legion/extensions/cognitive_anchor/helpers/anchor_engine.rb +119 -0
- data/lib/legion/extensions/cognitive_anchor/helpers/chain.rb +89 -0
- data/lib/legion/extensions/cognitive_anchor/helpers/constants.rb +42 -0
- data/lib/legion/extensions/cognitive_anchor/runners/cognitive_anchor.rb +66 -0
- data/lib/legion/extensions/cognitive_anchor/version.rb +9 -0
- data/lib/legion/extensions/cognitive_anchor.rb +19 -0
- metadata +73 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 59a9a520a9d3e551fd8affba60f56d2e7d675199745b75bed909b106b38a5905
|
|
4
|
+
data.tar.gz: 9ba81b07934db4cfb3de2bec2be52abd588256761d0af1882361ad6f8d6adc4b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 657a67870dca71022858a61d53592df527681066fb55f6ace939da6f87e763e93337e6a0216a21245a8c72c59c76ba8c8c2a3be0d42e08da73ca58274793ad06
|
|
7
|
+
data.tar.gz: 45e95298b0615d6071d18e3098a59d711907b9cbfc590c9699c0eed238b965b2a595531627b0aa73529e7ce51962240eb969852ca4d083db2d1bcfad5385aa65
|
data/.rspec
ADDED
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# lex-cognitive-anchor
|
|
2
|
+
|
|
3
|
+
**Level 3 Documentation**
|
|
4
|
+
- **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
|
|
5
|
+
- **Grandparent**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Cognitive anchoring and belief tethering — anchor points resist change via grip, chains link anchors with material-dependent flexibility, and bias application models how anchors distort estimates.
|
|
10
|
+
|
|
11
|
+
## Gem Info
|
|
12
|
+
|
|
13
|
+
- **Gem name**: `lex-cognitive-anchor`
|
|
14
|
+
- **Version**: `0.1.0`
|
|
15
|
+
- **Module**: `Legion::Extensions::CognitiveAnchor`
|
|
16
|
+
- **Ruby**: `>= 3.4`
|
|
17
|
+
- **License**: MIT
|
|
18
|
+
|
|
19
|
+
## File Structure
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
lib/legion/extensions/cognitive_anchor/
|
|
23
|
+
cognitive_anchor.rb # Main extension module
|
|
24
|
+
version.rb # VERSION = '0.1.0'
|
|
25
|
+
client.rb # Client wrapper
|
|
26
|
+
helpers/
|
|
27
|
+
constants.rb # Anchor types, chain materials, drag/drift rates, grip/flexibility labels
|
|
28
|
+
anchor.rb # Anchor value object (grip, type, domain, age)
|
|
29
|
+
chain.rb # Chain value object (material, two anchors, flexibility)
|
|
30
|
+
anchor_store.rb # AnchorStore — manages anchors and chains, applies bias
|
|
31
|
+
runners/
|
|
32
|
+
cognitive_anchor.rb # Runner module (extend self) with 6 public methods
|
|
33
|
+
spec/
|
|
34
|
+
(spec files)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Key Constants
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
ANCHOR_TYPES = %i[belief assumption experience authority number]
|
|
41
|
+
CHAIN_MATERIALS = %i[steel rope wire thread cobweb]
|
|
42
|
+
DRAG_RATE = 0.06 # how much grip decays when an anchor is dragged (challenged)
|
|
43
|
+
DRIFT_RATE = 0.03 # passive grip decay over time
|
|
44
|
+
BREAK_THRESHOLD = 0.1 # grip level at which an anchor breaks (chain severs)
|
|
45
|
+
GRIP_LABELS = {
|
|
46
|
+
(0.85..) => :immovable, (0.65...0.85) => :firm, (0.45...0.65) => :moderate,
|
|
47
|
+
(0.25...0.45) => :loose, (..0.25) => :slipping
|
|
48
|
+
}
|
|
49
|
+
FLEXIBILITY_LABELS = {
|
|
50
|
+
steel: :rigid, rope: :flexible, wire: :semiflexible,
|
|
51
|
+
thread: :very_flexible, cobweb: :fragile
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Runners
|
|
56
|
+
|
|
57
|
+
### `Runners::CognitiveAnchor`
|
|
58
|
+
|
|
59
|
+
Uses `extend self` — methods are module-level, not instance-delegating. Delegates to a per-call `engine` (parameter-injected `Helpers::AnchorStore` instance via `engine:` keyword, defaulting to a shared `@engine ||=`).
|
|
60
|
+
|
|
61
|
+
- `create_anchor(name:, anchor_type:, domain: :general, grip: 0.7, engine: @engine)` — register an anchor point with initial grip
|
|
62
|
+
- `create_chain(anchor_a:, anchor_b:, material:, engine: @engine)` — link two anchors with a chain of specified material; chain flexibility depends on material
|
|
63
|
+
- `apply_bias(anchor_id:, estimate:, engine: @engine)` — apply the anchor's distortion to an estimate; returns adjusted estimate and bias magnitude
|
|
64
|
+
- `list_anchors(engine: @engine)` — all anchors as array of hashes with grip labels
|
|
65
|
+
- `anchor_status(engine: @engine)` — summary: anchor count, chain count, average grip, broken chain count
|
|
66
|
+
|
|
67
|
+
## Helpers
|
|
68
|
+
|
|
69
|
+
### `Helpers::AnchorStore`
|
|
70
|
+
Core engine managing `@anchors` and `@chains` hashes. `apply_bias` computes how strongly the anchor distorts the given estimate based on current grip. `drag_anchor` reduces grip by `DRAG_RATE`; if grip drops below `BREAK_THRESHOLD`, all chains involving the anchor are severed. `drift_all` applies passive `DRIFT_RATE` decay to all anchors.
|
|
71
|
+
|
|
72
|
+
### `Helpers::Anchor`
|
|
73
|
+
Value object: name, anchor_type, domain, grip (0.0–1.0), age. `drag!` reduces grip by `DRAG_RATE` clamped to 0.0. `drift!` reduces grip by `DRIFT_RATE`. `broken?` returns true when `grip <= BREAK_THRESHOLD`. `grip_label` maps current grip to human-readable label.
|
|
74
|
+
|
|
75
|
+
### `Helpers::Chain`
|
|
76
|
+
Value object: material, anchor_a_id, anchor_b_id, flexibility (derived from material). `flexibility_label` maps material to label from `FLEXIBILITY_LABELS`. `active?` returns true only when neither linked anchor is broken.
|
|
77
|
+
|
|
78
|
+
## Integration Points
|
|
79
|
+
|
|
80
|
+
No actor defined — no automatic drift or drag. This extension models how existing beliefs and anchors resist or distort new information. Pairs with lex-anchoring (which models Kahneman/Tversky numeric anchoring and prospect theory) — these are complementary; lex-anchoring covers numeric estimation bias, lex-cognitive-anchor covers structural belief tethering. Pairs with lex-bias (anchor bias detection feeds here) and lex-belief-revision (chain breaks can trigger belief revision events).
|
|
81
|
+
|
|
82
|
+
## Development Notes
|
|
83
|
+
|
|
84
|
+
- `extend self` pattern: runner is a module with module-level methods; shared `@engine` lives in the module's singleton
|
|
85
|
+
- `BREAK_THRESHOLD = 0.1` is intentionally low — anchors are persistent; they require sustained challenge (`drag_anchor` calls) to break
|
|
86
|
+
- `DRAG_RATE (0.06) > DRIFT_RATE (0.03)`: active challenge degrades grip twice as fast as passive time decay
|
|
87
|
+
- Chain material determines flexibility label only — the mechanics of chain flexibility are not yet enforced on bias calculations (stub intent)
|
|
88
|
+
- `apply_bias` returns a hash with both the adjusted estimate and bias magnitude — callers must decide how to apply the adjustment
|
data/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# lex-cognitive-anchor
|
|
2
|
+
|
|
3
|
+
Cognitive anchoring and belief tethering — anchor points resist change via grip, chains link anchors, and bias application models how they distort estimates.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
Models how beliefs and assumptions act as anchors that resist revision. Each anchor has a `grip` (0.0–1.0) that decays when the anchor is challenged (`drag`) or simply ages (`drift`). Anchors can be chained together with material-dependent flexibility (steel = rigid, cobweb = fragile). When an anchor's grip falls below the break threshold, its chains sever.
|
|
8
|
+
|
|
9
|
+
## Core Concept: Anchor Types and Materials
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
ANCHOR_TYPES: belief, assumption, experience, authority, number
|
|
13
|
+
CHAIN_MATERIALS: steel (rigid) -> rope -> wire -> thread -> cobweb (fragile)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
client = Legion::Extensions::CognitiveAnchor::Client.new
|
|
20
|
+
|
|
21
|
+
# Register a strong belief anchor
|
|
22
|
+
belief = client.create_anchor(
|
|
23
|
+
name: :microservices_are_always_better,
|
|
24
|
+
anchor_type: :belief,
|
|
25
|
+
domain: :architecture,
|
|
26
|
+
grip: 0.85
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Register an experience-based anchor
|
|
30
|
+
exp = client.create_anchor(
|
|
31
|
+
name: :monolith_failure_2019,
|
|
32
|
+
anchor_type: :experience,
|
|
33
|
+
domain: :architecture,
|
|
34
|
+
grip: 0.70
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Chain them together with a semi-flexible wire link
|
|
38
|
+
client.create_chain(
|
|
39
|
+
anchor_a: belief[:anchor][:id],
|
|
40
|
+
anchor_b: exp[:anchor][:id],
|
|
41
|
+
material: :wire
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Apply anchor bias to a new architectural estimate
|
|
45
|
+
client.apply_bias(
|
|
46
|
+
anchor_id: belief[:anchor][:id],
|
|
47
|
+
estimate: { approach: :monolith, confidence: 0.7 }
|
|
48
|
+
)
|
|
49
|
+
# => { adjusted_estimate: ..., bias_magnitude: 0.72 }
|
|
50
|
+
|
|
51
|
+
# Check overall anchor landscape
|
|
52
|
+
client.anchor_status
|
|
53
|
+
# => { anchor_count: 2, chain_count: 1, average_grip: 0.78, broken_chains: 0 }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Integration
|
|
57
|
+
|
|
58
|
+
Pairs with lex-anchoring (numeric anchoring and prospect theory) and lex-bias (bias detection feeds here). Pairs with lex-belief-revision — when an anchor breaks, the freed beliefs become candidates for revision.
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bundle install
|
|
64
|
+
bundle exec rspec
|
|
65
|
+
bundle exec rubocop
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/cognitive_anchor/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-cognitive-anchor'
|
|
7
|
+
spec.version = Legion::Extensions::CognitiveAnchor::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Cognitive anchoring bias dynamics for LegionIO agentic architecture'
|
|
12
|
+
spec.description = 'Models anchoring biases where fixed reference points pull new information ' \
|
|
13
|
+
'toward them with configurable grip, weight, and chain flexibility'
|
|
14
|
+
spec.homepage = 'https://github.com/LegionIO/lex-cognitive-anchor'
|
|
15
|
+
spec.license = 'MIT'
|
|
16
|
+
|
|
17
|
+
spec.required_ruby_version = '>= 3.4'
|
|
18
|
+
|
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
20
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
|
21
|
+
spec.metadata['documentation_uri'] = "#{spec.homepage}/blob/master/README.md"
|
|
22
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
|
23
|
+
spec.metadata['bug_tracker_uri'] = "#{spec.homepage}/issues"
|
|
24
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
25
|
+
|
|
26
|
+
spec.files = Dir.chdir(__dir__) do
|
|
27
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
28
|
+
(File.expand_path(f) == __FILE__) ||
|
|
29
|
+
f.start_with?('spec/', '.git', '.rubocop', 'Gemfile')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
spec.require_paths = ['lib']
|
|
34
|
+
spec.add_development_dependency 'legion-gaia'
|
|
35
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveAnchor
|
|
6
|
+
module Helpers
|
|
7
|
+
class Anchor
|
|
8
|
+
attr_reader :id, :anchor_type, :domain, :content,
|
|
9
|
+
:reference_value, :created_at
|
|
10
|
+
attr_accessor :grip, :weight
|
|
11
|
+
|
|
12
|
+
def initialize(anchor_type:, domain:, content:,
|
|
13
|
+
reference_value: nil, grip: nil, weight: nil)
|
|
14
|
+
validate_anchor_type!(anchor_type)
|
|
15
|
+
@id = SecureRandom.uuid
|
|
16
|
+
@anchor_type = anchor_type.to_sym
|
|
17
|
+
@domain = domain.to_sym
|
|
18
|
+
@content = content.to_s
|
|
19
|
+
@reference_value = (reference_value || 0.5).to_f.clamp(0.0, 1.0).round(10)
|
|
20
|
+
@grip = (grip || 0.7).to_f.clamp(0.0, 1.0).round(10)
|
|
21
|
+
@weight = (weight || 0.5).to_f.clamp(0.0, 1.0).round(10)
|
|
22
|
+
@created_at = Time.now.utc
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def drag!(rate: Constants::DRAG_RATE)
|
|
26
|
+
@grip = (@grip + rate.abs).clamp(0.0, 1.0).round(10)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def drift!(rate: Constants::DRIFT_RATE)
|
|
30
|
+
@grip = (@grip - rate.abs).clamp(0.0, 1.0).round(10)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def bias_pull(new_value)
|
|
34
|
+
pull_strength = @grip * @weight
|
|
35
|
+
adjusted = new_value + ((reference_value - new_value) * pull_strength)
|
|
36
|
+
adjusted.clamp(0.0, 1.0).round(10)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def ironclad?
|
|
40
|
+
@grip >= 0.8
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def drifting?
|
|
44
|
+
@grip < 0.2
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def heavy?
|
|
48
|
+
@weight >= 0.7
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def light?
|
|
52
|
+
@weight < 0.3
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def grip_label
|
|
56
|
+
Constants.label_for(Constants::GRIP_LABELS, @grip)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_h
|
|
60
|
+
{
|
|
61
|
+
id: @id,
|
|
62
|
+
anchor_type: @anchor_type,
|
|
63
|
+
domain: @domain,
|
|
64
|
+
content: @content,
|
|
65
|
+
reference_value: @reference_value,
|
|
66
|
+
grip: @grip,
|
|
67
|
+
weight: @weight,
|
|
68
|
+
grip_label: grip_label,
|
|
69
|
+
ironclad: ironclad?,
|
|
70
|
+
drifting: drifting?,
|
|
71
|
+
created_at: @created_at
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def validate_anchor_type!(val)
|
|
78
|
+
return if Constants::ANCHOR_TYPES.include?(val.to_sym)
|
|
79
|
+
|
|
80
|
+
raise ArgumentError,
|
|
81
|
+
"unknown anchor type: #{val.inspect}; " \
|
|
82
|
+
"must be one of #{Constants::ANCHOR_TYPES.inspect}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveAnchor
|
|
6
|
+
module Helpers
|
|
7
|
+
class AnchorEngine
|
|
8
|
+
def initialize
|
|
9
|
+
@anchors = {}
|
|
10
|
+
@chains = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create_anchor(anchor_type:, domain:, content:,
|
|
14
|
+
reference_value: nil, grip: nil, weight: nil)
|
|
15
|
+
raise ArgumentError, 'anchor limit reached' if @anchors.size >= Constants::MAX_ANCHORS
|
|
16
|
+
|
|
17
|
+
a = Anchor.new(anchor_type: anchor_type, domain: domain, content: content,
|
|
18
|
+
reference_value: reference_value, grip: grip, weight: weight)
|
|
19
|
+
@anchors[a.id] = a
|
|
20
|
+
a
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def create_chain(anchor_id:, material: :steel, length: nil, flexibility: nil)
|
|
24
|
+
raise ArgumentError, 'chain limit reached' if @chains.size >= Constants::MAX_CHAINS
|
|
25
|
+
|
|
26
|
+
fetch_anchor(anchor_id)
|
|
27
|
+
c = Chain.new(anchor_id: anchor_id, material: material,
|
|
28
|
+
length: length, flexibility: flexibility)
|
|
29
|
+
@chains[c.id] = c
|
|
30
|
+
c
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def apply_bias(anchor_id:, new_value:)
|
|
34
|
+
anchor = fetch_anchor(anchor_id)
|
|
35
|
+
pulled = anchor.bias_pull(new_value.to_f)
|
|
36
|
+
{ anchor: anchor, original: new_value.to_f, biased: pulled,
|
|
37
|
+
shift: (pulled - new_value.to_f).abs.round(10) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def drag_anchor(anchor_id:, rate: Constants::DRAG_RATE)
|
|
41
|
+
anchor = fetch_anchor(anchor_id)
|
|
42
|
+
anchor.drag!(rate: rate)
|
|
43
|
+
anchor
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def drift_all!
|
|
47
|
+
@anchors.each_value(&:drift!)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def wear_all_chains!
|
|
51
|
+
@chains.each_value(&:wear!)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def broken_chains
|
|
55
|
+
@chains.values.select(&:broken?)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def anchors_by_type
|
|
59
|
+
counts = Constants::ANCHOR_TYPES.to_h { |t| [t, 0] }
|
|
60
|
+
@anchors.each_value { |a| counts[a.anchor_type] += 1 }
|
|
61
|
+
counts
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def strongest_anchors(limit: 5)
|
|
65
|
+
@anchors.values.sort_by { |a| -a.grip }.first(limit)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def weakest_anchors(limit: 5)
|
|
69
|
+
@anchors.values.sort_by(&:grip).first(limit)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def ironclad_anchors
|
|
73
|
+
@anchors.values.select(&:ironclad?)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def drifting_anchors
|
|
77
|
+
@anchors.values.select(&:drifting?)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def chains_for(anchor_id)
|
|
81
|
+
@chains.values.select { |c| c.anchor_id == anchor_id }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def avg_grip
|
|
85
|
+
return 0.0 if @anchors.empty?
|
|
86
|
+
|
|
87
|
+
(@anchors.values.sum(&:grip) / @anchors.size).round(10)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def anchor_report
|
|
91
|
+
{
|
|
92
|
+
total_anchors: @anchors.size,
|
|
93
|
+
total_chains: @chains.size,
|
|
94
|
+
by_type: anchors_by_type,
|
|
95
|
+
ironclad_count: ironclad_anchors.size,
|
|
96
|
+
drifting_count: drifting_anchors.size,
|
|
97
|
+
broken_chains: broken_chains.size,
|
|
98
|
+
avg_grip: avg_grip
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def all_anchors
|
|
103
|
+
@anchors.values
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def all_chains
|
|
107
|
+
@chains.values
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def fetch_anchor(id)
|
|
113
|
+
@anchors.fetch(id) { raise ArgumentError, "anchor not found: #{id}" }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveAnchor
|
|
6
|
+
module Helpers
|
|
7
|
+
class Chain
|
|
8
|
+
attr_reader :id, :anchor_id, :material, :created_at
|
|
9
|
+
attr_accessor :length, :flexibility
|
|
10
|
+
|
|
11
|
+
def initialize(anchor_id:, material: :steel, length: nil, flexibility: nil)
|
|
12
|
+
validate_material!(material)
|
|
13
|
+
@id = SecureRandom.uuid
|
|
14
|
+
@anchor_id = anchor_id
|
|
15
|
+
@material = material.to_sym
|
|
16
|
+
@length = (length || 0.5).to_f.clamp(0.0, 1.0).round(10)
|
|
17
|
+
@flexibility = (flexibility || material_flexibility).to_f.clamp(0.0, 1.0).round(10)
|
|
18
|
+
@created_at = Time.now.utc
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def extend!(amount: 0.1)
|
|
22
|
+
@length = (@length + amount.abs).clamp(0.0, 1.0).round(10)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def shorten!(amount: 0.1)
|
|
26
|
+
@length = (@length - amount.abs).clamp(0.0, 1.0).round(10)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def wear!(rate: 0.05)
|
|
30
|
+
@flexibility = (@flexibility - rate.abs).clamp(0.0, 1.0).round(10)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def broken?
|
|
34
|
+
@flexibility < Constants::BREAK_THRESHOLD
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def elastic?
|
|
38
|
+
@flexibility >= 0.8
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def rigid?
|
|
42
|
+
@flexibility < 0.2
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def short?
|
|
46
|
+
@length < 0.3
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def long?
|
|
50
|
+
@length >= 0.7
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def flexibility_label
|
|
54
|
+
Constants.label_for(Constants::FLEXIBILITY_LABELS, @flexibility)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_h
|
|
58
|
+
{
|
|
59
|
+
id: @id,
|
|
60
|
+
anchor_id: @anchor_id,
|
|
61
|
+
material: @material,
|
|
62
|
+
length: @length,
|
|
63
|
+
flexibility: @flexibility,
|
|
64
|
+
flexibility_label: flexibility_label,
|
|
65
|
+
broken: broken?,
|
|
66
|
+
elastic: elastic?,
|
|
67
|
+
created_at: @created_at
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def validate_material!(val)
|
|
74
|
+
return if Constants::CHAIN_MATERIALS.include?(val.to_sym)
|
|
75
|
+
|
|
76
|
+
raise ArgumentError,
|
|
77
|
+
"unknown material: #{val.inspect}; " \
|
|
78
|
+
"must be one of #{Constants::CHAIN_MATERIALS.inspect}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def material_flexibility
|
|
82
|
+
{ steel: 0.3, rope: 0.6, wire: 0.4, thread: 0.8, cobweb: 0.9 }
|
|
83
|
+
.fetch(@material, 0.5)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveAnchor
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
ANCHOR_TYPES = %i[belief assumption experience authority number].freeze
|
|
9
|
+
|
|
10
|
+
CHAIN_MATERIALS = %i[steel rope wire thread cobweb].freeze
|
|
11
|
+
|
|
12
|
+
MAX_ANCHORS = 200
|
|
13
|
+
MAX_CHAINS = 100
|
|
14
|
+
DRAG_RATE = 0.06
|
|
15
|
+
DRIFT_RATE = 0.03
|
|
16
|
+
BREAK_THRESHOLD = 0.1
|
|
17
|
+
|
|
18
|
+
GRIP_LABELS = [
|
|
19
|
+
[(0.8..), :ironclad],
|
|
20
|
+
[(0.6...0.8), :firm],
|
|
21
|
+
[(0.4...0.6), :moderate],
|
|
22
|
+
[(0.2...0.4), :loose],
|
|
23
|
+
[(..0.2), :drifting]
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
FLEXIBILITY_LABELS = [
|
|
27
|
+
[(0.8..), :elastic],
|
|
28
|
+
[(0.6...0.8), :flexible],
|
|
29
|
+
[(0.4...0.6), :moderate],
|
|
30
|
+
[(0.2...0.4), :rigid],
|
|
31
|
+
[(..0.2), :brittle]
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
def self.label_for(table, value)
|
|
35
|
+
table.each { |range, label| return label if range.cover?(value) }
|
|
36
|
+
table.last.last
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveAnchor
|
|
6
|
+
module Runners
|
|
7
|
+
module CognitiveAnchor
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
def create_anchor(anchor_type:, domain:, content:,
|
|
11
|
+
reference_value: nil, grip: nil, weight: nil, engine: nil, **)
|
|
12
|
+
eng = resolve_engine(engine)
|
|
13
|
+
a = eng.create_anchor(anchor_type: anchor_type, domain: domain, content: content,
|
|
14
|
+
reference_value: reference_value, grip: grip, weight: weight)
|
|
15
|
+
{ success: true, anchor: a.to_h }
|
|
16
|
+
rescue ArgumentError => e
|
|
17
|
+
{ success: false, error: e.message }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create_chain(anchor_id:, material: :steel, length: nil,
|
|
21
|
+
flexibility: nil, engine: nil, **)
|
|
22
|
+
eng = resolve_engine(engine)
|
|
23
|
+
c = eng.create_chain(anchor_id: anchor_id, material: material,
|
|
24
|
+
length: length, flexibility: flexibility)
|
|
25
|
+
{ success: true, chain: c.to_h }
|
|
26
|
+
rescue ArgumentError => e
|
|
27
|
+
{ success: false, error: e.message }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def apply_bias(anchor_id:, new_value:, engine: nil, **)
|
|
31
|
+
eng = resolve_engine(engine)
|
|
32
|
+
result = eng.apply_bias(anchor_id: anchor_id, new_value: new_value)
|
|
33
|
+
{ success: true, anchor: result[:anchor].to_h,
|
|
34
|
+
original: result[:original], biased: result[:biased], shift: result[:shift] }
|
|
35
|
+
rescue ArgumentError => e
|
|
36
|
+
{ success: false, error: e.message }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def list_anchors(engine: nil, anchor_type: nil, **)
|
|
40
|
+
eng = resolve_engine(engine)
|
|
41
|
+
results = eng.all_anchors
|
|
42
|
+
results = results.select { |a| a.anchor_type == anchor_type.to_sym } if anchor_type
|
|
43
|
+
{ success: true, anchors: results.map(&:to_h), count: results.size }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def anchor_status(engine: nil, **)
|
|
47
|
+
eng = resolve_engine(engine)
|
|
48
|
+
{ success: true, report: eng.anchor_report }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def resolve_engine(engine)
|
|
56
|
+
engine || default_engine
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def default_engine
|
|
60
|
+
@default_engine ||= Helpers::AnchorEngine.new
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
require_relative 'cognitive_anchor/version'
|
|
6
|
+
require_relative 'cognitive_anchor/helpers/constants'
|
|
7
|
+
require_relative 'cognitive_anchor/helpers/anchor'
|
|
8
|
+
require_relative 'cognitive_anchor/helpers/chain'
|
|
9
|
+
require_relative 'cognitive_anchor/helpers/anchor_engine'
|
|
10
|
+
require_relative 'cognitive_anchor/runners/cognitive_anchor'
|
|
11
|
+
require_relative 'cognitive_anchor/client'
|
|
12
|
+
|
|
13
|
+
module Legion
|
|
14
|
+
module Extensions
|
|
15
|
+
module CognitiveAnchor
|
|
16
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-cognitive-anchor
|
|
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 anchoring biases where fixed reference points pull new information
|
|
27
|
+
toward them with configurable grip, weight, and chain flexibility
|
|
28
|
+
email:
|
|
29
|
+
- matthewdiverson@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- ".rspec"
|
|
35
|
+
- CLAUDE.md
|
|
36
|
+
- README.md
|
|
37
|
+
- lex-cognitive-anchor.gemspec
|
|
38
|
+
- lib/legion/extensions/cognitive_anchor.rb
|
|
39
|
+
- lib/legion/extensions/cognitive_anchor/client.rb
|
|
40
|
+
- lib/legion/extensions/cognitive_anchor/helpers/anchor.rb
|
|
41
|
+
- lib/legion/extensions/cognitive_anchor/helpers/anchor_engine.rb
|
|
42
|
+
- lib/legion/extensions/cognitive_anchor/helpers/chain.rb
|
|
43
|
+
- lib/legion/extensions/cognitive_anchor/helpers/constants.rb
|
|
44
|
+
- lib/legion/extensions/cognitive_anchor/runners/cognitive_anchor.rb
|
|
45
|
+
- lib/legion/extensions/cognitive_anchor/version.rb
|
|
46
|
+
homepage: https://github.com/LegionIO/lex-cognitive-anchor
|
|
47
|
+
licenses:
|
|
48
|
+
- MIT
|
|
49
|
+
metadata:
|
|
50
|
+
homepage_uri: https://github.com/LegionIO/lex-cognitive-anchor
|
|
51
|
+
source_code_uri: https://github.com/LegionIO/lex-cognitive-anchor
|
|
52
|
+
documentation_uri: https://github.com/LegionIO/lex-cognitive-anchor/blob/master/README.md
|
|
53
|
+
changelog_uri: https://github.com/LegionIO/lex-cognitive-anchor/blob/master/CHANGELOG.md
|
|
54
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-anchor/issues
|
|
55
|
+
rubygems_mfa_required: 'true'
|
|
56
|
+
rdoc_options: []
|
|
57
|
+
require_paths:
|
|
58
|
+
- lib
|
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '3.4'
|
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
requirements: []
|
|
70
|
+
rubygems_version: 3.6.9
|
|
71
|
+
specification_version: 4
|
|
72
|
+
summary: Cognitive anchoring bias dynamics for LegionIO agentic architecture
|
|
73
|
+
test_files: []
|