Updates

Changelog

What's new in Nova.

Memory subsystem hardening — two production-cliff queries fixed, real-DB test infrastructure

Five sprints of work on memory-service/app/engram/ close two scaling-cliff queries that would have stalled the subsystem at production scale, plus 214 unit tests on what was previously the most under-tested complex code in Nova.

  • Spreading-activation cliff fixed (P1). The recursive CTE used to join engram_edges on (source_id = X OR target_id = X) — an OR predicate that defeated both single-column edge indexes and forced Postgres into a Bitmap-OR plan. Rewritten as a single recursive reference with a LATERAL UNION ALL of two indexed branches (source-side + target-side), each with a per-hop fan-out cap (default 50) and a tenant filter on the neighbor join. The activation deep-mode follow-up got the same treatment: 1.507 ms → 0.129 ms (~12× speedup) measured on a 4156-engram database.
  • Consolidation merge cliff fixed (P2). The duplicate-merge phase used to do an O(N²) cartesian self-join over engram embeddings, generating 5.7M candidate pairs in 2.68s on the same database. Replaced with a per-candidate HNSW shortlist (top-K nearest neighbors of the same type/source_type, SET LOCAL hnsw.ef_search = 40 for stable top-K) plus a loser_ids exclusion set so popular winners can absorb additional duplicates within a cycle.
  • Schema-synthesis batched. The coherence-gate that compares each source engram’s embedding against a synthesized schema’s embedding used to make 5 round-trips per cycle (one per source). Now one batched WHERE id = ANY(...) query.
  • Working-memory dedup. assemble_context used to call get_embedding(query) twice — once for neural-router rerank, once for retrieval logging. Now computed at the top of the function and reused.
  • memory-service connection reuse. Eight per-call httpx.AsyncClient(...) sites collapsed to one shared singleton (get_http_client()) registered in the FastAPI lifespan. Mirrors the existing get_redis() pattern.
  • Real-Postgres + pgvector unit-test fixtures. New conftest.py provides db_session (per-test BEGIN/ROLLBACK), redis_test (db15 with FLUSHDB), engram_factory, edge_factory, fake_llm (replay+record with prompt-hash normalization), graph_builder, and a JSON snapshot helper. The previous mock-based fixtures are quarantined in conftest_legacy.py. 214 tests run end-to-end on real services in ~6 seconds.

If you’re upgrading: schema changes are additive and run automatically at memory-service startup. The 214 unit tests are gated behind cd memory-service && uv run scripts/setup_test_db.py && uv run pytest tests/ — you only need them if you’re modifying the engram subsystem.

A handful of spec/code mismatches surfaced during the test sweep — working_memory.py’s “five-tier slot system” is a DB-schema artifact only (no runtime promotion/demotion), decompose() is pure (no DB writes; persistence happens in ingestion.py), and reconstruction.py does no token-budget truncation (the caller is responsible). Tests now document the actual contracts; reconciling docs/audits with reality is follow-up work.

Platform secrets — provider keys + bridge tokens move out of writable .env

The platform_secrets store introduced for the Capability Platform now backs all sensitive runtime config — provider API keys, chat-bridge tokens, OAuth client secrets — not just user-granted GitHub PATs.

  • LLM gateway boots with provider keys read from platform_secrets (DB-backed, encrypted at rest) instead of .env. The dashboard’s Provider Status panel writes/rotates keys via the admin API; nothing edits .env at runtime.
  • chat-bridge boots with Telegram and Slack tokens read from platform_secrets. The bridge service no longer needs raw tokens in any process environment after the secrets land in the DB.
  • Recovery service rejects writes to secret-bearing keys via its /env editor — operators are nudged toward the proper Settings UI flow rather than .env poking.
  • Worker services (intel-worker, knowledge-worker) use a shared PlatformSecretsResolver to fetch their dependencies (HuggingFace tokens, RSS feed creds, etc.) at startup with the same encryption + audit guarantees.
  • Recovery’s Docker SDK gated behind socket-proxy (SEC-006b). Recovery no longer has direct access to /var/run/docker.sock; it talks to a tightly-scoped socket-proxy that allows only the operations recovery actually needs (compose up/down, container inspect for health). Reduces blast radius if recovery’s API is ever compromised.

Migration is automatic on first restart: the orchestrator’s startup handler reads any secret-bearing keys still in .env, writes them into platform_secrets, and logs a one-time notice. Subsequent restarts read from platform_secrets only. To rotate, use the dashboard or the admin API — direct .env edits to provider keys won’t take effect until a platform_secrets upsert runs.

Personal context capture — screenpipe-bridge service + Capture dashboard

Nova can now ingest your screen activity (with consent and a privacy denylist) so it has actual context for what you’re working on. New screenpipe-bridge service plus a dedicated Capture top-level page in the dashboard.

  • screenpipe-bridge service (port 8140). Subscribes to a user-installed screenpipe daemon over WebSocket, with HTTP polling fallback after 5 WS failures. Aggregates raw events into focus sessions capped at 30 minutes, applies a two-layer privacy denylist (apps + URL patterns + window titles), and pushes payloads into the engram ingestion queue.
  • Capture top-level nav. New “Capture” route in the dashboard sidebar with three sub-pages: Connection (live status of screenpipe daemon, connection test, today’s stats, recent activity feed), Meetings (placeholder for upcoming meeting-summary work), and Journals (placeholder for upcoming work-journal work). Capture is intentionally first-class — the Personal Context Layer is multi-sub-project work; the screen-activity capture is sub-project 1 of 4.
  • Privacy denylist editor. Manage app names, URL regex patterns, and window-title substrings to exclude from capture. Settings live in Redis (nova:config:capture.denylist.*) and apply at the bridge level — denylisted activity never enters the ingestion pipeline.
  • Pause without disconnect. A single toggle in advanced settings sets capture.paused=true; the bridge stays connected to screenpipe, sessions still arrive, but they’re discarded with a paused counter. Resume by flipping the toggle.
  • Bounded backpressure. Pipeline drops events past capture.buffer_size (default 10) with a dropped counter exposed via /health/ready so operators can see when the pipeline is saturated.
  • Trust-aware engrams. Screenpipe-derived engrams land at trust score 0.80 (above intel feeds and knowledge crawls, below chat). Source provenance traces every engram back to a session.

Optional service — only starts if the user installs and points to a screenpipe daemon (nova:config:screenpipe.url + screenpipe.api_key). Per-OS install + Nova config guide at docs/capture/.

Capability Platform — credentialed tools, consent gate, audit log, autonomous CI triage

The Capability Platform (M11) gives Nova the safe-by-default rails to act on external systems with user consent: a credential vault, an HMAC-signed audit log, a consent-gate workflow, and the first end-to-end autonomous “see a failing CI build → propose a PR” loop.

  • Encrypted credential vault. A platform_secrets store with per-secret AES encryption (master key auto-bootstrapped on first install) holds OAuth tokens, GitHub PATs, and service credentials. Secrets are referenced by name; runtime fetches happen via PlatformSecretsResolver so the orchestrator and worker services no longer read raw API keys from .env.
  • Hash-chained audit log. Every credentialed action — credential read, tool execution, approval decision — appends to an append-only audit log with a SHA-256 hash chain that ties each row to the previous. The maintain drive in cortex verifies the chain daily and pages on tampering.
  • Consent gate. New external tools declare a tier — READ (auto), PROPOSE (auto), MUTATE (consent required) — and MUTATE calls land in an approval_requests queue. The dashboard’s Approvals panel shows pending requests; a single approval/denial closes the loop, and a worker resumes the originating agent goal automatically. “Approve and remember” rules let users pre-authorize categories of action.
  • GitHub provider, four tiers. Twelve github_external tools cover READ (list_repos, get_pr, get_check_runs), PROPOSE (open_draft_pr, comment_on_pr), MUTATE (open_pr, merge_pr, close_pr), and SETUP (register_webhook, add_collaborator). Granted PAT scopes are captured at validation time; tools surface scope_mismatch warnings before attempting privileged calls.
  • Autonomous CI triage drive. Cortex’s new ci_triage_agent pod watches GitHub webhooks → check_run failures dispatch a stimulus → the goal pipeline proposes a fix PR. A singleton-elected polling worker provides a fallback when webhooks lag. The drive enforces a per-cycle cost budget and audits its own actions.
  • v1 release gate closed. Commit cf26c927 lands the automated end-to-end test that proves the full webhook → cortex → goal → PR loop works without human intervention on the happy path. This is the milestone where Nova first acts autonomously on a user’s repo.

If you’re upgrading: connect a GitHub PAT in the Settings → Connections panel, then watched_repos in the Connected Services section. The Approvals panel shows what Nova wants to do; nothing MUTATEs without explicit approval (or a matching auto-approve rule).

Inference modes — bundled Ollama by default

Nova now asks how you’d like to use it on first run, and ships sensible defaults for each option:

  • hybrid (default) — bundles Ollama for local AI, falls back to cloud providers when needed. Best of both worlds. First-run downloads ~5.4 GB of starter models (qwen2.5:1.5b, qwen2.5:7b, nomic-embed-text).
  • local-only — bundles Ollama, never uses cloud. Privacy-first or offline-friendly. Same ~5.4 GB starter models.
  • cloud-only — does not bundle Ollama at all (no image pull, no container, no model downloads). Lightest footprint, requires cloud API keys.

After install, switching modes (and pointing Nova at an external Ollama / vLLM instance like http://192.168.x.y:11434) lives in the dashboard — Settings → AI & Models, no scripts. The first-install prompt is just the bootstrap step before the dashboard is running.

Under the hood: a single user-facing NOVA_INFERENCE_MODE knob now derives both COMPOSE_PROFILES (whether the bundled ollama Compose service ships and starts) and LLM_ROUTING_STRATEGY (how the gateway picks providers). The setup.sh wizard writes both to .env idempotently, preserving any unrelated profiles you already have set.

Heads-up for existing installs: if your .env previously had OLLAMA_BASE_URL=auto or OLLAMA_BASE_URL=host and you depended on the gateway probing your host’s Ollama instance (Windows/macOS native install, remote LAN box, etc.), those values now resolve to the bundled service (http://ollama:11434) instead. The brittle subprocess-based probe (and scripts/resolve-ollama-url.sh) is gone. To keep using your host’s Ollama, set OLLAMA_BASE_URL to a literal URL — e.g. OLLAMA_BASE_URL=http://host.docker.internal:11434 for a same-host install, or OLLAMA_BASE_URL=http://192.168.x.y:11434 for a LAN box.

Security hardening — admin secret, OAuth state, recovery guards

Five interlocking security gaps closed in one chain:

  • Admin secret no longer impersonates users. The X-Admin-Secret header used to silently authenticate user-context endpoints — every fresh install was effectively “log in as anyone” by header. Now UserDep is JWT-only; admin secret authenticates admin endpoints only. Worker services (intel-worker, knowledge-worker) were unaffected because they already hit admin endpoints exclusively.
  • Random admin secret on install. scripts/install.sh generates a 32-byte URL-safe NOVA_ADMIN_SECRET when .env contains the literal default, an empty value, or no value at all. The generated secret is displayed once at the end of install with a “save this to your password manager” prompt. Orchestrator, recovery, and cortex now refuse to start with the literal default unless NOVA_ALLOW_DEFAULT_ADMIN_SECRET=1 is set (escape hatch for tests/dev).
  • Recovery service hardened. CORS allowlist replaces wildcard. Critical services (postgres, redis, recovery itself) cannot be restarted via the API — operators must use docker compose restart from the host where the consequences are obvious. Container matching switched from substring (if name in c.name) to exact com.docker.compose.service label, so restart_service("post") no longer accidentally matches postgres.
  • Cortex CORS allowlist. Same wildcard → settings-driven allowlist fix as recovery; cortex now matches the orchestrator pattern.
  • OAuth CSRF protection. Google OAuth flow now requires a state parameter. /api/v1/auth/google mints 32 bytes of URL-safe random state, stores it in Redis with a 10-minute TTL, and returns it alongside the consent URL. /api/v1/auth/google/callback validates state via GETDEL (single-use) and uses the Redis-stored redirect_uri rather than any client-supplied value — closing both CSRF and redirect_uri-tampering vectors. PKCE is a follow-up.

If you’re upgrading: rerun ./scripts/install.sh to regenerate your admin secret (or set a strong NOVA_ADMIN_SECRET manually). The Redis runtime override at nova:config:auth.admin_secret still wins at runtime — clear it with docker compose exec redis redis-cli -n 1 DEL nova:config:auth.admin_secret if it has drifted from .env.

A few intentionally-deferred items get their own future work: a built-in secret manager (which will absorb a “rotate from dashboard” feature), service-account JWTs to replace X-Admin-Secret for internal worker calls, OAuth PKCE, and retiring the admin secret entirely in favor of an is_admin JWT claim. The current chain shrinks blast radius enough that those become value-adds, not emergencies.

./uninstall — clean removal in one command

Nova now ships with a top-level ./uninstall script that removes everything Nova installed on the machine: containers, networks, named volumes, locally-built Docker images, the bind-mounted data/ directory, backups/, .env and its backups, build artifacts (dist/, node_modules/, __pycache__, .pytest_cache), and the agent workspace at ~/.nova/workspace/.

The flow is preview-first: ./uninstall always shows you exactly what will be deleted, broken down by category with disk-size totals, before asking for an explicit uninstall confirmation. Pass --dry-run to see the preview without any destruction, or --yes to skip the confirmation in scripted contexts.

The cloned repo source itself is left intact (delete it manually with cd .. && rm -rf nova if you want), and shared upstream Docker images (ollama/ollama, pgvector/pgvector, redis, etc.) are NOT touched — they may be in use by other Docker projects on the same machine. Use docker image prune -a separately if you want a deeper sweep.

For routine “reset Nova to a clean slate while keeping it installed,” use the in-app factory reset under Settings → System. ./uninstall is for “Nova is leaving this machine.”

Also in this release: the top-level setup script and scripts/setup.sh have been renamed to install and scripts/install.sh to match the conventional install/uninstall pairing. The make setup target is now make install.

One-line install + --help everywhere

Two small DX improvements that make Nova nicer to start and nicer to live with.

scripts/bootstrap.sh + curl-pipe-bash one-liner. New users can now install Nova with a single command:

Terminal window
curl -fsSL https://raw.githubusercontent.com/jeremyspofford/nova/main/scripts/bootstrap.sh | bash

The bootstrap script checks for git and docker, clones the repo (defaults to ./nova, override with NOVA_DIR=<path>), and re-attaches the install wizard to the controlling terminal so you land directly in the mode-selection prompt. Pass --no-install to clone without launching the wizard, or just keep using the git clone && cd nova && ./install flow if you’d rather audit the source first — both routes hit the same wizard.

--help on every script. Every user-facing script (./install, ./uninstall, scripts/install.sh, scripts/backup.sh, scripts/restore.sh, scripts/setup-remote-ollama.sh, scripts/detect_hardware.sh, scripts/bootstrap.sh) now responds to --help / -h with a clear usage block — what the script does, how to invoke it, what flags and environment variables it accepts. No more “what does this script even do” — just append --help.

Voice Chat & Conversation Mode

  • Voice service (voice-service, port 8130) — STT via OpenAI Whisper, TTS via OpenAI TTS with provider abstraction for Deepgram and ElevenLabs
  • Conversation mode on Brain page — Gemini-style hands-free voice loop with auto-listen, silence detection, and barge-in interruption
  • Barge-in support — start talking while Nova is speaking to interrupt her mid-sentence; warm mic pattern eliminates turn-transition latency
  • Live transcription — real-time display of words as you speak (Web Speech API for live display, Whisper for final accuracy)
  • Audio level visualization — animated bars respond to mic input volume in real-time
  • Mute now immediately stops TTS playback mid-sentence (previously only prevented new sentences)
  • Model selector synced between Chat and Brain pages — change model from either and it stays in sync
  • Configurable voice settings: silence timeout (500ms-10s) and barge-in threshold (0.05-0.50) in Dashboard Settings
  • Voice service is optional — enable with --profile voice, dashboard auto-hides voice UI when unavailable

Managed Inference Backends

  • Added managed inference backend lifecycle — select vLLM or Ollama from the dashboard, Nova handles container start/stop/health
  • Hardware detection: auto-detects GPU vendor, VRAM, Docker GPU runtime at setup and runtime
  • New LocalInferenceProvider in LLM gateway wraps the active backend with 5-second config cache
  • Backend switching with drain protocol: zero dropped requests when switching between backends
  • New Settings section: Local Inference (AI & Models tab) with backend selector, live status, hardware info
  • Recovery service now manages inference containers via Docker Compose profiles with health monitoring (30s interval, exponential backoff restart)
  • vLLM model discovery via /v1/models endpoint
  • In-flight request tracking (GET /health/inflight) enables graceful drain during backend switches
  • Redis db7 allocated for recovery service (nova:system:* namespace for system facts)

Remote Access & Navigation

  • Added Remote Access page with Cloudflare Tunnel and Tailscale setup wizards
  • Updated NavBar with improved navigation structure
  • Expanded MCP server catalog with new entries

Shared UI & Mobile

  • Introduced shared UI primitives for consistent component design
  • Fixed mobile layout overflow across all dashboard pages
  • CSS deduplication pass

Recovery Service

  • Added recovery sidecar service for backup/restore and factory reset
  • Database backup/restore via CLI and web UI
  • Service management with Docker SDK integration
  • Startup screen showing services coming online

Platform Hardening

  • Fixed MCP tools invisible to agents
  • Added test foundation with pytest fixtures
  • Fixed streaming token counts for subscription providers
  • Fixed reaper race condition with Redis dedup gate
  • Structured JSON logging across all services
  • Embedding cache activation (3-tier: Redis, PostgreSQL, LLM Gateway)
  • Working memory cleanup background job

Dashboard MVP

  • Overview page with live agent cards
  • Usage analytics with monthly/weekly/daily charts
  • API key management with one-time reveal
  • Models page showing 39 models grouped by provider
  • Dark mode and theme system
  • Settings page with provider status, context budgets, and admin controls