Docs menu›

Changelog

All notable changes to hedos are documented here. Each release section below is what ships as the GitHub Release notes.

v1.5.3 - 2026-10-14#

  • hedos upgrade brings hedos up to its newest release, the way it was installed (#32). A Homebrew install is upgraded with brew upgrade theiskaa/tap/hedos, and a cargo install with cargo install hedos --version <newest> into the root it was installed to, so each tool's record of what is installed stays true, and the new binary's version is checked afterwards (brew finds nothing newer until the formula catches up with the release, and is not reported as an upgrade). A binary from the install script or a release archive is replaced in place: the release archive for this platform is downloaded, checked against its published SHA-256, run to confirm its version, and moved over the old binary in a single rename. A checksum that does not match, a binary that reports another version, or a Ctrl-C installs nothing. A source build is left alone, with a note to pull and rebuild. --check only says whether a newer release is out and how it would be installed, and --json reports the current and newest versions, the install channel, the binary's path and whether it upgraded.
  • A command that runs to its end, or fails, ends with one line on stderr when a newer release is out: hedos 1.5.4 is available (you have 1.5.3). Run `hedos upgrade` to update. No command waits on the network for it: at most once a day a detached process looks up the newest release and remembers it in upgrade-check.json in the data directory, and the notice reads only that. It stays off under --json, when stderr is not a terminal, with HEDOS_NO_UPDATE_CHECK set to anything but an empty value, 0 or false, or with the new [advanced] check_for_updates = false.

v1.5.2 - 2026-10-12#

  • Audio generation (#30). A new audio capability covers models that make songs, music or sound effects from a description. hedos audio <model> <prompt> writes the piece to a FLAC or WAV file, and POST /v1/audio/generations (also at /v1/audio/music-generations, with the request fields mlx-serve's music API uses) returns the file, or streams progress events ending with the file when stream is set. Each request is checked against what the model takes, so lyrics sent to a model that cannot sing are a 400 naming what it does take. A client that disconnects cancels its job.
  • A built-in python:audio runtime serves the models diffusers and transformers run by themselves: Stable Audio Open, AudioLDM, AudioLDM2, MusicLDM, ACE-Step 1.5 (which sings from lyrics) and MusicGen.
  • YuE2 writes whole songs with vocals from a style prompt and lyrics, at 48 kHz stereo, on a shipped python:yue2 runtime. The runtime installs YuE2's own package from its repo, pinned by commit and hash. Pull m-a-p/YuE2-3B, then hedos runtimes approve python:yue2, which offers to pull its decoder, m-a-p/YuE2-Vae.
  • A manifest runtime can name a companion: a second repo its models need, such as a decoder. hedos finds it on the shelf, passes it to the runtime and counts its size in the model's fit. A model whose companion is missing is refused with the hedos pull that fetches it, and hedos ls and the shelf's model card say the same. Manifests can also declare the parameters a request may carry and detect a model by the model_type in its config or its diffusers pipeline class. docs/configuration.md walks through bringing another audio model this way, with no change to hedos.
  • Voices and generators are told apart. Speech checks run first, so Kokoro is unchanged, and the model card's tags now count: Orpheus and OuteTTS (voices) and YuE2 (a generator) are no longer listed as chat models. hedos pull keeps a repo's README.md so its card can be read. The speech runtime takes only Kokoro, the one model its sidecar can load, and each command and route points a model of the other kind to the right one. A shelf that got any of these wrong is corrected the first time it is read, with no rescan.
  • hedos recommend and both pull surfaces have an audio kind: MusicGen-small and ACE-Step 1.5 XL turbo.
  • A job that fails because of the request itself, such as YuE2 sent no lyrics, is a 400 at the gateway rather than a 500. hedos image writes the image in the format the model made, not always as .png.
  • A manifest's detect rule must use exactly one form. A rule that set two (such as file beside extension) used to load with part of it ignored, and is now refused.

v1.5.1 - 2026-10-10#

  • hedos recommend reads this machine and recommends models to pull for it (#26). It shows the chip, memory, free disk, how much memory a model may use and which engines are installed, then picks up to three models each for chat, code, voice and image: the largest that run well here. A missing engine keeps the same picks and says what to install first. --kind narrows the list, --all shows every catalog model and why it is or is not a pick, and --json carries all of it.
  • Fit follows the hardware a model runs on rather than total memory. On Apple Silicon that is Metal's working set, the share the GPU may take (51.8 GiB of 64 on an M5 Pro); on NVIDIA and AMD cards it is their memory, with a larger model judged against all of memory since Ollama and llama.cpp run the rest from there; elsewhere it is all of memory. hedos ls, the shelf, hedos scan, hedos bench and both pull surfaces judge the same way.
  • The install catalog is current: gemma4, qwen3.5, qwen3.8, gpt-oss and qwen3-coder for chat and code, Kokoro for speech, SDXL and SDXL Turbo for images, every size checked against its registry. The hedos pull picker and the shelf's pull screen offer the same picks as hedos recommend.
  • Tessera's contact extraction runs on the shelf (#27). A shipped cli:tessera runtime serves it through POST /v1/extract, hedos run and an extract mode on the try screen. hedos does not link Tessera: approving the runtime downloads the release it pins and checks it first.
  • Manifest runtimes can read their request on stdin, report a bad request with exit status 2 (a 400 at the gateway), and pin a release to download per platform.
  • The shelf draws on the terminal's own background, light or dark, and its header carries the gateway's pulse: whether it is on, its requests a minute, and the last day's traffic. A paste arrives whole instead of being read as keys.

v1.5.0 - 2026-10-08#

Decision models are served, and the shelf is redrawn. A GGUF that answers typed questions (clef, OpenJev, Kev, lev, Laya, Julia-1) is a judge now, served by llama.cpp over its own /v1/systemone and reachable from hedos run, hedos warm, the gateway and the shelf, and the ones that see take images. Getting there fixed llama.cpp vision for every model, which had never been handed its projector. The shelf has a new face, with try and pull as screens of their own. The rest of the release closes the open bugs: pulls that stop when told and read the same everywhere, models sized by what serving them loads, GGUF embedders served, a gateway that stops cleanly on any signal, and a scan that says what it found.

  • The shelf is redesigned (#25). On a terminal of at least 96 by 40 the header is a hero: the koala, the hedos wordmark in pixel type, and the shelf in big figures (models, warm, gone, free memory, gateway); a smaller one keeps a single summary line. The body is a set of rounded cards on a ground the shelf paints itself, in greys with brightness as the accent and hue kept for state. t opens a full-screen try screen in the shelf's place, with a session card beside the transcript, and on a judge it composes a typed question and lays the answer out as a distribution. p opens a full-screen pull screen with search, results, a preview and the downloads in flight. The screen moves to mark a change (figures easing, names arriving, cards opening) and keeps a few quiet signs of life; HEDOS_MOTION=off stops all of it and slow plays it ten times slower. docs/shelf.md is a new guide to the whole screen, and the other guides were rewritten against the code.
  • Stopping a pull is honoured and reported (#13). Registering a finished pull has a time limit (pull.register_timeout_seconds, 120 s by default), so a stuck scan can no longer hang it. pull pause and pull cancel say whether the stop landed, is still pending, or came too late (exiting non-zero for too late, and in --json as outcome), and a pause never overrides a waiting cancel.
  • A pull reads the same everywhere (#14). pull ls, its --json, the pulls screen, attach, resume --all and the shelf's background resume share one reading, so an abandoned pull is interrupted on all of them, and one whose model was pulled again is failed instead of offering a resume that cannot work. A damaged record reads unreadable, and nothing resumes, stops or overwrites it.
  • A model is sized by what serving it loads (#15). A Hugging Face repo holding several quantizations was judged by all of them at once, so fit, warm, bench, the memory governor and /api/tags could call a 9 GB model a 29 GB one. The shelf now shows the serving size, with the on-disk size kept for the machine pane, removal and scan totals. A pull's total is marked partial when the listing leaves a file without a size.
  • Loose ends in the pull surfaces (#16). Every scanner shares one rule for what a GGUF weight is, so a .gguf link to a folder or to nothing is no longer a model. The shelf plans pulls with your settings, and its pause, cancel and resume no longer stall the screen on a slow disk.
  • A GGUF that only embeds is served (#12). llama.cpp now runs embedding models through llama-server --embedding, on /v1/embeddings, /api/embed and /api/embeddings. Qwen3-Embedding, which was served as a chat model, embeds now, and /v1/models reports the longest input each llama.cpp embedder takes (8191 tokens for Qwen3). Rerankers, encoders with no pooling and decision models are no longer mistaken for embedders. A shelf that left these models unresolved, or served them as chat, is corrected the first time it is read. An embeddings request takes at most 2048 inputs, for every embedder.
  • hedos serve stops on SIGTERM and SIGHUP as well as Ctrl-C, and stops its servers every time (#12). A first Ctrl-C waits for the requests in flight and a second ends them; a SIGTERM or SIGHUP ends them after 5 s. nohup hedos serve keeps running when its terminal closes, and closing the terminal of hedos shelf no longer crashes it or leaves a server behind. Every other command stops its servers on SIGTERM or SIGHUP too, exiting 143 or 129, and a failed write to stdout exits 1 instead of panicking.
  • hedos scan shows what it found (#17): each store's size on disk with every file counted once, the models that can go because another model keeps everything removing them would delete (identical in size and sampled content), with what removing each one and all of them frees, and how many models are too big for this machine, in text and in --json. Nothing that removal would lose, and nothing another model reaches through a symlink, is ever offered. The shelf's disk figures match the scan, and a stalled disk never holds up the screen or quitting it.
  • Decision models are served (#22). A GGUF whose header names a decision type is a judge: Cloudflare's clef and clef-flash, OpenJev, Kev, lev, Laya and Julia-1, as ggml-org publishes them. llama.cpp serves each from a decision server over its own /v1/systemone, so hedos run, hedos warm and the gateway's /v1/systemone answer typed questions with it, the envelope byte for byte what llama.cpp wrote and the question sent as written, so no option moves. These models need llama.cpp 0.6.0 or newer (brew upgrade llama.cpp); an older llama-server fails with a message saying so. A decision server holds the model's window up to 16384 tokens, and a longer question is a 400 that names it. clef or kev taken for a chat model, and Laya left unresolved, by an older shelf are corrected the first time it is read.
  • Judges read images (#22). clef and OpenJev, with their projector beside them, take images on /v1/systemone (images as data URLs, or image_url parts of a state made of chat messages) and with hedos run <judge> --image. A judge that does not see refuses them with a 400 naming the ones that do.
  • llama.cpp models read images (#22). A GGUF with a projector claimed to see, but its server was never given the projector and hedos run --image refused it, so no llama.cpp vision model ever read a picture. Its server now loads the projector named for the weights' quantization (else the smallest), and images go as image_url parts, on the gateway and with hedos run. A projector counts only when it encodes images into the model's own width, so a text model kept beside a model of another width's mmproj file no longer claims to see, an audio-only projector is not sight, and a projector cut short by an interrupted download is never loaded. A llama.cpp server now names the model by its name rather than the path of its weights.
  • A FIFO or other special file in a model folder no longer hangs a scan. A command whose output is closed early, such as hedos run <model> | head, stops its servers and exits cleanly instead of panicking, and control or bidi characters in model names are shown escaped.

v1.4.4 - 2026-09-21#

A reranker can be a judge. zeroentropy's zerank-2 scores how well a document answers a query, and ranking options against a question is a typed judgment, so it now answers the same choice, score and noul questions laya does, through the same hedos run, hedos warm and /v1/systemone. Getting there meant fixing how hedos recognized it in the first place: it had been filing zerank as an embedding model and serving it as one.

  • zerank-2-reranker runs as a judge. A shipped python:zerank runtime scores each of a choice's options, or each of a score's levels, as a document against the situation and the question, and turns the scores into a distribution with the model card's own temperature; a noul scores the situation against the statement. The answer comes back in laya's shape, with the raw scores beside the probabilities, since a reranker's numbers mean something on their own. It goes through the same approval as any runtime: hedos runtimes approve python:zerank. Be clear on what a noul means here: it is how relevant the situation is to the statement, not whether the statement holds, and a false statement on the situation's topic still scores high. Its choices are sound but flatter than laya's, so a threshold tuned on one does not carry to the other.
  • A cross-encoder is no longer mistaken for an embedding model. Rerankers and embedders built with sentence-transformers ship the same config file, and hedos read that file as "embedder" on sight, so zerank was put on the embeddings runtime and a request for embeddings got a vector that meant nothing. The file's model_type tells the two apart now, and a shelf that already registered a reranker as an embedder is corrected the first time it is read, with no rescan. A rescan also stops keeping capabilities that belonged to what a model used to be identified as.
  • hedos warm on a judge asks a question with a situation in it, so a judge that needs one to answer, zerank among them, warms instead of failing after the whole load.
  • /api/tags and /api/ps stop reading a model's size and quantization out of the hex hashes in its weight path. zerank reported itself as a 69B model; any model in the Hugging Face cache could be misreported the same way.

v1.4.3 - 2026-09-20#

A judge can be asked a question. v1.4.2 could serve laya but not talk to it: hedos run laya offered a prompt box, which is the one thing a model that answers typed questions refuses, and replied with the schema it wanted instead. The question is composed now, the judgment is laid out, and a model warms where it is actually served.

  • hedos run <model> composes a typed question for a judge rather than offering a prompt. It asks for the situation being judged (a ticket, a log line, a message), then the kind of answer wanted, the question itself, and the options or levels it may be answered with. The situation can be skipped: a question like "which is darker, white or black" carries its whole world with it, and a filler word put there to satisfy the prompt is weighed by the model like anything else. The judgment comes back as ranked bars with the chosen option marked and the confidence under it, rather than as the envelope it arrived in. A question passed as an argument is sent exactly as written and laid out the same way, so a one-liner reads like a composed one.
  • The order of a choice's options is kept as it was given. It is part of the question rather than a detail of formatting: the same three options asked in a different order moved the winning probability from 0.90 to 0.84. Every map sorts its keys, so the request is written by hand, and a hand-written one is read back by scanning its keys, which also means an option label that happens to match another question's name can no longer reorder the answers.
  • hedos warm asks a judge a question instead of greeting it. The probe was the word "hi", so warming a judge spent the entire model load and then failed on the one input it refuses.
  • hedos warm loads the model on the gateway that serves it. It used to load it in its own process, which then exited and took the loaded model with it, leaving a running hedos serve exactly as cold as before, which is what the documentation has been recommending before pointing a client at it. --port reaches a gateway started on a port other than the configured one; a model whose warm request is not a conversation, a speech model for instance, still warms locally, since the gateway's chat endpoint has no route for it; and whether the model stayed resident is asked of the gateway rather than assumed from its having answered.
  • A colon is left alone where it belongs to the text. A score's level is one whole phrase rather than being cut at the first colon and half discarded, and a choice's option splits only at a colon followed by a space, so a label holding a URL or a clock time survives. A list that cannot end yet, a blank label, or a repeat is said out loud and asked again instead of failing the command and throwing away what was already typed. Ctrl-C at a judge stops, rather than reporting that the model failed to answer. --system, --max-tokens, --temperature and --image say they have no effect on a model that answers in one forward pass, instead of being accepted and quietly ignored. Option labels are padded by the width they occupy rather than by their bytes, so a list that is not Latin lines up.
  • Upgrading asks for a runtime's approval again. An approval is bound to a hash of every file the runtime consists of, and this release changes the shipped laya runtime, so hedos runtimes approve python:laya has to be given once more before it will serve.

v1.4.2 - 2026-09-20#

A model that does not generate can be served. laya answers typed questions with calibrated probabilities and never emits a token, and getting it onto the shelf meant finishing the manifest runtimes: a recipe for a model nothing built in can run, which now loads, waits for an approval given from the terminal, and serves. On top of that the gateway speaks TypeSafe's System One, so a client written against the hosted API reaches the local model by changing two environment variables.

  • Manifest runtimes load. A directory under runtimes.d in the data dir, holding a manifest.toml beside its entrypoint and lockfile, becomes a runtime: a sidecar for [serve], a one-shot command for [invoke]. A manifest bids last, so it only ever takes a model nothing built in would. A manifest that fails to load is reported by hedos scan and hedos runtimes rather than dropped, and a [vm] manifest is refused with the reason, since this build has no VM to contain it and would otherwise have run it on the host. A model identification can say nothing about takes its modality and capabilities from the manifest that wins it, and gives them back when that manifest stops bidding.
  • hedos runtimes is the consent a manifest runtime waits on. A manifest runs code on this machine, so it neither bids nor serves until approved. Bare, the command lists every runtime, what it serves, where its approval stands and which models it would take; approve <id> shows what is being agreed to and asks, refusing a pipe without --yes; revoke <id> takes it back. The approval is bound to a hash of every file in the runtime's directory, so editing the Python asks again. What a manifest declares about paths and network is shown as its own account: nothing here sandboxes it, and the card says so. hedos ls names the command under the table when a model is waiting on one.
  • laya runs. convaiinnovations/laya is a ModernBERT encoder with a decision head that answers choice, score and noul questions in one forward pass; it sat on the shelf unresolved. A shipped python:laya runtime serves it, on the GPU where the model's own loader would have settled for the CPU, and it goes through the same approval as a runtime of your own. The runtime executes the inference code the model ships, but only the exact bytes it pins by hash, so a later snapshot cannot bring new code in under an old approval. Over chat the question is JSON in the user message and the judgment is JSON in the reply; prose is refused with what a question looks like.
  • The gateway serves TypeSafe's POST /v1/systemone. Set TYPESAFE_BASE_URL=http://127.0.0.1:43367 and TYPESAFE_DEFAULT_MODEL=laya and a TypeSafe SDK reaches the local model unchanged. The question reaches the model as the text the client wrote, never re-encoded: the order of a choice's options changes the probabilities it gets, and the same question measured 0.90 in the client's order and 0.84 sorted. Only a model declaring the new judge capability answers (hedos ls --capability judge); any other name, the SDK's default jev-latest included, is refused with the names that would do rather than answered by something else. Errors carry their reason in error.message, where the SDK reads it, and a bearer token is ignored as on every route. The SDK gives up after 10 seconds and a cold model takes longer to load, so hedos warm laya first.
  • A runtime can say a failure was the request's fault, and the gateway answers it as a 400 instead of a 500. Clients that retry server errors were sending a request the model had refused, such as a choice with more options than laya has room for, three times over.
  • The runtime bundles inside the binary no longer carry whatever a test run left beside them. Bytecode caches, coverage profiles and the linters' cache directories were being packed in and written out on every machine.
  • A paste into any prompt (hedos pull's search, the model picker, a yes/no question) is drawn once. The terminal dropped back to line mode between every two keys, so in tmux a pasted URL came out once per character, each copy a character shorter than the last. The prompt now holds the terminal for its whole life, and a Ctrl-C at one still leaves a shell that echoes.
  • hedos pull fetches the config and tokenizer of a repo that keeps them in subdirectories, which used to arrive as weights nothing could load at a full progress bar. A subtree carrying weights of its own is an alternative checkpoint and is left alone, which for laya is the difference between 816 MB and 2.5 GB. A model in a form nothing here runs (rust_model.ot, .onnx, .tflite) no longer rides along as a support file.

v1.4.1 - 2026-09-09#

A model can be measured. hedos bench runs the same prompt through every model on the machine and leaves a ranked table behind, and the shelf has the same bench as a screen of its own; underneath it, two things that had to be true first, the runtimes timing the prompt and the decoding apart and a model's quantization being read at all.

  • hedos bench measures what every model on the machine actually does: tokens a second, time to first token and cold start, the same way for every row so two of them can be compared. Each model is cleared from memory, run once cold, run again warm a few times, and cleared after, so no row is measured on a machine another row warmed. On a terminal the table redraws in place while the models run and settles into a ranked one that stays in the scrollback; piped it prints once as plain text, and --json carries every run behind every figure. Ctrl-C stops the bench with what it measured standing; a second one gives up on the run in flight.
  • The shelf has the same bench on B, with b benching the model under the cursor, a every model, c stopping it, and esc back to the shelf while it carries on. The selected row's runs, cold start and where its timing came from sit beside the table. Both surfaces are driven by the same code, so they cannot disagree about a number.
  • The runtimes report the prompt and the decoding apart where they measure them apart. Ollama has always sent prompt_eval_duration and eval_duration and llama-server a timings object; both were dropped on the floor, and the MLX sidecars sent neither, so a rate divided out of the total charged the prompt to the generation and undercounted every model given a long one.
  • A model's quantization is read from its weights: general.file_type from a GGUF header, quantization.bits from an MLX config. Nothing showed it before, so two builds of the same model at different quants were indistinguishable on the shelf. A shelf already on disk picks the figure up on its next scan; until then the column reads —.

v1.4.0 - 2026-09-08#

Downloads stop belonging to the terminal that started them. A pull now runs in a worker process of its own, is managed under hedos pull from anywhere, and has a screen of its own in the shelf. The rest of the release is what end-to-end testing turned up once a model could be pulled and then run in the same sitting: pulled models that no runtime would bid for, sizes that read a unit low, and a handful of places where the interface said one thing while the machine did another.

  • A pull outlives its terminal. hedos pull <ref> hands the download to a detached worker and follows it, Ctrl-C detaches instead of cancelling, and -d returns straight away. Each job is a directory under the data dir holding its descriptor, live record, history, the lock a running worker holds, and a control file for asks; liveness is that lock rather than a pid, so a worker killed while the machine slept is known to be gone instead of being waited on forever. The worker scans the store when it finishes, so the model reaches the shelf whether anything is watching or not.
  • Seven subcommands under the one verb: hedos pull ls | attach | pause | resume [--all] | cancel | logs | clean, each honouring --json. A job is named by its id, an unambiguous prefix of one, or its reference; a name several pulls answer to means the one still going. Since a bare word is a valid Ollama tag, a model named after a subcommand is written hedos pull -- ls.
  • Pulls join and resume. Two terminals pulling the same model in the same instant share one download instead of the second failing as a duplicate; a pull that stopped part-way carries on from the bytes already on disk; a transfer that breaks retries with a growing pause inside pull.retry_window_minutes before it is left interrupted for you. A worker that died is picked back up the next time the shelf opens (pull.auto_resume, on by default), and pulling the model again always carries a stopped pull on, while a pull you paused stays paused until you say otherwise.
  • [pull] in hedos.toml: max_concurrent (a slot pool held across terminals, with a queued pull saying it is waiting for a free slot rather than reading as one nobody came for), auto_resume, partial_age_hours, retry_window_minutes, and keep_ended.
  • The shelf's task strip is a view over the job directory rather than over its own memory, so it shows pulls this screen never started and quitting no longer waits on a download. c opens a stop card that offers p to pause, keeping every byte that landed, or x to cancel, naming what has already arrived instead of acting on the spot; R resumes a stopped pull; a row dismissed with d stays dismissed between runs.
  • P opens a pulls screen in the shelf's place: every pull the store holds, newest first, with the selected one's record, rate, estimate, and history beside it. c, R, Y, x, and p stop, resume, copy the id, forget an ended record, and start a new pull. y and Y copy through pbcopy where there is one, falling back to OSC 52.
  • Models pulled from Hugging Face run. Identification looked inside a repo directory only for a diffusers index or a config with safetensors beside it, so a repo of GGUF weights came back unknown, no runtime bid for it, and hedos run said the model had no runtime for chat. The weights are read now, including in a repo that keeps a directory per quantization, and one rule picks the file a server is handed for both the scanner and identification, which used to walk the same directories by different rules: the largest set that could actually be loaded, hidden leftovers and directories wearing a weight's name skipped, a sharded set answered through the first file beside it. Models pulled before this resolve on the next scan; nothing needs pulling again.
  • The GGUF architecture table knows the encoders that only embed (nomic-bert-moe, jina-bert-v2, jina-bert-v3, neo-bert, modern-bert, eurobert, gemma-embedding, llama-embed, t5encoder), the newer models that can also see (qwen3vl, qwen3vlmoe, cogvlm, hunyuan-vl), and the vocoder half of a speech pair (wavtokenizer-dec), which serves nothing on its own. What a header says is final, an empty capability list included, so a projector no longer lists chat it cannot do, and a model claims sight only when its image encoder is on disk beside it.
  • Hugging Face downloads are verified again. The hub lists a file's hash as lfs.sha256, which the client now reads; it read lfs.oid, which the hub does not send, so no download had a hash to check against and a corrupted partial was registered rather than failed. A download that had already landed every byte is also no longer thrown away and fetched again: resuming asked for a range beginning at the end of a whole file, which a server answers 416 and the resume path read as a partial past the end, so a stream that errored after its last chunk cost the entire file, over and over across the retry window.
  • A failed pull says what actually happened. A repo the hub refuses to list reads as not found or private instead of gated; with a token attached a 403 says the token lacks access and a 401 says the token was rejected; an error from this machine, such as a directory that cannot be written, fails the pull on the first attempt instead of being retried as though the network had dropped; the disk-headroom check measures the volume the model will be written to rather than the directory the command was typed in; and hedos pull <ref> now exits non-zero when the pull was interrupted, so hedos pull X && hedos run X no longer runs weights that were never fetched. A pause or a cancel you asked for still exits 0.
  • Sizes are exact, and stated in the units the hubs state them in. The record kept a model's size in whole mebibytes and every reader expanded it again as decimal, so a 491,400,032-byte model read 490 MB while the pull that fetched it said 491 MB, and anything under a mebibyte read as unknown; hedos rm and a pull's confirmation now say 4.3 GB where they said ~4294 MB. A shelf written before this keeps its figures, folded into the exact ones as the registry loads, with no rescan to wait for.
  • A model whose weights are gone stops reading as though it could run. hedos ls marks the row ✕ and its fit column reads gone, the word the shelf already used; run, chat, warm and the rest refuse it with that reason instead of failing later about whatever the missing files broke; hedos rm still reaches it, which is what removal is for.
  • HF_HOME and HF_HUB_CACHE name the machine's one hub cache instead of adding a second. Discovery swept both the environment's cache and ~/.cache/huggingface/hub while a pull wrote only to the first, so the two could come apart; both resolve the root the same way now, which is the rule the hub's own tooling follows. Models left in the default cache while HF_HOME points elsewhere read as gone on the next scan; add that path to hf_cache_roots to keep sweeping both.
  • A model declaring a context window under 512 tokens can be run. The budget held back a flat 256 tokens for the reply before it looked at the prompt, so a 128-token model was refused everything it was ever asked; a window that cannot afford that floor now keeps half of itself for the reply. At 512 tokens and above nothing changes.
  • A chat reply whose characters are wider than one byte, in Georgian, Chinese, or emoji, no longer crashes the shelf, and reading the environment no longer panics on a variable this process cannot read as text.

v1.3.1 - 2026-08-29#

A craft pass over hedos shelf. Nothing the screen does has changed; how well it does it has.

  • The help is generated from the keymap and grouped by what each key acts on, so a key cannot exist without its help line and nothing collides.
  • The footer sheds keys one at a time, keeps ? help q quit at every width, and adds enter expand and o sort.
  • The pull card carries each recommendation's blurb, spaces its categories, hints what to type, and names the model in its preview.
  • The shelf has a palette of its own: warm grey chrome, sand headings, a teal for runtimes and stores, and green, amber, and coral for what is loaded, tight, and failed, all built around the orange the wordmark now wears and orange kept for focus and motion; the selected row is a tinted bar with a gutter mark instead of a reversed one.
  • Modals sit on a darkened screen with dim borders.
  • Task rows show c cancel and d dismiss only on the row the key acts on, and a running download is never pushed off the strip.
  • Long values wrap or elide instead of running past their pane; the header and detail no longer repeat the machine block; the stacked layout gains a gateway line.
  • A model whose weights are gone reads as gone and can be removed or pulled again; an abandoned partial download is removable.

v1.3.0 - 2026-08-27#

The shelf as a screen. hedos shelf opens a TUI over everything the command line already does, and keeps on screen what a command line cannot: what is loaded and by whom, how much memory is left, what the gateway served today. The rest of the release hardens what the screen exposed.

  • hedos shelf: every model with its runtime, store, and size; the selected one's fit, residency, capabilities, and gateway activity; the machine's memory with a bar per loaded model, disk per store, and the gateway's state. Every key is a subcommand (p pulls, w and u warm and unload, x removes with a preview, S serves) and the footer shows only the keys that apply to the model under the cursor. Pulls download in a task strip while you keep working, with c to cancel. It works over ssh and inside tmux, and it remembers the selection between runs.
  • A chat pane inside the shelf. t turns the shelf into a conversation with the selected model: the reply streams in, keeps its markdown (bold, headings, code blocks left alone), scrolls with the wheel, the arrows, and the page keys, and holds still while more text arrives. esc stops a reply, then closes the pane; the model stays warm for whatever comes next.
  • Hand-offs. l launches a coding harness on the selected model, T opens hedos chat, S runs hedos serve: the screen steps aside for anything that needs the terminal and is back the moment it ends, with a row in the task strip saying how it went.
  • Line editing everywhere. Every text field in the shelf (the chat prompt, the pull search, the filter) edits like a shell line: Ctrl-A and Ctrl-E, Ctrl-U, Ctrl-W and Option+Delete, the arrows by character and by word, with the cursor kept on screen. Text is measured the way the terminal draws it, so emoji, wide scripts, and combining marks neither overflow nor split.
  • Models the Ollama daemon holds now show as warm and unload from hedos. hedos warm, hedos unload, and hedos ls see what the daemon has loaded, and unloading through hedos evicts it there; a daemon too busy to answer is reported instead of being read as "not loaded", so an unload never claims success it cannot see and a removal never deletes a model the daemon still holds.
  • Downloads survive a dropped connection. A Hugging Face stream that breaks mid-file is reopened from the bytes already on disk, up to five times with a growing pause, before a pull is failed; the checksum still covers the whole file. Ollama plans carry their :latest tag, so a pulled model is recognised as installed.
  • Ctrl-C reaches what you meant. A Ctrl-C typed at hedos, or at hedos chat and hedos serve, no longer reaches the llama-server pool, the Python sidecars, or an ollama serve hedos started; they run in their own process groups. The shelf quits on Ctrl-C from every modal and hands the terminal back the way it found it, even after a harness that died in raw mode.
  • A running gateway reports the models it holds on /api/ps, with when each expires, and the gateway stats record when a model was last seen.

v1.2.1 - 2026-08-02#

A fix for model removal. hedos rm reported a model deleted but hedos ls kept showing it.

  • Removing a model now drops its record from the shelf, not just its files. rm deleted the weights (or the Ollama tag) but never removed the record from the registry that ls reads, so the model lingered — and a rescan only re-flagged it as missing rather than dropping it. A removed model now leaves the shelf immediately and stays gone.

v1.2.0 - 2026-07-24#

A native runtime for Apple's on-device model (#4). hedos now serves Apple Intelligence directly through the FoundationModels framework, so the model built into the machine appears on the shelf and answers chat and completion requests over the gateway with no download and no sidecar.

  • The runtime bridges Rust to the framework through a small Swift shim, streaming the model's output back over the gateway in every dialect. When Apple Intelligence is turned off or still downloading, the shelf says so instead of silently omitting the model.
  • Tool calling works end to end. Apple's model executes tools during generation, so the runtime captures each call as a structured tool_call, ends the turn, and replays the result on the next request as tool-call and tool-output history. Tool parameter schemas port into the framework's dynamic schema — strings, numbers, booleans, enums, arrays, and nested objects with required and optional fields — and a tool whose schema the framework cannot express is dropped from the offer rather than failing the request.
  • Apple's on-device model has a fixed 4096-token context window. hedos records that window honestly on the shelf and in /v1/models, and warns at pick time when a model's window is too tight for the request ahead of it, instead of letting the run fail deep in the model with a context error.

v1.1.1 - 2026-07-22#

Tool calling now reaches the models served by the Python sidecars (#5). MLX builds from the Hugging Face cache — Llama and Qwen instruct models among them — can drive the coding harnesses and serve tool requests over the gateway, closing the gap where capable weights sat out of hedos launch because their runtime never forwarded tools.

  • The mlx-lm sidecar renders a request's tools through the model's own chat template, so each model family sees tools in the exact format it was trained on. A template with no tool support gets a generic system-prompt description of the tools instead, and a template that rejects a system role gets it folded into the first user turn — the request degrades gracefully rather than failing.
  • Tool calls are parsed back out of the model's reply in each family's own format — Qwen/Hermes <tool_call> blocks, Mistral [TOOL_CALLS], Llama's python tag and bare JSON — and served over the gateway as structured calls in every dialect. A reply that contains no call streams as plain text, exactly as before.
  • mlx-vlm gets the same treatment, so tool-driving harnesses can also seat vision models.
  • Models served by these runtimes now carry the tools capability on the shelf and appear in the hedos launch picker; an existing shelf picks this up automatically on the next command, no rescan needed.

v1.1.0 - 2026-07-21#

This release turns hedos into a working seat for coding agents and rounds out the command surface. The gateway speaks a third wire format and enforces tool calling per model, hedos launch runs a coding harness against a local model with nothing to configure, and four new capabilities land on the command line. The default posture is unchanged: the gateway still binds loopback and trusts every local caller.

  • Coding harnesses in one command. hedos launch runs Claude Code, OpenCode, Aider, Goose, or Crush against a model on your shelf. The gateway starts inside the same process on a free port, the harness is wired to it, and both stop together; your own harness config is never touched, so running the tool directly afterwards behaves exactly as before. Name the harness and model, or pick each interactively.
  • The Anthropic messages dialect. Alongside OpenAI (/v1) and Ollama (/api), the gateway now serves Anthropic's /v1/messages, so Claude Code and anything built on the Anthropic SDK can point at hedos and reach every model you own.
  • Tool calling, end to end. hedos reads each model's chat template to decide whether it can call tools, shows that as a capability on the shelf, and the gateway advertises and enforces it: a request that asks a model to use tools is served when the model can and refused with a clear reason when it cannot, instead of failing deep in a runtime.
  • hedos transcribe turns an audio file into text through a local whisper model — the inverse of speak, and the last capability that lacked a first-class command. Point it at a WAV and it streams the transcript back; --language and --translate map to what the runtime honors.
  • hedos run --image feeds a local image to a vision (see) model, so you can ask a model about a picture from the shell. The picker offers only vision-capable models, and naming a model that cannot see says so up front instead of silently answering blind.
  • A fit verdict on the shelf. hedos ls now has a FIT column that says whether each model will actually run in this machine's memory — fits, tight, too big, or unknown — using the same assessment the install catalog uses, and --json carries it too.
  • hedos stats reads the gateway's audit log back and reports per-model request counts, the rejection rate, and p50/p90/p99 latency, as a table or under --json.
  • The Ollama daemon starts itself. A cold connection to Ollama now starts ollama serve and waits for it, so a served request or a pull no longer fails just because the daemon was not already running.
  • Gated Hugging Face repositories authenticate. hedos reads HF_TOKEN, HF_TOKEN_PATH, or the token that huggingface-cli login writes when it plans and downloads a gated model — the install path used to ignore it and refuse every gated repo. When access is still denied, the error points at the model's terms page instead of only telling you to add a token.
  • The interactive hedos pull picker is navigable. A blank search shows the models that fit this machine's memory, any query searches Hugging Face, and a "search again" row moves between the two, so switching from recommendations to a search, or trying another query, no longer means re-running the command. No matches or an all-installed recommendation list returns to the prompt instead of ending the command.
  • Hardening. Approved runtimes receive an allowlisted environment rather than the full ambient one; a Hugging Face revision that is not a safe path component is rejected before it reaches the filesystem; registry and settings writes serialize through an advisory file lock so two hedos processes cannot clobber each other; the audit log and generated launch configs are written owner-only; and an install request's headers are redacted when it is formatted, so an access token cannot leak into a log or an error string. An off-by-default token-authentication prototype is present but inert — multi-client access remains a later, opt-in decision.
  • Faster and lighter. The shelf is served from a shared cached snapshot instead of being cloned per request, one identification cache is reused across resolution passes, provisioned Python bundles are stamped so they are not rechecked every command, and the bundles themselves ship as a single compressed archive that unpacks on first use, shrinking the binary.

v1.0.0 - 2026-07-20#

hedos is now a terminal program. The macOS app is gone, replaced by a single hedos binary that does everything headlessly: it finds the models already on your machine, installs new ones, serves them over a local HTTP gateway, and runs chat, speech, and image generation straight from the shell. It works over SSH, in scripts, and on Linux. The Swift app remains on the macos-app branch at its final v0.1.4 release and is no longer developed.

  • Discovery of every local model, unchanged in spirit from the app. hedos scans the Ollama store, the Hugging Face hub cache, LM Studio's library, and loose GGUF and safetensors files in your folders, then reconciles them into one shelf and resolves each to the runtime that actually fits it. Weights are never moved, copied, or re-downloaded; the records point at where the files already sit, so every other tool on the machine still sees the same models.
  • The hedos command line covers the whole surface: scan and ls to see the shelf, run and chat to talk to a model, speak and image to generate media, pull and rm to install and remove, warm and unload to control residency, and serve to start the gateway. Every command takes --json for machine-readable output, and every command works non-interactively so scripts behave predictably.
  • The commands are interactive when you are. Leave off the model and you get a fuzzy-filterable picker showing the same columns as ls, scoped to what the command needs, with models that resolved a runtime listed first so the servable one is the default. Leave off the prompt or the text and hedos asks for it. speak offers a voice picker when a model ships several. Run pull with no reference and you can search Hugging Face by keyword or pick from a short list of models that fit your machine's memory. In a pipe, a script, or under --json, none of this triggers: a missing argument is a plain error instead, so nothing ever blocks waiting on input.
  • A loopback gateway speaking both the OpenAI (/v1) and Ollama (/api) wire formats, so editors, agents, and scripts that already talk to either can point at hedos and reach every model on the shelf. It serves each model's real context length, chat template, and tool-calling dialect rather than a lowest common denominator, and tells you plainly when a model cannot do what a request asks. Authentication is open on loopback, which makes the loopback boundary the security boundary.
  • Runtimes for local GGUF through a llama-server subprocess, the Ollama daemon, any OpenAI-compatible endpoint, the Python sidecars (mlx-lm, mlx-vlm, speech, embeddings, diffusers, mflux) and whisper, plus the ComfyUI and AUTOMATIC1111 image daemons. The Python runtime bundles ship inside the binary and unpack themselves on first use, so speech and image generation work from a fresh install without hunting for scripts. A memory governor coordinates residency and admission across every engine so two heavy loads cannot oversubscribe the machine.
  • Installing and removing models from the terminal. pull resolves a Hugging Face repo, an Ollama tag, or a link to either, plans the install before a byte moves, and shows the size and destination for confirmation. Hugging Face downloads land in the standard hub cache with resume and per-file checksum verification; Ollama models pull through the daemon. rm previews exactly what would go and asks before deleting, and refuses to delete non-interactively without -y.
  • One settings file at ~/.config/hedos.toml and one state directory under ~/.local/share/hedos for the registry, generated artifacts, job history, and the gateway's audit log. Neither holds your weights. Both follow the XDG variables when set.
  • The Apple Foundation and MLX-Swift runtimes from the macOS build are framework-bound and are intentionally not part of this port. A model that would need one still appears on the shelf, and hedos says it cannot serve it here rather than dropping it.