Unreleased
Everything below has merged tomain 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
BrowserAutomationToollaunches 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, unlikeShellTool’s subprocess exec. Extracted the sharedsanitize_env()helper (neuralcleave/tools/env_sanitize.py) so both tools get the same protection
/think
sentence-transformerswas never in the packaged dependency listpip install neuralcleaveactually reads (only in a separate, non-canonicalrequirements.txt) — semantic memory has been silently dead by default in every real install; now a core dependency.GET /api/v1/statusgainsmemory.semantic_availableso 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 injectedPluginRegistryreference 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 /thinkis 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 adaptersneuralcleave hub install/removeandneuralcleave orchestrate list/add/remove/route/statusnow proxy through a running gateway’s REST API first (same pattern as theapprovalsCLI fix), falling back to a local, disconnected instance only if no gateway is reachable- Corrected the
orchestrateCLI’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)
- Fixed the exec-approval gate being completely unreachable in production:
require_shell_approval/security_mode/ask_modehad nowhere to be configured, soShellTool/BrowserAutomationToolwere 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;ApprovalQueuegained anon_requesthook andAgentRuntimewires itself into it GET /api/v1/approvals/policynow also reportsrequire_shell_approvalsourced from the live tool registry, since the security/ask modes are meaningless when the gate itself is off;POSTcan now toggle it live too, no restart needed- New
approval_notifications_totalmetric (byoutcome=sent|failed) so the channel-forwarded notification above is actually observable - Fixed
GET /readyreportingready: trueeven whenAgentRuntime.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
/thinkto Ollama’s nativethinkfield (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-chatvs.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)
neuralcleave models list/status [--live]+GET /api/v1/models— credential-configured status for all 19 providers;--liveadds a real HTTP reachability probe for the 9 providers with a documented, stable checkGET /api/v1/statusnow 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|maxreasoning-effort control, mapped to Anthropic’s extended thinking and xAI/OpenRouter’sreasoning_effort— also settable viaPOST /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)
- Static per-model USD pricing table turns already-recorded token counts into an estimated cost —
neuralcleave usageCLI andGET /api/v1/usage - New
cost_usd_totalmetric
- Persistent glob/regex allowlist (
neuralcleave approvals allowlist list/add/remove,GET/POST /api/v1/approvals/allowlist) withsecurity: deny|allowlist|fullandask: off|on-miss|alwaysmodes (GET/POST /api/v1/approvals/policy) POST /api/v1/approvals/{id}/approveaccepts{"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_totalmetric
- Agent-authored skills (via the
write_skilltool) 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 diskwrite_skill/list_skills/delete_skilltools 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
PluginRegistrywas constructed with no reference to the running agent’sToolRegistryat all, so hub-installed and self-written-skill tools never became callable regardless of “successful” registration
neuralcleave plugins install/uninstall(pip wrapper, with an explicit--forcetrust gate for non-local/non-PyPI sources) andplugins enable/disable(persistent, independent of entry-point discovery) —plugins listnow shows disabled status
op://vault/item/fieldsecret references (1Password CLI), alongside the existingENV:VARpattern, 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
- Groq, Together AI, and Fireworks AI (OpenAI-compatible, joining the existing 16) — 19 providers total
- Brave Search and Tavily as alternate
web_searchbackends 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
VoiceStatusIndicatorshows idle “Voice” badge when STT is configured but nothing is actively recording- Dedicated
/voicedashboard page: subsystem status grid, wake word card, last transcript card, continuous-listening toggle - “Voice” link added to sidebar navigation
neuralcleave.mcppackage: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
ApprovalQueuewith 120-second timeout gateShellToolgainsrequire_approvalmode- Gateway routes:
GET /approvals/pending,POST /approvals/{id}/approve,POST /approvals/{id}/deny
PrivacyAuditLogrecords every outbound HTTP callneuralcleave privacy reportCLI commandGET /privacy/reportandGET /privacy/settingsendpoints
_run_tool_chainin the pipeline runs up tomax_tool_steps(default 5) tool calls per pipeline turntool_chain_depthmetric 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
POST /settings/gateway/restartendpoint- STT default changed to
whisper(wasnone)
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-Enterpriserepo - 32 channel adapters, 5 initial LLM providers
- Phase 8 integration: REST metrics, reflection engine, WebSocket channels
