Feral v2026.07.13
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 chatopens 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_COLORis 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,Escinterrupts 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
--plainmode 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 aferal-corecrate that both the desktop app and a headless
gateway boot through the same way — one runtime, several faces. - A
feralcommand-line tool. Gateway lifecycle (start/stop/status),
feral doctor, model management, logs, connectors, dreams, config, shell
completions, and--jsonon everything for scripting. Plainferalin a
terminal opens chat. - A public runtime HTTP API on loopback:
/runtime/*for reads and
actions,POST /runtime/chatfor streaming chat over SSE (cloud keys work
headlessly), and/eventsas a live SSE feed of what the runtime is doing.
Stability is declared per route — see the API stability contract below. - One
feralto 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, inferal setup(with
--classicfor 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 whatsappand/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 fromlatest.jsonand 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
remembertool, so the agent can write to memory directly instead of
waiting for the asynchronous extractor.recallsearches facts too. FERAL_HOMEis 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_getalways 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 forweb_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, andgovernance verifywalks 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_urland
http_requestare always registered with open egress (behind an SSRF guard,
a rate limit and an audit trail);FERAL_FETCH_DOMAINS/
FERAL_HTTP_DOMAINSnow restrict rather than enable. Workspace roots
default to the launch directory plus your home, with a hard deny wall on
~/.feral(except scratch),~/.sshand anything inFERAL_FS_DENY. - New
connectors_manageandproduct_infotools, 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, andhttp://[::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.
.npmrcis now
ignored. -
Dependency advisories:
plist1.9 → 1.10 andquick-xml0.39 → 0.41
(two high-severity RUSTSEC advisories),crossbeam-epoch0.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, honouringRetry-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_limitedevent, 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 withFERAL_RATE_LIMIT_RPMif 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, anddocs/CONFIGURATION.mdis 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 missingnvidiaand silently fell through
to "custom"). It is now derived from a single source. - The two god files are split.
lib.rsand the sidecar'sindex.tsare
now dispatch-only, with the work incommands/andboot.ts. The RSI code is
subdivided by layer, andsandbox/— which was really about network egress —
is nowegress/. - 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 brokenEXPECTED_COMMAND_COUNTand a failing SSRF guard test both
sat onmainunnoticed. 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.tomlthat 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 inFeralAgent/tests/,
one per safety path the marketing copy promises:l0-journal-tamper.e2e.test.ts— flip one byte in a chained
journal file, assertverifyJournalflags the row AND
defaultReadWindowexcludes 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-spawnmaxStrikestimes, assert registry
re-pointed tobuiltin,module_quarantinedrow lands in the
chained audit, last history row's actor iswatchdog, 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— drivesloadPolicy
through every failure mode (missing / unparseable / G0-violation
/ valid) and assertsgovernanceCheckrefuses every action under
the fail-closed builtin (per-layer frozen).l3-watchdog.e2e.test.ts—spawnSync'scargo test -p feral-core -- watchdogto wrap the 16 Rust watchdog unit tests
into the e2e gate. The full Faza-3 rebuild cycle is out of scope
per spec; the pure decision + persistence contracts are pinned.
Defaultbun testskips 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 inrsi-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|unstableheader.
A single middleware incrates/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 — isunstableuntil 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/:idroute.
- 9 new unit tests in
docs/API.mdlists all 47 routes (47/47 — checked) grouped by
operation class (read/evolve/govern) with stability tags.scripts/check-api-docs.mjsgrepsapi.rs::router()for every
.route("/path", verb(...))line, diffs against a fenced
feral-api-routesblock indocs/API.md, fails if any are
missing. Wired intobun testvia
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/). - Faza ↔ L-layer ↔ spec doc ↔ code-path translation table,
verified againstgit log --grep="Faza"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.mdanddocs/CONTRIBUTOR_GUIDE.mdupdated to
link to the new file (no duplication of the runtime narrative).
- The four runtimes (Desktop UI, Rust host, sidecar, TUI) and the
Configuration documentation
- New
docs/CONFIGURATION.md(B2 of
docs/2026-07-09-v1-architecture-hardening-spec.md). Catalogs all
95FERAL_*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) vsFERAL_AGENT_WORKSPACE(Rust single
path) trap is called out in its own section. scripts/check-env-docs.mjsgreps source forFERAL_*and
diffs against a fencedferal-env-varsblock in the doc. Wired
into the bun suite viaFeralAgent/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..gitignorenow ignores the whole directory instead of
only thecache/subfolder — the dir is reproducible output of the
graphifyskill and should never be committed. The .gitignore
patterns fortui/target/,tui/feral-tui.exe,target-check/,
anddata/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 readsettings.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_modelandlast_loaded_ctxfields are removed from
Settings; the startupauto-reloadtask is removed fromlib.rs;
the persistence write instart_model_loadis removed; the
clearing write inunload_modelis 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. - Visible lag at every app launch (model mmap takes seconds