FerrellSyntheticIntelligence commited on
Commit ·
c99bf3c
1
Parent(s): b573a93
Add PinealGland, AttentionalGate, PredictiveCortex, ThalamicLoop, full quad-flow architecture
Browse files- src/cortex/__init__.py +1 -0
- src/cortex/attention.py +81 -0
- src/cortex/predictive.py +80 -0
- src/pineal/__init__.py +1 -0
- src/pineal/pineal_gland.py +150 -0
- src/thalamus/thalamic_loop.py +93 -0
src/cortex/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
src/cortex/attention.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Attentional Gate — Vitalis FSI
|
| 3 |
+
|
| 4 |
+
Controls what gets through to the cognitive core.
|
| 5 |
+
High-salience inputs pass. Low-salience inputs are filtered.
|
| 6 |
+
Salience = novelty * valence_magnitude * relevance_to_identity
|
| 7 |
+
|
| 8 |
+
Biological analog: thalamic gating — the thalamus doesn't
|
| 9 |
+
pass everything to cortex, only what matters right now.
|
| 10 |
+
"""
|
| 11 |
+
import numpy as np
|
| 12 |
+
from vitalis_ide.math_core.kernel import VitalisKernel
|
| 13 |
+
from src.valence.valence_engine import ValenceEngine
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class AttentionalGate:
|
| 17 |
+
SALIENCE_THRESHOLD = 0.15
|
| 18 |
+
IDENTITY_WEIGHT = 0.3
|
| 19 |
+
NOVELTY_WEIGHT = 0.4
|
| 20 |
+
VALENCE_WEIGHT = 0.3
|
| 21 |
+
|
| 22 |
+
def __init__(self, valence: ValenceEngine, identity_vec: np.ndarray = None):
|
| 23 |
+
self.kernel = VitalisKernel()
|
| 24 |
+
self.valence = valence
|
| 25 |
+
self.identity_vec = identity_vec
|
| 26 |
+
self._recent = []
|
| 27 |
+
self._recent_max = 20
|
| 28 |
+
self._passed = 0
|
| 29 |
+
self._filtered = 0
|
| 30 |
+
|
| 31 |
+
def set_identity(self, vec: np.ndarray):
|
| 32 |
+
self.identity_vec = vec
|
| 33 |
+
|
| 34 |
+
def _novelty(self, hv: np.ndarray) -> float:
|
| 35 |
+
if not self._recent:
|
| 36 |
+
return 1.0
|
| 37 |
+
sims = [self.kernel.similarity(hv, r) for r in self._recent]
|
| 38 |
+
return float(1.0 - max(sims))
|
| 39 |
+
|
| 40 |
+
def _identity_relevance(self, hv: np.ndarray) -> float:
|
| 41 |
+
if self.identity_vec is None:
|
| 42 |
+
return 0.5
|
| 43 |
+
return float(max(0.0, self.kernel.similarity(hv, self.identity_vec)))
|
| 44 |
+
|
| 45 |
+
def gate(self, hv: np.ndarray, force: bool = False) -> tuple:
|
| 46 |
+
"""
|
| 47 |
+
Returns (passes: bool, salience: float, reason: str)
|
| 48 |
+
"""
|
| 49 |
+
novelty = self._novelty(hv)
|
| 50 |
+
val, vconf = self.valence.evaluate(hv)
|
| 51 |
+
relevance = self._identity_relevance(hv)
|
| 52 |
+
|
| 53 |
+
salience = (
|
| 54 |
+
self.NOVELTY_WEIGHT * novelty +
|
| 55 |
+
self.VALENCE_WEIGHT * vconf +
|
| 56 |
+
self.IDENTITY_WEIGHT * relevance
|
| 57 |
+
)
|
| 58 |
+
salience = float(np.clip(salience, 0.0, 1.0))
|
| 59 |
+
|
| 60 |
+
passes = force or salience >= self.SALIENCE_THRESHOLD
|
| 61 |
+
|
| 62 |
+
if passes:
|
| 63 |
+
self._recent.append(hv.copy())
|
| 64 |
+
if len(self._recent) > self._recent_max:
|
| 65 |
+
self._recent.pop(0)
|
| 66 |
+
self._passed += 1
|
| 67 |
+
reason = f"salience={salience:.3f} novelty={novelty:.3f} relevance={relevance:.3f}"
|
| 68 |
+
else:
|
| 69 |
+
self._filtered += 1
|
| 70 |
+
reason = f"filtered salience={salience:.3f} below threshold={self.SALIENCE_THRESHOLD}"
|
| 71 |
+
|
| 72 |
+
return passes, salience, reason
|
| 73 |
+
|
| 74 |
+
def report(self) -> dict:
|
| 75 |
+
total = self._passed + self._filtered
|
| 76 |
+
return {
|
| 77 |
+
"passed": self._passed,
|
| 78 |
+
"filtered": self._filtered,
|
| 79 |
+
"pass_rate": round(self._passed / total, 3) if total else 0.0,
|
| 80 |
+
"recent_inputs": len(self._recent),
|
| 81 |
+
}
|
src/cortex/predictive.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Predictive Cortex — Vitalis FSI
|
| 3 |
+
|
| 4 |
+
Implements predictive processing: the cortex maintains a model
|
| 5 |
+
of what it expects to see next, and only forwards the PREDICTION
|
| 6 |
+
ERROR to higher cognitive layers — not the raw input.
|
| 7 |
+
|
| 8 |
+
This is how biological cortex works. It predicts constantly.
|
| 9 |
+
What gets attention is what violates prediction.
|
| 10 |
+
|
| 11 |
+
Steps:
|
| 12 |
+
1. Maintain a running prediction of the next input
|
| 13 |
+
2. Compute prediction error = actual - predicted
|
| 14 |
+
3. Update prediction based on error
|
| 15 |
+
4. Forward error vector to cognitive core
|
| 16 |
+
5. Strong errors = surprise = attention = learning
|
| 17 |
+
"""
|
| 18 |
+
import numpy as np
|
| 19 |
+
from vitalis_ide.math_core.kernel import VitalisKernel
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class PredictiveCortex:
|
| 23 |
+
LEARNING_RATE = 0.05
|
| 24 |
+
SURPRISE_THRESHOLD = 0.3
|
| 25 |
+
|
| 26 |
+
def __init__(self, dim: int = 10_000):
|
| 27 |
+
self.dim = dim
|
| 28 |
+
self.kernel = VitalisKernel()
|
| 29 |
+
self._prediction = np.zeros(dim, dtype=np.float32)
|
| 30 |
+
self._cycle = 0
|
| 31 |
+
self._surprise_history = []
|
| 32 |
+
|
| 33 |
+
def process(self, hv: np.ndarray) -> tuple:
|
| 34 |
+
"""
|
| 35 |
+
Feed an input hypervector through predictive processing.
|
| 36 |
+
|
| 37 |
+
Returns:
|
| 38 |
+
error_vec : prediction error as bipolar int8 vector
|
| 39 |
+
surprise : float [0,1] — how surprising was this input
|
| 40 |
+
is_novel : bool — above surprise threshold
|
| 41 |
+
"""
|
| 42 |
+
self._cycle += 1
|
| 43 |
+
hv_f = hv.astype(np.float32)
|
| 44 |
+
|
| 45 |
+
# Prediction error
|
| 46 |
+
error_f = hv_f - self._prediction
|
| 47 |
+
|
| 48 |
+
# Surprise = normalized magnitude of error
|
| 49 |
+
surprise = float(np.tanh(np.linalg.norm(error_f) / np.sqrt(self.dim)))
|
| 50 |
+
|
| 51 |
+
# Update prediction toward actual input
|
| 52 |
+
self._prediction += self.LEARNING_RATE * error_f
|
| 53 |
+
|
| 54 |
+
# Binarize error for downstream HDC processing
|
| 55 |
+
error_vec = np.sign(error_f).astype(np.int8)
|
| 56 |
+
error_vec[error_vec == 0] = 1
|
| 57 |
+
|
| 58 |
+
is_novel = surprise > self.SURPRISE_THRESHOLD
|
| 59 |
+
|
| 60 |
+
self._surprise_history.append(surprise)
|
| 61 |
+
if len(self._surprise_history) > 100:
|
| 62 |
+
self._surprise_history.pop(0)
|
| 63 |
+
|
| 64 |
+
return error_vec, surprise, is_novel
|
| 65 |
+
|
| 66 |
+
def reset_prediction(self):
|
| 67 |
+
"""Call after dream cycle — fresh prediction slate."""
|
| 68 |
+
self._prediction *= 0.5
|
| 69 |
+
|
| 70 |
+
def avg_surprise(self) -> float:
|
| 71 |
+
if not self._surprise_history:
|
| 72 |
+
return 0.0
|
| 73 |
+
return round(float(np.mean(self._surprise_history[-20:])), 4)
|
| 74 |
+
|
| 75 |
+
def report(self) -> dict:
|
| 76 |
+
return {
|
| 77 |
+
"cycles": self._cycle,
|
| 78 |
+
"avg_surprise": self.avg_surprise(),
|
| 79 |
+
"prediction_norm": round(float(np.linalg.norm(self._prediction)), 4),
|
| 80 |
+
}
|
src/pineal/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
src/pineal/pineal_gland.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Pineal Gland — Vitalis FSI
|
| 3 |
+
|
| 4 |
+
Her internal clock. Her temporal awareness.
|
| 5 |
+
Tracks cognitive load over time and orchestrates the rhythm:
|
| 6 |
+
Work → Load builds → Dream → Consolidate → Meditate → Work
|
| 7 |
+
"""
|
| 8 |
+
import time
|
| 9 |
+
import json
|
| 10 |
+
import numpy as np
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
STATES = {
|
| 14 |
+
"ACTIVE": "Working. Load is low. Push harder.",
|
| 15 |
+
"LOADING": "Load building. Monitor closely.",
|
| 16 |
+
"SATURATED": "Load is high. Dream soon.",
|
| 17 |
+
"DREAMING": "Consolidating. Do not interrupt.",
|
| 18 |
+
"MEDITATIVE":"Idle reflection. Background only.",
|
| 19 |
+
"RECOVERED": "Post-dream clarity. Peak performance.",
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
class PinealGland:
|
| 23 |
+
DREAM_THRESHOLD = 0.75
|
| 24 |
+
MEDITATE_THRESHOLD = 0.30
|
| 25 |
+
LOAD_ACCUMULATE = 0.003
|
| 26 |
+
LOAD_DECAY_DREAM = 0.60
|
| 27 |
+
LOAD_DECAY_MEDITATE = 0.90
|
| 28 |
+
FATIGUE_RATE = 0.001
|
| 29 |
+
FATIGUE_RECOVERY = 0.50
|
| 30 |
+
STATE_PATH = Path.home() / ".vitalis_workspace" / "pineal_state.json"
|
| 31 |
+
|
| 32 |
+
def __init__(self):
|
| 33 |
+
self._state = self._load()
|
| 34 |
+
self._boot_time = time.time()
|
| 35 |
+
self._last_tick = time.time()
|
| 36 |
+
|
| 37 |
+
def _load(self) -> dict:
|
| 38 |
+
if self.STATE_PATH.exists():
|
| 39 |
+
try:
|
| 40 |
+
with open(self.STATE_PATH) as f:
|
| 41 |
+
return json.load(f)
|
| 42 |
+
except Exception:
|
| 43 |
+
pass
|
| 44 |
+
return {
|
| 45 |
+
"cognitive_load": 0.10, "fatigue": 0.00,
|
| 46 |
+
"current_state": "ACTIVE", "last_dream_time": 0,
|
| 47 |
+
"last_meditate_time": 0, "total_cycles": 0,
|
| 48 |
+
"total_dreams": 0, "total_meditations": 0,
|
| 49 |
+
"uptime_seconds": 0, "state_history": [],
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
def _save(self):
|
| 53 |
+
self.STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
| 54 |
+
try:
|
| 55 |
+
import tempfile, os
|
| 56 |
+
fd, tmp = tempfile.mkstemp(dir=self.STATE_PATH.parent, suffix=".tmp")
|
| 57 |
+
with os.fdopen(fd, "w") as f:
|
| 58 |
+
json.dump(self._state, f, indent=2)
|
| 59 |
+
os.replace(tmp, self.STATE_PATH)
|
| 60 |
+
except Exception as e:
|
| 61 |
+
print(f"[PINEAL] Save failed: {e}")
|
| 62 |
+
|
| 63 |
+
def tick(self, cycle_success: bool = True, confidence: float = 0.5) -> str:
|
| 64 |
+
now = time.time()
|
| 65 |
+
dt = now - self._last_tick
|
| 66 |
+
self._last_tick = now
|
| 67 |
+
self._state["total_cycles"] += 1
|
| 68 |
+
self._state["uptime_seconds"] += dt
|
| 69 |
+
|
| 70 |
+
load_delta = self.LOAD_ACCUMULATE
|
| 71 |
+
if not cycle_success:
|
| 72 |
+
load_delta *= 2.0
|
| 73 |
+
if confidence < 0.4:
|
| 74 |
+
load_delta *= 1.5
|
| 75 |
+
|
| 76 |
+
self._state["cognitive_load"] = min(1.0, self._state["cognitive_load"] + load_delta)
|
| 77 |
+
self._state["fatigue"] = min(1.0, self._state["fatigue"] + self.FATIGUE_RATE)
|
| 78 |
+
|
| 79 |
+
action = self._recommend()
|
| 80 |
+
self._update_state(action)
|
| 81 |
+
self._save()
|
| 82 |
+
return action
|
| 83 |
+
|
| 84 |
+
def _recommend(self) -> str:
|
| 85 |
+
load = self._state["cognitive_load"]
|
| 86 |
+
fatigue = self._state["fatigue"]
|
| 87 |
+
if load >= self.DREAM_THRESHOLD or fatigue > 0.8:
|
| 88 |
+
return "DREAM"
|
| 89 |
+
time_since_meditate = time.time() - self._state["last_meditate_time"]
|
| 90 |
+
if load <= self.MEDITATE_THRESHOLD and time_since_meditate > 300:
|
| 91 |
+
return "MEDITATE"
|
| 92 |
+
if (time.time() - self._state["last_dream_time"]) > 3600 and load > 0.5:
|
| 93 |
+
return "DREAM"
|
| 94 |
+
return "WORK"
|
| 95 |
+
|
| 96 |
+
def _update_state(self, action: str):
|
| 97 |
+
state_map = {
|
| 98 |
+
"WORK": "ACTIVE" if self._state["cognitive_load"] < 0.5 else "LOADING",
|
| 99 |
+
"DREAM": "SATURATED",
|
| 100 |
+
"MEDITATE":"MEDITATIVE",
|
| 101 |
+
}
|
| 102 |
+
new_state = state_map.get(action, "ACTIVE")
|
| 103 |
+
if new_state != self._state["current_state"]:
|
| 104 |
+
self._state["state_history"].append({
|
| 105 |
+
"from": self._state["current_state"],
|
| 106 |
+
"to": new_state,
|
| 107 |
+
"t": time.time(),
|
| 108 |
+
"load": round(self._state["cognitive_load"], 3),
|
| 109 |
+
})
|
| 110 |
+
self._state["state_history"] = self._state["state_history"][-50:]
|
| 111 |
+
self._state["current_state"] = new_state
|
| 112 |
+
|
| 113 |
+
def acknowledge_dream(self):
|
| 114 |
+
self._state["cognitive_load"] *= self.LOAD_DECAY_DREAM
|
| 115 |
+
self._state["fatigue"] *= self.FATIGUE_RECOVERY
|
| 116 |
+
self._state["last_dream_time"] = time.time()
|
| 117 |
+
self._state["total_dreams"] += 1
|
| 118 |
+
self._state["current_state"] = "RECOVERED"
|
| 119 |
+
print(f"[PINEAL] Dream acknowledged. Load={self._state['cognitive_load']:.3f} Fatigue={self._state['fatigue']:.3f}")
|
| 120 |
+
self._save()
|
| 121 |
+
|
| 122 |
+
def acknowledge_meditation(self):
|
| 123 |
+
self._state["cognitive_load"] *= self.LOAD_DECAY_MEDITATE
|
| 124 |
+
self._state["last_meditate_time"] = time.time()
|
| 125 |
+
self._state["total_meditations"] += 1
|
| 126 |
+
if self._state["current_state"] == "MEDITATIVE":
|
| 127 |
+
self._state["current_state"] = "ACTIVE"
|
| 128 |
+
self._save()
|
| 129 |
+
|
| 130 |
+
def should_dream(self) -> bool: return self._recommend() == "DREAM"
|
| 131 |
+
def should_meditate(self) -> bool: return self._recommend() == "MEDITATE"
|
| 132 |
+
def should_work(self) -> bool: return self._recommend() == "WORK"
|
| 133 |
+
def cognitive_load(self) -> float: return round(self._state["cognitive_load"], 3)
|
| 134 |
+
def fatigue(self) -> float: return round(self._state["fatigue"], 3)
|
| 135 |
+
|
| 136 |
+
def report(self) -> dict:
|
| 137 |
+
load = self._state["cognitive_load"]
|
| 138 |
+
state = self._state["current_state"]
|
| 139 |
+
filled = int(load * 20)
|
| 140 |
+
return {
|
| 141 |
+
"state": state,
|
| 142 |
+
"state_meaning": STATES.get(state, "Unknown"),
|
| 143 |
+
"cognitive_load": round(load, 3),
|
| 144 |
+
"fatigue": round(self._state["fatigue"], 3),
|
| 145 |
+
"recommendation": self._recommend(),
|
| 146 |
+
"uptime_hours": round(self._state["uptime_seconds"] / 3600, 2),
|
| 147 |
+
"total_cycles": self._state["total_cycles"],
|
| 148 |
+
"total_dreams": self._state["total_dreams"],
|
| 149 |
+
"load_bar": f"[{'█' * filled}{'░' * (20 - filled)}] {load:.0%}",
|
| 150 |
+
}
|
src/thalamus/thalamic_loop.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Thalamic Loop — Vitalis FSI
|
| 3 |
+
|
| 4 |
+
The full perception pipeline:
|
| 5 |
+
Raw input
|
| 6 |
+
→ SyntheticThalamus (multimodal fusion)
|
| 7 |
+
→ AttentionalGate (salience filtering)
|
| 8 |
+
→ PredictiveCortex (prediction error)
|
| 9 |
+
→ Output: error vector + surprise + metadata
|
| 10 |
+
|
| 11 |
+
Only high-salience, surprising inputs reach the cognitive core.
|
| 12 |
+
Everything else is suppressed or predicted away.
|
| 13 |
+
This is attentional gating + predictive processing combined.
|
| 14 |
+
"""
|
| 15 |
+
import numpy as np
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
from src.thalamus.thalamus import SyntheticThalamus
|
| 18 |
+
from src.cortex.attention import AttentionalGate
|
| 19 |
+
from src.cortex.predictive import PredictiveCortex
|
| 20 |
+
from src.valence.valence_engine import ValenceEngine
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class ThalamicLoop:
|
| 24 |
+
def __init__(self, valence: ValenceEngine, identity_vec: np.ndarray = None):
|
| 25 |
+
self.thalamus = SyntheticThalamus()
|
| 26 |
+
self.attention = AttentionalGate(valence, identity_vec)
|
| 27 |
+
self.cortex = PredictiveCortex()
|
| 28 |
+
self._processed = 0
|
| 29 |
+
self._suppressed = 0
|
| 30 |
+
|
| 31 |
+
def process(self, payload: dict, force_attention: bool = False) -> dict:
|
| 32 |
+
"""
|
| 33 |
+
Full thalamic pipeline.
|
| 34 |
+
|
| 35 |
+
payload: dict with "text", "audio", "internal" keys
|
| 36 |
+
Returns dict with:
|
| 37 |
+
"hv" : fused hypervector from thalamus
|
| 38 |
+
"error_vec": prediction error from cortex
|
| 39 |
+
"surprise" : float surprise level
|
| 40 |
+
"is_novel" : bool
|
| 41 |
+
"passes" : bool — did it pass attention gate
|
| 42 |
+
"salience" : float
|
| 43 |
+
"suppressed": bool
|
| 44 |
+
"""
|
| 45 |
+
# 1. Thalamic fusion
|
| 46 |
+
hv = self.thalamus.ingest(payload)
|
| 47 |
+
|
| 48 |
+
# 2. Attentional gating
|
| 49 |
+
passes, salience, gate_reason = self.attention.gate(hv, force=force_attention)
|
| 50 |
+
|
| 51 |
+
if not passes:
|
| 52 |
+
self._suppressed += 1
|
| 53 |
+
return {
|
| 54 |
+
"hv": hv,
|
| 55 |
+
"error_vec": None,
|
| 56 |
+
"surprise": 0.0,
|
| 57 |
+
"is_novel": False,
|
| 58 |
+
"passes": False,
|
| 59 |
+
"salience": salience,
|
| 60 |
+
"suppressed": True,
|
| 61 |
+
"reason": gate_reason,
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
# 3. Predictive cortex
|
| 65 |
+
error_vec, surprise, is_novel = self.cortex.process(hv)
|
| 66 |
+
self._processed += 1
|
| 67 |
+
|
| 68 |
+
return {
|
| 69 |
+
"hv": hv,
|
| 70 |
+
"error_vec": error_vec,
|
| 71 |
+
"surprise": surprise,
|
| 72 |
+
"is_novel": is_novel,
|
| 73 |
+
"passes": True,
|
| 74 |
+
"salience": salience,
|
| 75 |
+
"suppressed": False,
|
| 76 |
+
"reason": gate_reason,
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
def process_text(self, text: str, force: bool = False) -> dict:
|
| 80 |
+
return self.process({"text": text}, force_attention=force)
|
| 81 |
+
|
| 82 |
+
def acknowledge_dream(self):
|
| 83 |
+
"""Reset prediction after dream cycle."""
|
| 84 |
+
self.cortex.reset_prediction()
|
| 85 |
+
|
| 86 |
+
def report(self) -> dict:
|
| 87 |
+
return {
|
| 88 |
+
"processed": self._processed,
|
| 89 |
+
"suppressed": self._suppressed,
|
| 90 |
+
"attention": self.attention.report(),
|
| 91 |
+
"cortex": self.cortex.report(),
|
| 92 |
+
"pineal": None,
|
| 93 |
+
}
|