holographic_memory/core/agency/
goals.rs1use fxhash::FxHashMap;
12use parking_lot::RwLock;
13
14use crate::core::entangled::EntangledHVec;
15
16#[derive(Clone, Debug)]
18pub struct Goal {
19 pub name: String,
20 pub description: String,
21 pub vector: EntangledHVec,
22 pub relevance: f64,
23 pub urgency: f64,
24 pub cost: f64,
25 pub active: bool,
26}
27
28impl Goal {
29 pub fn utility(&self) -> f64 {
30 self.relevance * self.urgency * (1.0 - self.cost.clamp(0.0, 0.99))
31 }
32}
33
34struct GoalStoreInner {
35 goals: Vec<Goal>,
36 by_name: FxHashMap<String, usize>,
37}
38
39pub struct GoalStore {
41 inner: RwLock<GoalStoreInner>,
42}
43
44impl Default for GoalStore {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50impl GoalStore {
51 pub fn new() -> Self {
52 Self {
53 inner: RwLock::new(GoalStoreInner {
54 goals: Vec::new(),
55 by_name: FxHashMap::default(),
56 }),
57 }
58 }
59
60 pub fn add(&self, goal: Goal) -> usize {
61 let mut inner = self.inner.write();
62 let idx = inner.goals.len();
63 inner.by_name.insert(goal.name.clone(), idx);
64 inner.goals.push(goal);
65 idx
66 }
67
68 pub fn get(&self, name: &str) -> Option<Goal> {
69 let inner = self.inner.read();
70 let idx = *inner.by_name.get(name)?;
71 inner.goals.get(idx).cloned()
72 }
73
74 pub fn deactivate(&self, name: &str) -> bool {
75 let mut inner = self.inner.write();
76 if let Some(&idx) = inner.by_name.get(name) {
77 inner.goals[idx].active = false;
78 true
79 } else {
80 false
81 }
82 }
83
84 pub fn active_goals(&self) -> Vec<Goal> {
85 self.inner
86 .read()
87 .goals
88 .iter()
89 .filter(|g| g.active)
90 .cloned()
91 .collect()
92 }
93
94 pub fn prioritized(&self) -> Vec<Goal> {
96 let mut active = self.active_goals();
97 active.sort_by(|a, b| {
98 b.utility()
99 .partial_cmp(&a.utility())
100 .unwrap_or(std::cmp::Ordering::Equal)
101 });
102 active
103 }
104
105 pub fn goal_relevance(&self, vec: &EntangledHVec) -> f64 {
108 let goals = self.prioritized();
109 if goals.is_empty() {
110 return 0.0;
111 }
112 goals[0].vector.similarity(vec)
113 }
114
115 pub fn count(&self) -> usize {
116 self.inner.read().goals.len()
117 }
118
119 pub fn active_count(&self) -> usize {
120 self.inner.read().goals.iter().filter(|g| g.active).count()
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 fn make_goal(name: &str, seed: u64, relevance: f64, urgency: f64, cost: f64) -> Goal {
129 Goal {
130 name: name.to_string(),
131 description: format!("Goal: {}", name),
132 vector: EntangledHVec::new_deterministic(16384, seed),
133 relevance,
134 urgency,
135 cost,
136 active: true,
137 }
138 }
139
140 #[test]
141 fn test_utility_scoring() {
142 let g = make_goal("test", 1, 0.8, 0.9, 0.2);
143 let u = g.utility();
144 let expected = 0.8 * 0.9 * (1.0 - 0.2);
145 assert!((u - expected).abs() < 0.001);
146 }
147
148 #[test]
149 fn test_utility_clamps_cost() {
150 let g = make_goal("test", 1, 1.0, 1.0, 1.5);
151 assert!(g.utility() > 0.0, "cost should be clamped to 0.99");
152 }
153
154 #[test]
155 fn test_add_get() {
156 let store = GoalStore::new();
157 store.add(make_goal("learn_rust", 1, 0.9, 0.8, 0.3));
158 let g = store.get("learn_rust").unwrap();
159 assert_eq!(g.name, "learn_rust");
160 assert!(g.active);
161 }
162
163 #[test]
164 fn test_deactivate() {
165 let store = GoalStore::new();
166 store.add(make_goal("g1", 1, 1.0, 1.0, 0.0));
167 assert_eq!(store.active_count(), 1);
168 store.deactivate("g1");
169 assert_eq!(store.active_count(), 0);
170 }
171
172 #[test]
173 fn test_prioritized_ordering() {
174 let store = GoalStore::new();
175 store.add(make_goal("low", 1, 0.1, 0.1, 0.0));
176 store.add(make_goal("high", 2, 1.0, 1.0, 0.0));
177 store.add(make_goal("mid", 3, 0.5, 0.5, 0.0));
178 let pri = store.prioritized();
179 assert_eq!(pri[0].name, "high");
180 assert_eq!(pri[1].name, "mid");
181 assert_eq!(pri[2].name, "low");
182 }
183
184 #[test]
185 fn test_goal_relevance() {
186 let store = GoalStore::new();
187 let g = make_goal("g1", 42, 1.0, 1.0, 0.0);
188 let vec = g.vector.clone();
189 store.add(g);
190 let rel = store.goal_relevance(&vec);
191 assert!(
192 (rel - 1.0).abs() < 0.001,
193 "Same vector should have relevance ~1.0"
194 );
195 }
196
197 #[test]
198 fn test_empty_store() {
199 let store = GoalStore::new();
200 assert_eq!(store.count(), 0);
201 assert_eq!(store.active_count(), 0);
202 assert!(store.get("x").is_none());
203 let v = EntangledHVec::new_deterministic(16384, 1);
204 assert_eq!(store.goal_relevance(&v), 0.0);
205 }
206}