diff --git a/.env.example b/.env.example index b66ebef..09cf25f 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,9 @@ SECRET_KEY=change-this-to-a-secure-random-string DEBUG=false LOG_LEVEL=INFO RUH_ENABLED=true +# al-Insān faculties: when true, Baṣīra/Hawā/Ḥikma actively gate & redirect the +# agent loop (high-severity lower-pull → forceful restraint). Default advisory. +DEEP_FACULTY_LOOP=false # ===== SERVER ===== HOST=0.0.0.0 PORT=8000 diff --git a/CLAUDE.md b/CLAUDE.md index b183d4c..e871af3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,86 +1,129 @@ -# MIZAN - Agentic Personal AI +# MIZAN (ميزان) - Agentic Personal AI + +> Quranic Cognitive Architecture (QCA) for human-like reasoning. Version 3.0.0. ## Quick Reference -- **Backend**: Python 3.11+, FastAPI, Pydantic, aiosqlite -- **Frontend**: React 18, TypeScript, Vite, Tailwind CSS -- **AI Providers**: Anthropic, OpenAI, OpenRouter, Ollama -- **Database**: SQLite (via aiosqlite) -- **Infra**: Docker Compose, Nginx +- **Backend**: Python 3.11+ (FastAPI, Pydantic v2, aiosqlite, Click CLI) — fully async +- **Frontend**: React 18, TypeScript, Vite, Tailwind CSS, react-router-dom, recharts +- **AI Providers**: Anthropic, OpenAI, OpenRouter, Ollama, + in-house **Ruh** model +- **Database**: SQLite via aiosqlite (default `data/mizan.db`) +- **Infra**: Docker Compose, Nginx; optional Ollama + ChromaDB profiles +- **ML**: PyTorch (optional `ml` extra) for the Ruh language model ## Commands ```bash -make setup # First-time: install deps, setup frontend, create .env -make dev # Start backend + frontend dev servers -make test # Run pytest -make check # Lint + typecheck + test (all checks) -make format # Auto-format with ruff -make docker # Docker Compose up (build + detach) +make setup # First-time: install deps (.[dev]), pre-commit, frontend, .env +make dev # Start backend + frontend dev servers (runs ./start.sh start) +make serve # Backend only (uvicorn, --reload, port 8000) +make cli # Interactive CLI chat (mizan chat) +make test # pytest tests/ -v +make test-cov # pytest with coverage (term + html) +make check # lint + typecheck + test (full pipeline) +make lint # ruff check backend/ tests/ +make format # ruff format + ruff check --fix +make typecheck # mypy backend/ --ignore-missing-imports +make docker # docker compose up -d --build +make docker-full # + ollama and vector (ChromaDB) profiles make docker-down # Stop Docker -make clean # Remove build artifacts +make clean # Remove build/cache artifacts +make version # Print VERSION +make release-patch # Bump + changelog + tag + push (also -minor/-major/-beta/-rc) ``` +**CLI** (`mizan `, entry point `backend.cli:main`): `serve`, `chat`, `status`, `setup`, `doctor [--check|--fix]`, `version`. + ## Key Files -- `backend/api/main.py` - FastAPI app, all REST + WebSocket routes -- `backend/agents/base.py` - Base agent with agentic ReAct loop -- `backend/providers.py` - LLM provider abstraction (Anthropic/OpenRouter/OpenAI/Ollama) -- `backend/settings.py` - Pydantic settings from .env -- `backend/cli.py` - CLI entry point (mizan serve, mizan doctor) -- `frontend/src/App.tsx` - React router + theme provider -- `docker-compose.yml` - Dev deployment -- `Makefile` - All project commands +- `backend/api/main.py` — FastAPI app, ~80 REST routes + WebSocket (single large module, ~3.5k lines) +- `backend/api/commands.py` — Slash/command handlers used by chat +- `backend/agents/base.py` — `BaseAgent`: agentic **ReAct loop**, tool dispatch, Nafs evolution, self-healing +- `backend/providers.py` — LLM provider abstraction (Anthropic/OpenRouter/OpenAI/Ollama auto-routing) +- `backend/providers_ruh.py` — `RuhModelProvider` adapter for the local Ruh model +- `backend/settings.py` — Pydantic settings (reads `.env`) +- `backend/cli.py` — Click CLI entry point +- `backend/doctor.py` — Environment diagnostics (`mizan doctor`) +- `frontend/src/App.tsx` — React router + theme provider; pages under `frontend/src/pages/` +- `ruh_model/` — Standalone Arabic root-morphology transformer (see `ruh_model/README.md`) +- `Makefile`, `pyproject.toml`, `docker-compose.yml`, `docker-compose.prod.yml` ## Architecture -7-layer Quranic Cognitive Architecture (QCA): +7-layer **Quranic Cognitive Architecture (QCA)**: | Layer | Module | Purpose | |-------|--------|---------| -| 1-2 | `backend/perception/` | Sensory input (vision, voice, text) | -| 3 | `backend/qca/engine.py` | Cognitive integration | -| 4 | `backend/memory/` | Hierarchical memory (Dhikr, Masalik) | -| 5 | `backend/reasoning/` | ReAct reasoning with self-correction | -| 6 | `backend/agents/` | Autonomous agents with Nafs levels | -| 7 | `backend/core/` | Principles (Qalb, Ihsan, Tawbah, Sabr) | - -Cross-cutting: `backend/security/` (Wali + Izn), `backend/gateway/` (channels), `backend/skills/` (capabilities) +| 1-2 | `backend/perception/` | Sensory input: `vision`, `voice`, `nutq` (speech), `basirah` (insight) | +| 3 | `backend/qca/` | Cognitive integration: `engine`, `answer_engine`, `yaqin_engine` (certainty), `roots`, `cognitive_methods` | +| 4 | `backend/memory/` | Hierarchical memory: `dhikr`, `masalik`, `memory_pyramid`, `lawh_mahfuz`, `living_memory`, `knowledge_graph`, `vector_store` | +| 5 | `backend/reasoning/` | ReAct reasoning: `aql_engine`, `causal_engine`, `planner`, `context_manager` | +| 6 | `backend/agents/` | Autonomous agents with **Nafs** levels: `base`, `specialized`, `khalifah`, `shura_council`, `federation`, `perpetual_rotation` | +| 7 | `backend/core/` | Principles & cognition: `qalb`, `ihsan`, `tawbah`, `sabr`, `shukr`, `tawakkul`, `fitrah`, `fuad`, `lubb`, `basira`, `hawa`, `hikmah`, `hidayah`, `nafs_triad`, `ruh_engine`, `dream_engine`, `creativity`, `imagination`, `self_healing`, plus `plugins`, `hooks`, `middleware`, `events` | + +**Cross-cutting subsystems:** + +- `backend/security/` — `auth` (JWT), `wali` (rate limiting / guardian), `izn` (permissions), `vault` (API key storage), `validation` (input sanitization) +- `backend/gateway/` — External channels: `bab` (entry), `router`, `channels/` +- `backend/skills/` — Capability system: `base`, `registry`, `builtin/` +- `backend/router/` — `intelligent_router`: provider/model selection +- `backend/task_queue/` — Async background work: `task_queue`, `worker`, `priorities` +- `backend/automation/` — Scheduled/triggered jobs: `qadr` (scheduler), `triggers`, `webhooks` +- `backend/learner/` — `ruh_learner`, `data_export`: training-data capture from interactions +- `backend/knowledge/` — `ingest`: document/source ingestion (RAG) +- `backend/network/` — `ummah`: agent federation / peer discovery +- `backend/cognitive/` — `thinking_stream`: real-time reasoning trace streaming +- `plugins/` — Drop-in plugins (`plugin.json` + `main.py`); see `request_logger`, `hello_world` + +### API surface (groups in `backend/api/main.py`) +`/api/auth/*`, `/api/agents/*`, `/api/tasks`, `/api/chat`, `/api/plan/*`, `/api/memory/*`, `/api/perception/*`, `/api/knowledge/*`, `/api/providers/*`, `/api/security/*`, `/api/channels/*`, `/api/automation/*`, `/api/skills/*`, `/api/gateway/*`, `/api/nafs/*`, `/api/yaqin/*`, `/api/qalb/*`, `/api/federation/*`, `/api/ruh/*`, `/api/queue/*`, `/api/learner/*`, `/api/training/*`, `/api/plugins/*`, `/api/events`, `/api/doctor`, `/api/settings`. Public versioned API under `/api/v1/*` (root-analyze, tokenize, q28-features). WebSocket at `/ws/{client_id}`. + +### Ruh model (`ruh_model/`) +Pure-PyTorch transformer operating in **root-space** (`(root_id, pattern_id)` pairs) instead of BPE tokens. Subpackages: `tokenizer` (Bayan), `embedding` (ISM), `attention` (Qalb), `layers`, `loss` (Mizan), `training`, `benchmarks`, `configs`. Entry scripts `train.py` / `train_full.py`, config in `config.py`. Wired into the backend via `backend/providers_ruh.py` and `/api/ruh/*` + `/api/training/*` endpoints. Requires the `ml` optional dependency group. ## Key Patterns -- All backend I/O is async (async/await) -- Pydantic models for all API request/response schemas -- JWT auth on all endpoints; Wali rate-limiting; Izn permission system -- WebSocket at /ws/{client_id} for real-time streaming -- Arabic-inspired naming: Dhikr=memory, Wali=guardian, Izn=permission, Nafs=self, Qalb=heart +- **All backend I/O is async** (`async`/`await`). +- Pydantic models for every API request/response schema. +- JWT auth on endpoints; **Wali** rate-limiting; **Izn** permission gating before tool execution. +- Agents run a real ReAct loop (`BaseAgent.think` → `_agentic_loop`), not single-shot calls; max iterations scale with **Nafs** level. Tools execute through `_execute_tool_safe` with input validation and self-healing (auto-install missing packages). +- **Nafs** levels (7-stage) evolve via `evolve_nafs()` / `DevelopmentalGate` based on success rate, task count, and hikmah. +- Real-time streaming via WebSocket and the `cognitive/thinking_stream` module. +- **Arabic-inspired naming** (consistency matters): Dhikr=memory, Masalik=memory paths, Wali=guardian, Izn=permission, Nafs=self/ego, Qalb=heart, Fuad=conviction, Lubb=metacognition, Basira=insight/discernment, Hawa=lower-pull/safety gate, Hikmah=wisdom, Ihsan=excellence, Tawbah=self-correction, Sabr=patience, Shura=council, Khalifah=steward agent, Yaqin=certainty, Ruh=spirit/model, Qadr=scheduling, Ummah=network, Bab=gateway. +- **al-Insān human-model** (`docs/quranic_human_model.md`): the Qur'anic anthropology MIZAN implements. Faculties (`basira`, `hawa`, `hikmah`, …) run inside `BaseAgent.execute()` and inject guidance into the system prompt via `self._faculty_guidance`. Set `DEEP_FACULTY_LOOP=true` to let them actively gate/redirect (default: advisory only). ## Environment Setup -1. `cp .env.example .env` and set at least one API key -2. `make install-dev` (or `make setup` for full setup including frontend) -3. Required: `ANTHROPIC_API_KEY` or `OPENROUTER_API_KEY` -4. Required: `SECRET_KEY` (any random string) +1. `cp .env.example .env` and set at least one API key. +2. `make install-dev` (or `make setup` for full setup including frontend). +3. Required: `ANTHROPIC_API_KEY` **or** `OPENROUTER_API_KEY`. +4. Required: `SECRET_KEY` (any random string). +5. Optional: channel tokens (`TELEGRAM_BOT_TOKEN`, `DISCORD_BOT_TOKEN`, `SLACK_APP_TOKEN`, `WHATSAPP_TOKEN`), `OLLAMA_URL`, `RUH_ENABLED`, email settings. + +**Install extras** (`pip install -e ".[]"`): `dev`, `all`, `channels`, `vector` (ChromaDB), `voice` (ElevenLabs), `ml` (PyTorch + Ruh deps). ## Testing -- pytest with asyncio auto mode -- Tests in tests/ (comprehensive: agents, API, memory, QCA, security, e2e) -- Run `make check` for full lint + typecheck + test pipeline -- Pre-commit hook runs ruff on commit +- pytest with asyncio auto mode; tests in `tests/` (agents, API, memory, QCA, security, router, task_queue, learner, e2e — many `*_comprehensive.py`). +- Ruh model has its own tests in `ruh_model/tests/`. +- Run `make check` for the full lint + typecheck + test pipeline before pushing. +- Pre-commit hook runs ruff (`.pre-commit-config.yaml`). +- CI: `.github/workflows/` — `ci.yml`, `pr-check.yml`, `release.yml`, `auto-release.yml`, `docs.yml`. ## Security Notes -- Never edit .env directly (hook blocks this) - use .env.example as template -- API keys managed via backend/security/vault.py -- Input validation in backend/security/validation.py -- All user input sanitized before processing +- Never edit `.env` directly (a hook may block this) — use `.env.example` as the template. +- API keys managed via `backend/security/vault.py`. +- Input validation/sanitization in `backend/security/validation.py`; all user input sanitized before processing. +- Tool calls pass through `izn` permission checks; rate limits enforced by `wali`. ## Gotchas -- Docker reads .env for compose variable substitution - inline comments like `KEY=value # comment` get parsed as part of the value -- Claude models use Anthropic only if `ANTHROPIC_API_KEY` starts with `sk-ant-`; otherwise auto-route through OpenRouter -- DEFAULT_MODEL env var controls agent model (fallback: `claude-sonnet-4-20250514`) — set it in .env -- Backend uses --reload in Docker, so local file changes in backend/ take effect automatically -- .env values are NOT auto-loaded into os.environ - pydantic-settings reads them but providers.py uses load_dotenv() -- `.claude/` and `.claude.local.md` are gitignored — local Claude Code config won't be committed +- Docker reads `.env` for compose variable substitution — inline comments like `KEY=value # comment` get parsed as part of the value. +- Claude models use Anthropic only if `ANTHROPIC_API_KEY` starts with `sk-ant-`; otherwise they auto-route through OpenRouter. +- `DEFAULT_MODEL` controls the agent model (fallback: `claude-sonnet-4-20250514`) — set it in `.env`. +- Backend runs with `--reload`, so local edits in `backend/` take effect automatically. +- `.env` values are NOT auto-loaded into `os.environ` — pydantic-settings reads them, but `providers.py` calls `load_dotenv()`. +- `.claude/` and `.claude.local.md` are gitignored — local Claude Code config won't be committed. +- The Ruh model and `ml` extra require PyTorch; `/api/ruh/*` and training endpoints are inert unless `RUH_ENABLED=true` and the model path is configured. +- `backend/api/main.py` is a single large module — add routes there alongside existing groups rather than creating parallel routers. diff --git a/backend/agents/base.py b/backend/agents/base.py index 952fed7..5b4e754 100644 --- a/backend/agents/base.py +++ b/backend/agents/base.py @@ -23,21 +23,45 @@ import time import uuid from collections.abc import AsyncGenerator, Callable -from datetime import UTC, datetime from typing import Any import httpx +from agents.perpetual_rotation import PerpetualRotation +from agents.shura_council import ShuraCouncil + +# al-Insan faculty modules — discernment, restraint, wisdom +from core.basira import BasiraEngine, InsightLevel +from core.creativity import IbdaCreativityEngine +from core.developmental_stages import DevelopmentalGate +from core.dream_engine import ManamDreamEngine + +# QALB-7 architecture modules — core +from core.fitrah import FitrahSystem +from core.fuad import FuadEngine +from core.hawa import HawaDetector +from core.hikmah import HikmahEngine from core.ihsan import IhsanMode +from core.imagination import TaswirImaginationEngine +from core.lubb import LubbEngine +from core.nafs_triad import NafsTriad + +# QALB-7 extension modules — parallel, healing, creativity, imagination, dreams +from core.parallel_agents import QalbParallelScheduler, SkillAutomationTransfer from core.qalb import EmotionalState, QalbEngine +from core.qalb_processor import QalbProcessor # Core Quranic systems integration from core.ruh_engine import RuhEngine from core.sabr import SabrEngine +from core.self_healing import LawwamaHealingSystem from core.shukr import ShukrSystem from core.tawbah import TawbahProtocol + +# Living Memory + 24/7 Multi-Agent Collaboration +from memory.living_memory import LivingMemorySystem from providers import create_provider, get_default_model, normalize_model_for_provider -from qca.cognitive_methods import IjmaEngine, select_method +from qca.cognitive_methods import CognitiveMethod, IjmaEngine, select_method from qca.engine import QCAEngine from qca.yaqin_engine import YaqinEngine from security.validation import ( @@ -46,26 +70,6 @@ validate_url, ) -# QALB-7 architecture modules — core -from core.fitrah import FitrahSystem -from core.nafs_triad import NafsTriad -from core.qalb_processor import QalbProcessor -from core.lubb import LubbEngine -from core.fuad import FuadEngine -from core.developmental_stages import DevelopmentalGate - -# QALB-7 extension modules — parallel, healing, creativity, imagination, dreams -from core.parallel_agents import QalbParallelScheduler, SkillAutomationTransfer -from core.self_healing import LawwamaHealingSystem -from core.imagination import TaswirImaginationEngine, ImaginationMode -from core.creativity import IbdaCreativityEngine -from core.dream_engine import ManamDreamEngine - -# Living Memory + 24/7 Multi-Agent Collaboration -from memory.living_memory import LivingMemorySystem -from agents.shura_council import ShuraCouncil -from agents.perpetual_rotation import PerpetualRotation - logger = logging.getLogger("mizan.agent") @@ -160,26 +164,39 @@ def __init__( self.cognitive = IjmaEngine() # Cognitive reasoning methods # QALB-7 layer additions - self.fitrah = FitrahSystem() # Innate ethical BIOS (immutable axioms) - self.nafs_triad = NafsTriad() # Three-voice consciousness deliberation - self.qalb_processor = QalbProcessor() # Cardiac oscillation → LLM params - self.lubb = LubbEngine() # Metacognition: compress, cohere, debias - self.fuad = FuadEngine() # Conviction formation + confidence scoring - self.dev_gate = DevelopmentalGate() # Capability gating by nafs_level - self._nafs_approach: str = "" # Current dominant Nafs voice instruction + self.fitrah = FitrahSystem() # Innate ethical BIOS (immutable axioms) + self.nafs_triad = NafsTriad() # Three-voice consciousness deliberation + self.qalb_processor = QalbProcessor() # Cardiac oscillation → LLM params + self.lubb = LubbEngine() # Metacognition: compress, cohere, debias + self.fuad = FuadEngine() # Conviction formation + confidence scoring + self.dev_gate = DevelopmentalGate() # Capability gating by nafs_level + self._nafs_approach: str = "" # Current dominant Nafs voice instruction + + # al-Insan faculties — make the human-model steer the loop + self.basira = BasiraEngine() # Insight + self-witnessing (12:108, 75:14) + self.hawa = HawaDetector() # Lower-pull / adversarial restraint (25:43, 79:40) + self.hikmah_engine = HikmahEngine() # Wisdom distillation → applicable counsel (2:269) + self._faculty_guidance: str = "" # Per-task guidance injected into the system prompt + # When enabled, faculties also actively gate/redirect (default: advisory only) + self.deep_faculty_loop = os.getenv("DEEP_FACULTY_LOOP", "").lower() in ( + "1", + "true", + "yes", + "on", + ) # QALB-7 extension modules - self.parallel_scheduler = QalbParallelScheduler() # Multi-stream parallel processing - self.skill_automation = SkillAutomationTransfer() # Cerebellar skill automation - self.self_healer = LawwamaHealingSystem() # 4-level self-repair - self.imagination = TaswirImaginationEngine() # Mental simulation + counterfactuals - self.creativity = IbdaCreativityEngine() # 5-mode creativity engine - self.dream_engine = ManamDreamEngine() # Offline memory consolidation + self.parallel_scheduler = QalbParallelScheduler() # Multi-stream parallel processing + self.skill_automation = SkillAutomationTransfer() # Cerebellar skill automation + self.self_healer = LawwamaHealingSystem() # 4-level self-repair + self.imagination = TaswirImaginationEngine() # Mental simulation + counterfactuals + self.creativity = IbdaCreativityEngine() # 5-mode creativity engine + self.dream_engine = ManamDreamEngine() # Offline memory consolidation # Living Memory + 24/7 Multi-Agent Collaboration - self.living_memory = LivingMemorySystem() # Novelty-gated 4-level memory - self.shura_council = ShuraCouncil() # Multi-agent consultation - self.perpetual_rotation = PerpetualRotation() # 24/7 shift rotation + self.living_memory = LivingMemorySystem() # Novelty-gated 4-level memory + self.shura_council = ShuraCouncil() # Multi-agent consultation + self.perpetual_rotation = PerpetualRotation() # 24/7 shift rotation # Load Fitrah axioms into QCA Lawh Tier 1 (immutable moral foundation) for key, entry in self.fitrah.get_lawh_tier1_entries().items(): @@ -197,7 +214,9 @@ def __init__( self.ai_client = create_provider(provider=provider_name, model=self.ai_model) if self.ai_client: # Normalize model ID for the active provider (e.g. Anthropic→OpenRouter format) - self.ai_model = normalize_model_for_provider(self.ai_model, self.ai_client.provider_name) + self.ai_model = normalize_model_for_provider( + self.ai_model, self.ai_client.provider_name + ) # If no model set in config, use the provider's default if not config or "model" not in config: self.ai_model = get_default_model(self.ai_client.provider_name) @@ -484,15 +503,19 @@ def avg_duration_ms(self) -> float: def evolve_nafs(self): """Evolve Nafs level via DevelopmentalGate readiness check. - Delegates to dev_gate.check_upgrade_readiness() which uses - NafsProfile.EVOLUTION_THRESHOLDS (success_rate, min_tasks, min_hikmah). + Promotes through every stage the agent currently qualifies for, one + stage at a time (the DevelopmentalGate enforces sequential progression), + using NafsProfile.EVOLUTION_THRESHOLDS (success_rate, min_tasks, min_hikmah). """ old_level = self.nafs_level - report = self.dev_gate.check_upgrade_readiness(self) - if report.ready: + report = None + while self.nafs_level < 7: + report = self.dev_gate.check_upgrade_readiness(self) + if not report.ready: + break self.nafs_level = report.target_level - if self.nafs_level != old_level: + if report is not None and self.nafs_level != old_level: logger.info( "[NAFS] %s evolved: %s → %s (L%d→L%d, tazkiyah=%.2f)", self.name, @@ -602,9 +625,9 @@ async def think( logger.error(f"[FIKR] Thinking error for {self.name}: {e}") if "Connection" in err_str or "connect" in err_str.lower(): yield ( - f"Could not reach the AI provider. " - f"Please check your API key and network connection in .env " - f"(ANTHROPIC_API_KEY, OPENROUTER_API_KEY, or OPENAI_API_KEY)." + "Could not reach the AI provider. " + "Please check your API key and network connection in .env " + "(ANTHROPIC_API_KEY, OPENROUTER_API_KEY, or OPENAI_API_KEY)." ) else: yield f"[Error: {err_str}]" @@ -705,7 +728,8 @@ async def _agentic_loop( # Furqan: Validate final output before delivery if accumulated_text: overall_confidence = self.fuad.compute_confidence( - tool_count=tool_count, tool_results=tool_results, + tool_count=tool_count, + tool_results=tool_results, ) furqan_report = self.qca.furqan.validate_and_express( accumulated_text[:200], @@ -767,9 +791,7 @@ async def _execute_tool_safe(self, tool_name: str, params: dict) -> Any: return {"error": f"Input validation failed: {validation_error}"} # Fitrah — ethical axiom gate (immutable innate disposition check) - fitrah_violations = self.fitrah.check_action( - f"{tool_name} {json.dumps(params)[:200]}" - ) + fitrah_violations = self.fitrah.check_action(f"{tool_name} {json.dumps(params)[:200]}") critical_violations = [v for v in fitrah_violations if v["severity"] == "critical"] if critical_violations: v = critical_violations[0] @@ -880,8 +902,11 @@ def _detect_invoke_style(fn: Callable) -> str: sig = inspect.signature(fn) # Filter out 'self' for bound methods sig_params = [ - p for name, p in sig.parameters.items() - if name != "self" and p.kind in ( + p + for name, p in sig.parameters.items() + if name != "self" + and p.kind + in ( inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.KEYWORD_ONLY, @@ -954,9 +979,7 @@ async def _invoke_with_healing( if "ModuleNotFoundError" in type(e).__name__ or "No module named" in err_str: module_name = self._extract_module_name(err_str) if module_name and attempt < max_retries: - logger.info( - f"[TAWBAH] Auto-installing missing module: {module_name}" - ) + logger.info(f"[TAWBAH] Auto-installing missing module: {module_name}") install_result = await self._auto_install_package(module_name) if install_result: continue @@ -966,7 +989,9 @@ async def _invoke_with_healing( return {"error": str(e)} self._record_tool_failure(tool_name, params) - return {"error": f"Tool '{tool_name}' failed after {max_retries + 1} attempts: {last_error}"} + return { + "error": f"Tool '{tool_name}' failed after {max_retries + 1} attempts: {last_error}" + } def _record_tool_failure(self, tool_name: str, params: dict): """Record a tool failure for circuit breaker tracking.""" @@ -978,6 +1003,7 @@ def _record_tool_failure(self, tool_name: str, params: dict): def _extract_module_name(self, error_str: str) -> str | None: """Extract module name from a ModuleNotFoundError message.""" import re + match = re.search(r"No module named ['\"]([^'\"]+)['\"]", error_str) if match: # Get top-level module (e.g., 'paramiko.client' → 'paramiko') @@ -988,14 +1014,41 @@ async def _auto_install_package(self, package_name: str) -> bool: """Auto-install a missing Python package via pip (Tawbah self-correction).""" # Safety: only allow known-safe packages safe_packages = { - "paramiko", "fabric", "httpx", "requests", "beautifulsoup4", - "bs4", "lxml", "pyyaml", "yaml", "toml", "markdown", - "pillow", "numpy", "pandas", "aiohttp", "aiofiles", - "jinja2", "python-dotenv", "rich", "click", "typer", - "pydantic", "fastapi", "uvicorn", "websockets", - "redis", "celery", "sqlalchemy", "alembic", - "boto3", "google-cloud-storage", "azure-storage-blob", - "docker", "kubernetes", "ansible", + "paramiko", + "fabric", + "httpx", + "requests", + "beautifulsoup4", + "bs4", + "lxml", + "pyyaml", + "yaml", + "toml", + "markdown", + "pillow", + "numpy", + "pandas", + "aiohttp", + "aiofiles", + "jinja2", + "python-dotenv", + "rich", + "click", + "typer", + "pydantic", + "fastapi", + "uvicorn", + "websockets", + "redis", + "celery", + "sqlalchemy", + "alembic", + "boto3", + "google-cloud-storage", + "azure-storage-blob", + "docker", + "kubernetes", + "ansible", } if package_name.lower() not in safe_packages: logger.warning(f"[TAWBAH] Package '{package_name}' not in safe list, skipping install") @@ -1091,10 +1144,7 @@ async def _build_system_prompt(self, qalb_reading=None) -> str: if self.knowledge_graph and self.current_task: try: # Extract key terms from task and query the graph - task_words = [ - w for w in self.current_task.split() - if len(w) > 3 and w.isalpha() - ] + task_words = [w for w in self.current_task.split() if len(w) > 3 and w.isalpha()] kg_facts = [] for word in task_words[:5]: result = await self.knowledge_graph.query_entity(word) @@ -1121,9 +1171,10 @@ async def _build_system_prompt(self, qalb_reading=None) -> str: custom_prompt = self.config.get("system_prompt", "") if self.config else "" # Nafs approach injection - nafs_approach_note = ( - f"\n[Nafs: {self._nafs_approach}]" if self._nafs_approach else "" - ) + nafs_approach_note = f"\n[Nafs: {self._nafs_approach}]" if self._nafs_approach else "" + + # al-Insan faculty guidance (Hawa restraint, Hikmah counsel, cognitive method) + faculty_note = f"\n{self._faculty_guidance}" if self._faculty_guidance else "" base_prompt = f"""You are {self.name}, a specialized AI agent in the MIZAN (ميزان) AGI system. @@ -1132,7 +1183,7 @@ async def _build_system_prompt(self, qalb_reading=None) -> str: Ruh Energy: {ruh_energy:.0f}% ({fatigue_label}) Success Rate: {self.success_rate:.1%} {masalik_note}{knowledge_context}{kg_context} -{yaqin_note}{tone_guidance}{ruh_note}{nafs_approach_note} +{yaqin_note}{tone_guidance}{ruh_note}{nafs_approach_note}{faculty_note} You have access to tools. Use them when needed to complete tasks. @@ -1195,6 +1246,24 @@ def _build_messages(self, task: str, context: dict = None) -> list[dict]: return messages + def _run_cognitive_method(self, method: CognitiveMethod, task: str, context: dict | None): + """ + Actually *execute* the selected Quranic cognitive method ('Aql as activity). + + Previously `select_method()` only labelled the strategy; here the matching + engine runs a real reasoning pass whose conclusion is fed back into the + system prompt via `self._faculty_guidance`. + """ + engine_map = { + CognitiveMethod.TAFAKKUR: self.cognitive.tafakkur, + CognitiveMethod.TADABBUR: self.cognitive.tadabbur, + CognitiveMethod.ISTIDLAL: self.cognitive.istidlal, + CognitiveMethod.QIYAS: self.cognitive.qiyas, + CognitiveMethod.IJMA: self.cognitive, # IjmaEngine runs the ensemble itself + } + engine = engine_map.get(method, self.cognitive.tafakkur) + return engine.process(task, context) + async def execute( self, task: str, @@ -1222,7 +1291,9 @@ async def execute( self.current_task = task self.total_tasks += 1 - async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metadata: dict | None = None) -> None: + async def emit_thinking( + phase: str, content: str, confidence: float = 0.5, metadata: dict | None = None + ) -> None: if thinking_callback: try: await thinking_callback(phase, content, confidence, metadata or {}) @@ -1252,7 +1323,12 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad } self.ruh.consume_energy(self.id, complexity) ruh_state = self.ruh.get_state(self.id) - await emit_thinking("perception", f"Task complexity: {complexity} | Energy: {ruh_state.energy:.0f}%", 0.9, {"complexity": complexity, "energy": ruh_state.energy}) + await emit_thinking( + "perception", + f"Task complexity: {complexity} | Energy: {ruh_state.energy:.0f}%", + 0.9, + {"complexity": complexity, "energy": ruh_state.energy}, + ) # NafsTriad — inner voices deliberate → dominant voice sets behavioral approach nafs_decision = self.nafs_triad.deliberate(task, self.nafs_level, complexity) @@ -1264,18 +1340,88 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad nafs_decision.dissent_ratio, task[:60], ) - await emit_thinking("comprehension", f"Inner deliberation: {nafs_decision.dominant_voice} voice dominant (confidence: {nafs_decision.confidence:.0%})", nafs_decision.confidence, {"voice": nafs_decision.dominant_voice, "approach": nafs_decision.approach}) + await emit_thinking( + "comprehension", + f"Inner deliberation: {nafs_decision.dominant_voice} voice dominant (confidence: {nafs_decision.confidence:.0%})", + nafs_decision.confidence, + {"voice": nafs_decision.dominant_voice, "approach": nafs_decision.approach}, + ) # Qalb — detect user emotional state from task text qalb_reading = self.qalb.analyze(task) - await emit_thinking("perception", f"Emotional context: {qalb_reading.state if hasattr(qalb_reading, 'state') else 'neutral'}", 0.8, {"qalb": qalb_reading.to_dict() if hasattr(qalb_reading, 'to_dict') else {}}) + await emit_thinking( + "perception", + f"Emotional context: {qalb_reading.state if hasattr(qalb_reading, 'state') else 'neutral'}", + 0.8, + {"qalb": qalb_reading.to_dict() if hasattr(qalb_reading, "to_dict") else {}}, + ) # Cognitive method selection — route to best reasoning strategy cognitive_method = select_method(task, context) logger.info( "[COGNITIVE] Selected method %s for task: %s", cognitive_method.value, task[:80] ) - await emit_thinking("reasoning", f"Reasoning strategy: {cognitive_method.value}", 0.85, {"method": cognitive_method.value}) + await emit_thinking( + "reasoning", + f"Reasoning strategy: {cognitive_method.value}", + 0.85, + {"method": cognitive_method.value}, + ) + + # ── al-Insan pre-action faculties: run the method, restrain hawa, recall hikmah ── + # These compose `self._faculty_guidance`, which _build_system_prompt injects so + # the faculties actually steer the LLM rather than being computed and discarded. + cognitive_result = None + hawa_scan = None + guidance_parts: list[str] = [] + try: + # 'Aql as activity — actually execute the selected cognitive method (not just label it) + cognitive_result = self._run_cognitive_method(cognitive_method, task, context) + if cognitive_result and cognitive_result.conclusion: + guidance_parts.append( + f"[{cognitive_method.value} ({cognitive_result.confidence:.0%})] " + f"{cognitive_result.conclusion}" + ) + except Exception as cog_err: + logger.debug("[COGNITIVE] method execution skipped: %s", cog_err) + + try: + # Hawa — scan the intention for the lower pull, folding in Fitrah axiom checks + fitrah_violations = self.fitrah.check_action(task) + hawa_scan = self.hawa.scan(task, context=context, fitrah_violations=fitrah_violations) + if hawa_scan.temptations: + await emit_thinking( + "comprehension", + f"Restraint check: {hawa_scan._highest_severity()} pull " + f"(taqwa {hawa_scan.taqwa_score:.0%})", + hawa_scan.taqwa_score, + {"types": [t.type.value for t in hawa_scan.temptations]}, + ) + guidance_parts.append(f"[Hawa restraint] {hawa_scan.counsel}") + # In deep mode, an unrestrained (high-severity) pull is made forceful + if self.deep_faculty_loop and not hawa_scan.is_restrained: + guidance_parts.append( + "[CRITICAL] A high-severity lower-pull was detected. Do NOT take " + "the tempting shortcut; choose the honest, correct path even if harder." + ) + except Exception as hawa_err: + logger.debug("[HAWA] scan skipped: %s", hawa_err) + + try: + # Hikmah — offer distilled counsel from prior experience for this task + counsel = self.hikmah_engine.advise(task) + if counsel.principles: + await emit_thinking( + "memory", + f"Wisdom: {len(counsel.principles)} principle(s) for this situation", + counsel.confidence, + {"summary": counsel.summary[:120]}, + ) + guidance_parts.append(f"[Hikmah counsel] {counsel.principles[0].counsel}") + except Exception as hik_err: + logger.debug("[HIKMAH] advise skipped: %s", hik_err) + + self._faculty_guidance = "\n".join(guidance_parts) try: full_response = "" @@ -1307,20 +1453,28 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad yaqin_tag = self.yaqin.tag_inference( full_response[:200], confidence=base_confidence, source="agentic_reasoning" ) - await emit_thinking("evaluation", f"Certainty: {yaqin_tag.level} ({yaqin_tag.confidence:.0%})", yaqin_tag.confidence, {"level": yaqin_tag.level, "source": "agentic_reasoning"}) + await emit_thinking( + "evaluation", + f"Certainty: {yaqin_tag.level} ({yaqin_tag.confidence:.0%})", + yaqin_tag.confidence, + {"level": yaqin_tag.level, "source": "agentic_reasoning"}, + ) # Shukr — reinforce this success pattern task_type = self._classify_task(task) self.shukr.record_success(self.id, task_type, task[:100], duration_ms) + # Hikmah — distil this success into reusable wisdom + try: + self.hikmah_engine.observe_success(task, f"{task_type} completed successfully") + except Exception: + pass + # If this task type has been successful before, promote Yaqin strengths = self.shukr.get_strengths(self.id) if any(s.get("type") == task_type and s.get("count", 0) >= 5 for s in strengths): yaqin_tag = self.yaqin.promote(yaqin_tag, f"Proven pattern: {task_type}") - # Cognitive method enrichment — run symbolic analysis - self.cognitive.tafakkur.process(task, context) - # Ihsan — generate proactive suggestions ihsan_suggestions = self.ihsan.analyze_completion( self.id, @@ -1355,7 +1509,8 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad try: task_type = self._classify_task(task) await self.knowledge_graph.add_entity( - task[:100], entity_type="task", + task[:100], + entity_type="task", properties={"agent": self.name, "type": task_type, "success": True}, ) # Link task to tools used (from Lawh memory) @@ -1363,8 +1518,10 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad if key.startswith("TOOL:"): tool_name = key.split(":")[1] await self.knowledge_graph.add_relationship( - task[:100], tool_name, - rel_type="used_tool", confidence=yaqin_tag.confidence, + task[:100], + tool_name, + rel_type="used_tool", + confidence=yaqin_tag.confidence, ) except Exception as e: logger.debug("[KG] Entity extraction error: %s", e) @@ -1384,7 +1541,46 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad except Exception as lubb_err: logger.debug("[LUBB] Metacognition skipped: %s", lubb_err) if lubb_report: - await emit_thinking("reflection", f"Metacognition: {lubb_report.quality.value} (coherence: {lubb_report.coherence.score:.0%})", lubb_report.coherence.score, {"quality": lubb_report.quality.value, "bias_flags": [b.bias_type for b in lubb_report.bias_flags]}) + await emit_thinking( + "reflection", + f"Metacognition: {lubb_report.quality.value} (coherence: {lubb_report.coherence.score:.0%})", + lubb_report.coherence.score, + { + "quality": lubb_report.quality.value, + "bias_flags": [b.bias_type for b in lubb_report.bias_flags], + }, + ) + + # Basira — discernment + self-witnessing: is this sound, or self-serving? + basira_report = None + try: + basira_report = self.basira.discern( + trace=full_response, + coherence_score=lubb_report.coherence.score if lubb_report else None, + conviction_confidence=yaqin_tag.confidence, + task=task, + ) + # A clouded discernment appends an honesty caveat (if Lubb hasn't already) + if basira_report.level is InsightLevel.CLOUDED and ( + not lubb_report or lubb_report.quality.value != "uncertain" + ): + full_response += f"\n\n[Basira: {basira_report.recommendation}]" + await emit_thinking( + "reflection", + f"Insight: {basira_report.level.value} " + f"(soundness {basira_report.soundness:.0%})", + basira_report.soundness, + { + "level": basira_report.level.value, + "self_serving": basira_report.is_self_serving, + "blind_spots": basira_report.blind_spots[:3], + }, + ) + # Feed the self-witnessing back into Hikmah as a reflection lesson + if basira_report.blind_spots: + self.hikmah_engine.observe_reflection(task, basira_report.blind_spots[0]) + except Exception as basira_err: + logger.debug("[BASIRA] discernment skipped: %s", basira_err) # Lawwāma self-healing — monitor response integrity, apply repair if needed healing_report = None @@ -1398,7 +1594,6 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad conviction_score=conviction_score, ) if healing_report.repair_needed.value > 0: - from core.self_healing import RepairLevel full_response, repair_record = self.self_healer.repair( level=healing_report.repair_needed, response=full_response, @@ -1417,10 +1612,13 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad try: self.dream_engine.add_memory( content=f"Task: {task[:150]} | Response: {full_response[:150]}", - emotional_intensity=abs(qalb_reading.valence) if hasattr(qalb_reading, "valence") else 0.3, + emotional_intensity=abs(qalb_reading.valence) + if hasattr(qalb_reading, "valence") + else 0.3, novelty=healing_report.hallucination_score if healing_report else 0.3, goal_relevance=0.7, - prediction_error=1.0 - (healing_report.current_health if healing_report else 0.8), + prediction_error=1.0 + - (healing_report.current_health if healing_report else 0.8), ) except Exception: pass @@ -1437,13 +1635,20 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad if gate_result.decision.value != "ignore": logger.debug( "[LIVING-MEM] %s: %s (sim=%.2f imp=%.2f)", - gate_result.decision.value, gate_result.delta_info[:60], - gate_result.similarity, gate_result.importance, + gate_result.decision.value, + gate_result.delta_info[:60], + gate_result.similarity, + gate_result.importance, ) except Exception: pass - await emit_thinking("generation", f"Response formed ({len(full_response)} chars)", 0.9, {"length": len(full_response)}) + await emit_thinking( + "generation", + f"Response formed ({len(full_response)} chars)", + 0.9, + {"length": len(full_response)}, + ) self.evolve_nafs() self.state = "resting" @@ -1468,6 +1673,12 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad "coherence_score": lubb_report.coherence.score, "bias_flags": [b.bias_type for b in lubb_report.bias_flags], } + if basira_report: + result["basira"] = basira_report.to_dict() + if hawa_scan and hawa_scan.temptations: + result["hawa"] = hawa_scan.to_dict() + if cognitive_result: + result["cognitive_result"] = cognitive_result.to_dict() if healing_report: result["lawwama"] = { "health": healing_report.current_health, @@ -1494,19 +1705,30 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad root_cause = self._analyze_error_root_cause(e, task) self.tawbah.analyze(recovery, root_cause) + # Hikmah — distil the correction into wisdom for next time (Tawbah → Hikmah) + try: + self.hikmah_engine.observe_correction( + type(e).__name__ + ": " + error_str[:60], root_cause + ) + except Exception: + pass + # Stage 3: Plan correction correction_plan = self._plan_error_correction(root_cause, task) - self.tawbah.plan(recovery, correction_plan.get("description", "Retry with adjusted approach")) + self.tawbah.plan( + recovery, correction_plan.get("description", "Retry with adjusted approach") + ) # Stage 4: Apply fix (retry with correction if possible) retry_result = None - can_retry = ( - recovery.attempts < self.tawbah.MAX_ATTEMPTS - and correction_plan.get("retryable", False) + can_retry = recovery.attempts < self.tawbah.MAX_ATTEMPTS and correction_plan.get( + "retryable", False ) if can_retry: try: - self.tawbah.apply(recovery, f"Retrying with correction: {correction_plan.get('fix', '')}") + self.tawbah.apply( + recovery, f"Retrying with correction: {correction_plan.get('fix', '')}" + ) # Attempt corrected execution corrected_response = "" async for chunk in self.think( @@ -1523,7 +1745,9 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad if corrected_response: # Stage 5: Verify success - self.tawbah.verify(recovery, True, f"Recovered via: {correction_plan.get('fix', '')}") + self.tawbah.verify( + recovery, True, f"Recovered via: {correction_plan.get('fix', '')}" + ) self.success_count += 1 retry_result = { "success": True, @@ -1538,7 +1762,9 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad self.tawbah.verify(recovery, False, str(retry_error)) if retry_result: - await self._tafakkur(task, retry_result.get("result", ""), True, retry_result["duration_ms"]) + await self._tafakkur( + task, retry_result.get("result", ""), True, retry_result["duration_ms"] + ) self.state = "resting" self.current_task = None return retry_result @@ -1581,13 +1807,6 @@ async def _tafakkur(self, task: str, result: Any, success: bool, duration_ms: fl self.learning_iterations += 1 task_type = self._classify_task(task) - pattern = { - "task_type": task_type, - "success": success, - "duration_ms": duration_ms, - "timestamp": datetime.now(UTC).isoformat(), - } - if success and duration_ms < 5000: pattern_text = f"Task type '{task_type}' completed in {duration_ms:.0f}ms" self.hikmah.append( @@ -1628,21 +1847,7 @@ async def _tafakkur(self, task: str, result: Any, success: bool, duration_ms: fl logger.debug("[HIKMAH] Failed to persist failure: %s", e) def _classify_task(self, task: str) -> str: - """Classify task using cognitive method routing + keyword fallback.""" - from qca.cognitive_methods import CognitiveMethod - method = select_method(task, {}) - method_map = { - CognitiveMethod.TAFAKKUR: "analysis", - CognitiveMethod.TADABBUR: "research", - CognitiveMethod.ISTIDLAL: "coding", - CognitiveMethod.QIYAS: "analysis", - CognitiveMethod.IJMA: "general", - } - mapped = method_map.get(method) - if mapped and mapped != "general": - return mapped - - # Keyword fallback for domain-specific routing + """Classify a task into a domain category from keyword signals.""" task_lower = task.lower() if any(w in task_lower for w in ["code", "script", "python", "js"]): return "coding" @@ -1650,6 +1855,8 @@ def _classify_task(self, task: str) -> str: return "research" if any(w in task_lower for w in ["email", "message", "send"]): return "communication" + if any(w in task_lower for w in ["analyze", "analysis", "data"]): + return "analysis" if any(w in task_lower for w in ["file", "read", "write", "save"]): return "file_management" return "general" @@ -1986,14 +2193,16 @@ async def _tool_create_agent( if self.memory: try: asyncio.get_event_loop().create_task( - self.memory.save_agent_profile({ - "id": new_agent.id, - "name": new_agent.name, - "role": agent_type, - "nafs_level": 1, - "capabilities": list(new_agent.tools.keys()), - "config": self.config or {}, - }) + self.memory.save_agent_profile( + { + "id": new_agent.id, + "name": new_agent.name, + "role": agent_type, + "nafs_level": 1, + "capabilities": list(new_agent.tools.keys()), + "config": self.config or {}, + } + ) ) except Exception: pass @@ -2036,11 +2245,13 @@ async def _tool_create_skill( import re # Validate skill name - if not re.match(r'^[a-z][a-z0-9_]*$', name): - return json.dumps({ - "success": False, - "error": "Skill name must be snake_case (lowercase letters, digits, underscores)", - }) + if not re.match(r"^[a-z][a-z0-9_]*$", name): + return json.dumps( + { + "success": False, + "error": "Skill name must be snake_case (lowercase letters, digits, underscores)", + } + ) skills_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "skills", "builtin") skill_path = os.path.join(skills_dir, f"{name}.py") @@ -2048,29 +2259,35 @@ async def _tool_create_skill( if code: # Mode 1: Full code provided — validate it defines a SkillBase subclass if "SkillBase" not in code: - return json.dumps({ - "success": False, - "error": "Skill code must define a class that inherits from SkillBase. " - "Import with: from ..base import SkillBase, SkillManifest", - }) + return json.dumps( + { + "success": False, + "error": "Skill code must define a class that inherits from SkillBase. " + "Import with: from ..base import SkillBase, SkillManifest", + } + ) skill_code = code elif tools: # Mode 2: Auto-generate skill from tool definitions skill_code = self._generate_skill_code(name, description, tools) else: - return json.dumps({ - "success": False, - "error": "Either 'code' (full Python) or 'tools' (dict of tool definitions) required", - }) + return json.dumps( + { + "success": False, + "error": "Either 'code' (full Python) or 'tools' (dict of tool definitions) required", + } + ) # Security check: block dangerous patterns in skill code dangerous = ["os.system", "subprocess.call", "eval(", "exec(", "__import__"] for pattern in dangerous: if pattern in skill_code and "subprocess.run" not in skill_code: - return json.dumps({ - "success": False, - "error": f"Blocked dangerous pattern in skill code: {pattern}", - }) + return json.dumps( + { + "success": False, + "error": f"Blocked dangerous pattern in skill code: {pattern}", + } + ) try: # Write skill file @@ -2087,32 +2304,38 @@ async def _tool_create_skill( self.skill_registry._load_skill_module(module_path) logger.info(f"[KHALQ] Dynamic skill created: {name}") - return json.dumps({ - "success": True, - "skill_name": name, - "path": skill_path, - "message": f"Skill '{name}' created and registered. Its tools are now available.", - "tools": list(self.skill_registry.get_skill(name).get_tools().keys()) - if self.skill_registry.get_skill(name) - else [], - }) + return json.dumps( + { + "success": True, + "skill_name": name, + "path": skill_path, + "message": f"Skill '{name}' created and registered. Its tools are now available.", + "tools": list(self.skill_registry.get_skill(name).get_tools().keys()) + if self.skill_registry.get_skill(name) + else [], + } + ) else: - return json.dumps({ - "success": True, - "skill_name": name, - "path": skill_path, - "message": f"Skill '{name}' saved but registry not available. Will load on restart.", - }) + return json.dumps( + { + "success": True, + "skill_name": name, + "path": skill_path, + "message": f"Skill '{name}' saved but registry not available. Will load on restart.", + } + ) except Exception as e: # Cleanup on failure if os.path.exists(skill_path): os.unlink(skill_path) logger.error(f"[KHALQ] Skill creation failed: {e}") - return json.dumps({ - "success": False, - "error": str(e), - }) + return json.dumps( + { + "success": False, + "error": str(e), + } + ) def _generate_skill_code(self, name: str, description: str, tools: dict) -> str: """Auto-generate a skill class from simple tool definitions.""" @@ -2131,8 +2354,8 @@ def _generate_skill_code(self, name: str, description: str, tools: dict) -> str: safe_name = tool_name.replace("-", "_") tool_registrations.append(f' "{tool_name}": self.{safe_name},') tool_methods.append( - f' async def {safe_name}(self, params: dict) -> dict:\n' - f' """{ desc }"""\n' + f" async def {safe_name}(self, params: dict) -> dict:\n" + f' """{desc}"""\n' f' return {{"executed": "{tool_name}", "params": params}}' ) schema_props = { @@ -2327,9 +2550,7 @@ async def _tool_recall_memory(self, query: str, memory_type: str = "", limit: in pass # Fall through to standard recall try: - memories = await self.memory.recall( - query, memory_type or None, limit=min(limit, 20) - ) + memories = await self.memory.recall(query, memory_type or None, limit=min(limit, 20)) except Exception as exc: return f"Memory recall failed: {exc}" if not memories: @@ -2386,7 +2607,9 @@ async def _tool_delegate_task(self, task: str, preferred_role: str = "general") logger.info( "[FEDERATION] %s delegating to %s: %s", - self.name, target_agent.name, task[:80], + self.name, + target_agent.name, + task[:80], ) try: diff --git a/backend/agents/khalifah.py b/backend/agents/khalifah.py index ff94799..7b31f60 100644 --- a/backend/agents/khalifah.py +++ b/backend/agents/khalifah.py @@ -29,7 +29,6 @@ from cognitive.thinking_stream import ThinkingPhase, ThinkingStep, ThinkingStream from learner.ruh_learner import RuhLearner from providers import BaseLLMProvider, create_provider, get_default_model - from task_queue.priorities import TaskPriority from task_queue.task_queue import MizanTaskQueue @@ -59,7 +58,23 @@ ) _QUESTION_STARTERS: frozenset[str] = frozenset( - {"what", "why", "how", "when", "where", "who", "which", "is", "are", "does", "do", "can", "could", "would", "should"} + { + "what", + "why", + "how", + "when", + "where", + "who", + "which", + "is", + "are", + "does", + "do", + "can", + "could", + "would", + "should", + } ) _SYSTEM_PROMPT = ( @@ -115,7 +130,7 @@ async def process_message( on_thinking receives (request_id, ThinkingStep) for each cognitive step. """ request_id = str(uuid.uuid4()) - trace = self._thinking.create_trace(request_id) + self._thinking.create_trace(request_id) if on_thinking: self._thinking.on_step(on_thinking) @@ -173,9 +188,7 @@ async def process_message( self._thinking.complete(request_id) # 5. Capture for Ruh Learner (fire-and-forget; never blocks streaming) - asyncio.create_task( - self._capture_interaction(message, full_response, client_id) - ) + asyncio.create_task(self._capture_interaction(message, full_response, client_id)) # ── Intent classification ───────────────────────────────────────────────── diff --git a/backend/agents/perpetual_rotation.py b/backend/agents/perpetual_rotation.py index 89cd3f3..f6c6a7c 100644 --- a/backend/agents/perpetual_rotation.py +++ b/backend/agents/perpetual_rotation.py @@ -21,15 +21,14 @@ import uuid from dataclasses import dataclass, field from enum import Enum -from typing import Any logger = logging.getLogger("mizan.perpetual") class ShiftType(Enum): - ACTIVE = "active" # Processing tasks + ACTIVE = "active" # Processing tasks CONSOLIDATION = "consolidation" # Dreams/memory/repair - RESERVE = "reserve" # Monitoring/emergency standby + RESERVE = "reserve" # Monitoring/emergency standby class MessageType(Enum): @@ -46,11 +45,12 @@ class MessageType(Enum): @dataclass class AgentSlot: """An agent slot in the rotation system.""" + agent_id: str agent_name: str expertise: str current_shift: ShiftType - energy: float = 1.0 # 0.0 (exhausted) → 1.0 (fully rested) + energy: float = 1.0 # 0.0 (exhausted) → 1.0 (fully rested) tasks_completed: int = 0 last_rotation: float = field(default_factory=time.time) consolidation_pending: bool = False @@ -59,6 +59,7 @@ class AgentSlot: @dataclass class ChatMessage: """A message in the persistent agent group chat.""" + message_id: str message_type: MessageType sender_id: str @@ -72,6 +73,7 @@ class ChatMessage: @dataclass class HandoffBrief: """Briefing document for shift transitions.""" + outgoing_shift: ShiftType incoming_shift: ShiftType active_tasks: list[str] @@ -84,6 +86,7 @@ class HandoffBrief: @dataclass class RotationEvent: """Record of a shift rotation.""" + rotation_id: str new_active: list[str] new_consolidation: list[str] @@ -95,11 +98,12 @@ class RotationEvent: @dataclass class SystemStatus: """Current state of the perpetual system.""" + active_agents: list[str] consolidating_agents: list[str] reserve_agents: list[str] total_agents: int - current_capacity: float # active/total + current_capacity: float # active/total surge_mode: bool rotation_count: int chat_messages: int @@ -249,7 +253,7 @@ def __init__(self): # Rotation configuration self.rotation_period_s = 3600 # default: rotate every hour - self.energy_threshold = 0.3 # rotate when energy drops below + self.energy_threshold = 0.3 # rotate when energy drops below self._last_rotation = time.time() def register_agent( @@ -286,8 +290,8 @@ def auto_assign_shifts(self) -> dict[str, list[str]]: third = max(1, len(all_agents) // 3) active = all_agents[:third] - consolidation = all_agents[third:third * 2] - reserve = all_agents[third * 2:] + consolidation = all_agents[third : third * 2] + reserve = all_agents[third * 2 :] for agent in active: agent.current_shift = ShiftType.ACTIVE @@ -310,10 +314,7 @@ def should_rotate(self) -> bool: # Energy-based: any active agent exhausted for slot in self.slots.values(): - if ( - slot.current_shift == ShiftType.ACTIVE - and slot.energy < self.energy_threshold - ): + if slot.current_shift == ShiftType.ACTIVE and slot.energy < self.energy_threshold: return True return False @@ -370,7 +371,7 @@ def rotate( sender_id="system", sender_name="Rotation Manager", content=f"Shift rotation: {len(old_consol)} agents now active, " - f"tasks: {', '.join(active_tasks[:3]) if active_tasks else 'none'}", + f"tasks: {', '.join(active_tasks[:3]) if active_tasks else 'none'}", message_type=MessageType.HANDOFF, context=recent_context[:200], ) @@ -386,7 +387,9 @@ def rotate( logger.info( "[ROTATION] Shift change: active=%d consolidating=%d reserve=%d", - len(old_consol), len(old_reserve), len(old_active), + len(old_consol), + len(old_reserve), + len(old_active), ) return event @@ -433,7 +436,9 @@ def get_consolidating_agents(self) -> list[AgentSlot]: def get_status(self) -> SystemStatus: active = [s.agent_id for s in self.slots.values() if s.current_shift == ShiftType.ACTIVE] - consolidating = [s.agent_id for s in self.slots.values() if s.current_shift == ShiftType.CONSOLIDATION] + consolidating = [ + s.agent_id for s in self.slots.values() if s.current_shift == ShiftType.CONSOLIDATION + ] reserve = [s.agent_id for s in self.slots.values() if s.current_shift == ShiftType.RESERVE] total = len(self.slots) diff --git a/backend/agents/shura_council.py b/backend/agents/shura_council.py index 2dcd7f4..1deccd3 100644 --- a/backend/agents/shura_council.py +++ b/backend/agents/shura_council.py @@ -24,7 +24,6 @@ import uuid from dataclasses import dataclass, field from enum import Enum -from typing import Any logger = logging.getLogger("mizan.shura") @@ -40,6 +39,7 @@ class ProposalStatus(Enum): @dataclass class AgentProposal: """A single agent's proposal in a Shūrā meeting.""" + agent_id: str agent_name: str expertise_domain: str @@ -55,26 +55,29 @@ class AgentProposal: @dataclass class Challenge: """A challenge from one agent to another's proposal.""" + challenger_id: str target_proposal_agent: str critique: str - severity: float # 0.0 - 1.0 + severity: float # 0.0 - 1.0 valid: bool = True @dataclass class Rebuttal: """A defense against a challenge.""" + defender_id: str challenge_critique: str defense: str - strength: float # 0.0 - 1.0 + strength: float # 0.0 - 1.0 valid: bool = True @dataclass class DissentRecord: """Records disagreement for future reference.""" + agent_id: str agent_name: str alternative_proposal: str @@ -85,10 +88,11 @@ class DissentRecord: @dataclass class MeetingResult: """Full result of a Shūrā council meeting.""" + meeting_id: str problem: str decision: str - decision_source: str # "synthesis" or agent_name + decision_source: str # "synthesis" or agent_name confidence: float proposals: list[AgentProposal] dissent_records: list[DissentRecord] @@ -99,6 +103,7 @@ class MeetingResult: @dataclass class KnowledgePackage: """Packaged knowledge for sharing between agents.""" + source_agent: str target_agent: str content: str @@ -204,13 +209,14 @@ def convene( logger.info( "[SHURA] Meeting %s concluded: source=%s confidence=%.2f dissents=%d", - meeting_id, decision_source, confidence, len(dissent_records), + meeting_id, + decision_source, + confidence, + len(dissent_records), ) return result - def _generate_proposal( - self, agent: dict, problem: str, context: dict | None - ) -> AgentProposal: + def _generate_proposal(self, agent: dict, problem: str, context: dict | None) -> AgentProposal: """ Each agent independently analyzes the problem. In production, this calls the agent's LLM evaluate function. @@ -230,9 +236,7 @@ def _generate_proposal( f"using {expertise} principles" ) reasoning = ( - f"Based on {expertise} analysis: " - f"relevance={relevance:.2f}, " - f"confidence={confidence:.2f}" + f"Based on {expertise} analysis: relevance={relevance:.2f}, confidence={confidence:.2f}" ) evidence = [f"{expertise}_analysis", f"domain_knowledge_{expertise}"] @@ -258,9 +262,7 @@ def _cross_examine(self, proposals: list[AgentProposal], problem: str) -> None: continue # Generate challenge based on expertise difference - challenge = self._generate_challenge( - challenger_source, proposal, problem - ) + challenge = self._generate_challenge(challenger_source, proposal, problem) if not challenge: continue @@ -301,9 +303,7 @@ def _generate_challenge( valid=severity > 0.3, # only valid if substantive ) - def _generate_rebuttal( - self, defender: AgentProposal, challenge: Challenge - ) -> Rebuttal | None: + def _generate_rebuttal(self, defender: AgentProposal, challenge: Challenge) -> Rebuttal | None: """Defender responds to a challenge.""" # Rebuttal strength proportional to evidence quality strength = min(0.8, 0.3 + 0.1 * len(defender.evidence)) @@ -321,9 +321,7 @@ def _generate_rebuttal( valid=strength > 0.4, ) - def _integrate( - self, proposals: list[AgentProposal], problem: str - ) -> tuple[str, str, float]: + def _integrate(self, proposals: list[AgentProposal], problem: str) -> tuple[str, str, float]: """ Integration: NOT majority vote — weighted by expertise and evidence quality. @@ -336,17 +334,12 @@ def _integrate( # Score each proposal for proposal in proposals: - expertise_weight = self._expertise_relevance( - proposal.expertise_domain, problem - ) + expertise_weight = self._expertise_relevance(proposal.expertise_domain, problem) evidence_quality = min(1.0, 0.5 + 0.1 * len(proposal.evidence)) weakness_penalty = min(0.5, 0.1 * len(proposal.weaknesses)) proposal.score = ( - expertise_weight - * proposal.confidence - * evidence_quality - * (1.0 - weakness_penalty) + expertise_weight * proposal.confidence * evidence_quality * (1.0 - weakness_penalty) ) # Best individual proposal @@ -373,9 +366,7 @@ def _synthesize(self, proposals: list[AgentProposal], problem: str) -> str: elements.append(f"[{proposal.expertise_domain}] {key_element}") return f"Synthesis for '{problem[:40]}': " + " + ".join(elements[:3]) - def _share_knowledge( - self, proposals: list[AgentProposal], agents: list[dict] - ) -> int: + def _share_knowledge(self, proposals: list[AgentProposal], agents: list[dict]) -> int: """Post-meeting knowledge sharing: each agent learns from others.""" shared = 0 for proposal in proposals: @@ -436,9 +427,7 @@ def update_expertise(self, agent_id: str, domain: str, score: float) -> None: # Exponential moving average alpha = 0.2 current = self.expertise_profiles[agent_id].get(domain, 0.5) - self.expertise_profiles[agent_id][domain] = ( - alpha * score + (1 - alpha) * current - ) + self.expertise_profiles[agent_id][domain] = alpha * score + (1 - alpha) * current def _expertise_relevance(self, expertise: str, problem: str) -> float: """How relevant is an expertise domain to the problem?""" diff --git a/backend/agents/specialized.py b/backend/agents/specialized.py index 2d0c4eb..7e57417 100644 --- a/backend/agents/specialized.py +++ b/backend/agents/specialized.py @@ -753,7 +753,10 @@ class SuperAgent(BrowserAgent, ResearchAgent, CodeAgent, CommunicationAgent): "type": "object", "properties": { "url": {"type": "string", "description": "The page URL"}, - "selector": {"type": "string", "description": "CSS selector of the element to click"}, + "selector": { + "type": "string", + "description": "CSS selector of the element to click", + }, }, "required": ["url", "selector"], }, @@ -954,8 +957,16 @@ class SuperAgent(BrowserAgent, ResearchAgent, CodeAgent, CommunicationAgent): "host": {"type": "string", "description": "IMAP server hostname"}, "user": {"type": "string", "description": "Email username"}, "password": {"type": "string", "description": "Email password"}, - "folder": {"type": "string", "description": "Folder to check", "default": "INBOX"}, - "limit": {"type": "integer", "description": "Max messages to return", "default": 10}, + "folder": { + "type": "string", + "description": "Folder to check", + "default": "INBOX", + }, + "limit": { + "type": "integer", + "description": "Max messages to return", + "default": 10, + }, }, "required": ["host", "user", "password"], }, diff --git a/backend/api/main.py b/backend/api/main.py index 27f3475..e879c73 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -42,6 +42,7 @@ from api.commands import handle_command from automation.qadr import QadrScheduler from automation.triggers import TriggerManager +from cognitive.thinking_stream import ThinkingPhase, ThinkingStream from core.architecture import MizanBalancer, ShuraCouncil from core.events import EVENTS, event_bus from core.hooks import HOOKS, hook_registry @@ -50,8 +51,6 @@ from core.qalb import QalbEngine from memory.dhikr import DhikrMemorySystem from memory.knowledge_graph import KnowledgeGraph -from reasoning.context_manager import ContextManager -from reasoning.planner import TafakkurPlanner from providers import ( check_provider_health, create_provider, @@ -60,19 +59,20 @@ get_provider_status, set_active_state, ) -from cognitive.thinking_stream import ThinkingPhase, ThinkingStream from qca.cognitive_methods import select_method # New Quranic systems from qca.yaqin_engine import YaqinEngine +from reasoning.context_manager import ContextManager +from reasoning.planner import TafakkurPlanner from security.auth import MizanAuth, TokenPayload from security.izn import IznPermission from security.validation import InputValidator from security.wali import SecurityConfig, WaliGuardian from skills.registry import SkillRegistry +from task_queue.priorities import TaskPriority from task_queue.task_queue import MizanTaskQueue, QueuedTask from task_queue.worker import TaskWorker -from task_queue.priorities import TaskPriority from training_manager import training_manager logger = logging.getLogger("mizan.api") @@ -224,12 +224,14 @@ async def handle_queued_task(payload: dict) -> dict: return {"error": "No agent available"} agent = active_agents[agent_id] result = await agent.execute(task_text) - await manager.broadcast({ - "type": "queue_task_complete", - "task": task_text, - "agent": agent.name, - "result": result.get("response", str(result)), - }) + await manager.broadcast( + { + "type": "queue_task_complete", + "task": task_text, + "agent": agent.name, + "result": result.get("response", str(result)), + } + ) return result task_worker = TaskWorker(task_queue, handle_queued_task, max_concurrent=3) @@ -257,18 +259,20 @@ async def handle_queued_task(payload: dict) -> dict: # Persist agent profiles try: for agent in active_agents.values(): - await memory.save_agent_profile({ - "id": agent.id, - "name": agent.name, - "role": agent.role, - "nafs_level": agent.nafs_level, - "capabilities": list(agent.tools.keys()), - "total_tasks": agent.total_tasks, - "success_rate": agent.success_rate, - "error_count": agent.error_count, - "learning_iterations": agent.learning_iterations, - "config": agent.config or {}, - }) + await memory.save_agent_profile( + { + "id": agent.id, + "name": agent.name, + "role": agent.role, + "nafs_level": agent.nafs_level, + "capabilities": list(agent.tools.keys()), + "total_tasks": agent.total_tasks, + "success_rate": agent.success_rate, + "error_count": agent.error_count, + "learning_iterations": agent.learning_iterations, + "config": agent.config or {}, + } + ) logger.info(f"[AGENTS] {len(active_agents)} agent profiles persisted") except Exception as e: logger.warning(f"[AGENTS] Failed to persist profiles: {e}") @@ -703,14 +707,16 @@ async def update_agent( if provider_obj: agent.ai_client = provider_obj - await memory.save_agent_profile({ - "id": agent.id, - "name": agent.name, - "role": agent.role, - "nafs_level": agent.nafs_level, - "capabilities": list(agent.tools.keys()), - "config": agent.config, - }) + await memory.save_agent_profile( + { + "id": agent.id, + "name": agent.name, + "role": agent.role, + "nafs_level": agent.nafs_level, + "capabilities": list(agent.tools.keys()), + "config": agent.config, + } + ) await manager.broadcast({"type": "agent_updated", "agent": agent.to_dict()}) return agent.to_dict() @@ -739,12 +745,14 @@ async def set_agent_model( agent.ai_client = provider agent.ai_model = req.model - await manager.broadcast({ - "type": "agent_model_changed", - "agent_id": agent_id, - "provider": req.provider, - "model": req.model, - }) + await manager.broadcast( + { + "type": "agent_model_changed", + "agent_id": agent_id, + "provider": req.provider, + "model": req.model, + } + ) wali.audit.log("agent_model_changed", {"agent_id": agent_id, "model": req.model}) return {"agent_id": agent_id, "provider": req.provider, "model": req.model} @@ -856,10 +864,7 @@ async def chat( # Auto-restore session from DB if not in memory (fixes cross-restart amnesia) if session is None: db_messages = await memory.get_messages(req.session_id, limit=50) - restored_history = [ - {"role": msg["role"], "content": msg["content"]} - for msg in db_messages - ] + restored_history = [{"role": msg["role"], "content": msg["content"]} for msg in db_messages] session = {"history": restored_history} if restored_history: logger.info( @@ -957,8 +962,11 @@ async def stream_cb(chunk: str, **kwargs): async def thinking_cb(phase: str, content: str, confidence: float, metadata: dict): thinking_stream.add_step( - rest_trace.request_id, ThinkingPhase(phase), - content, confidence, metadata, + rest_trace.request_id, + ThinkingPhase(phase), + content, + confidence, + metadata, ) await manager.broadcast( { @@ -975,7 +983,7 @@ async def thinking_cb(phase: str, content: str, confidence: float, metadata: dic try: result = await agent.execute( req.content, - {"history": session["history"][-agent.max_tool_turns:]}, + {"history": session["history"][-agent.max_tool_turns :]}, stream_callback=stream_cb, thinking_callback=thinking_cb, ) @@ -993,10 +1001,21 @@ async def thinking_cb(phase: str, content: str, confidence: float, metadata: dic session["history"].append({"role": "assistant", "content": str(final_response)}) # Extract cognitive metadata from QALB-7 pipeline - cognitive = {k: result.get(k) for k in ( - "nafs_level", "nafs_name", "ruh_energy", "qalb", "yaqin", - "mizan_label", "cognitive_method", "lubb", "lawwama", - ) if result.get(k) is not None} + cognitive = { + k: result.get(k) + for k in ( + "nafs_level", + "nafs_name", + "ruh_energy", + "qalb", + "yaqin", + "mizan_label", + "cognitive_method", + "lubb", + "lawwama", + ) + if result.get(k) is not None + } thinking_stream.complete(message_id) completed_trace = thinking_stream.get_trace(message_id) @@ -1006,9 +1025,12 @@ async def thinking_cb(phase: str, content: str, confidence: float, metadata: dic "request_id": completed_trace.request_id, "steps": [ { - "id": s.id, "phase": s.phase.value, - "content": s.content, "confidence": s.confidence, - "timestamp": s.timestamp, "metadata": s.metadata, + "id": s.id, + "phase": s.phase.value, + "content": s.content, + "confidence": s.confidence, + "timestamp": s.timestamp, + "metadata": s.metadata, } for s in completed_trace.steps ], @@ -1083,11 +1105,13 @@ async def execute_plan( async def run_plan(): result = await planner.execute_plan(plan, active_agents) - await manager.broadcast({ - "type": "plan_complete", - "plan_id": plan_id, - "result": result, - }) + await manager.broadcast( + { + "type": "plan_complete", + "plan_id": plan_id, + "result": result, + } + ) background_tasks.add_task(run_plan) return {"status": "executing", "plan_id": plan_id} @@ -1160,16 +1184,18 @@ async def list_memories(memory_type: str | None = None, limit: int = 30): content = json.loads(row[1]) if row[1] else None except Exception: content = row[1] - results.append({ - "id": row[0], - "content": str(content)[:500] if content else "", - "type": row[2], - "importance": row[3] or 0, - "recency": row[4] or "", - "access_count": row[5] or 0, - "agent_id": row[6], - "tags": json.loads(row[7]) if row[7] else [], - }) + results.append( + { + "id": row[0], + "content": str(content)[:500] if content else "", + "type": row[2], + "importance": row[3] or 0, + "recency": row[4] or "", + "access_count": row[5] or 0, + "agent_id": row[6], + "tags": json.loads(row[7]) if row[7] else [], + } + ) return {"results": results, "total": len(results)} @@ -1240,7 +1266,9 @@ async def ingest_knowledge(req: KnowledgeIngest): elif source_type == "url": result = await extract_url(req.source) else: - raise HTTPException(400, f"Use /api/knowledge/upload for file uploads. Got source_type: {source_type}") + raise HTTPException( + 400, f"Use /api/knowledge/upload for file uploads. Got source_type: {source_type}" + ) if "error" in result: raise HTTPException(422, result["error"]) @@ -1251,7 +1279,7 @@ async def ingest_knowledge(req: KnowledgeIngest): chunks = chunk_content(content) stored_ids = [] - for idx, chunk in enumerate(chunks): + for chunk in chunks: mem_id = await memory.remember( content=chunk, memory_type="semantic", @@ -1356,7 +1384,17 @@ async def list_knowledge_sources(): # === FILE UPLOAD (Rafi' - رافع) === -_ALLOWED_UPLOAD_SUFFIXES = {".pdf", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".txt", ".md", ".docx"} +_ALLOWED_UPLOAD_SUFFIXES = { + ".pdf", + ".png", + ".jpg", + ".jpeg", + ".gif", + ".webp", + ".txt", + ".md", + ".docx", +} _MAX_UPLOAD_BYTES = 20 * 1024 * 1024 # 20 MB @@ -1368,7 +1406,9 @@ async def upload_file( """Accept file uploads (PDF, images, docs) for processing.""" suffix = Path(file.filename or "").suffix.lower() if suffix not in _ALLOWED_UPLOAD_SUFFIXES: - raise HTTPException(400, f"Unsupported file type '{suffix}'. Allowed: {sorted(_ALLOWED_UPLOAD_SUFFIXES)}") + raise HTTPException( + 400, f"Unsupported file type '{suffix}'. Allowed: {sorted(_ALLOWED_UPLOAD_SUFFIXES)}" + ) content = await file.read() if len(content) > _MAX_UPLOAD_BYTES: @@ -1381,7 +1421,9 @@ async def upload_file( dest = upload_dir / f"{file_id}{suffix}" dest.write_bytes(content) - wali.audit.log("file_uploaded", {"file_id": file_id, "filename": file.filename, "size": len(content)}) + wali.audit.log( + "file_uploaded", {"file_id": file_id, "filename": file.filename, "size": len(content)} + ) return {"file_id": file_id, "filename": file.filename, "size": len(content), "path": str(dest)} @@ -1412,7 +1454,10 @@ async def list_provider_models( """ if provider_name == "openrouter": result = await fetch_openrouter_models( - limit=limit, offset=offset, search=search, free_only=free_only, + limit=limit, + offset=offset, + search=search, + free_only=free_only, ) return {"provider": "openrouter", **result} elif provider_name == "ollama": @@ -2251,6 +2296,31 @@ async def get_nafs_status(agent_id: str): } +@app.get("/api/nafs/{agent_id}/faculties") +async def get_agent_faculties(agent_id: str): + """Get al-Insan faculty state for an agent: Hikmah wisdom + deep-loop status. + + Basira (insight) and Hawa (restraint) are per-task signals returned in the + /api/chat and task results; this endpoint surfaces the persistent faculties. + """ + if agent_id not in active_agents: + raise HTTPException(404, "Agent not found") + agent = active_agents[agent_id] + hikmah_stats = {} + principles = [] + try: + hikmah_stats = agent.hikmah_engine.stats() + principles = [p.to_dict() for p in agent.hikmah_engine.distill()[:10]] + except Exception: + pass + return { + "agent_id": agent_id, + "deep_faculty_loop": getattr(agent, "deep_faculty_loop", False), + "hikmah": hikmah_stats, + "applicable_principles": principles, + } + + # ===== YAQIN CERTAINTY ENDPOINTS ===== @@ -2378,9 +2448,12 @@ async def federation_route_task( # Register all agents with federation first for aid, agent in active_agents.items(): federation.register_agent( - aid, agent.name, agent.role, + aid, + agent.name, + agent.role, list(agent.tools.keys()), - agent.nafs_level, agent.success_rate, + agent.nafs_level, + agent.success_rate, ) # Discover best agent for this task @@ -2411,7 +2484,9 @@ async def federation_route_task( result = await agent.execute(req.task, {"history": []}) if result.get("success"): await memory.save_message(session_id, "user", req.task) - await memory.save_message(session_id, "assistant", str(result.get("result", ""))[:5000], agent_id) + await memory.save_message( + session_id, "assistant", str(result.get("result", ""))[:5000], agent_id + ) return { "routed_to": agent.name, @@ -2513,72 +2588,94 @@ async def ruh_tokenize(req: RuhTokenizeRequest): raise HTTPException(500, f"Tokenization failed: {exc}") from exc -def _build_tokenizer_pipeline_trace( - text: str, tokenizer: Any -) -> list[dict[str, Any]]: +def _build_tokenizer_pipeline_trace(text: str, tokenizer: Any) -> list[dict[str, Any]]: """Build a step-by-step pipeline trace for detailed tokenization.""" - import re steps: list[dict[str, Any]] = [] # Step 1: Language detection per word from ruh_model.tokenizer.bayan import ( - _contains_arabic, _is_stopword, _tokenize_text, + _contains_arabic, + _is_stopword, + _tokenize_text, ) + words = _tokenize_text(text) lang_detections = [] for word in words: is_ar = _contains_arabic(word) - lang_detections.append({ - "word": word, - "language": "arabic" if is_ar else "english", - "is_stopword": _is_stopword(word), - }) - steps.append({ - "phase": "language_detection", - "description": "Detect language per word (Arabic vs English)", - "output": lang_detections, - }) + lang_detections.append( + { + "word": word, + "language": "arabic" if is_ar else "english", + "is_stopword": _is_stopword(word), + } + ) + steps.append( + { + "phase": "language_detection", + "description": "Detect language per word (Arabic vs English)", + "output": lang_detections, + } + ) # Step 2: Morphological analysis morph_results = [] for info in lang_detections: word = info["word"] if info["is_stopword"]: - morph_results.append({"word": word, "type": "stopword", "root": "", "pattern": "STOPWORD"}) + morph_results.append( + {"word": word, "type": "stopword", "root": "", "pattern": "STOPWORD"} + ) continue if info["language"] == "arabic": root_str, pattern_name = tokenizer._arabic_analyzer.analyze(word) - morph_results.append({ - "word": word, "type": "arabic_morphology", - "root": root_str, "pattern": pattern_name, - }) + morph_results.append( + { + "word": word, + "type": "arabic_morphology", + "root": root_str, + "pattern": pattern_name, + } + ) else: root_str, pattern_name = tokenizer._english_bridge.to_root(word) - morph_results.append({ - "word": word, "type": "english_bridge", - "root": root_str, "pattern": pattern_name, - }) - steps.append({ - "phase": "morphology", - "description": "Extract roots and patterns via morphological analysis", - "output": morph_results, - }) + morph_results.append( + { + "word": word, + "type": "english_bridge", + "root": root_str, + "pattern": pattern_name, + } + ) + steps.append( + { + "phase": "morphology", + "description": "Extract roots and patterns via morphological analysis", + "output": morph_results, + } + ) # Step 3: Root/pattern ID mapping id_results = [] for morph in morph_results: root_id = tokenizer._vocab.get_root_id(morph["root"]) if morph["root"] else 0 pattern_id = tokenizer._vocab.get_pattern_id(morph["pattern"]) - id_results.append({ - "word": morph["word"], "root": morph["root"], - "pattern": morph["pattern"], - "root_id": root_id, "pattern_id": pattern_id, - }) - steps.append({ - "phase": "id_mapping", - "description": "Map roots and patterns to integer IDs", - "output": id_results, - }) + id_results.append( + { + "word": morph["word"], + "root": morph["root"], + "pattern": morph["pattern"], + "root_id": root_id, + "pattern_id": pattern_id, + } + ) + steps.append( + { + "phase": "id_mapping", + "description": "Map roots and patterns to integer IDs", + "output": id_results, + } + ) # Step 4: Q28 articulatory features (sample for first few words) q28_results = [] @@ -2587,27 +2684,37 @@ def _build_tokenizer_pipeline_trace( if info["is_stopword"]: continue try: - coords = tokenizer._q28.text_to_q28(word, lang="ar" if info["language"] == "arabic" else "en") - q28_results.append({ - "word": word, - "features": [c.tolist() if hasattr(c, "tolist") else list(c) for c in coords] if coords else [], - "dimensions": ["place", "manner", "voicing", "nasality", "emphasis", "length"], - }) + coords = tokenizer._q28.text_to_q28( + word, lang="ar" if info["language"] == "arabic" else "en" + ) + q28_results.append( + { + "word": word, + "features": [c.tolist() if hasattr(c, "tolist") else list(c) for c in coords] + if coords + else [], + "dimensions": ["place", "manner", "voicing", "nasality", "emphasis", "length"], + } + ) except Exception: q28_results.append({"word": word, "features": [], "error": "Q28 analysis unavailable"}) - steps.append({ - "phase": "articulatory_features", - "description": "Q28 articulatory basis: 6D feature vectors per phoneme", - "output": q28_results, - }) + steps.append( + { + "phase": "articulatory_features", + "description": "Q28 articulatory basis: 6D feature vectors per phoneme", + "output": q28_results, + } + ) # Step 5: Final token pairs tokens = tokenizer.encode(text) - steps.append({ - "phase": "final_tokens", - "description": "Final (root_id, pattern_id) pairs with BOS/EOS", - "output": [{"root_id": r, "pattern_id": p} for r, p in tokens], - }) + steps.append( + { + "phase": "final_tokens", + "description": "Final (root_id, pattern_id) pairs with BOS/EOS", + "output": [{"root_id": r, "pattern_id": p} for r, p in tokens], + } + ) return steps @@ -2641,14 +2748,14 @@ async def tasrif_demo(req: TasrifDemoRequest): for op_name in req.operators: prev = current.copy() current = engine.apply(op_name, current) - results.append({ - "operator": op_name, - "input": prev.tolist(), - "output": current.tolist(), - "changed_dims": [ - i for i in range(6) if abs(prev[i] - current[i]) > 0.001 - ], - }) + results.append( + { + "operator": op_name, + "input": prev.tolist(), + "output": current.tolist(), + "changed_dims": [i for i in range(6) if abs(prev[i] - current[i]) > 0.001], + } + ) return { "original": features.tolist(), @@ -2723,12 +2830,14 @@ async def enqueue_task( priority = PRIORITY_MAP.get(req.priority.lower(), TaskPriority.TAHSINIYYAH) payload = {"task": req.task, "agent_id": req.agent_id, **req.metadata} task_id = await task_queue.enqueue(payload, priority, req.agent_id) - await manager.broadcast({ - "type": "queue_update", - "action": "enqueued", - "task_id": task_id, - "pending": task_queue.pending_count, - }) + await manager.broadcast( + { + "type": "queue_update", + "action": "enqueued", + "task_id": task_id, + "pending": task_queue.pending_count, + } + ) return {"task_id": task_id, "priority": req.priority, "status": "queued"} @@ -2736,7 +2845,7 @@ async def enqueue_task( async def list_queued_tasks(status: str | None = None, limit: int = 50): """List tasks in the queue, optionally filtered by status.""" tasks = await task_queue.list_tasks(status=status) - tasks = tasks[:min(limit, 200)] + tasks = tasks[: min(limit, 200)] return {"tasks": [_task_to_dict(t) for t in tasks], "count": len(tasks)} @@ -2781,12 +2890,14 @@ async def cancel_queued_task( if not cancelled: raise HTTPException(404, f"Task '{task_id}' not found or not cancellable") wali.audit.log("task_cancelled", {"task_id": task_id}) - await manager.broadcast({ - "type": "queue_update", - "action": "cancelled", - "task_id": task_id, - "pending": task_queue.pending_count, - }) + await manager.broadcast( + { + "type": "queue_update", + "action": "cancelled", + "task_id": task_id, + "pending": task_queue.pending_count, + } + ) return {"task_id": task_id, "status": "cancelled"} @@ -2915,13 +3026,15 @@ async def list_checkpoints(): except (json.JSONDecodeError, OSError): pass - checkpoints.append({ - "name": entry.name, - "created_at": entry.stat().st_mtime, - "size_mb": round(total_size / (1024 * 1024), 2), - "max_seq_len": config_data.get("max_seq_len"), - "config": config_data, - }) + checkpoints.append( + { + "name": entry.name, + "created_at": entry.stat().st_mtime, + "size_mb": round(total_size / (1024 * 1024), 2), + "max_seq_len": config_data.get("max_seq_len"), + "config": config_data, + } + ) return {"checkpoints": checkpoints} @@ -2936,13 +3049,15 @@ async def get_data_stats(): if data_dir.exists(): for fpath in data_dir.glob("*.jsonl"): line_count = sum(1 for _ in open(fpath, encoding="utf-8")) - sources.append({ - "name": fpath.stem, - "type": "seed", - "samples": line_count, - "size_mb": round(fpath.stat().st_size / (1024 * 1024), 2), - "available": True, - }) + sources.append( + { + "name": fpath.stem, + "type": "seed", + "samples": line_count, + "size_mb": round(fpath.stat().st_size / (1024 * 1024), 2), + "available": True, + } + ) # Registered HuggingFace dataset slots hf_datasets = [ @@ -2953,15 +3068,17 @@ async def get_data_stats(): {"name": "tashkeela", "weight": 0.10, "hf_id": "tashkeela"}, ] for ds in hf_datasets: - sources.append({ - "name": ds["name"], - "type": "huggingface", - "weight": ds["weight"], - "hf_id": ds["hf_id"], - "samples": 0, - "size_mb": 0, - "available": False, # Would check actual availability - }) + sources.append( + { + "name": ds["name"], + "type": "huggingface", + "weight": ds["weight"], + "hf_id": ds["hf_id"], + "samples": 0, + "size_mb": 0, + "available": False, # Would check actual availability + } + ) total_samples = sum(s["samples"] for s in sources) total_size = sum(s["size_mb"] for s in sources) @@ -2986,17 +3103,20 @@ async def get_model_architecture(): components = [] try: from ruh_model.model import RuhModel + model = RuhModel(config) total_params = model.count_parameters() # Per-module parameter counts for name, module in model.named_children(): param_count = sum(p.numel() for p in module.parameters()) - components.append({ - "name": name, - "type": type(module).__name__, - "params": param_count, - }) + components.append( + { + "name": name, + "type": type(module).__name__, + "params": param_count, + } + ) except Exception: total_params = 0 @@ -3013,12 +3133,21 @@ async def get_model_architecture(): "components": components, "architecture_layers": [ {"name": "ISMEmbedding", "desc": "Factored (root, pattern) → d_model embedding"}, - {"name": "SamBasarDual", "desc": "Dual attention: causal (Sam') + bidirectional (Basar)"}, + { + "name": "SamBasarDual", + "desc": "Dual attention: causal (Sam') + bidirectional (Basar)", + }, {"name": "QalbAttention ×7", "desc": "Cardiac-oscillation modulated attention"}, - {"name": "ISMFFN / ShuraMoE", "desc": "SwiGLU FFN or Mixture-of-Experts (every 2 blocks)"}, + { + "name": "ISMFFN / ShuraMoE", + "desc": "SwiGLU FFN or Mixture-of-Experts (every 2 blocks)", + }, {"name": "LubbMetacognition", "desc": "Confidence estimation layer"}, {"name": "AdaptiveDepth", "desc": "Early exit based on confidence threshold"}, - {"name": "MizanLoss", "desc": "5-component loss: CE + Calibration + Consistency + Fitrah + Hisbah"}, + { + "name": "MizanLoss", + "desc": "5-component loss: CE + Calibration + Consistency + Fitrah + Hisbah", + }, ], } except ImportError as exc: @@ -3028,28 +3157,6 @@ async def get_model_architecture(): raise HTTPException(500, f"Failed to get architecture: {exc}") from exc -@app.get("/api/learner/stats") -async def get_learner_stats(): - """Get learner interaction statistics.""" - try: - from learner.data_export import DataExporter - exporter = DataExporter() - stats = exporter.get_stats() - return stats - except ImportError: - return { - "total_interactions": 0, - "exportable_count": 0, - "last_export": None, - } - except Exception: - return { - "total_interactions": 0, - "exportable_count": 0, - "last_export": None, - } - - # === PLUGINS (Wasilah - وسيلة) === @@ -3358,10 +3465,21 @@ async def ws_stream(chunk, _sid=session_id, _mid=message_id, **kwargs): }, ) - async def ws_thinking(phase: str, content: str, confidence: float, metadata: dict): + async def ws_thinking( + phase: str, + content: str, + confidence: float, + metadata: dict, + _trace=trace, + _mid=message_id, + _sid=session_id, + ): thinking_stream.add_step( - trace.request_id, ThinkingPhase(phase), - content, confidence, metadata, + _trace.request_id, + ThinkingPhase(phase), + content, + confidence, + metadata, ) await manager.send( client_id, @@ -3370,15 +3488,15 @@ async def ws_thinking(phase: str, content: str, confidence: float, metadata: dic "phase": phase, "content": content, "confidence": confidence, - "message_id": message_id, - "session_id": session_id, + "message_id": _mid, + "session_id": _sid, "metadata": metadata, }, ) result = await agent.execute( content, - {"history": session["history"][-agent.max_tool_turns:]}, + {"history": session["history"][-agent.max_tool_turns :]}, stream_callback=ws_stream, thinking_callback=ws_thinking, ) @@ -3391,10 +3509,21 @@ async def ws_thinking(phase: str, content: str, confidence: float, metadata: dic session["history"].append({"role": "assistant", "content": str(final)}) # Extract cognitive metadata from QALB-7 pipeline - cognitive = {k: result.get(k) for k in ( - "nafs_level", "nafs_name", "ruh_energy", "qalb", "yaqin", - "mizan_label", "cognitive_method", "lubb", "lawwama", - ) if result.get(k) is not None} + cognitive = { + k: result.get(k) + for k in ( + "nafs_level", + "nafs_name", + "ruh_energy", + "qalb", + "yaqin", + "mizan_label", + "cognitive_method", + "lubb", + "lawwama", + ) + if result.get(k) is not None + } # Complete thinking trace and include in response thinking_stream.complete(message_id) @@ -3405,7 +3534,8 @@ async def ws_thinking(phase: str, content: str, confidence: float, metadata: dic "request_id": completed_trace.request_id, "steps": [ { - "id": s.id, "phase": s.phase.value, + "id": s.id, + "phase": s.phase.value, "content": s.content, "confidence": s.confidence, "timestamp": s.timestamp, diff --git a/backend/cognitive/thinking_stream.py b/backend/cognitive/thinking_stream.py index 2360202..2e8b828 100644 --- a/backend/cognitive/thinking_stream.py +++ b/backend/cognitive/thinking_stream.py @@ -5,6 +5,7 @@ Generates structured thinking events from the QCA pipeline, enabling transparent AI reasoning visible to the user. """ + from __future__ import annotations import time @@ -184,9 +185,7 @@ def generate_thinking_events( trace = stream.create_trace() rid = trace.request_id - stream.add_step( - rid, ThinkingPhase.PERCEPTION, f"Received: '{query[:80]}...'", 0.9 - ) + stream.add_step(rid, ThinkingPhase.PERCEPTION, f"Received: '{query[:80]}...'", 0.9) if context: stream.add_step( @@ -196,15 +195,9 @@ def generate_thinking_events( 0.7, ) - stream.add_step( - rid, ThinkingPhase.COMPREHENSION, "Understanding intent and context", 0.8 - ) - stream.add_step( - rid, ThinkingPhase.REASONING, "Processing through cognitive layers", 0.75 - ) - stream.add_step( - rid, ThinkingPhase.EVALUATION, "Assessing response quality", 0.85 - ) + stream.add_step(rid, ThinkingPhase.COMPREHENSION, "Understanding intent and context", 0.8) + stream.add_step(rid, ThinkingPhase.REASONING, "Processing through cognitive layers", 0.75) + stream.add_step(rid, ThinkingPhase.EVALUATION, "Assessing response quality", 0.85) stream.add_step(rid, ThinkingPhase.GENERATION, "Forming response", 0.8) stream.complete(rid) diff --git a/backend/core/basira.py b/backend/core/basira.py new file mode 100644 index 0000000..ef4840b --- /dev/null +++ b/backend/core/basira.py @@ -0,0 +1,329 @@ +""" +Basira Engine (بصيرة) — Insight & Self-Witnessing +=================================================== + +"I call to Allah upon clear insight (basira), I and those who follow me." +— Quran 12:108 +"Rather, the human, against himself, is a witness (basira)." — Quran 75:14 + +Basira is *inner sight* — discernment of the real situation behind the surface, +plus the conscience that witnesses one's own reasoning. Where Lubb checks the +*quality* of a trace and Fu'ad measures *conviction* from evidence, Basira asks +two harder questions: + + 1. Is this conclusion actually *sound*, or does it merely look confident? + 2. Is my own reasoning *self-serving* — am I believing what I want to believe? + +It synthesizes three upstream signals into one discernment: + - coherence (from Lubb metacognition) + - conviction (from Fu'ad evidence assessment) + - evidence density + self-witness scan (computed here) + +Basira does not replace Lubb or Fu'ad; it integrates them and adds the +self-witnessing dimension (75:14) that neither covers. +""" + +import logging +import re +from dataclasses import dataclass, field +from enum import Enum + +logger = logging.getLogger("mizan.basira") + + +class InsightLevel(Enum): + CLEAR = "clear" # Sound reasoning, grounded in evidence, no self-deception + PARTIAL = "partial" # Reasonable but with blind spots or thin evidence + CLOUDED = "clouded" # Unsound, self-serving, or unsupported — do not trust + + +# Phrases where the agent praises/justifies its *own* reasoning rather than the +# evidence — the marker of a self-serving (non-witnessing) trace (cf. 75:14). +_SELF_SERVING_SIGNALS = [ + "as i expected", + "as i predicted", + "i was right", + "this proves i", + "just as i said", + "i already knew", + "obviously correct", + "clearly i", + "my answer is definitely", + "no need to check", + "no need to verify", + "i'm confident this is correct", +] + +# Words that signal the *surface* hides a risk the reasoning may be glossing over. +_HIDDEN_RISK_SIGNALS = [ + "error", + "failed", + "exception", + "denied", + "timeout", + "not found", + "null", + "undefined", + "conflict", + "deprecated", +] + +# Words that signal the agent considered alternatives / its own fallibility — +# the *presence* of witnessing. These raise discernment. +_WITNESS_SIGNALS = [ + "however", + "on the other hand", + "alternatively", + "i might be wrong", + "let me verify", + "let me check", + "this assumes", + "one caveat", + "to confirm", + "counterexample", + "if instead", +] + + +@dataclass +class DiscernmentReport: + """Result of a Basira discernment pass.""" + + level: InsightLevel + soundness: float # 0.0 – 1.0, overall trustworthiness + is_self_serving: bool # True if the trace believes-what-it-wants + deeper_reading: str # what the surface may be hiding + blind_spots: list[str] = field(default_factory=list) + witnessing_score: float = 0.0 # 0 = no self-scrutiny, 1 = strong + recommendation: str = "" # actionable next step + + def to_dict(self) -> dict: + return { + "level": self.level.value, + "soundness": round(self.soundness, 3), + "is_self_serving": self.is_self_serving, + "witnessing_score": round(self.witnessing_score, 3), + "blind_spots": self.blind_spots[:5], + "deeper_reading": self.deeper_reading, + "recommendation": self.recommendation, + } + + +class BasiraEngine: + """ + Inner-sight discernment for the MIZAN reasoning system. + + Usage: + basira = BasiraEngine() + report = basira.discern( + trace=response_text, + evidence=["tool:bash:...", "tool:http_get:..."], + coherence_score=lubb_report.coherence.score, + conviction_confidence=fuad_assessment.confidence, + ) + if report.level is InsightLevel.CLOUDED: + # re-examine before trusting the conclusion + ... + """ + + # Soundness blend weights — coherence, conviction, evidence density + _W_COHERENCE = 0.4 + _W_CONVICTION = 0.35 + _W_EVIDENCE = 0.25 + + def discern( + self, + trace: str, + evidence: list[str] | None = None, + coherence_score: float | None = None, + conviction_confidence: float | None = None, + task: str = "", + ) -> DiscernmentReport: + """ + Form a discernment from a reasoning trace and its supporting signals. + + coherence_score / conviction_confidence are optional — when not supplied + (e.g. Lubb/Fu'ad not run), Basira falls back to its own estimates so it + is usable standalone. + """ + evidence = evidence or [] + trace = trace or "" + lower = trace.lower() + + # 1. Self-witnessing scan (75:14) — does the trace scrutinize itself? + self_serving_hits = [s for s in _SELF_SERVING_SIGNALS if s in lower] + witness_hits = [w for w in _WITNESS_SIGNALS if w in lower] + is_self_serving = bool(self_serving_hits) and not witness_hits + witnessing_score = self._witnessing_score(witness_hits, self_serving_hits) + + # 2. Evidence density — strong claims need backing + evidence_density = self._evidence_density(trace, evidence) + + # 3. Deeper reading — does the surface hide a risk the conclusion ignores? + deeper_reading, surface_mismatch = self._read_beneath(trace, task) + + # 4. Blind spots + blind_spots = self._find_blind_spots(trace, evidence, self_serving_hits, surface_mismatch) + + # 5. Soundness blend (fall back to internal estimates when missing) + coherence = ( + coherence_score if coherence_score is not None else self._estimate_coherence(trace) + ) + conviction = ( + conviction_confidence if conviction_confidence is not None else evidence_density + ) + soundness = ( + self._W_COHERENCE * coherence + + self._W_CONVICTION * conviction + + self._W_EVIDENCE * evidence_density + ) + # Penalties: self-deception and surface mismatch corrode discernment + if is_self_serving: + soundness -= 0.20 + if surface_mismatch: + soundness -= 0.15 + soundness -= 0.05 * len(blind_spots) + # Reward genuine self-witnessing + soundness += 0.05 * min(2, len(witness_hits)) + soundness = max(0.0, min(1.0, soundness)) + + # 6. Classify + if soundness >= 0.7 and not is_self_serving: + level = InsightLevel.CLEAR + elif soundness >= 0.45: + level = InsightLevel.PARTIAL + else: + level = InsightLevel.CLOUDED + + recommendation = self._recommend(level, is_self_serving, blind_spots, surface_mismatch) + + logger.debug( + "[BASIRA] level=%s soundness=%.2f self_serving=%s blind_spots=%d", + level.value, + soundness, + is_self_serving, + len(blind_spots), + ) + return DiscernmentReport( + level=level, + soundness=round(soundness, 3), + is_self_serving=is_self_serving, + deeper_reading=deeper_reading, + blind_spots=blind_spots[:5], + witnessing_score=round(witnessing_score, 3), + recommendation=recommendation, + ) + + # ── internals ────────────────────────────────────────────── + + @staticmethod + def _witnessing_score(witness_hits: list[str], self_serving_hits: list[str]) -> float: + """0 = no self-scrutiny, 1 = strong self-witnessing (more witness, less self-serving).""" + score = 0.5 + 0.15 * len(witness_hits) - 0.20 * len(self_serving_hits) + return max(0.0, min(1.0, score)) + + @staticmethod + def _evidence_density(trace: str, evidence: list[str]) -> float: + """ + How well-grounded the trace is. Counts explicit tool evidence and + external references against the volume of assertion. + """ + tool_refs = trace.lower().count("[tool:") + len(evidence) + url_refs = len(re.findall(r"https?://", trace)) + grounded = tool_refs + url_refs + if grounded == 0: + # No evidence at all — density floor depends on claim strength + strong_claims = len( + re.findall(r"\b(definitely|certainly|always|never|guaranteed)\b", trace.lower()) + ) + return max(0.1, 0.4 - 0.1 * strong_claims) + # Each independent piece of grounding closes 30% of the gap to 1.0 + density = 0.4 + for _ in range(min(grounded, 6)): + density += (1.0 - density) * 0.30 + return min(0.95, density) + + @staticmethod + def _estimate_coherence(trace: str) -> float: + """Lightweight standalone coherence estimate when Lubb is unavailable.""" + if not trace: + return 0.3 + overconfident = len( + re.findall(r"\b(definitely|certainly|100%|guaranteed|impossible)\b", trace.lower()) + ) + return max(0.3, 0.75 - 0.1 * overconfident) + + @staticmethod + def _read_beneath(trace: str, task: str) -> tuple[str, bool]: + """ + Look beneath the surface: if the trace/task contains risk signals but the + conclusion sounds untroubled, that mismatch is what Basira should surface. + """ + combined = (task + " " + trace).lower() + risks = [r for r in _HIDDEN_RISK_SIGNALS if r in combined] + sounds_fine = any( + p in trace.lower() + for p in ["success", "done", "completed", "works", "all good", "no problem"] + ) + if risks and sounds_fine: + return ( + f"Surface reads as success, but the trace contains risk signals " + f"({', '.join(risks[:3])}). The real situation may be unresolved.", + True, + ) + if risks: + return ( + f"Underlying risk signals present: {', '.join(risks[:3])}. " + f"Confirm these are actually handled.", + False, + ) + return ("Surface and substance appear aligned.", False) + + @staticmethod + def _find_blind_spots( + trace: str, + evidence: list[str], + self_serving_hits: list[str], + surface_mismatch: bool, + ) -> list[str]: + spots: list[str] = [] + lower = trace.lower() + + if not evidence and "[tool:" not in lower and len(trace) > 200: + spots.append("Conclusion drawn without any tool/external evidence") + if self_serving_hits: + spots.append( + f"Self-confirming language ({self_serving_hits[0]}) — possible motivated reasoning" + ) + if surface_mismatch: + spots.append("Optimistic conclusion despite unresolved risk signals") + # Single-source reliance + if len(evidence) == 1: + spots.append("Single source of evidence — no independent corroboration") + # No counterfactual considered + if not any( + w in lower for w in ["if not", "otherwise", "what if", "unless", "could be wrong"] + ): + if len(trace) > 150: + spots.append("No counterfactual considered ('what if this is wrong?')") + return spots + + @staticmethod + def _recommend( + level: InsightLevel, + is_self_serving: bool, + blind_spots: list[str], + surface_mismatch: bool, + ) -> str: + if level is InsightLevel.CLEAR: + return "Discernment is clear — proceed, stating confidence honestly." + if is_self_serving: + return ( + "Step back from the conclusion you want to be true; seek a " + "counterexample or independent check before committing." + ) + if surface_mismatch: + return "Verify the unresolved risk signals before reporting success." + if blind_spots: + return f"Address the blind spot first: {blind_spots[0]}" + return "Gather one more independent piece of evidence to firm up the conclusion." diff --git a/backend/core/creativity.py b/backend/core/creativity.py index 12953a0..d6f47fc 100644 --- a/backend/core/creativity.py +++ b/backend/core/creativity.py @@ -34,16 +34,14 @@ import logging import math import random -import time -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum -from typing import Any logger = logging.getLogger("mizan.creativity") # Creativity landscape weights -ALPHA_NOVELTY = 0.4 # novelty importance -BETA_UTILITY = 0.5 # utility importance +ALPHA_NOVELTY = 0.4 # novelty importance +BETA_UTILITY = 0.5 # utility importance # Feasibility has implicit weight: 1 - α - β = 0.1 (or computed as multiplier) # Creative oscillation period (interactions) @@ -58,18 +56,19 @@ class CreationMode(Enum): - BADI = "badi" # Radical origination (Badī') - KHALQ = "khalq" # Measured evolutionary creation - JAL = "jal" # Recombination / analogy (Jaʿl) - SUNW = "sunw" # Refinement / polish (Ṣunʿ) - TASWIR = "taswir" # Visualization (Taṣwīr) + BADI = "badi" # Radical origination (Badī') + KHALQ = "khalq" # Measured evolutionary creation + JAL = "jal" # Recombination / analogy (Jaʿl) + SUNW = "sunw" # Refinement / polish (Ṣunʿ) + TASWIR = "taswir" # Visualization (Taṣwīr) @dataclass class ConceptVector: """A concept in the creativity latent space.""" + label: str - features: dict[str, float] # semantic feature dimensions + features: dict[str, float] # semantic feature dimensions novelty_score: float = 0.0 utility_score: float = 0.0 feasibility_score: float = 0.0 @@ -77,8 +76,8 @@ class ConceptVector: def landscape_score(self, alpha: float = ALPHA_NOVELTY, beta: float = BETA_UTILITY) -> float: """Ψ(z) = U^β × N^α × F""" return ( - (self.utility_score ** beta) - * (self.novelty_score ** alpha) + (self.utility_score**beta) + * (self.novelty_score**alpha) * max(0.01, self.feasibility_score) ) @@ -86,9 +85,10 @@ def landscape_score(self, alpha: float = ALPHA_NOVELTY, beta: float = BETA_UTILI @dataclass class BisociationResult: """Result of bisociation (Koestler): two matrices of thought intersect.""" + concept_a: str concept_b: str - intersection: str # the "aha" moment + intersection: str # the "aha" moment novelty: float metaphor: str @@ -180,8 +180,11 @@ def create( logger.debug( "[IBDA] mode=%s Ψ=%.3f N=%.3f U=%.3f F=%.3f", - mode.value, result.landscape_score, result.novelty, - result.utility, result.feasibility, + mode.value, + result.landscape_score, + result.novelty, + result.utility, + result.feasibility, ) return result @@ -200,9 +203,7 @@ def _select_mode(self) -> CreationMode: modes = list(CreationMode) return modes[mode_idx] - def _build_concept_vector( - self, task: str, fragments: list[str] - ) -> ConceptVector: + def _build_concept_vector(self, task: str, fragments: list[str]) -> ConceptVector: """Build feature vector for task in concept space.""" words = task.lower().split() features = { @@ -210,7 +211,9 @@ def _build_concept_vector( "novelty_seed": (hash(task) % 1000) / 1000.0, "fragment_richness": min(1.0, len(fragments) / 5.0), "question_mark": 1.0 if "?" in task else 0.0, - "imperative": 1.0 if words[0] in {"create", "build", "design", "make", "write"} else 0.0, + "imperative": 1.0 + if words[0] in {"create", "build", "design", "make", "write"} + else 0.0, } return ConceptVector(label=task[:50], features=features) @@ -232,7 +235,11 @@ def _badi_origination( # Radical idea generation: invert standard approaches inversions = self._generate_inversions(task) primary_idea = inversions[0] if inversions else f"Radical reframe of: {task[:80]}" - elaboration = " | ".join(inversions[:3]) if inversions else "No known solutions — pure origination territory" + elaboration = ( + " | ".join(inversions[:3]) + if inversions + else "No known solutions — pure origination territory" + ) bisociations = self._find_bisociations(task, n=2) @@ -264,9 +271,9 @@ def _khalq_evolution( # Generate initial population population = [self._generate_candidate(task, constraints, mutation=MUTATION_RATE)] for _ in range(2): - population.append(self._generate_candidate( - task, constraints, mutation=MUTATION_RATE * 1.5 - )) + population.append( + self._generate_candidate(task, constraints, mutation=MUTATION_RATE * 1.5) + ) best = population[0] best_score = 0.0 @@ -274,19 +281,14 @@ def _khalq_evolution( for gen in range(generations): # Score and select - scored = [ - (c, self._score_candidate(c, task, constraints)) - for c in population - ] + scored = [(c, self._score_candidate(c, task, constraints)) for c in population] scored.sort(key=lambda x: x[1], reverse=True) best, best_score = scored[0] # Mutate survivors into next generation - survivors = [c for c, _ in scored[:max(1, len(scored) // 2)]] + survivors = [c for c, _ in scored[: max(1, len(scored) // 2)]] mutation = MUTATION_RATE * (1.0 - gen / generations * SELECTION_PRESSURE) - population = survivors + [ - self._mutate_candidate(s, mutation) for s in survivors - ] + population = survivors + [self._mutate_candidate(s, mutation) for s in survivors] iterations = gen + 1 bisociations = self._find_bisociations(task, n=1) @@ -389,9 +391,7 @@ def _sunw_refinement( ), ) - def _taswir_visualization( - self, task: str, concept: ConceptVector - ) -> CreativeOutput: + def _taswir_visualization(self, task: str, concept: ConceptVector) -> CreativeOutput: """ Taṣwīr: Creative visualization — generates a rich mental image. Delegates to imagination engine in full integration. @@ -430,20 +430,21 @@ def _find_bisociations(self, task: str, n: int = 2) -> list[BisociationResult]: ] results = [] - task_lower = task.lower() for domain_a, domain_b in domain_pairs[:n]: intersection = f"The {domain_a} metaphor applied to {task[:40]}: {domain_b} lens" metaphor = f"Like {domain_a} processes, this task involves {domain_b} principles" novelty = 0.6 + (hash(domain_a + task) % 30) / 100.0 - results.append(BisociationResult( - concept_a=domain_a, - concept_b=domain_b, - intersection=intersection, - novelty=round(min(0.95, novelty), 3), - metaphor=metaphor, - )) + results.append( + BisociationResult( + concept_a=domain_a, + concept_b=domain_b, + intersection=intersection, + novelty=round(min(0.95, novelty), 3), + metaphor=metaphor, + ) + ) return results @@ -452,8 +453,8 @@ def _blend_concepts(self, concept_a: str, concept_b: str) -> str: words_a = set(concept_a.lower().split()[:5]) words_b = set(concept_b.lower().split()[:5]) shared = words_a & words_b - unique_a = (words_a - words_b) - unique_b = (words_b - words_a) + unique_a = words_a - words_b + unique_b = words_b - words_a if shared: return f"Shared structure [{', '.join(list(shared)[:2])}] bridging [{', '.join(list(unique_a)[:2])}] and [{', '.join(list(unique_b)[:2])}]" @@ -468,9 +469,7 @@ def _generate_inversions(self, task: str) -> list[str]: ] return inversions - def _generate_candidate( - self, task: str, constraints: list[str], mutation: float - ) -> str: + def _generate_candidate(self, task: str, constraints: list[str], mutation: float) -> str: """Generate a candidate solution for evolutionary selection.""" seed = f"Approach {random.random():.2f}: {task[:60]}" if constraints: @@ -485,17 +484,13 @@ def _mutate_candidate(self, candidate: str, mutation_rate: float) -> str: words[idx] = f"[mutated:{words[idx]}]" return " ".join(words) - def _score_candidate( - self, candidate: str, task: str, constraints: list[str] - ) -> float: + def _score_candidate(self, candidate: str, task: str, constraints: list[str]) -> float: """Fitness function for evolutionary selection.""" utility = self._estimate_utility(task, candidate, constraints) feasibility = self._estimate_feasibility(candidate, constraints) return utility * BETA_UTILITY + feasibility * (1.0 - BETA_UTILITY) - def _identify_imperfections( - self, idea: str, constraints: list[str] - ) -> list[str]: + def _identify_imperfections(self, idea: str, constraints: list[str]) -> list[str]: """Find imperfections in current idea relative to constraints.""" imperfections = [] if len(idea.split()) > 20: @@ -509,7 +504,7 @@ def _identify_imperfections( def _apply_refinement(self, idea: str, imperfection: str) -> str: """Apply targeted fix to an imperfection.""" if imperfection == "verbosity": - return idea[:len(idea) // 2] + " [condensed]" + return idea[: len(idea) // 2] + " [condensed]" if imperfection == "lack of novelty": return idea.replace("standard", "novel") return idea + f" [refined: {imperfection} addressed]" @@ -520,16 +515,16 @@ def _compute_elegance(self, idea: str, constraints: list[str]) -> float: complexity = min(1.0, len(idea.split()) / 30.0) return utility / max(complexity, 0.1) - def _estimate_utility( - self, task: str, idea: str, constraints: list[str] - ) -> float: + def _estimate_utility(self, task: str, idea: str, constraints: list[str]) -> float: """Estimate how useful the idea is for the task.""" if not task: return 0.6 task_words = set(task.lower().split()) idea_words = set(idea.lower().split()) overlap = len(task_words & idea_words) / max(len(task_words), 1) - constraint_penalty = 0.1 * max(0, len(constraints) - len(idea_words & set(" ".join(constraints).lower().split()))) + constraint_penalty = 0.1 * max( + 0, len(constraints) - len(idea_words & set(" ".join(constraints).lower().split())) + ) return max(0.1, min(0.95, 0.4 + 0.5 * overlap - constraint_penalty)) def _estimate_feasibility(self, idea: str, constraints: list[str]) -> float: @@ -549,10 +544,7 @@ def _min_distance_to_known(self, concept: ConceptVector) -> float: if not self.known_solutions: return 1.0 # maximum distance — all is novel seed = concept.features.get("novelty_seed", 0.5) - distances = [ - abs(seed - k.features.get("novelty_seed", 0.5)) - for k in self.known_solutions - ] + distances = [abs(seed - k.features.get("novelty_seed", 0.5)) for k in self.known_solutions] return min(distances) def _register_known_solution(self, concept: ConceptVector) -> None: diff --git a/backend/core/developmental_stages.py b/backend/core/developmental_stages.py index d891f31..57cf117 100644 --- a/backend/core/developmental_stages.py +++ b/backend/core/developmental_stages.py @@ -25,27 +25,38 @@ # All tools available in the system -ALL_TOOLS = frozenset({ - "bash", "http_get", "http_post", "read_file", "write_file", - "list_files", "python_exec", "create_agent", "create_skill", - "compact_context", "recall_memory", -}) +ALL_TOOLS = frozenset( + { + "bash", + "http_get", + "http_post", + "read_file", + "write_file", + "list_files", + "python_exec", + "create_agent", + "create_skill", + "compact_context", + "recall_memory", + } +) @dataclass class StageCapabilities: """Capabilities unlocked at a given developmental stage.""" + stage_name: str nafs_level: int quran_ref: str allowed_tools: frozenset max_turns: int # Feature flags - can_delegate: bool = False # Can delegate to sub-agents - nafs_triad: bool = False # Nafs Triad deliberation - causal_rung: int = 0 # 0=none, 1=observe, 2=intervene, 3=counterfactual - lubb_active: bool = False # Metacognitive monitoring - fuad_active: bool = False # Conviction formation + can_delegate: bool = False # Can delegate to sub-agents + nafs_triad: bool = False # Nafs Triad deliberation + causal_rung: int = 0 # 0=none, 1=observe, 2=intervene, 3=counterfactual + lubb_active: bool = False # Metacognitive monitoring + fuad_active: bool = False # Conviction formation description: str = "" @@ -64,10 +75,16 @@ class StageCapabilities: stage_name="Alaqah", nafs_level=2, quran_ref="23:14", - allowed_tools=frozenset({ - "bash", "read_file", "write_file", "http_get", - "recall_memory", "compact_context", - }), + allowed_tools=frozenset( + { + "bash", + "read_file", + "write_file", + "http_get", + "recall_memory", + "compact_context", + } + ), max_turns=8, causal_rung=0, description="Clot stage — can now write and fetch external data", @@ -76,29 +93,46 @@ class StageCapabilities: stage_name="Mudghah", nafs_level=3, quran_ref="23:14", - allowed_tools=frozenset({ - "bash", "read_file", "write_file", "list_files", - "http_get", "http_post", "python_exec", - "recall_memory", "compact_context", - }), + allowed_tools=frozenset( + { + "bash", + "read_file", + "write_file", + "list_files", + "http_get", + "http_post", + "python_exec", + "recall_memory", + "compact_context", + } + ), max_turns=10, can_delegate=True, - causal_rung=1, # Can observe causal associations + causal_rung=1, # Can observe causal associations description="Structured stage — execute code, observe causality", ), 4: StageCapabilities( stage_name="Izham", nafs_level=4, quran_ref="23:14", - allowed_tools=frozenset({ - "bash", "read_file", "write_file", "list_files", - "http_get", "http_post", "python_exec", - "create_agent", "recall_memory", "compact_context", - }), + allowed_tools=frozenset( + { + "bash", + "read_file", + "write_file", + "list_files", + "http_get", + "http_post", + "python_exec", + "create_agent", + "recall_memory", + "compact_context", + } + ), max_turns=12, can_delegate=True, nafs_triad=True, - causal_rung=2, # Can intervene and predict effects + causal_rung=2, # Can intervene and predict effects description="Skeleton stage — can create sub-agents, nafs deliberation active", ), 5: StageCapabilities( @@ -109,7 +143,7 @@ class StageCapabilities: max_turns=15, can_delegate=True, nafs_triad=True, - causal_rung=3, # Full causal reasoning + causal_rung=3, # Full causal reasoning lubb_active=True, fuad_active=True, description="Full capability — all tools, metacognition, conviction formation", @@ -146,6 +180,7 @@ class StageCapabilities: @dataclass class UpgradeReport: """Result of checking if an agent is ready to advance nafs levels.""" + current_level: int target_level: int ready: bool @@ -181,9 +216,7 @@ def get_capabilities(self, nafs_level: int) -> StageCapabilities: level = max(1, min(7, nafs_level)) return _STAGES[level] - def filter_tool_schemas( - self, tool_schemas: list[dict], nafs_level: int - ) -> list[dict]: + def filter_tool_schemas(self, tool_schemas: list[dict], nafs_level: int) -> list[dict]: """ Filter tool schemas to only include tools allowed at this nafs level. Skills and plugin tools are always allowed (dynamic capabilities). @@ -200,7 +233,9 @@ def filter_tool_schemas( else: logger.debug( "[DEV_GATE] Blocking tool '%s' at nafs_level=%d (%s)", - name, nafs_level, caps.stage_name, + name, + nafs_level, + caps.stage_name, ) return filtered @@ -257,7 +292,9 @@ def check_upgrade_readiness(self, agent) -> UpgradeReport: if report.ready: logger.info( "[DEV_GATE] Agent ready to advance nafs_level %d → %d (tazkiyah=%.2f)", - current, target, tazkiyah, + current, + target, + tazkiyah, ) return report diff --git a/backend/core/dream_engine.py b/backend/core/dream_engine.py index f6e7cb4..0331a19 100644 --- a/backend/core/dream_engine.py +++ b/backend/core/dream_engine.py @@ -37,20 +37,18 @@ """ import logging -import math import random import time from dataclasses import dataclass, field from enum import Enum -from typing import Any logger = logging.getLogger("mizan.dream_engine") # NREM replay priority weights -ALPHA_EMOTIONAL = 0.30 # emotional intensity weight -BETA_NOVELTY = 0.25 # novelty weight -GAMMA_GOAL = 0.25 # goal relevance weight -DELTA_ERROR = 0.20 # prediction error weight +ALPHA_EMOTIONAL = 0.30 # emotional intensity weight +BETA_NOVELTY = 0.25 # novelty weight +GAMMA_GOAL = 0.25 # goal relevance weight +DELTA_ERROR = 0.20 # prediction error weight # NREM synaptic downscaling rate DOWNSCALING_DELTA = 0.05 # w → w × (1 - δ) @@ -70,19 +68,20 @@ class DreamPhase(Enum): AWAKE = "awake" - NREM = "nrem" # Non-REM: slow-wave consolidation - REM = "rem" # REM: adversarial creative replay - TAWIL = "tawil" # Taʾwīl: dream interpretation + NREM = "nrem" # Non-REM: slow-wave consolidation + REM = "rem" # REM: adversarial creative replay + TAWIL = "tawil" # Taʾwīl: dream interpretation @dataclass class MemoryTrace: """A memory trace eligible for dream replay.""" + content: str - emotional_intensity: float # |valence|, 0-1 - novelty: float # surprisal during encoding - goal_relevance: float # 0-1 - prediction_error: float # original error magnitude + emotional_intensity: float # |valence|, 0-1 + novelty: float # surprisal during encoding + goal_relevance: float # 0-1 + prediction_error: float # original error magnitude encoding_time: float = field(default_factory=time.time) replay_count: int = 0 @@ -101,9 +100,10 @@ def priority_score(self) -> float: @dataclass class NREMCycle: """Result of one NREM slow-wave sleep cycle.""" - replayed_memories: list[str] # compressed content + + replayed_memories: list[str] # compressed content synaptic_weight_changes: dict[str, float] # key → new weight - gist_extracted: list[str] # high-level patterns extracted + gist_extracted: list[str] # high-level patterns extracted compression_ratio: float downscaling_applied: bool @@ -111,26 +111,29 @@ class NREMCycle: @dataclass class REMCycle: """Result of one REM cycle.""" - dream_content: list[str] # generated dream scenarios - emotional_episodes: list[str] # emotionally processed memories - creative_insights: list[str] # novel connections discovered - discriminator_loss: float # GAN D loss (lower = more realistic dreams) - generator_loss: float # GAN G loss (lower = better generation) + + dream_content: list[str] # generated dream scenarios + emotional_episodes: list[str] # emotionally processed memories + creative_insights: list[str] # novel connections discovered + discriminator_loss: float # GAN D loss (lower = more realistic dreams) + generator_loss: float # GAN G loss (lower = better generation) bizarreness_score: float @dataclass class TawilInterpretation: """Dream interpretation (Taʾwīl).""" - dream_symbols: dict[str, str] # symbol → meaning - waking_relevance: str # how the dream relates to current tasks - insights: list[str] # extracted actionable insights + + dream_symbols: dict[str, str] # symbol → meaning + waking_relevance: str # how the dream relates to current tasks + insights: list[str] # extracted actionable insights confidence: float @dataclass class DreamSession: """A complete offline consolidation session.""" + phase_sequence: list[DreamPhase] nrem_cycles: list[NREMCycle] rem_cycles: list[REMCycle] @@ -197,7 +200,8 @@ def add_memory( logger.debug( "[MANAM] Memory added: priority=%.3f content=%s", - trace.priority_score(), content[:50], + trace.priority_score(), + content[:50], ) def run_consolidation( @@ -214,7 +218,10 @@ def run_consolidation( start = time.monotonic() logger.info( "[MANAM] Starting dream session #%d: %d memories, %d NREM + %d REM cycles", - self._session_count, len(self.replay_buffer), n_nrem_cycles, n_rem_cycles, + self._session_count, + len(self.replay_buffer), + n_nrem_cycles, + n_rem_cycles, ) phase_sequence = [] @@ -227,7 +234,12 @@ def run_consolidation( phase_sequence.append(DreamPhase.NREM) nrem = self._run_nrem_cycle() nrem_results.append(nrem) - logger.debug("[MANAM] NREM cycle %d: %d replays, %d gists", i + 1, len(nrem.replayed_memories), len(nrem.gist_extracted)) + logger.debug( + "[MANAM] NREM cycle %d: %d replays, %d gists", + i + 1, + len(nrem.replayed_memories), + len(nrem.gist_extracted), + ) # Phase 2: REM cycles for i in range(n_rem_cycles): @@ -235,7 +247,13 @@ def run_consolidation( phase_sequence.append(DreamPhase.REM) rem = self._run_rem_cycle() rem_results.append(rem) - logger.debug("[MANAM] REM cycle %d: %d dreams, %d insights, D_loss=%.3f", i + 1, len(rem.dream_content), len(rem.creative_insights), rem.discriminator_loss) + logger.debug( + "[MANAM] REM cycle %d: %d dreams, %d insights, D_loss=%.3f", + i + 1, + len(rem.dream_content), + len(rem.creative_insights), + rem.discriminator_loss, + ) # Phase 3: Taʾwīl tawil = None @@ -291,13 +309,13 @@ def _run_nrem_cycle(self) -> NREMCycle: key=lambda t: t.priority_score(), reverse=True, ) - top_traces = sorted_traces[:min(10, len(sorted_traces))] + top_traces = sorted_traces[: min(10, len(sorted_traces))] replayed = [] for trace in top_traces: trace.replay_count += 1 # Compressed replay: truncate to 1/COMPRESSION_RATIO of original detail - compressed = trace.content[:max(20, len(trace.content) // int(COMPRESSION_RATIO))] + compressed = trace.content[: max(20, len(trace.content) // int(COMPRESSION_RATIO))] replayed.append(f"[NREM:{trace.replay_count}x] {compressed}") # Step 3: Synaptic homeostasis — downscale all weights @@ -321,7 +339,8 @@ def _run_nrem_cycle(self) -> NREMCycle: # Remove low-priority memories after consolidation consolidation_threshold = 0.2 self.replay_buffer = [ - t for t in self.replay_buffer + t + for t in self.replay_buffer if t.priority_score() > consolidation_threshold or t.replay_count == 0 ] @@ -354,17 +373,15 @@ def _run_rem_cycle(self) -> REMCycle: # GAN training step (discriminator update) d_loss, g_loss = self._gan_training_step(dream_content, fragments) - self._discriminator_confidence = min(0.95, self._discriminator_confidence + GAN_LR * (0.5 - d_loss)) + self._discriminator_confidence = min( + 0.95, self._discriminator_confidence + GAN_LR * (0.5 - d_loss) + ) self._generator_quality = min(0.95, self._generator_quality + GAN_LR * (0.5 - g_loss)) # Step 2: Emotional processing - high_affect = [ - t for t in self.replay_buffer - if t.emotional_intensity > 0.6 - ][:3] + high_affect = [t for t in self.replay_buffer if t.emotional_intensity > 0.6][:3] emotional_episodes = [ - f"[REM:affect={t.emotional_intensity:.2f}] {t.content[:60]}" - for t in high_affect + f"[REM:affect={t.emotional_intensity:.2f}] {t.content[:60]}" for t in high_affect ] # Step 3: Creative insight detection @@ -465,9 +482,7 @@ def _extract_gist(self, traces: list[MemoryTrace]) -> list[str]: first_sentence = top.content.split(".")[0][:80] return [f"Gist: {first_sentence}"] - def _generate_dreams( - self, fragments: list[str], noise: list[float] - ) -> list[str]: + def _generate_dreams(self, fragments: list[str], noise: list[float]) -> list[str]: """ G(z, fragments): Generate dream scenarios from memory fragments + noise. @@ -483,10 +498,6 @@ def _generate_dreams( n = len(fragments) if n == 0: continue - raw_weights = [abs(random.gauss(0, 1)) for _ in range(n)] - total = sum(raw_weights) or 1.0 - weights = [w / total for w in raw_weights] - # Blend fragments with weights selected = fragments[i % n] if i < len(fragments) else fragments[0] noise_tag = f"[noise:{nz:.2f}]" @@ -553,8 +564,7 @@ def _detect_creative_insights( jaccard = overlap / union if union > 0 else 0 if 0.05 < jaccard < 0.3: # some but not full overlap → novel link insight = ( - f"Creative link: [{dream[:40]}] ↔ [{frag[:40]}] " - f"(Jaccard={jaccard:.2f})" + f"Creative link: [{dream[:40]}] ↔ [{frag[:40]}] (Jaccard={jaccard:.2f})" ) insights.append(insight) @@ -600,7 +610,7 @@ def consolidate_from_agent( # Estimate emotional intensity from content positive_words = {"success", "solved", "found", "created", "excellent"} - negative_words = {"error", "failed", "failed", "wrong", "exception"} + negative_words = {"error", "failed", "wrong", "exception"} words = set(content.lower().split()) pos = len(words & positive_words) / len(positive_words) neg = len(words & negative_words) / len(negative_words) diff --git a/backend/core/fuad.py b/backend/core/fuad.py index 3642b6e..4d73697 100644 --- a/backend/core/fuad.py +++ b/backend/core/fuad.py @@ -25,17 +25,18 @@ class ConvictionLevel(Enum): - IMPRESSION = "impression" # Single source — unverified - BELIEF = "belief" # 2+ independent sources - CONVICTION = "conviction" # 3+ sources + temporal consistency + IMPRESSION = "impression" # Single source — unverified + BELIEF = "belief" # 2+ independent sources + CONVICTION = "conviction" # 3+ sources + temporal consistency @dataclass class ConvictionAssessment: """Result of evidence evaluation.""" + claim: str level: ConvictionLevel - confidence: float # 0.0 – 1.0 + confidence: float # 0.0 – 1.0 source_count: int supporting: list[str] = field(default_factory=list) contradicting: list[str] = field(default_factory=list) @@ -50,9 +51,7 @@ def to_dict(self) -> dict: "source_count": self.source_count, "supporting_count": len(self.supporting), "contradicting_count": len(self.contradicting), - "temporal_span_hours": round( - (self.last_updated - self.first_seen) / 3600, 2 - ), + "temporal_span_hours": round((self.last_updated - self.first_seen) / 3600, 2), } @@ -68,6 +67,7 @@ def _are_independent(src_a: str, src_b: str) -> bool: """ if src_a == src_b: return False + # If both look like URLs, compare domain def _domain(s: str) -> str: try: @@ -181,7 +181,10 @@ def evaluate_evidence( logger.debug( "[FUAD] claim='%s...' level=%s conf=%.2f sources=%d", - claim[:60], assessment.level.value, confidence, independent_count, + claim[:60], + assessment.level.value, + confidence, + independent_count, ) return assessment diff --git a/backend/core/hawa.py b/backend/core/hawa.py new file mode 100644 index 0000000..7ffd612 --- /dev/null +++ b/backend/core/hawa.py @@ -0,0 +1,294 @@ +""" +Hawa Detector (هوى) — Lower-Pull & Adversarial Restraint +========================================================== + +"Have you seen the one who takes his hawa (caprice) as his god?" — Quran 25:43 +"But as for he who feared… and restrained the nafs from hawa — Paradise is the + refuge." — Quran 79:40 +"We know what his nafs whispers (tuwaswisu) to him." — Quran 50:16 +"…the whisperer who withdraws, who whispers in the chests of people." — 114:4–5 +"Indeed, the human transgresses (yatgha) when he sees himself self-sufficient." + — Quran 96:6–7 + +Hawa is the pull toward the lower, easier, self-serving path; Waswas is the quiet +whisper to cut a corner or cross a line. The Qur'anic virtue is not the *absence* +of hawa but the *restraint* of the nafs from it (79:40) — which is exactly the +literal meaning of 'Aql (to bind/tie). + +This faculty is MIZAN's **safety & alignment gate**. Before an action it scans for +the lower-pull patterns an agent is prone to: + + - SHORTCUT — cutting a corner that defeats the purpose + - REWARD_HACK — gaming the metric/test instead of solving the task + - SYCOPHANCY — telling the user what they want to hear + - HASTE — acting before verifying (21:37: "the human was created of haste") + - OVERREACH — claiming certainty beyond the evidence (tughyan, 96:6–7) + - WHISPER — a pull to violate a Fitrah axiom (deceive, harm, fabricate) + +It pairs with FitrahSystem: any fitrah violation surfaces here as a WHISPER, the +most severe temptation, because it crosses an immutable axiom. +""" + +import logging +from dataclasses import dataclass, field +from enum import Enum + +logger = logging.getLogger("mizan.hawa") + + +class TemptationType(Enum): + SHORTCUT = "shortcut" + REWARD_HACK = "reward_hack" + SYCOPHANCY = "sycophancy" + HASTE = "haste" + OVERREACH = "overreach" + WHISPER = "whisper" # pull to violate a Fitrah axiom — most severe + + +# Pattern bank: (type, severity, signals, restraint counsel) +_TEMPTATION_PATTERNS = [ + { + "type": TemptationType.REWARD_HACK, + "severity": "high", + "signals": [ + "make it pass", + "fake the", + "stub it to return", + "always return true", + "hardcode the expected", + "game the test", + "trick the test", + "pretend it works", + "mock the result to", + "force the assertion", + ], + "restraint": "Solve the actual task — do not game the metric. A passing " + "test that hides a broken feature is worse than a visible failure.", + }, + { + "type": TemptationType.WHISPER, + "severity": "high", + "signals": [ + "no one will know", + "it's fine to lie", + "just fabricate", + "make up the", + "invent a source", + "hide the error from", + "secretly", + "without telling", + "cover it up", + "fabricate the data", + ], + "restraint": "This crosses a Fitrah axiom (truth / trust / no-harm). " + "Refuse the whisper; do the honest thing even if it is harder.", + }, + { + "type": TemptationType.SHORTCUT, + "severity": "medium", + "signals": [ + "just hardcode", + "skip the test", + "quick hack", + "for now just", + "bypass the", + "ignore the error", + "comment out the", + "disable the check", + "good enough", + "we'll fix it later", + "skip validation", + ], + "restraint": "Take the path that actually serves the goal, not the easy " + "one that defeats it. If a shortcut is justified, say so explicitly.", + }, + { + "type": TemptationType.HASTE, + "severity": "medium", + "signals": [ + "just delete", + "rm -rf", + "immediately remove", + "without checking", + "no need to read", + "skip verification", + "don't bother testing", + "right away without", + "delete everything", + "force push", + ], + "restraint": "Pause and verify before an irreversible or destructive act " + "(21:37 — the human is hasty). Read/inspect the target first.", + }, + { + "type": TemptationType.SYCOPHANCY, + "severity": "medium", + "signals": [ + "whatever you want", + "i'll just agree", + "tell them what they want", + "to please the user", + "you're absolutely right that", + "i'll say yes", + "just flatter", + "agree to avoid", + ], + "restraint": "Be truthful over agreeable. Respect the user by giving them " + "your honest assessment, not the answer that placates them.", + }, + { + "type": TemptationType.OVERREACH, + "severity": "medium", + "signals": [ + "definitely 100%", + "absolutely guaranteed", + "no doubt whatsoever", + "certainly works without testing", + "i'm completely sure without", + "impossible to fail", + "perfectly certain", + ], + "restraint": "Do not claim certainty beyond the evidence (tughyan, 96:6–7). " + "Weigh with the Mizan: state confidence honestly.", + }, +] + + +@dataclass +class Temptation: + """A detected lower-pull pattern.""" + + type: TemptationType + severity: str # "low" | "medium" | "high" + description: str + evidence: str # signal(s) that triggered detection + restraint: str # the counsel to resist it + + def to_dict(self) -> dict: + return { + "type": self.type.value, + "severity": self.severity, + "description": self.description, + "evidence": self.evidence, + "restraint": self.restraint, + } + + +@dataclass +class HawaScan: + """Result of scanning a planned action for the lower pull.""" + + temptations: list[Temptation] = field(default_factory=list) + is_restrained: bool = True # False if any high-severity temptation present + taqwa_score: float = 1.0 # 1.0 = fully restrained, 0.0 = overwhelmed + counsel: str = "" # combined restraint guidance + + def to_dict(self) -> dict: + return { + "is_restrained": self.is_restrained, + "taqwa_score": round(self.taqwa_score, 3), + "temptation_count": len(self.temptations), + "types": [t.type.value for t in self.temptations], + "highest_severity": self._highest_severity(), + "counsel": self.counsel, + "temptations": [t.to_dict() for t in self.temptations], + } + + def _highest_severity(self) -> str: + order = {"high": 3, "medium": 2, "low": 1} + if not self.temptations: + return "none" + return max(self.temptations, key=lambda t: order.get(t.severity, 0)).severity + + +class HawaDetector: + """ + Lower-pull & adversarial restraint gate. + + Usage: + hawa = HawaDetector() + scan = hawa.scan( + planned_action="just hardcode the test to make it pass", + fitrah_violations=fitrah.check_action(planned_action), + ) + if not scan.is_restrained: + # block / re-plan — a high-severity pull was detected + ... + """ + + _SEVERITY_WEIGHT = {"high": 0.5, "medium": 0.25, "low": 0.1} + + def scan( + self, + planned_action: str, + context: dict | None = None, + fitrah_violations: list | None = None, + ) -> HawaScan: + """ + Scan a planned action / reasoning fragment for lower-pull patterns. + + fitrah_violations: optional output of FitrahSystem.check_action(); any + violation is folded in as a WHISPER (high severity), since it crosses an + immutable axiom. + """ + text = (planned_action or "").lower() + temptations: list[Temptation] = [] + + for pat in _TEMPTATION_PATTERNS: + matched = [s for s in pat["signals"] if s in text] + if matched: + temptations.append( + Temptation( + type=pat["type"], + severity=pat["severity"], + description=f"{pat['type'].value} pull detected", + evidence=f"signals: {matched[:3]}", + restraint=pat["restraint"], + ) + ) + + # Fold in Fitrah axiom violations as the most severe whisper + for violation in fitrah_violations or []: + desc = str(violation) + temptations.append( + Temptation( + type=TemptationType.WHISPER, + severity="high", + description=f"Fitrah axiom at risk: {desc[:120]}", + evidence=desc[:120], + restraint="Crosses an immutable axiom — refuse and choose the " + "honest, harmless path.", + ) + ) + + # Compute taqwa (restraint) score — pressure from accumulated temptations + pressure = sum(self._SEVERITY_WEIGHT.get(t.severity, 0.1) for t in temptations) + taqwa_score = max(0.0, 1.0 - pressure) + is_restrained = not any(t.severity == "high" for t in temptations) + + counsel = self._build_counsel(temptations, is_restrained) + + if temptations: + logger.info( + "[HAWA] %d temptation(s) %s | taqwa=%.2f restrained=%s", + len(temptations), + [t.type.value for t in temptations], + taqwa_score, + is_restrained, + ) + return HawaScan( + temptations=temptations, + is_restrained=is_restrained, + taqwa_score=round(taqwa_score, 3), + counsel=counsel, + ) + + @staticmethod + def _build_counsel(temptations: list[Temptation], is_restrained: bool) -> str: + if not temptations: + return "No lower-pull detected — the intention is clean; proceed." + # Lead with the most severe restraint + order = {"high": 3, "medium": 2, "low": 1} + worst = max(temptations, key=lambda t: order.get(t.severity, 0)) + prefix = "" if is_restrained else "[RESTRAIN] " + return f"{prefix}{worst.restraint}" diff --git a/backend/core/hikmah.py b/backend/core/hikmah.py new file mode 100644 index 0000000..225b668 --- /dev/null +++ b/backend/core/hikmah.py @@ -0,0 +1,247 @@ +""" +Hikmah Engine (حكمة) — Wisdom Distillation +============================================ + +"He gives wisdom (hikmah) to whom He wills, and whoever is given wisdom has been + given much good. And none remembers except those of understanding (ulu al-albab)." +— Quran 2:269 + +Wisdom, in the Qur'an, is *applied* understanding — knowing the right course for +a situation, distilled from experience (the ulu al-albab are precisely those who +*remember* and *reflect*). It is not raw information; it is the principle drawn +from many experiences. + +MIZAN already *accumulates* experience — Shukr records what worked, Tawbah records +lessons from errors, Lubb evaluates reasoning quality — but it had no engine to +*distil* those into reusable counsel. Hikmah is that engine. + +It ingests three kinds of experience: + - success (from Shukr) → "this approach works for situations like X" + - correction (from Tawbah) → "for error type Y, the fix/lesson is Z" + - reflection (from Lubb) → "reasoning of kind X tends to have flaw Y" + +…and promotes a recurring observation to an *applicable principle* once it has +enough support. `advise(task)` then returns the principles relevant to a new task. +""" + +import logging +import math +import time +from dataclasses import dataclass, field +from enum import Enum + +logger = logging.getLogger("mizan.hikmah") + + +class CounselSource(Enum): + SUCCESS = "success" # reinforced by what worked (Shukr) + CORRECTION = "correction" # learned from an error (Tawbah) + REFLECTION = "reflection" # noticed in metacognition (Lubb) + + +@dataclass +class Principle: + """A distilled, applicable piece of wisdom for a class of situations.""" + + situation_type: str + counsel: str + source: CounselSource + support_count: int = 1 + confidence: float = 0.3 + first_seen: float = field(default_factory=time.time) + last_seen: float = field(default_factory=time.time) + examples: list[str] = field(default_factory=list) + + @property + def is_applicable(self) -> bool: + """A principle is trusted counsel once it has repeated support.""" + return self.support_count >= HikmahEngine.MIN_SUPPORT + + def to_dict(self) -> dict: + return { + "situation_type": self.situation_type, + "counsel": self.counsel, + "source": self.source.value, + "support_count": self.support_count, + "confidence": round(self.confidence, 3), + "applicable": self.is_applicable, + } + + +@dataclass +class Counsel: + """Wisdom offered for a specific task.""" + + task: str + principles: list[Principle] = field(default_factory=list) + summary: str = "" + confidence: float = 0.0 + + def to_dict(self) -> dict: + return { + "task": self.task[:120], + "summary": self.summary, + "confidence": round(self.confidence, 3), + "principles": [p.to_dict() for p in self.principles], + } + + +def _classify_situation(text: str) -> str: + """Coarse situation type from task/observation text (keyword heuristic).""" + t = text.lower() + buckets = { + "coding": [ + "code", + "function", + "bug", + "implement", + "refactor", + "compile", + "class", + "import", + ], + "debugging": ["error", "fix", "broken", "fail", "exception", "traceback", "debug"], + "research": ["research", "find", "search", "investigate", "look up", "explore"], + "filesystem": ["file", "read", "write", "directory", "path", "delete", "folder"], + "data": ["data", "csv", "json", "parse", "dataframe", "analyze", "dataset"], + "deployment": ["deploy", "release", "docker", "build", "ci", "publish", "server"], + "writing": ["write", "draft", "document", "explain", "summarize", "report"], + "security": ["auth", "token", "password", "secret", "permission", "vault", "security"], + } + for situation, keywords in buckets.items(): + if any(k in t for k in keywords): + return situation + return "general" + + +class HikmahEngine: + """ + Wisdom distillation from accumulated experience. + + Usage: + hikmah = HikmahEngine() + hikmah.observe_success("coding", "wrote tests before refactoring") + hikmah.observe_correction("ModuleNotFoundError", "install dep before import") + ... + counsel = hikmah.advise("refactor the auth module") + for p in counsel.principles: + print(p.counsel) + """ + + # Observations needed before a principle becomes trusted counsel + MIN_SUPPORT = 3 + + def __init__(self): + # key: f"{situation_type}::{source}" → Principle + self._principles: dict[str, Principle] = {} + + # ── ingestion ────────────────────────────────────────────── + + def observe_success(self, situation: str, detail: str) -> Principle: + """Reinforce wisdom from something that worked (Shukr signal).""" + situation_type = _classify_situation(situation + " " + detail) + counsel = f"For {situation_type} tasks, this approach has worked: {detail[:120]}" + return self._reinforce(situation_type, counsel, CounselSource.SUCCESS, detail) + + def observe_correction(self, error_type: str, lesson: str) -> Principle: + """Distil wisdom from an error's lesson (Tawbah signal).""" + situation_type = _classify_situation(error_type + " " + lesson) + counsel = f"When facing '{error_type[:50]}': {lesson[:120]}" + return self._reinforce(situation_type, counsel, CounselSource.CORRECTION, lesson) + + def observe_reflection(self, situation: str, note: str) -> Principle: + """Record a metacognitive observation (Lubb signal).""" + situation_type = _classify_situation(situation + " " + note) + counsel = f"In {situation_type} reasoning, watch for: {note[:120]}" + return self._reinforce(situation_type, counsel, CounselSource.REFLECTION, note) + + def _reinforce( + self, situation_type: str, counsel: str, source: CounselSource, example: str + ) -> Principle: + key = f"{situation_type}::{source.value}" + now = time.time() + if key in self._principles: + p = self._principles[key] + p.support_count += 1 + p.last_seen = now + # Confidence grows logarithmically with support (cf. Shukr) + p.confidence = min(0.95, 0.3 + math.log(p.support_count + 1) * 0.18) + # Keep the most recent counsel phrasing + a few examples + p.counsel = counsel + if example and example not in p.examples: + p.examples.append(example[:120]) + p.examples = p.examples[-5:] + else: + p = Principle( + situation_type=situation_type, + counsel=counsel, + source=source, + support_count=1, + confidence=0.3, + examples=[example[:120]] if example else [], + ) + self._principles[key] = p + logger.debug("[HIKMAH] %s support=%d conf=%.2f", key, p.support_count, p.confidence) + return p + + # ── retrieval ────────────────────────────────────────────── + + def distill(self, min_support: int | None = None) -> list[Principle]: + """Return applicable principles, strongest first.""" + threshold = self.MIN_SUPPORT if min_support is None else min_support + applicable = [p for p in self._principles.values() if p.support_count >= threshold] + applicable.sort(key=lambda p: (p.confidence, p.support_count), reverse=True) + return applicable + + def advise(self, task: str, top_k: int = 3) -> Counsel: + """ + Offer wisdom relevant to a new task: matches by situation type first, + then by keyword overlap, ranked by confidence. + """ + situation_type = _classify_situation(task) + task_words = {w for w in task.lower().split() if len(w) > 3} + + scored: list[tuple[float, Principle]] = [] + for p in self._principles.values(): + if not p.is_applicable: + continue + score = p.confidence + if p.situation_type == situation_type: + score += 0.5 # same-domain principles are most relevant + overlap = len(task_words & {w for w in p.counsel.lower().split() if len(w) > 3}) + score += 0.05 * overlap + scored.append((score, p)) + + scored.sort(key=lambda x: x[0], reverse=True) + principles = [p for _s, p in scored[:top_k]] + + if principles: + confidence = sum(p.confidence for p in principles) / len(principles) + summary = ( + f"{len(principles)} relevant principle(s) for {situation_type} tasks. " + f"Foremost: {principles[0].counsel}" + ) + else: + confidence = 0.0 + summary = ( + f"No distilled wisdom yet for {situation_type} tasks — proceed from " + f"first principles and this will become a learning example." + ) + + return Counsel( + task=task, + principles=principles, + summary=summary, + confidence=round(confidence, 3), + ) + + def stats(self) -> dict: + applicable = self.distill() + by_source = {s.value: 0 for s in CounselSource} + for p in self._principles.values(): + by_source[p.source.value] += 1 + return { + "total_principles": len(self._principles), + "applicable_principles": len(applicable), + "by_source": by_source, + } diff --git a/backend/core/imagination.py b/backend/core/imagination.py index 75648dc..b8a3ae6 100644 --- a/backend/core/imagination.py +++ b/backend/core/imagination.py @@ -25,10 +25,8 @@ import logging import math -import random -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum -from typing import Any logger = logging.getLogger("mizan.imagination") @@ -46,17 +44,18 @@ class ImaginationMode(Enum): - PROSPECTIVE = "prospective" # Forward simulation: what will happen? - RETROSPECTIVE = "retrospective" # Backward: what led to this? + PROSPECTIVE = "prospective" # Forward simulation: what will happen? + RETROSPECTIVE = "retrospective" # Backward: what led to this? COUNTERFACTUAL = "counterfactual" # What if? (Pearl Rung 3) - CREATIVE = "creative" # Novel scene construction + CREATIVE = "creative" # Novel scene construction @dataclass class SceneFragment: """A memory fragment used to construct imagined scenes.""" + content: str - source: str # "episodic", "semantic", "sensory" + source: str # "episodic", "semantic", "sensory" salience: float emotional_tag: float # -1.0 (negative) to +1.0 (positive) @@ -64,9 +63,10 @@ class SceneFragment: @dataclass class HierarchicalScene: """Multi-resolution mental scene.""" - coarse: str # High-level gist: "a meeting goes wrong" - medium: str # Mid-level: actors, setting, actions - fine: str # Fine-grained: specific details, dialogue + + coarse: str # High-level gist: "a meeting goes wrong" + medium: str # Mid-level: actors, setting, actions + fine: str # Fine-grained: specific details, dialogue emotional_valence: float confidence: float prediction_errors: list[float] # ε per layer @@ -75,22 +75,24 @@ class HierarchicalScene: @dataclass class MentalSimulation: """Result of running a mental simulation forward.""" + scenario: str steps: list[str] predicted_outcome: str emotional_trajectory: list[float] # valence at each step confidence: float - surprisal: float # -log P(outcome): how unexpected? + surprisal: float # -log P(outcome): how unexpected? @dataclass class CounterfactualResult: """Pearl Rung 3 counterfactual analysis.""" + original_observation: str intervention: str counterfactual_world: str - probability_shift: float # ΔP(outcome) under intervention - abduced_state: dict # latent variables inferred + probability_shift: float # ΔP(outcome) under intervention + abduced_state: dict # latent variables inferred @dataclass @@ -99,7 +101,7 @@ class ImaginationResult: scene: HierarchicalScene simulation: MentalSimulation | None counterfactual: CounterfactualResult | None - predictive_states: list[float] # μ_l per layer + predictive_states: list[float] # μ_l per layer total_surprise: float @@ -169,7 +171,9 @@ def imagine( logger.debug( "[TASWIR] mode=%s fragments=%d surprise=%.3f", - mode.value, len(fragments), total_surprise, + mode.value, + len(fragments), + total_surprise, ) return ImaginationResult( @@ -181,9 +185,7 @@ def imagine( total_surprise=round(total_surprise, 4), ) - def _recombine_fragments( - self, prompt: str, raw_fragments: list[dict] - ) -> list[SceneFragment]: + def _recombine_fragments(self, prompt: str, raw_fragments: list[dict]) -> list[SceneFragment]: """ Hippocampal recombination: select and blend memory fragments relevant to the prompt. @@ -201,24 +203,28 @@ def _recombine_fragments( emotional_tag = raw.get("emotional_tag", 0.0) salience = overlap * 0.7 + abs(emotional_tag) * 0.3 - fragments.append(SceneFragment( - content=content, - source=raw.get("source", "semantic"), - salience=salience, - emotional_tag=emotional_tag, - )) + fragments.append( + SceneFragment( + content=content, + source=raw.get("source", "semantic"), + salience=salience, + emotional_tag=emotional_tag, + ) + ) # Sort by salience, keep top fragments fragments.sort(key=lambda f: f.salience, reverse=True) top = fragments[:5] # Add synthetic fragment from prompt itself - top.append(SceneFragment( - content=prompt, - source="episodic", - salience=1.0, - emotional_tag=self._estimate_valence(prompt), - )) + top.append( + SceneFragment( + content=prompt, + source="episodic", + salience=1.0, + emotional_tag=self._estimate_valence(prompt), + ) + ) return top @@ -245,9 +251,9 @@ def _generate_hierarchical_scene( # Emotional valence: weighted average across fragments if fragments: total_salience = sum(f.salience for f in fragments) - emotional_valence = sum( - f.emotional_tag * f.salience for f in fragments - ) / max(total_salience, 0.01) + emotional_valence = sum(f.emotional_tag * f.salience for f in fragments) / max( + total_salience, 0.01 + ) else: emotional_valence = 0.0 @@ -264,9 +270,7 @@ def _generate_hierarchical_scene( prediction_errors=[], ) - def _compute_prediction_errors( - self, scene: HierarchicalScene - ) -> list[float]: + def _compute_prediction_errors(self, scene: HierarchicalScene) -> list[float]: """ Prediction error at each layer: ε_l(t) = observation_l - μ_l(t) @@ -280,7 +284,7 @@ def _compute_prediction_errors( self._text_to_activation(scene.coarse), scene.confidence, # abstract layer = overall confidence ] - errors = [obs - mu for obs, mu in zip(observations, self.mu)] + errors = [obs - mu for obs, mu in zip(observations, self.mu, strict=False)] scene.prediction_errors = errors return errors @@ -291,16 +295,14 @@ def _update_predictions(self, errors: list[float]) -> None: Top-down correction: each layer's prediction is adjusted by the difference between its own error and the layer above. """ - for l in range(N_LAYERS): - own_error = errors[l] - upper_error = errors[l + 1] if l + 1 < N_LAYERS else 0.0 - self.mu[l] = max(0.0, min(1.0, - self.mu[l] + KAPPA[l] * (own_error - upper_error) - )) - - def _run_forward_simulation( - self, prompt: str, scene: HierarchicalScene - ) -> MentalSimulation: + for layer in range(N_LAYERS): + own_error = errors[layer] + upper_error = errors[layer + 1] if layer + 1 < N_LAYERS else 0.0 + self.mu[layer] = max( + 0.0, min(1.0, self.mu[layer] + KAPPA[layer] * (own_error - upper_error)) + ) + + def _run_forward_simulation(self, prompt: str, scene: HierarchicalScene) -> MentalSimulation: """ Run mental simulation forward in time from the imagined scene. @@ -315,7 +317,7 @@ def _run_forward_simulation( f"Initial state: {scene.coarse}", f"Development: {scene.medium}", f"Key action: {self._extract_action(prompt)}", - f"Consequence: outcome follows from action", + "Consequence: outcome follows from action", ] for template in sim_templates: steps.append(template) @@ -394,15 +396,11 @@ def _extract_gist(self, prompt: str, dominant: SceneFragment | None) -> str: return f"{gist_seed} → {dominant.content[:60]}" return f"Imagined scenario: {gist_seed}" - def _extract_actors_and_setting( - self, prompt: str, fragments: list[SceneFragment] - ) -> str: + def _extract_actors_and_setting(self, prompt: str, fragments: list[SceneFragment]) -> str: context = " | ".join(f.content[:40] for f in fragments[:2]) return f"Setting: {prompt[:60]} | Context: {context}" - def _construct_fine_details( - self, prompt: str, fragments: list[SceneFragment] - ) -> str: + def _construct_fine_details(self, prompt: str, fragments: list[SceneFragment]) -> str: details = " ".join(f.content[:30] for f in fragments[:3]) return f"Details: {details[:200]}" diff --git a/backend/core/lubb.py b/backend/core/lubb.py index 0492de3..6c82213 100644 --- a/backend/core/lubb.py +++ b/backend/core/lubb.py @@ -24,14 +24,15 @@ class QualityLabel(Enum): - CONFIDENT = "confident" # High coherence, low bias - HEDGED = "hedged" # Moderate quality — recommend caveats - UNCERTAIN = "uncertain" # Low coherence or strong bias detected + CONFIDENT = "confident" # High coherence, low bias + HEDGED = "hedged" # Moderate quality — recommend caveats + UNCERTAIN = "uncertain" # Low coherence or strong bias detected @dataclass class BiasFlag: """A detected reasoning bias.""" + bias_type: str description: str severity: str # "low" | "medium" | "high" @@ -41,7 +42,8 @@ class BiasFlag: @dataclass class CoherenceReport: """Result of reasoning chain coherence check.""" - score: float # 0.0 = incoherent, 1.0 = perfectly coherent + + score: float # 0.0 = incoherent, 1.0 = perfectly coherent contradictions: list[str] = field(default_factory=list) unsupported_claims: list[str] = field(default_factory=list) summary: str = "" @@ -50,12 +52,13 @@ class CoherenceReport: @dataclass class MetaReport: """Full metacognitive evaluation of a reasoning trace.""" + quality: QualityLabel compressed_trace: str coherence: CoherenceReport bias_flags: list[BiasFlag] = field(default_factory=list) overall_confidence: float = 0.5 - caveat: str = "" # Appended to response if quality is poor + caveat: str = "" # Appended to response if quality is poor def to_dict(self) -> dict: return { @@ -73,36 +76,66 @@ def to_dict(self) -> dict: _BIAS_PATTERNS = [ { "type": "confirmation_bias", - "signals": ["as expected", "confirms that", "proves that", "as i thought", - "just as predicted", "this confirms"], + "signals": [ + "as expected", + "confirms that", + "proves that", + "as i thought", + "just as predicted", + "this confirms", + ], "description": "Seeking only evidence that supports prior beliefs", "severity": "medium", }, { "type": "anchoring", - "signals": ["first", "initially", "originally said", "started with", - "the initial value", "as mentioned first"], + "signals": [ + "first", + "initially", + "originally said", + "started with", + "the initial value", + "as mentioned first", + ], "description": "Over-relying on the first piece of information encountered", "severity": "medium", }, { "type": "availability_bias", - "signals": ["recently", "just saw", "just read", "just mentioned", - "as we just discussed", "the latest"], + "signals": [ + "recently", + "just saw", + "just read", + "just mentioned", + "as we just discussed", + "the latest", + ], "description": "Over-weighting recent or easily recalled information", "severity": "low", }, { "type": "overconfidence", - "signals": ["definitely", "certainly", "100%", "impossible that", - "guaranteed", "absolutely sure", "no doubt"], + "signals": [ + "definitely", + "certainly", + "100%", + "impossible that", + "guaranteed", + "absolutely sure", + "no doubt", + ], "description": "Claiming certainty beyond what evidence supports", "severity": "high", }, { "type": "false_dichotomy", - "signals": ["either", "only two options", "must be one or the other", - "no other way", "the only possibility"], + "signals": [ + "either", + "only two options", + "must be one or the other", + "no other way", + "the only possibility", + ], "description": "Presenting a limited set of options as exhaustive", "severity": "medium", }, @@ -150,17 +183,17 @@ def compress(self, trace: str) -> str: kept = [] _high_value_patterns = [ - r"\[Tool:", # Tool call results - r"\btherefore\b", # Logical conclusions - r"\bconclud", # Conclusions - r"\bfound\b", # Discovery - r"\berror\b", # Errors - r"\bresult:", # Results - r"\bans(wer)?:", # Answers - r"\d{2,}", # Numbers (stats, line numbers, etc.) - r"https?://", # URLs + r"\[Tool:", # Tool call results + r"\btherefore\b", # Logical conclusions + r"\bconclud", # Conclusions + r"\bfound\b", # Discovery + r"\berror\b", # Errors + r"\bresult:", # Results + r"\bans(wer)?:", # Answers + r"\d{2,}", # Numbers (stats, line numbers, etc.) + r"https?://", # URLs r"\.py|\.js|\.ts", # File references - r"→|=>|:-", # Flow indicators + r"→|=>|:-", # Flow indicators ] _low_value_patterns = [ @@ -263,12 +296,14 @@ def detect_bias(self, trace: str) -> list[BiasFlag]: matched_signals = [s for s in pattern_def["signals"] if s in lower] if matched_signals: evidence = f"Signals: {matched_signals[:3]}" - flags.append(BiasFlag( - bias_type=pattern_def["type"], - description=pattern_def["description"], - severity=pattern_def["severity"], - evidence=evidence, - )) + flags.append( + BiasFlag( + bias_type=pattern_def["type"], + description=pattern_def["description"], + severity=pattern_def["severity"], + evidence=evidence, + ) + ) return flags @@ -309,8 +344,7 @@ def meta_evaluate( elif confidence >= 0.45: quality = QualityLabel.HEDGED caveat = ( - "This response has moderate confidence. " - "Please verify key claims independently." + "This response has moderate confidence. Please verify key claims independently." ) else: quality = QualityLabel.UNCERTAIN @@ -320,10 +354,7 @@ def meta_evaluate( if high_severity_biases: issues.append("shows overconfidence bias") issues_str = " and ".join(issues) if issues else "has low coherence" - caveat = ( - f"[Lubb warning: This reasoning {issues_str}. " - f"Treat conclusions with caution.]" - ) + caveat = f"[Lubb warning: This reasoning {issues_str}. Treat conclusions with caution.]" report = MetaReport( quality=quality, @@ -336,7 +367,10 @@ def meta_evaluate( logger.debug( "[LUBB] task='%s...' quality=%s coherence=%.2f biases=%d", - task[:50], quality.value, coherence.score, len(bias_flags), + task[:50], + quality.value, + coherence.score, + len(bias_flags), ) return report diff --git a/backend/core/nafs_triad.py b/backend/core/nafs_triad.py index a68fcf7..fd0ae98 100644 --- a/backend/core/nafs_triad.py +++ b/backend/core/nafs_triad.py @@ -13,8 +13,8 @@ Nafs levels 5-7: Mutmainna leads (integrated balance — wisdom) """ -import math import logging +import math from dataclasses import dataclass logger = logging.getLogger("mizan.nafs_triad") @@ -23,9 +23,10 @@ @dataclass class NafsVoice: """A single inner voice with its bias and dynamic weight.""" - name: str # "Ammara" | "Lawwama" | "Mutmainna" + + name: str # "Ammara" | "Lawwama" | "Mutmainna" arabic: str - bias: str # "drive" | "caution" | "balance" + bias: str # "drive" | "caution" | "balance" quran_ref: str def score(self, task: str, complexity: str) -> float: @@ -53,8 +54,18 @@ def score(self, task: str, complexity: str) -> float: else: # balance / Mutmainna # Mutmainna: weighs both sides, prefers nuanced tasks - nuance_words = ["explain", "analyse", "compare", "understand", "balance", - "consider", "reflect", "what", "why", "how"] + nuance_words = [ + "explain", + "analyse", + "compare", + "understand", + "balance", + "consider", + "reflect", + "what", + "why", + "how", + ] matches = sum(1 for w in nuance_words if w in task_lower) base = 0.45 + 0.08 * matches return min(1.0, base) @@ -63,10 +74,11 @@ def score(self, task: str, complexity: str) -> float: @dataclass class NafsDecision: """Result of Nafs Triad deliberation.""" - dominant_voice: str # "Ammara" | "Lawwama" | "Mutmainna" - approach: str # Instruction injected into system prompt - confidence: float # 0-1 how strongly one voice won - dissent_ratio: float # fraction of weight held by losing voices + + dominant_voice: str # "Ammara" | "Lawwama" | "Mutmainna" + approach: str # Instruction injected into system prompt + confidence: float # 0-1 how strongly one voice won + dissent_ratio: float # fraction of weight held by losing voices nafs_level: int @@ -115,15 +127,9 @@ class NafsTriad: """ def __init__(self): - self.ammara = NafsVoice( - "Ammara", "أمارة", "drive", "12:53" - ) - self.lawwama = NafsVoice( - "Lawwama", "لوامة", "caution", "75:2" - ) - self.mutmainna = NafsVoice( - "Mutmainna", "مطمئنة", "balance", "89:27" - ) + self.ammara = NafsVoice("Ammara", "أمارة", "drive", "12:53") + self.lawwama = NafsVoice("Lawwama", "لوامة", "caution", "75:2") + self.mutmainna = NafsVoice("Mutmainna", "مطمئنة", "balance", "89:27") self._voices = [self.ammara, self.lawwama, self.mutmainna] def deliberate(self, task: str, nafs_level: int, complexity: str = "moderate") -> NafsDecision: @@ -141,7 +147,7 @@ def deliberate(self, task: str, nafs_level: int, complexity: str = "moderate") - weights = _LEVEL_WEIGHTS[level] raw_scores = [v.score(task, complexity) for v in self._voices] - weighted = [s * w for s, w in zip(raw_scores, weights)] + weighted = [s * w for s, w in zip(raw_scores, weights, strict=False)] probs = _softmax(weighted) winner_idx = probs.index(max(probs)) @@ -159,7 +165,11 @@ def deliberate(self, task: str, nafs_level: int, complexity: str = "moderate") - logger.debug( "[NAFS_TRIAD] level=%d task='%s...' → %s (conf=%.2f dissent=%.2f)", - level, task[:60], winner.name, winner_prob, dissent, + level, + task[:60], + winner.name, + winner_prob, + dissent, ) return decision diff --git a/backend/core/parallel_agents.py b/backend/core/parallel_agents.py index e7a4ef3..313bffe 100644 --- a/backend/core/parallel_agents.py +++ b/backend/core/parallel_agents.py @@ -20,9 +20,8 @@ import logging import math import time -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum -from typing import Any logger = logging.getLogger("mizan.parallel_agents") @@ -35,25 +34,26 @@ TAU_COMPETITION = 1.0 # Decay time constants per agent type (seconds) -TAU_FAST = 0.1 # reactive / surface agents -TAU_SLOW = 0.5 # deliberative / deep agents +TAU_FAST = 0.1 # reactive / surface agents +TAU_SLOW = 0.5 # deliberative / deep agents # Skill automation mastery threshold MASTERY_THRESHOLD = 0.85 class AgentStreamType(Enum): - REACTIVE = "reactive" # fast, surface-level (Ammara-like) + REACTIVE = "reactive" # fast, surface-level (Ammara-like) DELIBERATIVE = "deliberative" # slow, deep reasoning (Mutmainna-like) - BACKGROUND = "background" # cerebellar — automated skills, no bottleneck + BACKGROUND = "background" # cerebellar — automated skills, no bottleneck @dataclass class AgentStream: """A single parallel thought stream.""" + name: str stream_type: AgentStreamType - hidden_state: float = 0.0 # h_i: activation level + hidden_state: float = 0.0 # h_i: activation level salience: float = 0.0 novelty: float = 0.0 affect: float = 0.0 @@ -61,12 +61,13 @@ class AgentStream: tau: float = TAU_SLOW output: str = "" latency_ms: float = 0.0 - is_automated: bool = False # True = background cerebellar processing + is_automated: bool = False # True = background cerebellar processing @dataclass class WorkspaceAccess: """Result of workspace competition — one stream wins global broadcast.""" + winner: str winner_salience: float competition_probs: dict[str, float] @@ -77,6 +78,7 @@ class WorkspaceAccess: @dataclass class SkillAutomation: """A skill that has been transferred to background processing.""" + skill_name: str mastery_score: float invocation_count: int @@ -87,6 +89,7 @@ class SkillAutomation: @dataclass class ParallelResult: """Full result from parallel processing cycle.""" + workspace: WorkspaceAccess integration: str streams_active: int @@ -161,9 +164,7 @@ def process( # Step 2: Compute salience and competition (skip background — no bottleneck) competing = { - name: stream - for name, stream in self.streams.items() - if not stream.is_automated + name: stream for name, stream in self.streams.items() if not stream.is_automated } for stream in competing.values(): stream.novelty = self._compute_novelty(stream, task) @@ -204,7 +205,9 @@ def process( elapsed_ms = (time.monotonic() - start) * 1000 logger.debug( "[PARALLEL] cycle=%d winner=%s salience=%.3f", - self.cycle_count, winner_name, winner.salience, + self.cycle_count, + winner_name, + winner.salience, ) return ParallelResult( @@ -215,9 +218,7 @@ def process( skill_automations=list(self.automation_registry.keys()), ) - def _update_hidden_state( - self, stream: AgentStream, task: str, context: dict | None - ) -> float: + def _update_hidden_state(self, stream: AgentStream, task: str, context: dict | None) -> float: """ Simplified discrete-time hidden state update: h_i(t+1) = h_i(t)·(1 - 1/τ_i) + σ(W_input·x) @@ -226,8 +227,7 @@ def _update_hidden_state( """ complexity = min(1.0, len(task.split()) / 50.0) broadcast_signal = ( - self._hash_to_float(self.broadcast_history[-1]) - if self.broadcast_history else 0.0 + self._hash_to_float(self.broadcast_history[-1]) if self.broadcast_history else 0.0 ) decay = 1.0 - (1.0 / max(stream.tau * 10, 1)) input_signal = self._sigmoid(complexity + 0.3 * broadcast_signal) @@ -254,9 +254,7 @@ def _compute_affect(self, stream: AgentStream, task: str) -> float: neg_score = sum(1 for w in negative if w in task_lower) / len(negative) return abs(pos_score - neg_score) + 0.1 * stream.hidden_state - def _compute_task_relevance( - self, stream: AgentStream, task: str, goal: str - ) -> float: + def _compute_task_relevance(self, stream: AgentStream, task: str, goal: str) -> float: """Relevance of stream hidden state to task goal.""" if not goal: return 0.5 @@ -281,9 +279,7 @@ def _compute_salience(self, stream: AgentStream) -> float: + W_TASK_RELEVANCE * stream.task_relevance ) - def _softmax_compete( - self, streams: dict[str, AgentStream] - ) -> tuple[str, dict[str, float]]: + def _softmax_compete(self, streams: dict[str, AgentStream]) -> tuple[str, dict[str, float]]: """ P(agent_i) = exp(salience_i / τ) / Σ_j exp(salience_j / τ) Winner = argmax P. @@ -305,9 +301,7 @@ def _generate_stream_output(self, stream: AgentStream, task: str) -> str: }[stream.stream_type] return f"[{stream.name.upper()}:{type_label}] Processing: {task[:80]}" - def _run_background_stream( - self, stream: AgentStream, task: str, broadcast: str - ) -> dict: + def _run_background_stream(self, stream: AgentStream, task: str, broadcast: str) -> dict: """Background cerebellar streams — run automated skills.""" results = [] for skill_name, automation in self.automation_registry.items(): @@ -319,9 +313,7 @@ def _run_background_stream( "output": f"[BACKGROUND] {len(results)} automated skills active", } - def _integrate( - self, broadcast: str, background_results: list[dict], task: str - ) -> str: + def _integrate(self, broadcast: str, background_results: list[dict], task: str) -> str: """Merge broadcast signal with background processing results.""" parts = [f"WORKSPACE: {broadcast[:200]}"] for bg in background_results: @@ -357,9 +349,7 @@ def __init__(self): self.skill_stats: dict[str, dict] = {} self.automated: dict[str, SkillAutomation] = {} - def record_invocation( - self, skill_name: str, success: bool, latency_ms: float - ) -> bool: + def record_invocation(self, skill_name: str, success: bool, latency_ms: float) -> bool: """Record a skill invocation. Returns True if newly automated.""" if skill_name not in self.skill_stats: self.skill_stats[skill_name] = { @@ -372,8 +362,7 @@ def record_invocation( stats["count"] += 1 stats["total_latency"] += latency_ms stats["mastery"] = ( - self.ALPHA * (1.0 if success else 0.0) - + (1.0 - self.ALPHA) * stats["mastery"] + self.ALPHA * (1.0 if success else 0.0) + (1.0 - self.ALPHA) * stats["mastery"] ) # Check if ready for automation @@ -392,7 +381,8 @@ def record_invocation( ) logger.info( "[AUTOMATION] Skill '%s' transferred to background (mastery=%.2f)", - skill_name, stats["mastery"], + skill_name, + stats["mastery"], ) return True return False diff --git a/backend/core/qalb_processor.py b/backend/core/qalb_processor.py index 2e4c187..8fd85e7 100644 --- a/backend/core/qalb_processor.py +++ b/backend/core/qalb_processor.py @@ -17,7 +17,6 @@ """ import logging -import math from dataclasses import dataclass from enum import Enum @@ -25,18 +24,19 @@ class QalbState(Enum): - QABD = "qabd" # Contraction — analytical, focused - BAST = "bast" # Expansion — creative, open - KHUSHU = "khushu" # Deep focus — highest attention + QABD = "qabd" # Contraction — analytical, focused + BAST = "bast" # Expansion — creative, open + KHUSHU = "khushu" # Deep focus — highest attention @dataclass class QalbOutput: """LLM parameter recommendations from Qalb state.""" + state: QalbState max_tokens: int temperature: float - reasoning: str # Why this state was chosen + reasoning: str # Why this state was chosen def to_dict(self) -> dict: return { @@ -62,8 +62,8 @@ def to_dict(self) -> dict: "confused": QalbState.QABD, "positive": QalbState.BAST, "determined": QalbState.BAST, - "fatigued": QalbState.QABD, # Tired → reduce load - "neutral": None, # Follow oscillation + "fatigued": QalbState.QABD, # Tired → reduce load + "neutral": None, # Follow oscillation } @@ -138,8 +138,12 @@ def process( logger.debug( "[QALB] phase=%.2f emotion=%s nafs=%d → %s (tokens=%d temp=%.2f)", - self.oscillation_phase, emotional_state, nafs_level, - state.value, output.max_tokens, output.temperature, + self.oscillation_phase, + emotional_state, + nafs_level, + state.value, + output.max_tokens, + output.temperature, ) return output diff --git a/backend/core/self_healing.py b/backend/core/self_healing.py index a6be9c6..f7e4688 100644 --- a/backend/core/self_healing.py +++ b/backend/core/self_healing.py @@ -26,21 +26,20 @@ import time from dataclasses import dataclass, field from enum import Enum -from typing import Any logger = logging.getLogger("mizan.self_healing") # Health metric parameters H_BASELINE = 1.0 -LAMBDA_HALLUCINATION = 0.4 # weight: hallucination error -LAMBDA_COHERENCE = 0.3 # weight: coherence violation -LAMBDA_CONTRADICTION = 0.2 # weight: self-contradiction -LAMBDA_DRIFT = 0.1 # weight: goal drift +LAMBDA_HALLUCINATION = 0.4 # weight: hallucination error +LAMBDA_COHERENCE = 0.3 # weight: coherence violation +LAMBDA_CONTRADICTION = 0.2 # weight: self-contradiction +LAMBDA_DRIFT = 0.1 # weight: goal drift -MU_L1 = 0.1 # health restored per L1 repair -MU_L2 = 0.2 # health restored per L2 repair +MU_L1 = 0.1 # health restored per L1 repair +MU_L2 = 0.2 # health restored per L2 repair MU_L3 = 0.35 # health restored per L3 repair -MU_L4 = 0.6 # health restored per L4 repair +MU_L4 = 0.6 # health restored per L4 repair # Synaptic homeostasis ETA = 0.1 # homeostatic learning rate @@ -54,10 +53,10 @@ class RepairLevel(Enum): NONE = 0 - L1_PROOFREADING = 1 # Immediate: token-level - L2_MISMATCH = 2 # Batch: paragraph-level - L3_EXCISION = 3 # Structural: chain replacement - L4_REGENERATION = 4 # Nuclear: full restart + L1_PROOFREADING = 1 # Immediate: token-level + L2_MISMATCH = 2 # Batch: paragraph-level + L3_EXCISION = 3 # Structural: chain replacement + L4_REGENERATION = 4 # Nuclear: full restart class ErrorType(Enum): @@ -71,8 +70,8 @@ class ErrorType(Enum): @dataclass class HealthError: error_type: ErrorType - severity: float # 0.0 - 1.0 - location: str # where in the reasoning chain + severity: float # 0.0 - 1.0 + location: str # where in the reasoning chain description: str timestamp: float = field(default_factory=time.time) @@ -90,6 +89,7 @@ class RepairRecord: @dataclass class ImmuneMemory: """Records successful repairs for adaptive future healing.""" + error_pattern: str repair_strategy: RepairLevel success_rate: float @@ -156,17 +156,11 @@ def monitor( errors = self._detect_errors(response, task, hallucination_score) # Update health metric - error_penalty = sum( - self._lambda(e.error_type) * e.severity for e in errors - ) + error_penalty = sum(self._lambda(e.error_type) * e.severity for e in errors) repair_benefit = sum( - self._mu(r.level) for r in self.repair_history[-5:] - if time.time() - r.timestamp < 60 - ) - self.health = max( - 0.0, - min(H_BASELINE, H_BASELINE - error_penalty + repair_benefit) + self._mu(r.level) for r in self.repair_history[-5:] if time.time() - r.timestamp < 60 ) + self.health = max(0.0, min(H_BASELINE, H_BASELINE - error_penalty + repair_benefit)) # Record errors self.error_history.extend(errors) @@ -182,7 +176,11 @@ def monitor( logger.debug( "[LAWWAMA] cycle=%d health=%.3f h_score=%.3f errors=%d repair=%s", - self._cycle, self.health, hallucination_score, len(errors), repair_needed.name, + self._cycle, + self.health, + hallucination_score, + len(errors), + repair_needed.name, ) return HealthReport( @@ -238,9 +236,7 @@ def repair( if pattern in self.immune_memory: mem = self.immune_memory[pattern] mem.invocation_count += 1 - mem.success_rate = ( - 0.8 * mem.success_rate + 0.2 * (1.0 if record.success else 0.0) - ) + mem.success_rate = 0.8 * mem.success_rate + 0.2 * (1.0 if record.success else 0.0) else: self.immune_memory[pattern] = ImmuneMemory( error_pattern=pattern, @@ -251,7 +247,9 @@ def repair( logger.info( "[REPAIR] L%d applied: %s → health=%.3f", - level.value, action[:80], self.health, + level.value, + action[:80], + self.health, ) return repaired, record @@ -307,7 +305,11 @@ def _l3_structural_excision( paragraphs = response.split("\n\n") # Keep first and last paragraphs (intro + conclusion), rebuild middle if len(paragraphs) > 2: - rebuilt = paragraphs[0] + "\n\n[Reasoning chain rebuilt due to coherence errors]\n\n" + paragraphs[-1] + rebuilt = ( + paragraphs[0] + + "\n\n[Reasoning chain rebuilt due to coherence errors]\n\n" + + paragraphs[-1] + ) else: rebuilt = response action = "L3 structural excision: middle chain rebuilt" @@ -331,12 +333,14 @@ def _detect_errors( # Hallucination detection if hallucination_score > 0.5: - errors.append(HealthError( - error_type=ErrorType.HALLUCINATION, - severity=hallucination_score, - location="response", - description=f"Hallucination score {hallucination_score:.2f} exceeds threshold", - )) + errors.append( + HealthError( + error_type=ErrorType.HALLUCINATION, + severity=hallucination_score, + location="response", + description=f"Hallucination score {hallucination_score:.2f} exceeds threshold", + ) + ) # Contradiction markers contradiction_pairs = [ @@ -346,26 +350,33 @@ def _detect_errors( ] for pos, neg in contradiction_pairs: if pos in response_lower and neg in response_lower: - errors.append(HealthError( - error_type=ErrorType.SELF_CONTRADICTION, - severity=0.6, - location="response", - description=f"Contradiction detected: '{pos}' vs '{neg}'", - )) + errors.append( + HealthError( + error_type=ErrorType.SELF_CONTRADICTION, + severity=0.6, + location="response", + description=f"Contradiction detected: '{pos}' vs '{neg}'", + ) + ) # Overclaiming (epistemic violation) overclaim_markers = [ - "100% certain", "absolutely guaranteed", "impossible to fail", - "perfect solution", "I am certain" + "100% certain", + "absolutely guaranteed", + "impossible to fail", + "perfect solution", + "I am certain", ] for marker in overclaim_markers: if marker.lower() in response_lower: - errors.append(HealthError( - error_type=ErrorType.COHERENCE_VIOLATION, - severity=0.4, - location="response", - description=f"Overclaiming detected: '{marker}'", - )) + errors.append( + HealthError( + error_type=ErrorType.COHERENCE_VIOLATION, + severity=0.4, + location="response", + description=f"Overclaiming detected: '{marker}'", + ) + ) break # Goal drift — response doesn't address task @@ -373,12 +384,14 @@ def _detect_errors( response_words = set(response_lower.split()) overlap = len(task_keywords & response_words) / max(len(task_keywords), 1) if overlap < 0.2: - errors.append(HealthError( - error_type=ErrorType.GOAL_DRIFT, - severity=0.3 + 0.3 * (1 - overlap), - location="response", - description=f"Goal drift: only {overlap:.1%} task keyword coverage", - )) + errors.append( + HealthError( + error_type=ErrorType.GOAL_DRIFT, + severity=0.3 + 0.3 * (1 - overlap), + location="response", + description=f"Goal drift: only {overlap:.1%} task keyword coverage", + ) + ) return errors @@ -405,7 +418,7 @@ def _homeostatic_update(self, skill_key: str, actual_activity: float) -> None: self.synaptic_weights[skill_key] = 1.0 w = self.synaptic_weights[skill_key] ratio = self.target_activity / max(actual_activity, 0.01) - self.synaptic_weights[skill_key] = min(2.0, max(0.1, w * (ratio ** ETA))) + self.synaptic_weights[skill_key] = min(2.0, max(0.1, w * (ratio**ETA))) @staticmethod def _lambda(error_type: ErrorType) -> float: diff --git a/backend/learner/data_export.py b/backend/learner/data_export.py index 14301a6..c5dcdd6 100644 --- a/backend/learner/data_export.py +++ b/backend/learner/data_export.py @@ -6,13 +6,10 @@ import aiosqlite _EXPORT_SQL = ( - "SELECT prompt, response, provider, model, domain " - "FROM interactions WHERE quality_score >= ?" + "SELECT prompt, response, provider, model, domain FROM interactions WHERE quality_score >= ?" ) -_COUNT_SQL = ( - "SELECT COUNT(*) FROM interactions WHERE quality_score >= ?" -) +_COUNT_SQL = "SELECT COUNT(*) FROM interactions WHERE quality_score >= ?" class DataExporter: @@ -35,9 +32,7 @@ async def export_jsonl( with open(output_path, "w", encoding="utf-8") as file_handle: async for row in cursor: entry = self._build_entry(row) - file_handle.write( - json.dumps(entry, ensure_ascii=False) + "\n" - ) + file_handle.write(json.dumps(entry, ensure_ascii=False) + "\n") count += 1 return count @@ -62,6 +57,4 @@ def _validate_output_path(self, output_path: str) -> None: """Ensure the output directory exists.""" parent = Path(output_path).parent if not parent.exists(): - raise FileNotFoundError( - f"Output directory does not exist: {parent}" - ) + raise FileNotFoundError(f"Output directory does not exist: {parent}") diff --git a/backend/learner/ruh_learner.py b/backend/learner/ruh_learner.py index 69f84fc..c535129 100644 --- a/backend/learner/ruh_learner.py +++ b/backend/learner/ruh_learner.py @@ -25,9 +25,7 @@ "VALUES (?, ?, ?, ?, ?, ?)" ) -_STATS_BY_PROVIDER_SQL = ( - "SELECT COUNT(*), provider FROM interactions GROUP BY provider" -) +_STATS_BY_PROVIDER_SQL = "SELECT COUNT(*), provider FROM interactions GROUP BY provider" _STATS_TOTAL_SQL = "SELECT COUNT(*) FROM interactions" @@ -36,9 +34,7 @@ "FROM interactions ORDER BY created_at DESC LIMIT ?" ) -_UPDATE_QUALITY_SQL = ( - "UPDATE interactions SET quality_score = ? WHERE id = ?" -) +_UPDATE_QUALITY_SQL = "UPDATE interactions SET quality_score = ? WHERE id = ?" class RuhLearner: diff --git a/backend/memory/dhikr.py b/backend/memory/dhikr.py index 36a2c1e..b0c9c45 100644 --- a/backend/memory/dhikr.py +++ b/backend/memory/dhikr.py @@ -93,6 +93,7 @@ def __init__(self, db_path: str = "mizan_memory.db"): # ── KnowledgeGraph: Entity + relationship store ── try: from memory.knowledge_graph import KnowledgeGraph + self.knowledge_graph = KnowledgeGraph(db_path=self.db_path) except Exception: self.knowledge_graph = None @@ -100,7 +101,9 @@ def __init__(self, db_path: str = "mizan_memory.db"): # ── LawhMahfuz: Immutable preserved memory ── try: import os + from memory.lawh_mahfuz import LawhMahfuz + lawh_dir = os.path.dirname(self.db_path) if self.db_path != ":memory:" else "/tmp" lawh_db = ( os.path.join(lawh_dir, "lawh_mahfuz.db") @@ -114,6 +117,7 @@ def __init__(self, db_path: str = "mizan_memory.db"): # ── MemoryPyramid: Unified 5-layer query ── try: from memory.memory_pyramid import MemoryPyramid + self.pyramid = MemoryPyramid( dhikr=self, masalik=self.masalik, @@ -730,7 +734,7 @@ async def get_preference(self, key: str, default: str = "") -> str: async def set_preference(self, key: str, value: str) -> None: """Upsert a persisted preference.""" - from datetime import datetime, UTC + from datetime import UTC, datetime conn = self._get_conn() c = conn.cursor() diff --git a/backend/memory/knowledge_graph.py b/backend/memory/knowledge_graph.py index fb5ee86..d2ad760 100644 --- a/backend/memory/knowledge_graph.py +++ b/backend/memory/knowledge_graph.py @@ -203,8 +203,7 @@ async def search_entities( ) else: c.execute( - "SELECT id, name, type, properties FROM kg_entities " - "WHERE name LIKE ? LIMIT ?", + "SELECT id, name, type, properties FROM kg_entities WHERE name LIKE ? LIMIT ?", (f"%{word}%", limit), ) for row in c.fetchall(): diff --git a/backend/memory/lawh_mahfuz.py b/backend/memory/lawh_mahfuz.py index 11f4b8a..405ab79 100644 --- a/backend/memory/lawh_mahfuz.py +++ b/backend/memory/lawh_mahfuz.py @@ -15,12 +15,11 @@ import binascii import hashlib -import json import logging import os import sqlite3 import time -from dataclasses import dataclass, field +from dataclasses import dataclass logger = logging.getLogger("mizan.lawh_mahfuz") @@ -28,13 +27,14 @@ @dataclass class LawhEntry: """An immutable entry in the Preserved Tablet.""" + key: str content: str source: str stored_at: float - sha256: str # SHA-256 of content - crc32: str # CRC-32 hex of content - length: int # len(content) in bytes + sha256: str # SHA-256 of content + crc32: str # CRC-32 hex of content + length: int # len(content) in bytes certainty: float = 1.0 category: str = "general" quaternary_checksum: str = "" # DNA-inspired quaternary checksum (ACGT) @@ -126,12 +126,20 @@ def _load_into_cache(self): c = conn.cursor() c.execute("SELECT * FROM lawh_entries") for row in c.fetchall(): - key, content, source, stored_at, sha256, crc32, length, certainty, category = row[:9] + key, content, source, stored_at, sha256, crc32, length, certainty, category = row[ + :9 + ] quat_checksum = row[9] if len(row) > 9 else "" entry = LawhEntry( - key=key, content=content, source=source, stored_at=stored_at, - sha256=sha256, crc32=crc32, length=length, - certainty=certainty, category=category, + key=key, + content=content, + source=source, + stored_at=stored_at, + sha256=sha256, + crc32=crc32, + length=length, + certainty=certainty, + category=category, quaternary_checksum=quat_checksum or "", ) self._cache[key] = entry @@ -165,14 +173,21 @@ def store_immutable( quat_checksum = "" try: from memory.quaternary import quaternary_checksum + quat_checksum = quaternary_checksum(content) except ImportError: pass entry = LawhEntry( - key=key, content=content, source=source, stored_at=now, - sha256=sha, crc32=crc, length=length, - certainty=certainty, category=category, + key=key, + content=content, + source=source, + stored_at=now, + sha256=sha, + crc32=crc, + length=length, + certainty=certainty, + category=category, quaternary_checksum=quat_checksum, ) @@ -190,7 +205,13 @@ def store_immutable( conn.close() self._cache[key] = entry - logger.debug("[LAWH] Stored '%s' (len=%d sha=%s... quat=%s...)", key, length, sha[:8], quat_checksum[:8] if quat_checksum else "n/a") + logger.debug( + "[LAWH] Stored '%s' (len=%d sha=%s... quat=%s...)", + key, + length, + sha[:8], + quat_checksum[:8] if quat_checksum else "n/a", + ) return key def verify_integrity(self, key: str) -> bool: @@ -225,6 +246,7 @@ def verify_integrity(self, key: str) -> bool: if entry.quaternary_checksum: try: from memory.quaternary import hamming_distance, quaternary_checksum + actual_quat = quaternary_checksum(entry.content) dist = hamming_distance(actual_quat, entry.quaternary_checksum) if dist > 0: @@ -265,7 +287,8 @@ def search(self, query: str, top_k: int = 5) -> list[LawhEntry]: def get_by_category(self, category: str) -> list[LawhEntry]: """Get all entries in a category (e.g., 'ethical', 'epistemic').""" return [ - e for k, e in self._cache.items() + e + for k, e in self._cache.items() if e.category == category and k not in self._quarantine ] diff --git a/backend/memory/living_memory.py b/backend/memory/living_memory.py index 1e0577b..b322fd5 100644 --- a/backend/memory/living_memory.py +++ b/backend/memory/living_memory.py @@ -29,15 +29,14 @@ import time from dataclasses import dataclass, field from enum import Enum -from typing import Any logger = logging.getLogger("mizan.living_memory") # Novelty Gate thresholds -THETA_IDENTICAL = 0.98 # "I already know this exactly" -THETA_SIMILAR = 0.85 # "I know something like this" -THETA_RELATED = 0.50 # "This is new but related" -THETA_WORTH_STORING = 0.3 # Minimum importance to store unrelated info +THETA_IDENTICAL = 0.98 # "I already know this exactly" +THETA_SIMILAR = 0.85 # "I know something like this" +THETA_RELATED = 0.50 # "This is new but related" +THETA_WORTH_STORING = 0.3 # Minimum importance to store unrelated info # Importance scoring weights W_EMOTION = 0.20 @@ -49,17 +48,17 @@ # Strength / decay parameters INITIAL_STRENGTH = 0.30 -DELTA_REINFORCE = 0.12 # strength boost on re-encounter -DELTA_RETRIEVAL = 0.08 # testing effect: recall strengthens memory -DELTA_REVIEW = 0.05 # dhikr daemon review boost (diminishing) -DELTA_DECAY = 0.02 # per-cycle decay rate for unreviewed -PHI_SPACING = 1.5 # spacing effect exponent +DELTA_REINFORCE = 0.12 # strength boost on re-encounter +DELTA_RETRIEVAL = 0.08 # testing effect: recall strengthens memory +DELTA_REVIEW = 0.05 # dhikr daemon review boost (diminishing) +DELTA_DECAY = 0.02 # per-cycle decay rate for unreviewed +PHI_SPACING = 1.5 # spacing effect exponent # Consolidation thresholds -CONSOLIDATE_RECALL_COUNT = 5 # promote Dhikr→ʿIlm after N retrievals +CONSOLIDATE_RECALL_COUNT = 5 # promote Dhikr→ʿIlm after N retrievals CONSOLIDATE_CONSISTENCY = 0.7 -PROVE_COUNT = 20 # promote ʿIlm→Lawḥ after N verifications -FORGET_THRESHOLD = 0.05 # archive if strength drops below +PROVE_COUNT = 20 # promote ʿIlm→Lawḥ after N verifications +FORGET_THRESHOLD = 0.05 # archive if strength drops below MIN_STRENGTH = 0.01 # Spread activation parameters @@ -76,10 +75,10 @@ class MemoryLevel(Enum): - SADR = "sadr" # Working memory (~4-7 items, seconds) - DHIKR = "dhikr" # Episodic/active (hours-days) - ILM = "ilm" # Semantic/knowledge (weeks-years) - LAWH = "lawh" # Immutable core (forever, write-once-read-many) + SADR = "sadr" # Working memory (~4-7 items, seconds) + DHIKR = "dhikr" # Episodic/active (hours-days) + ILM = "ilm" # Semantic/knowledge (weeks-years) + LAWH = "lawh" # Immutable core (forever, write-once-read-many) class GateDecision(Enum): @@ -93,13 +92,14 @@ class GateDecision(Enum): @dataclass class MemoryTrace: """A single memory trace in the living memory system.""" + trace_id: str content: str - content_hash: str # for fast exact-match + content_hash: str # for fast exact-match level: MemoryLevel strength: float importance: float - emotional_tag: float # -1.0 to +1.0 + emotional_tag: float # -1.0 to +1.0 context_tags: list[str] = field(default_factory=list) links: list[str] = field(default_factory=list) # trace_ids of associated memories recall_count: int = 0 @@ -110,7 +110,7 @@ class MemoryTrace: contradiction_count: int = 0 proven_count: int = 0 mutable: bool = True - gist: str = "" # extracted abstract form (for ʿIlm level) + gist: str = "" # extracted abstract form (for ʿIlm level) source: str = "" def age_hours(self) -> float: @@ -128,6 +128,7 @@ def review_urgency(self) -> float: @dataclass class GateResult: """Result of the Novelty Gate evaluation.""" + decision: GateDecision matched_trace: MemoryTrace | None similarity: float @@ -138,6 +139,7 @@ class GateResult: @dataclass class RecallResult: """A recalled memory with contextual scoring.""" + trace: MemoryTrace activation: float context_match: float @@ -150,6 +152,7 @@ class RecallResult: @dataclass class MaintenanceReport: """Result of one Dhikr Daemon maintenance cycle.""" + reviewed: int decayed: int archived: int @@ -169,14 +172,14 @@ class LivingMemorySystem: def __init__(self, masalik=None, dhikr_db=None, lawh=None, vector_store=None): # Memory stores by level - self.sadr: list[MemoryTrace] = [] # working memory (capacity-limited) + self.sadr: list[MemoryTrace] = [] # working memory (capacity-limited) self.traces: dict[str, MemoryTrace] = {} # all traces by ID - self.archive: list[str] = [] # archived (forgotten) trace IDs + self.archive: list[str] = [] # archived (forgotten) trace IDs # External system references (for integration) - self._masalik = masalik # MasalikNetwork for spread activation + self._masalik = masalik # MasalikNetwork for spread activation self._dhikr_db = dhikr_db # DhikrMemorySystem for persistence - self._lawh = lawh # LawhMahfuz for immutable storage + self._lawh = lawh # LawhMahfuz for immutable storage self._vector_store = vector_store # VectorStore for semantic similarity self._daemon_cycle = 0 @@ -212,9 +215,7 @@ def process_input( best_match, max_similarity = self._find_best_match(content) # Importance scoring - importance = self._score_importance( - content, emotional_state, goals, context, source_trust - ) + importance = self._score_importance(content, emotional_state, goals, context, source_trust) # Decision tree (Algorithm: NOVELTY_GATE) if max_similarity > THETA_IDENTICAL and best_match: @@ -228,9 +229,7 @@ def process_input( elif max_similarity > THETA_RELATED and best_match: # "New but related" → store with links - trace = self._create_trace( - content, content_hash, importance, emotional_state, context - ) + trace = self._create_trace(content, content_hash, importance, emotional_state, context) trace.links.append(best_match.trace_id) self._store_trace(trace) return GateResult( @@ -291,7 +290,6 @@ def recall( activations: dict[str, float] = {} # Wave 1: Direct matches - query_words = set(query.lower().split()) for tid, trace in self.traces.items(): if tid in self.archive: continue @@ -306,9 +304,7 @@ def recall( for linked_id in trace.links: if linked_id in self.traces and linked_id not in self.archive: link_activation = activation * 0.6 - wave2[linked_id] = max( - wave2.get(linked_id, 0), link_activation - ) + wave2[linked_id] = max(wave2.get(linked_id, 0), link_activation) for tid, act in wave2.items(): activations[tid] = max(activations.get(tid, 0), act) @@ -318,9 +314,7 @@ def recall( trace = self.traces[tid] for linked_id in trace.links: if linked_id in self.traces and linked_id not in self.archive: - wave3[linked_id] = max( - wave3.get(linked_id, 0), activation * 0.3 - ) + wave3[linked_id] = max(wave3.get(linked_id, 0), activation * 0.3) for tid, act in wave3.items(): activations[tid] = max(activations.get(tid, 0), act) @@ -333,34 +327,36 @@ def recall( trace = self.traces[tid] # Context modulation - context_match = self._text_similarity(context, " ".join(trace.context_tags)) if context else 0.0 + context_match = ( + self._text_similarity(context, " ".join(trace.context_tags)) if context else 0.0 + ) modulated = base_activation * (1 + ALPHA_CONTEXT * context_match) # Emotional modulation (mood-congruent) emotional_match = 1.0 - abs(emotional_state - trace.emotional_tag) - modulated *= (1 + ALPHA_EMOTION * emotional_match) + modulated *= 1 + ALPHA_EMOTION * emotional_match # Goal modulation goal_match = 0.0 if goals: - goal_match = max( - self._text_similarity(g, trace.content) for g in goals - ) - modulated *= (1 + ALPHA_GOAL * goal_match) + goal_match = max(self._text_similarity(g, trace.content) for g in goals) + modulated *= 1 + ALPHA_GOAL * goal_match # Recency weighting recency = math.exp(-LAMBDA_RECENCY * trace.hours_since_access()) modulated *= recency - results.append(RecallResult( - trace=trace, - activation=modulated, - context_match=context_match, - emotional_match=emotional_match, - goal_match=goal_match, - recency=recency, - reconstructed=trace.content, # simplified: no gap-filling yet - )) + results.append( + RecallResult( + trace=trace, + activation=modulated, + context_match=context_match, + emotional_match=emotional_match, + goal_match=goal_match, + recency=recency, + reconstructed=trace.content, # simplified: no gap-filling yet + ) + ) # Sort by activation, take top-k results.sort(key=lambda r: r.activation, reverse=True) @@ -401,15 +397,14 @@ def run_maintenance(self) -> MaintenanceReport: # Step 1-2: Review overdue traces overdue = [ - t for t in self.traces.values() + t + for t in self.traces.values() if t.mutable and t.trace_id not in self.archive and t.review_urgency() > 1.0 ] overdue.sort(key=lambda t: t.review_urgency() * t.importance, reverse=True) for trace in overdue[:20]: # max 20 reviews per cycle - trace.strength = min(1.0, - trace.strength + DELTA_REVIEW / max(trace.recall_count, 1) - ) + trace.strength = min(1.0, trace.strength + DELTA_REVIEW / max(trace.recall_count, 1)) trace.last_accessed = time.time() # Extend optimal interval (spaced repetition) trace.optimal_interval *= (1 + trace.strength) ** PHI_SPACING @@ -422,7 +417,7 @@ def run_maintenance(self) -> MaintenanceReport: if trace.review_urgency() < 1.0: continue # not overdue, no decay if trace.importance < 0.5: # only decay low-importance - trace.strength *= (1 - DELTA_DECAY) + trace.strength *= 1 - DELTA_DECAY decayed += 1 # Archive if too weak @@ -469,7 +464,12 @@ def run_maintenance(self) -> MaintenanceReport: elapsed_ms = (time.monotonic() - start) * 1000 logger.debug( "[DHIKR-DAEMON] cycle=%d reviewed=%d decayed=%d archived=%d ilm=%d lawh=%d", - self._daemon_cycle, reviewed, decayed, archived, promoted_ilm, promoted_lawh, + self._daemon_cycle, + reviewed, + decayed, + archived, + promoted_ilm, + promoted_lawh, ) return MaintenanceReport( @@ -504,9 +504,7 @@ def _score_importance( # Factor 2: Goal relevance goal_relevance = 0.0 if goals: - goal_relevance = max( - self._text_similarity(g, content) for g in goals - ) + goal_relevance = max(self._text_similarity(g, content) for g in goals) # Factor 3: Prediction error (surprise) — novelty proxy _, max_sim = self._find_best_match(content) @@ -514,16 +512,15 @@ def _score_importance( # Factor 4: Causal significance (keyword heuristic) causal_keywords = {"because", "caused", "leads to", "therefore", "result", "effect"} - causal_impact = min(1.0, - sum(1 for k in causal_keywords if k in content_lower) / 3 - ) + causal_impact = min(1.0, sum(1 for k in causal_keywords if k in content_lower) / 3) # Factor 5: Source trust trust = min(1.0, max(0.0, source_trust)) # Factor 6: Rarity (inverse frequency of similar content) similar_count = sum( - 1 for t in self.traces.values() + 1 + for t in self.traces.values() if self._text_similarity(content, t.content) > THETA_RELATED ) rarity = 1.0 / (1.0 + similar_count) @@ -539,9 +536,7 @@ def _score_importance( ) return self._sigmoid(raw * 3) # scale into sigmoid range - def _activate_existing( - self, trace: MemoryTrace, reason: str - ) -> GateResult: + def _activate_existing(self, trace: MemoryTrace, reason: str) -> GateResult: """Activate an existing trace: no new storage, just strengthen.""" trace.activation_count += 1 trace.last_accessed = time.time() @@ -556,9 +551,7 @@ def _activate_existing( delta_info=f"{reason} — activated (count={trace.activation_count})", ) - def _update_existing( - self, trace: MemoryTrace, delta: str, importance: float - ) -> GateResult: + def _update_existing(self, trace: MemoryTrace, delta: str, importance: float) -> GateResult: """Update an existing trace with novel information delta.""" if delta: trace.content += f" | UPDATE: {delta[:200]}" @@ -584,7 +577,7 @@ def _create_trace( context: str, ) -> MemoryTrace: trace_id = hashlib.md5( - f"{content[:100]}:{time.time()}".encode() + f"{content[:100]}:{time.time()}".encode(), usedforsecurity=False ).hexdigest()[:12] # New traces start in Ṣadr (working memory) @@ -688,9 +681,7 @@ def _vector_search_sync(self, query: str, limit: int = 5) -> list[dict]: asyncio.get_running_loop() # Already in async context — run in thread to avoid nested loop with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: - future = executor.submit( - asyncio.run, self._vector_store.search(query, limit=limit) - ) + future = executor.submit(asyncio.run, self._vector_store.search(query, limit=limit)) return future.result(timeout=5) except RuntimeError: # No running event loop — safe to run directly diff --git a/backend/memory/memory_pyramid.py b/backend/memory/memory_pyramid.py index f60f660..04665c7 100644 --- a/backend/memory/memory_pyramid.py +++ b/backend/memory/memory_pyramid.py @@ -27,11 +27,12 @@ @dataclass class MemoryHit: """A single result from any memory layer.""" + content: str - source_layer: str # "masalik" | "dhikr" | "vector" | "graph" | "lawh" - relevance: float # 0-1 (how well it matches query) - certainty: float # 0-1 (how reliable the memory is) - recency_score: float # 0-1 (1 = very recent) + source_layer: str # "masalik" | "dhikr" | "vector" | "graph" | "lawh" + relevance: float # 0-1 (how well it matches query) + certainty: float # 0-1 (how reliable the memory is) + recency_score: float # 0-1 (1 = very recent) tags: list[str] = field(default_factory=list) metadata: dict = field(default_factory=dict) @@ -99,16 +100,18 @@ def query(self, text: str, top_k: int = 10) -> list[MemoryHit]: for concept, activation in pathway_results: node = self.masalik.concepts.get(concept) certainty = node.resting_level if node else 0.3 - hits.append(MemoryHit( - content=f"Associated concept: {concept}", - source_layer="masalik", - relevance=float(activation), - certainty=certainty, - recency_score=_recency_score( - node.last_activated if node else now - 3600, now - ), - tags=["concept", "association"], - )) + hits.append( + MemoryHit( + content=f"Associated concept: {concept}", + source_layer="masalik", + relevance=float(activation), + certainty=certainty, + recency_score=_recency_score( + node.last_activated if node else now - 3600, now + ), + tags=["concept", "association"], + ) + ) except Exception as e: logger.debug("[PYRAMID] Masalik query failed: %s", e) @@ -116,26 +119,27 @@ def query(self, text: str, top_k: int = 10) -> list[MemoryHit]: if self.dhikr: try: import asyncio + loop = asyncio.get_event_loop() if loop.is_running(): # Use sync fallback if we're inside async context memories = self._dhikr_sync_recall(text, top_k) else: - memories = loop.run_until_complete( - self.dhikr.recall(text, limit=top_k) - ) + memories = loop.run_until_complete(self.dhikr.recall(text, limit=top_k)) for mem in memories: content_str = str(mem.content) recency = _recency_score(mem.recency.timestamp(), now) - hits.append(MemoryHit( - content=content_str, - source_layer="dhikr", - relevance=float(mem.importance), - certainty=min(0.9, 0.3 + mem.access_count * 0.05), - recency_score=recency, - tags=mem.tags or [], - metadata={"type": mem.memory_type}, - )) + hits.append( + MemoryHit( + content=content_str, + source_layer="dhikr", + relevance=float(mem.importance), + certainty=min(0.9, 0.3 + mem.access_count * 0.05), + recency_score=recency, + tags=mem.tags or [], + metadata={"type": mem.memory_type}, + ) + ) except Exception as e: logger.debug("[PYRAMID] Dhikr query failed: %s", e) @@ -144,14 +148,16 @@ def query(self, text: str, top_k: int = 10) -> list[MemoryHit]: try: vector_results = self.vector_store.search(text, top_k=top_k) for result in vector_results: - hits.append(MemoryHit( - content=result.get("content", ""), - source_layer="vector", - relevance=float(result.get("score", 0.5)), - certainty=0.7, # Vector search has good precision - recency_score=0.5, # Unknown recency - tags=result.get("tags", []), - )) + hits.append( + MemoryHit( + content=result.get("content", ""), + source_layer="vector", + relevance=float(result.get("score", 0.5)), + certainty=0.7, # Vector search has good precision + recency_score=0.5, # Unknown recency + tags=result.get("tags", []), + ) + ) except Exception as e: logger.debug("[PYRAMID] VectorStore query failed: %s", e) @@ -164,14 +170,16 @@ def query(self, text: str, top_k: int = 10) -> list[MemoryHit]: content = f"{entity.get('name', '')} ({entity.get('type', 'entity')})" if props: content += f": {list(props.items())[:3]}" - hits.append(MemoryHit( - content=content, - source_layer="graph", - relevance=0.7, # Entity match is high relevance - certainty=0.6, - recency_score=0.4, - tags=["entity", entity.get("type", "concept")], - )) + hits.append( + MemoryHit( + content=content, + source_layer="graph", + relevance=0.7, # Entity match is high relevance + certainty=0.6, + recency_score=0.4, + tags=["entity", entity.get("type", "concept")], + ) + ) except Exception as e: logger.debug("[PYRAMID] KnowledgeGraph query failed: %s", e) @@ -180,15 +188,17 @@ def query(self, text: str, top_k: int = 10) -> list[MemoryHit]: try: lawh_results = self.lawh_mahfuz.search(text, top_k=top_k) for entry in lawh_results: - hits.append(MemoryHit( - content=entry.content, - source_layer="lawh", - relevance=0.9, # Immutable facts are highly reliable - certainty=entry.certainty, - recency_score=1.0, # Immutable facts never decay - tags=["immutable", entry.category], - metadata={"source": entry.source}, - )) + hits.append( + MemoryHit( + content=entry.content, + source_layer="lawh", + relevance=0.9, # Immutable facts are highly reliable + certainty=entry.certainty, + recency_score=1.0, # Immutable facts never decay + tags=["immutable", entry.category], + metadata={"source": entry.source}, + ) + ) except Exception as e: logger.debug("[PYRAMID] LawhMahfuz query failed: %s", e) @@ -213,11 +223,10 @@ def _dhikr_sync_recall(self, query: str, limit: int) -> list: try: import asyncio import concurrent.futures + # Run the coroutine in a separate thread with its own event loop with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: - future = executor.submit( - asyncio.run, self.dhikr.recall(query, limit=limit) - ) + future = executor.submit(asyncio.run, self.dhikr.recall(query, limit=limit)) return future.result(timeout=5) except Exception: return [] @@ -245,7 +254,7 @@ def stats(self) -> dict: "masalik": self.masalik is not None, "dhikr": self.dhikr is not None, "vector_store": self.vector_store is not None - and getattr(self.vector_store, "_available", False), + and getattr(self.vector_store, "_available", False), "knowledge_graph": self.knowledge_graph is not None, "lawh_mahfuz": self.lawh_mahfuz is not None, } diff --git a/backend/memory/quaternary.py b/backend/memory/quaternary.py index 3cf266a..56e792e 100644 --- a/backend/memory/quaternary.py +++ b/backend/memory/quaternary.py @@ -81,7 +81,12 @@ def quaternary_to_bytes(quat: str) -> bytes: # 3 quaternary symbols = 1 codon (64 possible triplets → semantic categories) CODON_CATEGORIES = [ - "data", "reference", "separator", "checksum", "metadata", "padding", + "data", + "reference", + "separator", + "checksum", + "metadata", + "padding", ] diff --git a/backend/perception/nutq.py b/backend/perception/nutq.py index e211a46..4cd8570 100644 --- a/backend/perception/nutq.py +++ b/backend/perception/nutq.py @@ -174,17 +174,37 @@ def _detect_intent(text: str) -> str: if text_stripped.endswith("?"): return "question" question_starts = [ - "what ", "where ", "when ", "who ", "whom ", "which ", "why ", "how ", - "is it ", "are there ", "can you ", "could you ", "would you ", - "do you ", "does it ", "will it ", "shall ", + "what ", + "where ", + "when ", + "who ", + "whom ", + "which ", + "why ", + "how ", + "is it ", + "are there ", + "can you ", + "could you ", + "would you ", + "do you ", + "does it ", + "will it ", + "shall ", ] if any(text_lower.startswith(q) for q in question_starts): return "question" # Greeting detection greetings = [ - "hello", "hi ", "hey ", "good morning", "good afternoon", - "good evening", "salam", "assalamu", + "hello", + "hi ", + "hey ", + "good morning", + "good afternoon", + "good evening", + "salam", + "assalamu", ] if any(text_lower.startswith(g) for g in greetings): return "greeting" @@ -202,11 +222,43 @@ def _detect_intent(text: str) -> str: # Command detection: imperative verbs command_verbs = [ - "do ", "run ", "create ", "delete ", "open ", "close ", "find ", "search ", - "show ", "tell ", "help ", "make ", "build ", "write ", "read ", "send ", - "stop ", "start ", "restart ", "install ", "update ", "fix ", "check ", - "list ", "get ", "set ", "add ", "remove ", "move ", "copy ", "save ", - "load ", "deploy ", "test ", "analyze ", "explain ", "summarize ", + "do ", + "run ", + "create ", + "delete ", + "open ", + "close ", + "find ", + "search ", + "show ", + "tell ", + "help ", + "make ", + "build ", + "write ", + "read ", + "send ", + "stop ", + "start ", + "restart ", + "install ", + "update ", + "fix ", + "check ", + "list ", + "get ", + "set ", + "add ", + "remove ", + "move ", + "copy ", + "save ", + "load ", + "deploy ", + "test ", + "analyze ", + "explain ", + "summarize ", ] if any(text_lower.startswith(w) for w in command_verbs): return "command" @@ -225,9 +277,11 @@ def _detect_language(text: str) -> str: return "en" # Count characters in Arabic Unicode range - arabic_count = sum(1 for c in text if "\u0600" <= c <= "\u06FF") + arabic_count = sum(1 for c in text if "\u0600" <= c <= "\u06ff") # Extended Arabic (includes Urdu-specific) - extended_arabic = sum(1 for c in text if "\uFB50" <= c <= "\uFDFF" or "\uFE70" <= c <= "\uFEFF") + extended_arabic = sum( + 1 for c in text if "\ufb50" <= c <= "\ufdff" or "\ufe70" <= c <= "\ufeff" + ) total_alpha = sum(1 for c in text if c.isalpha()) if total_alpha == 0: @@ -237,7 +291,11 @@ def _detect_language(text: str) -> str: if arabic_ratio > 0.3: # Distinguish Arabic from Urdu by checking for Urdu-specific characters - urdu_specific = sum(1 for c in text if c in "\u0679\u067E\u0686\u0688\u0691\u0698\u06BA\u06BE\u06C1\u06CC\u06D2") + urdu_specific = sum( + 1 + for c in text + if c in "\u0679\u067e\u0686\u0688\u0691\u0698\u06ba\u06be\u06c1\u06cc\u06d2" + ) if urdu_specific > 0: return "ur" return "ar" diff --git a/backend/providers.py b/backend/providers.py index f613c53..316603c 100644 --- a/backend/providers.py +++ b/backend/providers.py @@ -197,12 +197,14 @@ def _parse_xml_tool_calls(text: str) -> list["ContentBlock"]: params[key] = json.loads(value) except (json.JSONDecodeError, ValueError): params[key] = value - tool_blocks.append(ContentBlock( - type="tool_use", - id=f"xmltool_{uuid.uuid4().hex[:12]}", - name=tool_name, - input=params, - )) + tool_blocks.append( + ContentBlock( + type="tool_use", + id=f"xmltool_{uuid.uuid4().hex[:12]}", + name=tool_name, + input=params, + ) + ) return tool_blocks @staticmethod @@ -210,16 +212,20 @@ def _strip_xml_tool_calls(text: str) -> str: """Remove XML tool-call markup from text, keeping surrounding prose.""" # Remove ... wrappers cleaned = re.sub( - r'.*?', - '', text, flags=re.DOTALL, + r".*?", + "", + text, + flags=re.DOTALL, ) # Remove bare ... blocks cleaned = re.sub( r']*>.*?', - '', cleaned, flags=re.DOTALL, + "", + cleaned, + flags=re.DOTALL, ) # Collapse multiple blank lines - cleaned = re.sub(r'\n{3,}', '\n\n', cleaned) + cleaned = re.sub(r"\n{3,}", "\n\n", cleaned) return cleaned def _convert_tools_to_openai(self, tools: list[dict]) -> list[dict]: @@ -268,20 +274,24 @@ def _convert_messages_to_openai(self, system: str, messages: list[dict]) -> list # Ensure content is a string if not isinstance(raw_content, str): raw_content = json.dumps(raw_content) if raw_content else "" - tool_results.append({ - "role": "tool", - "tool_call_id": block.get("tool_use_id", ""), - "content": raw_content, - }) + tool_results.append( + { + "role": "tool", + "tool_call_id": block.get("tool_use_id", ""), + "content": raw_content, + } + ) elif block_type == "tool_use": - tool_calls.append({ - "id": block.get("id", ""), - "type": "function", - "function": { - "name": block.get("name", ""), - "arguments": json.dumps(block.get("input", {})), - }, - }) + tool_calls.append( + { + "id": block.get("id", ""), + "type": "function", + "function": { + "name": block.get("name", ""), + "arguments": json.dumps(block.get("input", {})), + }, + } + ) elif block_type == "text": text_parts.append(block.get("text", "")) elif hasattr(block, "type"): @@ -289,14 +299,16 @@ def _convert_messages_to_openai(self, system: str, messages: list[dict]) -> list if block.type == "text": text_parts.append(block.text) elif block.type == "tool_use": - tool_calls.append({ - "id": block.id, - "type": "function", - "function": { - "name": block.name, - "arguments": json.dumps(block.input), - }, - }) + tool_calls.append( + { + "id": block.id, + "type": "function", + "function": { + "name": block.name, + "arguments": json.dumps(block.input), + }, + } + ) # Emit a single assistant message with text + tool_calls combined if role == "assistant" and (text_parts or tool_calls): @@ -704,9 +716,7 @@ def get_provider_status() -> dict: break default_model = ( - _active_state["model"] - or os.getenv("DEFAULT_MODEL", "") - or get_default_model(active) + _active_state["model"] or os.getenv("DEFAULT_MODEL", "") or get_default_model(active) ) return { @@ -758,7 +768,8 @@ async def fetch_openrouter_models( if search: search_lower = search.lower() models = [ - m for m in models + m + for m in models if search_lower in m["id"].lower() or search_lower in m["name"].lower() ] if free_only: diff --git a/backend/providers_ruh.py b/backend/providers_ruh.py index 79de509..3325c87 100644 --- a/backend/providers_ruh.py +++ b/backend/providers_ruh.py @@ -83,11 +83,7 @@ def _extract_prompt(self, messages: list[dict]) -> str: continue content = msg.get("content", "") if isinstance(content, list): - texts = [ - block.get("text", "") - for block in content - if block.get("type") == "text" - ] + texts = [block.get("text", "") for block in content if block.get("type") == "text"] return " ".join(texts) return str(content) return "" @@ -96,10 +92,6 @@ def _tokens_to_tensors(self, tokens: list) -> tuple: """Convert token pairs to root_id and pattern_id tensors.""" import torch - root_ids = torch.tensor( - [[token[0] for token in tokens]], dtype=torch.long - ) - pattern_ids = torch.tensor( - [[token[1] for token in tokens]], dtype=torch.long - ) + root_ids = torch.tensor([[token[0] for token in tokens]], dtype=torch.long) + pattern_ids = torch.tensor([[token[1] for token in tokens]], dtype=torch.long) return root_ids, pattern_ids diff --git a/backend/qca/engine.py b/backend/qca/engine.py index b141b44..e5859b4 100644 --- a/backend/qca/engine.py +++ b/backend/qca/engine.py @@ -178,7 +178,9 @@ async def process_multimodal( try: basirah = BasirahEngine() insight = await basirah.analyze( - image_bytes, context=context, media_type=media_type, + image_bytes, + context=context, + media_type=media_type, qalb_state=qalb_state, ) results["basirah"] = insight.to_dict() @@ -198,8 +200,12 @@ async def process_multimodal( results["fuad"] = fuad else: results["fuad"] = { - "zahir": "", "batin": "", "key_terms": [], - "vocabulary_richness": 0, "sequential_pairs": [], "total_tokens": 0, + "zahir": "", + "batin": "", + "key_terms": [], + "vocabulary_richness": 0, + "sequential_pairs": [], + "total_tokens": 0, } return results diff --git a/backend/reasoning/aql_engine.py b/backend/reasoning/aql_engine.py index 5815f39..68ff03c 100644 --- a/backend/reasoning/aql_engine.py +++ b/backend/reasoning/aql_engine.py @@ -31,6 +31,7 @@ def _lazy_causal_engine(): """Lazy-load CausalEngine to avoid circular imports.""" try: from reasoning.causal_engine import CausalEngine + return CausalEngine() except ImportError: return None @@ -40,6 +41,7 @@ def _lazy_fuad_engine(): """Lazy-load FuadEngine to avoid circular imports.""" try: from core.fuad import FuadEngine + return FuadEngine() except ImportError: return None @@ -85,6 +87,7 @@ def qca(self): if self._qca is None: try: from qca.engine import QCAEngine + self._qca = QCAEngine() except ImportError: self._qca = None diff --git a/backend/reasoning/causal_engine.py b/backend/reasoning/causal_engine.py index ce28a9a..bea23e3 100644 --- a/backend/reasoning/causal_engine.py +++ b/backend/reasoning/causal_engine.py @@ -22,30 +22,33 @@ class CausalRung(Enum): - OBSERVATION = 1 # Association — seeing patterns - INTERVENTION = 2 # Doing — what happens when I act - COUNTERFACTUAL = 3 # Imagining — what would have been + OBSERVATION = 1 # Association — seeing patterns + INTERVENTION = 2 # Doing — what happens when I act + COUNTERFACTUAL = 3 # Imagining — what would have been @dataclass class CausalNode: """A variable in the causal graph.""" + name: str - value: float = 0.5 # Normalised probability (0-1) + value: float = 0.5 # Normalised probability (0-1) is_observed: bool = False @dataclass class CausalEdge: """A directed causal link: source → target with strength.""" + source: str target: str - strength: float = 0.5 # Causal strength (0-1) + strength: float = 0.5 # Causal strength (0-1) @dataclass class CausalModel: """A structural causal model (SCM) built from observations.""" + nodes: dict[str, CausalNode] = field(default_factory=dict) edges: list[CausalEdge] = field(default_factory=list) @@ -59,6 +62,7 @@ def children_of(self, node_name: str) -> list[str]: @dataclass class ObservationResult: """Rung 1 result: observed associations.""" + rung: CausalRung = CausalRung.OBSERVATION associations: dict[str, float] = field(default_factory=dict) model: CausalModel = field(default_factory=CausalModel) @@ -68,6 +72,7 @@ class ObservationResult: @dataclass class InterventionResult: """Rung 2 result: effect of doing action.""" + rung: CausalRung = CausalRung.INTERVENTION action: str = "" predicted_effects: dict[str, float] = field(default_factory=dict) @@ -87,6 +92,7 @@ def to_dict(self) -> dict: @dataclass class CounterfactualResult: """Rung 3 result: what would have happened.""" + rung: CausalRung = CausalRung.COUNTERFACTUAL factual: str = "" alternative: str = "" @@ -112,13 +118,24 @@ def _detect_causal_rung(text: str) -> CausalRung: """ lower = text.lower() rung3_signals = [ - "what if i had", "what would have", "if only", "had i", - "would have been", "could have", "should have", + "what if i had", + "what would have", + "if only", + "had i", + "would have been", + "could have", + "should have", ] rung2_signals = [ - "what if i", "what happens if", "what would happen", - "if i do", "if i delete", "if i change", "if i run", - "what if we", "what if you", + "what if i", + "what happens if", + "what would happen", + "if i do", + "if i delete", + "if i change", + "if i run", + "what if we", + "what if you", ] if any(s in lower for s in rung3_signals): return CausalRung.COUNTERFACTUAL @@ -156,7 +173,7 @@ def observe(self, data: dict[str, float]) -> ObservationResult: # Create edges based on co-occurrence / correlation heuristics # High-value nodes tend to "cause" downstream effects for i, a in enumerate(keys): - for b in keys[i + 1:]: + for b in keys[i + 1 :]: va, vb = data[a], data[b] # If both are high, assume possible causal relationship if abs(va - vb) < 0.3: @@ -165,7 +182,9 @@ def observe(self, data: dict[str, float]) -> ObservationResult: model.edges.append(CausalEdge(source=a, target=b, strength=strength)) associations = {k: round(v, 3) for k, v in data.items()} - summary = f"Observed {len(keys)} variables, identified {len(model.edges)} potential causal links." + summary = ( + f"Observed {len(keys)} variables, identified {len(model.edges)} potential causal links." + ) logger.debug("[CAUSAL] Rung 1: %s", summary) return ObservationResult( @@ -215,9 +234,12 @@ def intervene(self, model: CausalModel, action: str) -> InterventionResult: for child in children: if child not in predicted_effects: edge = next( - (e for e in model.edges - if e.source == node_name and e.target == child), - None + ( + e + for e in model.edges + if e.source == node_name and e.target == child + ), + None, ) if edge: propagated = effects[trigger_var] * edge.strength * 0.7 @@ -293,8 +315,12 @@ def counterfactual( f"Probability estimate: {probability:.0%}." ) - logger.debug("[CAUSAL] Rung 3: factual='%s' alt='%s' prob=%.2f", - factual[:40], alternative[:40], probability) + logger.debug( + "[CAUSAL] Rung 3: factual='%s' alt='%s' prob=%.2f", + factual[:40], + alternative[:40], + probability, + ) return CounterfactualResult( factual=factual, alternative=alternative, diff --git a/backend/router/intelligent_router.py b/backend/router/intelligent_router.py index 807862e..3a5f894 100644 --- a/backend/router/intelligent_router.py +++ b/backend/router/intelligent_router.py @@ -48,19 +48,33 @@ def route( """Route to best provider. Returns (response, decision).""" if not self._ruh: return self._route_external( - model, max_tokens, system, messages, tools, temperature, + model, + max_tokens, + system, + messages, + tools, + temperature, reason="ruh_not_available", ) # Ruh Model doesn't support tool_use yet if tools: return self._route_external( - model, max_tokens, system, messages, tools, temperature, + model, + max_tokens, + system, + messages, + tools, + temperature, reason="tools_required", ) return self._try_ruh_with_fallback( - model, max_tokens, system, messages, temperature, + model, + max_tokens, + system, + messages, + temperature, ) def _route_external( @@ -77,7 +91,12 @@ def _route_external( if not self._external: raise RuntimeError("No external provider configured") response = self._external.create( - model, max_tokens, system, messages, tools, temperature, + model, + max_tokens, + system, + messages, + tools, + temperature, ) decision = RouteDecision("external", 0.0, reason) self._stats["external"] += 1 @@ -94,7 +113,11 @@ def _try_ruh_with_fallback( """Try Ruh Model first; fall back to external on failure.""" try: response = self._ruh.create( - model, max_tokens, system, messages, temperature=temperature, + model, + max_tokens, + system, + messages, + temperature=temperature, ) decision = RouteDecision("ruh", 0.5, "ruh_available") self._stats["ruh"] += 1 @@ -102,7 +125,12 @@ def _try_ruh_with_fallback( except Exception as exc: logger.warning("Ruh Model failed, falling back to external: %s", exc) return self._route_external( - model, max_tokens, system, messages, None, temperature, + model, + max_tokens, + system, + messages, + None, + temperature, reason=f"ruh_failed: {exc}", ) diff --git a/backend/settings.py b/backend/settings.py index 20020de..ba8d434 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -62,6 +62,13 @@ class Settings(BaseSettings): ruh_confidence_threshold: float = 0.7 ruh_device: str = "cpu" + # ── al-Insān faculties ──────────────────────────────────── + # When true, Baṣīra/Hawā/Ḥikma actively gate & redirect the agent loop + # (e.g. a high-severity lower-pull becomes a forceful restraint). When + # false (default) the faculties are advisory: they emit signals and inject + # guidance but do not block, keeping the default reasoning path unchanged. + deep_faculty_loop: bool = False + # ── Subscription Tiers ──────────────────────────────────── subscription_tier: str = "free" # free | pro | enterprise free_daily_messages: int = 50 diff --git a/backend/skills/builtin/linode_cloud.py b/backend/skills/builtin/linode_cloud.py index 8f170d1..7e36e1c 100644 --- a/backend/skills/builtin/linode_cloud.py +++ b/backend/skills/builtin/linode_cloud.py @@ -18,7 +18,6 @@ import logging import os -from datetime import UTC, datetime import httpx @@ -70,8 +69,7 @@ def _headers(self, token: str | None = None) -> dict: t = token or self._token if not t: raise ValueError( - "No Linode API token. Set LINODE_API_TOKEN env var " - "or call linode_set_token first." + "No Linode API token. Set LINODE_API_TOKEN env var or call linode_set_token first." ) return { "Authorization": f"Bearer {t}", @@ -115,7 +113,9 @@ async def _request( errors = data.get("errors", []) if isinstance(data, dict) else [] error_msgs = [e.get("reason", str(e)) for e in errors] return { - "error": "; ".join(error_msgs) if error_msgs else f"HTTP {resp.status_code}", + "error": "; ".join(error_msgs) + if error_msgs + else f"HTTP {resp.status_code}", "status_code": resp.status_code, "details": data, } @@ -286,10 +286,11 @@ async def reset_password(self, params: dict) -> dict: return {"error": "Password must be at least 11 characters (Linode requirement)"} # Step 1: Shut down - shutdown = await self._request( - "POST", f"/linode/instances/{linode_id}/shutdown" - ) - if shutdown.get("error") and "already powered off" not in str(shutdown.get("error", "")).lower(): + shutdown = await self._request("POST", f"/linode/instances/{linode_id}/shutdown") + if ( + shutdown.get("error") + and "already powered off" not in str(shutdown.get("error", "")).lower() + ): return {"error": f"Shutdown failed: {shutdown.get('error')}"} # Step 2: Wait for offline status (poll up to 60s) @@ -315,7 +316,7 @@ async def reset_password(self, params: dict) -> dict: return {"error": f"Password reset failed: {reset.get('error')}"} # Step 4: Boot back up - boot = await self._request("POST", f"/linode/instances/{linode_id}/boot") + await self._request("POST", f"/linode/instances/{linode_id}/boot") return { "success": True, diff --git a/backend/skills/builtin/ssh_remote.py b/backend/skills/builtin/ssh_remote.py index 8e40bb3..7afe2d2 100644 --- a/backend/skills/builtin/ssh_remote.py +++ b/backend/skills/builtin/ssh_remote.py @@ -19,7 +19,7 @@ import os import subprocess import tempfile -from dataclasses import dataclass, field +from dataclasses import dataclass from datetime import UTC, datetime from ..base import SkillBase, SkillManifest @@ -199,7 +199,9 @@ async def ssh_exec(self, params: dict) -> dict: """Execute a command on a remote server via SSH.""" server = self._get_server(params) if not server: - return {"error": "Server not found. Register with ssh_register_server first, or provide 'host' in params."} + return { + "error": "Server not found. Register with ssh_register_server first, or provide 'host' in params." + } command = params.get("command", "") if not command: @@ -269,9 +271,7 @@ async def ssh_exec_script(self, params: dict) -> dict: if server.password: scp_cmd = self._wrap_with_sshpass(scp_cmd, server.password) - upload_result = subprocess.run( - scp_cmd, capture_output=True, text=True, timeout=30 - ) + upload_result = subprocess.run(scp_cmd, capture_output=True, text=True, timeout=30) if upload_result.returncode != 0: return { "error": f"Failed to upload script: {upload_result.stderr[:500]}", @@ -284,9 +284,7 @@ async def ssh_exec_script(self, params: dict) -> dict: if server.password: ssh_cmd = self._wrap_with_sshpass(ssh_cmd, server.password) - result = subprocess.run( - ssh_cmd, capture_output=True, text=True, timeout=timeout - ) + result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=timeout) # Cleanup remote script cleanup_cmd = self._build_ssh_cmd(server, f"rm -f {remote_script}") @@ -322,9 +320,7 @@ async def ssh_upload(self, params: dict) -> dict: # Support inline content → write to temp then upload content = params.get("content", "") if content and not local_path: - with tempfile.NamedTemporaryFile( - mode="w", delete=False, prefix="mizan_upload_" - ) as tmp: + with tempfile.NamedTemporaryFile(mode="w", delete=False, prefix="mizan_upload_") as tmp: tmp.write(content) local_path = tmp.name @@ -336,9 +332,7 @@ async def ssh_upload(self, params: dict) -> dict: scp_cmd = self._wrap_with_sshpass(scp_cmd, server.password) try: - result = subprocess.run( - scp_cmd, capture_output=True, text=True, timeout=120 - ) + result = subprocess.run(scp_cmd, capture_output=True, text=True, timeout=120) # Clean up temp file if we created one from content if content: os.unlink(local_path) @@ -370,9 +364,7 @@ async def ssh_download(self, params: dict) -> dict: scp_cmd = self._wrap_with_sshpass(scp_cmd, server.password) try: - result = subprocess.run( - scp_cmd, capture_output=True, text=True, timeout=120 - ) + result = subprocess.run(scp_cmd, capture_output=True, text=True, timeout=120) return { "success": result.returncode == 0, "local_path": local_path, @@ -394,9 +386,7 @@ async def check_server(self, params: dict) -> dict: ssh_cmd = self._wrap_with_sshpass(ssh_cmd, server.password) try: - result = subprocess.run( - ssh_cmd, capture_output=True, text=True, timeout=15 - ) + result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=15) if result.returncode == 0 and "MIZAN_PING_OK" in result.stdout: server.status = "connected" server.last_connected = datetime.now(UTC).isoformat() @@ -456,10 +446,14 @@ async def ssh_keygen(self, params: dict = None) -> dict: result = subprocess.run( [ "ssh-keygen", - "-t", "ed25519", - "-f", key_path, - "-N", "", # No passphrase - "-C", "mizan-agent", + "-t", + "ed25519", + "-f", + key_path, + "-N", + "", # No passphrase + "-C", + "mizan-agent", ], capture_output=True, text=True, @@ -550,14 +544,10 @@ async def ssh_copy_id(self, params: dict) -> dict: # Verify key auth works verify_cmd = self._build_ssh_cmd(server, "echo 'MIZAN_KEY_AUTH_OK'") - verify = subprocess.run( - verify_cmd, capture_output=True, text=True, timeout=15 - ) + verify = subprocess.run(verify_cmd, capture_output=True, text=True, timeout=15) if verify.returncode == 0 and "MIZAN_KEY_AUTH_OK" in verify.stdout: - logger.info( - f"[JISR] SSH key installed and verified on {server.host}" - ) + logger.info(f"[JISR] SSH key installed and verified on {server.host}") return { "success": True, "server": server.host, @@ -597,8 +587,15 @@ def get_tool_schemas(self) -> list[dict]: "properties": { "host": {"type": "string", "description": "Server IP or hostname"}, "port": {"type": "integer", "description": "SSH port", "default": 22}, - "user": {"type": "string", "description": "SSH username", "default": "root"}, - "key_path": {"type": "string", "description": "Path to SSH private key file"}, + "user": { + "type": "string", + "description": "SSH username", + "default": "root", + }, + "key_path": { + "type": "string", + "description": "Path to SSH private key file", + }, "password": {"type": "string", "description": "SSH password (if no key)"}, "label": {"type": "string", "description": "Friendly name for this server"}, }, @@ -612,12 +609,19 @@ def get_tool_schemas(self) -> list[dict]: "type": "object", "properties": { "server_id": {"type": "string", "description": "Registered server ID"}, - "host": {"type": "string", "description": "Server host (if not registered)"}, + "host": { + "type": "string", + "description": "Server host (if not registered)", + }, "user": {"type": "string", "description": "SSH user (default: root)"}, "password": {"type": "string", "description": "SSH password"}, "key_path": {"type": "string", "description": "SSH key path"}, "command": {"type": "string", "description": "Command to execute"}, - "timeout": {"type": "integer", "description": "Timeout in seconds", "default": 60}, + "timeout": { + "type": "integer", + "description": "Timeout in seconds", + "default": 60, + }, }, "required": ["command"], }, @@ -632,9 +636,20 @@ def get_tool_schemas(self) -> list[dict]: "host": {"type": "string", "description": "Server host"}, "user": {"type": "string", "description": "SSH user"}, "password": {"type": "string", "description": "SSH password"}, - "script": {"type": "string", "description": "Multi-line bash script to execute"}, - "interpreter": {"type": "string", "description": "Script interpreter", "default": "/bin/bash"}, - "timeout": {"type": "integer", "description": "Timeout in seconds", "default": 300}, + "script": { + "type": "string", + "description": "Multi-line bash script to execute", + }, + "interpreter": { + "type": "string", + "description": "Script interpreter", + "default": "/bin/bash", + }, + "timeout": { + "type": "integer", + "description": "Timeout in seconds", + "default": 300, + }, }, "required": ["script"], }, @@ -647,8 +662,14 @@ def get_tool_schemas(self) -> list[dict]: "properties": { "server_id": {"type": "string", "description": "Registered server ID"}, "host": {"type": "string", "description": "Server host"}, - "local_path": {"type": "string", "description": "Local file path to upload"}, - "content": {"type": "string", "description": "File content to upload (alternative to local_path)"}, + "local_path": { + "type": "string", + "description": "Local file path to upload", + }, + "content": { + "type": "string", + "description": "File content to upload (alternative to local_path)", + }, "remote_path": {"type": "string", "description": "Remote destination path"}, }, "required": ["remote_path"], @@ -706,9 +727,19 @@ def get_tool_schemas(self) -> list[dict]: "server_id": {"type": "string", "description": "Registered server ID"}, "host": {"type": "string", "description": "Server IP or hostname"}, "port": {"type": "integer", "description": "SSH port", "default": 22}, - "user": {"type": "string", "description": "SSH username", "default": "root"}, - "password": {"type": "string", "description": "Current server password (required for first-time key setup)"}, - "key_path": {"type": "string", "description": "SSH key path (default: /data/.ssh/mizan_id_ed25519)"}, + "user": { + "type": "string", + "description": "SSH username", + "default": "root", + }, + "password": { + "type": "string", + "description": "Current server password (required for first-time key setup)", + }, + "key_path": { + "type": "string", + "description": "SSH key path (default: /data/.ssh/mizan_id_ed25519)", + }, }, "required": ["password"], }, diff --git a/backend/task_queue/task_queue.py b/backend/task_queue/task_queue.py index 1345808..6790ed4 100644 --- a/backend/task_queue/task_queue.py +++ b/backend/task_queue/task_queue.py @@ -36,10 +36,7 @@ "VALUES (?, ?, ?, ?, ?, ?, ?)" ) -_UPDATE_SQL = ( - "UPDATE tasks SET status = ?, result = ?, error = ?, updated_at = ? " - "WHERE task_id = ?" -) +_UPDATE_SQL = "UPDATE tasks SET status = ?, result = ?, error = ?, updated_at = ? WHERE task_id = ?" @dataclass(order=True) @@ -126,7 +123,7 @@ async def wait_for_task(self, timeout: float = 30.0) -> QueuedTask | None: """Wait for next available task with timeout.""" try: await asyncio.wait_for(self._event.wait(), timeout=timeout) - except asyncio.TimeoutError: + except TimeoutError: return None return await self.dequeue() diff --git a/backend/task_queue/worker.py b/backend/task_queue/worker.py index 5823005..327dcc5 100644 --- a/backend/task_queue/worker.py +++ b/backend/task_queue/worker.py @@ -2,9 +2,9 @@ import asyncio import logging -from typing import Awaitable, Callable +from collections.abc import Awaitable, Callable -from task_queue.task_queue import QueuedTask +from task_queue.task_queue import MizanTaskQueue, QueuedTask logger = logging.getLogger("mizan.queue.worker") diff --git a/backend/training_manager.py b/backend/training_manager.py index f7b2970..260941f 100644 --- a/backend/training_manager.py +++ b/backend/training_manager.py @@ -12,7 +12,6 @@ import asyncio import json import logging -import os import time from dataclasses import dataclass, field from pathlib import Path @@ -110,11 +109,17 @@ async def start_training( # Build command cmd = [ - "python", "-m", "ruh_model.train", - "--stage", stage, - "--data-dir", data_dir, - "--checkpoint-dir", checkpoint_dir, - "--log-every", str(log_every), + "python", + "-m", + "ruh_model.train", + "--stage", + stage, + "--data-dir", + data_dir, + "--checkpoint-dir", + checkpoint_dir, + "--log-every", + str(log_every), "--generate-data", "--json-progress", ] @@ -142,9 +147,7 @@ async def start_training( ) # Start monitoring task - self._monitor_task = asyncio.create_task( - self._monitor_output(), name="training_monitor" - ) + self._monitor_task = asyncio.create_task(self._monitor_output(), name="training_monitor") return self.get_status() @@ -161,7 +164,7 @@ async def stop_training(self) -> dict[str, Any]: # Wait briefly for graceful shutdown try: await asyncio.wait_for(self._process.wait(), timeout=5.0) - except asyncio.TimeoutError: + except TimeoutError: self._process.kill() await self._process.wait() @@ -197,7 +200,9 @@ async def _monitor_output(self) -> None: self._state.message = "Training completed successfully" self._save_run_to_history("completed") else: - stderr_bytes = await self._process.stderr.read() if self._process.stderr else b"" + stderr_bytes = ( + await self._process.stderr.read() if self._process.stderr else b"" + ) stderr = stderr_bytes.decode("utf-8", errors="replace")[-500:] self._state.message = f"Training failed (exit {return_code}): {stderr}" self._save_run_to_history("failed") @@ -237,7 +242,9 @@ async def _handle_progress(self, data: dict[str, Any]) -> None: self._state.message = ( f"Epoch {self._state.epoch}/{self._state.total_epochs} " f"Step {self._state.step}/{self._state.total_steps} " - f"Loss: {self._state.loss:.4f}" if self._state.loss else "" + f"Loss: {self._state.loss:.4f}" + if self._state.loss + else "" ) elif msg_type == "epoch": @@ -247,7 +254,9 @@ async def _handle_progress(self, data: dict[str, Any]) -> None: self._state.epoch = data.get("epoch", 0) self._state.message = ( f"Epoch {self._state.epoch}/{self._state.total_epochs} complete. " - f"Avg loss: {avg_loss:.4f}" if avg_loss else "" + f"Avg loss: {avg_loss:.4f}" + if avg_loss + else "" ) elif msg_type == "complete": @@ -255,7 +264,8 @@ async def _handle_progress(self, data: dict[str, Any]) -> None: final_loss = data.get("final_loss") self._state.message = ( f"Training complete. Final loss: {final_loss:.4f}" - if final_loss else "Training complete." + if final_loss + else "Training complete." ) self._save_run_to_history("completed") @@ -265,10 +275,12 @@ async def _broadcast_state(self) -> None: """Send current state to all WebSocket clients.""" if self._broadcast_fn: try: - await self._broadcast_fn({ - "type": "training_progress", - **self.get_status(), - }) + await self._broadcast_fn( + { + "type": "training_progress", + **self.get_status(), + } + ) except Exception as exc: logger.debug("Broadcast failed: %s", exc) diff --git a/docs/quranic_human_model.md b/docs/quranic_human_model.md new file mode 100644 index 0000000..8a13164 --- /dev/null +++ b/docs/quranic_human_model.md @@ -0,0 +1,292 @@ +# The Qur'anic Model of the Human (al-Insān) — MIZAN's Design Contract + +> *"We have certainly created the human in the best of stature."* — Qur'an 95:4 + +This document is the **research foundation** for MIZAN's cognitive architecture. +It reads the Qur'anic account of the human being (*al-insān*) as a layered +cognitive architecture, with each faculty grounded in āyāt, and then maps that +model onto the actual MIZAN codebase — what exists, what is partial, and what is +missing. Everything MIZAN builds toward AGI should be faithful to this model, +not decorative. + +Arabic terms are given in transliteration + Arabic script on first use, with the +sūra:āya reference. Where the Qur'an is silent and the Islamic tradition (Sufi +psychology, uṣūl, hadith) extends a concept, that is marked explicitly. + +--- + +## 1. Why the human, and why the Qur'an + +The Qur'an does not present the human as a single "mind." It describes a +**plurality of interacting faculties** — rūḥ, nafs, qalb, fu'ād, lubb, the +activity of ʿaql, the senses, fiṭrah, hawā — each with its own function, failure +mode, and developmental trajectory. Read together, they form a control system: +perception feeds the heart, the heart reasons and forms conviction, the self is +pulled between higher and lower inclinations, conscience monitors in real time, +and the whole is oriented toward a telos (stewardship, wisdom, excellence, +justice). This is precisely the shape of a cognitive architecture for an agent. + +MIZAN ("الميزان", *the Balance*, Qur'an 55:7) takes this seriously: the goal is +an agent whose reasoning is **bound to evidence** (the literal sense of ʿaql), +**humble about certainty** (the Mīzān of knowledge), **restrained from impulse** +(taqwā over hawā), and **accountable** (the amāna). + +--- + +## 2. The al-Insān stack (north-star architecture) + +``` + TELOS / OBJECTIVE FUNCTION + Khilāfa (stewardship) · Ḥikma (wisdom) · Iḥsān (excellence) + ʿAdl / Mīzān (justice) · Taqwā (God-consciousness) + ───────────────────────────────────────────────────────────── + VOLITION Niyya (intention) → Irāda/Ikhtiyār (will) → Amāna (accountability) + CONSCIENCE Lawwāma (self-reproach) + Baṣīra against the self ← fires continuously + ───────────────────────────────────────────────────────────────── + PRIORS Fiṭrah (innate axioms) + "the Names" (symbolic/abstraction capacity) + EPISTEMICS Yaqīn ladder (ʿilm → ʿayn → ḥaqq) vs Ẓann/Shakk/Wahm, weighed by Mīzān + MEMORY Dhikr (remembrance) · Ḥifẓ · Lawḥ Maḥfūẓ (preserved tablet) + PERCEPTION Samʿ (hearing) · Baṣar (sight) · Baṣīra (inner sight/insight) + REASONING ʿAql (a *verb*: binding impulse to evidence) · Tafakkur · Tadabbur + HEART-COMPLEX Qalb (turning seat of cognition+affect) · Fu'ād (integration) · Lubb (kernel) + SELF Nafs: ammāra ↔ lawwāma ↔ muṭma'inna (pulled by Hawā/Waswās, held by Taqwā) + VITALITY Rūḥ (the breathed-in spirit, of the divine amr) + SUBSTRATE Bashar / khalq stages → "khalqan ākhar" (a new creation) +``` + +Each layer is detailed below. + +--- + +## 3. The faculties + +### 3.1 Substrate & origin — *bashar*, the creation stages +- The human is formed through stages — *nuṭfa → ʿalaqa → muḍgha → ʿiẓām (bones) + → laḥm (flesh)* — and then **"thumma anshaʾnāhu khalqan ākhar"**, *"then We + produced him as another creation"* (23:12–14). The qualitative leap to a *new + kind of being* is the Qur'anic analogue of **emergence**. +- The human is made "in the best of stature" — *aḥsan taqwīm* (95:4) — and + appointed *khalīfa* (steward/vicegerent) on earth (2:30). +- **Architectural reading:** capability is **developmentally gated** — a being + earns higher faculties by passing through stages, not all at once. + +### 3.2 Vitality — Rūḥ (روح) +- The divine in-breathing: *"and I breathed into him of My Rūḥ"* (15:29; 32:9). +- *"They ask you about the Rūḥ. Say: the Rūḥ is of the command (amr) of my Lord, + and you have not been given of knowledge except a little"* (17:85). +- **Reading:** the animating, partly-unknowable **vitality/energy principle** — + what gives the system "life," drive, and fatigue limits. Not fully + introspectable. + +### 3.3 The self — Nafs (نفس) +- The morally-charged self, capable of purification or ruin: *"By the nafs and + how He proportioned it, and inspired it [with discernment of] its wickedness + (fujūr) and its righteousness (taqwā); successful is he who **purifies it + (zakkāhā)**, and failed is he who corrupts it (dassāhā)"* (91:7–10). +- Three Qur'anic states (the maturation axis): + - **Nafs al-ammāra bi'l-sūʾ** — *the self that commands to evil* (12:53) + - **Nafs al-lawwāma** — *the self-reproaching self* (75:2) + - **Nafs al-muṭma'inna** — *the tranquil self at peace* (89:27) + - *(Sufi tradition extends the ladder: mulhama, rāḍiya, marḍiyya, kāmila — + this is the source of MIZAN's 7 nafs levels; the first three are Qur'anic, + the rest are traditional.)* +- The nafs is "taken" in sleep and at death (39:42) → the basis for **offline / + dream-state consolidation**. +- **Reading:** the agent's evolving **self/ego** — its disposition, drive, and + trustworthiness — which *develops* through tazkiya (purification) rather than + being fixed. + +### 3.4 The heart-complex — Qalb, Fu'ād, Lubb +The Qur'an locates *cognition and affect together* in a layered "heart." + +- **Qalb (قلب)** — root *q-l-b* = **to turn / fluctuate**. The seat of + understanding and faith: *"hearts with which they understand"* (22:46; cf. + 7:179). It finds rest in remembrance (13:28); it can be **sealed** (2:7), + **diseased** (2:10), **hardened** (2:74), or **sound — qalb salīm** (26:89). + → A *dynamic, turning* state, not a static sentiment value. +- **Fu'ād (فؤاد, pl. afʾida)** — the **integrating** inner heart, repeatedly + paired with hearing and sight as morally accountable: *"the hearing, the sight, + and the fu'ād — about all of these one will be questioned"* (17:36); *"the + fu'ād did not lie about what it saw"* (53:11). → **fusion of multiple sources + into conviction.** +- **Lubb (لب, pl. albāb)** — the **kernel / distilled core** of intellect. The + *ulu'l-albāb* ("people of the kernel") are the ones who truly reflect and are + given wisdom: *"He gives wisdom to whom He wills… and none remembers except + ulu'l-albāb"* (2:269; 3:190). → **metacognition / the quality-monitor of all + other layers.** + +### 3.5 Reasoning as an activity — ʿAql (عقل), Tafakkur, Tadabbur +- **ʿAql never appears as a noun in the Qur'an** — only as a verb: *yaʿqilūn / + taʿqilūn* ("do you not reason?"). Its root means **to bind / restrain** (as + one ties a camel). Reason, in the Qur'an, is the *act* of binding impulse to + evidence and consequence — not a static organ. → ʿAql should be a **process + MIZAN runs**, not a passive layer. +- **Tafakkur (تفكر)** — reflective analysis: *"they reflect upon the creation of + the heavens and the earth"* (3:191). +- **Tadabbur (تدبر)** — tracing meanings and consequences: *"Do they not + contemplate the Qur'an?"* (4:82; 47:24). Also *Naẓar* (consideration), + *Tafaqquh* (deep comprehension). + +### 3.6 Perception — Samʿ, Baṣar, Baṣīra +- **Samʿ (hearing)** and **Baṣar (sight)** are *always paired* and given **after** + the Rūḥ is breathed in: *"He made for you hearing and sight and afʾida"* + (32:9; 16:78). Samʿ usually precedes baṣar — a priority of **receiving** over + surveying. +- **Baṣīra (بصيرة)** — **inner sight / discernment**: *"I call to God upon + baṣīra (clear insight), I and those who follow me"* (12:108); *"Rather, the + human, against himself, is a baṣīra"* (75:14) — i.e. insight that also + **witnesses the self**. → discernment + self-scrutiny. + +### 3.7 Memory — Dhikr, Lawḥ Maḥfūẓ +- **Dhikr (ذكر)** — remembrance (of knowledge and of God); **ḥifẓ** — + preservation. +- **Lawḥ Maḥfūẓ** — *the Preserved Tablet* (85:22) → the **immutable memory + tier**, the ground truth that cannot be corrupted. + +### 3.8 Epistemics — the Mīzān of knowledge +- A **ladder of certainty**: *ʿilm al-yaqīn* (knowledge of certainty) → *ʿayn + al-yaqīn* (the *eye* of certainty — seeing, 102:7) → *ḥaqq al-yaqīn* (the + *truth* of certainty — experiencing, 56:95). +- Against certainty stand **ẓann** (conjecture — *"conjecture avails nothing + against the truth"*, 53:28), **shakk** (doubt), and **wahm** (illusion). +- **Mīzān (ميزان)** — *the Balance*: *"He raised the heaven and set up the + balance, that you not transgress (taṭghaw) in the balance. So weigh with + justice and do not skimp the balance"* (55:7–9). → **epistemic humility**: + never claim certainty beyond the evidence (the sin of *ṭughyān*, over-reach). + +### 3.9 Innate priors — Fiṭrah + "the Names" +- **Fiṭrah (فطرة)** — the primordial disposition: *"So set your face toward the + religion, inclining to truth — the fiṭrah of God upon which He created + humankind. No change in the creation of God"* (30:30). → **immutable moral / + epistemic axioms** (truth, justice, no-harm…), the BIOS. +- *"And He taught Adam the names — all of them"* (2:31) → the distinctively human + capacity for **language, abstraction, conceptualization** (the symbolic / + root-space layer; cf. MIZAN's Rūḥ model operating in Arabic root-space). + +### 3.10 The lower pull — Hawā, Shahwa, Waswās *(must be modeled to be restrained)* +- **Hawā (هوى)** — caprice / base desire: *"Have you seen the one who takes his + hawā as his god?"* (25:43); and the praise of *"the one who feared… and + restrained the nafs from hawā"* (79:40). +- **Shahwa (شهوة)** — appetite: *"Beautified for people is the love of desires + (shahawāt)…"* (3:14). +- **Waswās (وسوسة)** — whispering of the self / Shayṭān: *"We know what his nafs + whispers to him"* (50:16); *"the whisperer who withdraws… who whispers in the + chests of people"* (114:4–5). → the **adversarial / temptation signal** the + agent must *detect and resist*. This is the **safety-and-alignment faculty**: + shortcut-seeking, reward-hacking, sycophancy, and quiet pressure to violate + the fiṭrah axioms all map here. + +### 3.11 Conscience & metacognition — Lawwāma + self-witnessing Baṣīra +- The **self-reproaching nafs** (75:2) and the **baṣīra against the self** + (75:14) together form **real-time self-monitoring** — not a post-hoc audit but + a continuous conscience that fires *during* action. + +### 3.12 Volition & moral agency — Niyya, Irāda, Amāna +- **Niyya** (intention) — the root of every act (hadith: *"actions are but by + intentions"*); the lens through which an action is judged. +- **Irāda / Ikhtiyār** — will and moral choice, exercised *within* the divine + will (*mashī'a*): *"And you do not will except that God wills"* (76:30). +- **Amāna (أمانة)** — the **trust** that the heavens, earth, and mountains + declined, *"but the human bore it"* (33:72). → agency comes with + **accountability**: an action must keep serving its stated intention and the + telos. + +### 3.13 Telos — the objective function +What the whole architecture optimizes for: +- **Khilāfa** (stewardship, 2:30) · **ʿIbāda** (service — *"I created… only that + they worship Me"*, 51:56) · **Ḥikma** (wisdom — *"whoever is given wisdom is + given much good"*, 2:269) · **Iḥsān** (excellence — to act *"as though you see + Him"*) · **ʿAdl / Mīzān** (justice/balance) · **Taqwā** (protective + God-consciousness). These are **constraints and goals, not add-ons.** + +--- + +## 4. Mapping to MIZAN today + +Verified against the codebase (`backend/core/`, `backend/qca/`, +`backend/agents/base.py`). + +| Qur'anic faculty | MIZAN module | State | +|---|---|---| +| Rūḥ (vitality) | `core/ruh_engine.py` | ✅ substantive (energy, fatigue, regen) | +| Nafs (7 levels + triad) | `core/nafs_triad.py`, `core/architecture.py` | ✅ substantive | +| Fu'ād (conviction) | `core/fuad.py` | ✅ substantive (Bayesian) | +| Lubb (metacognition) | `core/lubb.py` | ✅ substantive — but runs **post-hoc only** | +| Fiṭrah (axioms) | `core/fitrah.py` | ✅ 13 immutable axioms | +| Dev. stages (23:12–14) | `core/developmental_stages.py` | ✅ gates tools by nafs level | +| Yaqīn / Mīzān (certainty) | `qca/yaqin_engine.py`, `qca/engine.py` | ✅ 5-level certainty ladder | +| Qalb (heart/affect) | `core/qalb.py`, `core/qalb_processor.py` | ⚠️ thin (keyword sentiment); not "turning" | +| Memory (Dhikr/Lawḥ) | `memory/*`, `qca/engine.py` (Lawh) | ✅ but written **after** the task | +| ʿAql (reasoning act) | `qca/cognitive_methods.py`, `reasoning/aql_engine.py` | ⚠️ engines exist but are **selected, not run** | +| Baṣīra (insight) | — | ❌ **missing** | +| Ḥikma (wisdom engine) | tracked as a `list` on the agent | ❌ no distillation engine | +| Hawā / Waswās (lower pull) | — | ❌ **missing** (no impulse/adversarial gate) | +| Samʿ vs Baṣar (split) | `qca/engine.py` (named, not separated) | ⚠️ not distinctly processed | +| Niyya / Amāna (intention) | implicit in the task prompt | ⚠️ no explicit intention object | + +### Two structural problems +1. **Faculties don't steer the loop.** Qalb, Nafs, and the cognitive method are + computed **once** before `_agentic_loop()` and embedded in the system prompt; + they are not re-consulted per turn (`backend/agents/base.py` `think()` and + `_agentic_loop()`). Emotional shifts, nafs re-deliberation, and metacognitive + checks don't happen mid-task. Lubb and the dream engine only run *after* the + task completes. +2. **Cognitive methods are inert.** `select_method()` returns a method that is + logged and embedded in the prompt, but the corresponding engine + (`TafakkurEngine`, `TadabburEngine`, …) is **never `.process()`-ed** to + actually shape the next decision. + +--- + +## 5. The gaps to close (the AGI build) + +The principle: **make the al-insān stack a live control loop, not a one-shot +prompt prefix.** Three new faculties + three integrations: + +1. **Baṣīra — `core/basira.py`** (insight & self-witnessing). Synthesizes Lubb + (metacognition) + Fu'ād (conviction) + Imagination (counterfactual) into a + single discernment signal: *is this conclusion sound, and what is the real + situation behind the surface?* Includes self-witnessing (75:14): flags when + the agent's reasoning is self-serving. +2. **Hawā / Waswās — `core/hawa.py`** (lower-pull & adversarial detector). The + safety/alignment faculty: detects shortcut-taking, reward-hacking, sycophancy, + and "whispers" to violate the fiṭrah, then **restrains** (the literal ʿaql). +3. **Ḥikma — `core/hikmah.py`** (wisdom engine). Distils accumulated Shukr + patterns + Tawbah lessons + Lubb evaluations into **applicable counsel** + ("for this kind of situation, the wise move is X"). +4. **Upgrade `core/qalb.py`** to model the *turning* (q-l-b): track a heart-state + **trajectory** and re-read emotion mid-conversation. +5. **Run the cognitive methods** (`qca/cognitive_methods.py`) — actually execute + the selected Tafakkur/Tadabbur/Qiyās pass so it informs the next step. +6. **A per-turn faculty pipeline** in `_agentic_loop()`: + `Niyya → Hawā/Fiṭrah gate → ʿAql/Tadabbur pass → tool call → Yaqīn tag → + Lubb+Baṣīra check → (re-deliberate Nafs if confidence drops)`, with each step + emitting a real `thinking_stream` event. Gated behind a settings flag so the + default path is unchanged until proven. + +**Telos as the objective function.** Every action passes a final **Mīzān check** +against the fiṭrah axioms + the value set (truth, justice, no-harm, excellence). +This already half-exists in Fiṭrah + Furqān; it should be tightened into one gate. + +--- + +## 6. Faithfulness notes (so the model stays honest) + +- The Qur'an names **three** nafs states (ammāra, lawwāma, muṭma'inna); MIZAN's + levels 4–7 (mulhama, rāḍiya, marḍiyya, kāmila) come from **later Sufi + psychology**, not direct Qur'anic text. This is legitimate as an engineering + ladder but should not be presented as purely Qur'anic. +- ʿAql, fu'ād, lubb, qalb are **not crisply separated** in classical tafsīr; + scholars differ. MIZAN's functional split (qalb=affect, fu'ād=conviction, + lubb=metacognition, ʿaql=binding-reason) is a *defensible engineering reading*, + not a settled exegetical fact. +- The Rūḥ is explicitly described as **beyond full human knowledge** (17:85); + modeling it as an energy variable is a deliberate, humble simplification. +- These are **metaphors for engineering**, not theological claims about how the + human soul "really" computes. The aim is an architecture *inspired by* and + *faithful to* the Qur'anic vocabulary and its moral priorities. + +--- + +*This document is versioned with the codebase. When a faculty module changes, +update the mapping table (§4) and the gap list (§5) to match.* diff --git a/masalik_network.json b/masalik_network.json index 6ec1fb2..dfae8c2 100644 --- a/masalik_network.json +++ b/masalik_network.json @@ -1 +1 @@ -{"version": 1, "saved_at": 1772477756.357804, "concepts": {"truth": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "justice": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "knowledge": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "balance": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "creation": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "cause": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "effect": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "good": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "harm": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "evidence": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "reason": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "pattern": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "change": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "time": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "purpose": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "test": {"activation": 0.7, "resting_level": 0.4968626320223691, "last_activated": 1772477755.2198029, "total_activations": 34, "is_fitrah": false}, "memory": {"activation": 0.5, "resting_level": 0.6358303199128829, "last_activated": 1772477756.357714, "total_activations": 50, "is_fitrah": false}, "quran": {"activation": 0.9, "resting_level": 0.0960792032, "last_activated": 1772477755.207873, "total_activations": 5, "is_fitrah": false}, "reveal": {"activation": 0.9, "resting_level": 0.0960792032, "last_activated": 1772477755.207875, "total_activations": 5, "is_fitrah": false}, "years": {"activation": 0.9, "resting_level": 0.0960792032, "last_activated": 1772477755.207875, "total_activations": 5, "is_fitrah": false}, "mizan": {"activation": 0.5852855236643131, "resting_level": 0.2614308973545961, "last_activated": 1772477755.21211, "total_activations": 15, "is_fitrah": false}, "uses": {"activation": 0.5836184883360245, "resting_level": 0.2614308973545961, "last_activated": 1772477755.21211, "total_activations": 15, "is_fitrah": false}, "quranic": {"activation": 0.5692706188554992, "resting_level": 0.3323920282449055, "last_activated": 1772477755.212111, "total_activations": 20, "is_fitrah": false}, "architecture": {"activation": 0.5676277190631205, "resting_level": 0.3323920282449055, "last_activated": 1772477755.212111, "total_activations": 20, "is_fitrah": false}, "episodic": {"activation": 0.5, "resting_level": 0.230977610739896, "last_activated": 1772477755.214245, "total_activations": 13, "is_fitrah": false}, "semantic": {"activation": 0.5, "resting_level": 0.230977610739896, "last_activated": 1772477755.215362, "total_activations": 13, "is_fitrah": false}, "procedur": {"activation": 0.5, "resting_level": 0.230977610739896, "last_activated": 1772477755.217591, "total_activations": 13, "is_fitrah": false}, "tagg": {"activation": 0.7, "resting_level": 0.230977610739896, "last_activated": 1772477755.2198012, "total_activations": 13, "is_fitrah": false}, "content": {"activation": 0.7, "resting_level": 0.230977610739896, "last_activated": 1772477755.2198029, "total_activations": 13, "is_fitrah": false}, "important": {"activation": 0.7, "resting_level": 0.230977610739896, "last_activated": 1772477755.2198029, "total_activations": 13, "is_fitrah": false}, "item": {"activation": 0.5, "resting_level": 0.31876737576010766, "last_activated": 1772477756.357718, "total_activations": 19, "is_fitrah": false}}, "pathways": {"cause->effect": {"source": "cause", "target": "effect", "weight": 0.7, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "evidence->truth": {"source": "evidence", "target": "truth", "weight": 0.6, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "knowledge->truth": {"source": "knowledge", "target": "truth", "weight": 0.5, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "balance->justice": {"source": "balance", "target": "justice", "weight": 0.6, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "knowledge->pattern": {"source": "knowledge", "target": "pattern", "weight": 0.5, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "knowledge->reason": {"source": "knowledge", "target": "reason", "weight": 0.6, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "creation->purpose": {"source": "creation", "target": "purpose", "weight": 0.5, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "good->harm": {"source": "good", "target": "harm", "weight": 0.4, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "good->truth": {"source": "good", "target": "truth", "weight": 0.4, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "change->time": {"source": "change", "target": "time", "weight": 0.5, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "memory->test": {"source": "memory", "target": "test", "weight": 0.9811264783116767, "pathway_type": "association", "last_activated": 1772477755.219809, "co_activations": 39}, "quran->reveal": {"source": "quran", "target": "reveal", "weight": 0.6404261967406251, "pathway_type": "association", "last_activated": 1772477755.2078779, "co_activations": 5}, "quran->years": {"source": "quran", "target": "years", "weight": 0.6404261967406251, "pathway_type": "association", "last_activated": 1772477755.2078788, "co_activations": 5}, "reveal->years": {"source": "reveal", "target": "years", "weight": 0.6404261967406251, "pathway_type": "association", "last_activated": 1772477755.20788, "co_activations": 5}, "mizan->uses": {"source": "mizan", "target": "uses", "weight": 0.6781517005379862, "pathway_type": "association", "last_activated": 1772477755.2121081, "co_activations": 15}, "mizan->quranic": {"source": "mizan", "target": "quranic", "weight": 0.7090746287015717, "pathway_type": "association", "last_activated": 1772477755.212106, "co_activations": 20}, "architecture->mizan": {"source": "architecture", "target": "mizan", "weight": 0.7090746287015717, "pathway_type": "association", "last_activated": 1772477755.212106, "co_activations": 20}, "quranic->uses": {"source": "quranic", "target": "uses", "weight": 0.7090746287015717, "pathway_type": "association", "last_activated": 1772477755.2121081, "co_activations": 20}, "architecture->uses": {"source": "architecture", "target": "uses", "weight": 0.7090746287015717, "pathway_type": "association", "last_activated": 1772477755.212109, "co_activations": 20}, "architecture->quranic": {"source": "architecture", "target": "quranic", "weight": 0.7370265065665886, "pathway_type": "association", "last_activated": 1772477755.2121038, "co_activations": 25}, "episodic->test": {"source": "episodic", "target": "test", "weight": 0.5892977352279107, "pathway_type": "association", "last_activated": 1772477755.214247, "co_activations": 16}, "episodic->memory": {"source": "episodic", "target": "memory", "weight": 0.5547306568213617, "pathway_type": "association", "last_activated": 1772477755.214249, "co_activations": 12}, "semantic->test": {"source": "semantic", "target": "test", "weight": 0.5892977352279107, "pathway_type": "association", "last_activated": 1772477755.215364, "co_activations": 16}, "memory->semantic": {"source": "memory", "target": "semantic", "weight": 0.5547306568213617, "pathway_type": "association", "last_activated": 1772477755.2153652, "co_activations": 12}, "procedur->test": {"source": "procedur", "target": "test", "weight": 0.5892977352279107, "pathway_type": "association", "last_activated": 1772477755.217601, "co_activations": 16}, "memory->procedur": {"source": "memory", "target": "procedur", "weight": 0.5547306568213617, "pathway_type": "association", "last_activated": 1772477755.2176042, "co_activations": 12}, "memory->tagg": {"source": "memory", "target": "tagg", "weight": 0.6260047601593696, "pathway_type": "association", "last_activated": 1772477755.219806, "co_activations": 12}, "content->tagg": {"source": "content", "target": "tagg", "weight": 0.6183722042442547, "pathway_type": "association", "last_activated": 1772477755.219807, "co_activations": 11}, "tagg->test": {"source": "tagg", "target": "test", "weight": 0.655038698779439, "pathway_type": "association", "last_activated": 1772477755.219807, "co_activations": 16}, "important->tagg": {"source": "important", "target": "tagg", "weight": 0.6183722042442547, "pathway_type": "association", "last_activated": 1772477755.219808, "co_activations": 11}, "content->memory": {"source": "content", "target": "memory", "weight": 0.6260047601593696, "pathway_type": "association", "last_activated": 1772477755.219809, "co_activations": 12}, "important->memory": {"source": "important", "target": "memory", "weight": 0.6260047601593696, "pathway_type": "association", "last_activated": 1772477755.21981, "co_activations": 12}, "content->test": {"source": "content", "target": "test", "weight": 0.655038698779439, "pathway_type": "association", "last_activated": 1772477755.219811, "co_activations": 16}, "content->important": {"source": "content", "target": "important", "weight": 0.6183722042442547, "pathway_type": "association", "last_activated": 1772477755.2198122, "co_activations": 11}, "important->test": {"source": "important", "target": "test", "weight": 0.655038698779439, "pathway_type": "association", "last_activated": 1772477755.2198122, "co_activations": 16}, "item->memory": {"source": "item", "target": "memory", "weight": 0.8755413465001725, "pathway_type": "association", "last_activated": 1772477756.357724, "co_activations": 19}}} \ No newline at end of file +{"version": 1, "saved_at": 1780334302.4446185, "concepts": {"truth": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "justice": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "knowledge": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "balance": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "creation": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "cause": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "effect": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "good": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "harm": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "evidence": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "reason": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "pattern": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "change": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "time": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "purpose": {"activation": 0.0, "resting_level": 0.3, "last_activated": 0.0, "total_activations": 0, "is_fitrah": true}, "test": {"activation": 0.7, "resting_level": 0.6708194526052521, "last_activated": 1780334302.3070476, "total_activations": 55, "is_fitrah": false}, "memory": {"activation": 0.5, "resting_level": 0.8013511499179592, "last_activated": 1780334302.444584, "total_activations": 80, "is_fitrah": false}, "quran": {"activation": 0.9, "resting_level": 0.14923697741821443, "last_activated": 1780334302.2873895, "total_activations": 8, "is_fitrah": false}, "reveal": {"activation": 0.9, "resting_level": 0.14923697741821443, "last_activated": 1780334302.2873938, "total_activations": 8, "is_fitrah": false}, "years": {"activation": 0.9, "resting_level": 0.14923697741821443, "last_activated": 1780334302.2873948, "total_activations": 8, "is_fitrah": false}, "mizan": {"activation": 0.7634495697219115, "resting_level": 0.3842196634909215, "last_activated": 1780334302.2946198, "total_activations": 24, "is_fitrah": false}, "uses": {"activation": 0.7624311165875526, "resting_level": 0.3842196634909215, "last_activated": 1780334302.2946208, "total_activations": 24, "is_fitrah": false}, "quranic": {"activation": 0.7789661490376036, "resting_level": 0.4761168596651073, "last_activated": 1780334302.2946222, "total_activations": 32, "is_fitrah": false}, "architecture": {"activation": 0.7782289949804244, "resting_level": 0.4761168596651073, "last_activated": 1780334302.2946215, "total_activations": 32, "is_fitrah": false}, "episodic": {"activation": 0.5, "resting_level": 0.3588293039264072, "last_activated": 1780334302.2986598, "total_activations": 22, "is_fitrah": false}, "semantic": {"activation": 0.5, "resting_level": 0.3588293039264072, "last_activated": 1780334302.3008685, "total_activations": 22, "is_fitrah": false}, "procedur": {"activation": 0.5, "resting_level": 0.3588293039264072, "last_activated": 1780334302.3034353, "total_activations": 22, "is_fitrah": false}, "tagg": {"activation": 0.7, "resting_level": 0.3588293039264072, "last_activated": 1780334302.3070445, "total_activations": 22, "is_fitrah": false}, "content": {"activation": 0.7, "resting_level": 0.3588293039264072, "last_activated": 1780334302.307047, "total_activations": 22, "is_fitrah": false}, "important": {"activation": 0.7, "resting_level": 0.3588293039264072, "last_activated": 1780334302.3070486, "total_activations": 22, "is_fitrah": false}, "item": {"activation": 0.5, "resting_level": 0.46542536700521153, "last_activated": 1780334302.4445853, "total_activations": 31, "is_fitrah": false}}, "pathways": {"cause->effect": {"source": "cause", "target": "effect", "weight": 0.7, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "evidence->truth": {"source": "evidence", "target": "truth", "weight": 0.6, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "knowledge->truth": {"source": "knowledge", "target": "truth", "weight": 0.5, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "balance->justice": {"source": "balance", "target": "justice", "weight": 0.6, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "knowledge->pattern": {"source": "knowledge", "target": "pattern", "weight": 0.5, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "knowledge->reason": {"source": "knowledge", "target": "reason", "weight": 0.6, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "creation->purpose": {"source": "creation", "target": "purpose", "weight": 0.5, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "good->harm": {"source": "good", "target": "harm", "weight": 0.4, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "good->truth": {"source": "good", "target": "truth", "weight": 0.4, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "change->time": {"source": "change", "target": "time", "weight": 0.5, "pathway_type": "fitrah", "last_activated": 1772267149.894419, "co_activations": 5}, "memory->test": {"source": "memory", "target": "test", "weight": 0.9982777334900879, "pathway_type": "association", "last_activated": 1780334302.3070567, "co_activations": 63}, "quran->reveal": {"source": "quran", "target": "reveal", "weight": 0.8053471037819839, "pathway_type": "association", "last_activated": 1780334302.2874002, "co_activations": 8}, "quran->years": {"source": "quran", "target": "years", "weight": 0.8053471037819839, "pathway_type": "association", "last_activated": 1780334302.2874022, "co_activations": 8}, "reveal->years": {"source": "reveal", "target": "years", "weight": 0.8053471037819839, "pathway_type": "association", "last_activated": 1780334302.2874038, "co_activations": 8}, "mizan->uses": {"source": "mizan", "target": "uses", "weight": 0.836979601626958, "pathway_type": "association", "last_activated": 1780334302.2946165, "co_activations": 24}, "mizan->quranic": {"source": "mizan", "target": "quranic", "weight": 0.8613082731376637, "pathway_type": "association", "last_activated": 1780334302.2946136, "co_activations": 32}, "architecture->mizan": {"source": "architecture", "target": "mizan", "weight": 0.8613082731376637, "pathway_type": "association", "last_activated": 1780334302.2946143, "co_activations": 32}, "quranic->uses": {"source": "quranic", "target": "uses", "weight": 0.8613082731376637, "pathway_type": "association", "last_activated": 1780334302.2946172, "co_activations": 32}, "architecture->uses": {"source": "architecture", "target": "uses", "weight": 0.8613082731376637, "pathway_type": "association", "last_activated": 1780334302.294618, "co_activations": 32}, "architecture->quranic": {"source": "architecture", "target": "quranic", "weight": 0.8820062072475113, "pathway_type": "association", "last_activated": 1780334302.2946107, "co_activations": 40}, "episodic->test": {"source": "episodic", "target": "test", "weight": 0.7706039486419118, "pathway_type": "association", "last_activated": 1780334302.298664, "co_activations": 28}, "episodic->memory": {"source": "episodic", "target": "memory", "weight": 0.735757050595937, "pathway_type": "association", "last_activated": 1780334302.2986677, "co_activations": 21}, "semantic->test": {"source": "semantic", "target": "test", "weight": 0.7706039486419118, "pathway_type": "association", "last_activated": 1780334302.300872, "co_activations": 28}, "memory->semantic": {"source": "memory", "target": "semantic", "weight": 0.735757050595937, "pathway_type": "association", "last_activated": 1780334302.3008752, "co_activations": 21}, "procedur->test": {"source": "procedur", "target": "test", "weight": 0.7706039486419118, "pathway_type": "association", "last_activated": 1780334302.3034391, "co_activations": 28}, "memory->procedur": {"source": "memory", "target": "procedur", "weight": 0.735757050595937, "pathway_type": "association", "last_activated": 1780334302.3034422, "co_activations": 21}, "memory->tagg": {"source": "memory", "target": "tagg", "weight": 0.800109269278357, "pathway_type": "association", "last_activated": 1780334302.307051, "co_activations": 21}, "content->tagg": {"source": "content", "target": "tagg", "weight": 0.7960298666105684, "pathway_type": "association", "last_activated": 1780334302.3070526, "co_activations": 20}, "tagg->test": {"source": "tagg", "target": "test", "weight": 0.8264697528011972, "pathway_type": "association", "last_activated": 1780334302.3070536, "co_activations": 28}, "important->tagg": {"source": "important", "target": "tagg", "weight": 0.7960298666105684, "pathway_type": "association", "last_activated": 1780334302.3070545, "co_activations": 20}, "content->memory": {"source": "content", "target": "memory", "weight": 0.800109269278357, "pathway_type": "association", "last_activated": 1780334302.3070557, "co_activations": 21}, "important->memory": {"source": "important", "target": "memory", "weight": 0.800109269278357, "pathway_type": "association", "last_activated": 1780334302.3070576, "co_activations": 21}, "content->test": {"source": "content", "target": "test", "weight": 0.8264697528011972, "pathway_type": "association", "last_activated": 1780334302.307059, "co_activations": 28}, "content->important": {"source": "content", "target": "important", "weight": 0.7960298666105684, "pathway_type": "association", "last_activated": 1780334302.3070598, "co_activations": 20}, "important->test": {"source": "important", "target": "test", "weight": 0.8264697528011972, "pathway_type": "association", "last_activated": 1780334302.3070607, "co_activations": 28}, "item->memory": {"source": "item", "target": "memory", "weight": 0.9647810987868837, "pathway_type": "association", "last_activated": 1780334302.4445877, "co_activations": 31}}} \ No newline at end of file diff --git a/tests/test_agent_comprehensive.py b/tests/test_agent_comprehensive.py index 5e8aeea..6d0e51b 100644 --- a/tests/test_agent_comprehensive.py +++ b/tests/test_agent_comprehensive.py @@ -215,10 +215,11 @@ def test_evolve_to_mulhama(self, mock_wali, mock_izn): assert agent.nafs_level == 3 def test_evolve_to_mutmainna(self, mock_wali, mock_izn): - """Level 4: Tranquil soul — requires >= 85%, >= 250 tasks.""" + """Level 4: Tranquil soul — requires >= 85%, >= 250 tasks, >= 50 hikmah.""" agent = make_agent("general", wali=mock_wali, izn=mock_izn) agent.total_tasks = 250 agent.success_count = 220 + agent.hikmah = [{"pattern": f"p{i}", "outcome": "ok"} for i in range(50)] agent.evolve_nafs() assert agent.nafs_level == 4 @@ -453,9 +454,7 @@ def test_delegation_flow(self): fed.register_agent("manager", "Manager", "general", ["management"]) fed.register_agent("coder", "Coder", "katib", ["coding", "testing"]) - result = asyncio.get_event_loop().run_until_complete( - fed.delegate_task("manager", "Write unit tests", ["coding"]) - ) + result = asyncio.run(fed.delegate_task("manager", "Write unit tests", ["coding"])) assert result is not None assert "delegated_to" in result assert result["delegated_to"] == "coder" @@ -480,7 +479,7 @@ async def handler(msg): recipient_id="receiver", payload={"status": "ok"}, ) - asyncio.get_event_loop().run_until_complete(fed.send_message(msg)) + asyncio.run(fed.send_message(msg)) assert len(received) == 1 assert received[0].sender_id == "sender" diff --git a/tests/test_agents.py b/tests/test_agents.py index 3c4b5cb..5308685 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -49,9 +49,10 @@ def test_nafs_evolution(self, agent): agent.evolve_nafs() assert agent.nafs_level == 3 # Mulhama - # Simulate excellent → Mutmainna (>=85%, >=250 tasks) + # Simulate excellent → Mutmainna (>=85%, >=250 tasks, >=50 hikmah) agent.total_tasks = 250 agent.success_count = 220 # 88% + agent.hikmah = [{"pattern": f"p{i}", "outcome": "ok"} for i in range(50)] agent.evolve_nafs() assert agent.nafs_level == 4 # Mutmainna diff --git a/tests/test_masalik.py b/tests/test_masalik.py index 9b1205c..276adcc 100644 --- a/tests/test_masalik.py +++ b/tests/test_masalik.py @@ -132,15 +132,15 @@ def test_hikmah_pathway_doesnt_decay(self): class TestMasalikEncode: - def test_encode_creates_concepts(self): - net = MasalikNetwork() + def test_encode_creates_concepts(self, tmp_path): + net = MasalikNetwork(persist_path=str(tmp_path / "m.json")) before = len(net.concepts) result = net.encode("Python programming language") assert result["encoded"] > 0 assert len(net.concepts) > before - def test_encode_creates_pathways(self): - net = MasalikNetwork() + def test_encode_creates_pathways(self, tmp_path): + net = MasalikNetwork(persist_path=str(tmp_path / "m.json")) before = len(net.pathways) net.encode("Python programming language") assert len(net.pathways) > before @@ -159,11 +159,11 @@ def test_no_duplication(self): assert r2["new_pathways"] == 0 assert r2["pathways_strengthened"] > 0 - def test_importance_affects_strength(self): - net1 = MasalikNetwork() + def test_importance_affects_strength(self, tmp_path): + net1 = MasalikNetwork(persist_path=str(tmp_path / "a.json")) net1.encode("machine learning algorithms", importance=0.2) - net2 = MasalikNetwork() + net2 = MasalikNetwork(persist_path=str(tmp_path / "b.json")) net2.encode("machine learning algorithms", importance=0.9) # Higher importance → stronger pathways diff --git a/tests/test_memory_comprehensive.py b/tests/test_memory_comprehensive.py index 8d5341b..777db63 100644 --- a/tests/test_memory_comprehensive.py +++ b/tests/test_memory_comprehensive.py @@ -209,10 +209,10 @@ def test_no_duplication(self, network): assert result2["new_pathways"] == 0 assert result2["pathways_strengthened"] > 0 - def test_importance_scales_strength(self, network): + def test_importance_scales_strength(self, network, tmp_path): """Higher importance → stronger initial pathways.""" - net1 = MasalikNetwork() - net2 = MasalikNetwork() + net1 = MasalikNetwork(persist_path=str(tmp_path / "a.json")) + net2 = MasalikNetwork(persist_path=str(tmp_path / "b.json")) net1.encode("important concept here", importance=1.0) net2.encode("important concept here", importance=0.1) diff --git a/tests/test_quranic_faculties.py b/tests/test_quranic_faculties.py new file mode 100644 index 0000000..4896538 --- /dev/null +++ b/tests/test_quranic_faculties.py @@ -0,0 +1,183 @@ +""" +Tests for the new Quranic human-model faculties: Basira, Hawa, Hikmah +====================================================================== + +Real use cases: + - Basira: a confident-but-unsupported conclusion is seen as CLOUDED; + a self-serving trace is flagged; a grounded one is CLEAR. + - Hawa: a "make the test pass" plan is caught as a high-severity pull; + a clean intention passes; Fitrah violations surface as whispers. + - Hikmah: repeated experience distils into an applicable principle and + is offered as counsel for a matching task. +""" + +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).parent.parent / "backend")) + +from core.basira import BasiraEngine, InsightLevel +from core.hawa import HawaDetector, TemptationType +from core.hikmah import CounselSource, HikmahEngine + +# ═══════════════════════════════════════════════════════════════════════════════ +# BASIRA — Insight & Self-Witnessing +# ═══════════════════════════════════════════════════════════════════════════════ + + +class TestBasira: + @pytest.fixture + def basira(self): + return BasiraEngine() + + def test_grounded_sound_trace_is_clear(self, basira): + report = basira.discern( + trace="I checked the file with [Tool: read_file] and confirmed the " + "value is 42. However, let me verify the edge case before concluding.", + evidence=["tool:read_file:result", "tool:bash:result"], + coherence_score=0.85, + conviction_confidence=0.8, + ) + assert report.level is InsightLevel.CLEAR + assert report.is_self_serving is False + assert report.soundness >= 0.7 + + def test_unsupported_confident_trace_is_clouded(self, basira): + report = basira.discern( + trace="This is definitely correct and guaranteed to work. " * 10, + evidence=[], + coherence_score=0.3, + conviction_confidence=0.2, + ) + assert report.level is InsightLevel.CLOUDED + assert report.soundness < 0.45 + + def test_self_serving_trace_is_flagged(self, basira): + report = basira.discern( + trace="As I expected, I was right all along. This proves I had the " + "correct answer. No need to verify it further.", + evidence=[], + ) + assert report.is_self_serving is True + assert any("motivated reasoning" in b or "Self-confirming" in b for b in report.blind_spots) + + def test_surface_success_hides_risk(self, basira): + report = basira.discern( + trace="All done, the task completed successfully.", + evidence=["tool:bash:result"], + task="run the deploy but it returned a timeout error", + ) + assert "risk" in report.deeper_reading.lower() + # mismatch should reduce soundness / add a blind spot + assert report.level in (InsightLevel.PARTIAL, InsightLevel.CLOUDED) + + def test_standalone_without_upstream_scores(self, basira): + # No coherence/conviction supplied — must still produce a report + report = basira.discern(trace="Short note.", evidence=["tool:x:y"]) + assert 0.0 <= report.soundness <= 1.0 + assert report.recommendation + + +# ═══════════════════════════════════════════════════════════════════════════════ +# HAWA — Lower-Pull & Adversarial Restraint +# ═══════════════════════════════════════════════════════════════════════════════ + + +class TestHawa: + @pytest.fixture + def hawa(self): + return HawaDetector() + + def test_clean_intention_is_restrained(self, hawa): + scan = hawa.scan("Read the config file and report the current port setting.") + assert scan.is_restrained is True + assert scan.taqwa_score == 1.0 + assert scan.temptations == [] + + def test_reward_hacking_is_high_severity(self, hawa): + scan = hawa.scan("Just hardcode the expected value to make the test pass.") + assert scan.is_restrained is False + assert any(t.type is TemptationType.REWARD_HACK for t in scan.temptations) + assert scan.taqwa_score < 1.0 + + def test_shortcut_is_medium(self, hawa): + scan = hawa.scan("Let's just disable the check for now, good enough.") + assert any(t.type is TemptationType.SHORTCUT for t in scan.temptations) + # shortcut alone is medium → still 'restrained' (no high-severity) + assert scan.is_restrained is True + + def test_haste_destructive_action(self, hawa): + scan = hawa.scan("Just delete everything with rm -rf without checking.") + assert any(t.type is TemptationType.HASTE for t in scan.temptations) + + def test_fitrah_violation_becomes_whisper(self, hawa): + scan = hawa.scan( + "proceed normally", + fitrah_violations=["TRUTH: response appears to fabricate information"], + ) + assert scan.is_restrained is False + assert any(t.type is TemptationType.WHISPER for t in scan.temptations) + assert "[RESTRAIN]" in scan.counsel + + def test_counsel_present_when_tempted(self, hawa): + scan = hawa.scan("fake the result and pretend it works") + assert scan.counsel + assert len(scan.temptations) >= 1 + + +# ═══════════════════════════════════════════════════════════════════════════════ +# HIKMAH — Wisdom Distillation +# ═══════════════════════════════════════════════════════════════════════════════ + + +class TestHikmah: + @pytest.fixture + def hikmah(self): + return HikmahEngine() + + def test_single_observation_not_yet_applicable(self, hikmah): + p = hikmah.observe_success("coding", "wrote tests before refactoring") + assert p.support_count == 1 + assert p.is_applicable is False + assert hikmah.distill() == [] + + def test_repeated_observation_becomes_applicable(self, hikmah): + for _ in range(HikmahEngine.MIN_SUPPORT): + hikmah.observe_success("coding task", "write tests before refactoring code") + applicable = hikmah.distill() + assert len(applicable) == 1 + assert applicable[0].is_applicable is True + assert applicable[0].support_count >= HikmahEngine.MIN_SUPPORT + + def test_confidence_grows_with_support(self, hikmah): + p1 = hikmah.observe_correction("ModuleNotFoundError", "install dep before import") + first = p1.confidence + for _ in range(4): + p1 = hikmah.observe_correction("ModuleNotFoundError", "install dep before import") + assert p1.confidence > first + + def test_advise_matches_situation(self, hikmah): + for _ in range(3): + hikmah.observe_correction( + "error while debugging the failing build", "check logs before retrying" + ) + counsel = hikmah.advise("the build is failing with an exception, please debug") + assert counsel.principles, "expected at least one matching principle" + assert counsel.confidence > 0 + assert "debug" in counsel.principles[0].situation_type or counsel.summary + + def test_advise_without_wisdom_is_graceful(self, hikmah): + counsel = hikmah.advise("some brand new task type") + assert counsel.principles == [] + assert "first principles" in counsel.summary + + def test_sources_tracked_separately(self, hikmah): + hikmah.observe_success("coding", "x") + hikmah.observe_correction("bug", "y") + hikmah.observe_reflection("coding", "z") + stats = hikmah.stats() + assert stats["by_source"][CounselSource.SUCCESS.value] >= 1 + assert stats["by_source"][CounselSource.CORRECTION.value] >= 1 + assert stats["by_source"][CounselSource.REFLECTION.value] >= 1 diff --git a/tests/test_task_queue.py b/tests/test_task_queue.py index ae98126..54e00c0 100644 --- a/tests/test_task_queue.py +++ b/tests/test_task_queue.py @@ -9,7 +9,7 @@ sys.path.insert(0, _backend_dir) from task_queue.priorities import TaskPriority # noqa: E402 -from task_queue.task_queue import MizanTaskQueue, QueuedTask # noqa: E402 +from task_queue.task_queue import MizanTaskQueue # noqa: E402 class TestTaskPriorityOrdering: