> ## Documentation Index
> Fetch the complete documentation index at: https://docs.neuralcleave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> Release history and what changed in each version of NeuralCleave.

## Unreleased

Everything below has merged to `main` since v2.1.5 but hasn't shipped under a bumped version number yet — per project policy, `__version__` only bumps at a full OpenClaw-parity launch, not for incremental gap-closing work. Tracked toward the "NeuralCleave v2.1.6" milestone.

**Fixed `neuralcleave usage` reading the wrong process's counters, and a stale/incomplete pricing table** — the command read a `REGISTRY` singleton local to the CLI's own process, which never performs a generation, so it could only ever print "No LLM generations recorded" regardless of how much the actual running gateway had processed. Now proxies through `GET /api/v1/usage` first (the same pattern `approvals`/`hub install` use), falling back to the local registry only when no gateway is reachable. Separately, the pricing table used to price only 14 of 29 provider/model pairs the router can actually select — including missing `gemini-2.5-flash`/`gemini-2.5-pro` and `deepseek-coder`, the first-choice models for 6 of the router's 10 task types — added the missing entries (plus new Groq/Together/Fireworks pricing) and a test that fails if a routed model is ever left unpriced again. A model with genuinely no pricing entry now reports as unpriced (excluded from totals) rather than a misleading `$0.0000`, which was indistinguishable from a real free model (Ollama).

**Fixed the embedded terminal's Stop button — it has never actually worked** — `_run_command()`'s subprocess was tracked in a local variable the "interrupt" handler and disconnect cleanup could never see, so both were permanent no-ops; on top of that, even a correctly-wired interrupt only killed the shell wrapper the command runs through, leaving whatever it actually spawned (e.g. a `python script.py`) running as an orphan. Both fixed: `_run_command()` now runs concurrently with the WebSocket's message loop so an interrupt can reach it (including one that arrives before the subprocess even exists yet), and termination now kills the whole process tree, not just its immediate shell.

**Orchestrator routing now actually generates a response** — `AgentOrchestrator.route()` was a documented stub returning a hardcoded placeholder string in every deployment; it now calls `ModelRouter.generate()` with the selected node's `model_override` and returns the real generated text (with `model`/`provider`/`usage` in the result metadata) whenever the orchestrator has a router — which the live gateway now provides. New `ModelRouter.generate(model_override=...)` param backs this: forces one specific model for a single call without mutating router-wide state. Not the full `CognitivePipeline` (still no memory retrieval, reflection, or tool calls for a routed task) — that stays open as a larger future integration. `neuralcleave orchestrate route` now prints the generated content, not just the selected node name.

**Canvas `html` block network egress locked down** — an agent-authored `html` block already ran in a sandboxed iframe (`sandbox="allow-scripts"`, isolating it from the parent page/cookies), but that alone doesn't restrict network access. Every `html` block's `srcdoc` now gets a `Content-Security-Policy` meta tag with `connect-src 'none'`, blocking all `fetch`/`XHR`/`WebSocket` egress by default.

**Removed the orphaned `commands/` package** — a complete, parallel slash-command implementation (`/reset`, `/memory`, `/model`, `/status`, `/compact`, `/voice`, `/canvas`) that nothing in the live gateway ever called into; every real message goes through `agent/runtime.py`'s own separate inline dispatch instead, which also had five commands (`/think`, `/tag`, `/tags`, `/privacy`, `/forget`) this module never had. Removes no working functionality — the one command unique to it, `/canvas` as a typed chat message, was never reachable in production either.

**Closed a WebSocket auth gap on the embedded terminal, main chat socket, and voice socket** — `gateway/terminal.py`'s own docstring claimed CORS protected its WebSocket, but Starlette's `CORSMiddleware` never applies to WebSocket scope at all (documented Starlette behavior, not a NeuralCleave bug), and the optional `gateway.api_key` middleware explicitly exempts every `/ws/*` path. Net effect: any local web page could open the embedded terminal's WebSocket and run arbitrary shell commands with zero authentication, with the full unsanitized process environment (every provider API key included) handed to the subprocess. Fixed with a new shared `gateway/origin_check.py` allow-list, consulted by `CORSMiddleware` and by each of `/ws/terminal`, `/ws`, and `/ws/voice`'s own pre-`accept()` origin check (rejecting a mismatched `Origin` with close code 1008; a missing `Origin` — the case for non-browser clients — is still allowed). `terminal.py`'s subprocess spawn now goes through the same `sanitize_env()` helper `ShellTool`/`BrowserAutomationTool` already use, instead of a raw, unscrubbed `os.environ`.

**Hardened the Hub installer's fetch path** — `_fetch_code()` accepted cleartext `http://` despite its own documented https-only contract; the fetched code gets loaded and registered as a live, LLM-callable tool, so this let a network-position attacker substitute what actually runs. Now rejects anything but `https://`/`data:`. `neuralcleave hub install --checksum <sha256>` (and `POST /api/v1/hub/packages` with `expected_checksum`) now verifies the fetched code against a publisher-declared digest before installing — previously the recorded checksum only ever documented what was fetched, it couldn't verify anything. Also removed `scan_url()`: dead code with zero callers that would have raised if ever invoked from an async context.

**Closed the WebSocket auth gap round 6 missed, and the regression it introduced** — round 6 added an origin check to `/ws`, `/ws/voice`, and `/ws/terminal` but missed a fourth socket, `/ws/canvas`, which accepted every connection unconditionally and streamed live agent-authored canvas content to any origin. Also fixed: the origin allow-list's regex only matched `localhost`, not `127.0.0.1` — the same host to every browser — so a page served on the gateway's own port via its loopback IP (e.g. `/canvas` at `http://127.0.0.1:7432/canvas`) had its WebSocket wrongly rejected.

**Made the web/desktop chat UI actually benefit from the session-identity fix below** — the previous fix made `Session.session_id` stable, but `gateway/websocket.py` was passing its own per-*connection* ID (a fresh random UUID every reconnect) as the sender identity, so the fix never reached the primary chat client — only external channel adapters (Telegram, Discord, etc.) benefited. The frontend now generates and persists a client ID in `localStorage` and sends it on every `/ws`/`/ws/voice` connection; the gateway uses it as the durable memory identity instead of the connection's own ID.

**Fixed config hot-reload watching the wrong file, and added an audit trail for the embedded terminal** — `ConfigWatcher` used to re-guess which config file to watch from two hard-coded candidates, ignoring the path actually passed via `-c custom.toml`; a save to the unrelated default path could then silently apply its `[security]` settings to a running gateway. `NeuralCleaveConfig` now records the path it was actually loaded from, and the watcher uses that directly. `watchfiles` and `cryptography` — both directly imported by real code but previously only present transitively — are now explicit dependencies. Separately: the embedded desktop terminal now records every command it runs to a lightweight, purpose-built log (`~/.neuralcleave/terminal_history.log`) — deliberately *not* gated behind an approval prompt like agent-issued shell commands are, since it's a direct interactive session for the operator's own machine, not an agent acting on their behalf.

**Fixed the PWA mobile companion's broken chat, and stopped `/push/notify` claiming fake success** — the PWA app shell sent `{content: text}` but the server only reads `text`/`payload`, so every message sent from `/app` failed with "Empty message"; the receive side had the same mismatch (`delta`/`text` vs. `content`). `/push/notify` returned `{"sent": N}` implying N devices were notified, when no push-subscription client exists anywhere and no WebPush delivery is wired at all — it now honestly reports `sent: 0` with a separate `matched_subscribers` count. Push notifications themselves remain unbuilt (no subscription client, no VAPID key provisioning, no `pywebpush` dependency) — deliberately deferred rather than fixed in this pass.

**Fixed long-term memory silently resetting on restart or idle timeout** — `Session.session_id`, the key every long-term-memory write/search/`/forget` path scopes by, was a fresh random UUID minted every time a `Session` object was constructed. A gateway restart or a single 30-minute idle gap (the default `SessionManager` timeout) silently orphaned a real user's entire memory history from every later lookup — "long-term" memory only actually lasted as long as one continuous in-process session. Now derived deterministically from `(channel, sender_id)`, so it survives both. Separately, `/forget <keyword>` now previews matches (count + snippets) before deleting anything; `/forget <keyword> confirm` performs the actual deletion, matching the existing `approve <id-prefix>`/`deny <id-prefix>` chat-command pattern.

**Fixed wrong voice config keys across README, mintlify docs, and the API reference** — `stt_backend`/`tts_backend`/`tts_provider`/`wake_word_model`/`wake_word_sensitivity`/`wake_word_enabled` don't exist in `VoiceConfig`; the real fields are `stt`, `tts_engine`, `wake_word`, `wake_word_threshold`. Following the most prominent "Quick enable" example in the docs as written configured nothing at all, silently. Also fixed a doubly-wrong `PATCH /settings/voice` API reference example (real: `POST`, accepting only `stt`/`stt_model`/`stt_device`) and documented two previously-undocumented limitations found in the same pass: `tts_engine` only gates TTS on/off — it doesn't pin synthesis to one engine or skip the ElevenLabs→Kokoro→pyttsx3 fallback chain — and `privacy_mode` has no effect on the voice pipeline (it's a text-generation-only, in-chat-`/privacy`-only toggle, not a `[voice]` config key).

**Browser tool environment sanitization**

* `BrowserAutomationTool` launches a real Chromium child process via Playwright — it previously inherited every provider API key/secret in the gateway's own environment with no scrubbing at all, unlike `ShellTool`'s subprocess exec. Extracted the shared `sanitize_env()` helper (`neuralcleave/tools/env_sanitize.py`) so both tools get the same protection

**Semantic memory, Hub installs, auto-compaction, the scheduler, and streaming `/think`**

* `sentence-transformers` was never in the packaged dependency list `pip install neuralcleave` actually reads (only in a separate, non-canonical `requirements.txt`) — semantic memory has been silently dead by default in every real install; now a core dependency. `GET /api/v1/status` gains `memory.semantic_available` so a degraded install is visible instead of silent
* Hub-marketplace installs (`neuralcleave hub install`, `POST /api/v1/hub/packages`) reported success while never actually registering the installed skill's tools with the live agent — the injected `PluginRegistry` reference was stored and never read. Installs now genuinely validate, write, load, and (when a registry is available) hot-reload
* Auto-compaction (`ConversationCompactor.maybe_compact()`) and the heartbeat scheduler were both dead code — zero real call sites outside their own docstrings/tests despite claiming otherwise. The pipeline now auto-compacts a session's history after each turn once it crosses 50% of the context window, and a daily memory-archival job is registered with the scheduler on startup
* `/think` is now wired all the way through the streaming path (`generate_stream()`/`_call_stream()`) — it previously had zero effect for NeuralCleave's own Web UI chat and voice assistant while working correctly for all 32 external channel adapters
* `neuralcleave hub install/remove` and `neuralcleave orchestrate list/add/remove/route/status` now proxy through a running gateway's REST API first (same pattern as the `approvals` CLI fix), falling back to a local, disconnected instance only if no gateway is reachable
* Corrected the `orchestrate` CLI's docs: node-selection logic is real, but routing itself is a stub that returns a placeholder — no node registered there is reachable from an actual conversation yet (full pipeline wiring deferred to a future round)

**Exec-approval gate, readiness probe, and reasoning-control fixes**

* Fixed the exec-approval gate being completely unreachable in production: `require_shell_approval`/`security_mode`/`ask_mode` had nowhere to be configured, so `ShellTool`/`BrowserAutomationTool` were always constructed with the gate off regardless of the CLI/REST surface built for it — new `[security]` config section closes this
* The channel-forwarded approval notification described below now actually fires — `notify_channel()` existed but was never called from the live request path; `ApprovalQueue` gained an `on_request` hook and `AgentRuntime` wires itself into it
* `GET /api/v1/approvals/policy` now also reports `require_shell_approval` sourced from the live tool registry, since the security/ask modes are meaningless when the gate itself is off; `POST` can now toggle it live too, no restart needed
* New `approval_notifications_total` metric (by `outcome=sent|failed`) so the channel-forwarded notification above is actually observable
* Fixed `GET /ready` reporting `ready: true` even when `AgentRuntime.from_config()` had raised and the runtime never got constructed at all — it now checks the actual runtime object and (when channels are configured) at least one connected adapter, not just a phase string
* Extended `/think` to Ollama's native `think` field (collapsed to a boolean, since not every model supports the 3-tier string form); DeepSeek stays unmapped on purpose — its API has no per-request reasoning-effort field, only a model choice (`deepseek-chat` vs. `deepseek-reasoner`)
* Fixed config hot-reload's `[security]` handling: the reload callback previously only logged that it applied "model settings + API keys" without touching anything at all; it now genuinely applies `[security]` live (model/API-key hot-reload remains unimplemented — the doc claim for that was removed rather than faked)

**Provider status, host metrics, and reasoning control**

* `neuralcleave models list/status [--live]` + `GET /api/v1/models` — credential-configured status for all 19 providers; `--live` adds a real HTTP reachability probe for the 9 providers with a documented, stable check
* `GET /api/v1/status` now includes live CPU/RSS/disk gauges (`process_cpu_percent`, `process_memory_rss_bytes`, `host_disk_usage_percent`, `host_disk_free_bytes`)
* New root-level `GET /ready` (200 once startup fully completes, 503 during init) — distinct from the always-200 `/health`
* Normalized `/think off|low|medium|high|xhigh|max` reasoning-effort control, mapped to Anthropic's extended thinking and xAI/OpenRouter's `reasoning_effort` — also settable via `POST /api/v1/settings/model {"thinking": ...}`
* `ModelRouter.from_config()` extracted as the single config→provider-key mapping (was previously duplicated inline and had silently dropped provider keys once before)

**Cost tracking**

* Static per-model USD pricing table turns already-recorded token counts into an estimated cost — `neuralcleave usage` CLI and `GET /api/v1/usage`
* New `cost_usd_total` metric

**Exec-approval allowlist**

* Persistent glob/regex allowlist (`neuralcleave approvals allowlist list/add/remove`, `GET/POST /api/v1/approvals/allowlist`) with `security: deny|allowlist|full` and `ask: off|on-miss|always` modes (`GET/POST /api/v1/approvals/policy`)
* `POST /api/v1/approvals/{id}/approve` accepts `{"always": true}` to persist a durable allowlist entry
* Pending approvals now forward as a plain-text message in the channel that triggered them — reply `approve <id-prefix>` / `deny <id-prefix>` directly in chat
* New `approval_decisions_total` metric

**Skill review (propose → apply/reject/quarantine)**

* Agent-authored skills (via the `write_skill` tool) no longer load immediately — they're queued as a pending proposal a human must approve or reject (`neuralcleave skills review pending/show/approve/reject`, `GET/POST /api/v1/skills/review/*`)
* `neuralcleave skills quarantine <name>` unloads a skill without deleting it from disk
* `write_skill`/`list_skills`/`delete_skill` tools are now actually wired into the agent's default tool registry — this closes a real gap where they existed but were never reachable by the live agent
* Fixed a related, deeper bug: the gateway's `PluginRegistry` was constructed with no reference to the running agent's `ToolRegistry` at all, so hub-installed and self-written-skill tools never became callable regardless of "successful" registration

**Plugin management**

* `neuralcleave plugins install/uninstall` (pip wrapper, with an explicit `--force` trust gate for non-local/non-PyPI sources) and `plugins enable/disable` (persistent, independent of entry-point discovery) — `plugins list` now shows disabled status

**Secrets, backup, and durability**

* `op://vault/item/field` secret references (1Password CLI), alongside the existing `ENV:VAR` pattern, for every provider key and all 34 channel adapters
* Privacy audit log now **persists across restarts** (SQLite, 90-day retention) — it used to reset every time the gateway restarted
* `neuralcleave backup create/list/verify/restore` — the state directory (config, memory, audit log, skills) had no first-party backup path before this

**New providers & search backends**

* Groq, Together AI, and Fireworks AI (OpenAI-compatible, joining the existing 16) — 19 providers total
* Brave Search and Tavily as alternate `web_search` backends alongside SearXNG/DuckDuckGo

***

## v2.1.5 — 2026-08-08

**Voice UI (P6)**

* PTT button always visible in chat toolbar — shows disabled state with Settings tooltip when unconfigured
* `VoiceStatusIndicator` shows idle "Voice" badge when STT is configured but nothing is actively recording
* Dedicated `/voice` dashboard page: subsystem status grid, wake word card, last transcript card, continuous-listening toggle
* "Voice" link added to sidebar navigation

**MCP stdio server (P3)**

* `neuralcleave.mcp` package: `protocol.py`, `tool_adapter.py`, `server.py`, `spawn.py`
* JSON-RPC 2.0 over stdio; implements `initialize`, `tools/list`, `tools/call`, `ping`
* Gateway routes: `POST /mcp/spawn`, `GET /mcp/status`, `DELETE /mcp/server`
* Claude Desktop and Cursor integration supported

**Exec approval manager**

* `ApprovalQueue` with 120-second timeout gate
* `ShellTool` gains `require_approval` mode
* Gateway routes: `GET /approvals/pending`, `POST /approvals/{id}/approve`, `POST /approvals/{id}/deny`

**Privacy audit (P7)**

* `PrivacyAuditLog` records every outbound HTTP call
* `neuralcleave privacy report` CLI command
* `GET /privacy/report` and `GET /privacy/settings` endpoints

**Multi-step tool chains (P10)**

* `_run_tool_chain` in the pipeline runs up to `max_tool_steps` (default 5) tool calls per pipeline turn
* `tool_chain_depth` metric in `/metrics`

***

## v2.1.4 — 2026-08-07

**OpenClaw parity gaps P0–P5 (PR #114)**

* Semantic memory with sentence-transformers embeddings and cosine retrieval
* Orchestrator for multi-agent routing
* Canvas WebSocket for real-time chart updates
* 8 new LLM providers: Mistral, xAI Grok, Cohere, Groq, Together AI, Perplexity, Fireworks, OpenRouter
* Settings UI fields for all 13 providers
* Config hot-reload via watchfiles (API keys and model settings without restart)

***

## v2.1.3 — 2026-07-30

**Voice pipeline phases 1–2 (PR #88)**

* Binary audio lane over WebSocket
* faster-whisper STT integration
* ElevenLabs, Kokoro, and pyttsx3 TTS
* OpenWakeWord wake word detection
* PTT REST API (`/voice/ptt/start`, `/voice/ptt/stop`)
* Voice settings API (`PATCH /settings/voice`)

***

## v2.1.2 — 2026-07-15

**Audio device selector**

* Settings → Voice: input/output device dropdowns populated from `GET /voice/devices`
* Active device persistence across gateway restarts

**Gateway restart command**

* `POST /settings/gateway/restart` endpoint
* STT default changed to `whisper` (was `none`)

***

## v2.1.1 — 2026-06-15

**CortexFlow → NeuralCleave rename**

* All Python packages renamed `cortexflow` → `neuralcleave`
* CLI renamed `cortexflow` → `neuralcleave`
* neuralcleave.com domain acquired

***

## v2.0.0 — 2026-06-07

**Personal AI assistant pivot**

* Pivoted from enterprise B2B to personal AI assistant (OpenClaw competitor)
* Enterprise features mirrored to `CortexFlow-Enterprise` repo
* 32 channel adapters, 5 initial LLM providers
* Phase 8 integration: REST metrics, reflection engine, WebSocket channels
