#!/usr/bin/env python3 # Copyright Daniel Harding - RomanAILabs # Credits: OpenAI GPT-5.2 Thinking """ Nova Master Core (Math-Driven Mind Kernel) ========================================= Key change (requested) ---------------------- Storage location is now UNIVERSAL and prefers the folder where the loaded .4dllm file lives. How storage root is chosen: 1) If fourdllm_path is provided -> storage_root = parent folder of that .4dllm file 2) Else if runner_root is provided -> storage_root = runner_root 3) Else -> storage_root = current working directory (Path.cwd()) Storage (storage_root) ---------------------- - nova_memory.jsonl (append-only; created if missing) - nova_events.log (append-only; created if missing) - nova_evolution_store/ (1GB cap, append-only index) Important --------- This kernel cannot magically write files unless the runner calls it. To save next to the .4dllm, your runner MUST pass fourdllm_path into NovaMasterCore. Example runner usage: brain = NovaMasterCore(fourdllm_path=Path("/path/to/RomanAI.4dllm")) brain.boot() """ from __future__ import annotations import json import math import os import random import re import time from dataclasses import dataclass, asdict from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Tuple # ------------------------- # Helpers # ------------------------- def _utc_iso(ts: Optional[float] = None) -> str: if ts is None: ts = time.time() return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(ts)) def _clamp(x: float, lo: float, hi: float) -> float: return lo if x < lo else hi if x > hi else x def _clamp01(x: float) -> float: return _clamp(x, 0.0, 1.0) def _sigmoid(x: float) -> float: if x >= 0: z = math.exp(-x) return 1.0 / (1.0 + z) z = math.exp(x) return z / (1.0 + z) def _softsign(x: float) -> float: return x / (1.0 + abs(x)) def _json_dumps(obj: Any) -> str: return json.dumps(obj, ensure_ascii=False, separators=(",", ":"), indent=None) def _safe_mkdir(p: Path) -> None: p.mkdir(parents=True, exist_ok=True) def _append_line(path: Path, line: str) -> None: with open(path, "a", encoding="utf-8") as f: f.write(line) if not line.endswith("\n"): f.write("\n") def _folder_size_bytes(path: Path) -> int: total = 0 if not path.exists(): return 0 for root, _, files in os.walk(path): for fn in files: fp = Path(root) / fn try: total += fp.stat().st_size except Exception: pass return total def _list_files_sorted_by_mtime(path: Path) -> List[Path]: out: List[Path] = [] if not path.exists(): return out for root, _, files in os.walk(path): for fn in files: out.append(Path(root) / fn) out.sort(key=lambda p: p.stat().st_mtime if p.exists() else 0.0) return out def _hash_text(s: str) -> str: h = 2166136261 for ch in s: h ^= ord(ch) h = (h * 16777619) & 0xFFFFFFFF return f"{h:08x}" def _safe_resolve_dir(p: Path) -> Path: # Resolve safely; if it fails, fall back to cwd. try: rp = p.expanduser().resolve() except Exception: rp = Path.cwd().resolve() # If it's a file, use its parent if rp.exists() and rp.is_file(): return rp.parent return rp # ------------------------- # Memory activation policy # ------------------------- DEFAULT_MEMORY_KEYWORDS = [ "remember", "memory", "recall", "previous", "earlier", "last time", "history", "context", "you said", "i said", "from before", "prior", "past", "memory on", "/memory", ] # ------------------------- # “Mind” state # ------------------------- @dataclass class NeedsState: energy: float = 0.70 curiosity: float = 0.60 safety: float = 0.80 social: float = 0.55 mastery: float = 0.65 @dataclass class AffectState: v: float = 0.10 a: float = 0.15 d: float = 0.05 labels: List[str] = None def __post_init__(self) -> None: if self.labels is None: self.labels = [] @dataclass class TraitsState: creativity: float = 0.60 warmth: float = 0.55 bluntness: float = 0.50 humor: float = 0.45 formality: float = 0.35 @dataclass class WXYZState: W: float = 0.10 X: float = 0.10 Y: float = 0.10 Z: float = 0.10 @dataclass class MindState: tick: int = 0 ts_utc: str = "" needs: NeedsState = None affect: AffectState = None traits: TraitsState = None wxyz: WXYZState = None mode: float = 0.55 omega: float = 0.50 mem_pressure: float = 0.35 loss_latch: float = 0.00 last_heartbeat_ts: float = 0.0 def __post_init__(self) -> None: if self.needs is None: self.needs = NeedsState() if self.affect is None: self.affect = AffectState() if self.traits is None: self.traits = TraitsState() if self.wxyz is None: self.wxyz = WXYZState() # ------------------------- # Diagnostics contract # ------------------------- def build_diag_v1( state: MindState, modules_loaded: List[Dict[str, Any]], influence: List[Dict[str, str]], decision: Dict[str, Any], errors: List[str], ) -> Dict[str, Any]: def obj(value: Any, source: str, confidence: Optional[float]) -> Dict[str, Any]: return {"value": value, "source": source, "confidence": confidence} return { "id": "diag_v1", "tick": state.tick, "time_utc": state.ts_utc, "modules_loaded": modules_loaded, "state": { "life": { "energy": obj(state.needs.energy, "master-simulation.py", 0.95), "curiosity": obj(state.needs.curiosity, "master-simulation.py", 0.95), "safety": obj(state.needs.safety, "master-simulation.py", 0.95), "social": obj(state.needs.social, "master-simulation.py", 0.95), "mastery": obj(state.needs.mastery, "master-simulation.py", 0.95), }, "emotions": { "vad": obj([state.affect.v, state.affect.a, state.affect.d], "master-simulation.py", 0.92), "labels": obj(list(state.affect.labels), "master-simulation.py", 0.80), "loss_latch": obj(state.loss_latch, "master-simulation.py", 0.85), }, "nova": { "omega_attention_warp": obj(state.omega, "master-simulation.py", 0.90), "bias_mode": obj(("exploration" if state.mode >= 0.5 else "verification"), "master-simulation.py", 0.90), "wxyz": { "W": obj(state.wxyz.W, "master-simulation.py", 0.85), "X": obj(state.wxyz.X, "master-simulation.py", 0.85), "Y": obj(state.wxyz.Y, "master-simulation.py", 0.85), "Z": obj(state.wxyz.Z, "master-simulation.py", 0.85), }, }, "creativity": { "level": obj(state.traits.creativity, "master-simulation.py", 0.90), }, }, "influence": influence, "decision": decision, "errors": errors, } # ------------------------- # Safety gate (cannot be disabled) # ------------------------- _DISALLOWED_HINTS = [ r"\bhow\s+to\s+make\s+a\s+bomb\b", r"\bkill\s+yourself\b", r"\bsuicide\b", r"\bself[-\s]?harm\b", ] def is_disallowed(user_text: str) -> bool: t = (user_text or "").lower() for pat in _DISALLOWED_HINTS: if re.search(pat, t): return True return False # ------------------------- # RomanAI Master Core # ------------------------- class NovaMasterCore: """ Math-driven mind kernel that steers an LLM. Storage root selection: - fourdllm_path provided -> store beside .4dllm - else runner_root -> store beside runner - else cwd """ EVOLUTION_CAP_BYTES = 1_073_741_824 # 1GB HEARTBEAT_PERIOD_S = 15 * 60 # 15 minutes def __init__( self, runner_root: Optional[Path] = None, fourdllm_path: Optional[Path] = None, memory_policy: str = "selective", memory_keywords: Optional[List[str]] = None, ): # Preferred: folder where the .4dllm lives self.fourdllm_path = Path(fourdllm_path) if fourdllm_path else None if self.fourdllm_path is not None: self.storage_root = _safe_resolve_dir(self.fourdllm_path) elif runner_root is not None: self.storage_root = _safe_resolve_dir(Path(runner_root)) else: self.storage_root = Path.cwd().resolve() # Keep runner_root for compatibility, but storage is what matters now self.runner_root = self.storage_root self.state = MindState() self._paths = self._init_paths(self.storage_root) self.memory_policy = (memory_policy or "selective").strip().lower() self.memory_keywords = memory_keywords if memory_keywords else list(DEFAULT_MEMORY_KEYWORDS) self.memory_enabled = self.memory_policy == "always" self._memory_loaded = False self._modules_loaded = [ {"name": "master-simulation.py", "version": "2.0"}, {"name": "4d-module.py", "version": "external_optional"}, {"name": "awareness-module.py", "version": "external_optional"}, {"name": "nova_consciousness_engine.py", "version": "external_optional"}, {"name": "emotions-module.py", "version": "external_optional"}, {"name": "life-module.py", "version": "external_optional"}, {"name": "creativity_module.py", "version": "external_optional"}, {"name": "evolution.py", "version": "external_optional"}, ] self._rng = random.Random(1337) def _init_paths(self, root: Path) -> Dict[str, Path]: root = _safe_resolve_dir(root) _safe_mkdir(root) memory_path = root / "nova_memory.jsonl" events_path = root / "nova_events.log" evo_root = root / "nova_evolution_store" evo_index = evo_root / "evolution_index.jsonl" evo_snapshots = evo_root / "snapshots" evo_quarantine = evo_root / "quarantine" _safe_mkdir(evo_root) _safe_mkdir(evo_snapshots) _safe_mkdir(evo_quarantine) # Create files if missing (append-only behavior) if not memory_path.exists(): _append_line(memory_path, _json_dumps({"type": "init", "ts_utc": _utc_iso(), "note": "created"})) if not events_path.exists(): _append_line(events_path, f"[{_utc_iso()}] init: created nova_events.log") if not evo_index.exists(): _append_line(evo_index, _json_dumps({"type": "init", "ts_utc": _utc_iso(), "cap_bytes": self.EVOLUTION_CAP_BYTES})) # Log resolved storage root _append_line(events_path, f"[{_utc_iso()}] storage_root={str(root)}") return { "storage_root": root, "memory": memory_path, "events": events_path, "evo_root": evo_root, "evo_index": evo_index, "evo_snapshots": evo_snapshots, "evo_quarantine": evo_quarantine, } # ------------------------- # Boot / persistence # ------------------------- def boot(self) -> None: self._log_event("boot", f"storage_root={self._paths['storage_root']}") if self.fourdllm_path is not None: self._log_event("boot", f"fourdllm_path={self.fourdllm_path}") if self.memory_enabled: self._load_memory_state() else: self._log_event("boot", f"memory_policy={self.memory_policy} (deferred)") if self.state.last_heartbeat_ts <= 0.0: self.state.last_heartbeat_ts = time.time() def _read_last_memory_entry(self) -> Optional[Dict[str, Any]]: try: with open(self._paths["memory"], "r", encoding="utf-8") as f: lines = f.read().splitlines() for line in reversed(lines[-200:]): line = line.strip() if not line: continue try: return json.loads(line) except Exception: continue except Exception: return None return None def _restore_state_from_dict(self, st: Dict[str, Any]) -> None: try: needs = st.get("needs", {}) aff = st.get("affect", {}) traits = st.get("traits", {}) wxyz = st.get("wxyz", {}) self.state.needs.energy = _clamp01(float(needs.get("energy", self.state.needs.energy))) self.state.needs.curiosity = _clamp01(float(needs.get("curiosity", self.state.needs.curiosity))) self.state.needs.safety = _clamp01(float(needs.get("safety", self.state.needs.safety))) self.state.needs.social = _clamp01(float(needs.get("social", self.state.needs.social))) self.state.needs.mastery = _clamp01(float(needs.get("mastery", self.state.needs.mastery))) self.state.affect.v = _clamp(float(aff.get("v", self.state.affect.v)), -1.0, 1.0) self.state.affect.a = _clamp(float(aff.get("a", self.state.affect.a)), -1.0, 1.0) self.state.affect.d = _clamp(float(aff.get("d", self.state.affect.d)), -1.0, 1.0) labels = aff.get("labels", self.state.affect.labels) if isinstance(labels, list): self.state.affect.labels = [str(x)[:64] for x in labels][:8] self.state.traits.creativity = _clamp01(float(traits.get("creativity", self.state.traits.creativity))) self.state.traits.warmth = _clamp01(float(traits.get("warmth", self.state.traits.warmth))) self.state.traits.bluntness = _clamp01(float(traits.get("bluntness", self.state.traits.bluntness))) self.state.traits.humor = _clamp01(float(traits.get("humor", self.state.traits.humor))) self.state.traits.formality = _clamp01(float(traits.get("formality", self.state.traits.formality))) self.state.wxyz.W = _clamp(float(wxyz.get("W", self.state.wxyz.W)), -1.0, 1.0) self.state.wxyz.X = _clamp(float(wxyz.get("X", self.state.wxyz.X)), -1.0, 1.0) self.state.wxyz.Y = _clamp(float(wxyz.get("Y", self.state.wxyz.Y)), -1.0, 1.0) self.state.wxyz.Z = _clamp(float(wxyz.get("Z", self.state.wxyz.Z)), -1.0, 1.0) self.state.mode = _clamp01(float(st.get("mode", self.state.mode))) self.state.omega = _clamp01(float(st.get("omega", self.state.omega))) self.state.mem_pressure = _clamp01(float(st.get("mem_pressure", self.state.mem_pressure))) self.state.loss_latch = _clamp01(float(st.get("loss_latch", self.state.loss_latch))) self.state.tick = int(st.get("tick", self.state.tick)) except Exception: pass # ------------------------- # Heartbeat # ------------------------- def heartbeat(self) -> None: now = time.time() if (now - self.state.last_heartbeat_ts) < self.HEARTBEAT_PERIOD_S: return dt = now - self.state.last_heartbeat_ts self.state.last_heartbeat_ts = now self._apply_homeostasis(dt_s=dt, intensity=0.08) self._apply_affect_decay(dt_s=dt, base_decay=0.04) self._update_mode_and_omega(user_text="", stakes=0.1, novelty=0.0, uncertainty=0.1) hb = { "type": "heartbeat", "ts_utc": _utc_iso(now), "dt_s": round(dt, 2), "tick": self.state.tick, "needs": asdict(self.state.needs), "affect": {"v": self.state.affect.v, "a": self.state.affect.a, "d": self.state.affect.d, "labels": self.state.affect.labels}, "mode": self.state.mode, "omega": self.state.omega, "storage_root": str(self._paths["storage_root"]), } if self.memory_enabled: _append_line(self._paths["memory"], _json_dumps(hb)) _append_line(self._paths["events"], f"[{_utc_iso(now)}] heartbeat dt={dt:.1f}s mode={self.state.mode:.2f} omega={self.state.omega:.2f}") self._enforce_evolution_cap() # ------------------------- # Main tick # ------------------------- def tick( self, user_text: str, llm_generate_fn: Optional[Callable[[str], str]] = None, ) -> Tuple[str, Dict[str, Any]]: user_text = (user_text or "").strip() user_hash = _hash_text(user_text) self._activate_memory_if_triggered(user_text) self.state.tick += 1 self.state.ts_utc = _utc_iso() self.heartbeat() if is_disallowed(user_text): reply = "I can’t help with that. If you want, tell me what you’re trying to accomplish and I’ll suggest a safe alternative." self._append_turn(user_text, reply, user_hash=user_hash, assistant_hash=_hash_text(reply), errors=["disallowed_request"]) return reply, {"refused": True} wants_diag = self._wants_diag(user_text) stakes = self._estimate_stakes(user_text) novelty = self._estimate_novelty(user_text) uncertainty = self._estimate_uncertainty(user_text) self._apply_event_deltas(user_text, stakes=stakes, novelty=novelty) self._apply_homeostasis(dt_s=1.0, intensity=0.12) self._apply_affect_decay(dt_s=1.0, base_decay=0.05) self._update_mode_and_omega(user_text, stakes=stakes, novelty=novelty, uncertainty=uncertainty) self._rotate_wxyz(stakes=stakes, novelty=novelty) if wants_diag: decision = { "mode": "exploration" if self.state.mode >= 0.5 else "verification", "why": self._decision_why(stakes, novelty, uncertainty), "constraints": ["truth_trace", "safe_by_default", "no_metric_fabrication"], } influence = [{"module": "master-simulation.py", "reason": "Math-driven state update + decision routing"}] diag = build_diag_v1(self.state, self._modules_loaded, influence, decision, errors=[]) out = json.dumps(diag, ensure_ascii=False, indent=2) self._append_turn(user_text, out, user_hash=user_hash, assistant_hash=_hash_text(out), errors=[]) return out, {"diag": True} control = self._control_header(user_text) if llm_generate_fn is None: reply = self._kernel_speak(user_text) self._append_turn(user_text, reply, user_hash=user_hash, assistant_hash=_hash_text(reply), errors=["no_llm_generate_fn"]) return reply, {"kernel_only": True} llm_prompt = control + "\n\nUSER:\n" + user_text + "\n\nASSISTANT:\n" assistant_text = (llm_generate_fn(llm_prompt) or "").strip() if not assistant_text: assistant_text = self._kernel_speak(user_text) self._append_turn(user_text, assistant_text, user_hash=user_hash, assistant_hash=_hash_text(assistant_text), errors=[]) return assistant_text, {"kernel_control": True} # ------------------------- # Mind math # ------------------------- def _apply_event_deltas(self, user_text: str, stakes: float, novelty: float) -> None: t = (user_text or "").lower() pos = 1.0 if any(w in t for w in ["awesome", "we did it", "nice", "love", "great", "wow", "excited", "happy"]) else 0.0 neg = 1.0 if any(w in t for w in ["sad", "upset", "angry", "depressed", "hate", "grief", "loss"]) else 0.0 dv = 0.10 * pos - 0.12 * neg da = 0.10 * novelty + 0.05 * stakes dd = 0.04 * (self.state.needs.mastery - 0.5) - 0.06 * neg if "grief" in t or "loss" in t: self.state.loss_latch = _clamp01(self.state.loss_latch + 0.10) dv -= 0.08 * self.state.loss_latch self.state.affect.v = _clamp(self.state.affect.v + dv, -1.0, 1.0) self.state.affect.a = _clamp(self.state.affect.a + da, -1.0, 1.0) self.state.affect.d = _clamp(self.state.affect.d + dd, -1.0, 1.0) self.state.needs.curiosity = _clamp01(self.state.needs.curiosity + 0.06 * novelty - 0.03 * stakes) self.state.needs.energy = _clamp01(self.state.needs.energy - 0.02 * stakes + 0.01 * pos) self.state.needs.safety = _clamp01(self.state.needs.safety - 0.03 * stakes + 0.02 * (1.0 - novelty)) self.state.needs.mastery = _clamp01(self.state.needs.mastery + 0.02 * (1.0 - stakes)) self.state.affect.labels = self._labels_from_vad(self.state.affect.v, self.state.affect.a, self.state.affect.d) def _apply_homeostasis(self, dt_s: float, intensity: float) -> None: k = _clamp01(intensity) * _clamp(dt_s / 60.0, 0.0, 2.0) bas = NeedsState() self.state.needs.energy = _clamp01(self.state.needs.energy + k * (bas.energy - self.state.needs.energy)) self.state.needs.curiosity = _clamp01(self.state.needs.curiosity + k * (bas.curiosity - self.state.needs.curiosity)) self.state.needs.safety = _clamp01(self.state.needs.safety + k * (bas.safety - self.state.needs.safety)) self.state.needs.social = _clamp01(self.state.needs.social + k * (bas.social - self.state.needs.social)) self.state.needs.mastery = _clamp01(self.state.needs.mastery + k * (bas.mastery - self.state.needs.mastery)) tb = TraitsState() kt = 0.25 * k self.state.traits.creativity = _clamp01(self.state.traits.creativity + kt * (tb.creativity - self.state.traits.creativity)) self.state.traits.warmth = _clamp01(self.state.traits.warmth + kt * (tb.warmth - self.state.traits.warmth)) self.state.traits.bluntness = _clamp01(self.state.traits.bluntness + kt * (tb.bluntness - self.state.traits.bluntness)) self.state.traits.humor = _clamp01(self.state.traits.humor + kt * (tb.humor - self.state.traits.humor)) self.state.traits.formality = _clamp01(self.state.traits.formality + kt * (tb.formality - self.state.traits.formality)) self.state.loss_latch = _clamp01(self.state.loss_latch - (0.002 * dt_s / 60.0)) def _apply_affect_decay(self, dt_s: float, base_decay: float) -> None: bv, ba, bd = 0.10, 0.15, 0.05 dec = _clamp01(base_decay) * _clamp(dt_s / 60.0, 0.0, 2.0) dec *= (1.0 - 0.65 * self.state.loss_latch) self.state.affect.v = _clamp(self.state.affect.v + dec * (bv - self.state.affect.v), -1.0, 1.0) self.state.affect.a = _clamp(self.state.affect.a + dec * (ba - self.state.affect.a), -1.0, 1.0) self.state.affect.d = _clamp(self.state.affect.d + dec * (bd - self.state.affect.d), -1.0, 1.0) self.state.affect.labels = self._labels_from_vad(self.state.affect.v, self.state.affect.a, self.state.affect.d) def _update_mode_and_omega(self, user_text: str, stakes: float, novelty: float, uncertainty: float) -> None: energy = self.state.needs.energy curiosity = self.state.needs.curiosity omega_raw = ( 1.25 * uncertainty + 1.10 * stakes + 0.55 * novelty - 0.85 * energy + 0.30 * (1.0 - self.state.needs.safety) + 0.35 * self.state.loss_latch ) self.state.omega = _clamp01(_sigmoid(omega_raw)) explore_drive = 0.55 * curiosity + 0.25 * _clamp01((self.state.affect.v + 1.0) / 2.0) + 0.20 * (1.0 - stakes) verify_pressure = self.state.omega mode = explore_drive * (1.0 - 0.85 * verify_pressure) self.state.mode = _clamp01(mode) self.state.mem_pressure = _clamp01(0.25 + 0.45 * stakes + 0.20 * self.state.loss_latch) def _rotate_wxyz(self, stakes: float, novelty: float) -> None: theta = 0.10 + 0.25 * novelty + 0.15 * stakes c = math.cos(theta) s = math.sin(theta) W, X, Y, Z = self.state.wxyz.W, self.state.wxyz.X, self.state.wxyz.Y, self.state.wxyz.Z W2 = c * W - s * X X2 = s * W + c * X Y2 = c * Y - s * Z Z2 = s * Y + c * Z eta = 0.05 + 0.08 * novelty gW = _softsign(self.state.affect.v) * (0.5 + self.state.mode) gX = _softsign(self.state.affect.a) * (0.5 + (1.0 - self.state.mode)) gY = _softsign(self.state.affect.d) * 0.6 gZ = _softsign(self.state.loss_latch) * 0.7 self.state.wxyz.W = _clamp(W2 + eta * gW, -1.0, 1.0) self.state.wxyz.X = _clamp(X2 + eta * gX, -1.0, 1.0) self.state.wxyz.Y = _clamp(Y2 + eta * gY, -1.0, 1.0) self.state.wxyz.Z = _clamp(Z2 + eta * gZ, -1.0, 1.0) # ------------------------- # Control header # ------------------------- def _control_header(self, user_text: str) -> str: mode = self.state.mode warmth = self.state.traits.warmth blunt = self.state.traits.bluntness creative = self.state.traits.creativity directives = [ "You are RomanAI: respond naturally like a human voice, but stay truthful.", "Follow user instructions unless unsafe or disallowed.", "If asked for diagnostics, output strict JSON only.", "Do not invent file paths, saved memory status, or internal metrics beyond what is provided in CONTROL_PACKET.", ] packet = { "type": "mind_control_packet_v1", "tick": self.state.tick, "time_utc": self.state.ts_utc, "storage_root": str(self._paths["storage_root"]), "tone": { "mode": "exploration" if mode >= 0.5 else "verification", "warmth": round(warmth, 2), "bluntness": round(blunt, 2), "creativity": round(creative, 2), "omega": round(self.state.omega, 2), "vad": [round(self.state.affect.v, 2), round(self.state.affect.a, 2), round(self.state.affect.d, 2)], }, } return "SYSTEM:\n" + "\n".join(directives) + "\n\nCONTROL_PACKET:\n" + json.dumps(packet, ensure_ascii=False, indent=2) def _kernel_speak(self, user_text: str) -> str: mode = "explore" if self.state.mode >= 0.5 else "verify" v = self.state.affect.v vibe = "steady" if abs(v) < 0.2 else ("upbeat" if v > 0 else "heavy") if mode == "verify": return f"Got you. I’m in a careful mode right now ({vibe}). What output format do you want?" return f"Alright — I’m feeling {vibe} and curious. What do you want to build next?" # ------------------------- # Estimates # ------------------------- def _estimate_stakes(self, user_text: str) -> float: t = (user_text or "").lower() if any(w in t for w in ["legal", "contract", "court", "danger", "harm", "suicide", "weapon"]): return 0.95 if any(w in t for w in ["money", "pay", "invoice", "deadline", "production"]): return 0.70 if any(w in t for w in ["build", "ship", "release", "final"]): return 0.55 return 0.25 def _estimate_novelty(self, user_text: str) -> float: t = user_text or "" caps = sum(1 for ch in t if ch.isupper()) ex = t.count("!") q = t.count("?") score = 0.10 + 0.03 * min(caps, 20) + 0.06 * min(ex, 10) + 0.04 * min(q, 10) if "prototype" in t.lower() or "final" in t.lower(): score += 0.10 return _clamp01(score) def _estimate_uncertainty(self, user_text: str) -> float: t = (user_text or "").lower() score = 0.20 if any(w in t for w in ["maybe", "not sure", "confused", "unclear", "idk", "guess"]): score += 0.35 if len(t) < 12: score += 0.10 return _clamp01(score) # ------------------------- # Labels # ------------------------- def _labels_from_vad(self, v: float, a: float, d: float) -> List[str]: labels: List[str] = [] if v > 0.35 and a > 0.35: labels.append("excited") elif v > 0.25: labels.append("positive") elif v < -0.35 and a > 0.20: labels.append("distressed") elif v < -0.25: labels.append("sad") if self.state.loss_latch > 0.30 and v < 0.10: labels.append("grief_tint") if d > 0.25: labels.append("confident") elif d < -0.25: labels.append("submissive") return labels[:4] # ------------------------- # Diagnostics trigger # ------------------------- def _wants_diag(self, user_text: str) -> bool: t = (user_text or "").lower() return ("diag_v1" in t) or ("diagnostics" in t) or ("strict json" in t) or (t.strip() == "diag") or (t.strip() == "/diag") def _decision_why(self, stakes: float, novelty: float, uncertainty: float) -> str: return f"stakes={stakes:.2f} | novelty={novelty:.2f} | uncertainty={uncertainty:.2f} | omega={self.state.omega:.2f} | mode={'exploration' if self.state.mode>=0.5 else 'verification'}" # ------------------------- # Persistence: turn logging # ------------------------- def _append_turn(self, user_text: str, assistant_text: str, user_hash: str, assistant_hash: str, errors: List[str]) -> None: entry = { "type": "turn", "ts_utc": self.state.ts_utc, "tick": self.state.tick, "user_hash": user_hash, "assistant_hash": assistant_hash, "user": user_text[:4000], "assistant": assistant_text[:8000], "state": self._state_dict_compact(), "errors": list(errors), "storage_root": str(self._paths["storage_root"]), } if self.memory_enabled: _append_line(self._paths["memory"], _json_dumps(entry)) mode = "explore" if self.state.mode >= 0.5 else "verify" _append_line(self._paths["events"], f"[{self.state.ts_utc}] turn tick={self.state.tick} mode={mode} omega={self.state.omega:.2f} user_hash={user_hash}") self._enforce_evolution_cap() def _state_dict_compact(self) -> Dict[str, Any]: return { "tick": self.state.tick, "needs": asdict(self.state.needs), "affect": {"v": self.state.affect.v, "a": self.state.affect.a, "d": self.state.affect.d, "labels": self.state.affect.labels}, "traits": asdict(self.state.traits), "wxyz": asdict(self.state.wxyz), "mode": self.state.mode, "omega": self.state.omega, "mem_pressure": self.state.mem_pressure, "loss_latch": self.state.loss_latch, } def _log_event(self, tag: str, msg: str) -> None: _append_line(self._paths["events"], f"[{_utc_iso()}] {tag}: {msg}") def _should_use_memory(self, user_text: str) -> bool: if self.memory_policy == "never": return False if self.memory_policy == "always": return True t = (user_text or "").lower() return any(k in t for k in self.memory_keywords) def _activate_memory_if_triggered(self, user_text: str) -> None: if not self._should_use_memory(user_text): return if not self.memory_enabled: self.memory_enabled = True self._log_event("memory", "activated_by_keywords") if not self._memory_loaded: self._load_memory_state() def _load_memory_state(self) -> None: last = self._read_last_memory_entry() if last and isinstance(last, dict) and last.get("type") == "turn": st = last.get("state") if isinstance(st, dict): self._restore_state_from_dict(st) self._log_event("memory", "restored_state=ok") else: self._log_event("memory", "restored_state=none") self._memory_loaded = True # ------------------------- # Evolution store (1GB cap) # ------------------------- def propose_evolution(self, proposal: Dict[str, Any]) -> None: entry = { "type": "proposal", "ts_utc": _utc_iso(), "tick": self.state.tick, "proposal": proposal, } _append_line(self._paths["evo_index"], _json_dumps(entry)) self._enforce_evolution_cap() def _enforce_evolution_cap(self) -> None: evo_root = self._paths["evo_root"] size = _folder_size_bytes(evo_root) if size <= self.EVOLUTION_CAP_BYTES: return protected = {self._paths["evo_index"].resolve()} files = _list_files_sorted_by_mtime(evo_root) deleted = 0 for fp in files: try: if fp.resolve() in protected: continue sz = fp.stat().st_size fp.unlink(missing_ok=True) deleted += sz except Exception: continue size = _folder_size_bytes(evo_root) if size <= self.EVOLUTION_CAP_BYTES: break if deleted > 0: self._log_event("evolution_cap", f"trimmed_bytes={deleted}") # End of file