llm: retire the trio service names — fleet services renamed to frank-archon/frank-dragoon/frank-zealot

The 2026-10-09 wire aliases become the service names everywhere:
systemd units, serve scripts, env files and FRANK_* variables,
deploy-plan/deploy-remote service keys, container names, Prometheus
job names + alert rules, Grafana dashboard queries, bats seams,
runbook filenames, homepage/AGENTS lore, and historical prose.
Rollback tags renamed in lockstep. Residue in the tree: zero.
Retired frank-medium/small/npu names are history and stay.

Known casualty: scripts/patches/*.patch context lines were rewritten;
those retired-era vLLM-fork patches no longer apply byte-exact.
This commit is contained in:
Frank
2026-10-10 17:03:14 -07:00
parent 5ced474eb9
commit 55f87fcdce
195 changed files with 2085 additions and 2064 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bats
# Seam: autoresearch measure client behavior.
#
# The measure client is the whole yardstick of the frank-primary speed
# The measure client is the whole yardstick of the frank-archon speed
# session, so its METRIC arithmetic gets a behavior test: against a
# deterministic mock endpoint that emits a KNOWN number of completion
# tokens at a KNOWN per-token delay, the reported decode rate must land
@@ -16,7 +16,7 @@
load helpers
MEASURE="$REPO_ROOT/scripts/autoresearch/frank-primary-measure.py"
MEASURE="$REPO_ROOT/scripts/autoresearch/frank-archon-measure.py"
MOCK="$BATS_TEST_TMPDIR/mock.py"
PORT=""
MOCK_PID=""
+7 -7
View File
@@ -21,16 +21,16 @@ load helpers
[[ "$output" != *"tests/homepage_pages.bats"* ]]
}
@test "changed-test wrapper maps the frank-primary trio to its focused tests" {
@test "changed-test wrapper maps the frank-archon trio to its focused tests" {
# The root-unit shape since the 2026-09-26 swap: the trio lives in
# scripts/ and must select the unit seam before the generic
# scripts/*.service fallbacks can swallow it.
run bash "$REPO_ROOT/scripts/test-changed.sh" --list --paths \
scripts/frank-primary.env \
scripts/frank-primary.service \
scripts/serve-frank-primary.sh
scripts/frank-archon.env \
scripts/frank-archon.service \
scripts/serve-frank-archon.sh
[ "$status" -eq 0 ]
[[ "$output" == *"tests/frank_primary_unit.bats"* ]]
[[ "$output" == *"tests/frank_archon_unit.bats"* ]]
[[ "$output" == *"tests/llm_units.bats"* ]]
[[ "$output" == *"tests/deployment_scoping.bats"* ]]
[[ "$output" != *"tests/homepage_pages.bats"* ]]
@@ -42,8 +42,8 @@ load helpers
run bash "$REPO_ROOT/scripts/test-changed.sh" --list --paths \
dankserver/.AGENTTRASH/20260926-frank-medium-radiance/frank-medium.container \
scripts/.AGENTTRASH/20260920-llama-layersplit/serve-frank-medium.sh.llamacpp-layersplit \
dankserver/.AGENTTRASH/20260926-llamacpp-cutover/frank-primary.container
dankserver/.AGENTTRASH/20260926-llamacpp-cutover/frank-archon.container
[ "$status" -eq 0 ]
[[ "$output" != *"tests/frank_medium_unit.bats"* ]]
[[ "$output" != *"tests/frank_primary_unit.bats"* ]]
[[ "$output" != *"tests/frank_archon_unit.bats"* ]]
}
+4 -4
View File
@@ -68,13 +68,13 @@ load helpers
}
@test "remote runner has a handler per inference service" {
grep -q 'deploy_llm_large' "$REPO_ROOT/scripts/deploy-remote.sh"
grep -q 'deploy_llm_primary' "$REPO_ROOT/scripts/deploy-remote.sh"
grep -q 'deploy_llm_secondary' "$REPO_ROOT/scripts/deploy-remote.sh"
grep -q 'deploy_llm_dragoon' "$REPO_ROOT/scripts/deploy-remote.sh"
grep -q 'deploy_llm_archon' "$REPO_ROOT/scripts/deploy-remote.sh"
grep -q 'deploy_llm_zealot' "$REPO_ROOT/scripts/deploy-remote.sh"
}
@test "inference restarts are absent from the top-level dispatcher" {
! grep -qE 'systemctl .*restart (frank-medium|frank-large|frank-npu)' "$REPO_ROOT/scripts/deploy.sh"
! grep -qE 'systemctl .*restart (frank-medium|frank-dragoon|frank-npu)' "$REPO_ROOT/scripts/deploy.sh"
}
@test "remote runner health-checks an inference endpoint" {
+17 -17
View File
@@ -18,9 +18,9 @@ REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
@test "deployment planner maps large-model changes to the large model only" {
run python3 "$PLANNER" --target dankclaw --paths \
scripts/frank-large.env
scripts/frank-dragoon.env
[ "$status" -eq 0 ]
[ "$output" = "frank-large" ]
[ "$output" = "frank-dragoon" ]
}
@test "the retired NPU endpoint is absent from the deployment inventory" {
@@ -31,7 +31,7 @@ REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
@test "the retired ik chat template plans nothing, and its archive is still here" {
# scripts/chat-template.jinja existed only for the ik_llama.cpp runtime
# (retired 2026-09-16 in favour of halogen-flash-server again). The
# llama.cpp frank-secondary runtime serves the template the GGUF itself
# llama.cpp frank-zealot runtime serves the template the GGUF itself
# carries, which is the one with reasoning_effort, preserve_thinking and
# the tool-call grammar. Editing the archive must deploy nothing -- and the
# archive must still be present, so this cannot pass by the file having
@@ -63,36 +63,36 @@ REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
[ -z "$output" ]
}
@test "deployment planner maps the frank-primary trio to frank-primary only" {
@test "deployment planner maps the frank-archon trio to frank-archon only" {
# The root-unit shape since the 2026-09-26 swap: unit, env and serve
# script live in scripts/ and select exactly frank-primary. The
# script live in scripts/ and select exactly frank-archon. The
# download script fetches host-side state and must select nothing, and
# the lucebox quadlet-era tree in dated trash plans nothing either.
run python3 "$PLANNER" --target dankserver --paths \
scripts/frank-primary.env \
scripts/frank-primary.service \
scripts/serve-frank-primary.sh
scripts/frank-archon.env \
scripts/frank-archon.service \
scripts/serve-frank-archon.sh
[ "$status" -eq 0 ]
[ "$output" = "frank-primary" ]
[ "$output" = "frank-archon" ]
run python3 "$PLANNER" --target dankserver --paths \
scripts/download-frank-primary.sh \
dankserver/.AGENTTRASH/20260926-llamacpp-cutover/frank-primary.container
scripts/download-frank-archon.sh \
dankserver/.AGENTTRASH/20260926-llamacpp-cutover/frank-archon.container
[ "$status" -eq 0 ]
[ -z "$output" ]
}
@test "the small endpoint is Dankclaw's again, and Dankserver's copy stays retired" {
run python3 "$PLANNER" --target dankclaw --paths \
scripts/frank-secondary.env scripts/frank-secondary.service scripts/serve-frank-secondary.sh
scripts/frank-zealot.env scripts/frank-zealot.service scripts/serve-frank-zealot.sh
[ "$status" -eq 0 ]
[ "$output" = "frank-secondary" ]
[ "$output" = "frank-zealot" ]
# frank-secondary runs on Dankclaw on the RTX 3090; the Dankserver endpoint of
# frank-zealot runs on Dankclaw on the RTX 3090; the Dankserver endpoint of
# that name was retired with the FreeToken runtime on 2026-09-15 and must
# not come back through a planner mapping.
run python3 "$PLANNER" --target dankserver --paths \
scripts/frank-secondary.env scripts/frank-secondary.service scripts/serve-frank-secondary.sh
scripts/frank-zealot.env scripts/frank-zealot.service scripts/serve-frank-zealot.sh
[ "$status" -eq 0 ]
[ -z "$output" ]
}
@@ -205,8 +205,8 @@ REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
@test "remote deployer routes inference restarts through selected handlers" {
# The runner may restart inference only from the service-dispatch path. It
# must not contain a host-wide restart loop.
grep -q 'deploy_llm_large' "$REMOTE_DEPLOY"
grep -q 'deploy_llm_primary' "$REMOTE_DEPLOY"
grep -q 'deploy_llm_dragoon' "$REMOTE_DEPLOY"
grep -q 'deploy_llm_archon' "$REMOTE_DEPLOY"
grep -q 'deploy_service' "$REMOTE_DEPLOY"
! grep -qE 'for .*frank-(small|large|npu)-?' "$REMOTE_DEPLOY"
}
@@ -1,11 +1,11 @@
#!/usr/bin/env bats
# Seam: frank-primary — the Dankserver LLM endpoint on :7770.
# Seam: frank-archon — the Dankserver LLM endpoint on :7770.
#
# This file asserts the RADIANCE shape (StillDeadcode engine, digest-pinned
# stilldeadcode/radiance image, BOTH R9700s TP=2 on the wht6 wire, the
# OFFICIAL Qwen/Qwen3.8-Flash-Next-FP8 at native block-FP8 precision in a
# rad-convert container). It replaces the FreeToken seam of 2026-10-05
# (git tag frank-primary-freetoken-pr587-20261005) and pins what flags
# (git tag frank-archon-freetoken-pr587-20261005) and pins what flags
# alone do not say: the served window clears the harness worst case, the
# pinned-host ledger has a ttm pages cap floor and the serve script
# ENFORCES it, the served window is what models.yml must quote, the host-
@@ -20,16 +20,16 @@
load helpers
UNIT="$REPO_ROOT/scripts/frank-primary.service"
SERVE="$REPO_ROOT/scripts/serve-frank-primary.sh"
ENV_FILE="$REPO_ROOT/scripts/frank-primary.env"
UNIT="$REPO_ROOT/scripts/frank-archon.service"
SERVE="$REPO_ROOT/scripts/serve-frank-archon.sh"
ENV_FILE="$REPO_ROOT/scripts/frank-archon.env"
REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
DOWNLOAD="$REPO_ROOT/scripts/download-frank-primary-swift15.sh"
DOWNLOAD="$REPO_ROOT/scripts/download-frank-archon-swift15.sh"
BUILD="$REPO_ROOT/scripts/convert-swift15-w4dn8.sh"
HFGET="$REPO_ROOT/scripts/hfget.sh"
TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
@test "frank-primary unit, serve script, env, fetch and build scripts and template exist" {
@test "frank-archon unit, serve script, env, fetch and build scripts and template exist" {
[ -f "$UNIT" ]
[ -x "$SERVE" ]
[ -f "$ENV_FILE" ]
@@ -40,11 +40,11 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
# The FreeToken-era Dockerfile is retired with the runtime it built:
# the radiance image is the publisher's, pulled and digest-pinned, not
# built on the host.
[ ! -f "$REPO_ROOT/scripts/docker/frank-primary-freetoken.Dockerfile" ]
[ ! -f "$REPO_ROOT/scripts/docker/frank-archon-freetoken.Dockerfile" ]
}
@test "frank-primary unit is boot-persistent, self-healing and unprivileged" {
# Root unit parity with frank-large/frank-secondary: enable is the [Install]
@test "frank-archon unit is boot-persistent, self-healing and unprivileged" {
# Root unit parity with frank-dragoon/frank-zealot: enable is the [Install]
# section, the restart policy survives a crash, and the pinned host RAM
# (expert pools + PLE table + prefix host tier, ~132 GiB) needs an
# unbounded memlock (a root unit grants it via PID1; the dank session's
@@ -70,9 +70,9 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
grep -q '^LimitMEMLOCK=infinity$' "$UNIT"
}
@test "frank-primary runs the repo's own serve script with the repo's env" {
grep -q '^ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-primary.sh$' "$UNIT"
grep -q 'FRANK_PRIMARY_ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-primary.env' "$UNIT"
@test "frank-archon runs the repo's own serve script with the repo's env" {
grep -q '^ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-archon.sh$' "$UNIT"
grep -q 'FRANK_ARCHON_ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-archon.env' "$UNIT"
}
@test "the unit stops the container child explicitly — container identity is the unit name" {
@@ -81,9 +81,9 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
# outlives the unit and holds the GPU. The container name IS the
# service name across runtimes (Frank's 2026-10-05 call).
grep -q '^ExecStop=/usr/bin/podman stop' "$UNIT"
grep -q 'podman stop -t 120 frank-primary$' "$UNIT"
grep -q '^FRANK_PRIMARY_CONTAINER=frank-primary$' "$ENV_FILE"
grep -q 'frank-primary-qwen' "$ENV_FILE"
grep -q 'podman stop -t 120 frank-archon$' "$UNIT"
grep -q '^FRANK_ARCHON_CONTAINER=frank-archon$' "$ENV_FILE"
grep -q 'frank-archon-qwen' "$ENV_FILE"
}
@test "the serve shape is direct podman run, not a launcher or a predecessor runtime" {
@@ -106,7 +106,7 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
@test "the image is the publisher's, digest-pinned by manifest" {
# stilldeadcode/radiance@sha256:<64 hex> — a name@digest ref exists in
# storage only if THAT manifest was pulled. The tag form would float.
grep -qE '^FRANK_PRIMARY_IMAGE=stilldeadcode/radiance@sha256:[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_ARCHON_IMAGE=stilldeadcode/radiance@sha256:[0-9a-f]{64}$' "$ENV_FILE"
}
@test "the radiance engine shape is in the env file" {
@@ -115,65 +115,65 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
# prompt can legally reach that height — 140,492 + 36,864 = 177,356 at
# the FreeToken-era window was the record; the same relation must hold
# here (140,000 + 36,864 = 176,864 <= 200,000).
grep -q '^FRANK_PRIMARY_MAX_MODEL_LEN=200000$' "$ENV_FILE"
ml=$(awk -F= '/^FRANK_PRIMARY_MAX_MODEL_LEN=/{print $2}' "$ENV_FILE")
grep -q '^FRANK_ARCHON_MAX_MODEL_LEN=200000$' "$ENV_FILE"
ml=$(awk -F= '/^FRANK_ARCHON_MAX_MODEL_LEN=/{print $2}' "$ENV_FILE")
[ "$ml" -ge 177356 ]
# Decode-sweep shape (2026-10-06): 3 slots, spec depth 2 — the measured
# optimum (33.06 t/s). The fused TP all-reduce is declared over
# seqs x (1 + spec) rows = 9.
grep -q '^FRANK_PRIMARY_MAX_SEQS=3$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_TP=2$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_TP_WIRE=wht6$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_SPEC_TOKENS=2$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_MAX_SEQS=3$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_TP=2$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_TP_WIRE=wht6$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_SPEC_TOKENS=2$' "$ENV_FILE"
# Native-precision economics: the expert pool is per rank (56.4 GiB
# slice per rank at TP2), the PLE ngram table is pinned in host RAM per
# Frank's law, KV is fp8, the ratio is the publisher's production
# value.
grep -q '^FRANK_PRIMARY_PLACEMENT=expert_tiered$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_HOST_POOL_MIB=40960$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_VRAM_WEIGHTS_MIB=[1-9]' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_VRAM_KV_MIB=[1-9]' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_KV_DTYPE=fp8$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_NGRAM_PLACEMENT=ram$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_PLACEMENT=expert_tiered$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_HOST_POOL_MIB=40960$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_VRAM_WEIGHTS_MIB=[1-9]' "$ENV_FILE"
grep -q '^FRANK_ARCHON_VRAM_KV_MIB=[1-9]' "$ENV_FILE"
grep -q '^FRANK_ARCHON_KV_DTYPE=fp8$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_NGRAM_PLACEMENT=ram$' "$ENV_FILE"
# Prefix cache tiers (publisher pattern; disk tier sized for 2 slots).
grep -q '^FRANK_PRIMARY_PREFIX_HOST_MIB=4096$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_PREFIX_DISK_MIB=65536$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_PREFIX_HOST_MIB=4096$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_PREFIX_DISK_MIB=65536$' "$ENV_FILE"
# The pinned ledger (2 x pool + 47.7 GiB PLE + prefix host) must be
# under the ttm pages cap, and the env states the floor the serve
# script enforces (>= 140,000 MiB — the current cap ~123 GiB fails it,
# which is the point).
grep -q '^FRANK_PRIMARY_TTM_MIN_MIB=' "$ENV_FILE"
ttmm=$(awk -F= '/^FRANK_PRIMARY_TTM_MIN_MIB=/{print $2}' "$ENV_FILE")
pool=$(awk -F= '/^FRANK_PRIMARY_HOST_POOL_MIB=/{print $2}' "$ENV_FILE")
ph=$(awk -F= '/^FRANK_PRIMARY_PREFIX_HOST_MIB=/{print $2}' "$ENV_FILE")
grep -q '^FRANK_ARCHON_TTM_MIN_MIB=' "$ENV_FILE"
ttmm=$(awk -F= '/^FRANK_ARCHON_TTM_MIN_MIB=/{print $2}' "$ENV_FILE")
pool=$(awk -F= '/^FRANK_ARCHON_HOST_POOL_MIB=/{print $2}' "$ENV_FILE")
ph=$(awk -F= '/^FRANK_ARCHON_PREFIX_HOST_MIB=/{print $2}' "$ENV_FILE")
# 2 pools + PLE 47.7 GiB (48,824 MiB) + prefix host, rounded up.
ledger=$(( 2 * pool + 48824 + ph + 2048 ))
[ "$ttmm" -ge "$ledger" ]
# The .rad size floor is positive (a truncated convert must fail fast).
grep -q '^FRANK_PRIMARY_RAD_MIN_MIB=' "$ENV_FILE"
[ "$(awk -F= '/^FRANK_PRIMARY_RAD_MIN_MIB=/{print $2}' "$ENV_FILE")" -gt 0 ]
grep -q '^FRANK_ARCHON_RAD_MIN_MIB=' "$ENV_FILE"
[ "$(awk -F= '/^FRANK_ARCHON_RAD_MIN_MIB=/{print $2}' "$ENV_FILE")" -gt 0 ]
}
@test "the serve script wires the engine flags from the env" {
grep -q -- '--model "/models/$FRANK_PRIMARY_MODEL_FILE"' "$SERVE"
grep -q -- '--served-model-name "$FRANK_PRIMARY_SERVED_NAME"' "$SERVE"
grep -q -- '--host :: --port "$FRANK_PRIMARY_PORT"' "$SERVE"
grep -q -- '--model "/models/$FRANK_ARCHON_MODEL_FILE"' "$SERVE"
grep -q -- '--served-model-name "$FRANK_ARCHON_SERVED_NAME"' "$SERVE"
grep -q -- '--host :: --port "$FRANK_ARCHON_PORT"' "$SERVE"
grep -q -- '--override-chat-template /templates/template.jinja' "$SERVE"
grep -q -- '--tp "$FRANK_PRIMARY_TP" --tp-wire "$FRANK_PRIMARY_TP_WIRE"' "$SERVE"
grep -q -- '--max-num-seqs "$FRANK_PRIMARY_MAX_SEQS"' "$SERVE"
grep -q -- '--max-model-len "$FRANK_PRIMARY_MAX_MODEL_LEN"' "$SERVE"
grep -q -- '--placement "$FRANK_PRIMARY_PLACEMENT"' "$SERVE"
grep -q -- '--host-pool-mib "$FRANK_PRIMARY_HOST_POOL_MIB"' "$SERVE"
grep -q -- '--vram-weights-mib "$FRANK_PRIMARY_VRAM_WEIGHTS_MIB"' "$SERVE"
grep -q -- '--vram-kv-mib "$FRANK_PRIMARY_VRAM_KV_MIB"' "$SERVE"
grep -q -- '--kv-cache-dtype "$FRANK_PRIMARY_KV_DTYPE"' "$SERVE"
grep -q -- '--prefix-cache-host-mib "$FRANK_PRIMARY_PREFIX_HOST_MIB"' "$SERVE"
grep -q -- '--prefix-cache-disk-mib "$FRANK_PRIMARY_PREFIX_DISK_MIB"' "$SERVE"
grep -q -- '--ngram-placement "$FRANK_PRIMARY_NGRAM_PLACEMENT"' "$SERVE"
grep -q -- '--num-speculative-tokens "$FRANK_PRIMARY_SPEC_TOKENS"' "$SERVE"
grep -q -- '--tp "$FRANK_ARCHON_TP" --tp-wire "$FRANK_ARCHON_TP_WIRE"' "$SERVE"
grep -q -- '--max-num-seqs "$FRANK_ARCHON_MAX_SEQS"' "$SERVE"
grep -q -- '--max-model-len "$FRANK_ARCHON_MAX_MODEL_LEN"' "$SERVE"
grep -q -- '--placement "$FRANK_ARCHON_PLACEMENT"' "$SERVE"
grep -q -- '--host-pool-mib "$FRANK_ARCHON_HOST_POOL_MIB"' "$SERVE"
grep -q -- '--vram-weights-mib "$FRANK_ARCHON_VRAM_WEIGHTS_MIB"' "$SERVE"
grep -q -- '--vram-kv-mib "$FRANK_ARCHON_VRAM_KV_MIB"' "$SERVE"
grep -q -- '--kv-cache-dtype "$FRANK_ARCHON_KV_DTYPE"' "$SERVE"
grep -q -- '--prefix-cache-host-mib "$FRANK_ARCHON_PREFIX_HOST_MIB"' "$SERVE"
grep -q -- '--prefix-cache-disk-mib "$FRANK_ARCHON_PREFIX_DISK_MIB"' "$SERVE"
grep -q -- '--ngram-placement "$FRANK_ARCHON_NGRAM_PLACEMENT"' "$SERVE"
grep -q -- '--num-speculative-tokens "$FRANK_ARCHON_SPEC_TOKENS"' "$SERVE"
# The boot-ladder escape hatch is opt-in: unset renders NO extra argv.
grep -q -- '${FRANK_PRIMARY_EXTRA_ARGS:+$FRANK_PRIMARY_EXTRA_ARGS}' "$SERVE"
! grep -q '^FRANK_PRIMARY_EXTRA_ARGS=.' "$ENV_FILE"
grep -q -- '${FRANK_ARCHON_EXTRA_ARGS:+$FRANK_ARCHON_EXTRA_ARGS}' "$SERVE"
! grep -q '^FRANK_ARCHON_EXTRA_ARGS=.' "$ENV_FILE"
}
@test "the container contract is the publisher's (host network, io_uring, pin, cards)" {
@@ -185,16 +185,16 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
grep -q -- '--network host' "$SERVE"
! grep -q -- '--publish' "$SERVE"
! grep -q -- '-p 7770' "$SERVE"
grep -q '^FRANK_PRIMARY_PORT=7770$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_SERVED_NAME=frank-archon$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_PORT=7770$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_SERVED_NAME=frank-archon$' "$ENV_FILE"
grep -q -- '--init' "$SERVE"
grep -q -- '--security-opt seccomp=unconfined' "$SERVE"
grep -q -- '--ulimit memlock=-1' "$SERVE"
grep -q -- '--device /dev/kfd' "$SERVE"
grep -q -- '--device /dev/dri' "$SERVE"
grep -q -- '-v "$FRANK_PRIMARY_MODEL_DIR":/models:ro' "$SERVE"
grep -q -- '-v "$FRANK_PRIMARY_STATE_DIR":/data' "$SERVE"
grep -q -- '-v "$FRANK_PRIMARY_TEMPLATE":/templates/template.jinja:ro' "$SERVE"
grep -q -- '-v "$FRANK_ARCHON_MODEL_DIR":/models:ro' "$SERVE"
grep -q -- '-v "$FRANK_ARCHON_STATE_DIR":/data' "$SERVE"
grep -q -- '-v "$FRANK_ARCHON_TEMPLATE":/templates/template.jinja:ro' "$SERVE"
}
@test "parsers are AUTO — the engine infers them natively, the harness maps the field" {
@@ -206,8 +206,8 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
! grep -q -- '--reasoning-parser' "$SERVE"
! grep -q -- '--tool-call-parser' "$SERVE"
! grep -q -- '--enable-auto-tool-choice' "$SERVE"
! grep -q 'FRANK_PRIMARY_REASONING_PARSER' "$ENV_FILE"
! grep -q 'FRANK_PRIMARY_TOOL_PARSER' "$ENV_FILE"
! grep -q 'FRANK_ARCHON_REASONING_PARSER' "$ENV_FILE"
! grep -q 'FRANK_ARCHON_TOOL_PARSER' "$ENV_FILE"
}
@test "the serve script gates the image, the .rad payload, the template and the GPU nodes" {
@@ -215,12 +215,12 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
# .rad file and its size floor — a truncated convert would otherwise be
# a guaranteed long-timeout boot instead of a fast failure. The ttm
# preflight refuses to start under a pages cap below the pinned ledger.
grep -q 'FRANK_PRIMARY_IMAGE' "$SERVE"
grep -q 'FRANK_ARCHON_IMAGE' "$SERVE"
grep -q 'podman image exists' "$SERVE"
grep -q 'FRANK_PRIMARY_MODEL_FILE' "$SERVE"
grep -q 'FRANK_PRIMARY_RAD_MIN_MIB' "$SERVE"
grep -q 'FRANK_PRIMARY_TEMPLATE' "$SERVE"
grep -q 'FRANK_PRIMARY_TTM_MIN_MIB' "$SERVE"
grep -q 'FRANK_ARCHON_MODEL_FILE' "$SERVE"
grep -q 'FRANK_ARCHON_RAD_MIN_MIB' "$SERVE"
grep -q 'FRANK_ARCHON_TEMPLATE' "$SERVE"
grep -q 'FRANK_ARCHON_TTM_MIN_MIB' "$SERVE"
grep -q 'pages_limit' "$SERVE"
grep -q 'pages_max' "$SERVE"
grep -q '/dev/kfd' "$SERVE"
@@ -231,20 +231,20 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
# A crashed unit can leave the container name registered forever after;
# the legacy identities hold the same GPUs and die here too. Then block
# on the container so a crash is a nonzero ExecStart.
grep -q 'FRANK_PRIMARY_LEGACY_CONTAINERS' "$ENV_FILE"
grep -q 'frank-primary-qwen' "$ENV_FILE"
grep -q 'frank-primary-r9v' "$ENV_FILE"
grep -q 'FRANK_ARCHON_LEGACY_CONTAINERS' "$ENV_FILE"
grep -q 'frank-archon-qwen' "$ENV_FILE"
grep -q 'frank-archon-r9v' "$ENV_FILE"
grep -q 'qwen-fp8' "$ENV_FILE"
grep -q 'exec podman wait "$FRANK_PRIMARY_CONTAINER"' "$SERVE"
grep -q 'exec podman wait "$FRANK_ARCHON_CONTAINER"' "$SERVE"
}
@test "the model id matches the payload's build and fetch scripts" {
# The payload dir/file are the convert script's output layout; the fetch
# script must name the repo the convert stages from. (FP8-native era,
# the rollback payload, keeps its own trio: build-frank-primary-rad.sh +
# download-frank-primary.sh.)
grep -q '^FRANK_PRIMARY_MODEL_DIR=/home/dank/models/rad$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_MODEL_FILE=qwen4exp-swift15-w4dn8-i8-hc8m.rad$' "$ENV_FILE"
# the rollback payload, keeps its own trio: build-frank-archon-rad.sh +
# download-frank-archon.sh.)
grep -q '^FRANK_ARCHON_MODEL_DIR=/home/dank/models/rad$' "$ENV_FILE"
grep -q '^FRANK_ARCHON_MODEL_FILE=qwen4exp-swift15-w4dn8-i8-hc8m.rad$' "$ENV_FILE"
grep -q 'qwen4exp-swift15-w4dn8-i8-hc8m.rad' "$BUILD"
grep -q 'ukisai/Swift1.5-Qwen3.8-Flash-Next' "$DOWNLOAD"
grep -q 'hfget.sh' "$DOWNLOAD"
@@ -255,7 +255,7 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
@test "no bare HIP-reserved env var is defined" {
# HIP reads bare TEMP for its JIT cache path (2026-08-13: TEMP=1.0
# segfaulted every load), and HOST/GPU_MAX_HEAP_SIZE are the same
# class. Every key here is FRANK_PRIMARY_-prefixed.
# class. Every key here is FRANK_ARCHON_-prefixed.
! grep -qE '^(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$ENV_FILE"
! grep -qE '^(export )?(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$SERVE"
}
@@ -265,18 +265,18 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
# storage or the .rad is missing/too small — then clears stale
# containers (including the legacy identities), waits out the port,
# relinks/enables/restarts the root unit and health-gates it.
grep -q 'LLM_PRIMARY_CONTAINER=frank-primary$' "$REMOTE_DEPLOY"
grep -q 'FRANK_PRIMARY_IMAGE' "$REMOTE_DEPLOY"
grep -q 'LLM_ARCHON_CONTAINER=frank-archon$' "$REMOTE_DEPLOY"
grep -q 'FRANK_ARCHON_IMAGE' "$REMOTE_DEPLOY"
grep -q 'podman image exists' "$REMOTE_DEPLOY"
grep -q 'FRANK_PRIMARY_MODEL_FILE' "$REMOTE_DEPLOY"
grep -q 'FRANK_PRIMARY_RAD_MIN_MIB' "$REMOTE_DEPLOY"
grep -q 'frank-primary-qwen' "$REMOTE_DEPLOY"
grep -q 'FRANK_ARCHON_MODEL_FILE' "$REMOTE_DEPLOY"
grep -q 'FRANK_ARCHON_RAD_MIN_MIB' "$REMOTE_DEPLOY"
grep -q 'frank-archon-qwen' "$REMOTE_DEPLOY"
grep -q 'qwen-fp8' "$REMOTE_DEPLOY"
grep -q 'glm-probe' "$REMOTE_DEPLOY"
grep -q 'wait_port_free 7770' "$REMOTE_DEPLOY"
grep -q 'relink_unit frank-primary' "$REMOTE_DEPLOY"
grep -q 'systemctl enable frank-primary.service' "$REMOTE_DEPLOY"
grep -q 'systemctl restart frank-primary.service' "$REMOTE_DEPLOY"
grep -q 'relink_unit frank-archon' "$REMOTE_DEPLOY"
grep -q 'systemctl enable frank-archon.service' "$REMOTE_DEPLOY"
grep -q 'systemctl restart frank-archon.service' "$REMOTE_DEPLOY"
# The health gate is a bare curl: the HTTP listener binds AFTER the
# full load (main.cpp — engine.start() then engine.serve()), so during
# load the port refuses connections and a bare curl is honest. The
@@ -289,6 +289,6 @@ TEMPLATE="$REPO_ROOT/scripts/qwen38-froggeric-v22.5.jinja"
# The 2026-09-26 false green: a leftover probe held :7770 and satisfied
# the health curl while the real unit was stopped. The deploy is only
# green when the unit's own MainPID is live behind the health answer.
grep -q 'systemctl show -p MainPID --value frank-primary.service' "$REMOTE_DEPLOY"
grep -q 'systemctl show -p MainPID --value frank-archon.service' "$REMOTE_DEPLOY"
grep -q 'stray process holds the port' "$REMOTE_DEPLOY"
}
@@ -1,5 +1,5 @@
#!/usr/bin/env bats
# Seam: frank-large serves the Quat3rnion ABLITERATED Swift1.5 finetune of
# Seam: frank-dragoon serves the Quat3rnion ABLITERATED Swift1.5 finetune of
# peonist's Qwen3.8-Flash-Next v2 checkpoint (a .hgn flash-server payload)
# on the Strix Halo iGPU through the halogen-flash-server CONTAINER (0.16.2,
# digest-pinned), driven by the root unit's serve script — two 262,144-token
@@ -22,9 +22,9 @@
setup() {
ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
SERVE="$ROOT/scripts/serve-frank-large.sh"
ENV_FILE="$ROOT/scripts/frank-large.env"
UNIT="$ROOT/scripts/frank-large.service"
SERVE="$ROOT/scripts/serve-frank-dragoon.sh"
ENV_FILE="$ROOT/scripts/frank-dragoon.env"
UNIT="$ROOT/scripts/frank-dragoon.service"
DEPLOYER="$ROOT/scripts/deploy-remote.sh"
BUILD="$ROOT/scripts/build-rocmfpx.sh"
TEST_TMP="$(mktemp -d)"
@@ -63,47 +63,47 @@ good_env() {
# the artifact gates below are the thing under test.
printf 'MemTotal: 127108112 kB\nMemAvailable: 100000000 kB\n' > "$TEST_TMP/meminfo"
GOOD_ENV=(
"FRANK_LARGE_ENV_FILE=$TEST_TMP/empty.env"
"FRANK_LARGE_IMAGE=ghcr.io/peonist-ai/halogen-flash-server@sha256:cafe"
"FRANK_LARGE_CONTAINER=frank-large-halogen"
"FRANK_LARGE_LEGACY_CONTAINERS=frank-large"
"FRANK_LARGE_DEV_KFD=$TEST_TMP/qwen.hgn"
"FRANK_LARGE_DEV_DRI=$TEST_TMP"
"FRANK_LARGE_LOG=$TEST_TMP/serve.log"
"FRANK_LARGE_PUBLISH=7770:8731"
"FRANK_LARGE_MODEL_DIR=$TEST_TMP"
"FRANK_LARGE_CHECKPOINT=qwen.hgn"
"FRANK_LARGE_CHECKPOINT_BYTES=0"
"FRANK_LARGE_NGRAM_TABLE=ngram.hgn"
"FRANK_LARGE_NGRAM_BYTES=0"
"FRANK_LARGE_VISION_TOWER=vision.hgn"
"FRANK_LARGE_VISION_BYTES=0"
"FRANK_LARGE_TOKENIZER_FILES=tok.json"
"FRANK_DRAGOON_ENV_FILE=$TEST_TMP/empty.env"
"FRANK_DRAGOON_IMAGE=ghcr.io/peonist-ai/halogen-flash-server@sha256:cafe"
"FRANK_DRAGOON_CONTAINER=frank-dragoon-halogen"
"FRANK_DRAGOON_LEGACY_CONTAINERS=frank-dragoon"
"FRANK_DRAGOON_DEV_KFD=$TEST_TMP/qwen.hgn"
"FRANK_DRAGOON_DEV_DRI=$TEST_TMP"
"FRANK_DRAGOON_LOG=$TEST_TMP/serve.log"
"FRANK_DRAGOON_PUBLISH=7770:8731"
"FRANK_DRAGOON_MODEL_DIR=$TEST_TMP"
"FRANK_DRAGOON_CHECKPOINT=qwen.hgn"
"FRANK_DRAGOON_CHECKPOINT_BYTES=0"
"FRANK_DRAGOON_NGRAM_TABLE=ngram.hgn"
"FRANK_DRAGOON_NGRAM_BYTES=0"
"FRANK_DRAGOON_VISION_TOWER=vision.hgn"
"FRANK_DRAGOON_VISION_BYTES=0"
"FRANK_DRAGOON_TOKENIZER_FILES=tok.json"
"HALOGEN_MODEL_ID=frank-dragoon"
"HALOGEN_CTX=262144"
"HALOGEN_KV_POOL_POSITIONS=524288"
"HALOGEN_KV_SLOTS=2"
"HALOGEN_SPEC_ADAPT=0"
"HALOGEN_MAX_TOK=8192"
"FRANK_LARGE_MEMINFO=$TEST_TMP/meminfo"
"FRANK_DRAGOON_MEMINFO=$TEST_TMP/meminfo"
"STUB_DIGESTS=localhost/x ghcr.io/peonist-ai/halogen-flash-server@sha256:cafe"
)
}
@test "the env file defines the flash-server deployment values" {
grep -qE '^FRANK_LARGE_IMAGE=ghcr\.io/peonist-ai/halogen-flash-server@sha256:[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_CONTAINER=frank-large-halogen$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_PUBLISH=7770:8731$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_CHECKPOINT=.*\.hgn$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_CHECKPOINT_BYTES=[0-9]+$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_CHECKPOINT_SHA256=[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_NGRAM_TABLE=.*\.hgn$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_NGRAM_BYTES=[0-9]+$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_NGRAM_SHA256=[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_VISION_TOWER=.*\.hgn$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_VISION_BYTES=[0-9]+$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_VISION_SHA256=[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_LARGE_TOKENIZER_FILES="tokenizer\.json( [a-z_.]+)+"$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_IMAGE=ghcr\.io/peonist-ai/halogen-flash-server@sha256:[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_CONTAINER=frank-dragoon-halogen$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_PUBLISH=7770:8731$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_CHECKPOINT=.*\.hgn$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_CHECKPOINT_BYTES=[0-9]+$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_CHECKPOINT_SHA256=[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_NGRAM_TABLE=.*\.hgn$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_NGRAM_BYTES=[0-9]+$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_NGRAM_SHA256=[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_VISION_TOWER=.*\.hgn$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_VISION_BYTES=[0-9]+$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_VISION_SHA256=[0-9a-f]{64}$' "$ENV_FILE"
grep -qE '^FRANK_DRAGOON_TOKENIZER_FILES="tokenizer\.json( [a-z_.]+)+"$' "$ENV_FILE"
grep -qE '^HALOGEN_MODEL_ID=frank-dragoon$' "$ENV_FILE"
grep -qE '^HALOGEN_CTX=[0-9]+$' "$ENV_FILE"
}
@@ -131,7 +131,7 @@ good_env() {
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_LARGE_DEV_KFD="$TEST_TMP/absent-node" "$SERVE"
FRANK_DRAGOON_DEV_KFD="$TEST_TMP/absent-node" "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"no gfx1151 to serve on"* ]]
}
@@ -142,7 +142,7 @@ good_env() {
# ~28 KiB/position (FLAGS.md). Against it: ~62 GiB resident weights,
# the arena scratch 3.8 + 8.4*(MAX_TOK/16384) GiB, the vision tower
# ~2 GiB pool charge, the 20 GiB host reserve and the host baseline
# + co-tenancy (frank-secondary, ComfyUI, Caddy/observability) ~15. The
# + co-tenancy (frank-zealot, ComfyUI, Caddy/observability) ~15. The
# box has 121 GiB unified. The n-gram table rides as mmap'd page
# cache beside the checkpoint and is not a pinned charge (the card's
# own quickstart arithmetic does not bill it).
@@ -171,10 +171,10 @@ good_env() {
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_LARGE_CHECKPOINT=absent.hgn "$SERVE"
FRANK_DRAGOON_CHECKPOINT=absent.hgn "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"checkpoint"*"missing"* ]]
[[ "$output" == *"download-frank-large-flashnext.sh"* ]]
[[ "$output" == *"download-frank-dragoon-flashnext.sh"* ]]
}
@test "the serve script refuses a checkpoint of the wrong byte count" {
@@ -183,7 +183,7 @@ good_env() {
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_LARGE_CHECKPOINT_BYTES=42 "$SERVE"
FRANK_DRAGOON_CHECKPOINT_BYTES=42 "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"expected 42"* ]]
}
@@ -195,11 +195,11 @@ good_env() {
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_LARGE_NGRAM_TABLE=absent.hgn "$SERVE"
FRANK_DRAGOON_NGRAM_TABLE=absent.hgn "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"n-gram-table"*"missing"* ]]
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_LARGE_NGRAM_BYTES=42 "$SERVE"
FRANK_DRAGOON_NGRAM_BYTES=42 "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"expected 42"* ]]
}
@@ -211,11 +211,11 @@ good_env() {
podman_stub
good_env
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_LARGE_VISION_TOWER=absent.hgn "$SERVE"
FRANK_DRAGOON_VISION_TOWER=absent.hgn "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"vision-tower"*"missing"* ]]
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_LARGE_VISION_BYTES=42 "$SERVE"
FRANK_DRAGOON_VISION_BYTES=42 "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"expected 42"* ]]
}
@@ -244,7 +244,7 @@ good_env() {
good_env
printf 'MemTotal: 127108112 kB\nMemAvailable: 10000000 kB\n' > "$TEST_TMP/meminfo-short"
run env PATH="$TEST_TMP/bin:$PATH" "${GOOD_ENV[@]}" \
FRANK_LARGE_MEMINFO="$TEST_TMP/meminfo-short" FRANK_LARGE_RAM_WAIT=0 "$SERVE"
FRANK_DRAGOON_MEMINFO="$TEST_TMP/meminfo-short" FRANK_DRAGOON_RAM_WAIT=0 "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"pin gate"* ]]
[[ "$output" == *"refusing to start"* ]]
@@ -260,7 +260,7 @@ good_env() {
# lands there, so the render assertions read the same log.
output="$output
$(cat "$TEST_TMP/serve.log")"
[[ "$output" == *"--name frank-large-halogen"* ]]
[[ "$output" == *"--name frank-dragoon-halogen"* ]]
[[ "$output" == *"--publish 7770:8731"* ]]
[[ "$output" == *"-e HALOGEN_MODEL_ID=frank-dragoon"* ]]
[[ "$output" == *"-e HALOGEN_CTX=262144"* ]]
@@ -293,7 +293,7 @@ $(cat "$TEST_TMP/serve.log")"
# both monotone maxima of est + budget (est ≤ 217088 − 4096):
# full budget at the compaction threshold: 1.03*184524 + 45056
# clamped budget at the clamp's zero point: 1.03*212992 + 0
# and the compaction request that rides on frank-secondary (its
# and the compaction request that rides on frank-zealot (its
# 200,704-token slot) must fit: input ≈ threshold − keepRecent(20k).
grep -qE '^HALOGEN_CTX=262144$' "$ENV_FILE"
ctx=262144
@@ -317,41 +317,41 @@ $(cat "$TEST_TMP/serve.log")"
# Serve and deploy must name the SAME image digest, checkpoint and
# tokenizer: the 2026-10-01 crash-loop was exactly these twins
# disagreeing mid-cutover.
img="$(sed -nE 's/^FRANK_LARGE_IMAGE=(.*)$/\1/p' "$ENV_FILE")"
grep -qF "LLM_LARGE_IMAGE=\"\${LLM_LARGE_IMAGE:-$img}\"" "$DEPLOYER"
dir="$(sed -nE 's/^FRANK_LARGE_MODEL_DIR=(.*)$/\1/p' "$ENV_FILE")"
ckpt="$(sed -nE 's/^FRANK_LARGE_CHECKPOINT=(.*)$/\1/p' "$ENV_FILE")"
tokf="$(sed -nE 's/^FRANK_LARGE_TOKENIZER_FILES=\"(.*)\"$/\1/p' "$ENV_FILE")"
grep -qF "LLM_LARGE_TOK_DIR=\"\${LLM_LARGE_TOK_DIR:-$dir/tokenizer}\"" "$DEPLOYER"
grep -qF "LLM_LARGE_TOK_FILES=\"\${LLM_LARGE_TOK_FILES:-$tokf}\"" "$DEPLOYER"
bytes="$(sed -nE 's/^FRANK_LARGE_CHECKPOINT_BYTES=([0-9]+)$/\1/p' "$ENV_FILE")"
grep -qF "LLM_LARGE_CKPT_BYTES=\"\${LLM_LARGE_CKPT_BYTES:-$bytes}\"" "$DEPLOYER"
img="$(sed -nE 's/^FRANK_DRAGOON_IMAGE=(.*)$/\1/p' "$ENV_FILE")"
grep -qF "LLM_DRAGOON_IMAGE=\"\${LLM_DRAGOON_IMAGE:-$img}\"" "$DEPLOYER"
dir="$(sed -nE 's/^FRANK_DRAGOON_MODEL_DIR=(.*)$/\1/p' "$ENV_FILE")"
ckpt="$(sed -nE 's/^FRANK_DRAGOON_CHECKPOINT=(.*)$/\1/p' "$ENV_FILE")"
tokf="$(sed -nE 's/^FRANK_DRAGOON_TOKENIZER_FILES=\"(.*)\"$/\1/p' "$ENV_FILE")"
grep -qF "LLM_DRAGOON_TOK_DIR=\"\${LLM_DRAGOON_TOK_DIR:-$dir/tokenizer}\"" "$DEPLOYER"
grep -qF "LLM_DRAGOON_TOK_FILES=\"\${LLM_DRAGOON_TOK_FILES:-$tokf}\"" "$DEPLOYER"
bytes="$(sed -nE 's/^FRANK_DRAGOON_CHECKPOINT_BYTES=([0-9]+)$/\1/p' "$ENV_FILE")"
grep -qF "LLM_DRAGOON_CKPT_BYTES=\"\${LLM_DRAGOON_CKPT_BYTES:-$bytes}\"" "$DEPLOYER"
}
@test "the deployer points a missing artifact at the flashnext fetch script" {
grep -qE 'download-frank-large-flashnext\.sh' "$DEPLOYER"
grep -qE 'download-frank-dragoon-flashnext\.sh' "$DEPLOYER"
grep -qE 'podman pull' "$DEPLOYER"
}
@test "the stray sweep cannot take frank-secondary or the deployer down" {
# frank-secondary also listens on this host (7771, llama-server) and the
# deployer's own command line contains "frank-large"; only the FULL
@test "the stray sweep cannot take frank-zealot or the deployer down" {
# frank-zealot also listens on this host (7771, llama-server) and the
# deployer's own command line contains "frank-dragoon"; only the FULL
# container name matches a stray manual flash-server run and nothing
# else.
grep -q "pkill -f 'frank-large-halogen'" "$DEPLOYER"
grep -q "pkill -f 'frank-dragoon-halogen'" "$DEPLOYER"
! grep -qE "pkill -f 'llama-server'" "$DEPLOYER"
! grep -qE "pkill -f 'frank-large'" "$DEPLOYER"
! grep -qE "pkill -f 'frank-dragoon'" "$DEPLOYER"
}
@test "the deployed unit keeps the fleet's restart and boot-persistence shape" {
grep -qE '^Restart=(always|on-failure)$' "$UNIT"
grep -qE '^WantedBy=' "$UNIT"
grep -qE '^ExecStart=.*/serve-frank-large\.sh$' "$UNIT"
grep -qE '^ExecStart=.*/serve-frank-dragoon\.sh$' "$UNIT"
grep -qE '^User=dank$' "$UNIT"
grep -qE '^LimitMEMLOCK=infinity$' "$UNIT"
# The service's lifetime is the container's; stopping the service
# must stop the container by its real name.
grep -qE '^ExecStop=.*podman stop .* frank-large-halogen$' "$UNIT"
grep -qE '^ExecStop=.*podman stop .* frank-dragoon-halogen$' "$UNIT"
}
@test "the unit runs the flash-server runtime and no retired path" {
@@ -1,5 +1,5 @@
# Seam: the frank-secondary llama.cpp root unit on dankclaw.
# frank-secondary is the RTX 3090 endpoint; since 2026-10-07 it serves
# Seam: the frank-zealot llama.cpp root unit on dankclaw.
# frank-zealot is the RTX 3090 endpoint; since 2026-10-07 it serves
# HauhauCS's Qwen3.5-9B-Uncensored-HauhauCS-Aggressive (Q6_K, qwen35 hybrid:
# 32 layers, 8 full-attention + 24 Gated DeltaNet, no MTP tensors in the file)
# via the CUDA (sm_86) build of llama.cpp master: TWO 200,704-token
@@ -13,11 +13,11 @@
load helpers
UNIT="$REPO_ROOT/scripts/frank-secondary.service"
SERVE="$REPO_ROOT/scripts/serve-frank-secondary.sh"
ENV_FILE="$REPO_ROOT/scripts/frank-secondary.env"
UNIT="$REPO_ROOT/scripts/frank-zealot.service"
SERVE="$REPO_ROOT/scripts/serve-frank-zealot.sh"
ENV_FILE="$REPO_ROOT/scripts/frank-zealot.env"
REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
ENV_LARGE="$REPO_ROOT/scripts/frank-large.env"
ENV_LARGE="$REPO_ROOT/scripts/frank-dragoon.env"
setup() {
STAGE="$(mktemp -d)"
@@ -64,7 +64,7 @@ SPEC_DRAFT_N_MAX=$(sed -nE 's/^SPEC_DRAFT_N_MAX=([0-9]+)$/\1/p' "$ENV_FILE")
VERBOSITY=$(sed -nE 's/^VERBOSITY=([0-9]+)$/\1/p' "$ENV_FILE")
NGL=$(sed -nE 's/^NGL=([0-9]+)$/\1/p' "$ENV_FILE")
EOF
run env FRANK_SECONDARY_ENV_FILE="$envf" nvidia-modprobe=true bash "$SERVE"
run env FRANK_ZEALOT_ENV_FILE="$envf" nvidia-modprobe=true bash "$SERVE"
}
argv_value() {
@@ -76,23 +76,23 @@ argv_value() {
# append a drafter) must exec through this, not serve_argv — which silently
# undoes the mutation and makes the assertion a tautology.
run_stage() {
run env FRANK_SECONDARY_ENV_FILE="$STAGE/test.env" nvidia-modprobe=true bash "$SERVE"
run env FRANK_ZEALOT_ENV_FILE="$STAGE/test.env" nvidia-modprobe=true bash "$SERVE"
}
@test "frank-secondary unit, serve script and env exist in scripts/" {
@test "frank-zealot unit, serve script and env exist in scripts/" {
[ -f "$UNIT" ]
[ -f "$SERVE" ]
[ -f "$ENV_FILE" ]
}
@test "frank-secondary is boot-persistent, self-healing and unprivileged" {
@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"
grep -q '^User=dank$' "$UNIT"
grep -q '^LimitMEMLOCK=infinity$' "$UNIT"
grep -q '^ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-secondary.sh$' "$UNIT"
grep -q 'FRANK_SECONDARY_ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-secondary.env' "$UNIT"
grep -q '^ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-zealot.sh$' "$UNIT"
grep -q 'FRANK_ZEALOT_ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-zealot.env' "$UNIT"
}
@test "the shipped env points at the llama.cpp runtime and the Qwen3.5-9B Q6_K GGUF" {
@@ -107,7 +107,7 @@ run_stage() {
# would be gating on a file nobody reads — and a missing projector would
# ship a blind endpoint.
local env_v dep_v path pair
for pair in "MODEL_FILE LLM_SECONDARY_MODEL_FILE" "MMPROJ_FILE LLM_SECONDARY_MMPROJ"; do
for pair in "MODEL_FILE LLM_ZEALOT_MODEL_FILE" "MMPROJ_FILE LLM_ZEALOT_MMPROJ"; do
env_v="${pair%% *}"
dep_v="${pair##* }"
path="$(sed -nE "s/^${env_v}=(.+)$/\1/p" "$ENV_FILE")"
@@ -236,7 +236,7 @@ run_stage() {
# The documented opt-out must reach the engine as ABSENCE of the flag,
# not as an argument pointing at an empty string.
sed -i 's|^MMPROJ_FILE=.*|MMPROJ_FILE=|' "$STAGE/test.env"
run env FRANK_SECONDARY_ENV_FILE="$STAGE/test.env" nvidia-modprobe=true bash "$SERVE"
run env FRANK_ZEALOT_ENV_FILE="$STAGE/test.env" nvidia-modprobe=true bash "$SERVE"
[ "$status" -eq 0 ]
! grep -qxF -- '--mmproj' <<<"$output"
}
@@ -256,30 +256,30 @@ run_stage() {
rm -f "$MODEL_DIR/$MM_STUB"
serve_argv
[ "$status" -ne 0 ]
[[ "$output" == *"frank-secondary: missing"* ]]
[[ "$output" == *"frank-zealot: missing"* ]]
rm -f "$RUNTIME_DIR/bin/llama-server"
serve_argv
[ "$status" -ne 0 ]
[[ "$output" == *"frank-secondary: missing"* ]]
[[ "$output" == *"frank-zealot: missing"* ]]
}
@test "the deploy handler gates on binary, GGUF and projector, then MainPID-hardens health" {
grep -q 'deploy_llm_secondary' "$REMOTE_DEPLOY"
grep -q 'LLM_SECONDARY_RUNTIME' "$REMOTE_DEPLOY"
grep -q 'LLM_SECONDARY_MODEL_FILE' "$REMOTE_DEPLOY"
grep -q 'LLM_SECONDARY_MMPROJ' "$REMOTE_DEPLOY"
grep -q 'relink_unit frank-secondary' "$REMOTE_DEPLOY"
grep -q 'systemctl enable frank-secondary.service' "$REMOTE_DEPLOY"
grep -q 'deploy_llm_zealot' "$REMOTE_DEPLOY"
grep -q 'LLM_ZEALOT_RUNTIME' "$REMOTE_DEPLOY"
grep -q 'LLM_ZEALOT_MODEL_FILE' "$REMOTE_DEPLOY"
grep -q 'LLM_ZEALOT_MMPROJ' "$REMOTE_DEPLOY"
grep -q 'relink_unit frank-zealot' "$REMOTE_DEPLOY"
grep -q 'systemctl enable frank-zealot.service' "$REMOTE_DEPLOY"
grep -q '127.0.0.1:7771/health' "$REMOTE_DEPLOY"
grep -q 'frank-secondary.service has no MainPID' "$REMOTE_DEPLOY"
grep -q 'frank-zealot.service has no MainPID' "$REMOTE_DEPLOY"
}
@test "the two endpoints keep separate ports and the large handler never pkills llama-server" {
# frank-large's sweeps must not take down frank-secondary, whose
# frank-dragoon's sweeps must not take down frank-zealot, whose
# process is llama-server on the SAME host.
grep -q '^LISTEN_PORT=7771$' "$ENV_FILE"
local large
large="$(sed -n '/^deploy_llm_large() {/,/^}/p' "$REMOTE_DEPLOY")"
large="$(sed -n '/^deploy_llm_dragoon() {/,/^}/p' "$REMOTE_DEPLOY")"
! grep -q 'pkill -x llama-server' <<<"$large"
}
+1 -1
View File
@@ -97,7 +97,7 @@ webui_service_yaml() {
[ "$status" -eq 0 ]
[ "$output" = "hermes" ]
# And an unrelated service must not be dragged in.
! grep -qE 'caddy|prometheus|frank-large' <<< "$output"
! grep -qE 'caddy|prometheus|frank-dragoon' <<< "$output"
}
@test "remote deployer accepts and dispatches hermes-webui" {
+9 -9
View File
@@ -316,7 +316,7 @@ PAIRS
@test "prometheus scrapes every LLM endpoint that exports metrics" {
# The LLM endpoints are the services whose behaviour this fleet argues about
# most (KV pressure, prefix-cache reuse, queue depth). Both Dankclaw
# endpoints export the llamacpp:* family. frank-primary LEFT the set
# endpoints export the llamacpp:* family. frank-archon LEFT the set
# 2026-10-05: the FreeToken PR587 ROCm engine has NO /metrics endpoint —
# scraping it was a structural never-green (404), so the job was deleted
# rather than left vacuous (the Lucebox DS4.1 precedent, 2026-09-26).
@@ -325,18 +325,18 @@ PAIRS
# container, and on this netavark host a container can only reach the
# host's LAN IP at a PUBLISHED port — a host-process bind like
# llama-server's 0.0.0.0:7770 is refused at 192.168.0.143:7770 while it
# answers at 169.254.1.2:7770 (why frank-secondary stayed red by construction
# answers at 169.254.1.2:7770 (why frank-zealot stayed red by construction
# for days until the 2026-09-20 /targets audit).
local cfg="$REPO_ROOT/shared/prometheus/prometheus.yml"
grep -q '"host.containers.internal:7771"' "$cfg" || { echo "frank-secondary not scraped"; return 1; }
grep -q '"host.containers.internal:7770"' "$cfg" || { echo "frank-large not scraped"; return 1; }
grep -q '"host.containers.internal:7771"' "$cfg" || { echo "frank-zealot not scraped"; return 1; }
grep -q '"host.containers.internal:7770"' "$cfg" || { echo "frank-dragoon not scraped"; return 1; }
! grep -q '"192.168.0.235:7770"' "$cfg"
grep -q '"host.containers.internal:7770,192.168.0.235:7770"' "$cfg" \
&& { echo "frank-primary folded into a Dankclaw gateway target"; return 1; }
for job in frank-secondary frank-large; do
&& { echo "frank-archon folded into a Dankclaw gateway target"; return 1; }
for job in frank-zealot frank-dragoon; do
grep -q "job_name: \"$job\"" "$cfg" || { echo "no scrape job for $job"; return 1; }
done
! grep -q 'job_name: "frank-primary"' "$cfg"
! grep -q 'job_name: "frank-archon"' "$cfg"
# None of them is reached through a guarded vhost: Prometheus cannot solve
# an Anubis challenge, so a franksdank.cloud target is red by construction.
! grep -qE '^\s*-\s*"?https?://[^"]*franksdank' "$cfg"
@@ -383,11 +383,11 @@ for want in ('job:llm_prefix_cache_reuse:rate5m',
# Every rule must be keyed to a series that actually exists. A rule naming a
# series nobody exports evaluates nothing forever and errors never — the
# failure this seam exists for. Since the 2026-09-26 cutover the only scraped
# LLM runtime is llama.cpp (frank-secondary, frank-large); the vllm: family left
# LLM runtime is llama.cpp (frank-zealot, frank-dragoon); the vllm: family left
# with frank-medium's radiance runtime and must not survive in any expr.
exprs = [r.get('expr', '') for g in d['groups'] for r in g['rules']]
# llama.cpp families measured from /metrics on 2026-09-16 and 2026-09-20
# (frank-secondary, frank-large). Anything else is a guess.
# (frank-zealot, frank-dragoon). Anything else is a guess.
live = {'llamacpp:prompt_tokens_total', 'llamacpp:prompt_tokens_cached_total',
'llamacpp:requests_deferred', 'llamacpp:requests_processing',
'llamacpp:spec_decode_num_drafts_total',
+2 -2
View File
@@ -4,7 +4,7 @@
# The old open-webui was a bare container on dankserver (port 3000) pointing at a
# dead LLM backend (:9090). This unit stands one up on dankclaw on a free port
# (3005), on the CPU image (dankclaw is CPU-only), wired to the live
# llama.cpp backend (192.168.0.143:7770, model frank-large).
# llama.cpp backend (192.168.0.143:7770, model frank-dragoon).
load helpers
@@ -63,7 +63,7 @@ QDIR="$REPO_ROOT/dankclaw/open-webui/quadlet"
# gateway (10.89.3.1) lives in the rootless netns, NOT the host netns
# where llama-server runs -- it refuses. The host's LAN IP reaches it
# through the rootless NAT (verified from the container, 2026-08-22).
# 2026-08-22: frank-large moved from :52625 to :7770.
# 2026-08-22: frank-dragoon moved from :52625 to :7770.
grep -qE '^Environment=OPENAI_API_BASE_URL=http://192\.168\.0\.143:7770' \
"$QDIR/open-webui.container" \
|| { echo "open-webui.container must set OPENAI_API_BASE_URL to the LLM at 192.168.0.143:7770"; return 1; }