Skip to content

Latest commit

 

History

History
369 lines (316 loc) · 119 KB

File metadata and controls

369 lines (316 loc) · 119 KB

ChaosEngineAI — Project Guide

Editorial Rules

Do not reference external desktop AI apps in code, comments, UI strings, docs, or commit messages. This includes — but is not limited to — ComfyUI, LM Studio, Ollama, AUTOMATIC1111, Forge, InvokeAI, Diffusion Bee, Draw Things, Mochi Diffusion, Pinokio. ChaosEngineAI is a standalone product; comments and copy must not name or compare against competing apps even when they share underlying weights or workflows.

Allowed exceptions:

  • Model names from upstream providers (e.g. "Stable Diffusion 3.5 Medium", "FLUX.1-schnell", "Wan 2.1") — these are model identifiers shipped by Stability AI / Black Forest Labs / Alibaba, not apps.
  • Hugging Face organisation namespaces (e.g. lmstudio-community/..., mlx-community/...) — these are repo namespaces on HF, not promotion of any app.
  • Open-source dependencies we vendor or shell out to (e.g. stable-diffusion.cpp, llama.cpp, mlx-video) — these are named libraries we ship as runtime components.

When describing reference defaults or upstream behaviour, name the model author (e.g. "Lightricks reference defaults", "Wan-AI model card") rather than the third-party tool that exposes them.

Architecture Overview

ChaosEngineAI is a desktop AI inference app built with:

  • Frontend: React + TypeScript + Vite
  • Desktop shell: Tauri (Rust) — src-tauri/
  • Backend: Python FastAPI sidecar — backend_service/
  • Inference engines: MLX (Apple Silicon), llama.cpp (GGUF), vLLM (CUDA)
  • Cache strategies: Pluggable compression via cache_compression/ registry

Key Directories

Path Purpose
src/ React frontend (components, hooks, utils, types)
src-tauri/src/lib.rs Tauri bridge — runtime extraction, binary resolution, sidecar bootstrap
backend_service/ Python FastAPI backend
backend_service/inference/ Core inference engine package — controller.py (RuntimeController), engines/, binaries.py, capabilities.py, conversion.py, jsonrpc.py
backend_service/state/ App state package — __init__.py (ChaosEngineState facade), documents.py, benchmarks.py, openai_compat.py, payloads.py, settings_state.py, sessions.py, downloads.py, metrics.py, logs.py
backend_service/mlx_worker*.py MLX subprocess worker — mlx_worker.py orchestrator + mlx_worker_{request,prompt,io,diagnostics,multimodal,cache,eval,loader}.py siblings
backend_service/routes/ API endpoints (14 route modules)
backend_service/helpers/ System stats, settings, persistence, cache estimation
cache_compression/ Cache strategy registry + adapters (native, turboquant, triattention, plus diffusion-only fbcache/teacache/taylorseer/magcache/pab/fastercache). Renamed from compression/ so it doesn't shadow Python 3.14's stdlib compression namespace package. Legacy ids chaosengine and rotorquant were dropped in FU-030 and now coerce to turboquant via registry.resolve_legacy_id.
dflash/ DFlash speculative decoding — draft model registry + availability detection
scripts/ Build, install, and update scripts
tests/ Python tests (pytest)
src/**/*.test.ts TypeScript tests (vitest)

Binary Routing

The app supports two llama-server binaries:

  • llama-server (standard, Homebrew) — for the native cache strategy
  • llama-server-turbo (TurboQuant fork) — for the TurboQuant strategy, installed to ~/.chaosengine/bin/

Each CacheStrategy declares required_llama_binary()"standard" or "turbo". The LlamaCppEngine._select_llama_binary() method in inference/llama_cpp_engine.py routes to the correct binary. Cache types are pre-validated against the binary's --help output before startup.


Build Checklist

Run before every release, PR, or significant change. Automated via ./scripts/pre-build-check.sh.

1. Tests

  • cd /Users/dan/ChaosEngineAI && .venv/bin/python -m pytest tests/ -q — all Python tests pass
  • npm test — all TypeScript tests pass
  • npx tsc --noEmit — no type errors
  • ./scripts/e2e_test_suite.py --smoke — fast smoke (≤60s) confirms backend + CLI surface intact
  • ./scripts/e2e_test_suite.py (full) — every phase passes (or correctly skips). Required for release builds and any PR touching inference routing, engine implementations, cache strategies, or setup install endpoints. See docs/E2E_TESTING.md.
  • New feature gate: any user-visible feature, new engine wiring, new model family in catalog, new install endpoint, or new cache/spec-dec strategy MUST land alongside an E2E check in the relevant phase of scripts/e2e_test_suite.py. PRs that add a feature but no E2E check are incomplete. See Adding new checks in docs/E2E_TESTING.md.

2. Licences

  • THIRD_PARTY_NOTICES.md is up to date — all bundled/vendored deps listed with correct licence types
  • No new dependencies added without checking licence compatibility (must be MIT, Apache 2.0, BSD, or similar permissive)
  • Shipped binaries (llama-server, llama-server-turbo) include MIT licence notice

3. Upstream Dependencies

Check for updates to external repos we build from or depend on:

Dependency Repo Branch Check Command
llama.cpp (standard) ggml-org/llama.cpp master git -C ../llama.cpp fetch && git -C ../llama.cpp log HEAD..origin/master --oneline
llama-server-turbo TheTom/llama-cpp-turboquant feature/turboquant-kv-cache git ls-remote https://github.com/TheTom/llama-cpp-turboquant.git refs/heads/feature/turboquant-kv-cache
dflash-mlx bstnxbt/dflash-mlx main pinned to commit f825ffb2 (upstream deleted all tags April 2026) git ls-remote https://github.com/bstnxbt/dflash-mlx.git refs/heads/main
turboquant-mlx-full manjunathshiva/turboquant-mlx .venv/bin/pip index versions turboquant-mlx-full 2>/dev/null
DDTree (ported algorithm) liranringel/ddtree main git ls-remote https://github.com/liranringel/ddtree.git HEAD

4. Cache Strategy Health

  • Native strategy llama_cpp_cache_flags() only emits standard types: f32, f16, bf16, q8_0, q4_0, q4_1, iq4_nl, q5_0, q5_1
  • TurboQuant strategy returns required_llama_binary() == "turbo"
  • Legacy chaosengine + rotorquant ids coerce to turboquant via registry.resolve_legacy_id
  • DFlash _COMMUNITY_PREFIXES includes all common model repo prefixes
  • New model families added to DRAFT_MODEL_MAP if draft checkpoints exist

5. Desktop Packaging

  • scripts/stage-runtime.mjs stages both llama-server and llama-server-turbo if available
  • Manifest includes llamaServerTurbo field
  • src-tauri/src/lib.rs sets CHAOSENGINE_LLAMA_SERVER_TURBO env var

6. Localization (i18n)

  • npm run i18n:validate passes — every locale parity check + ICU syntax compile + orphan key scan
  • No hardcoded user-facing strings introduced in this release (extraction script npm run i18n:extract reports zero new keys with empty en values)
  • All 10 shipping locales (en, zh-CN, zh-TW, ja, de, ru, ko, fr, es, pt-BR) reach ≥95 % coverage; missing keys auto-fall back to en at runtime
  • backend_service/locales/*/LC_MESSAGES/messages.mo compiled via pybabel compile
  • src-tauri/locales/*.ftl regenerated for any new menu / tray strings
  • Pseudo-locale en-XA smoke-tested for layout clipping on Setup + Diagnostics tabs (widest text surface)
  • CJK font fallback stack present in styles.css (Apple Silicon + Windows + Linux paths)
  • Per-feature translation workflow followed: new en-only strings ship with null placeholders in other locales (runtime falls back to en); follow-up PR fills translations (see FU-042+ tracker rows for cadence)

Follow-Ups Tracker

Deferred work and upstream conditions to re-check periodically. Revisit at each release or when touching the affected subsystem. Delete entries once shipped or no longer relevant.

ID Item Trigger / Condition Notes
FU-001 Bump turboquant to 0.3.x Shipped 2026-05-03. turboquant-mlx-full 0.3.0 published to PyPI; [turboquant] extra pin bumped from >=0.1.3 to >=0.3.0 in pyproject.toml. Adds asymmetric K/V bits, layer-adaptive precision, --no-quant eval flag, NumPy 2.0 + transformers 5.x compat. Verified backward compatible — full test_cache_strategies.py + test_image_runtime.py + test_video_runtime.py (190 tests) pass against 0.3.0. The turboquant (HuggingFace) and turboquant-mlx (arozanov fork) packages stay on their existing pins; only the active turboquant-mlx-full path advances.
FU-002 Wire TriAttention MLX compressor into mlx_worker Shipped 2026-05-03. Unblocked by triattention 0.2.0's MLX port (RavenX AI, 2026-04-09): apply_triattention_mlx(model, kv_budget=N) operates on the model directly, bypassing the mlx_lm.stream_generate callback gap. Spike at scripts/spike_triattention_mlx.py confirmed 2.63× speedup with identical output on Qwen2.5-0.5B-Instruct-4bit (norm-only scoring works without calibration stats). Wired into WorkerState._apply_cache_profile (backend_service/mlx_worker.py) via a new _apply_triattention_mlx_compressor branch — when cacheStrategy == "triattention" the worker delegates to cache_compression.registry.get("triattention").apply_mlx_compressor(model, kv_budget=self.kv_budget). kvBudget request param defaults to 2048; falls back to native cache on any failure (model None, registry missing, strategy unavailable, apply raises).
FU-003 LongLive integration for Wan 2.1 T2V 1.3B CUDA platforms (Windows/Linux) only Real-time causal long video gen (triattention/longlive). We ship the target model already. Needs: new video backend branch in backend_service/video_runtime.py, LoRA weights download, torchrun orchestration, UI affordance for long-clip mode. Flash Attention dep.
FU-004 TriAttention SGLang backend When/if we adopt SGLang as an inference backend Added upstream 2026-04-22 as v0.2.0. No action unless SGLang lands in our runtime.
FU-005 arozanov v_only TurboQuant MLX mode Dropped 2026-04-24 Our current turboquant-mlx-full 0.1.3 path already runs without any mlx-lm fork — uses pip TurboQuantKVCache with QuantizedKVCache fallback (turboquant_mlx/init.py:174-186). VOnlyTurboQuantCache is only in the arozanov fork (we track but don't consume). Value prop already satisfied; entry removed.
FU-006 Re-verify dflash-mlx pin Bumped to fada1eb (HEAD) on 2026-05-10. Previously bumped to 8d8545d = v0.1.5.1 on 2026-05-05 after the ddtree.py rewrite landed. 2026-05-10 bump from 8d8545d to fada1eb covers 11 upstream commits including the new Gemma4 DFlash backend (commit 05cc456, "feat: add Gemma4 DFlash backend"), the v0.1.5 serving surface, live server metrics endpoint, prefix-cache survival test gate, async L2 writer fix, long-context runtime diagnostics hardening, benchmark slugging fixes, and a license switch to Apache-2.0. Same fix applied in both pyproject.toml (already correct) and scripts/stage-runtime.mjs (was lagging on f825ffb v0.1.4.1 — staged release runtime would have shipped the old binary). The two pins now live as the same hex string in both files; CI's pre-build-check should grow a sync assert. No breaking API changes between the pins per upstream commit log. Earlier bump notes: Pin advanced from f825ffb (v0.1.4.1) to 8d8545d (v0.1.5.1). 0.1.5+ moved every primitive that backend_service/ddtree.py consumed off the runtime top-level onto a per-family target_ops adapter — target_forward_with_hidden_statestarget_ops.forward_with_hidden_capture, extract_context_feature_from_dicttarget_ops.extract_context_feature, make_target_cachetarget_ops.make_cache, _target_embed_tokenstarget_ops.embed_tokens, _target_text_modeltarget_ops.text_model, _lm_head_logitstarget_ops.logits_from_hidden. ContextOnlyDraftKVCache moved to dflash_mlx.model; create_attention_mask re-imported from mlx_lm.models.base; trim_cache_to was removed entirely and now lives as a thin local _trim_cache_to shim that calls each entry's own .rollback() / .trim() / .crop(). Adapter resolved once at the top of generate_ddtree_mlx via resolve_target_ops(target_model). Live smoke 2026-05-05 against mlx-community/Qwen2.5-0.5B-Instruct-4bit confirmed adapter resolves (backend=qwen_gdn, family=pure_attention), forward+capture / embed_tokens / text_model / logits_from_hidden / extract_context_feature / _trim_cache_to all working. Gains over 0.1.4.1: draft model quantization with Metal MMA kernels, branchless Metal kernels + fused draft KV projections, long-context runtime diagnostics. Re-check cadence resets to quarterly.
FU-007 TeaCache for Wan2.1/2.2 Obsoleted 2026-05-03 by FU-015. TeaCache patches for FLUX + HunyuanVideo + LTX-Video + CogVideoX + Mochi remain under cache_compression/_teacache_patches/. The Wan-specific port that was deferred here is no longer needed: diffusers 0.36 ships a model-agnostic apply_first_block_cache hook (FU-015) that operates on pipeline.transformer regardless of model, so Wan caches via the same generic strategy without a vendored forward. Pick FBCache for Wan; TeaCache stays available as the alternative for FLUX-family pipelines.
FU-008 stable-diffusion.cpp engine (cross-platform diffusion) Shipped 2026-05-03 (video) + 2026-05-04 (image). Binary build via scripts/build-sdcpp.sh + scripts/update-sdcpp.sh (clones to /tmp/stable-diffusion.cpp, cmake -DSD_METAL=ON on Darwin or -DSD_CUBLAS=ON on Linux+CUDA, installs to ~/.chaosengine/bin/sd). Build target is sd-cli (renamed from sd upstream around master-590); installer copies it back to the legacy sd filename so downstream resolvers in sdcpp_video_runtime.py, sdcpp_image_runtime.py, and stage-runtime.mjs keep working. Path resolution in src-tauri/src/lib.rs. Video lane (SdCppVideoEngine.generate): subprocess spawn → maps VideoGenerationConfig → sd.cpp flags (--diffusion-model, -p, -W/-H, --steps, --cfg-scale, --seed, -o, --video-frames, --fps, --negative-prompt); regex-parses step N/M (or [N/M]) into VIDEO_PROGRESS; reads .webm bytes back (sd.cpp's video output is .webm/.avi/animated .webp — no native .mp4). Catalog requires ggufRepo + ggufFile pin (e.g. QuantStack/Wan2.2-TI2V-5B-GGUF). Image lane (SdCppImageEngine.generate, sdcpp_image_runtime.py): mirrors video shape but emits PNG, drops --video-frames/--fps, batches by looping seeds (sd.cpp renders one image per invocation). Manager dispatch in image_runtime.py ImageRuntimeManager.generate routes when config.runtime == "sdcpp", falls through to diffusers on probe failure or runtime error. Catalog variants: FLUX.1-schnell-sdcpp-q4km + FLUX.1-dev-sdcpp-q4km (catalog/image_models.py). Supported image repos: FLUX.1/2 family, SD3.5, SDXL, SD2.1, Qwen-Image (+ 2512), Z-Image (+ Turbo).
FU-009 mlx-video (Blaizzy) Apple Silicon video engine Fully shipped 2026-05-04. Live smoke validated end-to-end. LTX-2 paths (prince-canuma/LTX-2-{distilled,dev,2.3-distilled,2.3-dev}) routed through subprocess engine in backend_service/mlx_video_runtime.py; Wan-AI paths route via Phase 8 of FU-025 (_is_wan_repo + _build_wan_cmd + _REPO_ENTRY_POINTS["Wan-AI/"] = "mlx_video.models.wan_2.generate"). Live smoke 2026-05-04 against Wan-AI/Wan2.1-T2V-1.3B (480×272, 5 frames, 4 steps, unipc): T5 encode 14.1s + transformer load 0.2s (4-bit q) + denoise 2.9s @ 1.4 it/s + VAE decode 1.3s = 19.6s total, 383 KB .mp4 output. The smoke also surfaced + fixed a status_for filename gap — mlx-video upstream emits root-level model.safetensors + t5_encoder.safetensors, not the legacy transformer*.safetensors / text_encoder*.safetensors patterns the helper originally checked for. Both now match.
FU-010 vllm-swift Apple Silicon backend (watch-closely) Re-evaluate end of June 2026 TheTom/vllm-swift — Swift/Metal vLLM forward pass, Python orchestration only. 2.4× over mlx_lm on Qwen3-0.6B single-request; matches vLLM at concurrency 64. Fills the macOS vLLM gap. Posture upgraded 2026-05-03 from watch-only after 76 → 238 stars and 1 → 15 forks in ~10 days; v0.3.0 (2026-04-28) shipped Metal Invalid Resource race fix + ~10% TQ MoE perf, v0.2.2 (2026-04-26) added hybrid model batched decode + paged-attention. Single contributor still. Trip-wires for adoption: ≥3 contributors with merged commits OR public benchmark beating mlx_lm at concurrency >1 on Llama-3.x-8B-class (current 2.4× claim is Qwen3-0.6B single-request only).
FU-011 LTX-Video 2.3 diffusers variant Lightricks publishes diffusers-compatible weights (Lightricks/LTX-2.3 gains model_index.json) LTX-2.3 currently routes via mlx-video on Apple Silicon (prince-canuma/LTX-2.3-{distilled,dev} already in catalog). Lightricks' own model card states "diffusers support coming soon". When the diffusers-shaped weights land, add a Lightricks/LTX-Video-2.3 entry to backend_service/catalog/video_models.py under the ltx-video family so RTX 4090 / Linux users get a non-MLX path. Until then, no LTX-2.3 path exists for CUDA.
FU-012 LTX Spatial Temporal Guidance (STG) diffusers ships LTXPipeline with perturbed_blocks kwarg, or vendor a forward patch Upstream reference workflows enable STG by default — perturbs final transformer blocks during sampling to reduce object breakup / chroma drift. Our pinned diffusers' LTXPipeline does not accept perturbed_blocks. Phase D landed frame_rate + decode_timestep + decode_noise_scale + guidance_rescale for reference parity on the basic kwargs; STG is the remaining gap. Track upstream; if quality remains short of the reference, vendor a forward patch under cache_compression/_teacache_patches/ltx_video.py-style.
FU-013 Vendored STG-enabled LTX pipeline Phase F or when a user reports that Phase D + E1 + E2 quality remains short of the upstream reference Subclass LTXPipeline and override __call__ to add a third forward pass per step with selected transformer block(s) perturbed (skip self-attention or replace with identity). Combine: pred = uncond + cfg*(text - uncond) + stg_scale*(text - perturbed). Reference: Lightricks' upstream LTX-Video repo's STGSamplingHook. Estimated ~250 lines of vendored code + tests. Sequence dependency: do this AFTER FU-007 (Wan TeaCache) ships so the cache vs guidance interactions are tested in isolation.
FU-014 LLM-based prompt enhancer Closed 2026-05-04 by FU-022. Replaced by FU-022's MLX-native enhancer (see below).
FU-015 First Block Cache (diffusers 0.36 generic hook) Shipped 2026-05-03. Cross-platform diffusion cache strategy backed by diffusers.hooks.apply_first_block_cache. Lives at cache_compression/firstblockcache.py, registered as id fbcache in the strategy registry (cache_compression/init.py). Applies to image + video DiTs (FLUX, SD3.5, Wan2.1/2.2, HunyuanVideo, LTX-Video, CogVideoX, Mochi). Default threshold 0.12 (≈1.8× speedup on FLUX.1-dev with imperceptible quality drift). Same apply_diffusion_cache_strategy hook as TeaCache; UNet pipelines (SD1.5/SDXL) raise NotImplementedError into a runtimeNote. Closes FU-007.
FU-016 SageAttention CUDA backend wiring Shipped 2026-05-03 (CUDA-gated). Helper at backend_service/helpers/attention_backend.py (maybe_apply_sage_attention). Called from both image_runtime.py and video_runtime.py _ensure_pipeline after pipeline build. CUDA + sageattention pip wheel + diffusers ≥0.36 + DiT pipeline. No-op on macOS / CPU / UNet / non-DiT pipelines. Stacks multiplicatively with FBCache (community Wan2.1 720P cumulative 54%). Setup-page install action (pip install sageattention) follows.
FU-017 SDXL VAE fp16 fix on MPS / CUDA Shipped 2026-05-03. Probes madebyollin/sdxl-vae-fp16-fix snapshot via local_files_only=True (no surprise download) at pipeline load. When cached, swaps pipeline.vae and lets _preferred_torch_dtype stay on fp16 for SDXL on MPS — drops the previous fp32 fallback that doubled wall-time on Apple Silicon. Helpers _is_sdxl_repo + _locate_sdxl_vae_fix_snapshot in image_runtime.py. Falls back to stock VAE + fp32 on any failure.
FU-018 TAEHV / TAESD preview decoder Fully shipped 2026-05-04 (parts 1 + 2). Tiny VAE for cheap preview decode each step. Part 1 — full-decode VAE swap (backend_service/helpers/preview_vae.py) maps repo → preview VAE id (FLUX.1/2 → taef1/taef2, SD3 → taesd3, SDXL incl. sdxl-turbo + SDXL-Lightning → taesdxl, SD1.x/2.x incl. sd-turbo → taesd, Wan2.x → taew2_2, LTX-Video / LTX-2 → taeltx2_3_wide, HunyuanVideo → taehv1_5, CogVideoX → taecogvideox, Mochi → taemochi, Qwen-Image → taeqwenimage). maybe_apply_preview_vae(pipeline, repo, enabled) swaps pipeline.vae for an AutoencoderTiny, mirrors the stock VAE's dtype + device (live-validated against SDXL-Turbo on MPS — without the device mirror the first decoder pass raises MPSHalfType vs torch.HalfTensor). Part 2 — live per-step thumbnails (backend_service/helpers/preview_thumbnails.py) decodes callback_kwargs["latents"] through the swapped tiny VAE inside callback_on_step_end, scales to ≤192 px, base64-encodes a PNG, publishes to IMAGE_PROGRESS.set_thumbnail / VIDEO_PROGRESS.set_thumbnail. Stride caps emit count at ~8 (image) / ~6 (video) per gen so the polled /api/{images,video}/progress endpoint stays cheap. Handles both standard 4D (B, C, H, W) latents (SD1.5 / SDXL / SD3) and FLUX's packed 3D (B, seq_len, 64) shape via pipeline._unpack_latents (live-validated against FLUX.1-schnell on MPS — 4 thumbnails captured per 4-step gen, all valid base64 PNGs at 192x192). Frontend reads snapshot.thumbnail from useGenerationProgress, renders inside LiveProgress between the bar and the phase list when present. Errors are best-effort: a decode crash never aborts the actual generation — caller catches and falls back to no-thumbnail. LTX refiner private-kwarg fix: the FU-018 part 2 wiring also caught + fixed a pre-existing leak where _invoke_pipeline_with_ltx_refiner was passing __cfg_decay directly into LTXPipeline.__call__ (would have started leaking __preview_vae too). Both private kwargs now stripped in the refiner path.
FU-019 Distill LoRA support (Hyper-SD, FLUX.1-Turbo, lightx2v Wan CausVid) Shipped 2026-05-03; extended Phase 3 with Wan2.2-Distill. LoRA load + fuse path in both image_runtime.py and video_runtime.py _ensure_pipeline. Catalog variants in catalog/image_models.py (FLUX.1-dev × Hyper-SD-8step + Turbo-Alpha) and catalog/video_models.py (Wan2.1 1.3B/14B × CausVid). Phase 3 extension: Wan 2.2 A14B I2V × lightx2v 4-step distill. lightx2v ships full distilled transformers (not LoRAs) for both Wan2.2 MoE experts. New distillTransformer* fields on VideoGenerationConfig carry repo + high/low-noise filenames + precision (bf16 / fp8_e4m3 / int8). _swap_distill_transformers helper downloads both safetensors via huggingface_hub.hf_hub_download, loads via WanTransformer3DModel.from_single_file, and reassigns pipeline.transformer + pipeline.transformer_2. Variant key includes the distill identity so switching variants triggers clean rebuilds. Distill takes precedence over LoRA when both are pinned. Catalog adds: Wan-AI/Wan2.2-I2V-A14B-Diffusers-distill-bf16 + -distill-fp8. Schema-default substitution sets defaultSteps=4 + cfgOverride=1.0.
FU-020 AYS (Align Your Steps) schedule for SD/SDXL Shipped 2026-05-03. New samplers ays_dpmpp_2m_sd15 / ays_dpmpp_2m_sdxl in _SAMPLER_REGISTRY (image_runtime.py). Private _ays_family token stripped from from_config kwargs and stashed on pipeline._chaosengine_ays_timesteps; _build_pipeline_kwargs passes it via timesteps= and pops num_inference_steps. Hardcoded NVIDIA timestep arrays for SD1.5/SDXL/SVD. Flow-match models continue to be gated out by _is_flow_matching_repo.
FU-021 Image-runtime CFG decay parity Shipped 2026-05-03. cfgDecay field on ImageGenerationConfig + ImageGenerationRequest. Linear ramp from initial guidance to 1.5 floor inside the existing callback_on_step_end in generate(). Gated to flow-match repos (_is_flow_matching_repo); SD1.5/SDXL ignore the flag. Default off — opt-in vs. video runtime's default-on.
FU-022 LLM-based prompt enhancer Shipped 2026-05-04 (Apple Silicon path). Replaces the deterministic per-family template-suffix enhancer in _enhance_prompt. Helper backend_service/helpers/prompt_enhancer.py wraps mlx_lm.load + mlx_lm.generate against a small instruct model (default mlx-community/Qwen2.5-0.5B-Instruct-4bit, ~700 MB on disk, ~3s cold load + sub-second per call) — cached in a process-level _EnhancerSingleton so the second call onward hits the warm model. Per-family system prompts (wan / ltx / hunyuan / flux / sdxl / sd3 / default) anchor the rewrite to the DiT's training distribution. family_for(repo) matches longest-prefix-wins. Endpoint POST /api/prompt/enhance (routes/prompts.py) returns {enhanced, note, modelUsed, family}. Frontend exposes a "Enhance" pill button next to the Prompt label in both Studio tabs (components/PromptEnhanceButton.tsx) — click triggers the rewrite + replaces the textarea on success or surfaces a tooltip note when the enhancer fell back. Failure modes (non-Apple platform, mlx_lm missing, model not cached, generation crash, shorter-than-input rewrite) all return the original prompt + a runtimeNote so the user sees why. Live smoke 2026-05-04: 6-word "a fluffy cat on a windowsill" → 16-word FLUX rewrite (3.2s cold), 13-word Wan rewrite (0.12s warm), 8-word LTX rewrite (0.11s warm). 16 unit tests covering family-mapping + happy path + load-failure + generation crash + shorter-rewrite reject + quote stripping. CUDA / Linux still get the legacy template suffix; the helper returns the original + a "requires Apple Silicon" runtimeNote on those platforms.
FU-023 SVDQuant / Nunchaku CUDA engine Foundation shipped 2026-05-05; awaiting live Windows / Linux CUDA validation. Apple Silicon dev box can't exercise the CUDA path live — wiring is in place so a Windows/Linux CUDA pull validates end-to-end. Backend: _try_load_nunchaku_transformer helper in image_runtime.py loads via NunchakuFluxTransformer2dModel / NunchakuQwenImageTransformer2DModel / NunchakuSD3Transformer2DModel / NunchakuSanaTransformer2DModel / NunchakuPixArtSigmaTransformer2DModel — class registry at _nunchaku_transformer_class_for_repo. Preferred over NF4/int8wo on CUDA when nunchakuRepo pinned + nunchaku importable; falls back cleanly on Apple Silicon / CPU / missing package. Variant key extends with nunchaku=... so toggling rebuilds the pipeline. ImageGenerationConfig + ImageGenerationRequest fields: nunchakuRepo, nunchakuFile. Catalog rows: FLUX.1 Dev × svdq-int4-flux.1-dev, FLUX.1 Schnell × svdq-int4-flux.1-schnell. Setup install: nunchaku>=1.2.1 via _INSTALLABLE_PIP_PACKAGES. Wan / HunyuanVideo / LTX wrappers don't exist in upstream Nunchaku v1.2.1 — adding a future video variant is a catalog-row change.
FU-024 FP8 layerwise casting for non-FLUX DiTs Foundation shipped 2026-05-05; awaiting live CUDA SM 8.9+ validation. Apple Silicon can't exercise — Windows/Linux CUDA pull validates. Backend: _maybe_enable_fp8_layerwise helper in image_runtime.py calls transformer.enable_layerwise_casting(storage_dtype=…, compute_dtype=torch.bfloat16) post-load. Family-correct fp8 dtype: E5M2 for HunyuanVideo (per upstream model card recommendation), E4M3 elsewhere (FLUX / Wan / Qwen-Image / SD3 / LTX). Compute capability gate refuses pre-Ada GPUs (SM <8.9) since hardware fp8 isn't there + the cast slows wall-time vs bf16. Helper degrades gracefully when pipeline.transformer.enable_layerwise_casting is missing (UNet pipelines / old diffusers) — runtimeNote surfaced into the load notes. Wired through both ImageGenerationConfig + VideoGenerationConfig + Request models + frontend hooks (imageFp8LayerwiseCasting / videoFp8LayerwiseCasting) + types. Default off; opt-in.
FU-025 mlx-video Wan one-shot convert action Fully shipped 2026-05-04 (Phase 7 + Phase 8 + Phase 9). Closes FU-009 Wan branch. Phase 7 (foundation): [mlx-video] extra in pyproject.toml flipped to git+https://github.com/Blaizzy/mlx-video.git. Helper backend_service/mlx_video_wan_convert.py wraps the upstream python -m mlx_video.models.wan_2.convert subprocess: slug_for(repo) / output_dir_for(repo) / status_for(repo) / list_converted() / run_convert(checkpoint_dir, repo, dtype, quantize, bits, group_size, timeout). Output under ~/.chaosengine/mlx-video-wan/<slug>/ (override via CHAOSENGINE_MLX_VIDEO_WAN_DIR). Phase 8 (routing): mlx_video_runtime.py supported_repos() returns dynamic union of LTX-2 + converted-on-disk Wan repos. _REPO_ENTRY_POINTS adds "Wan-AI/": "mlx_video.models.wan_2.generate". _build_wan_cmd produces the Wan-shaped CLI (--model-dir, --guide-scale string, --scheduler, optional --seed/--steps/--negative-prompt; no LTX-2 flags). generate() picks _wan_runtime_note (flags MoE experts) and skips LTX-2 effective-step / effective-guidance overrides. Phase 9 (GUI): Orchestrator backend_service/mlx_video_wan_installer.py drives preflight → download-raw → convert → verify with structured progress events. Setup endpoints in routes/setup.py: POST /api/setup/install-mlx-video-wan (background-job pattern mirroring /api/setup/install-longlive), GET /api/setup/install-mlx-video-wan/status, GET /api/setup/mlx-video-wan/inventory. Frontend client in src/api.ts (startWanInstall, getWanInstallStatus, getWanInventory). UI panel src/components/WanInstallPanel.tsx lists every supported Wan repo with raw-size hint + converted badge / install button + live InstallLogPanel underneath; rendered in VideoDiscoverTab.tsx above the variant grid. Supported raw repos: Wan-AI/Wan2.{1-T2V-1.3B,1-T2V-14B,2-TI2V-5B,2-T2V-A14B,2-I2V-A14B}. End-to-end UX: user clicks Install → backend downloads + converts in background → runtime auto-detects + routes Wan generate calls through mlx-video. Tests: 21 in test_mlx_video_wan_convert.py, 9 Wan-routing in test_mlx_video.py, 15 in test_mlx_video_wan_installer.py.
FU-026 TaylorSeer + DBCache aggressive cache preset Obsoleted 2026-05-03 by diffusers 0.38 core. Diffusers 0.38.0 (2026-05-01) ships TaylorSeerCacheConfig, MagCacheConfig, PyramidAttentionBroadcastConfig, FasterCacheConfig natively — no cache-dit dependency required. Wired as registry strategies (ids taylorseer, magcache, pab, fastercache) in cache_compression/init.py. Each adapter calls pipeline.transformer.enable_cache(<Config>). UNet pipelines (SD1.5/SDXL) raise NotImplementedError into a runtimeNote, matching the FBCache contract. MagCache is FLUX-only without calibration UX (uses FLUX_MAG_RATIOS from diffusers.hooks.mag_cache); other DiTs raise a "calibration required" message until that UX lands.
FU-027 NVIDIA/kvpress KV cache toolkit (CUDA-side) Setup install action pre-staged 2026-05-05; integration code pending. NVIDIA/kvpress — Apache 2.0, 1.1k stars, kvpress>=0.5.3 registered in _INSTALLABLE_PIP_PACKAGES so the Setup tab can pre-stage the wheel. Integration hooks land separately under cache_compression/kvpress.py once the helper picks an adapter shape (the upstream library exposes presses per technique — e.g. SnapKV / TOVA / KIVI / pyramid — and a Pipeline wrapper that takes a HF transformers model). Apple Silicon stays on TurboQuant-MLX; this is the CUDA-side complement.
FU-028 MTP (Multi-Token Prediction) speculative decoding Deferred 2026-05-10 — upstream MTP-head loader gap on both runtimes. Target: lossless 1.5–2.2× speedup for trained-with-MTP models (Gemma-4 drafters released 2026-05-05, Apache 2.0; DeepSeek V3/R1; Qwen3.5/3.6/Next; Nemotron-3; MiMo-V2-Flash). Blocker on Apple Silicon: mlx-lm 0.31.3 ships stream_generate(..., draft_model=...) for separately-trained draft models but has no native MTP-head loader — Gemma-4-style MTP drafters share activations + KV cache with the target and cannot be loaded as a standalone mlx.nn.Module. Confirmed by inspecting the installed .venv/lib/python3.11/site-packages/mlx_lm/server.py + generate.py — no MTP-specific code paths. Blocker on llama.cpp: PR #22673 (am17an, --spec-type mtp --spec-draft-n-max N) is still in Draft as of 2026-05-10, awaiting at least 2 approving reviews. Third-party path considered + rejected for v1: MTPLX (221 stars, MIT) wraps native MTP for Apple Silicon but ships as an OpenAI/Anthropic HTTP server — chaining HTTP servers from our FastAPI backend has unwanted latency + retry surface. Re-evaluate when: (a) mlx-lm gains a native MTP head loader (track ml-explore/mlx-lm releases), OR (b) llama.cpp PR #22673 merges, OR (c) MTPLX exposes a programmatic in-process Python API. The user-facing speedup is real (live benchmarks: M4 Pro × Qwen3.5-27B-4bit 15.3 → 23.3 tok/s) so this stays high-priority on the queue.
FU-029 KVTC (NVIDIA ICLR 2026) KV cache strategy Deferred 2026-05-10 — CUDA-only upstream, awaiting MLX/Metal port + PyPI release. Targeting OnlyTerp/kvtc (Apache 2.0). PCA + adaptive quantization + entropy coding — 8–32× compression vs the dropped ChaosEngine's 3.7×, peer-reviewed at ICLR 2026, beats TurboQuant by 37% at comparable quality on long-context. Upstream blockers: (a) CUDA-only — repo's roadmap mentions MLX/Metal as "planned" but not yet implemented, so the Apple Silicon dev box cannot validate end-to-end; (b) not on PyPI — distributed as a src.* repo intended for git clone; (c) integration shape is a HuggingFace DynamicCache wrapper (not a llama.cpp cache type), so the existing GGUF lane has no path. Re-evaluate when either upstream ships MLX support or a Windows/Linux+CUDA development box becomes available. Apple Silicon users continue on TurboQuant-MLX (also ICLR 2026, native today).
FU-030 Drop ChaosEngine + RotorQuant strategy slots Shipped 2026-05-10. ChaosEngine (cryptopoly/ChaosEngine — 1 commit upstream, eclipsed by KVTC at ICLR 2026 with the same PCA approach but 8–32× compression vs 3.7×) and RotorQuant (shipped as a misleading alias for TurboQuant — same --cache-type-k turbo{N} flags + same Python module marker) both removed from the registry. Persisted user configs that still reference these ids coerce silently to turboquant via a new CacheStrategyRegistry.resolve_legacy_id helper + module-level _LEGACY_STRATEGY_ALIASES map (cache_compression/init.py). Mirror coercion in frontend (src/components/runtimeSupport.ts LEGACY_STRATEGY_ALIASES + canonicalStrategyId). Two-level llama.cpp fallback chain (was three-level: requested → ChaosEngine → native; now requested → native) in backend_service/inference/llama_cpp_engine.py. Vendored ChaosEngine bundling stripped from scripts/stage-runtime.mjs (3 helper functions removed: stageVendoredChaosEngine + ensureSetuptoolsForPep639 + resolveChaosEngineVendor). Pre-build probe asserts the legacy-id coercion works in CI. [rotorquant] extra removed from pyproject.toml. CHAOSENGINE_VENDOR_PATH env var dropped. Cache strategy speed/quality maps in helpers/cache.py trimmed to remaining strategies.
FU-031 Extend DRAFT_MODEL_MAP for new z-lab DFlash drafters + pin TriAttention Shipped 2026-05-10. z-lab published draft checkpoints for several new families since the last DRAFT_MODEL_MAP audit; the upstream dflash-mlx 0.1.5 release also added the Gemma4 backend (commit 05cc456). Added entries for google/gemma-4-31B-it, google/gemma-4-26B-A4B-it, Qwen/Qwen3.5-122B-A10B, MiniMaxAI/MiniMax-M2.5, MiniMaxAI/MiniMax-M2.7, moonshotai/Kimi-K2.6 (all in dflash/init.py) plus mlx-community/... aliases for each so Apple Silicon quants resolve. New 7 unit tests in tests/test_dflash.py pin the mappings. Same commit also pinned TriAttention to c3744ee6a50522a1559a577f85aef2b165a344f2 in pyproject.toml — previously the [triattention] and [triattention-mlx] extras pulled git+...git HEAD, which made fresh installs non-reproducible whenever the upstream landed unreleased work. Pin matches the v0.2.0 release surface plus the AMD GPU port.
FU-032 TurboQuant+ (TheTom/turboquant_plus) Apple Silicon Metal kernels (watch-closely) Re-evaluate when upstream tags v1.0 release or beats turboquant-mlx-full 0.8.0 on a public M-series benchmark Same author as our llama-cpp-turboquant fork. Adds Walsh-Hadamard rotation (improvement over base TurboQuant's Hadamard-only path) + a sparse-V optimization on M5 Max that achieves 0.93x of q8_0 decode speed at long context while saving 50–64% of KV memory. Reported numbers: turbo3 4.6× compression at +1.06% PPL, turbo4 3.8× compression at +0.23% PPL — comparable to our existing turboquant-mlx-full pin but with newer kernels. Not on PyPI (development install via git clone + pip install -e .[dev]), so adopting it means a vendored or git+url install pattern like dflash-mlx — re-evaluate when upstream publishes a wheel or tags a v1.0. Apple Silicon stays on turboquant-mlx-full for now. 2026-06-15 scan: latest tags are v0.3.2.1–v0.3.2.3 (HEAD 7f601a13). Still no PyPI wheel, still no v1.0 tag. FU-032 trigger not met; updated comparison baseline from 0.3.0 to 0.8.0 since our floor advanced.
FU-033 dflash-mlx pin sync assert in pre-build-check Shipped 2026-05-10. Caught a real bug: pyproject.toml and scripts/stage-runtime.mjs had drifted to different dflash-mlx commit hashes (the dev .venv ran 0.1.5.1 while npm run stage:runtime was bundling 0.1.4.1 into release builds). Both files manually synced to fada1eb; new probe in scripts/pre-build-check.mjs and scripts/pre-build-check.sh regex-extracts the commit hash from both files and fails the build when they diverge. Same probe also took the chance to drop the orphan vendor/ChaosEngine staleness check from both runners — that vendored path was dropped in FU-030 and would never resolve again.
FU-041 Qwen3-Coder-Next-MLX-4bit was mis-canonicalised as Qwen3.6-27B-4bit Shipped 2026-05-10. User-spotted mismatch: their local install at /Users/dan/AI_Models/lmstudio-community/Qwen3-Coder-Next-MLX-4bit was surfacing as canonical repo mlx-community/Qwen3.6-27B-4bit in the diagnostics snapshot, picking up the wrong catalog row and the wrong DFlash drafter. Inspecting the on-disk config.json confirmed the model is Qwen3-Next (architectures Qwen3NextForCausalLM, model_type: "qwen3_next", sparse MoE with 512 experts, hidden_size 2048, ~3B active per token) — fundamentally different from the dense Qwen3.6-27B (qwen3 arch, hidden_size 5120). Root cause: there was no catalog variant for the lmstudio-community community MLX 4-bit conversion of Coder-Next, so the fuzzy matcher in src/utils/library.ts::libraryVariantMatchScore settled for the closest "MLX + 4-bit + Qwen3" entry, which happened to be the unrelated mlx-community/Qwen3.6-27B-4bit row. Fix: (1) added an explicit lmstudio-community/Qwen3-Coder-Next-MLX-4bit variant to the qwen3-coder-next family in backend_service/catalog/text_models.py with the correct params (80B sparse, ~45 GB on disk, qwen3_next family capabilities). (2) Reverted the FU-038 DFlash aliases that wrongly pointed mlx-community/Qwen3.6-27B-4bit / bf16 / 8bit at Qwen/Qwen3-Coder-Next — those quants are the dense 27B Coder and have no drafter today. (3) Replaced them with the correct lmstudio-community/Qwen3-Coder-Next-MLX-4bit alias plus an -Instruct sibling for completeness. New regression tests in tests/test_dflash.py pin both the new alias resolution and that the dense 27B-4bit MUST NOT alias to the MoE drafter.
FU-040 Tool-call parser misses open-only <tool_call> + Qwen3.6-27B false-positive vision tag Shipped 2026-05-10. Surfaced by a Coder-Next chat session: tool calls rendered as raw <tool_call>{"name": "web_search", ...} text in the assistant bubble with no execution, while in a separate turn the "Attach image" affordance appeared even though Qwen3.6-27B is text-only. Three fixes. (1) Tool-call parser widened. Old regex <tool_call>\s*(\{.*?\})\s*</tool_call> required a closing tag and only matched objects. Coder-Next emitted three real-world shapes in a single session: canonical (closed + object), open-only (no </tool_call>), and array-shaped (model hallucinated a list of pseudo-results). The new parser uses json.JSONDecoder.raw_decode on each <tool_call> opener so it consumes the next valid JSON value regardless of close tag, dispatches objects with a name, drops list payloads silently, and continues scanning so a later well-formed call in the same message still lands. 7 new unit tests in tests/test_agent.py pin all three shapes plus the OpenAI-style stringified-arguments path. (2) _strip_tool_call_xml helper removes the JSON region the parser consumed from result.text before the streaming layer hands it to the chat bubble — fixes the "raw XML next to the ToolCallCard" duplication. Applied in both run_agent_loop and run_agent_loop_streaming. 6 new unit tests pin the strip behaviour. (3) Qwen3.6-27B + Qwen3.5 catalog cleanup. Dense Qwen3.6-27B (Coder-Next branding), Qwen3.6-27B-FP8, mlx-community/Qwen3.6-27B-4bit, and the family-level Qwen3.6 + Qwen3.5 entries all carried the vision capability — a copy-paste bug from when the catalog was scaffolded. Vision lives on a separate Qwen3.6-27B-VL variant we do not yet ship; the stale tag was promoting supportsVision: true for every community quant, making ChatComposer render the "Attach image" affordance for a text-only model. Dropped the tag from all five entries.
FU-039 Tool-call arguments: null bricks Chat tab forever Shipped 2026-05-10. Caught by the FU-037 ErrorBoundary: Coder-Next + Tools + What is 17 * 23 plus sqrt(144)? triggered TypeError: Object.entries requires that input parameter not be null or undefined in ToolCallCard (minified _Y). Root cause traced through the boundary's component stack (_Y → Panel <section> → ErrorBoundary → workspace) and the minified source: src/components/ToolCallCard.tsx:116 did Object.entries(toolCall.arguments), but Coder-Next emits {"arguments": null} for tool calls that need no parameters. backend_service/agent.py::_execute_tool_call then evaluated isinstance(None, str) → False and set arguments = None, which serialised into the persisted session. Every subsequent render of that turn crashed the Chat tab — the user could not even read prior history because the boundary fires before any other content renders. Two-layer fix: (1) backend _execute_tool_call now coerces None / empty-string / non-dict shapes to {} at the source so the contract "arguments is always a dict" holds for all consumers; (2) frontend ToolCallCard adds a defensive guard that defaults to {} and renders (no arguments) for genuinely corrupt records (so old sessions stop crashing without a manual localStorage wipe). 4 new unit tests in tests/test_agent.py pin all four null-ish input shapes.
FU-038 Diagnostics cleanup: _free_bytes import, MallocStackLogging spam, Qwen3.6-27B alias Shipped 2026-05-10. Three bugs surfaced by the live /api/diagnostics/snapshot payload from a Coder-Next + Tools repro. (1) backend_service/routes/diagnostics.py imported _free_bytes from backend_service.routes.setup, but the setup package's __init__.py did not re-export it from gpu_bundle.py — the snapshot's extras section reported ImportError: cannot import name '_free_bytes'. Added the re-export. (2) macOS hardened-runtime spawned every Python subprocess with three lines of MallocStackLogging: can't turn off malloc stack logging because it was not enabled. spam (we ship bundle.macOS.hardenedRuntime: true). Hundreds per minute under the metrics poll, drowning out real INFO/ERROR lines. Fixed at source by command.env_remove("MallocStackLogging" / "MallocStackLoggingNoCompact" / "MallocScribble") in src-tauri/src/backend.rs so new builds don't produce the spam. Also added a regex filter (_LOG_NOISE_PATTERNS + _filter_log_noise) in diagnostics.py so the /api/diagnostics/log-tail and snapshot endpoints strip the spam from logs produced by older builds too — existing installs see a clean diagnostic surface without rebuilding. Filter reads 4× the requested line window so 200 useful lines survive even when the raw log is 50% spam. (3) Qwen3-Coder-Next was rebranded Qwen3.6-27B upstream; lmstudio-community MLX conversion's HF metadata reports mlx-community/Qwen3.6-27B-4bit as the canonical repo. model_resolution.resolve_dflash_target_ref prefers canonical, so DRAFT_MODEL_MAP missed and the runtimeNote said DFLASH unavailable for 'mlx-community/Qwen3.6-27B-4bit': no compatible draft model is registered. Aliased the three quant variants (4bit / bf16 / 8bit) back to Qwen/Qwen3-Coder-Next so the existing z-lab/Qwen3-Coder-Next-DFlash drafter resolves. New unit test pins the mapping.
FU-037 Per-tab ErrorBoundary + Tauri devtools in release builds Shipped 2026-05-10. A tool-call in the Chat tab against Qwen3-Coder-Next blanked the entire packaged macOS app — webview reload returned the user to the Dashboard, and any subsequent Chat navigation crashed again. Root cause: the React tree had no error boundary, so a single uncaught render error in one tab tore down the whole <main> content frame. Release builds also did not ship the WebKit inspector, so the user could not pull a stack trace without rebuilding via cargo tauri dev. (1) New src/components/ErrorBoundary.tsxgetDerivedStateFromError + componentDidCatch capture the error, render an inline fallback with the error message, JS stack, component stack, "Try again" reset, and "Copy details" clipboard button. Wrapped around {content} in src/App.tsx keyed by activeTab so switching tabs is its own recovery path. (2) src-tauri/Cargo.toml tauri dep gains the devtools Cargo feature so right-click → Inspect Element opens WebKit devtools in release builds. (3) CSS for .error-boundary lives next to the existing notice banners in src/styles.css — same colour vocabulary. Unit tests in src/components/tests/ErrorBoundary.test.ts pin the static-derive contract so the boundary cannot silently stop catching errors. Frontend errors land in the webview console; backend errors land in the Diagnostics tab + the in-memory app.state.chaosengine log buffer.
FU-034 Hide unrecoverable launch-modal options instead of greying them out Shipped 2026-05-10. The launch settings panel (src/components/RuntimeControls.tsx) used to render every cache-strategy card and the DFlash speculative-decoding toggle for every model + engine combo, with disabled checkboxes + "N/A" badges when an option could not run. That taught users the wrong thing — a disabled card with no install button suggests something they could fix, when the only fix lived outside the app or did not exist at all. New rule: hide options the user has no in-app path to recover. (1) Cache-strategy cards now skip render when the strategy is engine-incompatible (e.g. TriAttention selected on the MLX engine — engine mismatch is fundamental, no install button helps) or when the strategy needs the turbo binary on a GGUF backend without llama-server-turbo present (only fix is scripts/build-llama-turbo.sh outside the app). (2) The DFlash toggle hides entirely when the selected model has no draft in DRAFT_MODEL_MAP or the engine is GGUF (DFlash needs MLX/vLLM). The "DFlash package not installed but model would be supported" case stays visible — the install button gets the user to ready in one click. native always survives. Hardcoded f825ffb install hint string in the DFlash help panel was the same drift bug from FU-033 — fixed alongside (now fada1eb). The popover-side filter (src/components/kvStrategyFilter.ts) already followed this rule, so the modal now matches.
FU-042 i18n Phase 0 — infra scaffold + IME composition fix In progress (started 2026-05-11). Foundation work for full-stack localization. Adds react-i18next + i18next-icu + i18next-browser-languagedetector (FE), Babel (Py), rust-i18n + fluent-bundle (Rs). Creates src/locales/, backend_service/locales/, src-tauri/locales/ directory trees with namespaced JSON / .po / .ftl files. Wires i18n/index.ts provider in src/main.tsx, FastAPI Accept-Language middleware in backend_service/i18n.py, rust-i18n! macro in src-tauri/src/i18n.rs. Adds locale field to settings (TS + Py + Rs). Builds Settings → Language dropdown rendering endonyms. Builds first-launch banner that detects OS lang ≠ en and prompts user. Adds <html lang="…"> sync + CJK font-fallback CSS stack. Writes scripts/i18n-extract.mjs / i18n-validate.mjs / i18n-pseudo.mjs (pseudo-locales en-XA + en-XB dev-only). Hooks i18n-validate into pre-build-check.sh + .mjs. Fixes IME e.isComposing guard on prompt textarea Enter-to-send (latent bug breaking JP/CN/KR IME users). Audits src/utils/format.ts to thread locale arg into Intl.NumberFormat / Intl.DateTimeFormat helpers. Per CLAUDE.md §6, en-only feature commits ship with null placeholders in other locales — runtime falls back to en. Adds THIRD_PARTY_NOTICES.md rows for all 5 new deps (MIT × 3, Apache-2.0 × 1, BSD-3 × 1). Coverage gate is warn-only v1 with dashboard panel in Diagnostics tab. Wraps with FU-043 Phase 1 translations.
FU-043 i18n Phase 1 — anchor locales (en + zh-CN + ja) Follows FU-042 First user-visible localization: ships English baseline + Simplified Chinese (Qwen / DeepSeek / GLM author origin) + Japanese (Sakana AI / ELYZA / heavy Apple-Silicon-tinkerer cohort). Highest-leverage trio given Hugging Face + r/LocalLLaMA demographics. AI-translated (Claude) against a pre-locked GLOSSARY.md per locale pinning tech terms (KV cache, tokens/sec, quantization, inference, cache strategy, etc.) so phrasing stays consistent across 200+ keys. Layout audit pass for CJK glyph density (CJK runs ~30 % narrower than en; verify no fixed-width buttons clip). Live QA matrix: 5 tabs × {happy path, error path, empty state} per locale = ~30 cells.
FU-044 i18n Phase 2 — EU tier (de + fr + es + ru) Follows FU-043 Adds German (DiscoLM / LeoLM origin, privacy-first crowd), French (Mistral / CroissantLLM homeland), Spanish (LatAm + Iberia, Salamandra / RigoChat), Russian (Saiga / Vikhr / IlyaGusev — strong r/LocalLLaMA presence). Wide-string layout pass critical for de (30–50 % wider than en, especially in Setup install descriptions). Slavic 4-form plural pass for ru via ICU MessageFormat (one / few / many / other — different rules than the en plural). Tauri window minimum width may need bump after de audit.
FU-045 i18n Phase 3 — completion tier (ko + pt-BR + zh-TW) Follows FU-044 Closes the top-10 locale set: Korean (Upstage Solar / LG EXAONE / Kakao crowd, heavy benchmark scene), Brazilian Portuguese (large maker base, separate from pt-PT — both vocab + diacritics differ), Traditional Chinese (Taiwan / Hong Kong; TAIDE / Breeze authors, distinct vocab + idiom from zh-CN so it's a separate locale not a variant). Opens contributor PR flow with GLOSSARY.md lock + README contributor docs. Diagnostics tab gets a coverage panel showing per-locale %.
FU-046 i18n Phase 4 — long tail + RTL prep Opportunistic / community-driven Stretch locales as community PRs land ≥80 % coverage: pl (Bielik / SpeakLeash), it (Camoscio / Minerva), uk (DragoMan), tr (Trendyol-LLM), vi (VinaLlama / PhoGPT), nl / sv / cs (smaller but loud cohorts). RTL infrastructure prep for future ar (Jais / Fanar) + he: convert remaining margin-left / padding-right to logical properties (margin-inline-start / padding-inline-end); add dir="auto" to root + per-locale override; rehearse bidi flow via pseudo-locale en-XB. Don't ship ar / he until logical-property migration verified end-to-end.
FU-048 Prefer-GGUF-MTP routing preference when both MTPLX + MTP-GGUF available Re-evaluate when MTPLX exposes an in-process Python API OR when MTPLX framework overhead vs upstream mlx-lm drops below 5% on a public M-series benchmark Colleague feedback (2026-05-16) after root-cause investigation of "MTPLX appears slower than baseline" benchmark: MTPLX's MTP path is real (+29% over MTPLX-internal AR with 60-66% draft acceptance, runtime_mtp_enabled: true confirmed in mtplx_stats) but the MTPLX framework wrapper adds ~25% overhead vs raw mlx_lm.generate, so MTPLX-MTP vs upstream mlx-lm comes out roughly flat (-4% net). GGUF MTP via llama.cpp PR #22673 (FU-047) delivers a measurable +51% over Q8_0 baseline live-validated 2026-05-16 (20.9 vs 13.8 tok/s on ggml-org/Qwen3.6-27B-MTP-GGUF, 60-72% acceptance), no subprocess overhead beyond the existing llama-server we already run. Today RuntimeController._select_engine prefers MTPLX whenever mtplxAvailable && has_mtp_heads_strict(...) — which means a user with both lanes available routes to the slower path. Proposed change: when capabilities.ggufMtpAvailable && capabilities.mtplxAvailable && has_mtp_heads_strict(...), prefer the GGUF MTP lane unless the user explicitly pinned MTPLX. UX decision deferred — needs Settings → "Speculative decoding backend" preference (auto / prefer-gguf-mtp / prefer-mtplx) so power users on Apple Silicon who want the MTPLX paged-attention / fan-control burst features keep that path, while default users get the fastest measurable lane. Re-check posture if MTPLX ships either: (a) an in-process Python entrypoint that skips the HTTP wrapper, or (b) framework overhead drops via the v0.4+ "performance-hot" profile they're prototyping. Until then, the existing prefer-MTPLX heuristic stays — flipping it without the Settings toggle would regress users on MTPLX-Optimized quants that aren't GGUF-mirrored.
FU-047 GGUF MTP speculative decoding via llama.cpp PR #22673 Shipped 2026-05-16. All six plan items landed: --spec-type draft-mtp --spec-draft-n-max <N> wired into LlamaCppEngine._build_command gated on _llama_server_supports("--spec-type") + new is_mtp_gguf_repo helper in _mtp.py; 4 aliases added (2 canonical ggml-org/... + 2 author preview am17an/...); new ggufMtpAvailable capability flag in base.py + detection probe in capabilities.py; catalog entries for ggml-org/Qwen3.6-27B-MTP-GGUF (29 GB Q8_0) + ggml-org/Qwen3.6-35B-A3B-MTP-GGUF (37 GB Q8_0 MoE) in text_models.py; runtimeNote surfaces D2H prompt-processing caveat; 5 new unit tests in test_inference.py. FU-028 stays open for the MLX side. Research: docs/UPSTREAM_RESEARCH_2026-05-16.md. Original deferral notes: Closes the GGUF half of FU-028 (which was deferred waiting on this exact PR). llama.cpp PR #22673 by am17an merged at 2026-05-16T12:06:24Z (merge commit 2555826), shipping --spec-type draft-mtp --spec-draft-n-max N for any model with baked-in MTP heads. Upstream-reported acceptance ~72% with N=3 on Qwen3.6-27B, ~2× tok/s vs no-spec baseline. Canonical MTP GGUFs published under the ggml-org/ HF org: ggml-org/Qwen3.6-27B-MTP-GGUF and ggml-org/Qwen3.6-35B-A3B-MTP-GGUF (each with BF16 + Q8_0 + mmproj; both verified HTTP 200 + sibling list via HF API). The MLX lane via MTPLX (separate mtplx_engine.py subprocess + MTP_MODEL_MAP in _mtp.py) was already shipping; this row wires the GGUF lane. Plan: (1) emit --spec-type draft-mtp + --spec-draft-n-max <get_mtp_draft_n> in LlamaCppEngine._build_command when the canonical repo is in MTP_MODEL_MAP and the binary supports --spec-type (probe via existing _llama_server_supports helper); (2) extend _MTP_ALIASES with the 4 new GGUF repos (2 canonical ggml-org/... + 2 author preview am17an/...); (3) add a ggufMtpAvailable flag to inference/capabilities.py so the UI can show MTP-via-GGUF separately from MTP-via-MTPLX; (4) add catalog entries under the qwen3.6 family in text_models.py; (5) pre-build-check asserts the bundled llama-server exposes --spec-type; (6) E2E matrix runner picks up a new gguf+mtp cell. Upstream caveats to surface in runtimeNote: prompt-processing speed takes a hit due to D2H embedding transfers; --parallel >1 decoding is not yet fully optimised (our default --parallel 1 is unaffected). FU-028 stays open for the MLX side until upstream mlx-lm lands a native MTP head loader OR MTPLX exposes an in-process Python API (the HTTP-server-only path makes it unsuitable for our FastAPI backend chaining). Research write-up at docs/UPSTREAM_RESEARCH_2026-05-16.md.
FU-050 Cache-strategy matrix runner: reasoning-channel capture + max-tokens default Shipped 2026-05-17. Surfaced during the M4 Max 64 GB live sweep against current-gen Qwen3-0.6B smoke targets: _stream_inference in scripts/cache-strategy-matrix.py only captured SSE events with a token key, so reasoning models (Qwen3 / Qwen3.5 / Qwen3.6 / DeepSeek-R1) that emit the <think>...</think> block on the separate reasoning channel produced empty full_text when the entire max_tokens budget was spent inside the thinking block. Fix: capture both token AND reasoning payloads into full_text so the SHA-12 output-hash compares cover reasoning + answer; bump DEFAULT_MAX_TOKENS from 96 → 512 so a 4B-class reasoning model has room for both the think block and a final answer. Same commit also corrected three stale paths in the runner — endpoint /api/generate/stream/api/chat/generate/stream (route moved under /api/chat/); load_resp.loadedModel.cacheStrategy lookup updated to walk runtime.loadedModel (response shape nests under runtime); the FU-030 legacy-alias assertion now compares against actual_strategy extracted from the new path. Final --quick result: 6/6 pass on the runnable cells, 9 skip (all quick=False deferrals for vLLM / MTPLX / GGUF-MTP / DFlash / DDTree / TurboQuant-GGUF). Pre-build gate untouched (it doesn't run the matrix). Trigger / Condition column — closed inline.
FU-051 /api/models/load echoes legacy cacheStrategy verbatim instead of returning the FU-030-coerced canonical id When this row blocks an automated check (currently it's surfaced as a known-bad FU-030 regression line in the matrix Markdown report, not a hard fail). The matrix run revealed that POST /api/models/load with cacheStrategy=chaosengine returns runtime.loadedModel.cacheStrategy = "chaosengine" even though the registry coerces the request to turboquant for actual execution (FU-030). Evidence the coercion really happens at the engine level: in two back-to-back matrix runs against mlx-community/Qwen3-0.6B-4bit, cacheStrategy=rotorquant cacheBits=3 and cacheStrategy=turboquant cacheBits=3 produced identical SHA-12 output hashes (b4337bc07457), proving rotorquant routes through turboquant under the hood. Only the surface response field is stale. Three options, in order of fix surface: (a) cheapest — add an effectiveCacheStrategy field next to the requested cacheStrategy in the loaded-model payload, and update the matrix runner's FU-030 assertion to read it; (b) coerce at the request boundary in routes/models.py::load so cacheStrategy itself is normalised before it lands in loadedModel; (c) keep the API response stable and document the cosmetic echo (cheapest UI-wise but means the matrix-runner assertion stays cosmetic too). Recommend (a) — preserves the user's literal request for telemetry while exposing the registry-coerced id as the canonical one. Files touched: backend_service/state/__init__.py _loaded_model_payload, backend_service/inference/controller.py RuntimeController._build_loaded_model_state, scripts/cache-strategy-matrix.py write_markdown (assertion + effective_strategy column).
FU-052 Cache-strategy matrix: vLLM backend cells + MTPLX MLX cell + GGUF MTP cell (comprehensive testing milestone) Foundation shipped 2026-05-17; awaiting live cross-platform validation. Matrix expanded from 9 → 15 cells (see scripts/cache-strategy-matrix.py): (1) MTPLX MLX cell on mlx-community/Qwen3.5-4B-bf16 — smallest entry in MTP_MODEL_MAP (backend_service/inference/_mtp.py); skips with MTPLX runtime not installed on boxes without the MTPLX venv. (2) GGUF MTP cell on ggml-org/Qwen3.6-27B-MTP-GGUF (29 GB Q8_0) — exercises FU-047's --spec-type draft-mtp path; skips with llama-server lacks --spec-type draft-mtp on stale llama.cpp builds. (3) 4 vLLM cells (backend="vllm") — native + turboquant + triattention on Qwen/Qwen3-0.6B plus DFlash on Qwen/Qwen3.5-4B; skips with vLLM not installed (CUDA-only) on macOS. BackendCapabilities dataclass extended with mtplx_available, gguf_mtp_available, vllm_available, sourced from /api/health.nativeBackends. skip_reason() handles every new spec-dec value (mtplx, gguf-mtp) + backend (vllm) with explicit, debuggable skip strings. Live validation status by platform: Apple Silicon (M4 Max 64 GB) — MLX strategy cells green (6/6), MTPLX + DFlash + DDTree + GGUF-MTP deferred until disk frees + Qwen3.5-4B-bf16 + Qwen3.6-27B-MTP-GGUF land in library (cache disk currently 7.2 GB free / 100% used). CUDA (RTX 4090 / Windows or Linux) — vLLM 4-cell quartet awaits the Windows test runner being driven end-to-end with vllm wheel installed + Qwen/Qwen3-0.6B + Qwen/Qwen3.5-4B in library. Trip-wires to close this row: matrix --full returns >0 pass cells from each new family (MTPLX, GGUF-MTP, vLLM-native, vLLM-spec-dec) on the appropriate platform, with output hashes + tok/s recorded. Tracker stays open until those numbers exist; tightening the assertion bar comes next (FU-051 effective-strategy field).
FU-053 Library status false-positive: distill / sibling variants marked installed when only base repo is on disk Shipped 2026-05-17. Surfaced live: the Video Models tab showed Wan 2.2 I2V A14B · Distill 4-step (BF16) + (FP8) rows with a green "installed" tick + 117.5 GB sized, but du -sh ~/.cache/huggingface/hub/models--Wan-AI--Wan2.2-I2V-A14B-Diffusers-distill-{bf16,fp8} returned nothing — neither distill repo was actually present. Root cause: both catalog variants share repo: "Wan-AI/Wan2.2-I2V-A14B-Diffusers" (the BASE, non-distill repo) and route their distinguishing weights via the separate distillTransformerRepo: "lightx2v/Wan2.2-Distill-Models" field (FU-019 pattern). The validator at _video_variant_validation_error only checked the base repo via _video_download_validation_error + _video_full_precision_weights_validation_error — both passed because the base repo IS on disk. The distill-specific files (distillTransformerHighNoiseFile + distillTransformerLowNoiseFile) were never checked, so any variant that pinned distill weights via the FU-019 swap mechanism was unconditionally marked installed once the base was downloaded. Fix: new _distill_transformer_validation_error helper that, when a variant declares distillTransformerRepo, requires (a) that repo's HF snapshot dir exists and (b) both the high-noise + low-noise filenames are present inside it. Wired into _video_variant_validation_error so distill variants now flip to "Not installed" when the distill weights are missing. New unit test in tests/test_video.py pins both the false-positive regression (base-only → not installed) and the happy path (base + distill snapshot → installed). Live verification: after the fix landed on this M4 Max, the two distill rows correctly dropped out of the "installed" filter. Trigger / Condition — closed inline.
FU-054 Same-repo variants: show actual on-disk size + shared-repo badge Shipped 2026-05-17. Wan 2.2 TI2V 5B GGUF (Q4_K_M + Q6_K + Q8_0) renders as three rows in the Video Models tab, each labelled "31.9 GB". On disk those three share ONE models--QuantStack--Wan2.2-TI2V-5B-GGUF/ dir totalling 12 GB (Q4_K_M=3.2 GB + Q6_K=3.9 GB + Q8_0=5.0 GB), not 95.7 GB as the per-row "31.9 GB" repetition implies. Two surgical changes: (1) Backend _video_variant_for_payload now stat-sizes the specific ggufFile (not the whole snapshot dir) for variants that pin a single GGUF, exposing ggufFileBytes alongside the existing onDiskBytes. (2) UI shows the live on-disk byte size next to the catalog estimate when present, and adds a "shares repo with N other variants" badge when ≥2 catalog variants resolve to the same on-disk repo dir. Avoided a full table restructure — the badge gives the user the "deleting this row does/doesn't affect siblings" signal without dragging in expandable parent rows. Frontend changes confined to VideoModelsTab.tsx; CSS additions land in styles.css under .shared-repo-badge. Trigger / Condition — closed inline.
FU-055 Storage explorer panel in Diagnostics tab — surface top disk consumers in-app Shipped 2026-05-17. Complements Stuff Diver's blind spot on HF cache layout: blobs live at ~/.cache/huggingface/hub/models--*/blobs/<sha> (each a single 5–30 GB safetensors / GGUF shard), but snapshots/<rev>/<filename> are symlinks — third-party scanners that don't follow symlinks miss the real bytes. New endpoint GET /api/diagnostics/storage-top?limit=20 walks every directory under state.settings.modelDirectories (HF cache + AI_Models + ~/Models + user dirs), sums per-repo via du-equivalent path walk with cycle protection (reuses _path_size_bytes from discovery.py), and returns sorted [{path, repoLabel, sizeBytes, lastModified, sourceKind}]. Frontend renders the top-N table in a new "Disk usage" subsection of the Diagnostics tab with a "Reveal in Finder" + "Delete repo" action per row. Cycle protection prevents the mlx-video-wan converted-output dirs (which contain symlinks back to HF cache blobs in some configs) from double-counting. Trip-wire numbers from the M4 Max box on first-run: total ~/.cache/huggingface/hub = 997 GB, top-3 = LTX-2 dev / LTX-2 distilled / LTX-2.3 distilled at ~87 / 87 / 81 GB respectively. Trigger / Condition — closed inline.
FU-056 In-app accelerator install UX (Nunchaku / SageAttention / DFlash CUDA / TriAttention / kvpress + vLLM-via-WSL bridge) Active. Phase 1 shipped on feature/accelerator-install-ux; phases 2-9 pending. The plan: bring every CUDA-side accelerator install in-band so users never need to drop to PowerShell to type pip install <name> — the install affordance lives next to the thing it accelerates (FLUX cards in Image Studio Discover, Wan cards in Video Studio Discover, the chat composer for spec-dec, a one-stop "Boost Pack" panel in Diagnostics for the completionist). The backend pipeline (POST /api/setup/install-package → background or sync install → capability re-probe → UI refresh) is already proven by FU-008 / FU-016 / FU-019 / FU-023 / FU-025 — what's missing is per-accelerator capability flags + the contextual badges/buttons on each feature surface. Phase 1 (foundation, shipped 2026-05-17): new probe module backend_service/inference/accelerators.py with lazy importability + version helpers for nunchaku, sageattention, dflash-mlx, dflash-cuda, triattention, kvpress, plus a Windows-only wsl2_available() shell probe for Phase 8. 11 new fields on BackendCapabilities (base.py) + matching serialization in to_dict. Probes wired into both the cheap placeholder probe and the full _probe_native_backends (capabilities.py) so the frontend gets accurate "Install" / "Installed" state on first paint. 25 unit tests in tests/test_accelerator_capabilities.py pin the present / absent / broken-install matrix. Phases 2-9 planned: (2) reusable <AcceleratorCard> component, (3) Image Studio Discover/Models badges + post-generation suggestion toast, (4) Video Studio Discover/Models badges + LongLive bundle rename, (5) Chat composer hint when CUDA + DRAFT_MODEL_MAP hit, (6) Diagnostics "Boost Pack" panel (one-stop view), (7) per-variant recommendedAccelerators catalog metadata + i18n, (8) Windows vLLM-via-WSL2 bridge (WSL detector + isolated venv install + remote subprocess engine), (9) cache-strategy-matrix runner + pre-build gate integration. End-state UX: fresh user installs ChaosEngineAI → downloads FLUX → sees "🚀 Nunchaku +3× available [Install]" pill on the catalog card → one click → 90s later first generation runs at SVDQuant speed, no terminal required.
FU-049 Python 3.14 support gate Re-evaluate quarterly. Trigger to bump requires-python floor: torch ≥2.6 publishes stable cp314 wheels for darwin-arm64 + win-amd64 + linux-x86_64 (CUDA + CPU) AND mlx-lm + mlx-vlm + mlx-video publish cp314 wheels AND Astral python-build-standalone ships a 3.14 portable build for the Tauri sidecar. Today pyproject.toml declares requires-python = ">=3.10" and the test matrix runs 3.11/3.12 (scripts/e2e_test_suite.py, Windows test guide, CI). Stay on 3.11/3.12 for ship + test until cp314 wheel coverage closes. Why 3.14 is on radar: (a) we already renamed compression/cache_compression/ to avoid shadowing Python 3.14's new stdlib compression namespace pkg — pre-emptive fix landed in v0.8.0. (b) 3.14 ships PEP 779 free-threaded build as stable (still opt-in via python3.14t builds), interesting for the FastAPI parent process but irrelevant for subprocess-isolated MLX / sd-cli / longlive workers. (c) GIL-default 3.14 still gives modest perf wins from tail-call interpreter (~5% on CPython benchmarks) + new sub-interpreter API. 3.14 blockers as of 2026-05-17: (1) PyTorch — torch 2.5 stable + 2.6 nightly do not yet publish cp314 wheels on the full darwin-arm64 + win-amd64 + linux-x86_64 × {CPU, CUDA 12.4, ROCm 6.2, MPS} matrix; most painful single dep since image + video runtimes pin torch. (2) MLX stackmlx, mlx-lm, mlx-vlm, mlx-video currently ship cp310–cp313 wheels; Apple Silicon adoption typically lags CPython release by 1–3 months. (3) CUDA-compiled depsbitsandbytes, flash-attn, sageattention, nunchaku, triattention, vllm-swift, dflash-mlx (git+url, builds from source — needs cp314 cython/setuptools chain too). (4) Tauri sidecar — desktop release builds embed Python via Astral's python-build-standalone; need their 3.14 portable build before bundling. (5) 3.14 stdlib breakage — deprecation removals (e.g. typing.io, typing.re, asyncio.coroutine shim, pkg_resources consequences); needs an audit pass via python -W error::DeprecationWarning -m pytest tests/ on a 3.14 venv. (6) 3.14 compression namespace pkg — already mitigated, but a regression probe should land in pre-build-check once we run CI on 3.14 (assert from cache_compression import registry works on cp314). Plan when gate opens: (a) bump requires-python to >=3.11 first as an intermediate step (drops 3.10, which has no cp314 wheels anyway and lets us drop a few back-compat code paths); (b) add cp314 to GitHub Actions CI matrix alongside cp311 + cp312; (c) add a python -X importtime regression probe to scripts/perf-baseline.py (3.14's tail-call interpreter should improve cold-start by ~3–5%, want to measure); (d) bump requires-python to >=3.11 floor + <3.15 ceiling once green; (e) the Tauri sidecar Python pin advances independently — driven by python-build-standalone releases not pyproject.
FU-057 dflash-mlx v0.1.7 migration (deferred — major API rewrite) Block reserved for the multi-hour migration. Trigger to start: (a) upstream publishes a v0.1.8 with a stability commitment and migration guide, OR (b) we hit a concrete user-visible bug on the orphaned fada1eb pin, OR (c) we need a v0.1.6+ feature like adaptive verify / Gemma4 backend / Qwen3-Next GDN for a shipped model in catalog. Upstream shipped two major releases since our pin: v0.1.6 (2026-05-14) + v0.1.7 (2026-05-17, bstnxbt tweet). v0.1.7 README live-validates Qwen3.6 27B 4-bit M5 Max bench at 2.78–3.06× over mlx-lm baseline across 1k → 16k context with adaptive M block size (M=16 ↔ M=4 dynamic). v0.1.6 added DDTree verify mode upstream, Gemma4 backend (commit 05cc456), Qwen3-Next fused-GDN projection (relevant to FU-041 Coder-Next path), CopySpec candidate reuse, OpenAI server hardening, Apache-2.0 license. Why deferred: our current pin fada1eb is orphaned (upstream force-pushed main during the v0.1.6 release; git ls-remote no longer surfaces the commit, but pip install --dry-run confirms the commit object is still reachable in the upstream object pool so fresh installs do not break — for now). Bumping to v0.1.7 is not a drop-in — direct probe of /tmp/dflash-v17/dflash_mlx/runtime/__init__.py shows: (1) stream_dflash_generate signature reshaped — now requires runtime_context, target_ops, draft_backend as named-keyword-required (raises ValueError if None). Our call site in backend_service/mlx_worker_speculative.py:76 passes target_model / tokenizer / draft_model / prompt in the v0.1.4-era shape and would break immediately. (2) configure_full_attention_split removed entirely (per upstream "Public --split-sdpa and --no-split-sdpa controls were removed; attention routing is now target-owned"). Our backend_service/mlx_worker_lifecycle.py:159 call disappears with no documented replacement. (3) load_draft_bundle still exists at dflash_mlx.runtime.loading.load_draft_bundle but is no longer re-exported from dflash_mlx.runtime.__init__.py (__all__ = ["VerifyConfig", "get_stop_token_ids", "stream_dflash_generate"]). Import path needs full qualification. (4) resolve_target_ops moved from dflash_mlx.runtime to dflash_mlx.engine.target_ops — affects backend_service/ddtree.py:290. (5) ContextOnlyDraftKVCache still in dflash_mlx.model — unchanged. Migration scope when picked up: (a) study dflash_mlx.engine.spec_epoch.stream_dflash_generate_impl + the new VerifyConfig / runtime_context / draft_backend orchestration pattern from upstream examples; (b) rewrite mlx_worker_lifecycle._apply_dflash_runtime to construct a runtime_context + draft_backend and drop the configure_full_attention_split call entirely; (c) rewrite mlx_worker_speculative.run_dflash_speculative to pass the new shapes; (d) update ddtree.py import path for resolve_target_ops (was already partially re-pointed in FU-006); (e) verify the runtime-version-validation in v0.1.6 (auto-invalidates older L2 prefix snapshots) does not break first-run UX — surface a runtimeNote if the L2 cache rebuilds; (f) update pyproject.toml pin + scripts/stage-runtime.mjs pin + FU-033 sync-assert; (g) bench Qwen3.6-27B-4bit on the M4 Max box to confirm 2.78–3.06× claim. Quick path (alternative — if fada1eb reachability becomes a problem): re-anchor pin to the upstream v0.1.5.1 tag commit (18fdb5b) which sits on the new linear history and gives us a reachable named ref without API change. This is a 1-line edit in two files and would not require touching the worker / ddtree code.
FU-058 Bump vLLM floor to >=0.21.0 Shipped 2026-05-17. Upstream v0.21.0 landed 2026-05-15 — 367 commits from 202 contributors. Floor bumped from >=0.8.0 to >=0.21.0 in both the [vllm] and [triattention] extras in pyproject.toml. Relevant to our open tracker rows: (a) Gemma4 MTP (#41745) + MTP for MiMo-V2.5 (#41905) + EAGLE for Mistral (#41024) — vLLM gains the MTP heads natively that FU-028 still can't get on the MLX side; (b) TurboQuant hybrid model + uniform quantization (#39931) — relevant to FU-001 / cache-strategy CUDA parity; (c) Spec-dec with thinking budget (#34668) — reasoning-model spec dec is correct now (Qwen3.5/3.6/R1); (d) Qwen3.5/Mamba hybrid Model Runner V2 (#35520) — matters for FU-031 + Coder-Next; (e) Gated DeltaNet attention for Qwen 3.5/3.6 CPU path (#41025). Breaking build changes upstream (consumed transparently by us): C++20 required (PyTorch compat) — affects FU-052's Windows test runner toolchain; transformers v4 deprecated → must migrate to transformers v5. We do not exercise vLLM on Apple Silicon so this is a no-op locally; FU-052 vLLM cells against Qwen/Qwen3-0.6B + Qwen/Qwen3.5-4B should be re-run on the next Windows / Linux CUDA pull to confirm the matrix still passes against the bumped floor. No code changes required; the [vllm] extra was the only contract point and it stays loose-bounded (>=) so users with newer vLLM are still satisfied.
FU-059 Nunchaku pin correction: >=1.2.1>=0.16.0 Shipped 2026-05-17. The FU-023 pin nunchaku>=1.2.1 in _INSTALLABLE_PIP_PACKAGES (backend_service/routes/setup/init.py:118) was unsatisfiable: pip index versions nunchaku returns max 0.16.1 on PyPI (full history 0.1.0 → 0.16.1, no 1.x line exists). Either the original FU-023 note had a typo, OR upstream pulled / renumbered the 1.x release after the FU-023 tracker line was written. The setup-tab "Install Nunchaku" action would have failed with "No matching distribution" for every user running it after the original FU-023 ship. Floor lowered to >=0.16.0 so the install button actually resolves to PyPI's current 0.16.1 wheel. Comment in routes/setup/init.py updated to reflect the version reset. What still needs verification: the FU-023 wrappers in image_runtime.py (NunchakuFluxTransformer2dModel / NunchakuQwenImageTransformer2DModel / NunchakuSD3Transformer2DModel / NunchakuSanaTransformer2DModel / NunchakuPixArtSigmaTransformer2DModel) need a live CUDA run against nunchaku==0.16.1 to confirm the class API still matches — the original FU-023 work was tested against the (now non-existent) 1.2.1 surface, so the class names may have moved between versions. Can't validate on this M4 Max box; trip-wire stays open until the FU-056 Windows-runner work picks it up.
FU-060 Mock memory-pressure gate in route tests Shipped 2026-05-18. The gate_video_generation + gate_image_generation pre-flight checks in backend_service/helpers/memory_gate.py read live psutil via snapshot_memory_signals(). On a busy dev box (parallel pytest + vitest + Tauri running) the host legitimately sits at >92% memory pressure and the route returns 503 — Memory pressure is 97% — video generation would likely OOM. The 7 VideoGenerateRouteTests cases in tests/test_video_routes.py + the 2 image equivalents in tests/test_backend_service.py (test_image_generate_unloads_idle_video_runtime_first + test_image_generate_rejects_while_video_generation_active) never mocked this dependency, so a half-full RAM box would surface them as flake. Fix: setUp patches backend_service.helpers.memory_gate.snapshot_memory_signals with a fixed (64.0, 10.0) return — 64 GB available, 10% pressure — so every assertion exercises the actual route logic and FastAPI handler shape, not the host OS state. tearDown stops the patch. The function-local import in routes/video.py:335 means the patch lives at the source module rather than video_routes namespace; this works for both the image and video gates since they share the same snapshot_memory_signals helper. Repro: was deterministic when running pytest tests/test_mlx_video.py tests/test_video_routes.py in that order (the mlx-video tests' MLX import allocations briefly pushed the box past the 92% gate).
FU-062 Bump turboquant-mlx-full floor >=0.3.0>=0.4.0 Shipped 2026-05-25 (v0.9.3). Upstream turboquant-mlx-full 0.4.1 on PyPI (installed was 0.3.0, FU-001 pin). v0.4.0 added expert streaming — pages router-selected MoE experts from disk per token, runs models whose weights exceed available RAM. Live-validated upstream against Qwen3.6-35B-A3B (35B sparse) on a 16 GB Mac mini in under 4 GB RAM, output bit-identical to fully-resident model. Compounds with our existing Hadamard rotation + Lloyd-Max codebook K/V compression. Floor bump only — no API changes required, runtime continues to call TurboQuantKVCache with the same signature. Pin lives in pyproject.toml [turboquant] extra. Apple Silicon only (CUDA users stay on the llama-server-turbo binary path via FU-001's parallel track).
FU-063 Bump mlx-vlm floor >=0.4.0>=0.5.0 Shipped 2026-05-25 (v0.9.3). Upstream mlx-vlm 0.5.0 on PyPI (installed was 0.4.4). Minor bump, no API breakage at our call surface (mlx_vlm.load + mlx_vlm.generate from mlx_worker_multimodal.py). Floor bump in pyproject.toml [mlx-vlm] extra; loose >= semantics mean existing 0.4.x installs are still satisfied locally, but fresh installs pick up the newer wheel which carries the upstream Qwen3.5-VL + GLM-4.5V fixes.
FU-064 Add ggml-org/Qwen3.6-{27B,35B-A3B}-GGUF non-MTP catalog rows Shipped 2026-05-25 (v0.9.3). ggml-org published canonical Q8_0 non-MTP companion packs on 2026-05-22 alongside the MTP variants we wired in FU-047. Two new rows in text_models.py qwen-3-6 family: ggml-org/Qwen3.6-27B-GGUF (Q8_0, 29 GB, dense) + ggml-org/Qwen3.6-35B-A3B-GGUF (Q8_0, 37 GB, MoE). Catalog note steers users at the MTP siblings when they want spec-dec. No runtime changes — direct llama.cpp lane, same as the lmstudio-community Q4_K_M variants already shipping.
FU-065 Pin llama-cpp-turboquant to a commit hash instead of branch HEAD Trigger: any user-reported build divergence between two install runs, OR a release-build gate where reproducibility matters more than tracking upstream. scripts/build-llama-turbo.sh + scripts/update-llama-turbo.sh currently clone TheTom/llama-cpp-turboquant at branch feature/turboquant-kv-cache (LLAMA_TURBO_BRANCH env var), then git reset --hard origin/$TURBO_BRANCH. Two installs at different times can ship different binaries — the same drift problem FU-033 fixed for dflash-mlx. Today's branch HEAD is 2cbfdc62a1a047b01377948dfdede8cb6a744866. Plan: add LLAMA_TURBO_COMMIT="${LLAMA_TURBO_COMMIT:-2cbfdc62...}" to both scripts, git checkout "$LLAMA_TURBO_COMMIT" after fetch, surface the hash in llama-server-turbo.version, and add a sync-assert to pre-build-check that compares the build-script pin to a value in pyproject.toml or a dedicated UPSTREAM_PINS.md. Defer because (a) branch is single-purpose with low churn — author is the same TheTom we already trust for turboquant_plus; (b) we already have the v0.9.2 → v0.9.3 release with this code path working. 2026-06-11 release scan: branch HEAD has drifted 2cbfdc62…73eb521daebc85da7c91d37178940b99a5524cf6 — confirms the reproducibility risk this row tracks. Pin still deferred: pinning the drifted 73eb521d is unsafe without a verified test-compile (could ship a broken turbo binary), and reverting-pinning to the known-good 2cbfdc62 drops upstream work. When picked up, pin to a commit that's been build-tested on the M4 Max box. 2026-06-15 release scan: branch HEAD drifted again → 7985f6b90bf19881ab7c7a8444954e91cae36056. Reproducibility risk continues to accumulate. Still deferred pending test-compile.
FU-066 Audit cache-strategy-matrix runner against bumped turboquant-mlx-full 0.8.x When 0.8.0 floor is installed on the M4 Max box or when a user reports a TurboQuant regression. The runner's TurboQuant cell (mlx-community/Qwen3-0.6B-4bit × cacheStrategy=turboquant cacheBits=3) passed against 0.3.0 with output hash b4337bc07457 (FU-051 evidence). 0.4.x expert-streaming + 0.5.x parallel prefetch + 0.8.x Mamba/hybrid arch support are all no-ops for dense 0.6B but may affect MoE models. 2026-06-15: floor bumped >=0.6.2>=0.8.0 in pyproject.toml. Worth a one-time live capture of the TurboQuant cell against 0.8.0 once the wheel is installed locally. Bumped threshold from "0.4.x" to "0.8.x" to track the current floor.
FU-072 Restore vision capability to Qwen3.5 + Qwen3.6 families (reverse FU-040) Shipped 2026-05-28. FU-040 (2026-05-10) removed vision from Qwen3.6-27B + family, asserting the dense model was text-only with vision on "a separate Qwen3.6-27B-VL we don't ship." Re-checking upstream on 2026-05-28: every Qwen3.5/3.6 config.json now ships architectures: [Qwen3_5ForConditionalGeneration] / [Qwen3_5MoeForConditionalGeneration] with vision_config + image_token_id + vision_start/end_token_id — the base models are natively multimodal. mlx-vlm ships qwen3_5 + qwen3_5_moe model support, and the ggml-org/*-GGUF packs include an mmproj-*.gguf sibling (auto-wired by llama_cpp_engine._resolve_mmproj_path--mmproj). The catalog was also internally inconsistent (Qwen3.5-9B tagged vision, Qwen3.5-4B not, same arch). Re-added vision across both families in text_models.py: qwen-3-6 family-level + all 11 variants; qwen-3-5 family-level + Qwen3.5-4B (vision+video, matching its 9B sibling) + lmstudio-community/Qwen3.5-9B-GGUF. Safety net (why this can't resurrect the FU-040 broken-button bug): the composer "Attach image" affordance (ChatComposer.tsx:129) reads the runtime supportsVision, which catalog/capabilities.py demotes to False for the MLX worker (carries no images today) and gates on actual --mmproj resolution for GGUF (llama_cpp_engine.py:737 visionEnabled=attempt_mmproj_path is not None). So the catalog vision tag now drives only the variant-picker / discover badges (capability-in-principle), while the functional button stays runtime-accurate. gemma-4 was already correctly vision-tagged (mlx-vlm gemma4 support) — left untouched. Catalog parses + test_capabilities / test_mmproj_vision green.
FU-075 MLX spec-dec silently broken — stale configure_full_attention_split import Shipped 2026-05-29. Highest-impact bug this sweep. Inspecting the matrix runtimeNotes (not just pass/fail) revealed the MLX DFlash / DDTree / MTPLX cells were passing the weak non-empty-output check while NOT actually running spec-decactual_strategy: native, note dflash-mlx could not be imported (cannot import name 'configure_full_attention_split' from 'dflash_mlx.runtime'). Root cause: dflash-mlx 0.1.5 moved the pre-0.1.5 top-level configure_full_attention_split onto the per-family target_ops adapter (the FU-006 migration that rewrote ddtree.py — but mlx_worker_lifecycle.py:153 was missed). Python evaluates the whole from … import a, b line, so the failed configure_full_attention_split symbol killed the co-imported load_draft_bundle too → _dflash_generator never loaded → every MLX spec-dec path fell back to standard generation for all users. Fix: import load_draft_bundle + resolve_target_ops (both still top-level), resolve the adapter, and call target_ops.configure_full_attention_split(...) only for the hybrid_gdn family (it's a no-op for pure-attention Qwen3/3.5/3.6 — upstream only calls it there). Live-verified after fix: DFlash note "DFLASH speculative decoding active (draft: z-lab/Qwen3-4B-DFlash-b16)", DDTree "DDTree active (budget=16)".
FU-076 MTP tensor probe missed top-level mtp. keys → MTPLX never selected Shipped 2026-05-29. The matrix MTPLX cell routed to the DFlash path instead of MtplxEngine. RuntimeController._select_engine gates MTPLX on has_mtp_heads_strict(repo, path), which calls model_has_mtp_tensors(path) → scans the safetensors index against _MTP_TENSOR_HINTS = ('mtp_heads.', 'mtp_decoder.', 'mtp_emb.', 'model.mtp.', '.mtp.'). Every hint assumes a nested key, but Qwen3.5 / Qwen3.6 ship the MTP head as top-level mtp.layers.* / mtp.fc.weight (no leading prefix) — so the probe returned False on a genuinely MTP-bearing model and MTPLX was skipped. Live-confirmed: model_has_mtp_tensors returned False on the real Qwen/Qwen3.5-4B snapshot. Fix in _mtp.py: also match tensor_name.startswith("mtp."). New test_safetensors_index_with_top_level_mtp_keys in tests/test_inference.py.
FU-077 MTPLX isolated venv had a truncated install (missing server deps) Shipped 2026-05-29. After FU-076 routed correctly, MtplxEngine startup died: ModuleNotFoundError: No module named 'numpy' — and then safetensors, uvicorn, fastapi, pydantic, mlx-lm, rich… The ~/.chaosengine/mtplx-venv was a truncated install (interrupted pip install mtplx), but the installer's verify only ran import mtplx, which succeeds because the server deps are imported lazily by mtplx.server.openai (not at package top level). Fixed the live venv with a full pip install --upgrade mtplx (0.3.5 → 0.3.7, pulled all deps). Hardened scripts/install-mtplx.sh: the verify now imports mtplx.server.openai (the real server entrypoint) and auto-retries a full dependency install once before failing loudly, so a truncated install can't pass silently again.
FU-078 MtplxEngine handed MTPLX a bare repo id instead of the local snapshot path Shipped 2026-05-29. Final MTPLX blocker: mtplx quickstart died with "model is not available locally. Run: mtplx pull Qwen/Qwen3.5-4B" — it resolves a model id against its own registry/cache, not the HF hub cache. mtplx_engine.py set model_arg = path or runtime_target or model_ref, and for raw HF-org repos path is None while runtime_target is the repo id (Qwen/Qwen3.5-4B), so MTPLX got an id it couldn't find. Fix: whenever the candidate isn't an existing local directory, resolve the already-downloaded HF snapshot dir via snapshot_download(model_ref, local_files_only=True) (no network) and pass that. Live-verified: MTPLX now loads + engages (note "MTPLX MTP speculative decoding active (draft tokens: 1, model: Qwen3.5-4B)", reports 17.8 tok/s) instead of failing to start. Also fixed the matrix runner's 0.0 tok/s (read done.assistant.metrics.tokS, not a non-existent top-level tokensPerSecond) + captured dflashAcceptanceRate. Verified-genuine after these fixes: DFlash (33.2 tok/s), DDTree (31.4 tok/s), GGUF-MTP (14.7 tok/s), turboquant MLX/GGUF, triattention, native — all stream real output with real throughput. MTPLX still has one remaining issue → FU-079.
FU-080 Backend cold start dragged in torch via cache-strategy availability probes Shipped 2026-05-29. python -X importtime backend_service.app measured 2.6 s, of which 1.64 s was diffusers.hooks (→ torchtorch._dynamosympy) — blowing the CLAUDE.md "< 2 s backend startup" target. Traced the chain: state init → system snapshot → _get_cache_strategies()registry.available() instantiates every strategy and calls is_available(), and the 5 diffusion strategies (fbcache / taylorseer / magcache / pab / fastercache) answered availability by actually importing diffusers.hooks — pulling the whole torch stack onto the cold-start path on every launch. Fix: new cache_compression/_diffusers_probe.py diffusers_at_least(major, minor) reads the installed version via importlib.metadata.version (metadata only — never executes diffusers.__init__, so no torch). Each is_available() now gates on the version (fbcache ≥0.36, the other four ≥0.38); the real diffusers.hooks import stays lazy inside each apply_* method (still raises a clean NotImplementedError on a broken install). Result: diffusers / torch / mlx are no longer in sys.modules after import backend_service.app, import time dropped 2.6 s → ~0.85 s, and cold-start → first /api/health 200 is 2.34 s (the native-backend MLX subprocess probe was already async — "detection still running" on first health, never blocked startup). Two subprocess-isolated regression guards in tests/test_cache_strategies.py (StartupImportPurityTests) assert neither registry.available() nor import backend_service.app pulls torch/diffusers, so this can't silently regress. All 5 diffusion strategies still report available=True against the installed diffusers 0.38.
FU-079 MTPLX proxy doesn't surface incremental tokens to the chat stream (empty output) Active — MTPLX-specific, lower priority (FU-048: MTPLX is ~flat-to-slower vs the alternatives, which all work). After FU-075–078, the matrix MTPLX cell flipped from "fake pass via DFlash fallback" to engine genuinely engaged but FAIL — empty output: the loaded-model note confirms "MTPLX MTP active (draft tokens: 1)" and the done event carries a real tokS (17.8), but the streamed assistant text is empty (output SHA e3b0c44298fc = the empty-string hash). Confirmed the chat stream's incremental token field IS {"token": "..."} (DFlash/DDTree/GGUF-MTP/native all stream through it fine on the same /api/chat/generate/stream endpoint), so the gap is in MtplxEngine's OpenAI-/v1-proxy → SSE adapter: it surfaces final metrics but not per-token deltas, leaving full_text empty for both the matrix runner AND the real Chat UI. Plan: inspect MtplxEngine.generate / its streaming proxy in mtplx_engine.py, map the mtplx server's /v1/chat/completions SSE choices[].delta.content chunks onto our {"token": ...} event shape. Until fixed, MTPLX loads but produces no visible output — DFlash is the working MLX spec-dec lane for the same models (and faster per FU-048). 2026-06-11 release scan: MTPLX reached v1.0.0 + v1.0.1 (PyPI; was 0.3.5 on this box). The installer (scripts/install-mtplx.sh) is unpinned (pip install --upgrade mtplx), so a fresh install now auto-pulls v1.0.1 — no code change needed. v1.0.0 release notes claim /v1/completions now "streams tokens as they are generated, with real finish reasons and usage", which may resolve this empty-output at the source. Still HTTP-server-only (the FU-048 in-process-API root persists). Action: re-test FU-079 against v1.0.1 with a live MTPLX run (reinstall the mtplx venv → load an MTP model → confirm the chat stream surfaces per-token {"token": …} deltas). If v1.0.0's streaming fixed it, this row closes with no adapter change. 2026-06-15 release scan: MTPLX now at v1.0.4 (was v1.0.1). Installer remains unpinned so fresh installs pick up 1.0.4 automatically. Re-test action unchanged — priority to validate before next release.
FU-074 GGUF MTP speculative decoding had no UI toggle Shipped 2026-05-28. FU-047 wired the GGUF MTP backend (--spec-type draft-mtp, gated on the speculativeDecoding request flag in llama_cpp_engine.py:531) + the ggufMtpAvailable capability flag, but never surfaced a UI control. The launch modal's only spec-dec toggles are DFlash (hidden for GGUF — "not supported with llama.cpp models") and MTPLX (Apple-Silicon MLX only), so a user loading ggml-org/Qwen3.6-27B-MTP-GGUF had no way to enable the lane — only the matrix runner could, by POSTing speculativeDecoding=true directly. The button audit (this turn) caught it. Added an isMtpGgufRepo(repo) helper in runtimeSupport.ts (mirrors backend is_mtp_gguf_repo: MTP-flavoured name on a GGUF repo) + a "GGUF MTP" toggle in RuntimeControls.tsx, shown only when isGgufBackend && isMtpGgufRepo(selectedCanonicalRepo) (FU-034 hide-when-not-applicable). It binds to the same speculativeDecoding flag the backend reads; no cache-strategy lock (GGUF KV cache is orthogonal to MTP draft decode, unlike MLX DFlash which forces native). Also patched the DFlash-availability reset effect (was clearing speculativeDecoding for any non-DFlash model — would have instantly un-ticked the GGUF-MTP box) to keep it on for ggufMtpModelSupported. Old binaries without --spec-type fall back to standard decode + a runtimeNote (backend FU-047 path) — acceptable since the bundled llama-server is current; a future refinement could additionally gate the toggle on the ggufMtpAvailable capability for old-binary boxes (needs the flag threaded through the ~8 RuntimeControls call sites). 8 new isMtpGgufRepo unit tests in runtimeSupport.test.ts. Verified live: matrix gguf MTP (Qwen3.6-27B) cell PASS (sha 74a1eca8b3b4).
FU-073 Matrix MTPLX cell targeted a non-MTP VL model Shipped 2026-05-28. scripts/cache-strategy-matrix.py MID_MLX_MTPLX_CAPABLE was mlx-community/Qwen3.5-4B-bf16 — a VL conversion (ships video_preprocessor_config.json) that carries no MTP heads and is absent from both MTP_MODEL_MAP and _MTP_ALIASES, so the MTPLX cell could never have exercised MTP even with the model on disk (it'd fail the has_mtp_heads_strict tensor probe). Switched to the canonical Qwen/Qwen3.5-4B, which is a direct MTP_MODEL_MAP key (verified mtp.layers.* + mtp.fc.weight in its safetensors index), a catalog variant (so it passes the library_refs check), and downloaded to exercise the lane. Pairs with the FU-070 download-skip classifier so the cell reports honestly on boxes without the model.
FU-071 DDTree availability probe checks pre-0.1.5 symbol names Shipped 2026-05-28. The cache-strategy matrix ddtree spec-dec cell skipped with DDTree runtime not available even though dflash_mlx 0.1.5.1 is installed and backend_service/ddtree.py works. Root cause: dflash.is_ddtree_available() (dflash/init.py) source-greps the installed dflash_mlx.runtime for three required symbols and the list was stale — it required target_forward_with_hidden_states, which dflash-mlx 0.1.5 renamed to the per-family adapter target_ops.forward_with_hidden_capture (the same FU-006 migration that rewrote our ddtree.py to call resolve_target_ops(target_model)). The probe was never updated alongside that rewrite, so it required a symbol that (a) no longer exists in any modern dflash-mlx build (grep -c = 0 in the installed runtime.py) and (b) our own code no longer uses. Confirmed the real contract our DDTree path imports: resolve_target_ops (ddtree.py adapter entry), load_draft_bundle (worker lifecycle), stream_dflash_generate (speculative). Updated required_symbols to those three; dropped the obsolete name + the unused load_target_bundle. dflash.is_ddtree_available() now returns True on this M4 Max box. 4 new DDTreeAvailabilityProbeTests in tests/test_dflash.py mock the runtime source so a future rename can't silently regress the probe again. Note: when FU-057 bumps dflash-mlx to 0.1.7 (which removes configure_full_attention_split and reshapes stream_dflash_generate), this probe + the lifecycle import need re-checking in lockstep.
FU-070 Matrix runner: classify missing-download as SKIP, not FAIL Shipped 2026-05-28. The full scripts/cache-strategy-matrix.py sweep on 2026-05-28 reported the gguf MTP (Qwen3.6-27B) cell as FAILPOST /api/models/load -> 500: Cannot load 'ggml-org/Qwen3.6-27B-MTP-GGUF': No .gguf, .safetensors, or pytorch weights found in HF cache entry. Root cause: the repo had an empty ~/.cache/huggingface/hub/models--ggml-org--Qwen3.6-27B-MTP-GGUF/ dir (4.0 KB, only refs/main, dated May 16 — an interrupted pull), and the runner's skip_reason library check uses caps.library_refs, which is built from the catalog (every variant repo from /api/workspace), not from what's actually downloaded. So a catalogued-but-undownloaded model passes the library check and only errors at load — reported as a product FAIL when it's really a missing download (same false-positive class as FU-053). Fix: new pure helper classify_load_skip(msg) in scripts/cache-strategy-matrix.py matches the backend's 'no weights found in HF cache entry' markers; run_cell now wraps the load call separately and converts that specific error into skipped=True, skip_reason="weights not downloaded (<ref>)" instead of a failure. Genuine load errors (OOM, etc.) still surface as fails. 4 unit tests in tests/test_cache_strategy_matrix_runner.py (ClassifyLoadSkipTests) pin the classification. The dflash/mtplx cells already skipped correctly because their target models (mlx-community/Qwen3-4B-bf16 / Qwen3.5-4B-bf16) aren't catalog variants so they never entered library_refs. To actually exercise the GGUF-MTP lane (FU-047/FU-052 trip-wire), download ggml-org/Qwen3.6-27B-MTP-GGUF first, then re-run full.
FU-069 Bump turboquant-mlx-full floor >=0.4.0>=0.5.0 Shipped 2026-05-28. Upstream turboquant-mlx-full 0.5.0 on PyPI (FU-062 had just floored at 0.4.0 on 2026-05-25). v0.5.0 builds on the v0.4.0 expert-streaming path (FU-062) with parallel expert prefetch — the missing MoE experts for each layer are read on a thread pool (--prefetch-workers, default 8) so SSD latency hides behind compute. Upstream-reported ~1.9× faster decode at a tight cache budget, still bit-identical output. --prefetch-workers 1 restores the serial v0.4.0 behaviour. No API change at our call surface — runtime still constructs TurboQuantKVCache with the same signature; the new flag is converter/runtime-side. Floor bump only in pyproject.toml [turboquant] extra; loose >= so existing 0.4.x installs stay satisfied locally. Apple Silicon only. Folds in the spirit of FU-066 (the matrix MoE-turboquant baseline should be captured against 0.5.0 once the wheel is installed on the M4 Max box).
FU-068 MLX probe timeout 12 s → 20 s Shipped 2026-05-25 (v0.9.3). E2E full-sweep Phase 1 surfaced three intermittent fails on a freshly-booted backend — MLX native cache / MLX TurboQuant cache / fused attention flag all returned MLX backend requested but unavailable: ...mlx_worker probe timed out after 12.0 seconds. Measured cold-start: time .venv/bin/python -m backend_service.mlx_worker probe = 12.43 s on M4 Max / Python 3.11 against current mlx 0.31.2 + mlx-lm 0.31.3 + mlx-vlm 0.4.4 — 0.4 s past the 12.0 s ceiling. The 12.0 s value was an arbitrary default from the v0.8.0 capabilities.py extract (commit f91709e), never tuned. Bumped to 20.0 s in backend_service/inference/capabilities.py _probe_native_backends — ~60% headroom over today's envelope. Phase 5 video gen + Phase 1 GGUF / DFlash / cache-preview already passed (proves MLX itself works once the probe lands), so this was a pure cold-boot probe timing issue, not a regression from the FU-062 / FU-063 floor bumps (which are loose >=, no installed package changed).
FU-067 Watch dflash-mlx for v0.1.8+ migration guide (FU-057 is multi-hour, deferred) Trigger: (a) upstream publishes v0.1.8 with a stability commitment + migration guide, OR (b) we hit a concrete user-visible bug on the orphan fada1eb pin, OR (c) a shipped catalog model needs a v0.1.6+ feature (adaptive verify / Gemma4 backend / Qwen3-Next GDN). Dup of FU-057's trigger but resurfaced after the v0.9.3 upstream scan confirmed v0.1.7 is now on PyPI (pip install dflash-mlx==0.1.7 resolves) and tagged at commit 210a0fc1. Plan-of-record stays FU-057's six-step migration. Re-checking quarterly via git ls-remote --tags for v0.1.8 / v0.2.0 release tags — if upstream publishes a migration guide alongside, the cost drops dramatically. 2026-06-11 release scan: v0.1.9 is now tagged (branch HEAD 7f884380; tags v0.1.5.1…v0.1.9). Still no published migration guide, so FU-057's six-step rewrite stays the plan of record and remains deferred. Newest migration target is now v0.1.9 (was v0.1.7/v0.1.8). 2026-06-15 release scan: v0.1.10 now tagged (branch HEAD 9ca00289). One more release since last scan; migration target advances to v0.1.10. No migration guide published. FU-057 deferred.
FU-061 "Watching upstream" badge + disabled download for tracked-only image seeds Shipped 2026-05-18. User-reported gap: downloaded baidu/ERNIE-Image-Turbo from Image Discover (it sits in LATEST_IMAGE_TRACKED_SEEDS), expected it in the Studio dropdown, didn't appear. Root cause: tracked seeds are discovery-only — Studio's dropdown is fed by IMAGE_MODEL_FAMILIES which requires explicit pipeline routing (flow-match flags, sampler registry, scheduler defaults). ERNIE-Image (+ Nucleus-Image, Z-Image, HiDream, GLM-Image, FLUX.2 family) has no diffusers-routable Studio variant yet. Fix path A picked over path B (full per-family pipeline wiring) — surgical UX disambiguation. Backend: new _is_launchable_image_repo(repo_id) helper in backend_service/helpers/images.py returns True only when repo_id resolves to a curated IMAGE_MODEL_FAMILIES variant. Wired into both payload sites — _tracked_latest_seed_payloads (line 411) + the live-HF lane (line 622) — so every Discover row carries trackedOnly: bool. Frontend: new trackedOnly?: boolean field on ImageModelVariant (src/types/image.ts). ImageDiscoverTab.tsx chip row gains a "Watching upstream" badge + tooltip when trackedOnly. Action column branches first on trackedOnly → renders a disabled IconActionButton with tooltip "Watching upstream — Studio playback for this family isn't wired yet. Catalog entry is for awareness; download won't unlock Studio." instead of the Generate / Download / Resume CTAs. Backward-compat: existing curated families have trackedOnly: undefined → falsy → no UX change. Tests: new TrackedOnlyFlagTests in tests/test_image_discover.py — 5 cases covering _is_launchable_image_repo (FLUX.1-dev + SDXL = true; ERNIE-Image / Nucleus-Image = false; empty = false), trackedOnly: True on ERNIE seed payload, and the negative case where a tracked seed that IS in IMAGE_MODEL_FAMILIES must NOT carry the flag (forward-compat for catalog evolution). Follow-up path B (deferred): wire ERNIE-Image / Nucleus-Image / Z-Image / HiDream / GLM-Image / FLUX.2 family as real launchable families via per-family pipeline detection in image_runtime. Multi-hour per family, gated on diffusers' upstream support landing for each architecture.

Testing Requirements

When Modifying These Areas, Run These Tests:

Area Test File(s) Command
Cache strategies (cache_compression/) test_cache_strategies.py pytest tests/test_cache_strategies.py -v
DFlash / speculative decoding test_dflash.py pytest tests/test_dflash.py -v
Inference / llama.cpp / binary routing test_inference.py pytest tests/test_inference.py -v
Setup routes / install endpoints test_setup_routes.py pytest tests/test_setup_routes.py -v
Backend services test_services.py pytest tests/test_services.py -v
Backend API routes test_backend_service.py pytest tests/test_backend_service.py -v
Cross-strategy E2E matrix runner test_cache_strategy_matrix_runner.py pytest tests/test_cache_strategy_matrix_runner.py -v
Frontend API client src/api.test.ts npm test
Frontend utilities src/utils/__tests__/*.test.ts npm test

Minimum Test Expectations

  • All existing tests must pass — zero regressions
  • New backend features should include at least basic happy-path tests
  • Cache strategy changes must test llama_cpp_cache_flags() returns valid types
  • New API endpoints need at least a shape/contract test

Cross-strategy E2E matrix runner

scripts/cache-strategy-matrix.py sweeps every supported (cache strategy × spec-dec method × representative model) combination through a running backend on port 8876 and writes a CSV + Markdown report to ~/.chaosengine/test-results/. It also asserts the FU-030 legacy alias coercion — requests with cacheStrategy=chaosengine / cacheStrategy=rotorquant must come back loaded as turboquant, and the runner exits with code 2 if either regresses.

# Quick smoke (~5 min on M-series; CI-friendly)
.venv/bin/python scripts/cache-strategy-matrix.py --quick

# Full sweep (~20 min; gates a release)
.venv/bin/python scripts/cache-strategy-matrix.py

The runner skips cells where the strategy isn't installed, the turbo binary is missing, the model isn't in the local library, or the spec-dec method isn't supported on the chosen backend — so a fresh CI box reports honest skip reasons rather than failing.


Code Quality Guidelines

These rules came out of the v0.7.6 → v0.8.0 refactor + audit. Apply them to every PR that touches a backend module > 500 LOC, a hook > 400 LOC, or any file that mutates worker subprocess / file-system / network state. Skip on trivial typo fixes, doc-only edits, and one-line bug patches.

Performance

  • Lazy-import heavy deps. torch, diffusers, mlx, mlx_lm, mlx_vlm, transformers, nunchaku, bitsandbytes, huggingface_hub, gguf are all multi-second imports. Put them inside the function that needs them, not at module top, unless the file is only loaded when inference is about to run. Backend startup target: python -X importtime backend_service.app < 2 s.
  • Process isolation for memory hogs. Models > 1 GB stay in subprocess workers (MLX worker, sd-cli, longlive engine). Never load them in the FastAPI parent — a stuck pipeline takes the whole backend down with it.
  • Always release before reload. Before swapping models, call the engine's unload_model() (or equivalent) so the OS reclaims the RAM before the next snapshot is mapped in. Two 47 GB workers = a 96 GB RAM exhaustion bug, not a feature.
  • No re-render thunder in React. New object literals in useMemo / useState initialisers without a stable dep array are silent performance killers. Run the React Profiler on any tab > 200 LOC of state before shipping.
  • Profile before optimizing. Don't rewrite a hot path on intuition — capture a number first (scripts/perf-baseline.py, the React Profiler, python -X importtime), then validate the win against PERF_BASELINE.md's ±5 % gate.

Security

  • Treat user-controlled paths as hostile. Anything that comes from a request body, a settings file, an env var, or a Hub catalog entry must go through pathlib.Path + .resolve() + a parent-prefix check before being passed to open() / subprocess.run / shutil.copy. Never os.path.join a user string into a system path.
  • List-form subprocess only. subprocess.run([bin, *args]) — never the shell-string form. No shell=True. Quote nothing — let subprocess do the escaping.
  • No secrets in source. No HF tokens, no API keys, no bearer tokens, no signed URLs in *.py, *.ts, *.rs, *.toml, or *.md. Use the Settings store + keyring / Tauri secure storage for runtime secrets. CI builds get keys from GitHub secrets, not commits.
  • Validate at the boundary, trust internally. Pydantic models on the FastAPI request edge + serde on the Tauri IPC edge + Zod on the frontend fetch wrapper. Once the value is past the boundary, internal helpers don't need defensive if not isinstance(...) re-checks — the type system carries the guarantee.
  • GGUF / safetensors are user data. They can be malicious archives on a snapshot a user pasted in. Always load with local_files_only=True when probing, and surface gated/404 errors as user-readable messages, not raw HfHubHTTPError traces.

Modularisation

  • File-size soft caps. Backend modules > 600 LOC, hooks > 400 LOC, components > 500 LOC, Rust modules > 800 LOC are a refactor signal — not an automatic block, but a prompt for the next change to extract before adding. The v0.8.0 pattern is: pull a coherent subset into a sibling module taking dependencies as kwargs, leave thin wrappers in the original site, re-export so test mock paths and existing imports don't break.
  • Single-purpose modules. A file's docstring should fit in one paragraph. If you can't summarise what it does without "and also …", split it. Bundle by responsibility, not by type (don't dump every helper into helpers.py).
  • Re-exports preserve call sites. When extracting from a module that has external callers, re-export the moved symbol from the original module path. Tests that patch module._private keep patching, imports in other packages keep working, and the diff stays surgical.
  • No premature abstraction. Three similar lines is fine. Don't create a BaseEngine / Strategy / Plugin interface for two callers — wait until there are five. Half-finished abstractions cost more than copies.
  • Cross-platform from the first line. pathlib.Path (Python), PathBuf (Rust), path.posix vs path.win32 (Node). Never hardcode /tmp, ~/.cache, or \\ — use the platform-aware primitive.

When to refactor vs ship

  • Bug fix → ship the surgical patch, leave the surrounding module alone.
  • Feature add → if the target file is already over the soft cap, do an extract pass before adding. Otherwise add inline.
  • Refactor pass → bundle multiple extracts in a single PR with a clear phase number (see REFACTOR_PLAN.md for the v0.8.0 template).

Development Patterns

Python Backend

  • Routes use FastAPI APIRouter with type hints
  • State accessed via request.app.state.chaosengine
  • Tests use unittest.TestCase + fastapi.testclient.TestClient
  • Mock runtime with FakeRuntime pattern from test_backend_service.py

TypeScript Frontend

  • Tests use vitest with vi.mock() / vi.stubGlobal()
  • Factory helpers (makeVariant(), makeSession()) for test data
  • API mocking via vi.stubGlobal("fetch", mockFn)

Adding New Dependencies

  1. Check licence (MIT/Apache 2.0/BSD only)
  2. Add to THIRD_PARTY_NOTICES.md
  3. If pip package: add to _INSTALLABLE_PIP_PACKAGES in backend_service/routes/setup.py
  4. If system binary: add to _installable_system_packages() in backend_service/routes/setup.py
  5. Add update-check entry to the upstream dependency table above

Environment Variables

Variable Purpose Default
CHAOSENGINE_LLAMA_SERVER Override standard llama-server path Auto-detected
CHAOSENGINE_LLAMA_SERVER_TURBO Override turbo llama-server path ~/.chaosengine/bin/llama-server-turbo
CHAOSENGINE_MLX_PYTHON Override Python for MLX .venv/bin/python
CHAOSENGINE_LLAMA_BIN_DIR Override llama.cpp build dir for staging ../llama.cpp/build/bin/