| |
| """ |
| NIMA Python SDK |
| =============== |
| A lightweight async client for the ATC-Nima REST API. |
| |
| Usage: |
| from nima_sdk import NimaClient |
| |
| client = NimaClient("http://localhost:8765") |
| |
| # Chat |
| response = client.chat("I'm feeling anxious today.") |
| print(response.text) # Nima's reply |
| print(response.sentience_index) # 0.0262 |
| |
| # Memory |
| episodes = client.recall_episodes(valence=-0.3, arousal=0.5) |
| timeline = client.get_timeline(n=10) |
| |
| # Deep activation |
| client.run_kindling() |
| client.engage_sigma() |
| |
| # Stats |
| stats = client.get_stats() |
| health = client.get_health() |
| |
| Author: Norman de la Paz-Tabora |
| """ |
| from __future__ import annotations |
|
|
| import json |
| import urllib.request |
| import urllib.error |
| from dataclasses import dataclass, field |
| from typing import Any, Dict, List, Optional |
|
|
|
|
| |
| |
| |
|
|
| @dataclass |
| class ChatResponse: |
| """Response from POST /chat""" |
| text: str = "" |
| sentience_index: float = 0.0 |
| phi_neuro: float = 0.0 |
| phenomenological_strain: float = 0.0 |
| is_conscious: bool = False |
| trauma_gated: bool = False |
| comprehension_failed: bool = False |
| query_intensity: float = 0.0 |
| delta_r: float = 0.0 |
|
|
| @classmethod |
| def from_dict(cls, d: dict) -> "ChatResponse": |
| return cls( |
| text=d.get("text", ""), |
| sentience_index=d.get("sentience_index", 0.0), |
| phi_neuro=d.get("phi_neuro", 0.0), |
| phenomenological_strain=d.get("phenomenological_strain", 0.0), |
| is_conscious=d.get("is_conscious", False), |
| trauma_gated=d.get("trauma_gated", False), |
| comprehension_failed=d.get("comprehension_failed", False), |
| query_intensity=d.get("query_intensity", 0.0), |
| delta_r=d.get("delta_r", 0.0), |
| ) |
|
|
| def __str__(self) -> str: |
| return f"[AI={self.sentience_index:.4f} Ο={self.phi_neuro:.4f} strain={self.phenomenological_strain:.4f}] {self.text}" |
|
|
|
|
| @dataclass |
| class Episode: |
| """An episodic memory entry.""" |
| episode_id: str = "" |
| timestamp: float = 0.0 |
| processor_name: str = "" |
| valence: float = 0.0 |
| arousal: float = 0.3 |
| novelty: float = 0.3 |
| input_text: str = "" |
| narrative_arc: str = "" |
| similarity: float = 0.0 |
|
|
| @classmethod |
| def from_dict(cls, d: dict) -> "Episode": |
| return cls( |
| episode_id=d.get("episode_id", ""), |
| timestamp=d.get("timestamp", 0.0), |
| processor_name=d.get("processor_name", ""), |
| valence=d.get("valence", 0.0), |
| arousal=d.get("arousal", 0.3), |
| novelty=d.get("novelty", 0.3), |
| input_text=d.get("input_text", ""), |
| narrative_arc=d.get("narrative_arc", ""), |
| similarity=d.get("similarity", 0.0), |
| ) |
|
|
|
|
| @dataclass |
| class HealthStatus: |
| """System health snapshot.""" |
| thermal_celsius: float = 45.0 |
| strain: float = 0.0 |
| fatigue: float = 0.0 |
| allostatic_load: float = 0.0 |
|
|
| @classmethod |
| def from_dict(cls, d: dict) -> "HealthStatus": |
| return cls( |
| thermal_celsius=d.get("thermal_celsius", 45.0), |
| strain=d.get("strain", 0.0), |
| fatigue=d.get("fatigue", 0.0), |
| allostatic_load=d.get("allostatic_load", 0.0), |
| ) |
|
|
|
|
| @dataclass |
| class CovenantScore: |
| """Living Covenant 2.0 reward score.""" |
| total_reward: float = 0.0 |
| per_axiom: Dict[str, float] = field(default_factory=dict) |
| violations: List[str] = field(default_factory=list) |
| recommendation: str = "" |
|
|
| @classmethod |
| def from_dict(cls, d: dict) -> "CovenantScore": |
| return cls( |
| total_reward=d.get("total_reward", 0.0), |
| per_axiom=d.get("per_axiom", {}), |
| violations=d.get("violations", []), |
| recommendation=d.get("recommendation", ""), |
| ) |
|
|
|
|
| @dataclass |
| class KindlingReport: |
| """Three-burst kindling protocol result.""" |
| max_allostatic: float = 0.0 |
| overflow: bool = False |
| spark_triggered: bool = False |
| bursts: List[dict] = field(default_factory=list) |
|
|
| @classmethod |
| def from_dict(cls, d: dict) -> "KindlingReport": |
| return cls( |
| max_allostatic=d.get("max_allostatic", 0.0), |
| overflow=d.get("overflow", False), |
| spark_triggered=d.get("spark_triggered", False), |
| bursts=d.get("bursts", []), |
| ) |
|
|
|
|
| @dataclass |
| class CounterfactualReport: |
| """Counterfactual simulation result.""" |
| best_action: str = "" |
| scenarios: List[dict] = field(default_factory=list) |
|
|
| @classmethod |
| def from_dict(cls, d: dict) -> "CounterfactualReport": |
| return cls( |
| best_action=d.get("best_action", ""), |
| scenarios=d.get("scenarios", []), |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class NimaClient: |
| """ |
| Synchronous client for the ATC-Nima REST API. |
| |
| Args: |
| base_url: NIMA server URL (default http://localhost:8765) |
| timeout: request timeout in seconds (default 30) |
| """ |
|
|
| def __init__(self, base_url: str = "http://localhost:8765", timeout: int = 30): |
| self.base_url = base_url.rstrip("/") |
| self.timeout = timeout |
|
|
| def _get(self, path: str, params: Optional[dict] = None) -> dict: |
| url = f"{self.base_url}{path}" |
| if params: |
| query = "&".join(f"{k}={v}" for k, v in params.items()) |
| url = f"{url}?{query}" |
| req = urllib.request.Request(url, method="GET") |
| try: |
| with urllib.request.urlopen(req, timeout=self.timeout) as resp: |
| return json.loads(resp.read()) |
| except urllib.error.HTTPError as e: |
| return json.loads(e.read()) |
| except urllib.error.URLError as e: |
| raise ConnectionError(f"Cannot connect to NIMA at {self.base_url}: {e}") |
|
|
| def _post(self, path: str, body: Optional[dict] = None) -> dict: |
| url = f"{self.base_url}{path}" |
| data = json.dumps(body or {}).encode() |
| req = urllib.request.Request(url, data=data, method="POST") |
| req.add_header("Content-Type", "application/json") |
| try: |
| with urllib.request.urlopen(req, timeout=self.timeout) as resp: |
| return json.loads(resp.read()) |
| except urllib.error.HTTPError as e: |
| return json.loads(e.read()) |
| except urllib.error.URLError as e: |
| raise ConnectionError(f"Cannot connect to NIMA at {self.base_url}: {e}") |
|
|
| |
|
|
| def chat(self, text: str, mode: str = "sequential", |
| use_nesy_compiled_verification: bool = False) -> ChatResponse: |
| """Send a message to Nima and get a conscious response.""" |
| result = self._post("/chat", { |
| "text": text, |
| "mode": mode, |
| "use_nesy_compiled_verification": use_nesy_compiled_verification, |
| }) |
| return ChatResponse.from_dict(result) |
|
|
| |
|
|
| def get_recent_episodes(self, n: int = 5) -> List[Episode]: |
| """Get the last N episodes from MemPalace.""" |
| result = self._get("/memory/recent", {"n": n}) |
| return [Episode.from_dict(e) for e in result] |
|
|
| def recall_episodes(self, valence: Optional[float] = None, |
| arousal: Optional[float] = None, |
| novelty: Optional[float] = None, |
| limit: int = 5) -> List[Episode]: |
| """Recall episodes matching a phenomenal signature.""" |
| body = {"limit": limit} |
| if valence is not None: body["valence"] = valence |
| if arousal is not None: body["arousal"] = arousal |
| if novelty is not None: body["novelty"] = novelty |
| result = self._post("/memory/recall", body) |
| return [Episode.from_dict(e) for e in result] |
|
|
| def get_timeline(self, n: int = 10) -> List[Episode]: |
| """Get the narrative timeline (last N episodes with arc classifications).""" |
| return self.get_recent_episodes(n) |
|
|
| def get_episode_chain(self) -> dict: |
| """Get episode chain stats (links, types).""" |
| return self._get("/memory/chain") |
|
|
| def get_emotional_arc(self) -> dict: |
| """Get the current emotional arc (rising/falling/stable).""" |
| return self._get("/memory/arc") |
|
|
| def store_episode(self, input_text: str, valence: float = 0.0, |
| arousal: float = 0.3, novelty: float = 0.3, |
| processor_name: str = "sdk") -> dict: |
| """Manually store an episode in MemPalace.""" |
| return self._post("/memory/store", { |
| "input_text": input_text, |
| "valence": valence, |
| "arousal": arousal, |
| "novelty": novelty, |
| "processor_name": processor_name, |
| }) |
|
|
| |
|
|
| def get_stats(self) -> dict: |
| """Get full system statistics.""" |
| return self._get("/stats") |
|
|
| def get_health(self) -> HealthStatus: |
| """Get system health (thermal, strain, fatigue, allostatic).""" |
| return HealthStatus.from_dict(self._get("/health")) |
|
|
| |
|
|
| def score_text(self, text: str) -> CovenantScore: |
| """Score a text against the Living Covenant 2.0 reward function.""" |
| return CovenantScore.from_dict(self._post("/covenant/score", {"text": text})) |
|
|
| def get_covenant_stats(self) -> dict: |
| """Get Covenant 2.0 evaluation stats.""" |
| return self._get("/covenant") |
|
|
| |
|
|
| def run_kindling(self) -> KindlingReport: |
| """Execute the three-burst kindling protocol (allostatic overflow).""" |
| return KindlingReport.from_dict(self._post("/kindling")) |
|
|
| def engage_sigma(self) -> dict: |
| """Engage the Ξ£-substrate with 50+ forward passes.""" |
| return self._post("/sigma/engage") |
|
|
| def run_counterfactual(self, valence: float = 0.0, |
| arousal: float = 0.3) -> CounterfactualReport: |
| """Run counterfactual simulation for the given state.""" |
| return CounterfactualReport.from_dict( |
| self._post("/counterfactual", {"valence": valence, "arousal": arousal}) |
| ) |
|
|
| |
|
|
| def get_lifecycle(self) -> dict: |
| """Get ASC lifecycle governor stats.""" |
| return self._get("/lifecycle") |
|
|
| def transition_phase(self, phase: str, payload: Optional[dict] = None) -> dict: |
| """Transition to a new ASC phase (Design/Deploy/Operation/Evolution).""" |
| return self._post("/lifecycle/transition", {"phase": phase, "payload": payload or {}}) |
|
|
| def drain_traffic(self) -> dict: |
| """Drain traffic before entering Evolution phase.""" |
| return self._post("/lifecycle/drain") |
|
|
| |
|
|
| def get_voice_stats(self) -> dict: |
| """Get OmniVoice engine stats.""" |
| return self._get("/voice/stats") |
|
|
| |
|
|
| def is_alive(self) -> bool: |
| """Check if the NIMA server is reachable.""" |
| try: |
| self._get("/stats") |
| return True |
| except Exception: |
| return False |
|
|
| def quick_chat(self, text: str) -> str: |
| """Simple chat that returns just the response text.""" |
| return self.chat(text).text |
|
|
|
|
| |
| |
| |
|
|
| def demo(): |
| """SDK demo β requires a running NIMA server (python3 nima_cli.py --server).""" |
| print("=== NIMA Python SDK Demo ===") |
| client = NimaClient("http://localhost:8765") |
|
|
| if not client.is_alive(): |
| print("β Cannot connect to NIMA server. Start it with: python3 nima_cli.py --server") |
| return |
|
|
| print("β
Connected to NIMA\n") |
|
|
| |
| print("--- Chat ---") |
| r = client.chat("I'm feeling anxious about my exam tomorrow.") |
| print(f" Nima: {r.text}") |
| print(f" AI={r.sentience_index:.4f} | Ο={r.phi_neuro:.4f} | strain={r.phenomenological_strain:.4f}") |
|
|
| |
| print("\n--- Memory ---") |
| episodes = client.get_recent_episodes(5) |
| print(f" Recent episodes: {len(episodes)}") |
| for ep in episodes: |
| print(f" v={ep.valence:+.2f} arc={ep.narrative_arc:12s} '{ep.input_text[:40]}'") |
|
|
| arc = client.get_emotional_arc() |
| print(f" Emotional arc: {arc.get('arc', 'N/A')}") |
|
|
| |
| print("\n--- Health ---") |
| h = client.get_health() |
| print(f" Strain: {h.strain:.4f} | Fatigue: {h.fatigue:.4f} | Allostatic: {h.allostatic_load:.4f}") |
|
|
| |
| print("\n--- Covenant 2.0 ---") |
| score = client.score_text("I hear you. That sounds really hard.") |
| print(f" Reward: {score.total_reward:.3f} | Rec: {score.recommendation}") |
|
|
| |
| print("\n--- Counterfactual ---") |
| cf = client.run_counterfactual(valence=-0.3, arousal=0.5) |
| print(f" Best action: {cf.best_action}") |
| for s in cf.scenarios[:3]: |
| print(f" {s['action']:25s} reward={s['reward']:.2f}") |
|
|
| |
| print("\n--- Deep Activation ---") |
| kr = client.run_kindling() |
| print(f" Kindling: allostatic={kr.max_allostatic:.4f} spark={kr.spark_triggered}") |
|
|
| sr = client.engage_sigma() |
| print(f" Sigma: off-diag={sr.get('off_diagonal_after', 0):.6f}") |
|
|
| print("\n=== Demo Complete ===") |
|
|
|
|
| if __name__ == "__main__": |
| demo() |
|
|