Skip to content

Feral v2026.07.13

Choose a tag to compare

@github-actions github-actions released this 13 Jul 13:51

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.tsspawnSync's cargo test -p feral-core -- watchdog to 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.
      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/).
    • Faza ↔ L-layer ↔ spec doc ↔ code-path translation table,
      verified against git 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.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.