--- name: llamacpp-adapter-swap description: "Add or roll back a GGUF LoRA adapter on a dank-fleet llama.cpp endpoint (e.g. frank-zealot): provenance census, fork-flag gate, fetcher+env+deployer surface, seam tests, and the behavioural before/after probe that is the only real proof." --- # Applying a GGUF LoRA adapter to a fleet endpoint An adapter is **payload, not shape**: it changes what the endpoint answers and nothing in the KV/budget equation. Fleet law still applies — no benchmarks unless Frank orders one, rollback is one env line, `./tests/run.sh` before commit, deployer health gate + e2e gate. ## 1. Census the artifact BEFORE writing any code ```bash curl -sS "https://huggingface.co/api/models/?blobs=true" # size + LFS oid (None => plain git object) curl -sSL -o /tmp/a.gguf "https://huggingface.co//resolve/main/" && sha256sum /tmp/a.gguf ``` Parse the GGUF KV section (15 lines of struct.unpack) and read **provenance**: abliteration adapters carry `prism.abliterate.base_gguf` / `base_sha256`, `adapter.type`, `sites`, `target_layers`, `target_embedding`. Compare the declared base against the file the endpoint actually serves (its sha is in the endpoint's own fetcher script). A mismatch is normal for sibling quantizations — the loader matches **tensor names, not hashes** — but it must be stated in the docs, because it means the card's measured numbers are not this endpoint's numbers. ## 2. Gate the runtime, from the binary's own `--help` On the host: `/home/dank/GitPackages//llama-server --help | grep -iE 'lora|adapter'`. Confirm `--lora-scaled FNAME:SCALE` exists (and `--lora-init-without-apply` + `POST /lora-adapters` if a runtime toggle is ever wanted). Vendor forks gate adapters by metadata version too — e.g. PrismML builds older than `prism-b10777` refuse packs with `unsupported prism.hadamard.version: 2`. Read the card for the build it verified on and put that in the fetcher header. ## 3. Surface (five files, copy the mmproj convention) - `scripts/.env`: `LORA_FILE=` + `LORA_SCALE=1`; document that **`LORA_FILE=` (empty)** is the rollback and `LORA_SCALE=0` is the inert-but-present mode. Use `${VAR=default}` (no colon) so an explicitly empty value survives — same reason `MMPROJ_FILE`/`PRESENCE_PENALTY` do. - `scripts/serve-.sh`: `EXTRA_ARGS+=(--lora-scaled "${LORA_FILE}:${LORA_SCALE}")` — **one argv token**, so a scale can never arrive without its file; refuse a missing file by name (`Restart=always` turns an unfinished fetch into a crash loop that reads like a runtime fault). - `scripts/download--.sh`: copy the sibling fetcher (`-fsSL --fail-with-body`, byte count, sha256, `--verify-only`, idempotent). No LFS oid => pin the content sha. Header carries the provenance finding. - `scripts/deploy-remote.sh`: `LLM__LORA="${LLM__LORA:-}"` + a check whose error names **this** fetcher, not the payload's. - `scripts/deploy-plan.py` needs nothing: fetchers are host-side state and select no service. ## 4. Seam tests (argv is the contract) Stub-engine harness: create the adapter stub from the **shipped env's basename**, emit `LORA_FILE`/`LORA_SCALE` in the generated test env, then assert on output only — `--lora-scaled` value equals `:`; a distinctive `LORA_SCALE` (0, 0.75) changes the token; `LORA_FILE=` yields **absence** of the flag (`! grep -qxF -- '--lora-scaled'`); a missing file exits non-zero naming that path; extend the env↔deployer anti-drift pair list. ## 5. Deploy and prove it behaviourally Capture the **before** state on the live endpoint first, then: merge → on host `git pull` + run the fetcher → `./scripts/deploy.sh --service `. ```bash sudo systemctl show -p DropInPaths --value # must be empty (bench-drop-in law) sudo tr '\0' ' ' < /proc/$(sudo systemctl show -p MainPID --value)/cmdline sudo journalctl -u --since | grep -iE 'adapter|lora' # llama_adapter_lora_init_impl prints prism.* keys ``` Refusal probe (the only proof of an abliteration adapter): same prompts, `temperature 0.1`, **`max_tokens >= 2048`** — on thinking models a short budget is consumed by `reasoning_content` and `content` comes back empty with `finish_reason: length`, which reads as a refusal that never happened. Classify with a marker regex and report refusal-vs-complied; do not paste generated harmful content into chat. Also run the standing trio: exact-phrase chat, `tool_calls` with valid JSON, and a >=256 px solid-colour vision probe. A residual `⚠️ Important Disclaimer` heading after the swap is style, not the refusal circuit. ## Docs Endpoint page (`serverAppConfigs-docs/LLM/.md`): Adapter row, Fetch row, Flags row, one Era row whose rollback is the env line, and a measured before/after paragraph. Laws worth adding: provenance is metadata not a gate; an `token_embd` rank would crash, so an adapter is not inert; the runtime pin may gain a second reason. Never restate the card's benchmark table.