wally-e2e
RunanywhereAI/RCLI/.claude/skills/wally-e2e/SKILL.md
Verify a built wally binary against a pinned C++ desktop kit on macOS and Windows. Use when CI smoke/e2e is red, backends are missing, DLLs fail to load, or the Apple MLX host fails to link.
Skill1.6k starsChanged 10 days ago
What's in it
- Wally e2e
- What "green" means
- backends must walk every live primitive
- Windows DLLs
- Apple MLX host link
- Linux
- Private engines
- Device / overlay gotchas (0.5.1 + kit 0.20.28)
---
name: wally-e2e
description: Verify a built wally binary against a pinned C++ desktop kit on macOS and Windows. Use when CI smoke/e2e is red, backends are missing, DLLs fail to load, or the Apple MLX host fails to link.
---
# Wally e2e
Entry: `scripts/test/e2e.sh <path-to-wally>`. Always runs `scripts/test/smoke.sh`, then
`scripts/test/e2e-modalities.sh` (engine-agnostic primitives). Public CI leaves
modality knobs unset so every round-trip **skips**. Device runs set
`WALLY_E2E_<MOD>` / `WALLY_E2E_MODEL_ROOTS` / `WALLY_E2E_AUTO=1`. See
`wally-device-e2e` for ANE/NPU.
| Env | Primitive | Example |
|---|---|---|
| `WALLY_E2E_LLM` / `WALLY_E2E_MODEL` | llm | `mlx-qwen3` or a `*_HNPU` dir |
| `WALLY_E2E_STT` | stt | `whisper-tiny` or `whisper_base_HNPU` |
| `WALLY_E2E_TTS` | tts | `piper` or `kitten_micro_0_8_HNPU` |
| `WALLY_E2E_VLM` | vlm | `smolvlm2` (SDK inserts the media marker) |
| `WALLY_E2E_EMBED` | embed | `minilm` or `embeddinggemma_300m_HNPU` |
| `WALLY_E2E_IMAGE` | image | compiled SD1.5 tree / `sd15` |
| `WALLY_E2E_NEURT_MODEL` | classified by path | `sd15`, a Parakeet ANE tree, or `lfm2-230m-ane` |
| `WALLY_E2E_VAD` | vad | `silero` |
| `WALLY_E2E_RERANK` | rerank | `bge-reranker` |
| `WALLY_E2E_SEGMENT` | segment | `segformer` (P6 PPM) |
| `WALLY_E2E_ENGINE` | override only | `qhexrt` / `neurt` / `mlx` |
Legacy `WALLY_E2E_MLX_MODEL` / `WALLY_E2E_NEURT_MODEL` / `WALLY_E2E_QHEXRT_MODEL`
are classified by path/id into a primitive (not always image). Do not add new
engine-named knobs.
`scripts/test/assert-binary-backends.sh` greps `nm`/`llvm-nm`/`dumpbin`/`strings`
for registrar symbols (`raMLXRegisterRuntime`, `rac_plugin_entry_neurt`,
`rac_plugin_entry_qhexrt`, …) so a backends() listing cannot pass without
the engine actually being linked into the bottle.
Pass `WALLY_SDK_KIT` so overlay backends (`neurt` / `qhexrt`) are required
when those libs are in the kit. `CMAKE_PREFIX_PATH` is only used for
`HAS_*` flags and Windows DLL staging — an ambient overlay prefix must
not make a public OSS bottle fail for missing NeuRT.
## What "green" means
`scripts/test/assert-backends.sh` requires every engine the kit actually ships:
| Condition | Required `wally --json backends` name |
|---|---|
| no kit Config (public OSS bottle) | `llamacpp` + `onnx` + `sherpa` |
| kit `RunAnywhere_HAS_LLAMACPP` TRUE | `llamacpp` |
| kit `RunAnywhere_HAS_ONNX` TRUE | `onnx` |
| kit `RunAnywhere_HAS_SHERPA` TRUE | `sherpa` |
| Darwin arm64 product binary `wally` (not `wally-cxx`) | `mlx` |
| overlay `lib/librac_backend_neurt.a` / `rac_backend_neurt.lib` | `neurt` |
| overlay `lib/librac_backend_qhexrt.a` / `rac_backend_qhexrt.lib` | `qhexrt` |
Do **not** drop sherpa from the expected list to make 0.20.26 Windows green
while `HAS_SHERPA` is TRUE. That kit compiled sherpa with speech ops off
(`RAC_SHERPA_ROUTABLE=0`): `rac_backend_sherpa_register()` returned SUCCESS,
`capability_check` returned `BACKEND_UNAVAILABLE`, the registry refused the
plugin. The fix is pin a routable kit (0.20.28+), not weaken the assertion.
## `backends` must walk every live primitive
`src/commands/cmd_backends.rs` iterates `1 .. RAC_PRIMITIVE_COUNT-1`, skipping
retired wire value 6. ONNX without RAG only advertises SEGMENT / DIARIZE. A
hardcoded GENERATE_TEXT / TRANSCRIBE / EMBED list made onnx invisible even when
the plugin was registered. Do not reintroduce a primitive allow-list.
## Windows DLLs
Win32 `LoadLibrary` searches the exe directory, then PATH. `e2e.sh` copies
`third_party` / `bin` / `lib` `*.dll` next to `wally.exe` and prepends those
dirs to PATH **before** smoke. Skipping that produces "llamacpp only" even when
the kit contains `rac_backend_onnx.lib` + `onnxruntime.dll`.
Never pass `onnxruntime.dll` to `link.exe` (LNK1107) — link the import lib;
stage the DLL at runtime.
GitHub Windows: `GITHUB_WORKSPACE` is `D:\a\...`; msys `tar -C` needs
`cygpath -u` (`fetch-kit.sh` already does).
## Apple MLX host link
`cmake/WallyRust.cmake` gets the kit's link line without hand-parsing Ninja: it
queries the CMake file API (`cmake_file_api(QUERY API_VERSION 1 CODEMODEL 2)`)
against `wally_link_probe`, a target configured
but never built that carries the same kit closure the old C++ `wally`
executable had. `build.rs` (`link_native`/`probe_link_args`) reads that reply,
drops compile-only fragments (`-D`/`-I`/`-O`/…), hands the rest to `cargo` as
`rustc-link-arg`s for every artifact it links, and — when
`WALLY_NATIVE_LINK_ARGS_OUT` is set (CMake sets it) — writes the same list to
`build/wally-native-link-args.txt`. `scripts/build/build-mlx.sh` reads that
file and turns each fragment into an `xcodebuild` `OTHER_LDFLAGS` token, then
links `build/cargo/release/libwally.a` (the crate's staticlib, built with the
same fragments) against it. No manual `ninja -t commands` harvesting, no
`bundle-core.sh` merge step — both are gone.
- swiftc (Xcode 27) rejects raw `-Wl,` options and ignores bare archive paths
in `OTHER_LDFLAGS`, so `build-mlx.sh` sends everything aimed at `ld` through
`-Xlinker`; `-l`/`-L`/`-F` and `-framework` are swiftc options and pass
through as-is.
`scripts/build/build-mlx.sh` must dump the xcodebuild log on failure (`Undefined
symbols` does not contain `error:`). Do not grep bare `error:` — every
CompileC line contains `-Werror=`. Observed CI `32786359915`: grep
`error:|Metal|BUILD` left only `clang: error: linker command failed`.
Never put `#` comments in a `\`-continued `xcodebuild` invocation. Bash
cuts the command there, so `OTHER_LDFLAGS` and the log redirect never
run (empty `xcodebuild-mlx.log`, status taken from a later assignment).
Link flags that must survive the Swift host:
- The kit's plugin backends (static registrars) arrive already force-loaded in
the fragments CMake recorded for `wally_link_probe` — `build-mlx.sh` does not
re-derive which archives need `-force_load`, it replays what CMake linked.
- `-L$KIT/third_party -lonnxruntime` and `-Wl,-rpath,$KIT/third_party` must
both survive the flag rewrite, or the Swift host abort-traps at launch
(`Library not loaded: @rpath/libonnxruntime.dylib`).
- The crate's own native dependencies (Rust std, native-tls's
`Security.framework`, `-liconv`) and the C++ runtime (`-lc++`, the kit links
through the C++ driver) go on the link line after the kit's own flags
(`build-mlx.sh`'s `rust_native` array).
- Canonicalize `WALLY_SDK_SWIFT_PATH` with `cd && pwd`. SwiftPM's local package
identity is the **directory name**, so `…/EXTERNAL/Wally/../..` registers as
`..`. Nested checkouts named `sdks1` must use that name in
`.product(..., package:)`.
Do not point `WALLY_SDK_SWIFT_PATH` at an unreleased `Package.swift` whose
`sdkVersion` zips 404 (`v0.20.28` before publish). CI checks out the **tagged**
SDK tree (`ref: v$SDK`) whose binaryTargets already exist.
CI macOS runner is **macos-26** (Xcode 26 / Swift 6.2). The MLX host resolves
`RunanywhereAI/runanywhere-sdks` `Package.swift`, which is
`swift-tools-version: 6.2`. macos-15 is Xcode 16.4 / Swift 6.1 and fails after
a successful libtool merge with `package 'runanywhere-sdks' is using Swift
tools version 6.2.0 but the installed version is 6.1.0`. macos-14 is Swift
5.10. `release.yml` must use the same runner as `ci.yml`.
## Linux
Linux bottles are not a v1 merge blocker. Windows x64 and macOS arm64 are.
`scripts/test/e2e-linux.sh` exists for later.
## Private engines
NeuRT / QHexRT only appear in `backends` when the overlay was applied.
`scripts/test/e2e.sh` requires `neurt` / `qhexrt` when
`lib/librac_backend_neurt.a` or `lib/rac_backend_qhexrt.lib` exists — not by
grepping packaged `HAS_NEURT FALSE` (that stays false; find_package flips it
when the archive is present). Public CI must pass without overlays. Image gen
(`cmd_image.rs`, gated `#[cfg(wally_has_neurt)]`) is compiled out unless NeuRT is present.
## Device / overlay gotchas (0.5.1 + kit 0.20.28)
Public bottles never list `neurt` or `qhexrt`. That is the product, not a
test gap. Overlay-rebuild the product binary (`WALLY_APPLE_MLX_HOST=ON` on
Mac; ARM64 MSVC + QHexRT overlay on Snapdragon).
- **`CMAKE_PREFIX_PATH` is not an overlay opt-in.** Only `WALLY_SDK_KIT`
makes e2e require `neurt`/`qhexrt`. An ambient overlay prefix from a
previous rebuild will otherwise fail a public-bottle run.
- **Binary assert:** never `nm | grep -q` under `pipefail` (SIGPIPE → false
FAIL). Stream `strings -a` / `nm -a`. Darwin MLX proof is
`mlx-swift_Cmlx.bundle` next to product `wally` (`nm -gU` misses Swift
host symbols). First C++ `rac_plugin_register(mlx)` logs `-811`; Swift
callbacks then register MLX — noisy, not a miss.
- **Windows ARM64 public/overlay kits have `HAS_LLAMACPP FALSE`.** Do not
require `llamacpp` in e2e. Overlay `wally.exe` listing **only** `qhexrt`
(priority 150) is correct. On-disk GGUF (`qwen3.5-2b`) cannot run there.
- **QHexRT generate needs QAIRT matching the device skel, not the overlay
DLL set.** Snapdragon X2 Elite / Hexagon v81: `QNN_SDK_ROOT` +
`ADSP_LIBRARY_PATH=%QNN_SDK_ROOT%\lib\hexagon-v81\unsigned`, copy
`aarch64-windows-msvc` `QnnHtp.dll` / `QnnHtpPrepare.dll` /
`QnnHtpV81Stub.dll` / `QnnHtpV81CalculatorStub.dll` / `QnnSystem.dll`
next to `wally.exe`. Overlay 2.47 DLLs vs device 2.41 skels fail; QAIRT
**2.48** worked. Pass the `*_HNPU` directory (`--engine qhexrt`), not a
GGUF. FastRPC `openSession` timeouts (~90s) then user-driver fallback
are normal; a second generate while DSP is wedged fails with
`Skel failed to process context binary` / `0x3ea` — `taskkill wally.exe`
and use a `.bat` with **fully expanded** `ADSP_LIBRARY_PATH` (nested
`%QNN_SDK_ROOT%` in `cmd /c "set A=…&& set B=%A%\…"` does not expand).
- **VS on the ARM64 box may be 2026 / 18 Community**, not 2022:
`C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvarsarm64.bat`.
CMake/Ninja live under VS CMake extensions; they are not on default PATH.
- **v0.20.28 Windows ARM64 public kit omits `libcurl.lib`.** Copy from
`arm64-windows-static` into the kit `lib/` before linking (fixed in the
SDK packager for the *next* kit; do not retag 0.20.28). Wally already
links kit `libcurl.lib` when present.
- Published product bottles: macOS `wally-$V-macos-arm64.tar.gz`, Windows
**x86_64** zip, Windows **arm64** zip, and Linux **x86_64**
`wally-$V-linux-x86_64.tar.gz`. NPU (NeuRT/QHexRT) is overlay-only on any
platform.
- **The private QHexRT overlay tarball used to ship zero skel files** (only
`.dll`/`.lib`, no `.so`/`.cat`) — `rac-cli`'s own overlay build could not
run `qwen3.8-27b-1bit-npu` (the Bonsai/Maple ternary decoder) out of the
box; validating it required hand-copying `librun_main_on_hexagon_skel.so`
+ `.cat` in from the `electron-qhexrt` npm package as a workaround. Fixed
in `runanywhere-sdks`' `scripts/build/package-private-engine-overlay.sh`
(widened the copy filter and added a pass for `dsp/win-arm64/`). **Wally
itself never had the `ADSP_LIBRARY_PATH` bug the Electron binding had** —
`fastrpc_win.cpp`'s `exe_dir()` fallback naturally resolves for `wally.exe`
because dependent DLLs/skels are staged flat beside the executable by this
repo's own packaging convention — but that protection is a property of the
*packaging layout*, not of Wally's code, so it is not something to assume
going forward. **Always build a fresh overlay from the actual release
script and run the ternary model against it after any SDK kit-pin bump**
that touches QHexRT — do not assume last time's manually-patched overlay
is still representative of what a real user's overlay build produces.
- **The private overlay tarball must be EXTRACTED ON TOP OF `kit/`, merging
into the same directory tree (`overlay/bin/*` → `kit/bin/`, `overlay/lib/*`
→ `kit/lib/`, `overlay/include/*` → `kit/include/`, `overlay/share/...` →
`kit/share/...`) — never kept as a separate sibling `overlay/` directory
fed to CMake via a second `CMAKE_PREFIX_PATH` entry.** `wally_stage_windows_runtime_dlls()`
(`cmake/RunAnywhereSDK.cmake`) only ever copies from
`${RunAnywhere_LIBRARY_DIR}/../bin` — i.e. `kit/bin` — so a same-named
`overlay/bin` sitting next to `kit/` is silently never consulted. Worse,
this fails **completely silently**: the build succeeds, `wally.exe` links,
and `wally backends --json` returns `{"backends":[]}` with no error naming
QHexRT at all (`find_library`-style detection in `RunAnywhereSDK.cmake`
just doesn't find `kit/lib/rac_backend_qhexrt.lib` because it was never
copied there). If a fresh overlay build reports zero backends, check this
BEFORE suspecting the overlay tarball's contents.
- **`qwen3.8-27b-1bit-npu`'s `HostOpFailed` had THREE compounding causes,
found and fixed one at a time — a kit-pin bump to v0.20.31 alone was NOT
enough; Wally needed its own additional fix (below) even with a perfectly
merged overlay.**
1. The overlay-skel-files-never-shipped bug (above), fixed upstream in
`runanywhere-sdks`' overlay packaging script.
2. `qhexrt::qnn::Backend::profile()` — called (via the same
`engines/qhexrt/qhexrt_session.cpp` this repo statically links, same as
the Electron binding) to pick the `v75`/`v79`/`v81` manifest directory
before the manifest is even parsed — shared its device query with the
code path that opens a real QNN HTP device, so the ternary decoder's
`host_only` manifest paid for a live QNN device it never needed. Fixed
in `neurun` v0.20.31 (`Backend::profile()` no longer shares
`ensure_device()` with `device()`) — see that repo's
`qhexrt-profile-must-not-create-live-device` KB finding.
3. **Wally-specific, and NOT fixed by the kit-pin bump alone**:
`copy-overlay-dlls.cmake` globbed `*.dll` only, so even a correctly
merged overlay (per the bullet above) left the Bonsai skel's `.so`/
`.cat` sitting in `kit/bin/` and NEVER staged next to `wally.exe` — the
one place `fastrpc_win.cpp`'s `ADSP_LIBRARY_PATH ∪ exe_dir()` search
actually looks. Fixed by widening the glob to `*.dll *.so *.cat`.
**`fastrpc_win.cpp`'s `SET_PATH`/`GET_PATH` both returning a non-zero rc
(`0x14`/`AEE_EUNSUPPORTED`) is EXPECTED and HARMLESS on this driver
(`libcdsprpc` 11.1.4 simply doesn't implement that control call — see that
file's own header comment) — do not treat it as a symptom of anything.**
This was chased as a diagnostic signal once and wasted real device time;
the only signal that matters is whether `remote_handle64_open` for the
skel itself returns non-zero (`0x80000406` = `AEE_EUNABLETOLOAD`, which
that same file's header comment exhaustively catalogs the causes of —
missing skel, missing/wrong/stale `.cat`, or — as this entry adds — the
pair never being in the searched directory at all).
Confirmed fixed end to end on a Snapdragon X2 Elite with all three fixes
in place: `wally run --engine qhexrt` against `qwen3.8-27b-1bit-npu` opens
the cDSP session and generates correctly ("The capital of France is
**Paris**.", 0.105 tok/s, 12425 DSP linears).
More agent context in RunanywhereAI/RCLI
12 other files this repository gives its agents.
AGENTS.md
CLAUDE.md
Skill
- wally-architecture.agents/skills/wally-architecture/SKILL.md
- wally-device-e2e.agents/skills/wally-device-e2e/SKILL.md
- wally-e2e.agents/skills/wally-e2e/SKILL.md
- wally-kit-pin.agents/skills/wally-kit-pin/SKILL.md
- wally-release.agents/skills/wally-release/SKILL.md
- wally-architecture.claude/skills/wally-architecture/SKILL.md
- wally-device-e2e.claude/skills/wally-device-e2e/SKILL.md
- wally-kit-pin.claude/skills/wally-kit-pin/SKILL.md
- wally-release.claude/skills/wally-release/SKILL.md
- runanywhereskills/runanywhere/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

