Files
serverAppConfigs/serverAppConfigs-docs/Operations/Payload and Disk Reclaim.md
Frank 846f81ddb8 docs(vault): the work log keeps incidents and laws; the numbers move to the page that owns them
log.md drops 466 lines of per-swap ledger prose (1,169 lines out, 703 back) and
keeps the causal stories: why the host went down, why a knob resets at boot, why
a stale drop-in served a different model for 24 minutes, why the RAM was the
fault. Date sections are unchanged (65 before, 65 after) -- this is compression
inside sections, not deletion of days.

Every number the compression would have orphaned was ported to its owner first,
and a census of the deleted lines against the whole vault is what found them:

- frank-zealot: the two boots that priced non-KV (20,190/KV 12,800 => 7,390
  without the projector; 22,972/KV 14,113 => 8,859 with it, so the vision tower
  costs ~1.4 GiB for a 918 MB file), the 1,153 MiB-free 3-slot boot that made
  the seam price non-KV at 9 GiB, and the f16 wall's own figures (fit 33,198 vs
  23,558 free on a 25,088.00 MiB KV request).
- frank-archon: the pool-cut-is-headroom-not-arena law (2.75 GiB/rank bought
  zero slots; d(reserve) = d(arena) = +248 slots; 14.87 GiB / 6,496 slots
  against a 398,658-token pool), the MNBT compile-RANGE vs SEQS+1 law, and the
  2 x 196,919 prefix-cache proof (393,837 tokens resident above the pool's
  386,904; 94.1/102.3/117.6 t/s, acceptance 0.7284/0.7525).
- frank-dragoon: the KL arm of the 2026-10-17 A/B (mean 0.155 / median 0.057,
  top-1 agreement 85.1 %) joins the PPL and NIAH figures already on the era row.
- Payload and Disk Reclaim: the 121 GB layer census lesson -- a huge image layer
  can be the engine image, not a payload.

Also fixes a wikilink the rewrite had hard-wrapped across two lines, which the
link checker reads as a target that does not exist.

New seam: 'one page per live LLM endpoint'. The live set is derived from
scripts/ (an endpoint has both an env and a unit), and any <endpoint>-*.md page
in the vault fails it -- the enforcement of the one-page-per-endpoint law, so a
re-created era page cannot pass tests.
2026-10-19 01:05:32 -07:00

210 lines
9.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: reference
title: Payload and Disk Reclaim
description: Standing procedure for disk pressure on a fleet host — measure all buckets first, classify against the runbook index, prove nothing live references the payload, stage to trash with fingerprints, purge only on approval; plus the purge-and-refresh protocol with fetch verification.
tags: [operations, disk, podman, payloads, huggingface, trash]
timestamp: 2026-10-05T00:00:00-07:00
---
# Payload and Disk Reclaim
Standing procedure for "clean up old models" / "disk is full" on a
serverAppConfigs host. Order matters: **measure, prove nothing live uses it,
stage, ask.** Staging frees nothing until the user approves the purge — say so
up front or the request reads as unfulfilled.
## 1. Measure the whole filesystem, not the category asked about
```sh
du -sh /home/dank/models /home/dank/.local /home/dank/.cache \
/home/dank/comfyui-data /home/dank/GitPackages 2>/dev/null
df -h /home/dank | tail -1
podman system df # reclaimable image space
du -sh ~/.local/share/containers # where rootless storage physically is
```
The answer is usually NOT the category the user named. On Dankclaw
(2026-09-28) retired models were 510G, but podman images were **279G
reclaimable (95%)** and uv+pip caches **73G** — 352G of regenerable garbage,
bigger than the models. Lead with that finding; it changes the decision.
Check for sparse files before claiming a saving:
```sh
stat -c 'apparent=%s blocks=%b' <file>; du -sh --apparent-size <file>; du -sh <file>
```
A 3.3G "log" (`logs/x4e-run.log`) was **4K real** (8 blocks × 512) — deleting
it frees nothing. `du` vs `du --apparent-size` finds these instantly.
## 2. Classify against the runbook index
Every payload directory maps to a runtime in the AGENTS.md retired-runbook
table. Three buckets:
- **live** — referenced by a running service's env/unit.
- **rollback** — named as a rollback target in a runbook. ASK before
touching: they are the difference between a one-minute flip and a 30G
re-download.
- **retired** — maps to a retired runtime. Safe candidates.
Trap: a directory can hold **both** a dead target and a live asset — an era's
drafter can sit beside the retired target it came with (per-endpoint era tables:
[[frank-dragoon]], [[frank-zealot]], [[frank-archon]]). Move the
single file, never the directory, and say so in the manifest.
## 3. The live-reference check — before moving anything
```sh
for d in <candidate dirs>; do
grep -rl "$d" --include="*.sh" --include="*.env" --include="*.service" \
--include="*.container" --include="*.yaml" . | grep -v '^./old/'
done
```
Then **read every hit**; do not count them. Most are benign (comments,
retired fetch-script destinations, `retire_*_quadlet` helpers that move a
quadlet not a model) — but on Dankclaw this check found a live bug:
`serve-frank-zealot.sh`'s `: "${MODEL_DIR:=...}"` fallback still pointed at a
payload retired a week earlier. The env file always overrode it, so it was
invisible; a missing env file would have produced a "missing asset" naming a
model that no longer exists.
**Durable lesson:** `:`-defaults in a serve script are never exercised in
production, so they rot silently. When reclaiming a payload, grep serve
scripts too — the fallback is a live reference in all but name.
Also check service **env files** (`scripts/*.env`) for `MODEL_DIR`/`MODEL`
paths, not just unit `Volume=` lines: a payload can be mounted by path and
named by env. `models/logs` and small tool dirs are not cache — logs are
evidence, keep them.
## 4. Stage, never delete
```sh
STAGE=/home/dank/.AGENTTRASH/$(date +%Y%m%d)-<topic>
mkdir -p "$STAGE"
mv <path> "$STAGE"/
```
Same filesystem ⇒ instant and recoverable — **and reclaims no space**; say so
rather than implying space was freed. Write `MANIFEST.md` into the stage dir:
per item — path, size, owning runtime, when retired, the `mv` command to
restore. Record what you **deliberately kept** and why; that is what a future
reader needs. Always check for pre-existing staged trash
(`du -sh ~/.AGENTTRASH/*`): 118G from a 2026-09-21 cleanup sat un-purged;
report the running total, not just the new batch. Root-owned leftovers need
`sudo`, and check `swapon --show` first (a trashed swapfile must not be
active).
## 5. Verify, then report — and do not purge
```sh
systemctl is-active frank-dragoon frank-zealot
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:7770/health
```
Report: staged items and sizes, what was kept and why, what is **still not
freed**, remaining candidates untouched. Purge only on explicit approval.
## Repo-flow gotcha: do NOT push before you deploy
`scripts/deploy.sh` sets `BASE_REVISION=$(git rev-parse origin/main)`. If you
pushed separately, the diff is empty and the planner reports "changed
services: (none)" — looks like a planner bug, is not one. The tool is
designed for the **unpushed** state: edit → test → `deploy.sh` (it pushes,
then dispatches). If you already pushed, pull on the host instead, and reason
about whether a restart is actually owed. Any serve-script/env change needs
`./tests/run.sh` before commit; a `:`-default change is usually **inert**
(env overrides it) and owes no restart — but say so explicitly.
# Purge and refresh protocol
When the task is clearing/reclaiming/**refreshing** model caches on a serving
host, add these steps.
## Fingerprint before deleting
`ls -l --block-size=1 <dir>`; for LLM payloads compare each file against the
Hub's declared size. Fingerprints are the only way to answer "did the refresh
change anything" afterwards — **a refresh is also an update check**: if
upstream never changed, the bytes come back identical, which is itself the
finding. Sidecars are versioned **in place**: equal main-checkpoint sizes do
not prove the repo is unchanged, and a stale sidecar is invisible from its
name (observed: `overlay.hgn` 2,477,677,120 → 2,572,466,560 while the main
checkpoint returned byte-identical). If a file is irreplaceable upstream
(delisted revision, gated repo), say so before deleting.
## Confirm re-fetchability first
Query the Hub anonymously; a gated repo 403s even with the host's token:
```bash
curl -s "https://huggingface.co/api/models/<repo>/tree/main?recursive=true&blobs=true" \
| python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d),'entries'); [print(e.get('size'),e['path']) for e in d[:5]]"
```
An error/empty result means it is NOT safe to delete. Filter by **quant
name**, never `endswith('.gguf')` — mradermacher ships large quants as
`.partNofM` fragments that filter hides.
## Refresh per engine — know the recovery path BEFORE deleting
- **frank-dragoon**: self-fetching — clear the directory but
leave it **existing and empty**, then start the unit; the download knobs and
the payload's byte count (which is what the re-download costs) are on
[[frank-dragoon]]. **Bind-mount trap:**
podman refuses a mount whose source is missing
(`Error: statfs /home/dank/models/halogen: no such file or directory`) and
the unit loops on `Restart=on-failure` — `mkdir -p` the empty dir.
- **llama.cpp services**: no self-fetch; the serve script refuses to start
without weights. Pull yourself and verify:
```bash
curl -fL -C - --retry 10 -o "$DEST/<file>" \
"https://huggingface.co/<repo>/resolve/main/<file>" # -f is mandatory
sha256sum "$DEST"/*; head -c 4 "$DEST"/*.gguf # compare to LFS oid + GGUF magic
```
`curl` exits 0 on HTTP 401/403 and writes the error body as the artifact — a
gated repo once produced three 153-byte shards with identical sha256s and a
cheerful DONE line for 104 GiB. Always `-fL -C -`, compare byte counts to the
Hub's declared sizes, compare sha256 to the LFS oid. Assemble `.partNofM`
fragments with `cat part1 part2 part3 > name.gguf` in numeric order (per-part
hashes verify the whole — concatenation is deterministic). Public repos need
no token: omit `-H "Authorization: Bearer …"` from argv entirely (it is
world-readable in `/proc/<pid>/cmdline`).
## Restore, then verify behaviour — not health alone
Put the quadlet back (`mv` from trash + `systemctl --user daemon-reload`),
`reset-failed`, `start` (root units need `sudo`). Watch the engine's own
fetch/load lines, then `/health`, `/v1/models`, and a **real completion** on
every restored endpoint. Read the engine's memory ledger (kv pool / weights /
blocks left) rather than assuming the config still fits. Sweep the vhosts too
(`www`, `grafana`, `prometheus`, `hermes`, `marreta`, `gitea`, `loki` via
`curl -sk --resolve <host>:443:192.168.0.143`) — a purge touches state the
whole host shares.
## Things that bite
- **Do not run a 100 GB+ re-fetch concurrently with a big iGPU benchmark** —
the A* run commits ~116 GiB and leaves ~10 GiB available; cache churn risks
the OOM/state failures this fleet has already seen.
- **Reboot is the repair of last resort** if rootless podman's pause process
dies mid-work (`invalid internal status … could not find any running
process`); `podman system migrate` panics in 5.7.0.
- A same-filesystem trash reclaims no space; root-owned leftovers need
`sudo`; never `rm` without explicit permission.
- **A giant image layer is not automatically a model blob.** The 2026-10-17 census found a 121 GB layer
(`16b6663d…`) that read like an undeleted payload; it is the **engine image** — the radiance runtime itself.
Map each layer to what the serving trio actually mounts before deleting anything: that census is both the
disk-reclaim proof and the check that no deleted payload path still backs a live container.
## Related
- [[Model Swap and Rollback]] — the runbook index this classification reads
- [[Quadlet Lifecycle]] — restoring the unit after a payload refresh
- [[Remote Job Runner]] — supervising long re-fetches
- [[Deploy State Triage]] — the push-before-deploy trap in context