kontinuum-core 0.1.1__tar.gz

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 (37) hide show
  1. kontinuum_core-0.1.1/LICENSE +10 -0
  2. kontinuum_core-0.1.1/PKG-INFO +46 -0
  3. kontinuum_core-0.1.1/README.md +28 -0
  4. kontinuum_core-0.1.1/pyproject.toml +34 -0
  5. kontinuum_core-0.1.1/setup.cfg +4 -0
  6. kontinuum_core-0.1.1/src/kontinuum_core/__init__.py +13 -0
  7. kontinuum_core-0.1.1/src/kontinuum_core/amygdala.py +164 -0
  8. kontinuum_core-0.1.1/src/kontinuum_core/anterior_cingulate.py +178 -0
  9. kontinuum_core-0.1.1/src/kontinuum_core/basal_ganglia.py +311 -0
  10. kontinuum_core-0.1.1/src/kontinuum_core/cerebellum.py +429 -0
  11. kontinuum_core-0.1.1/src/kontinuum_core/engine.py +206 -0
  12. kontinuum_core-0.1.1/src/kontinuum_core/entorhinal_cortex.py +51 -0
  13. kontinuum_core-0.1.1/src/kontinuum_core/hippocampus.py +493 -0
  14. kontinuum_core-0.1.1/src/kontinuum_core/hypothalamus.py +275 -0
  15. kontinuum_core-0.1.1/src/kontinuum_core/insula.py +211 -0
  16. kontinuum_core-0.1.1/src/kontinuum_core/locus_coeruleus.py +35 -0
  17. kontinuum_core-0.1.1/src/kontinuum_core/metaplasticity.py +213 -0
  18. kontinuum_core-0.1.1/src/kontinuum_core/neurorhythms.py +280 -0
  19. kontinuum_core-0.1.1/src/kontinuum_core/nucleus_accumbens.py +41 -0
  20. kontinuum_core-0.1.1/src/kontinuum_core/predictive_processing.py +238 -0
  21. kontinuum_core-0.1.1/src/kontinuum_core/prefrontal_cortex.py +421 -0
  22. kontinuum_core-0.1.1/src/kontinuum_core/reticular.py +87 -0
  23. kontinuum_core-0.1.1/src/kontinuum_core/scheduler.py +18 -0
  24. kontinuum_core-0.1.1/src/kontinuum_core/sleep_consolidation.py +276 -0
  25. kontinuum_core-0.1.1/src/kontinuum_core/spatial_cortex.py +292 -0
  26. kontinuum_core-0.1.1/src/kontinuum_core/thalamus.py +778 -0
  27. kontinuum_core-0.1.1/src/kontinuum_core/types.py +24 -0
  28. kontinuum_core-0.1.1/src/kontinuum_core.egg-info/PKG-INFO +46 -0
  29. kontinuum_core-0.1.1/src/kontinuum_core.egg-info/SOURCES.txt +35 -0
  30. kontinuum_core-0.1.1/src/kontinuum_core.egg-info/dependency_links.txt +1 -0
  31. kontinuum_core-0.1.1/src/kontinuum_core.egg-info/requires.txt +3 -0
  32. kontinuum_core-0.1.1/src/kontinuum_core.egg-info/top_level.txt +1 -0
  33. kontinuum_core-0.1.1/tests/test_cerebellum.py +77 -0
  34. kontinuum_core-0.1.1/tests/test_engine.py +190 -0
  35. kontinuum_core-0.1.1/tests/test_hippocampus.py +112 -0
  36. kontinuum_core-0.1.1/tests/test_predictive_processing.py +148 -0
  37. kontinuum_core-0.1.1/tests/test_thalamus.py +75 -0
@@ -0,0 +1,10 @@
1
+ GNU AFFERO GENERAL PUBLIC LICENSE
2
+ Version 3, 19 November 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ This repository uses the GNU Affero General Public License v3.0.
9
+ For the full license text, see:
10
+ https://www.gnu.org/licenses/agpl-3.0.txt
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: kontinuum-core
3
+ Version: 0.1.1
4
+ Summary: Neuro-inspired learning engine for anomaly detection
5
+ Author-email: Chance-Konstruktion <introareback@gmail.com>
6
+ License: AGPL-3.0
7
+ Project-URL: Homepage, https://github.com/Chance-Konstruktion/kontinuum-core
8
+ Project-URL: Issues, https://github.com/Chance-Konstruktion/kontinuum-core/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=7; extra == "dev"
17
+ Dynamic: license-file
18
+
19
+ # KONTINUUM Core
20
+
21
+ Pure Python learning engine extracted from [KONTINUUM](https://github.com/Chance-Konstruktion/ha-kontinuum). No Home Assistant dependency — usable from any Python project.
22
+
23
+ > **Part of the 3-repo family:**
24
+ > **kontinuum-core** (this repo, HA-free Python package on PyPI) ·
25
+ > [`ha-kontinuum`](https://github.com/Chance-Konstruktion/ha-kontinuum) (full HA Pro integration with UI) ·
26
+ > [`ha-kontinuum-lite`](https://github.com/Chance-Konstruktion/ha-kontinuum-lite) (slim HA integration, no UI)
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install kontinuum-core
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ```python
37
+ from kontinuum_core import KontinuumEngine
38
+
39
+ engine = KontinuumEngine()
40
+ snapshot = engine.observe({"token": "bedroom.light.on", "room": "bedroom"})
41
+ print(snapshot.surprise)
42
+ ```
43
+
44
+ ## License
45
+
46
+ AGPL-3.0 – see LICENSE file.
@@ -0,0 +1,28 @@
1
+ # KONTINUUM Core
2
+
3
+ Pure Python learning engine extracted from [KONTINUUM](https://github.com/Chance-Konstruktion/ha-kontinuum). No Home Assistant dependency — usable from any Python project.
4
+
5
+ > **Part of the 3-repo family:**
6
+ > **kontinuum-core** (this repo, HA-free Python package on PyPI) ·
7
+ > [`ha-kontinuum`](https://github.com/Chance-Konstruktion/ha-kontinuum) (full HA Pro integration with UI) ·
8
+ > [`ha-kontinuum-lite`](https://github.com/Chance-Konstruktion/ha-kontinuum-lite) (slim HA integration, no UI)
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pip install kontinuum-core
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ```python
19
+ from kontinuum_core import KontinuumEngine
20
+
21
+ engine = KontinuumEngine()
22
+ snapshot = engine.observe({"token": "bedroom.light.on", "room": "bedroom"})
23
+ print(snapshot.surprise)
24
+ ```
25
+
26
+ ## License
27
+
28
+ AGPL-3.0 – see LICENSE file.
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kontinuum-core"
7
+ version = "0.1.1"
8
+ authors = [{name = "Chance-Konstruktion", email = "introareback@gmail.com"}]
9
+ description = "Neuro-inspired learning engine for anomaly detection"
10
+ readme = "README.md"
11
+ license = {text = "AGPL-3.0"}
12
+ requires-python = ">=3.9"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "License :: OSI Approved :: GNU Affero General Public License v3",
16
+ "Operating System :: OS Independent",
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/Chance-Konstruktion/kontinuum-core"
21
+ Issues = "https://github.com/Chance-Konstruktion/kontinuum-core/issues"
22
+
23
+ [project.optional-dependencies]
24
+ dev = ["pytest>=7"]
25
+
26
+ [tool.setuptools]
27
+ package-dir = {"" = "src"}
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
31
+
32
+ [tool.pytest.ini_options]
33
+ testpaths = ["tests"]
34
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ """KONTINUUM Core - neuro-inspired learning engine (HA-free)."""
2
+ from .engine import KontinuumEngine
3
+ from .scheduler import Scheduler
4
+ from .types import MemoryState, Observation, Prediction
5
+
6
+ __version__ = "0.1.1"
7
+ __all__ = [
8
+ "KontinuumEngine",
9
+ "Scheduler",
10
+ "Observation",
11
+ "Prediction",
12
+ "MemoryState",
13
+ ]
@@ -0,0 +1,164 @@
1
+ """Amygdala module for KONTINUUM Core.
2
+
3
+ Biological inspiration: Risk assessment and safety gate. The amygdala
4
+ evaluates proposed actions emotionally – above all for danger – and can
5
+ trigger a rapid "STOP!" before the prefrontal cortex has finished
6
+ reasoning.
7
+ """
8
+
9
+ import logging
10
+ import time
11
+
12
+ _LOGGER = logging.getLogger(__name__)
13
+
14
+ NEVER_AUTO = {"alarm", "lock"}
15
+
16
+ RISK_SCORES = {
17
+ "light": 0.05, "media": 0.05, "automation": 0.1,
18
+ "switch": 0.2, "fan": 0.15, "cover": 0.3,
19
+ "climate": 0.35, "water_heater": 0.4, "vacuum": 0.25,
20
+ "lock": 0.9, "alarm": 1.0,
21
+ }
22
+
23
+ SAFE_STATES = {
24
+ "light": {"on", "off"},
25
+ "media": {"playing", "paused", "standby", "off"},
26
+ "switch": {"on", "off"},
27
+ "fan": {"on", "off"},
28
+ "climate": {"heating", "cooling", "idle", "off"},
29
+ "cover": {"open", "closed"},
30
+ "automation": {"on"},
31
+ }
32
+
33
+
34
+ class Amygdala:
35
+ """Assesses the risk of a proposed action."""
36
+
37
+ def __init__(self):
38
+ self.learned_risk = {}
39
+ self.veto_log = []
40
+ self.total_assessments = 0
41
+ self.total_vetoes = 0
42
+ self.total_cautions = 0
43
+
44
+ def assess(self, token: str, semantic: str, room: str,
45
+ state: str, confidence: float,
46
+ context: dict = None) -> dict:
47
+ """Assesses the risk of an action."""
48
+ self.total_assessments += 1
49
+ context = context or {}
50
+ reasons = []
51
+
52
+ # 1. Absolute vetoes
53
+ if semantic in NEVER_AUTO:
54
+ self.total_vetoes += 1
55
+ reason = f"{semantic} is safety-critical"
56
+ reasons.append(reason)
57
+ self._log_veto(token, reason)
58
+ return {
59
+ "decision": "VETO", "risk": 1.0,
60
+ "reasons": reasons, "required_confidence": float("inf"),
61
+ }
62
+
63
+ system_mode = context.get("system_mode", "home")
64
+ if system_mode in ("away", "vacation"):
65
+ if semantic not in ("automation",):
66
+ reasons.append(f"System in {system_mode} mode")
67
+
68
+ # 2. Base risk
69
+ base_risk = RISK_SCORES.get(semantic, 0.3)
70
+
71
+ # 2b. SAFE_STATES check
72
+ if semantic in SAFE_STATES:
73
+ if state not in SAFE_STATES[semantic]:
74
+ base_risk = min(1.0, base_risk + 0.2)
75
+ reasons.append(f"Unknown state '{state}' for {semantic}: +0.2")
76
+
77
+ # 3. Context modifiers
78
+ risk = base_risk
79
+
80
+ if system_mode in ("away", "vacation"):
81
+ risk = min(1.0, risk + 0.3)
82
+ reasons.append("Away mode: +0.3 risk")
83
+
84
+ is_night = context.get("is_night", False)
85
+ if is_night and semantic == "light" and state == "on":
86
+ risk = min(1.0, risk + 0.1)
87
+ reasons.append("Light on at night: +0.1")
88
+
89
+ learned_key = f"{room}.{semantic}.{state}"
90
+ if learned_key in self.learned_risk:
91
+ modifier = self.learned_risk[learned_key]
92
+ risk = max(0.0, min(1.0, risk + modifier))
93
+ reasons.append(f"Learned: {modifier:+.2f}")
94
+
95
+ # 4. Confidence thresholds
96
+ if risk < 0.15:
97
+ required_conf = 0.3
98
+ elif risk < 0.3:
99
+ required_conf = 0.5
100
+ elif risk < 0.5:
101
+ required_conf = 0.65
102
+ elif risk < 0.7:
103
+ required_conf = 0.8
104
+ else:
105
+ required_conf = 0.95
106
+
107
+ # 5. Decision
108
+ if risk >= 0.95:
109
+ decision = "VETO"
110
+ self.total_vetoes += 1
111
+ reasons.append(f"Risk {risk:.2f} >= 0.95")
112
+ self._log_veto(token, reasons[-1])
113
+ elif confidence < required_conf:
114
+ decision = "CAUTION"
115
+ self.total_cautions += 1
116
+ reasons.append(f"Conf {confidence:.0%} < {required_conf:.0%}")
117
+ else:
118
+ decision = "ALLOW"
119
+ reasons.append(f"Conf {confidence:.0%} >= {required_conf:.0%}")
120
+
121
+ return {
122
+ "decision": decision, "risk": round(risk, 3),
123
+ "reasons": reasons, "required_confidence": required_conf,
124
+ }
125
+
126
+ def learn_from_feedback(self, token: str, feedback: str):
127
+ """Adjusts risk based on user feedback."""
128
+ if feedback == "negative":
129
+ current = self.learned_risk.get(token, 0.0)
130
+ self.learned_risk[token] = min(0.5, current + 0.05)
131
+ elif feedback == "positive":
132
+ current = self.learned_risk.get(token, 0.0)
133
+ self.learned_risk[token] = max(-0.2, current - 0.02)
134
+
135
+ def _log_veto(self, token: str, reason: str):
136
+ self.veto_log.append({"timestamp": time.time(), "token": token, "reason": reason})
137
+ if len(self.veto_log) > 50:
138
+ self.veto_log = self.veto_log[-50:]
139
+ _LOGGER.info("VETO: %s – %s", token, reason)
140
+
141
+ def to_dict(self) -> dict:
142
+ return {
143
+ "learned_risk": self.learned_risk,
144
+ "total_assessments": self.total_assessments,
145
+ "total_vetoes": self.total_vetoes,
146
+ "total_cautions": self.total_cautions,
147
+ }
148
+
149
+ def from_dict(self, data: dict):
150
+ self.learned_risk = data.get("learned_risk", {})
151
+ self.total_assessments = data.get("total_assessments", 0)
152
+ self.total_vetoes = data.get("total_vetoes", 0)
153
+ self.total_cautions = data.get("total_cautions", 0)
154
+
155
+ @property
156
+ def stats(self) -> dict:
157
+ return {
158
+ "total_assessments": self.total_assessments,
159
+ "total_vetoes": self.total_vetoes,
160
+ "total_cautions": self.total_cautions,
161
+ "learned_risks": len(self.learned_risk),
162
+ "veto_rate": f"{self.total_vetoes / max(1, self.total_assessments):.1%}",
163
+ "recent_vetos": self.veto_log[-5:],
164
+ }
@@ -0,0 +1,178 @@
1
+ """Anterior Cingulate Cortex (ACC) – conflict monitor and error tracker.
2
+
3
+ Biological inspiration: The ACC detects conflicts between competing
4
+ action candidates and signals when uncertainty is too high. It
5
+ modulates the decision threshold: many conflicts → act more cautiously,
6
+ high clarity → act faster.
7
+
8
+ Responsibilities:
9
+ - Detect conflicts between modules (e.g. Hippocampus vs Amygdala)
10
+ - Track prediction errors (expectation vs reality)
11
+ - Adjust confidence thresholds dynamically
12
+ - Signal "cognitive control" when uncertainty is high
13
+
14
+ Performance: pure arithmetic, no I/O, no ML. ~0 ms per event.
15
+ """
16
+
17
+ import logging
18
+ from collections import deque
19
+
20
+ _LOGGER = logging.getLogger(__name__)
21
+
22
+ # How many decisions to keep in the rolling window
23
+ HISTORY_SIZE = 200
24
+ # EMA factor for conflict rate
25
+ EMA_ALPHA = 0.05
26
+ # Thresholds for conflict level
27
+ CONFLICT_LOW = 0.2
28
+ CONFLICT_HIGH = 0.6
29
+
30
+
31
+ class AnteriorCingulateCortex:
32
+ """Monitors conflicts between modules and adapts decision thresholds."""
33
+
34
+ def __init__(self):
35
+ self._conflict_history = deque(maxlen=HISTORY_SIZE)
36
+ self._error_history = deque(maxlen=HISTORY_SIZE)
37
+
38
+ self.conflict_level = 0.0 # 0.0 = no conflict, 1.0 = max conflict
39
+ self.error_rate = 0.0 # fraction of wrong predictions
40
+ self.cognitive_control = 0.0 # how hard to brake
41
+
42
+ self.total_conflicts = 0
43
+ self.total_agreements = 0
44
+ self.total_errors = 0
45
+ self.total_correct = 0
46
+ self.threshold_adjustments = 0
47
+
48
+ self.confidence_threshold = 0.5
49
+ self._last_update_ts = 0.0
50
+
51
+ def observe_decision(self, proposals: list) -> float:
52
+ """Measures the conflict level of a decision round.
53
+
54
+ proposals: list of module suggestions, e.g.:
55
+ [{"source": "hippocampus", "action": "light.on", "confidence": 0.8},
56
+ {"source": "amygdala", "action": "veto", "confidence": 0.6}]
57
+
58
+ Returns: conflict value in [0.0, 1.0]
59
+ """
60
+ if not proposals or len(proposals) < 2:
61
+ self._conflict_history.append(0.0)
62
+ self._update_ema()
63
+ self.total_agreements += 1
64
+ return 0.0
65
+
66
+ actions = [p.get("action", "") for p in proposals]
67
+ unique_actions = set(actions)
68
+
69
+ disagreement = (len(unique_actions) - 1) / max(1, len(actions) - 1)
70
+
71
+ has_veto = any(p.get("action") == "veto" or p.get("veto", False) for p in proposals)
72
+ if has_veto:
73
+ disagreement = min(1.0, disagreement + 0.3)
74
+
75
+ confidences = [p.get("confidence", 0.5) for p in proposals]
76
+ if len(confidences) > 1:
77
+ mean_conf = sum(confidences) / len(confidences)
78
+ variance = sum((c - mean_conf) ** 2 for c in confidences) / len(confidences)
79
+ disagreement = min(1.0, disagreement + variance * 0.5)
80
+
81
+ self._conflict_history.append(disagreement)
82
+ self._update_ema()
83
+
84
+ if disagreement > CONFLICT_LOW:
85
+ self.total_conflicts += 1
86
+ else:
87
+ self.total_agreements += 1
88
+
89
+ return disagreement
90
+
91
+ def observe_outcome(self, was_correct: bool):
92
+ """Records whether the last action was correct."""
93
+ self._error_history.append(0.0 if was_correct else 1.0)
94
+
95
+ if was_correct:
96
+ self.total_correct += 1
97
+ else:
98
+ self.total_errors += 1
99
+
100
+ self.error_rate = (
101
+ EMA_ALPHA * (0.0 if was_correct else 1.0)
102
+ + (1.0 - EMA_ALPHA) * self.error_rate
103
+ )
104
+
105
+ self._adjust_threshold()
106
+
107
+ def get_adjusted_threshold(self, base_confidence: float = 0.5) -> float:
108
+ """Returns an adjusted confidence threshold.
109
+
110
+ High conflict → threshold raised (more cautious).
111
+ Low conflict → threshold lowered (act faster).
112
+ """
113
+ return max(0.2, min(0.95, base_confidence + self.cognitive_control * 0.3))
114
+
115
+ def should_defer_to_cortex(self) -> bool:
116
+ """Returns True when uncertainty is high enough to consult the cortex (LLM)."""
117
+ return self.conflict_level > CONFLICT_HIGH or self.error_rate > 0.4
118
+
119
+ def _update_ema(self):
120
+ if self._conflict_history:
121
+ latest = self._conflict_history[-1]
122
+ self.conflict_level = (
123
+ EMA_ALPHA * latest + (1.0 - EMA_ALPHA) * self.conflict_level
124
+ )
125
+ self.cognitive_control = min(1.0, self.conflict_level * 0.6 + self.error_rate * 0.4)
126
+
127
+ def _adjust_threshold(self):
128
+ old = self.confidence_threshold
129
+
130
+ if self.error_rate > 0.3:
131
+ self.confidence_threshold = min(0.9, self.confidence_threshold + 0.02)
132
+ elif self.error_rate < 0.1 and self.conflict_level < CONFLICT_LOW:
133
+ self.confidence_threshold = max(0.3, self.confidence_threshold - 0.01)
134
+
135
+ if abs(old - self.confidence_threshold) > 0.001:
136
+ self.threshold_adjustments += 1
137
+
138
+ @property
139
+ def stats(self) -> dict:
140
+ return {
141
+ "conflict_level": round(self.conflict_level, 3),
142
+ "error_rate": round(self.error_rate, 3),
143
+ "cognitive_control": round(self.cognitive_control, 3),
144
+ "confidence_threshold": round(self.confidence_threshold, 3),
145
+ "total_conflicts": self.total_conflicts,
146
+ "total_agreements": self.total_agreements,
147
+ "total_errors": self.total_errors,
148
+ "total_correct": self.total_correct,
149
+ "threshold_adjustments": self.threshold_adjustments,
150
+ }
151
+
152
+ def to_dict(self) -> dict:
153
+ return {
154
+ "conflict_level": self.conflict_level,
155
+ "error_rate": self.error_rate,
156
+ "cognitive_control": self.cognitive_control,
157
+ "confidence_threshold": self.confidence_threshold,
158
+ "total_conflicts": self.total_conflicts,
159
+ "total_agreements": self.total_agreements,
160
+ "total_errors": self.total_errors,
161
+ "total_correct": self.total_correct,
162
+ "threshold_adjustments": self.threshold_adjustments,
163
+ }
164
+
165
+ def from_dict(self, data: dict):
166
+ self.conflict_level = data.get("conflict_level", 0.0)
167
+ self.error_rate = data.get("error_rate", 0.0)
168
+ self.cognitive_control = data.get("cognitive_control", 0.0)
169
+ self.confidence_threshold = data.get("confidence_threshold", 0.5)
170
+ self.total_conflicts = data.get("total_conflicts", 0)
171
+ self.total_agreements = data.get("total_agreements", 0)
172
+ self.total_errors = data.get("total_errors", 0)
173
+ self.total_correct = data.get("total_correct", 0)
174
+ self.threshold_adjustments = data.get("threshold_adjustments", 0)
175
+
176
+
177
+ # Backwards-compatible alias for engine.py imports.
178
+ AnteriorCingulate = AnteriorCingulateCortex