First public release. Feral is source-available under the Business Source License 1.1 (free for individuals and for organizations under $2M revenue; each version converts to Apache 2.0 after four years). Windows and macOS builds are unsigned — see the README for the SmartScreen and first-launch steps.
Builds: Windows x64 and Linux x64 (Vulkan), macOS Apple Silicon (Metal), macOS Intel (CPU — llama.cpp's Metal backend targets Apple GPUs, so an Intel Mac gets the CPU engine, which is what it would have fallen back to anyway). An opt-in NVIDIA CUDA build is published as a separate download for Windows and Linux; it is deliberately excluded from the auto-updater, so it does not update itself.
Feral in the terminal
A real terminal client. feral chat opens a full TUI: streaming answers rendered at 30fps, tool calls as inline pills, a thinking panel you can fold away, and slash commands (/think, /verbose, /usage, /restart, /compact, /model, /connectors). Layout is borderless and flat, in the shape terminal users already know from Claude Code.
It behaves like a terminal program should. NO_COLOR is honoured globally, there is an ASCII mode for terminals without glyph support, the mouse wheel scrolls, manual scrollback is not yanked away by an incoming stream, Esc interrupts the generation instead of quitting the app, Ctrl+C needs a second press, input history works, and a panic restores the terminal instead of leaving it wedged.
Failures are legible, not silent. No model, runtime offline, runtime lost, rate-limited — each gets an error card explaining what happened, with an automatic retry countdown where retrying makes sense.
A setup wizard and a --plain mode for scripting and for terminals where the full UI is not wanted.
Feral without the desktop app
The runtime is no longer trapped inside the desktop app. It has been extracted into a feral-core crate that both the desktop app and a headless gateway boot through the same way — one runtime, several faces.
A feral command-line tool. Gateway lifecycle (start/stop/status), feral doctor, model management, logs, connectors, dreams, config, shell completions, and --json on everything for scripting. Plain feral in a terminal opens chat.
A public runtime HTTP API on loopback: /runtime/* for reads and actions, POST /runtime/chat for streaming chat over SSE (cloud keys work headlessly), and /events as a live SSE feed of what the runtime is doing. Stability is declared per route — see the API stability contract below.
One feral to install. The npm package now ships the Rust binary and the sidecar together, so there is no second thing to install and no drift between them.
The model picks itself
Brain Stack: capability-routed model selection. Instead of pinning one model to everything, the runtime classifies the task and routes it to a model that can actually do it, weighing cost and health. A cheap model handles cheap turns; the expensive one is spent where it earns its keep. feral doctor checks the routing config for you.
Onboarding
Guided first run. Feral now looks at your machine before asking you anything: an existing config, GGUF files already on disk, a hardware-tier model download, provider keys in the environment, a running Ollama, or an OpenClaw config to import. Each candidate is verified with a real completion before it is saved, so a route that is persisted is a route that works. Available in the desktop wizard, in feral setup (with --classic for the old wizard), and as a guided screen in the terminal client.
WhatsApp pairing without the terminal. The pairing QR now renders in the desktop Connectors page with a live countdown to the next code, and in the TUI via /connectors add whatsapp and /connectors qr.
Local models and GPU
Partial GPU offload. Offload used to be all-or-nothing: if the model did not fit entirely in VRAM — including the KV cache — Feral dropped to *full CPU*. A card that missed by a few hundred MB ran the whole model on the CPU. Feral now fits as many layers as VRAM allows and leaves the rest on the CPU, sizing the budget from the model's real geometry rather than an estimate.
A GPU build no longer breaks the CPU fallback. On some cards (verified on an RX 580) llama.cpp routed buffers through the Vulkan device even at zero offloaded layers, so when the GPU could not take the model the CPU fallback failed too and the model did not load *at all* — the GPU build was worse than the CPU build for those users. The last-resort CPU path now detaches the device.
You can see where the model is running. A badge next to the model name and in Settings → Hardware shows the real outcome after the load (GPU (vulkan, 24/32 layers), or CPU). If a GPU-capable build lands on the CPU anyway, Feral raises one notification explaining why and what to try.
NVIDIA CUDA build as a separate, opt-in download. Vulkan stays the default for everyone (it runs on NVIDIA too). The CUDA assets are deliberately excluded from latest.json and do not auto-update.
Agent and memory
Sessions survive a restart. Working memory now rehydrates from the episodic store, so a conversation is not amnesiac after a restart or an eviction. Machine sessions (cron/RSI/dream) still start clean.
A provider error is now its own error. The "local fallback" was a keyless copy of the boot-time cloud provider, so after switching providers an error on the new one silently re-called the old one — and the old one's failure was what you saw. The fallback target is now always loopback, and if no local engine is serving, there is no fallback.
MCP tools are callable. They were discoverable but impossible to call: the tool schemas were snapshotted before the MCP servers finished connecting, so the tools appeared in the list and said "enabled" while the model had no function to call. The registry is versioned now and the agent loop rebuilds its prompt, grammar and schemas when it changes.
New remember tool, so the agent can write to memory directly instead of waiting for the asynchronous extractor. recall searches facts too.
FERAL_HOME is honoured. It was documented but ignored by eight modules (SOUL/IDENTITY, onboarding, the memory graph, the four RSI roots), so an isolated profile still read and wrote the real one.
Resume works. resume_get always returned null — nothing ever recorded the current task.
Cloud transcripts get room to breathe. The transcript budget on cloud providers is raised to 200k, and the agent is nudged to reach for web_search first rather than guessing from memory.
Feral improves itself — and shows its work
This is the part of Feral that is not like other assistants: it evolves its own configuration and, now, its own code. Every step of that is gated, journalled and reversible, because an agent that can rewrite itself and cannot be audited is not a feature.
Dream Cycle. When you are idle, Feral runs a seven-stage cycle over what it learned, proposes changes to itself, and evaluates them. You can trigger it yourself ("Dream now") and watch which stage it is in.
Nothing is promoted on a hunch. A statistical confidence gate decides whether a candidate actually beat the champion or merely got lucky; rejections are counted and shown rather than swallowed. A Tier 0 sanity floor is enforced at promotion, so a candidate that wins on the metric but fails the basics cannot be crowned.
An Evolution Journal with receipts. Every episode is journalled with honest budget accounting and per-candidate fitness, surfaced in the Dreams panel. Champions are archived per niche (a "tree of champions") rather than a single global winner.
Code-level self-improvement, behind a wall. Feral can now propose patches to its own source. They are parsed, checked against a patch policy wall on both sides of the boundary, and evaluated in a *disposable git worktree* — the candidate never runs in your working tree. A patch that passes still waits for your approval in the Dreams panel. On approval it is applied, the sidecar rebuilds and restarts, and a watchdog reverts it automatically if the new build crashes.
Governance
A policy layer over what Feral is allowed to do to itself, with a fail-closed loader: if the policy is missing, unparseable, or violates the ground rules, every governed action is refused rather than allowed.
The audit trail is hash-chained. The evolution journal and the policy history are chained, and governance verify walks the chain and tells you which file or row broke it — so tampering is detectable, not merely discouraged.
Propose / approve / reject / rollback / freeze, available from the CLI and from a Governance card in the desktop app with an approval inbox.
Modules
Feral's internals are becoming swappable at named seams. A module is a Bun subprocess with a manifest, run behind resource walls with a seeded RNG, speaking JSON-lines — so a replacement for a piece of Feral can be evaluated without being trusted.
Promotion is earned by a paired shadow evaluation against the builtin, with floors it has to clear. A promoted module that misbehaves is auto-quarantined by a watchdog after repeated strikes and the seam falls back to the builtin. Visible from IPC, the API, the CLI, and an Architecture card.
Personal adaptation (LoRA)
Feral can fine-tune itself to you, on your machine. A dataset is built from your own interactions, a LoRA adapter is trained locally, and it is promoted only if it beats the base model on an eval gate — with provenance recorded and one-click rollback. Adapters, their measured resource cost, and the review queue live in a dashboard.
Sandbox
Allow-by-default with a deny wall at call time. fetch_url and http_request are always registered with open egress (behind an SSRF guard, a rate limit and an audit trail); FERAL_FETCH_DOMAINS / FERAL_HTTP_DOMAINS now *restrict* rather than enable. Workspace roots default to the launch directory plus your home, with a hard deny wall on ~/.feral (except scratch), ~/.ssh and anything in FERAL_FS_DENY.
New connectors_manage and product_info tools, so the agent can configure its own connectors and answer questions about Feral itself.
Security
The SSRF guard let IPv6 loopback through. fetch_url / http_request refuse to contact loopback, private and link-local addresses. But on the Rust side the check parsed the hostname as an IP *with its brackets still on* ([::1]), which never parses — so the literal-IP check silently never ran for any IPv6 URL, and http://[::1]/ reached the network. And on both sides, loopback was recognised by matching the literal text ::1, so every other spelling of the same address walked through: [0:0:0:0:0:0:0:1] is the same address written out, and [::ffff:127.0.0.1] is IPv4 loopback wearing an IPv6 costume — including ::ffff:169.254.169.254, the cloud metadata endpoint. Both halves now decode the address and compare numbers instead of strings.
Found by the new Rust CI job on its first run: the guard's own test had been failing on Linux the whole time, and nothing ever compiled Rust on Linux before a release build.
Conversations were being written to the logs. The cloud chat path logged the full outbound request body — your messages included — and every inbound chunk, at warning level, behind a comment that said "Remove after triage". Removed.
The npm auth token could have entered git history. .npmrc is now ignored.
Dependency advisories: plist 1.9 → 1.10 and quick-xml 0.39 → 0.41 (two high-severity RUSTSEC advisories), crossbeam-epoch 0.9.18 → 0.9.20 (RUSTSEC-2026-0204). CI fails the build on new advisories.
Rate limits
Feral now stays under a provider's requests-per-minute cap instead of discovering it the expensive way. NVIDIA NIM's free tier allows 40 requests a minute. Nothing counted requests, and an agent turn spends one request per tool round-trip — so the first genuinely multi-step task tripped the cap and every call after it came back 429, killing the task mid-run.
A sliding 60-second window per endpoint now holds a request back when it would exceed the cap, waiting exactly long enough for the oldest one to age out — usually a couple of seconds, not a minute. It spends 90% of the published limit, because our minute and the provider's are not the same minute and aiming at exactly 40/40 produces 429s by construction. Endpoints with no published cap — the bundled local engine above all — are never throttled.
A 429 that slips through anyway (the count is local, so a key also used outside Feral is invisible to it) is retried, honouring Retry-After, up to three times. A provider asking us to come back in ten minutes surfaces as an error rather than freezing the agent for ten minutes.
Waits are announced as a rate_limited event, so a multi-second pause reads as a pause and not as a hang, and a stop cancels the wait instead of making the user sit through it. Override the cap with FERAL_RATE_LIMIT_RPM if you are on a paid tier or share one key with something outside Feral.
Privacy
The startup update check is opt-out (Settings → General) and contacts GitHub Releases only. Documented in the README's privacy section.
Internals
Nothing here changes what Feral does, but it changes how fast it can be changed safely.
The sidecar protocol is versioned and schema-checked, and a test fails the build if the Rust and TypeScript halves of it drift apart.
One typed config module. Every FERAL_* variable is declared in one place with a type and a default, and docs/CONFIGURATION.md is generated from it — a new variable that is not documented fails CI.
MCP is unified on the sidecar. There were two MCP implementations; the Rust one (rmcp) is gone, and the agent gets MCP tools through the one that remains.
One provider record. Provider id → family mapping was duplicated across several sites (three of which were missing nvidia and silently fell through to "custom"). It is now derived from a single source.
The two god files are split. lib.rs and the sidecar's index.ts are now dispatch-only, with the work in commands/ and boot.ts. The RSI code is subdivided by layer, and sandbox/ — which was really about network egress — is now egress/.
CI builds and tests the Rust half, on Linux and Windows, on every push. It used to be compiled for the first time *by the tagged release build*, which is how a broken EXPECTED_COMMAND_COUNT and a failing SSRF guard test both sat on main unnoticed. Both were caught the day the job landed.
Warnings are at zero across the workspace, build and clippy. Among the ones that turned out not to be cosmetic: a [profile.release] in src-tauri/Cargo.toml that Cargo was ignoring outright (the root workspace wins), so those release settings had never taken effect.
Safety smoke e2e tests (B5)
Four new FERAL_E2E-gated e2e files in FeralAgent/tests/, one per safety path the marketing copy promises:
l0-journal-tamper.e2e.test.ts — flip one byte in a chained journal file, assert verifyJournal flags the row AND defaultReadWindow excludes the file (failure surfaced, not silent drop). Negative control: same window accepts the file after the tamper is reverted.
l4-module-quarantine.e2e.test.ts — promote a deliberately- broken module id, fail-spawn maxStrikes times, assert registry re-pointed to builtin, module_quarantined row lands in the chained audit, last history row's actor is watchdog, post- quarantine invokes never spawn. Negative control: builtin-active path makes zero spawn attempts even with a faulty spawn stub.
l5-governance-fail-closed.e2e.test.ts — drives loadPolicy through every failure mode (missing / unparseable / G0-violation / valid) and asserts governanceCheck refuses every action under the fail-closed builtin (per-layer frozen).
l3-watchdog.e2e.test.ts — spawnSync's cargo test -p feral-core -- watchdog to wrap the 16 Rust watchdog unit tests into the e2e gate. The full Phase-3 rebuild cycle is out of scope per spec; the pure decision + persistence contracts are pinned. Default bun test skips all four (skip pattern mirrors fractal-scale.test.ts); run explicitly with FERAL_E2E=1 bun test FeralAgent/tests/*.e2e.test.ts. Granular tests already exist in rsi-seam-adapter.test.ts, rsi-governance.test.ts, rsi-governance-integration.test.ts, rsi-journal-chain.test.ts, and crates/feral-core/src/rsi/watchdog.rs; the e2e files are the assembled view a reviewer can read in 60 seconds.
HTTP API stability contract (B1, unstable pre-2.0)
Per-response X-Feral-Api-Stability: stable|unstable header. A single middleware in crates/feral-core/src/api.rs (api_stability_header) inspects the request path and tags every response. Stable prefixes are exclusively the third-party protocol compat: /api/* (Ollama) and /v1/* (OpenAI). Everything else — /runtime/*, /meta/*, /governance/*, /modules/*, /system_info, /providers/test, /tokenize, /events, the catalog reads — is unstable until v2.0. Header is set on 401s too, so clients can rely on it even before they auth.
9 new unit tests in crates/feral-core/tests/api_stability.rs pin the contract: stable on /api/* + /v1/*, unstable on every other routed path, present on auth failures, behavior preserved for the dynamic /runtime/models/download/:id route.
docs/API.md lists all 47 routes (47/47 — checked) grouped by operation class (read/evolve/govern) with stability tags.
scripts/check-api-docs.mjs greps api.rs::router() for every .route("/path", verb(...)) line, diffs against a fenced feral-api-routes block in docs/API.md, fails if any are missing. Wired into bun test via FeralAgent/tests/api-docs.test.ts.
Architecture overview
New ARCHITECTURE.md (B4 of docs/2026-07-09-v1-architecture-hardening-spec.md) at the repo root. The single map a senior contributor needs to self-orient:
The four runtimes (Desktop UI, Rust host, sidecar, TUI) and the three protocols (Tauri IPC, stdin JSON-lines, loopback HTTP).
L0–L6 layer model with file locations on both sides (FeralAgent/src/rsi/, crates/feral-core/src/rsi/, src-tauri/src/rsi/).
Phase ↔ L-layer ↔ spec doc ↔ code-path translation table, verified against git log --grep="Phase" on this branch.
Glossary of evocative terms (BRSI, ratchet, escape-time, recalcitrance, taste, champion-tree, FMS, seam, SandboxBounds, strikes, …) with owning file per term.
"Where do I add X" cheat sheet for provider / tool / connector / seam module / memory strategy. docs/CONTRIBUTING.md and docs/CONTRIBUTOR_GUIDE.md updated to link to the new file (no duplication of the runtime narrative).
Configuration documentation
New docs/CONFIGURATION.md (B2 of docs/2026-07-09-v1-architecture-hardening-spec.md). Catalogs all 95 FERAL_* env vars that source code reads, grouped by domain, with type/default for every var and an explicit threat note for every security-critical knob (FERAL_ENABLE_SHELL_EXEC, FERAL_ENABLE_CODE_EXEC, FERAL_ENABLE_DESKTOP_CONTROL, FERAL_DESKTOP_CONTROL_*, FERAL_DB_KEY, FERAL_AGENT_WORKSPACE, FERAL_WORKSPACE, FERAL_FETCH_DOMAINS, FERAL_HTTP_DOMAINS, FERAL_TRUSTED_BASE_URLS, FERAL_SHELL_WHITELIST, FERAL_PROACTIVE_ENABLED, FERAL_INNER_THOUGHTS_ENABLED, FERAL_JINA_API_KEY, FERAL_PII_REDACTION). The FERAL_WORKSPACE (TS list) vs FERAL_AGENT_WORKSPACE (Rust single path) trap is called out in its own section.
scripts/check-env-docs.mjs greps source for FERAL_* and diffs against a fenced feral-env-vars block in the doc. Wired into the bun suite via FeralAgent/tests/env-docs.test.ts — any new env var that isn't added to the doc fails CI.
Repository hygiene
Removed committed graphify output. graphify-out/ (16 files: graph JSON, cached chunks, generated HTML report) is no longer tracked. .gitignore now ignores the whole directory instead of only the cache/ subfolder — the dir is reproducible output of the graphify skill and should never be committed. The .gitignore patterns for tui/target/, tui/feral-tui.exe, target-check/, and data/ were already in place; this commit closes the graphify-out/ gap. No code or behavior changes; a fresh clone is a few hundred KB smaller.
Removed
Auto-load of the last model on startup. The Tauri host used to spawn a background task at app launch that read settings.last_loaded_model and reloaded the local model into RAM/VRAM before the user picked anything. For non-technical users this caused:
Visible lag at every app launch (model mmap takes seconds and consumes several GB; the machine visibly freezes).
Random crashes downstream — once the model was loaded at startup, PDF ingestion and longer messages had less RAM/VRAM headroom and hit OOM or Vulkan driver crashes.
Panic + close UX — users did not know why the app was frozen, so they killed it and reported Feral as unusable.
Now: the user picks a model explicitly from the Local Models tab (or the Onboarding wizard on first run). No background load at startup. No automatic persistence of the last-loaded path. The last_loaded_model and last_loaded_ctx fields are removed from Settings; the startup auto-reload task is removed from lib.rs; the persistence write in start_model_load is removed; the clearing write in unload_model is removed. Files touched: src-tauri/src/lib.rs, src-tauri/src/settings.rs.
This is a deliberate departure from the "remember so we don't bother the user" UX. For non-technical users, surprise is worse than friction: pick a model once per session, click Load, watch the progress bar.