diff --git a/scripts/frank-zealot.service b/scripts/frank-zealot.service index b74d7df..5e3ca96 100644 --- a/scripts/frank-zealot.service +++ b/scripts/frank-zealot.service @@ -12,7 +12,11 @@ Group=dank LimitMEMLOCK=infinity Environment="FRANK_ZEALOT_ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-zealot.env" ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-zealot.sh -Restart=on-failure +# Restart=always, the fleet law for LLM units: exit 0 is a CLEAN stop to +# systemd, so on-failure leaves the endpoint dark (and the unit reporting +# success) until the next boot. frank-zealot is the fleet's compaction hub — +# every endpoint's compaction dies when it does. +Restart=always RestartSec=10 [Install] diff --git a/serverAppConfigs-docs/LLM/frank-zealot-llama-qwen35-9b-q6k-2x200704.md b/serverAppConfigs-docs/LLM/frank-zealot-llama-qwen35-9b-q6k-2x200704.md index 777e412..09b34f3 100644 --- a/serverAppConfigs-docs/LLM/frank-zealot-llama-qwen35-9b-q6k-2x200704.md +++ b/serverAppConfigs-docs/LLM/frank-zealot-llama-qwen35-9b-q6k-2x200704.md @@ -27,7 +27,7 @@ conversation slot and unquantized KV back. | Host / device | Dankclaw (192.168.0.143), RTX 3090, 24,576 MiB | | Endpoint | `http://192.168.0.143:7771` — served model id **`frank-zealot`** (`ALIAS` in the env; the SERVICE keeps the `frank-zealot` name), `/v1` OpenAI surface | | Runtime | llama.cpp master (0.5.0-dev, commit `5fc4f3c`), CUDA sm_86 build `…/llama-master/build-cuda/bin/llama-server` (`GGML_CUDA_FA_ALL_QUANTS=ON`); `qwen35` is registered in the build's own `src/llama-arch.cpp` and `src/models/qwen35.cpp` exists at that commit | -| Unit | root systemd `frank-zealot.service` ([`scripts/frank-zealot.service`](../../scripts/frank-zealot.service)), `User=dank`, `Restart=on-failure`, `LimitMEMLOCK=infinity` | +| Unit | root systemd `frank-zealot.service` ([`scripts/frank-zealot.service`](../../scripts/frank-zealot.service)), `User=dank`, `Restart=always` (raised from `on-failure` 2026-10-13 for fleet parity — exit 0 is a CLEAN stop to systemd, so the weaker policy leaves the compaction hub dark while the unit reports success; `tests/llm_units.bats` gates it), `LimitMEMLOCK=infinity` | | Serve script | [`scripts/serve-frank-zealot.sh`](../../scripts/serve-frank-zealot.sh), tunables [`scripts/frank-zealot.env`](../../scripts/frank-zealot.env) | | Weights | `/home/dank/models/Qwen35-9B-HauhauCS-Q6K/Qwen3.5-9B-Uncensored-HauhauCS-Aggressive-Q6_K.gguf` — HauhauCS **Q6_K**, 7,359,259,008 B, sha256 `c0ba7beb68fd3fe47891bd549486d38dcf62d00817296ea314ad37017f5a4986` | | Projector | `mmproj-Qwen3.5-9B-Uncensored-HauhauCS-Aggressive-BF16.gguf`, 921,704,448 B, sha256 `05f662501f8bd45607b079723a3e238a4e888fd085a10a53f4057a0e250f6934` — the model is natively multimodal and the Q6_K file carries no vision tensors, so this file IS the vision capability | diff --git a/tests/frank_dragoon_unit.bats b/tests/frank_dragoon_unit.bats index c207ef9..41943e1 100755 --- a/tests/frank_dragoon_unit.bats +++ b/tests/frank_dragoon_unit.bats @@ -346,8 +346,9 @@ $(cat "$TEST_TMP/serve.log")" } @test "the deployed unit keeps the fleet's restart and boot-persistence shape" { - grep -qE '^Restart=(always|on-failure)$' "$UNIT" - grep -qE '^WantedBy=' "$UNIT" + # Restart=always and the WantedBy value are the FLEET parity seam + # (tests/llm_units.bats); what is endpoint-specific is which script the + # unit runs, under whom, and what a stop must reap. grep -qE '^ExecStart=.*/serve-frank-dragoon\.sh$' "$UNIT" grep -qE '^User=dank$' "$UNIT" grep -qE '^LimitMEMLOCK=infinity$' "$UNIT" diff --git a/tests/frank_zealot_unit.bats b/tests/frank_zealot_unit.bats index c7a8782..1244719 100755 --- a/tests/frank_zealot_unit.bats +++ b/tests/frank_zealot_unit.bats @@ -85,10 +85,12 @@ run_stage() { [ -f "$ENV_FILE" ] } -@test "frank-zealot is boot-persistent, self-healing and unprivileged" { - grep -q '^WantedBy=multi-user.target$' "$UNIT" - grep -q '^Restart=' "$UNIT" - grep -q '^RestartSec=' "$UNIT" +@test "frank-zealot runs unprivileged with the memlock budget and its env file" { + # Restart policy, RestartSec and WantedBy are the FLEET parity seam + # (tests/llm_units.bats). What is endpoint-specific: CUDA pins its + # staging buffers, so the memlock budget is load-bearing even though the + # weights are copied into VRAM, and the unit must name the serve script + # and the env file that sizes it. grep -q '^User=dank$' "$UNIT" grep -q '^LimitMEMLOCK=infinity$' "$UNIT" grep -q '^ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-zealot.sh$' "$UNIT" diff --git a/tests/llm_units.bats b/tests/llm_units.bats index 42c94a8..758045c 100644 --- a/tests/llm_units.bats +++ b/tests/llm_units.bats @@ -80,3 +80,38 @@ unit_repo_path() { } done < <(find "$REPO_ROOT/scripts" -name "frank-*.service" -type f | grep -v AGENTTRASH | sort) } + +@test "every LLM root unit is self-healing against a voluntary exit" { + # Restart=always, not on-failure. Every endpoint here is a container + # runner (podman run) or a server whose front end can exit 0 on a + # control-plane event; systemd treats exit 0 as a CLEAN stop and + # on-failure leaves the endpoint dark until the next boot, with the unit + # reporting success. frank-archon learned this on 2026-10-04; the fleet + # law is that no LLM unit may ship the weaker policy. + local unit + while IFS= read -r unit; do + grep -qx 'Restart=always' "$unit" || { + echo "$(basename "$unit"): $(grep '^Restart=' "$unit") (must be always)" + return 1 + } + done < <(find "$REPO_ROOT/scripts" -name "frank-*.service" -type f | grep -v AGENTTRASH | sort) +} + +@test "every LLM root unit restarts promptly and starts at boot" { + # RestartSec=10 is the fleet's cadence: fast enough that a transient + # fault is invisible to traffic, slow enough that a hard fault does not + # spin the engine (each radiance boot re-pins ~147 GiB). + # WantedBy=multi-user.target is boot persistence for a ROOT unit — + # default.target is the USER-manager's target and means nothing here. + local unit + while IFS= read -r unit; do + grep -qE '^RestartSec=([1-9]|1[0-9]|20)$' "$unit" || { + echo "$(basename "$unit"): no RestartSec in 1..20" + return 1 + } + grep -qx 'WantedBy=multi-user.target' "$unit" || { + echo "$(basename "$unit"): $(grep '^WantedBy=' "$unit")" + return 1 + } + done < <(find "$REPO_ROOT/scripts" -name "frank-*.service" -type f | grep -v AGENTTRASH | sort) +}