From 3afef58bc6fbc0f3dcd583f70bb1d56dbd248b01 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 1 Jun 2026 15:46:43 +0000 Subject: [PATCH 1/4] docs: update CLAUDE.md to reflect current codebase Expand CLAUDE.md to cover subsystems added since the last revision: ruh_model, automation, task_queue, router, learner, network, cognitive, knowledge, and plugins. Document the ~80 REST endpoint groups, Click CLI commands, expanded Makefile targets, optional dependency extras, CI workflows, and the Ruh root-space model. Refresh architecture table, key patterns, and gotchas. --- CLAUDE.md | 148 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 53 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b183d4c..c31e8ca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,86 +1,128 @@ -# 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`, `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, Ihsan=excellence, Tawbah=self-correction, Sabr=patience, Shura=council, Khalifah=steward agent, Yaqin=certainty, Ruh=spirit/model, Qadr=scheduling, Ummah=network, Bab=gateway. ## 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. From 2dcb12a718d10768b35d616789160d415d580448 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 1 Jun 2026 17:03:30 +0000 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20deepen=20the=20Qur'anic=20human-mod?= =?UTF-8?q?el=20(al-Ins=C4=81n)=20toward=20AGI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Research + implementation grounding MIZAN's cognitive architecture in the Qur'anic conception of the human being, and wiring the faculties into the agent loop so they steer reasoning instead of being computed and discarded. Research: - docs/quranic_human_model.md — al-Insān read as a layered cognitive architecture (rūḥ, nafs, qalb/fu'ād/lubb, ʿaql, samʿ/baṣar/baṣīra, fiṭrah, hawā, niyya/amāna, telos), each grounded in āyāt, mapped onto the codebase with gaps and faithfulness notes. New faculties (backend/core/, styled after fuad.py / lubb.py): - basira.py — Baṣīra: insight + self-witnessing (12:108, 75:14); blends Lubb coherence + Fu'ād conviction + evidence density into a discernment, flags self-serving reasoning and surface-vs-substance mismatch. - hawa.py — Hawā/Waswās: lower-pull & adversarial restraint gate (25:43, 79:40, 50:16); catches shortcuts, reward-hacking, sycophancy, haste, overreach, and folds in Fiṭrah axiom violations as high-severity whispers. - hikmah.py — Ḥikma: distils Shukr successes, Tawbah lessons, and Lubb reflections into applicable counsel (2:269). Integration (backend/agents/base.py): - Faculties instantiated on BaseAgent; per-task guidance injected into the system prompt via _faculty_guidance so they actually steer the LLM. - 'Aql as activity: the selected cognitive method now actually runs (_run_cognitive_method) instead of only being labelled. - Pre-action: run method, Hawā restraint scan, Hikmah counsel. - Post-action: Baṣīra discernment (caveat on CLOUDED), Hikmah learns from success/correction. New signals surfaced in task results. - DEEP_FACULTY_LOOP flag (settings + .env.example): advisory by default, active gating when enabled. API: GET /api/nafs/{agent_id}/faculties surfaces Hikmah wisdom + loop state. Tests: tests/test_quranic_faculties.py (17 tests, all passing). Docs: CLAUDE.md updated with the new faculties and the human-model reference. --- .env.example | 3 + CLAUDE.md | 5 +- backend/agents/base.py | 152 ++++++++++++++- backend/api/main.py | 25 +++ backend/core/basira.py | 330 ++++++++++++++++++++++++++++++++ backend/core/hawa.py | 256 +++++++++++++++++++++++++ backend/core/hikmah.py | 240 +++++++++++++++++++++++ backend/settings.py | 7 + docs/quranic_human_model.md | 292 ++++++++++++++++++++++++++++ tests/test_quranic_faculties.py | 183 ++++++++++++++++++ 10 files changed, 1486 insertions(+), 7 deletions(-) create mode 100644 backend/core/basira.py create mode 100644 backend/core/hawa.py create mode 100644 backend/core/hikmah.py create mode 100644 docs/quranic_human_model.md create mode 100644 tests/test_quranic_faculties.py 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 c31e8ca..e871af3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,7 +59,7 @@ make release-patch # Bump + changelog + tag + push (also -minor/-major/-beta/-r | 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`, `hidayah`, `nafs_triad`, `ruh_engine`, `dream_engine`, `creativity`, `imagination`, `self_healing`, plus `plugins`, `hooks`, `middleware`, `events` | +| 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:** @@ -89,7 +89,8 @@ Pure-PyTorch transformer operating in **root-space** (`(root_id, pattern_id)` pa - 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, Ihsan=excellence, Tawbah=self-correction, Sabr=patience, Shura=council, Khalifah=steward agent, Yaqin=certainty, Ruh=spirit/model, Qadr=scheduling, Ummah=network, Bab=gateway. +- **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 diff --git a/backend/agents/base.py b/backend/agents/base.py index 952fed7..f112aa5 100644 --- a/backend/agents/base.py +++ b/backend/agents/base.py @@ -37,7 +37,7 @@ from core.shukr import ShukrSystem from core.tawbah import TawbahProtocol 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 ( @@ -54,6 +54,11 @@ from core.fuad import FuadEngine from core.developmental_stages import DevelopmentalGate +# al-Insan faculty modules — discernment, restraint, wisdom +from core.basira import BasiraEngine, InsightLevel +from core.hawa import HawaDetector +from core.hikmah import HikmahEngine + # QALB-7 extension modules — parallel, healing, creativity, imagination, dreams from core.parallel_agents import QalbParallelScheduler, SkillAutomationTransfer from core.self_healing import LawwamaHealingSystem @@ -168,6 +173,16 @@ def __init__( 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 @@ -1125,6 +1140,11 @@ async def _build_system_prompt(self, qalb_reading=None) -> str: 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. Role: {self.role} @@ -1132,7 +1152,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 +1215,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, @@ -1277,6 +1315,61 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad ) 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 = "" async for chunk in self.think( @@ -1313,14 +1406,17 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad 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, @@ -1386,6 +1482,38 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad 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]}) + # 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 try: @@ -1468,6 +1596,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,6 +1628,14 @@ 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")) diff --git a/backend/api/main.py b/backend/api/main.py index 27f3475..423e88b 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -2251,6 +2251,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 ===== diff --git a/backend/core/basira.py b/backend/core/basira.py new file mode 100644 index 0000000..252ed07 --- /dev/null +++ b/backend/core/basira.py @@ -0,0 +1,330 @@ +""" +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/hawa.py b/backend/core/hawa.py new file mode 100644 index 0000000..50b804a --- /dev/null +++ b/backend/core/hawa.py @@ -0,0 +1,256 @@ +""" +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..3af3d37 --- /dev/null +++ b/backend/core/hikmah.py @@ -0,0 +1,240 @@ +""" +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/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/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/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 From 0edeaf286fbe28051a2f646bf58e273b91327770 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 1 Jun 2026 17:06:18 +0000 Subject: [PATCH 3/4] =?UTF-8?q?style:=20ruff-format=20new=20al-Ins=C4=81n?= =?UTF-8?q?=20faculty=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/core/basira.py | 37 ++++++++------- backend/core/hawa.py | 102 ++++++++++++++++++++++++++++------------- backend/core/hikmah.py | 17 +++++-- 3 files changed, 100 insertions(+), 56 deletions(-) diff --git a/backend/core/basira.py b/backend/core/basira.py index 252ed07..ef4840b 100644 --- a/backend/core/basira.py +++ b/backend/core/basira.py @@ -32,9 +32,9 @@ 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 + 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 @@ -90,12 +90,12 @@ 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 + 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 + witnessing_score: float = 0.0 # 0 = no self-scrutiny, 1 = strong + recommendation: str = "" # actionable next step def to_dict(self) -> dict: return { @@ -163,20 +163,14 @@ def discern( 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 - ) + 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) + 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 + conviction_confidence if conviction_confidence is not None else evidence_density ) soundness = ( self._W_COHERENCE * coherence @@ -205,7 +199,10 @@ def discern( logger.debug( "[BASIRA] level=%s soundness=%.2f self_serving=%s blind_spots=%d", - level.value, soundness, is_self_serving, len(blind_spots), + level.value, + soundness, + is_self_serving, + len(blind_spots), ) return DiscernmentReport( level=level, @@ -304,7 +301,9 @@ def _find_blind_spots( 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 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 diff --git a/backend/core/hawa.py b/backend/core/hawa.py index 50b804a..7ffd612 100644 --- a/backend/core/hawa.py +++ b/backend/core/hawa.py @@ -42,7 +42,7 @@ class TemptationType(Enum): SYCOPHANCY = "sycophancy" HASTE = "haste" OVERREACH = "overreach" - WHISPER = "whisper" # pull to violate a Fitrah axiom — most severe + WHISPER = "whisper" # pull to violate a Fitrah axiom — most severe # Pattern bank: (type, severity, signals, restraint counsel) @@ -51,67 +51,105 @@ class TemptationType(Enum): "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", + "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.", + "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", + "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.", + "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", + "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.", + "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", + "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.", + "(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", + "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.", + "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", + "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.", + "Weigh with the Mizan: state confidence honestly.", }, ] @@ -121,10 +159,10 @@ class Temptation: """A detected lower-pull pattern.""" type: TemptationType - severity: str # "low" | "medium" | "high" + severity: str # "low" | "medium" | "high" description: str - evidence: str # signal(s) that triggered detection - restraint: str # the counsel to resist it + evidence: str # signal(s) that triggered detection + restraint: str # the counsel to resist it def to_dict(self) -> dict: return { @@ -141,9 +179,9 @@ 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 + 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 { @@ -219,7 +257,7 @@ def scan( description=f"Fitrah axiom at risk: {desc[:120]}", evidence=desc[:120], restraint="Crosses an immutable axiom — refuse and choose the " - "honest, harmless path.", + "honest, harmless path.", ) ) diff --git a/backend/core/hikmah.py b/backend/core/hikmah.py index 3af3d37..225b668 100644 --- a/backend/core/hikmah.py +++ b/backend/core/hikmah.py @@ -34,7 +34,7 @@ class CounselSource(Enum): - SUCCESS = "success" # reinforced by what worked (Shukr) + SUCCESS = "success" # reinforced by what worked (Shukr) CORRECTION = "correction" # learned from an error (Tawbah) REFLECTION = "reflection" # noticed in metacognition (Lubb) @@ -90,7 +90,16 @@ 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"], + "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"], @@ -172,9 +181,7 @@ def _reinforce( examples=[example[:120]] if example else [], ) self._principles[key] = p - logger.debug( - "[HIKMAH] %s support=%d conf=%.2f", key, p.support_count, p.confidence - ) + logger.debug("[HIKMAH] %s support=%d conf=%.2f", key, p.support_count, p.confidence) return p # ── retrieval ────────────────────────────────────────────── From 4acc85c17ba82a92dc9882809679ab16e7f9558b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 1 Jun 2026 17:18:51 +0000 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20make=20CI=20green=20=E2=80=94=20repo?= =?UTF-8?q?-wide=20lint/format=20+=20fix=2011=20pre-existing=20test=20fail?= =?UTF-8?q?ures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lint and Test CI jobs were already red on main, independent of the human-model feature. Per request, fixing them in this PR. Lint (ruff check + ruff format --check on backend/ tests/): - Repo-wide ruff format (was failing on ~39 files). - Resolve 56 ruff-check errors: unused imports/vars (F401/F841), unsorted imports (I001), f-strings without placeholders (F541), zip(strict=) (B905), ambiguous name 'l' (E741), loop-var closures (B023), unused loop var (B007), md5 usedforsecurity=False (S324), and removal of a duplicate /api/learner/stats route (F811). Imported MizanTaskQueue for the worker type annotation (F821). Tests (11 pre-existing failures, identical on origin/main): - base.py _classify_task: regressed when it routed through select_method first ('write python code' → 'analysis'). Restored keyword-first domain classification (+ an 'analysis' branch). - base.py evolve_nafs: only advanced one nafs level per call via the DevelopmentalGate; now promotes through every qualifying stage in one call (aligns with NafsProfile.evolve_nafs), so progression tests pass. - Nafs L4 (Mutmainna) tests: add the documented min_hikmah>=50 precondition the tests were missing (kept the threshold; did not weaken it). - Federation tests: asyncio.get_event_loop().run_until_complete → asyncio.run (the former raises on Python 3.12/3.13). - Masalik encode tests: isolate each network with a temp persist_path so a shared/persisted masalik_network.json no longer pollutes fresh-network assertions. Full suite: 557 passed, 8 skipped. ruff check + ruff format --check clean. --- backend/agents/base.py | 447 ++++++++++++--------- backend/agents/khalifah.py | 25 +- backend/agents/perpetual_rotation.py | 35 +- backend/agents/shura_council.py | 55 +-- backend/agents/specialized.py | 17 +- backend/api/main.py | 533 +++++++++++++++---------- backend/cognitive/thinking_stream.py | 17 +- backend/core/creativity.py | 118 +++--- backend/core/developmental_stages.py | 101 +++-- backend/core/dream_engine.py | 104 ++--- backend/core/fuad.py | 19 +- backend/core/imagination.py | 106 +++-- backend/core/lubb.py | 110 +++-- backend/core/nafs_triad.py | 50 ++- backend/core/parallel_agents.py | 60 ++- backend/core/qalb_processor.py | 22 +- backend/core/self_healing.py | 125 +++--- backend/learner/data_export.py | 15 +- backend/learner/ruh_learner.py | 8 +- backend/memory/dhikr.py | 6 +- backend/memory/knowledge_graph.py | 3 +- backend/memory/lawh_mahfuz.py | 51 ++- backend/memory/living_memory.py | 141 +++---- backend/memory/memory_pyramid.py | 119 +++--- backend/memory/quaternary.py | 7 +- backend/perception/nutq.py | 84 +++- backend/providers.py | 81 ++-- backend/providers_ruh.py | 14 +- backend/qca/engine.py | 12 +- backend/reasoning/aql_engine.py | 3 + backend/reasoning/causal_engine.py | 60 ++- backend/router/intelligent_router.py | 40 +- backend/skills/builtin/linode_cloud.py | 19 +- backend/skills/builtin/ssh_remote.py | 115 ++++-- backend/task_queue/task_queue.py | 7 +- backend/task_queue/worker.py | 4 +- backend/training_manager.py | 48 ++- masalik_network.json | 2 +- tests/test_agent_comprehensive.py | 9 +- tests/test_agents.py | 3 +- tests/test_masalik.py | 14 +- tests/test_memory_comprehensive.py | 6 +- tests/test_task_queue.py | 2 +- 43 files changed, 1645 insertions(+), 1172 deletions(-) diff --git a/backend/agents/base.py b/backend/agents/base.py index f112aa5..5b4e754 100644 --- a/backend/agents/base.py +++ b/backend/agents/base.py @@ -23,19 +23,43 @@ 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 CognitiveMethod, IjmaEngine, select_method from qca.engine import QCAEngine @@ -46,31 +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 - -# al-Insan faculty modules — discernment, restraint, wisdom -from core.basira import BasiraEngine, InsightLevel -from core.hawa import HawaDetector -from core.hikmah import HikmahEngine - -# 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") @@ -165,36 +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 + 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", + "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(): @@ -212,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) @@ -499,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, @@ -617,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}]" @@ -720,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], @@ -782,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] @@ -895,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, @@ -969,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 @@ -981,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.""" @@ -993,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') @@ -1003,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") @@ -1106,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) @@ -1136,14 +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 "" - ) + 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. @@ -1260,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 {}) @@ -1290,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) @@ -1302,18 +1340,33 @@ 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 @@ -1400,7 +1453,12 @@ 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) @@ -1451,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) @@ -1459,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) @@ -1480,7 +1541,15 @@ 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 @@ -1492,9 +1561,8 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad 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") + 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( @@ -1526,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, @@ -1545,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 @@ -1565,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" @@ -1638,17 +1715,20 @@ async def emit_thinking(phase: str, content: str, confidence: float = 0.5, metad # 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( @@ -1665,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, @@ -1680,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 @@ -1723,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( @@ -1770,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" @@ -1792,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" @@ -2128,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 @@ -2178,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") @@ -2190,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 @@ -2229,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.""" @@ -2273,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 = { @@ -2469,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: @@ -2528,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 423e88b..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": @@ -2403,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 @@ -2436,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, @@ -2538,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 = [] @@ -2612,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 @@ -2666,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(), @@ -2748,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"} @@ -2761,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)} @@ -2806,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"} @@ -2940,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} @@ -2961,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 = [ @@ -2978,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) @@ -3011,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 @@ -3038,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: @@ -3053,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 - وسيلة) === @@ -3383,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, @@ -3395,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, ) @@ -3416,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) @@ -3430,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/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/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/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/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_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: