v0.8.0
[0.8.0] - 2026-05-23
Engine-robustness release covering the erllama_server hardening brief observed under real 30B/Metal load (cold-admit decode wedges, agentic tool-continue loops).
Added
- Bounded, interruptible, self-recovering decode. Every context installs a ggml abort callback. Each decode step arms a per-step wall-clock budget (
context_opts.decode_budget_ms, default 30000, 0 disables); exceeding it returns{error, decode_timeout}instead of blocking.erllama_nif:request_abort/1sets an atomic flag the callback honours without taking the context mutex, so a running decode can be interrupted from outside the blocked gen_statem and returns{error, decode_aborted};cancel/1fires it best-effort. Ondecode_timeout/decode_abortedthe engine recovers in place: fails in-flight and queued callers, recreates the context via the new backendreset_context/1(model stays loaded), resets seq and session state, returns to idle. Recovery drops only the live in-context KV and sticky-session pins; the persistent tiered cache survives, so the next admission still warm-restores from cache rather than starting fully cold. on_full => block | erroradmission option oncomplete/3,prefill_only/3,infer/4(defaultblock).errorfails fast with{error, seq_capacity}instead of queueing when no seq is free; pair withavailable_seqs/n_seq_maxfrommodel_info/1.generated => [token_id()]in theerllama_doneStats map: the exact generated token ids in order, so a caller can build a byte-exact suffix forcontinue/3without re-tokenising detokenised text.expect_committed => [token_id()]option oncontinue/3: the caller's view of the session's committed tokens. When supplied it must equal the stored context exactly, otherwisecontinue/3returns{error, {transcript_mismatch, #{stored_len, expected_len, diverge_at}}}without prefilling, leaving the seq pinned for a re-sync and retry.
Changed
- A binary
grammaris now authoritative through tool-call syntax tokens. Previously, on a model withtool_call_markers, atool_choice=required/response_formatgrammar was abandoned once a tool-call span opened (syntax tokens went through a grammar-less greedy sampler), letting output drift to free-form. The greedy-on-syntax swap is now disabled for any request carrying a grammar, so the constraint holds end to end oninfer/4andcontinue/3. nif_decode_onenow returns{error, {decode_failed, Rc}}instead of a bare{error, Rc}, matchingnif_step.