Releases: ferrumox/fox
Release list
v0.21.0
Three changes about being able to tell what the server is doing: a number for the
work its scheduler avoids, a document for what its interface promises, and a
model label so its metrics say which model an observation belongs to. The last
one renames every metric, which is why this is a minor rather than a patch.
And then a fourth, found by pointing a client at a server with real models on it:
the model listing endpoints were unusable on any machine with more than a few
gigabytes of GGUFs. Fixing that changes what digest means, which is Tier 1, so
it lands here rather than in a patch.
Changed
-
BREAKING (Tier 2): the Prometheus metrics move from
ferrumox_*tofox_*.
The binary, the CLI, the docs and everything a user types sayfox; the metrics
endpoint was the only place that saidferrumox. Renaming breaks every existing
dashboard, which is exactly why it happens now: after 1.0 the prefix is frozen and the
inconsistency would be permanent.Migration:
s/ferrumox_/fox_/in dashboards, alerts and recording rules. All thirteen
names change prefix only — type and meaning are identical.Per
COMPATIBILITY.mdthis is Tier 2: observable, changed on a
minor bump with a CHANGELOG entry and no promised deprecation window. -
Every metric now carries a
modellabel. Fox serves several models at once with
--max-models, and until now nothing on/metricssaid which one was responsible: a
saturated KV cache, a deep queue and a bad p99 all looked like properties of the server
rather than of one model inside it.The label could not be added without a cap. Model names are whatever the client asks
for —fox pullaccepts arbitrary HuggingFace repos — so the label set is influenced
from outside the server, and an unbounded one turns/metricsinto a memory leak that
every scrape then has to serialise. The cap is 32 distinct values per process; past that
everything collapses intomodel="<other>", with a warning emitted once per process.
Serving is never degraded by this.The cap counts models ever seen, not loaded at once: a load/evict/load cycle reuses
its slot instead of consuming a new one, so nobody can walk the limit upward by churning
models.Evicting a model retires its series. Counters could have been left alone — a monotonic
total that stops advancing is still true — but the gauges could not:
fox_kv_cache_usage_ratiofor an evicted model would sit at its last value forever, and
a dashboard would go on reporting a full KV cache for a model that no longer holds a
single block.scripts/e2e_smoke.pyread two of these metrics by line prefix and now sums the series
instead of keeping the last, which with more than one model loaded would have reported a
single model's drafting.A side effect of labelling, checked against a real server: a metric no longer appears
on/metricsuntil its first observation. Previously, unlabelled, all thirteen were
registered at startup and always emitted at zero. Now a series exists once something
touches it, so a freshly started server shows only the three gauges the engine loop
refreshes, andfox_requests_totaldoes not appear until the first request finishes.
This is normal Prometheus behaviour for labelled metrics, but a panel that assumed
"always present, possibly zero" now gets an empty result: useor vector(0)in those
queries. -
BREAKING (Tier 1): the
digestin/api/tags,/api/psand/api/showis derived
from the model file's name, size and mtime, not from its contents. It is still
sha256:<hex>and still changes whenever the file is replaced, but it is now an opaque
identifier rather than a content hash.This is the fix for the hang below, not a cosmetic change: a digest that is a content
hash cannot be produced without reading every byte of the file, and there is nowhere on
a listing request to put that work. Ollama can report a real hash because its blobs are
content-addressed and hashed once at pull; fox stores plain GGUF files in a directory
that users also drop models into by hand.Nothing in fox resolves a model by digest — it identifies, it does not address — and
/api/pullalready emittedsha256:<filename>rather than a content hash, so no fox
client could have been verifying one. PerCOMPATIBILITY.md,
changing a field's meaning is Tier 1 and belongs on a minor bump with an entry saying
so. This is that entry.
Fixed
-
GET /api/tagsno longer hangs with a core pinned at 100%. It computed the SHA-256
of every.ggufin the models directory before it could answer: measured on a 27 GB
directory, 51.6 s for the first call./api/psand/api/showdid the same.What turned a slow endpoint into an apparently dead server is that the digest cache was
only written once a hash finished, and identical work in flight was never shared. Each
retry — acurlre-run, an Open WebUI refresh, a page reload — started a full re-hash of
the whole directory on another blocking thread. Retrying, the natural response to no
response, is what saturated the CPU.GET /stayed instant throughout, because it
touches no disk, which made the server look up rather than stuck.The same directory now answers in 22 ms, and eight concurrent requests complete in 30 ms
total./api/psadditionally re-read the models directory once per resident model; it
now reads it once. -
GET /healthno longer loads a model to answer. It calledget_or_load, so the
liveness probe blocked for as long as a multi-gigabyte load took —curl -m 3against
a server whose model was not resident simply timed out — and it could cause a load,
which under the default--max-models 1evicts whatever is serving traffic. An
orchestrator polling/healthduring startup gets a timeout and restarts the process
before it ever finishes loading: the probe becomes the outage. Found while verifying an
unrelated fix on a server holding a 10 GB model.It now reports residency instead of establishing it, answering in 6 ms, and does not
count as a use — a probe that refreshed the LRU would keep a model resident forever and
--keep-alive-secswould never fire. The response gainsmodel_loaded, because
otherwise "not loaded yet" and "loaded and idle" are the same body; that state was
nearly unreachable while the handler loaded on demand and is now the normal one before
the first request. -
Diffusion models are refused at load instead of served as gibberish. LLaDA, Dream,
RND1 and the rest do not generate left to right — they unmask a sequence over a fixed
number of steps, which is why llama.cpp ships a separatediffusiontool for them.
fox's decode loop is autoregressive, and loading one anyway did not fail: it produced
replies with mask tokens (<|mask_start|>) embedded in them, fragments out of order,
duplicated spans and truncation. Reported as an output-formatting bug, which is exactly
what it looks like from the client side.llama_model_is_diffusion()is now checked
after load and the model is rejected with an explanation. -
An unrecognised
--type-kvor--split-modevalue is an error, not a shrug. Both
parsers answered anything they did not recognise with the default and no message, so
type_kv = "turbo3"inconfig.tomlquantised nothing, said nothing, and left the
operator believing the setting had applied. A config file has no completion and no type
checking; this warning was the only feedback available and it was not being given.
Rejected at startup now, before anything loads, naming the accepted values. -
A missing GPU dependency no longer stops fox compiling at all.
build.rsenabled
the Vulkan backend on the strength of any single signal —VULKAN_SDKset, orglslc
onPATH, orvulkan.hpresent. ggml-vulkan then opens with
find_package(Vulkan COMPONENTS glslc REQUIRED)andfind_package(SPIRV-Headers CONFIG REQUIRED), and a missing one of those is a fatal CMake error rather than a fallback.
So detecting half a toolchain did not produce a CPU build, it failed the whole cargo
build — and with no way to turn Vulkan off, the user could not build fox at all. Reported
from Windows with a LunarG SDK 1.3.246, which shipsglslcand the loader but no
SPIRV-HeadersConfig.cmake.All three pieces are now checked before the backend is switched on, on Linux and
Windows alike, and a partial toolchain produces a CPU build plus a warning naming what
is missing and how to install it.FOX_NO_VULKAN=1forces it off,FOX_FORCE_VULKAN=1
forces it on — the second doubling as the way to re-run the check, since installing a
package does not invalidate a build script.GGML_VULKAN=OFFis now passed explicitly instead of being left unset. CMake caches the
switch intarget/, so once any build had configured with it ON, every later build in
that tree inherited ON regardless — someone who hit this and then fixed their toolchain
would have kept failing, withcargo cleanand a full llama.cpp rebuild as the apparent
only cure. -
Releases are published with notes.
softprops/action-gh-releasewas only ever
handed files, so every release page was a bare list of six assets and nothing else.
Two of those assets are tarballs differing by a-vulkansuffix, which meant the page
never said that a GPU build existed or which file to take — and the one without a
suffix reads as the default. It cost someone a bug report:libggml-vulkan.solooked
missing from the release, when it was in the other tarball all along.The body is now this version's CHANGELOG section (
scripts/release_notes.py) plus a
table of which download is which, how to verify it, and the note thatfox probe
reports the backend actually in use. Written by a job that runs after both ...
v0.20.5
v0.20.4
v0.20.3
v0.13.0
release: v0.13.0 fox becomes a real server under concurrent, long-prompt, long-conversation load. The three serving-robustness gaps from the 0.12 capabilities checklist are closed (docs/design/serving-robustness.md): chunked prefill breaks a long prompt into per-step chunks so it interleaves with other requests' generation instead of head-of-line-blocking the engine loop; context rolling discards the oldest KV window when a conversation fills n_ctx so generation continues instead of stopping with length; and the Jinja chat template is compiled once and cached instead of re-parsed on every request. A new fox bench-prefill quantifies the chunked-prefill win, and every change rides the 0.12 regression net (golden tests in CI + the scheduler conservation stress test). Squash-merge of develop @ 37db0f4 into main (Git Flow release). First parent: v0.12.0 (keeps main's release line); second parent omitted — this history was rewritten to one clean commit per release (see git-history-squash, 2026-07-06).
v0.12.0
[0.12.0] - 2026-07-06
GPU inference becomes a first-class, reproducible path, and the model-architecture
rework (started in 0.11) is finished off. Vulkan is validated end-to-end on an AMD
Radeon 890M (gfx1150, RDNA 3.5) and shipped three ways — a Docker image, a prebuilt
release tarball, and a make vulkan bundle — with fox now reporting the active
backend at startup. On the correctness side, P4 (API consistency) lands, and the
rework's regression net is wired up for real: golden tests run in CI against a live
model, and a stress test settles the last open question (§7) by proving the prefix
cache doesn't leak.
Added
Dockerfile.vulkan— a reproducible Vulkan build for AMD/Intel iGPUs and any
Vulkan-capable GPU (no CUDA/ROCm). Validated end-to-end on an AMD Radeon 890M
(gfx1150, RDNA 3.5): coherent output, GPU-accelerated, both by extracting the
binary to run natively and by running the image with--device /dev/dri. The image
ships the Mesa Vulkan driver and falls back to CPU when no GPU is present.
CONTRIBUTING documents the GPU-build story, including the exact toolchain
(glslc,glslang-tools,libvulkan-dev,spirv-headers) and the
build-in-container / run-on-host split.- fox reports the active compute backend at startup.
fox run,fox serve(in
the log) andfox probenow show whether inference runs on the GPU (e.g.
Vulkan0 — AMD Radeon 890M) or the CPU, read from the ggml device registry —
closing the "is it actually using my GPU?" gap. Exposed onModelInfo.backend. - Prebuilt Vulkan binary in releases —
release.ymlnow builds a
x86_64-unknown-linux-gnu-vulkantarball (on Ubuntu 24.04) alongside the CPU one,
so GPU users get a ready-to-run binary. The Vulkan tarball needs glibc 2.39+ and a
Vulkan driver (Mesa RADV/ANV, etc.) at runtime. make vulkan— builds theDockerfile.vulkanimage and extracts the bundle
(fox,fox-bench,libggml-vulkan.so) to./fox-vulkan/, so you get a
GPU-enabled binary that runs natively on any host with a Vulkan driver — no build
toolchain needed on the host.- Golden tests now run in CI — a new
goldenjob builds llama.cpp for real (the
only CI job that does; the rest stay on the fast stub) and runs the golden suite
against a tiny GGUF (Qwen2.5-0.5B) on CPU:ModelInfoinvariants, non-degenerate
embeddings, and tokenize round-trips on emoji/CJK. The model and the llama.cpp build
are cached so it only pays the full cost when either changes. This wires up the
regression net that P0 built but only ran locally. - Prefix-cache leak stress test (
scheduler::tests::stress_prefix_cache_no_leak) —
settles the last open question of the model-architecture rework (§7). It drives 400
admit/finish/cache/hit/refuse-when-full cycles and asserts, after every step, that
every seq_id and KV block is owned by exactly one of {pool, running request, cache
entry} — never dropped, never duplicated — and that allocation returns to zero after
draining. Confirms the prefix cache does not leak (the initial automated flag was
a false positive). AddsKVCacheManager::allocated_blocks()for the assertion.
Changed
- Sampling defaults centralized (
src/api/shared/sampling_defaults.rs) — the
per-request defaults were duplicated as magic literals across the OpenAI and Ollama
handlers. They now live in one table keyed by API surface, with the cross-surface
divergence documented as a deliberate decision: the OpenAI surface (/v1/*)
mirrors OpenAI (notop_k, no repeat penalty) while the Ollama surface (/api/*)
mirrors upstream Ollama (top_k = 40,repeat_penalty = 1.1). A unit test locks
the divergence so it can't be "unified" by accident. (Model-architecture rework P4.)
Fixed
- API docs listed the wrong sampling defaults.
docs/api/{openai,ollama}.md
claimedtemperature = 1.0/top_p = 1.0(actual:0.8/0.9) and the Ollama
page showedtop_k = 0/repeat_penalty = 1.0when fox actually applies Ollama's
40/1.1. Corrected, with a note explaining the deliberate/v1vs/api
divergence. --max-modelshelp now states the default-1 trade-off — a request for a second
model evicts the first (logged), which is the safe choice for small-VRAM iGPUs;
raise it if you have the VRAM.
v0.11.0
[0.11.0] - 2026-07-03
Model-architecture correctness rework (see
docs/design/model-architecture-rework.md) — makes per-model facts a single
inspectable source of truth and closes several "fix one model, break another" gaps.
Added
fox probe <model>— loads a model and prints its resolvedModelInfo
(architecture,n_embd, head counts,head_dim, layers, trained context, EOS,
embedded-template presence, native-thinking/seq-copy, recommended sampling), then
flags contradictions between the model's metadata and the formulas fox uses.
Unlikefox show(which guesses from the filename), probe reads the truth.ModelInfo— one inspectable snapshot of a loaded model's facts, the basis of
the rework and offox probe.- Golden regression tests (
make golden GOLDEN_MODEL=<path.gguf>) — real-model
assertions (ModelInfo invariants, non-degenerate embeddings, tokenize round-trip)
that lock in the fixes below. Gated to real builds; the stub CI is unaffected. - Community-health files —
CODE_OF_CONDUCT.md, issue templates (bug/feature)
and a pull-request template.Cargo.tomlgains package metadata (repository,
homepage,documentation,keywords,categories).
Changed
-
CI/CD workflows simplified to stop failing. The Docker and Release workflows
dropped their fragile multi-platform matrices (arm64/CUDA, ROCm apt bundle,
aarch64 cross, Windows+Vulkan, macOS) — which failed often — for a single reliable
linux/amd64build; Docker now builds+pushes directly (no push-by-digest/manifest
merge). The redundanttest-linux-buildworkflow (only ran on-testtags, pinned
rotting ROCm versions) was removed. GPU users can use the Docker image or build
locally; platforms will be re-added once each is verified in isolation. -
Chat prompts now execute the model's real Jinja template (via
minijinja+
minijinja-contribpycompat) instead of llama.cpp's simplified built-in format,
and tokenize the result with the template's own BOS (add_special=false, no
double BOS) and real control tokens (parse_special=true, not literal text). The
prompt now matches what each model was trained on. Falls back to the built-in
format when a model has no embedded template or it fails to render. -
Thinking/reasoning is now opt-in and correctly detected.
supports_thinking
recognizes models whose chat template exposes anenable_thinkingtoggle
(Gemma-4, Qwen3), not just the<think>-token heuristic —fox probenow reports
Native thinking: yesfor Gemma. Thinking activates only when the request opts in
(OpenAI: athink: trueextension field; Ollama: the existingthinkfield) and
threadsenable_thinkinginto the model's Jinja template; the default is off (no
reasoning latency unless asked). Note: clean separation of reasoning from the
answer works for both<think>-delimited models (Qwen3, DeepSeek-R1) and
channel-format models (Gemma's<|channel>/<channel|>): the output filter AND
the API-layer thinking extraction read each model's reasoning markers via
Model::reasoning_delimiters, detected from the model's OWN chat template through
a small documented format registry (REASONING_FORMATS) — never the model name.
Supporting a new reasoning format is one registry line plus a golden test.
Tool-calling through the template remains a follow-up.
Fixed
- Embeddings returned an all-zeros vector for every model. The generation
context usespooling_type = NONE, sollama_get_embeddings_seqreturned NULL and
fox served a zero vector./v1/embeddingsand/api/embednow mean-pool the
per-token embeddings (llama_get_embeddings_ith) and L2-normalize the result. n_embdwas reconstructed asnum_heads * head_dim, wrong for Gemma/MLA-class
models (head_dim != n_embd/n_head). This produced wrong-length embeddings and an
out-of-bounds read of the embedding buffer.n_embdis now read from
llama_model_n_embdand stored onModelConfig.- The KV block pool was sized from an independent formula that could disagree with
the backend's realn_ctx(and is wrong for shared/SWA KV, MLA and recurrent
models), letting fox over-claim KV and crashllama_decodeunder load. The serving
paths now size the pool fromllama_n_ctxso it follows the backend exactly. frequency_penalty/presence_penaltywere accepted but silently ignored.
They are now applied in the sampler with OpenAI semantics
(logit -= presence*(seen) + frequency*count), threaded from the request. Default
0.0 (disabled).- Misleading load-failure message.
diagnose_load_failureasserted "not enough
memory" on any failure (its condition was almost always true), even when the real
cause was a missing compute backend. It now claims OOM only when free memory is
actually below the model size, and otherwise lists the real possible causes. - Image/audio content is no longer dropped silently. The OpenAI handler now
warns when a request carries non-text content blocks (fox has no vision/audio
support).--swap-fractionis documented as reserved/not-yet-implemented rather
than appearing to do something.
v0.10.0
[0.10.0] - 2026-06-30
Re-baselines the project after retracting a premature 1.0.0. The version line continues at 0.10.x and will reach 1.0.0 only when the engine is proven stable. This release also migrates the vendored llama.cpp to upstream and removes TurboQuant.
Changed
- Vendored llama.cpp now tracks upstream (
ggml-org/llama.cpp@b9842). Previously the
submodule pointed at a long-lived fork that carried the TurboQuant patches and had drifted
~1,200 commits / 3 months behind upstream. Tracking upstream restores binding/library version
parity, lets a clean--recurse-submodulesclone fetch the pinned commit, and unblocks newer
model architectures (e.g. Gemma 4) without maintaining a fork. The migration required only a
build-flag fix inbuild.rs(LLAMA_BUILD_APP/UI=OFF) — zero FFI changes.
Removed
- TurboQuant KV cache quantization (
turbo2/turbo3/turbo4). The fork's custom GGML
type IDs collided with upstream's (e.g.Q1_0=41vsTURBO3_0=41), making it impossible to
follow upstream while keeping TurboQuant. Compatibility with upstream llama.cpp was prioritized.
KV cache quantization remains available via the standard llama.cpp typesf16,q8_0and
q4_0. Configs or commands referencingturbo*KV types must switch to one of these.
v0.9.0
[0.9.0] - 2026-03-15
Added
-
Multi-Model support —
ModelRegistryloads and serves multiple models simultaneously
with LRU eviction.- New
src/model_registry.rs:ModelRegistry,EngineEntry,RegistryConfig. GET /api/psnow lists all currently-loaded models (previously only the one model).GET /v1/modelsnow lists all.gguffiles inmodels_dir(not just the loaded one).- Each inference/embedding request is routed to the correct engine based on the
modelfield;
unknown models return HTTP 404. DELETE /api/deletenow also unloads the model from the registry if it was loaded.
- New
-
--max-modelsflag (FOX_MAX_MODELSenv var, default1) — maximum number of models
kept in memory simultaneously; excess models are evicted LRU-first. -
--alias-fileflag (FOX_ALIAS_FILEenv var) — optional TOML file mapping short names
to model stems (e.g."llama3" = "Llama-3.2-3B-Instruct-f16").
Default path:~/.config/ferrumox/aliases.toml.
Changed
AppStatereplacesengine: Arc<InferenceEngine>withregistry: Arc<ModelRegistry>+
primary_model: String. Backward-compatible:fox serve --model-path X.ggufworks unchanged.router()signature updated accordingly.- Engine run-loop is now started inside
ModelRegistry::get_or_loadand aborted automatically
on LRU eviction viaDroponEngineEntry.
[0.8.0] - 2026-03-15
Added
-
Embeddings API — unlocks RAG pipelines (LangChain, LlamaIndex, Open WebUI RAG, etc.)
POST /v1/embeddings— OpenAI-compatible endpoint; acceptsinputas a string or array
of strings, returnsdata[].embeddingvectors.POST /api/embed— Ollama-compatible endpoint; returnsembeddings: [[f32]].InferenceEngine::embed()async method;Model::get_embeddings()+Model::embedding_dim()
trait methods with fullLlamaCppModelimplementation viallama_set_embeddings/
llama_get_embeddings_seqFFI and stub fallback.- New types:
EmbeddingInput(untagged enum for String/Vec),EmbeddingRequest,
EmbeddingObject,EmbeddingUsage,EmbeddingResponse,OllamaEmbedRequest,
OllamaEmbedResponse.
-
POST /api/pullwith SSE streaming — download models from HuggingFace Hub via the
server API, identical to Ollama's pull flow.- Emits newline-delimited JSON events:
pulling manifest→downloading(withdigest,
total,completedbytes) →verifying sha256 digest→success. - Automatically selects Q4_K_M quantization when available, otherwise picks the first GGUF.
- New
--hf-tokenflag onfox serve(alsoHF_TOKENenv var) forwarded to pulls. - New
AppState.hf_tokenfield; new filesrc/api/pull_handler.rs. - New types:
PullRequest,PullStatus.
- Emits newline-delimited JSON events:
-
Release binaries +
install.sh— one-command installation..github/workflows/release.yml— triggered onv*tags; builds for four targets:
x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,x86_64-apple-darwin,
aarch64-apple-darwin. Uploads tarballs as GitHub Release assets.install.sh— detects OS + arch, downloads the correct tarball, installs to
/usr/local/bin/fox(configurable via--prefix).fox.service— systemd unit for runningfox serveas a daemon.
Changed
Cargo.toml: version bumped to0.8.0.src/api/routes.rs:router()now takes an extrahf_token: Option<String>parameter.src/cli/serve.rs:ServeArgsgains--hf-token/HF_TOKEN.
[0.7.0] - 2026-03-14
Added
-
Ollama-compatible API layer (
src/api/routes.rs,src/api/types.rs)GET /api/tags— lists all.ggufmodels in~/.cache/ferrumox/models/with name,
size, SHA256 digest, architecture family, quantization level, andmodified_attimestamp.
Open WebUI and Continue.dev use this endpoint to discover available models.GET /api/ps— returns the currently loaded model with real file size (bytes) and
SHA256 digest looked up from disk.POST /api/show— returns detailed metadata for a named model: architecture family,
quantization, human-readable size, digest, modification date, and file path.DELETE /api/delete— removes a.gguffile from the models directory by model name
or filename. Returns404if the model is not found.- New response types:
OllamaModel,OllamaDetails,TagsResponse,PsEntry,
PsResponse,ShowRequest,ShowResponse,DeleteRequest. - SHA256 digest computed once per file via
sha2+hexand cached inAppState
(Arc<Mutex<HashMap<PathBuf, String>>>). Subsequent requests for the same file return
instantly. - New dependencies:
sha2 = "0.10",hex = "0.4".
-
models_diradded toAppState(src/api/routes.rs,src/cli/serve.rs)router()now accepts amodels_dir: PathBufparameter (default:
~/.cache/ferrumox/models) used by the Ollama-compat handlers.src/cli/show::parse_architectureandparse_quantizationpromoted topub(crate)
so they can be reused by the API layer without duplication.
Compatibility
With v0.7.0, Open WebUI and Continue.dev work out of the box by pointing their
Ollama URL to http://localhost:8080. No other configuration change is required.
v0.6.0
[0.6.0] - 2026-03-13
Added
-
CLI visual overhaul — minimalista con color (
src/cli/theme.rs, all CLI modules)- New
src/cli/theme.rsmodule centralises all ANSI styling. RespectsNO_COLORand
non-TTY contexts (pipes, CI) — every helper silently falls back to plain text. - New direct dependency:
crossterm = "0.28". fox runloading spinner — replaces the static"Loading model… done."line with a
cyan Braille spinner (indicatif) that clears itself and prints✓ Model loaded.
(bold green) on success.- REPL banner — after load, prints
🦊 <model name>(bold white), a dim separator
and a dim hint line (/bye o Ctrl+D para salir · N tokens). - Prompt glyph —
❯(bold cyan) replaces"You: ". - Thinking spinner — a dim Braille spinner labelled
"Thinking…"runs while the model
generates; cleared on the first emitted token. - Role label —
Fox(bold yellow) is printed once to stderr immediately before the
first token, producingFox <streamed response>inline. - Per-turn timing — dim
N tokens · X.Xsline printed after each assistant turn. fox list— table header bold, separator dim, SIZE column blue, MODIFIED dim.fox ps— table header bold, separator dim; STATUSok→ bold green; KV cache
usage colour-coded (green < 50 %, yellow < 80 %, red ≥ 80 %).fox show— all key/value rows usetheme::print_kv_pair(key bold+dim, padded).fox pull— post-download success line uses✓ Saved to …(bold green); hint
lines forfox run/fox serveare dimmed.fox serve— prints🦊 <model> · listening on <addr>(green) to stderr when
the server is ready.
- New
-
Interactive REPL mode for
fox run(src/cli/run.rs)- Running
fox run --model-path model.ggufwithout a prompt now opens a conversational chat session. - Full message history is maintained across turns: each new turn sends the complete history through
apply_chat_template, giving the model proper context. - Exit commands:
/bye,/exit,exit,quit, or Ctrl+D (EOF). - Existing one-shot behavior (
fox run --model-path model.gguf "prompt") is fully preserved. - The engine loop stays alive across turns; no model reload between messages.
- Running
Changed
- Project renamed from
ferrum-enginetoferrumox— the CLI binary is nowfox, the benchmark binary isfox-bench.- All environment variables renamed from
FERRUM_*toFOX_*(e.g.FOX_MODEL_PATH,FOX_PORT). - Model cache directory changed from
~/.cache/ferrum/modelsto~/.cache/ferrumox/models. - Prometheus metric names updated from
ferrum_*toferrumox_*. - Build stub flag renamed from
FERRUM_SKIP_LLAMAtoFOX_SKIP_LLAMA. - Docker image tag changed from
ferrum-engine:latesttoferrumox:latest.
- All environment variables renamed from