chip-brain 1.0.0__py3-none-any.whl

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.
Files changed (87) hide show
  1. amygdala/__init__.py +7 -0
  2. amygdala/affective_forecast.py +154 -0
  3. amygdala/arousal_modulator.py +92 -0
  4. amygdala/emotional_core.py +199 -0
  5. amygdala/emotional_memory.py +147 -0
  6. amygdala/fear_assessment.py +123 -0
  7. amygdala/habituation.py +152 -0
  8. brain.py +983 -0
  9. brainstem/__init__.py +8 -0
  10. brainstem/circadian.py +168 -0
  11. brainstem/cryostasis.py +268 -0
  12. brainstem/device.py +112 -0
  13. brainstem/forgetting_prevention.py +185 -0
  14. brainstem/gradient_clipper.py +102 -0
  15. brainstem/health_monitor.py +156 -0
  16. brainstem/online_trainer.py +358 -0
  17. brainstem/running_stats.py +78 -0
  18. brainstem/scheduler.py +140 -0
  19. cerebellum/__init__.py +7 -0
  20. cerebellum/action_smoother.py +111 -0
  21. cerebellum/emotional_contagion.py +235 -0
  22. cerebellum/skill_library.py +147 -0
  23. cerebellum/swarm_coordinator.py +150 -0
  24. cerebrum/__init__.py +7 -0
  25. cerebrum/attention_query.py +145 -0
  26. cerebrum/causal_engine.py +445 -0
  27. cerebrum/chip_policy.py +240 -0
  28. cerebrum/concept_grounding.py +138 -0
  29. cerebrum/goal_generator.py +153 -0
  30. cerebrum/goal_stack.py +292 -0
  31. cerebrum/inner_speech.py +326 -0
  32. cerebrum/meta_cognition.py +262 -0
  33. cerebrum/narrative_self.py +314 -0
  34. cerebrum/personality.py +130 -0
  35. cerebrum/planner.py +125 -0
  36. cerebrum/reasoning.py +193 -0
  37. cerebrum/self_consistency.py +268 -0
  38. cerebrum/theory_of_mind.py +195 -0
  39. cerebrum/working_memory.py +164 -0
  40. cerebrum/world_model.py +172 -0
  41. chip_brain-1.0.0.dist-info/METADATA +248 -0
  42. chip_brain-1.0.0.dist-info/RECORD +87 -0
  43. chip_brain-1.0.0.dist-info/WHEEL +5 -0
  44. chip_brain-1.0.0.dist-info/entry_points.txt +2 -0
  45. chip_brain-1.0.0.dist-info/licenses/LICENSE +21 -0
  46. chip_brain-1.0.0.dist-info/top_level.txt +13 -0
  47. core/chip_policy.py +15 -0
  48. core/device.py +7 -0
  49. core/emotional_core.py +7 -0
  50. core/episodic_memory.py +7 -0
  51. core/interfaces.py +21 -0
  52. core/latent_alignment.py +7 -0
  53. core/merge_strategies.py +7 -0
  54. core/online_trainer.py +7 -0
  55. core/running_stats.py +7 -0
  56. core/transformer_architecture.py +7 -0
  57. hippocampus/__init__.py +7 -0
  58. hippocampus/active_dreaming.py +294 -0
  59. hippocampus/boundary_detector.py +177 -0
  60. hippocampus/dream_cycle.py +123 -0
  61. hippocampus/episodic_memory.py +228 -0
  62. hippocampus/episodic_recall.py +178 -0
  63. hippocampus/memory_consolidation.py +96 -0
  64. hippocampus/spatial_map.py +167 -0
  65. hippocampus/temporal_abstraction.py +115 -0
  66. hypothalamus/__init__.py +7 -0
  67. hypothalamus/curiosity_drive.py +114 -0
  68. hypothalamus/drive_arbitrator.py +106 -0
  69. hypothalamus/energy_manager.py +131 -0
  70. hypothalamus/entropy_temperature.py +136 -0
  71. hypothalamus/homeostasis.py +93 -0
  72. interfaces/__init__.py +28 -0
  73. interfaces/base.py +231 -0
  74. interfaces/plugins.py +183 -0
  75. interfaces/signals.py +235 -0
  76. locomotion/ModelMovementAndLocomotion.py +532 -0
  77. locomotion/__init__.py +1 -0
  78. parasite/ModelWeightParasiticExtraction.py +257 -0
  79. parasite/__init__.py +1 -0
  80. run.py +133 -0
  81. thalamus/__init__.py +13 -0
  82. thalamus/attention_bottleneck.py +152 -0
  83. thalamus/granite_embedder.py +372 -0
  84. thalamus/latent_alignment.py +153 -0
  85. thalamus/merge_strategies.py +117 -0
  86. thalamus/sensory_encoder.py +177 -0
  87. thalamus/transformer_backbone.py +206 -0
amygdala/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """
2
+ amygdala/ — Emotion processing.
3
+
4
+ Valence network, mood tracking, fear/risk assessment,
5
+ emotional memory tagging, and arousal-based attention modulation.
6
+ Analogous to the amygdala: fear, emotional memories, threat detection.
7
+ """
@@ -0,0 +1,154 @@
1
+ """
2
+ amygdala/affective_forecast.py — Predict future valence of a trajectory.
3
+
4
+ The world model predicts future *states*. This module predicts future
5
+ *feelings* — how a trajectory will make the agent feel over time. This
6
+ lets the cerebrum pick actions not just by predicted reward, but by
7
+ anticipated emotional outcome.
8
+
9
+ predicted_valence_trajectory = forecast(z_trajectory)
10
+
11
+ Use cases:
12
+ - Avoid actions that lead to sustained negative valence (learned anxiety)
13
+ - Prefer actions that lead to engagement (learned motivation)
14
+ - Detect "emotional dead ends" (trajectories that flatten to neutral)
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import Optional, Tuple
20
+
21
+ import torch
22
+ import torch.nn as nn
23
+
24
+
25
+ class AffectiveForecaster(nn.Module):
26
+ """
27
+ Predicts the valence trajectory for a sequence of latent states.
28
+
29
+ Architecture: small GRU over the trajectory, outputs per-step valence
30
+ predictions in [-1, 1].
31
+
32
+ Args:
33
+ latent_dim: Dimensionality of latent states.
34
+ hidden_dim: GRU hidden size.
35
+ """
36
+
37
+ def __init__(self, latent_dim: int = 512, hidden_dim: int = 128) -> None:
38
+ super().__init__()
39
+ self.gru = nn.GRU(latent_dim, hidden_dim, batch_first=True)
40
+ self.head = nn.Sequential(
41
+ nn.Linear(hidden_dim, 64),
42
+ nn.GELU(),
43
+ nn.Linear(64, 1),
44
+ nn.Tanh(),
45
+ )
46
+
47
+ def forward(self, trajectory: torch.Tensor) -> torch.Tensor:
48
+ """
49
+ Predict valence at each step of a trajectory.
50
+
51
+ Args:
52
+ trajectory: (B, T, D) latent state sequence.
53
+
54
+ Returns:
55
+ (B, T, 1) predicted valence at each timestep.
56
+ """
57
+ h, _ = self.gru(trajectory) # (B, T, hidden)
58
+ return self.head(h) # (B, T, 1)
59
+
60
+ def forecast_mean(self, trajectory: torch.Tensor) -> torch.Tensor:
61
+ """
62
+ Predict the average valence over a trajectory.
63
+
64
+ Returns:
65
+ (B, 1) mean predicted valence.
66
+ """
67
+ per_step = self.forward(trajectory)
68
+ return per_step.mean(dim=1)
69
+
70
+ def forecast_final(self, trajectory: torch.Tensor) -> torch.Tensor:
71
+ """
72
+ Predict the valence at the END of the trajectory.
73
+
74
+ Returns:
75
+ (B, 1) final-step valence prediction.
76
+ """
77
+ per_step = self.forward(trajectory)
78
+ return per_step[:, -1, :]
79
+
80
+ def compare_trajectories(
81
+ self,
82
+ traj_a: torch.Tensor,
83
+ traj_b: torch.Tensor,
84
+ ) -> Tuple[float, float]:
85
+ """
86
+ Compare two trajectories by predicted emotional outcome.
87
+
88
+ Returns:
89
+ (mean_valence_a, mean_valence_b) — higher is emotionally preferred.
90
+ """
91
+ va = float(self.forecast_mean(traj_a).mean().item())
92
+ vb = float(self.forecast_mean(traj_b).mean().item())
93
+ return va, vb
94
+
95
+ def emotional_dead_end(
96
+ self,
97
+ trajectory: torch.Tensor,
98
+ threshold: float = 0.05,
99
+ ) -> bool:
100
+ """
101
+ Detect if a trajectory leads to emotional flatness (stagnation).
102
+
103
+ A dead end is where predicted valence variance across time is
104
+ near zero AND mean valence is near zero. The agent isn't feeling
105
+ anything — neither good nor bad. Often worse than negative valence
106
+ (which at least indicates engagement).
107
+
108
+ Returns:
109
+ True if the trajectory is an emotional dead end.
110
+ """
111
+ per_step = self.forward(trajectory) # (B, T, 1)
112
+ var = per_step.var(dim=1).mean().item()
113
+ mean = per_step.mean().abs().item()
114
+ return var < threshold and mean < threshold
115
+
116
+
117
+ class AffectiveForecasterTrainer:
118
+ """
119
+ Trains the forecaster on observed (trajectory, actual_valence) pairs.
120
+
121
+ Call `update()` with real episodes and their actual valences from the
122
+ emotional core. The forecaster learns to predict future affect.
123
+ """
124
+
125
+ def __init__(self, forecaster: AffectiveForecaster, lr: float = 1e-4) -> None:
126
+ self.forecaster = forecaster
127
+ self.optimizer = torch.optim.Adam(forecaster.parameters(), lr=lr)
128
+ self._step = 0
129
+
130
+ def update(
131
+ self,
132
+ trajectory: torch.Tensor,
133
+ actual_valences: torch.Tensor,
134
+ ) -> float:
135
+ """
136
+ One training step.
137
+
138
+ Args:
139
+ trajectory: (B, T, D) latent state sequences.
140
+ actual_valences: (B, T, 1) actual valences observed.
141
+
142
+ Returns:
143
+ Scalar MSE loss.
144
+ """
145
+ self.optimizer.zero_grad()
146
+ predicted = self.forecaster(trajectory)
147
+ loss = nn.functional.mse_loss(predicted, actual_valences.detach())
148
+ loss.backward()
149
+ self.optimizer.step()
150
+ self._step += 1
151
+ return loss.item()
152
+
153
+
154
+ __all__ = ["AffectiveForecaster", "AffectiveForecasterTrainer"]
@@ -0,0 +1,92 @@
1
+ """
2
+ amygdala/arousal_modulator.py — Attention gain control.
3
+
4
+ High arousal (fear, excitement) narrows attention to the most salient
5
+ features. Low arousal (calm, fatigue) broadens attention. The amygdala
6
+ modulates this gain signal and sends it to the thalamus to adjust
7
+ attention sensitivity in the transformer backbone.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import torch
13
+ import torch.nn as nn
14
+
15
+
16
+ class ArousalModulator(nn.Module):
17
+ """
18
+ Computes an attention gain signal from the current arousal level.
19
+
20
+ The gain is applied as a multiplicative bias to the transformer's
21
+ attention scores in the thalamus:
22
+ scores ← scores * gain_scale
23
+
24
+ High arousal → gain > 1.0 (sharper, more focused attention)
25
+ Low arousal → gain < 1.0 (softer, more diffuse attention)
26
+
27
+ Args:
28
+ d_model: Latent dimensionality.
29
+ arousal_dim: Dimensionality of the arousal input vector.
30
+ Defaults to 1 (scalar arousal from homeostasis).
31
+ """
32
+
33
+ def __init__(self, d_model: int, arousal_dim: int = 1) -> None:
34
+ super().__init__()
35
+ self.d_model = d_model
36
+
37
+ # Maps arousal level → per-head gain scale
38
+ self.gain_net = nn.Sequential(
39
+ nn.Linear(arousal_dim, 32),
40
+ nn.GELU(),
41
+ nn.Linear(32, 1),
42
+ )
43
+ # Initialise to output 0 so gain starts at sigmoid(0) = 0.5 → scale = 1.0
44
+ nn.init.zeros_(self.gain_net[-1].weight)
45
+ nn.init.zeros_(self.gain_net[-1].bias)
46
+
47
+ def forward(self, arousal: torch.Tensor) -> torch.Tensor:
48
+ """
49
+ Compute attention gain scale from arousal level.
50
+
51
+ Args:
52
+ arousal: (B, 1) or scalar arousal value in [0, 1].
53
+
54
+ Returns:
55
+ (B, 1) gain scale. Values > 1.0 sharpen attention,
56
+ values < 1.0 soften it.
57
+ """
58
+ if arousal.dim() == 0:
59
+ arousal = arousal.unsqueeze(0).unsqueeze(0)
60
+ elif arousal.dim() == 1:
61
+ arousal = arousal.unsqueeze(-1)
62
+
63
+ # Map [0,1] arousal to gain: 0.5 arousal → gain 1.0 (neutral)
64
+ raw = self.gain_net(arousal)
65
+ # Sigmoid centred at 0 → range (0, 1), then scale to (0.5, 2.0)
66
+ gain = 0.5 + 1.5 * torch.sigmoid(raw)
67
+ return gain
68
+
69
+ def get_valence_bias(
70
+ self,
71
+ arousal: torch.Tensor,
72
+ valence: torch.Tensor,
73
+ ) -> torch.Tensor:
74
+ """
75
+ Compute the combined arousal+valence attention bias for the thalamus.
76
+
77
+ This is the signal sent via "arousal_gain" on the SignalBus.
78
+
79
+ Args:
80
+ arousal: (B, 1) arousal level.
81
+ valence: (B, 1) emotional valence.
82
+
83
+ Returns:
84
+ (B, 1) combined bias for transformer attention scores.
85
+ """
86
+ gain = self.forward(arousal)
87
+ # Valence shifts the bias direction: positive valence → attend to
88
+ # rewarding features; negative valence → attend to threat features
89
+ return gain * valence
90
+
91
+
92
+ __all__ = ["ArousalModulator"]
@@ -0,0 +1,199 @@
1
+ """
2
+ amygdala/emotional_core.py — Emotion processing centre.
3
+
4
+ The amygdala processes emotions, especially fear and threat responses.
5
+ It tags memories with emotional significance and modulates attention
6
+ based on arousal state. This module is the computational equivalent:
7
+ mood tracking, homeostatic drives, and the valence network that maps
8
+ latent states to emotional tone.
9
+
10
+ Moved from: core/emotional_core.py
11
+ """
12
+
13
+ from __future__ import annotations
14
+ from typing import Tuple
15
+ import torch
16
+ import torch.nn as nn
17
+ from thalamus.latent_alignment import LatentAligner
18
+
19
+
20
+ class MoodState:
21
+ """
22
+ Tracks a single named emotion and the reason for the most recent change.
23
+ Performs no tensor operations — pure bookkeeping.
24
+ """
25
+
26
+ def __init__(self, initial_mood: str, valid_vocab: dict) -> None:
27
+ if initial_mood not in valid_vocab:
28
+ raise ValueError(
29
+ f"Initial mood '{initial_mood}' not in vocabulary: "
30
+ f"{list(valid_vocab.keys())}"
31
+ )
32
+ self._mood: str = initial_mood
33
+ self._reason: str = "Initialization"
34
+ self._vocab: dict = valid_vocab
35
+
36
+ @property
37
+ def name(self) -> str:
38
+ return self._mood
39
+
40
+ @property
41
+ def reason(self) -> str:
42
+ return self._reason
43
+
44
+ def transition(self, new_mood: str, reason: str) -> None:
45
+ if new_mood not in self._vocab:
46
+ print(f"[MoodState] Unknown mood '{new_mood}'; transition ignored.")
47
+ return
48
+ self._mood = new_mood
49
+ self._reason = reason
50
+
51
+
52
+ class HomeostasisState:
53
+ """
54
+ 4-D homeostatic drive vector: [arousal, energy, safety, engagement].
55
+ Clamped to [0,1]. strain() = L2 distance from target setpoint.
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ initial: Tuple[float, float, float, float] = (0.5, 0.8, 1.0, 0.5),
61
+ target: Tuple[float, float, float, float] = (0.8, 1.0, 0.7, 0.6),
62
+ ) -> None:
63
+ self._state = nn.Parameter(torch.tensor(list(initial)), requires_grad=False)
64
+ self._target = torch.tensor(list(target))
65
+
66
+ @property
67
+ def vector(self) -> torch.Tensor:
68
+ return self._state
69
+
70
+ def update(
71
+ self,
72
+ action_impact: float,
73
+ environment_surprise: float,
74
+ policy_violation: float = 0.0,
75
+ tool_failure_rate: float = 0.0,
76
+ task_success: float = 0.0,
77
+ ) -> None:
78
+ self._state[0] += 0.05 * environment_surprise
79
+ self._state[1] -= 0.02 * action_impact
80
+ self._state[2] -= 0.10 * float(policy_violation)
81
+ self._state[3] -= 0.05 * float(tool_failure_rate)
82
+ self._state[3] += 0.03 * float(task_success)
83
+ self._state.clamp_(0, 1)
84
+
85
+ def strain(self) -> torch.Tensor:
86
+ target = self._target.to(self._state.device)
87
+ return torch.norm(self._state - target)
88
+
89
+
90
+ class EmotionalCore(nn.Module):
91
+ """
92
+ Amygdala: orchestrates mood + homeostasis + valence network.
93
+
94
+ Responsibilities:
95
+ 1. Discrete mood bookkeeping (MoodState).
96
+ 2. Homeostatic drive updates (HomeostasisState).
97
+ 3. Learned valence network: (latent_state ‖ internal_state) → scalar ∈ (−1, +1).
98
+
99
+ Publishes to SignalBus:
100
+ "valence_update" → cerebrum (current emotional valence)
101
+ "arousal_gain" → thalamus (attention sensitivity scale)
102
+ "homeostasis_update" → hypothalamus (internal state vector)
103
+ """
104
+
105
+ def __init__(
106
+ self,
107
+ latent_aligner: LatentAligner,
108
+ initial_mood: str = "Calm",
109
+ hidden_dim: int = 512,
110
+ ) -> None:
111
+ super().__init__()
112
+ self._aligner = latent_aligner
113
+ self._mood = MoodState(initial_mood, latent_aligner.emotion_vocab)
114
+ self._homeostasis = HomeostasisState()
115
+ self.internal_state = self._homeostasis._state
116
+ self.valence_network = nn.Sequential(
117
+ nn.Linear(hidden_dim + 4, 128),
118
+ nn.ReLU(),
119
+ nn.Linear(128, 1),
120
+ nn.Tanh(),
121
+ )
122
+
123
+ def current_mood(self) -> Tuple[str, torch.Tensor]:
124
+ mood_id = self._aligner.emotion_vocab[self._mood.name]
125
+ idx = torch.tensor(
126
+ [mood_id], dtype=torch.long,
127
+ device=self._aligner.emotion_embeddings.weight.device,
128
+ )
129
+ vector = self._aligner.emotion_embeddings(idx).squeeze(0).detach()
130
+ return self._mood.name, vector
131
+
132
+ def mood_swing(self, new_mood: str, reason: str) -> None:
133
+ self._mood.transition(new_mood, reason)
134
+
135
+ def reason_for_mood_change(self) -> str:
136
+ return self._mood.reason
137
+
138
+ def set_mood_angry(self, reason: str) -> None:
139
+ self.mood_swing("Angry", reason)
140
+
141
+ def set_mood_sad(self, reason: str) -> None:
142
+ self.mood_swing("Sad", reason)
143
+
144
+ def set_mood_happy(self, reason: str) -> None:
145
+ self.mood_swing("Happy", reason)
146
+
147
+ def set_mood_calm(self, reason: str) -> None:
148
+ self.mood_swing("Calm", reason)
149
+
150
+ def auto_transition_mood(
151
+ self, valence: float, arousal: float, reason: str = "auto_circumplex",
152
+ ) -> str:
153
+ v = float(max(-1.0, min(1.0, valence)))
154
+ a = float(max(0.0, min(1.0, arousal)))
155
+ v_pos = v > 0.10
156
+ v_neg = v < -0.10
157
+ a_high = a > 0.60
158
+ a_low = a < 0.40
159
+
160
+ if a_high and v_pos:
161
+ new_mood = "Happy"
162
+ elif a_high and v_neg:
163
+ new_mood = "Angry"
164
+ elif a_low and v_neg:
165
+ new_mood = "Sad"
166
+ elif a_low and v_pos:
167
+ new_mood = "Calm"
168
+ else:
169
+ return self._mood.name
170
+
171
+ if new_mood != self._mood.name:
172
+ self._mood.transition(new_mood, reason)
173
+ return new_mood
174
+
175
+ def update_homeostasis(
176
+ self,
177
+ action_impact: float,
178
+ environment_surprise: float,
179
+ policy_violation: float = 0.0,
180
+ tool_failure_rate: float = 0.0,
181
+ task_success: float = 0.0,
182
+ ) -> None:
183
+ self._homeostasis.update(
184
+ action_impact=action_impact,
185
+ environment_surprise=environment_surprise,
186
+ policy_violation=policy_violation,
187
+ tool_failure_rate=tool_failure_rate,
188
+ task_success=task_success,
189
+ )
190
+
191
+ def calculate_strain(self) -> torch.Tensor:
192
+ return self._homeostasis.strain()
193
+
194
+ def get_valence(self, latent_state: torch.Tensor) -> torch.Tensor:
195
+ s = self._homeostasis.vector
196
+ if latent_state.dim() > 1:
197
+ s = s.unsqueeze(0).expand(latent_state.size(0), -1)
198
+ combined = torch.cat([latent_state, s], dim=-1)
199
+ return self.valence_network(combined)
@@ -0,0 +1,147 @@
1
+ """
2
+ amygdala/emotional_memory.py — Emotion-tagged memory encoding.
3
+
4
+ The amygdala tags memories with emotional significance at encoding time.
5
+ Traumatic or highly rewarding experiences are remembered more vividly
6
+ than neutral ones. This module biases hippocampal memory retrieval
7
+ toward emotionally significant episodes.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Dict, List, Optional, Tuple
13
+
14
+ import torch
15
+ import torch.nn as nn
16
+ import torch.nn.functional as F
17
+
18
+
19
+ class EmotionalMemoryTagger(nn.Module):
20
+ """
21
+ Tags latent states with emotional significance scores at encoding time.
22
+
23
+ The significance score is used by the hippocampus to weight replay
24
+ sampling — high-significance memories are replayed more often.
25
+
26
+ Significance formula:
27
+ σ = α · |valence| + β · arousal + γ · surprise
28
+
29
+ Where surprise = ||z_predicted - z_actual||₂ (from world model).
30
+
31
+ Args:
32
+ alpha: Weight on valence magnitude.
33
+ beta: Weight on arousal level.
34
+ gamma: Weight on prediction surprise.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ alpha: float = 0.4,
40
+ beta: float = 0.3,
41
+ gamma: float = 0.3,
42
+ ) -> None:
43
+ super().__init__()
44
+ self.alpha = alpha
45
+ self.beta = beta
46
+ self.gamma = gamma
47
+
48
+ def compute_significance(
49
+ self,
50
+ valence: torch.Tensor,
51
+ arousal: torch.Tensor,
52
+ surprise: Optional[torch.Tensor] = None,
53
+ ) -> torch.Tensor:
54
+ """
55
+ Compute emotional significance score for a batch of experiences.
56
+
57
+ Args:
58
+ valence: (B, 1) emotional valence in [-1, 1].
59
+ arousal: (B, 1) arousal level in [0, 1].
60
+ surprise: (B, 1) optional prediction error from world model.
61
+
62
+ Returns:
63
+ (B, 1) significance score in [0, 1].
64
+ """
65
+ sig = self.alpha * valence.abs() + self.beta * arousal
66
+ if surprise is not None:
67
+ sig = sig + self.gamma * surprise
68
+ return torch.clamp(sig, 0.0, 1.0)
69
+
70
+ def tag_episode(
71
+ self,
72
+ states: torch.Tensor,
73
+ valences: torch.Tensor,
74
+ arousal: float,
75
+ surprise: Optional[float] = None,
76
+ ) -> Dict:
77
+ """
78
+ Create an emotion-tagged episode dict for the hippocampus.
79
+
80
+ Args:
81
+ states: (T, D) state sequence.
82
+ valences: (T, 1) valence sequence.
83
+ arousal: Scalar arousal level.
84
+ surprise: Optional scalar prediction surprise.
85
+
86
+ Returns:
87
+ Dict with 'states', 'significance', 'emotional_tag'.
88
+ """
89
+ arousal_t = torch.tensor([[arousal]])
90
+ surprise_t = torch.tensor([[surprise]]) if surprise is not None else None
91
+
92
+ mean_valence = valences.mean().unsqueeze(0).unsqueeze(0)
93
+ sig = self.compute_significance(mean_valence, arousal_t, surprise_t)
94
+
95
+ return {
96
+ "states": states,
97
+ "significance": float(sig.item()),
98
+ "emotional_tag": {
99
+ "mean_valence": float(mean_valence.item()),
100
+ "arousal": arousal,
101
+ "surprise": surprise or 0.0,
102
+ },
103
+ }
104
+
105
+
106
+ class EmotionalRetrievalBias(nn.Module):
107
+ """
108
+ Biases memory retrieval queries toward emotionally congruent memories.
109
+
110
+ Mood-congruent memory: humans recall memories that match their current
111
+ mood more easily. This module implements the same bias: when the agent
112
+ is in a high-arousal state, it preferentially retrieves high-arousal
113
+ memories.
114
+
115
+ Args:
116
+ d_model: Latent dimensionality.
117
+ """
118
+
119
+ def __init__(self, d_model: int) -> None:
120
+ super().__init__()
121
+ # Projects emotional state into query space
122
+ self.emotion_to_query = nn.Linear(4, d_model) # 4 = homeostasis dims
123
+
124
+ def bias_query(
125
+ self,
126
+ base_query: torch.Tensor,
127
+ homeostasis_vector: torch.Tensor,
128
+ strength: float = 0.3,
129
+ ) -> torch.Tensor:
130
+ """
131
+ Add an emotional bias to a memory retrieval query.
132
+
133
+ Args:
134
+ base_query: (B, D) base retrieval query.
135
+ homeostasis_vector: (4,) current homeostatic state.
136
+ strength: How strongly to bias the query.
137
+
138
+ Returns:
139
+ (B, D) emotionally-biased query.
140
+ """
141
+ if homeostasis_vector.dim() == 1:
142
+ homeostasis_vector = homeostasis_vector.unsqueeze(0)
143
+ emotion_bias = self.emotion_to_query(homeostasis_vector) # (1, D)
144
+ return base_query + strength * emotion_bias
145
+
146
+
147
+ __all__ = ["EmotionalMemoryTagger", "EmotionalRetrievalBias"]